fix(ci): fix flaky e2e editor tests caused by premature webServer readiness (#5116)

## Linked issue (required)

Fixes #5108

## Summary / motivation (required)

`/favicon.ico` responds as soon as mediasrv's HTTP thread starts, before
the profile's collection finishes loading, `setupMediaServer()` runs
synchronously in `AnkiQt.__init__`, while `setupProfile()` is deferred
via a `QTimer`. Playwright's `webServer.url` readiness check polled
`/favicon.ico`, so the first e2e test to run in a given CI run could
race the async profile load and time out waiting for `.note-editor`, an
intermittent false negative, not a real editor bug.

This adds `GET /_anki/readyz`, which only returns 200 once `aqt.mw.col`
is open (503 otherwise — Playwright's webServer keeps polling on
non-2xx/3xx responses), and points `playwright.config.ts`'s
`webServer.url` at it instead of `/favicon.ico`.

## Steps to reproduce (required, use N/A if not applicable)

1. Run `just test-e2e` repeatedly on a loaded machine (or add artificial
delay to profile loading).
2. Occasionally, the first test in `context-switching.spec.ts` times out
at `fixtures.ts:37` (`waitForSelector(".note-editor")`) because
Playwright started the test run before the collection had finished
opening.
3. See CI failure:
https://github.com/ankitects/anki/actions/runs/28748635969/job/85243844164

## How to test (required)

- Ran `just test-e2e` locally and confirmed the suite passes.
- Verified `/_anki/readyz` returns 503 immediately after mediasrv starts
and 200 once the profile/collection finishes loading, by curling it
during `qt/tests/launch_anki_for_e2e.py` startup.
- No new test was added — this is a fix to test *infrastructure* (the
readiness gate), not to editor behavior; existing e2e specs are the
regression coverage.

## Before / after behavior (optional)

Before: e2e run could fail intermittently on the first test with a
`.note-editor` timeout, unrelated to the change under test.
After: `webServer` only accepts traffic once the collection is open, so
no test races the profile-load window.

## Risk / compatibility / migration (optional)

Low risk. `/_anki/readyz` is a new, isolated, read-only route used only
by the e2e harness; no existing behavior changes.
This commit is contained in:
Fernando Lins
2026-07-07 10:20:43 +03:00
committed by GitHub
parent 257ea825b8
commit a8ba664e99
2 changed files with 21 additions and 1 deletions
+5 -1
View File
@@ -26,7 +26,11 @@ export default defineConfig({
},
webServer: {
command: `${PYENV_PYTHON} qt/tests/launch_anki_for_e2e.py`,
url: `http://127.0.0.1:${MEDIASRV_PORT}/favicon.ico`,
// /favicon.ico responds as soon as the HTTP server thread starts,
// before the profile's collection has finished loading. /_anki/readyz
// only returns 200 once the collection is open, so tests never race
// against the async profile-load that follows server startup.
url: `http://127.0.0.1:${MEDIASRV_PORT}/_anki/readyz`,
timeout: 60_000,
reuseExistingServer: process.env.ANKI_E2E_REUSE_SERVER === "1",
stdout: "pipe",
+16
View File
@@ -233,6 +233,22 @@ def favicon() -> Response:
return _handle_builtin_file_request(request)
@app.route("/_anki/readyz")
def readyz() -> Response:
"""Reports whether the profile's collection is open.
The HTTP server starts listening before the profile/collection finishes
loading (setupMediaServer() runs synchronously in AnkiQt.__init__, while
setupProfile() is deferred via a QTimer), so callers that need the
collection open - e.g. the e2e test harness's webServer readiness check -
should poll this instead of /favicon.ico, which responds regardless of
collection state.
"""
if aqt.mw.col is None:
return Response(status=HTTPStatus.SERVICE_UNAVAILABLE)
return Response(status=HTTPStatus.OK)
def _mime_for_path(path: str) -> str:
"Mime type for provided path/filename."