Files
siftlode/frontend/e2e/global.setup.ts
T
peter 5858e7b513 test(e2e): run the net on WebKit and an iPad profile; opt the account out of Home
The Playwright net now runs every flow in three projects: chromium (Desktop
Chrome), webkit (Desktop Safari) and ipad (iPad gen 7 landscape: touch, mobile
viewport). WebKit catches the engine-level differences every iOS browser
inherits; the config and the README say plainly what it does NOT catch: iPad
media refusals. Playwright's WebKit decodes through the host OS, and on Windows
it played an mp3-in-mp4 and an AV1 mp4 that a real iPad refuses, so media
formats are checked on a real iPad against the dev stack over the LAN.

global.setup opts the e2e account out of starting on Home (the Startup setting),
which the feed, channel and player specs need since E24. It only takes effect
with the new-tab boot fix: before it, all twelve of those specs landed on Home
in every project. 49/49 green on :8080 across the three projects.
2026-09-14 22:29:33 +02:00

85 lines
3.7 KiB
TypeScript

import { test as setup, expect } from "@playwright/test";
import { execFileSync } from "node:child_process";
import { mkdirSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
/**
* One-time setup for the characterization net (the `setup` project the tests depend on):
* 1. preflight — the localdev :8080 stack must be up (owned by `siftlode dev`);
* 2. seed — reset the dedicated E2E account to a known baseline, in-container (idempotent);
* 3. authenticate ONCE via password-login and persist the session + `en` locale to
* storageState, so the real specs never re-auth (auth endpoints are rate-limited).
*/
const STORAGE = "e2e/.auth/state.json";
const EMAIL = process.env.E2E_EMAIL || "e2e@siftlode.local";
const PASSWORD = process.env.E2E_PASSWORD || "e2e-golden-master";
const CONTAINER = process.env.E2E_API_CONTAINER || "siftlode-api-1";
const BASE_URL = process.env.PW_BASE_URL || "http://127.0.0.1:8080";
const HERE = dirname(fileURLToPath(import.meta.url));
const SEED_SCRIPT = resolve(HERE, "../../scripts/e2e_seed.py");
// The in-container seed only makes sense when the target is the local stack.
const isLocalTarget = /\/\/(127\.0\.0\.1|localhost)[:/]/.test(BASE_URL);
setup("seed + authenticate", async ({ page }) => {
// 1. Preflight: fail fast (with a useful message) if the stack isn't running.
const health = await page.request.get("/healthz");
expect(
health.ok(),
`localdev stack not reachable at ${BASE_URL} — start it with \`siftlode dev\``,
).toBeTruthy();
// 2. Idempotent seed inside the api container (skippable / local-only).
if (!process.env.E2E_SKIP_SEED && isLocalTarget) {
try {
const out = execFileSync(
"docker",
[
"exec",
"-i",
"-e",
`E2E_EMAIL=${EMAIL}`,
"-e",
`E2E_PASSWORD=${PASSWORD}`,
CONTAINER,
"python",
"-",
],
{ input: readFileSync(SEED_SCRIPT) },
);
console.log(`[e2e seed] ${out.toString().trim()}`);
} catch (err: unknown) {
const e = err as { stderr?: Buffer; message?: string };
throw new Error(`E2E seed failed: ${e.stderr?.toString() || e.message}`);
}
}
// 3. Authenticate. page.request shares the browser context's cookie jar, so the `session`
// cookie it receives is captured by storageState below.
const login = await page.request.post("/auth/password-login", {
data: { email: EMAIL, password: PASSWORD },
});
expect(login.ok(), "password-login failed — is the E2E account seeded?").toBeTruthy();
const me = await (await page.request.get("/api/me")).json();
expect(me.authenticated, "session not established after login").toBeTruthy();
// 4. Pin the UI locale, suppress the first-login onboarding wizard (a full-screen overlay
// that would intercept every click — the test account has no YouTube connection, so it
// auto-opens), and opt the account out of starting on Home. Since E24 a bare URL lands on
// Home, and the feed, channel and player flows this net locks all open "/" expecting the
// feed; the opt-out is the one Settings → Appearance → Startup writes. Both flags are
// per-account: `siftlode.onboarding.dismissed.<id>`, `siftlode.startOnHome.<id>`.
await page.goto("/");
await page.evaluate((id: number) => {
localStorage.setItem("siftlode.lang", "en");
localStorage.setItem(`siftlode.onboarding.dismissed.${id}`, "1");
localStorage.setItem(`siftlode.startOnHome.${id}`, "0");
}, me.id);
mkdirSync(dirname(STORAGE), { recursive: true });
await page.context().storageState({ path: STORAGE });
});