diff --git a/tests/e2e/fake_gitea.py b/tests/e2e/fake_gitea.py index 1efd791..9b6e5f6 100644 --- a/tests/e2e/fake_gitea.py +++ b/tests/e2e/fake_gitea.py @@ -4,6 +4,7 @@ from __future__ import annotations import json from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from threading import Event from urllib.parse import parse_qs, urlsplit @@ -51,6 +52,7 @@ class FakeGiteaServer(ThreadingHTTPServer): super().__init__(address, FakeGiteaHandler) self.created_issues: list[dict] = [] self.issue_creation_enabled = False + self.issue_creation_ready = Event() self.assigned_issue_numbers = [issue["number"] for issue in AVAILABLE_ISSUES] self.comments: list[tuple[int, str]] = [] self.requests: list[tuple[str, str]] = [] @@ -147,6 +149,8 @@ class FakeGiteaHandler(BaseHTTPRequestHandler): if path != "/api/v1/repos/acme/mobile/issues": self._json(404, {"message": "not found"}) return + if not self.server.issue_creation_enabled: + self.server.issue_creation_ready.wait(timeout=45) if not self.server.issue_creation_enabled: self._json(503, {"message": "release journey is still offline"}) return diff --git a/tests/e2e/test_mobile_offline_issue_release.py b/tests/e2e/test_mobile_offline_issue_release.py index 2ad2bbd..5c18145 100644 --- a/tests/e2e/test_mobile_offline_issue_release.py +++ b/tests/e2e/test_mobile_offline_issue_release.py @@ -232,6 +232,10 @@ def test_release_artifact_files_one_mobile_issue_exactly_once_after_offline_relo page.locator("#create-issue-repository").select_option("acme/mobile") expect(page.locator("#submit-new-issue")).to_be_enabled() + # Page offline emulation can leave service-worker requests connected. + # Abort the mutation at the browser boundary too, matching a real + # transport outage without poisoning server-side idempotency state. + context.route("**/api/v1/repos/acme/mobile/issues", lambda route: route.abort()) context.set_offline(True) page.locator("#submit-new-issue").click() expect(page.locator("#issue-filing-review")).to_be_visible() @@ -267,6 +271,8 @@ def test_release_artifact_files_one_mobile_issue_exactly_once_after_offline_relo browser_errors.clear() # Chromium reports expected network errors while the context is offline. failed_responses.clear() fake.issue_creation_enabled = True + fake.issue_creation_ready.set() + context.unroute("**/api/v1/repos/acme/mobile/issues") context.set_offline(False) page.evaluate("window.dispatchEvent(new Event('online'))") # Containerized Actions runners can take longer than a local browser to wake the @@ -275,12 +281,15 @@ def test_release_artifact_files_one_mobile_issue_exactly_once_after_offline_relo if fake.created_issues: break page.wait_for_timeout(250) + local_debug = page.evaluate("localStorage.getItem('stackchain.issue-outbox.v1')") + durable_debug = indexed_issue_records(page) assert fake.created_issues == [ {"title": TITLE, "body": BODY, "assignee": "timmy"} ], ( f"url={page.url} ready={page.evaluate('document.readyState')} " f"status={page.locator('#my-work-action-status').inner_text()!r} " f"errors={browser_errors[-5:]!r} responses={failed_responses[-10:]!r} " + f"local={local_debug!r} durable={durable_debug!r} " f"requests={fake.requests[-20:]!r}" ) # Deferred workspace startup can report its already-issued offline request @@ -310,6 +319,7 @@ def test_release_artifact_files_one_mobile_issue_exactly_once_after_offline_relo assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth") browser.close() finally: + fake.issue_creation_ready.set() fake.shutdown() fake.server_close() fake_thread.join(timeout=5)