- /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
82 lines
4.8 KiB
Markdown
82 lines
4.8 KiB
Markdown
# 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
|
||
|
||
```sh
|
||
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:
|
||
|
||
```sh
|
||
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`](../../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-testid`s 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).
|