Files
siftlode/frontend/e2e/README.md
T
peter ef99f7b5c7 fix(auth): a Google connect names its tab's account too, and a refused tab says why
- /auth/google/connect is a navigation and carries no headers: the tab names the account it
  was built for in `expect`, checked by the same comparison as the request header
  (built_for_another_account); a mismatch lands on /?account=changed
- the link marker names the OAuth state of its own flow: an older flow coming back no longer
  spends the marker of the connect still open (which then returned as a plain sign-in)
- a 401 account_changed reloads the tab with a one-time flag, and the reloaded tab says it
  now shows the account signed in
- a refused switch to a suspended or not-yet-approved account says so in the signed-in
  app's words, naming the address a request was recorded for
- the prefs merge locks FOR NO KEY UPDATE, so a transaction that only added a row referring
  to the account does not hold the save up; a test pins the lock
- giving up a lost page closes a channel page open over it
- e2e README names the chromium-only specs
2026-10-21 02:05:33 +02:00

4.8 KiB
Raw Permalink Blame History

E2E characterization (golden-master) net

Playwright tests that lock the app's five core flows — feed (scroll + filter), channel (open → back), player (open/close), settings (dirty-save + guard), and page switching — so the Platform refactor can prove it changed nothing. They assert current, correct behavior; they deliberately do not encode the known Platform bugs (those get their own specs when the sprints that fix them land).

Run

siftlode e2e            # against the baked localdev :8080 stack (authoritative)
siftlode e2e-ui         # same, Playwright UI mode
siftlode e2e-seed       # (re)seed the test account only, no tests

The localdev stack must be up (siftlode dev). global.setup.ts reseeds the account and logs in once, sharing the session via e2e/.auth/state.json (git-ignored).

A dropped localhost connect — on a Windows host, a burst of test traffic can drop a loopback connect now and then (every port, any program). The browser resends it and only waits (a loader outlives an expect timeout); Node does not, so a request from the test side fails with connect ETIMEDOUT. Those requests go through retryConnect (e2e/retryConnect.ts), which tries that one error again and passes every other failure straight on. Don't run two e2e suites on the same machine at once.

Browsers — every flow runs in three projects: chromium (Desktop Chrome), webkit (Desktop Safari) and ipad (iPad gen 7 landscape: 1080×810, touch, mobile) — except a11y, member and devices, which run on chromium only (CHROMIUM_ONLY; why is in the docs/gates.md row). Install the engines once per machine with npx playwright install chromium webkit; run one project with npx playwright test --project=ipad. WebKit catches what every iOS browser inherits from the engine (JS, CSS/layout, event order). It does not reproduce iPad media refusals: Playwright's WebKit decodes through the host OS (Media Foundation on Windows, GStreamer on Linux), and measured on 2026-09-14 it played an mp3-in-mp4 and an AV1 mp4 that a real iPad refuses. Check a media format on a real iPad against the dev stack over the LAN (http://<desktop LAN IP>:8080, e.g. a login-free /watch/<token> share link).

Target — PW_BASE_URL (default http://127.0.0.1:8080):

  • :8080 (baked SPA) is authoritative and same-origin. After a frontend change you must siftlode dev (rebuild) for its baked SPA to carry the latest test hooks.

  • :5173 (Vite dev) serves source live — the fast refactor loop; it proxies /api to :8080, so the stack must still be up. All five flows are proxy-safe:

    PW_BASE_URL=http://localhost:5173 siftlode e2e
    

Accessibility — a11y.spec.ts runs axe-core's WCAG 2.x A + AA rules (@axe-core/playwright) over every rail page, every admin page and the player, in chromium only (the rules read the DOM, which the engines build alike). It does not judge colour contrast: axe files text on a gradient — every glass surface — as "incomplete", not as a pass or a hit.

Account & fixture

Tests run as a dedicated admin account e2e@siftlode.local (never a real account; admin so the suite can reach the admin-only pages / render branches, matching the real primary user), reset to a deterministic baseline every run by scripts/e2e_seed.py (run in-container so it reuses the app's models + argon2 hasher): the top-3 organic channels subscribed, one in-progress + one watched + one saved video, prefs pinned to language: en. A second account, the plain member e2e-member@siftlode.local, gets the same baseline and its own session (e2e/.auth/member.json). Two specs run as it: member.spec.ts, holding what a non-admin sees (no admin entry, no admin call, no failed request, an admin link lands on the feed), and devices.spec.ts, which signs it in on a desktop and an iPad at once and changes its channel layout on the server (put back in a finally) — a spec that reads the member's server prefs must not run beside it. Credentials come from E2E_EMAIL (default e2e@siftlode.local), E2E_MEMBER_EMAIL (default e2e-member@siftlode.local) and E2E_PASSWORD (both accounts) — required, no default: run the suite through the ops wrapper, which exports it from the secret store. Override the container name with E2E_API_CONTAINER; skip the in-container seed with E2E_SKIP_SEED=1.

Selectors

The app has no data-testids of its own and all copy is i18n (en/hu), so the specs key on a thin data-testid layer (added with this sprint) plus the locale pinned to en. Stable ARIA hooks that already existed are used directly (role="dialog" on the player, aria-current="page" on the active nav row, aria-pressed on the feed toggles).

Typecheck

npx tsc -p e2e/tsconfig.json --noEmit (the specs are outside the app's src tsconfig).