Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/web/e2e/discovery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,12 @@ test("profile: the e-mail-notifications toggle round-trips, and the public profi
const emailSwitch = page.getByRole("switch", { name: "E-mail notifications" });
const before = await emailSwitch.getAttribute("aria-checked");
const after = before === "true" ? "false" : "true";
// The switch flips optimistically, before its PATCH lands. Reloading before the server has
// answered aborts the request (logged as "client disconnected") and the reload then reads the
// old value — so hold the PATCH's response and reload only once it has been committed.
const saved = page.waitForResponse((r) => r.url().endsWith("/api/profile") && r.request().method() === "PATCH");
await emailSwitch.click();
expect((await saved).status()).toBe(200);
await expect(emailSwitch).toHaveAttribute("aria-checked", after);
// The page carries one switch per notification preference (§12.1) — read the word that sits
// beside THIS switch, inside its own .toggle-field.
Expand Down
43 changes: 26 additions & 17 deletions packages/web/e2e/helpers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,34 @@ export async function signIn(
// if that chain breaks — e.g. a page loaded while Next compiles /_error mid-run (it does so on
// the first aborted request) never hydrates — so a load showing no panel within 20 s is
// retried with a fresh navigation instead of being waited on until the test times out.
//
// The whole sign-in is retried, not just the panel load: a dev server that stalls (Turbopack
// cache compaction, a heap-pressure pause) can hold the callback or the post-sign-in render
// past any single wait, and the attempt that follows must not leave the test stuck.
const menu = page.locator(".user-trigger");
const panel = page.locator("#dev-name");
await expect(async () => {
await page.goto(`${base}/`);
await page.locator("#dev-name").waitFor({ state: "visible", timeout: 20_000 });
}).toPass({ timeout: 75_000 });
await page.locator("#dev-name").fill(opts.name);
await page.locator("#dev-email").fill(`${devSlug(opts.name)}@dev.test`);
await page.locator("#dev-admin").fill(opts.admin ? "1" : "0");
await page.locator("#dev-fresh-onboarding").fill(opts.freshOnboarding ? "1" : "0");
await page.locator("#dev-whats-new-seen").fill(opts.whatsNewSeen ?? "");
await page.locator("#dev-whats-new-since").fill(opts.whatsNewSinceDate ?? "");
// §13.10: "1" = survey-eligible with every roll forced to win; otherwise surveys are off.
await page.locator("#dev-survey").fill(opts.survey ? "1" : "0");
await page.getByRole("button", { name: "Dev sign-in" }).click();
// On success the app returns to "/" authenticated and the account menu (.user-trigger, rendered
// only when signed in) appears; on failure it stays on the signed-out landing with the dev
// panel — so waiting for the account menu both confirms success and fails fast otherwise.
// 30 s like the suite's expect timeout: a fresh-onboarding persona is redirected to
// /quick-start server-side, and that route's first hit compiles cold in dev mode.
await page.locator(".user-trigger").waitFor({ state: "visible", timeout: 30_000 });
// Whichever renders first: the account menu means an earlier attempt's callback landed after
// its wait gave up — the session cookie is already set, so it must not be posted again (that
// would re-seed the persona, e.g. reset a fresh-onboarding flag the test relies on).
await menu.or(panel).first().waitFor({ state: "visible", timeout: 20_000 });
if (await menu.isVisible()) return;
await panel.fill(opts.name);
await page.locator("#dev-email").fill(`${devSlug(opts.name)}@dev.test`);
await page.locator("#dev-admin").fill(opts.admin ? "1" : "0");
await page.locator("#dev-fresh-onboarding").fill(opts.freshOnboarding ? "1" : "0");
await page.locator("#dev-whats-new-seen").fill(opts.whatsNewSeen ?? "");
await page.locator("#dev-whats-new-since").fill(opts.whatsNewSinceDate ?? "");
// §13.10: "1" = survey-eligible with every roll forced to win; otherwise surveys are off.
await page.locator("#dev-survey").fill(opts.survey ? "1" : "0");
await page.getByRole("button", { name: "Dev sign-in" }).click();
// On success the app returns to "/" authenticated and the account menu (.user-trigger,
// rendered only when signed in) appears; on failure it stays on the signed-out landing with
// the dev panel. 30 s like the suite's expect timeout: a fresh-onboarding persona is
// redirected to /quick-start server-side, and that route's first hit compiles cold in dev.
await menu.waitFor({ state: "visible", timeout: 30_000 });
}).toPass({ timeout: 100_000 });

const meRes = await context.request.get(`${base}/api/me`, { headers: { accept: "application/json" } });
const me = (await meRes.json()) as { user?: { id?: string }; roles?: { platformAdmin?: boolean } };
Expand Down
7 changes: 7 additions & 0 deletions packages/web/e2e/public-submissions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ test("while the switch is off, /submit and every public route answer 404", async
});

test("submit on /submit, confirm through the link, accept in the queue → External submission in triage", async ({ browser }) => {
// Sign-ins, a proof-of-work wait, confirm and accept run end to end: on a dev server that stalls
// for a few seconds per request this outgrows the default budget, so it gets three times it.
test.slow();
const stamp = uniqueStamp();
const admin = await browser.newContext();
const outsider = await browser.newContext(); // never signed in
Expand Down Expand Up @@ -183,6 +186,10 @@ test("submit on /submit, confirm through the link, accept in the queue → Exter
});

test("Specific people: the tag field, the pre-filled Accept, and sharing once the challenge leaves triage", async ({ browser }) => {
// Three sign-ins, a proof-of-work wait, confirm, accept, a triage move and the inbox check:
// the longest flow in the suite, and the first to outgrow the default budget when the dev
// server stalls — so it gets three times it.
test.slow();
const stamp = uniqueStamp();
const admin = await browser.newContext();
const listed = await browser.newContext();
Expand Down
Loading