Files
siftlode/frontend/e2e
peter 3553eac8c4 feat(settings): upload a Google Takeout export in Settings › Account
A "YouTube history" section under YouTube access: the watch history and
the Watch later csv are dropped or picked (one request each, each file's
outcome said on its own), and the section then says what the import did
- only the counts it produced - or that it is running, failed or was cut
off by a restart. While it runs it asks again until it ends; a failed
re-read shows the failure, not the previous answer.

The drop zone the Files upload had inline is now ui/DropZone, used by
both. The e2e seed clears the account's kept Takeout videos.
2026-10-23 18:51:05 +02:00
..

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).