# 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). 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://:8080`, e.g. a login-free `/watch/` 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`. Credentials come from `E2E_EMAIL` (default `e2e@siftlode.local`) and `E2E_PASSWORD` — **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).