On a Windows host a burst of test traffic can drop a loopback connect now and then (every port, any program — measured on the dev desktop). The browser resends it and only waits; Node (libuv) sends a loopback SYN once and fails at ~300 ms with "connect ETIMEDOUT". `e2e/retryConnect.ts` wraps Playwright's request context so that one error is tried again (0.5 / 1 / 2 / 4 / 8 s, the system's own retry waits) while every other failure goes straight to the test; global.setup's health check, login and /api/me and speed.measure's share probe go through it. Unit-tested; e2e 61/61.
70 lines
3.8 KiB
Markdown
70 lines
3.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). 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
|
||
```
|
||
|
||
## 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).
|