Second gate round over the full branch surfaced 4 verified findings (1 was a
regression the round-1 role change introduced); a 5th (--sbw load jump) was
dropped as refuted (the header only mounts after auth+data, so --sbw is set
before it ever paints).
- e2e (medium): the round-1 switch of FilterSelect rows from role=option to
menuitemcheckbox moved their state from aria-selected to aria-checked, but
the Playwright helper expectContentType still asserted aria-selected, so the
channel specs would fail whenever the e2e lane runs (not part of the fast
gate, so it stayed green). Assert aria-checked.
- a11y (low): the FilterSelect and SortControl pill triggers set
aria-label={label}, which overrides the visible "Label: value" text, so a
screen reader announced only the group name, never the current selection.
Carry the value (and, for sort, the direction) in the accessible name,
matching ViewSwitcher. Updated the unit-test trigger name queries to match.
- tests (low): cover the PageShell font-scale MutationObserver recompute path
(a post-mount root font-size change → --rail-pinned-w re-computes once, no
loop) — previously only the pre-render and resize paths were exercised.
Verified live: the triggers now announce e.g. "Sort: Date, Descending" /
"Watch state: All". Gate green: tsc (app+node+e2e), 373 vitest, prettier,
eslint (0 err).
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).
Target — PW_BASE_URL (default http://127.0.0.1:8080):
-
:8080(baked SPA) is authoritative and same-origin. After a frontend change you mustsiftlode dev(rebuild) for its baked SPA to carry the latest test hooks. -
:5173(Vite dev) serves source live — the fast refactor loop; it proxies/apito:8080, so the stack must still be up. All five flows are proxy-safe (none use the WebSocket):PW_BASE_URL=http://localhost:5173 siftlode e2e
Account & fixture
Tests run as a dedicated, non-demo 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. Credentials come
from E2E_EMAIL / E2E_PASSWORD (local-only defaults). 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).