Files
siftlode/frontend/playwright.config.ts
T
peter dc4f4f5143 test(e2e): a plain member's shell is held end to end, and a page it cannot open lands on the feed
The suite ran only as an admin, so nothing end to end held what a member sees. A second seeded
account, a member with its own session, now walks every rail page (no admin entry, no admin call,
no error state) and follows links to the admin pages.

It found one: a member's link to an admin page showed the feed with no rail row current, and the
address bar kept naming the admin page — only the content fell back. The navigation state now
derives the page on screen through each module's gate (reachablePage), so the rail, the content,
the tab title and the address bar agree, on a link, a Back, and a role taken away mid-session.
2026-10-20 04:08:11 +02:00

84 lines
3.3 KiB
TypeScript

import { defineConfig, devices } from "@playwright/test";
/**
* E2E characterization (golden-master) net for the Platform refactor.
*
* Target (PW_BASE_URL): defaults to the baked localdev stack on :8080 (production-faithful,
* same-origin, no HMR gaps). Point it at http://localhost:5173 for the fast Vite loop — all
* five characterization flows are proxy-safe.
*
* The stack must already be UP (owned by `siftlode dev`); there is deliberately no `webServer`.
* `e2e/global.setup.ts` seeds the dedicated test account and logs in once, sharing the resulting
* session via storageState so the real tests never re-authenticate (auth endpoints are rate-limited).
*
* Runs serial / single-worker on purpose: the specs share ONE seeded account, and a couple of
* them mutate server state (Settings save), so determinism beats parallelism here.
*/
const BASE_URL = process.env.PW_BASE_URL || "http://127.0.0.1:8080";
const STORAGE_STATE = "e2e/.auth/state.json";
// The axe scan reads the DOM and the accessibility tree, which the engines build alike, and the
// member spec checks which pages and requests a role gets, which no engine changes: one engine
// answers for all, so the other projects leave them out instead of tripling their time.
const CHROMIUM_ONLY = /(a11y|member)\.spec\.ts/;
export default defineConfig({
testDir: "./e2e",
fullyParallel: false,
workers: 1,
forbidOnly: !!process.env.CI,
retries: 0,
reporter: [["list"], ["html", { open: "never" }]],
timeout: 30_000,
expect: { timeout: 7_000 },
use: {
baseURL: BASE_URL,
storageState: STORAGE_STATE,
locale: "en-US",
// retain-on-failure (not on-first-retry): with retries:0 a "first retry" never happens, so a
// trace would never be captured — this keeps a full trace for any failure, discarded on success.
trace: "retain-on-failure",
screenshot: "only-on-failure",
},
projects: [
{
name: "setup",
testMatch: /global\.setup\.ts/,
use: { storageState: { cookies: [], origins: [] } },
},
{
name: "chromium",
use: { ...devices["Desktop Chrome"], storageState: STORAGE_STATE },
dependencies: ["setup"],
},
// WebKit catches engine-level differences (JS, CSS/layout, event order) that every iOS browser
// inherits. 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 iPad refuses. Media checks need a real iPad.
{
name: "webkit",
use: { ...devices["Desktop Safari"], storageState: STORAGE_STATE },
testIgnore: CHROMIUM_ONLY,
dependencies: ["setup"],
},
{
name: "ipad",
use: { ...devices["iPad (gen 7) landscape"], storageState: STORAGE_STATE },
testIgnore: CHROMIUM_ONLY,
dependencies: ["setup"],
},
// The speed baseline (e2e/speed.measure.ts): readings, not tests. Only when PW_SPEED is set —
// `scripts/audit_metrics.py --speed` sets it — so the e2e suite never runs it.
...(process.env.PW_SPEED
? [
{
name: "speed",
testMatch: /\.measure\.ts/,
use: { ...devices["Desktop Chrome"], storageState: STORAGE_STATE },
dependencies: ["setup"],
},
]
: []),
],
});