web/test: run the browser suite serially in CI

Under `workers: "50%"` the suite failed six tests on a five-worker run, and a different
six on the next. Every failure was a timeout on an operation that passes on its own, and
one was real interference rather than slowness: the groups and users suites both edit
`akadmin`'s group membership, so concurrent workers fight over the same records. The
tests share one authentik instance and one database; they were never independent.

Serial in CI only — local runs keep `"50%"`. The full suite takes about three and a half
minutes that way, against the job's 60 minute budget.

Also gives "Remember me persists username" a 60s budget. It signs in with remember-me,
signs out, and returns to a pre-filled form, so it pays the flow executor's startup cost
twice and did not fit in 30s; the wait for the identification stage is now explicit,
because the flow shell is served before the executor has resolved the first stage.

Full suite under the CI config: 36 passed, none flaky.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Teffen Ellis
2026-08-21 15:49:00 +01:00
parent a560261867
commit dff702e036
2 changed files with 15 additions and 2 deletions

View File

@@ -24,7 +24,13 @@ export default defineConfig({
fullyParallel: true,
forbidOnly: CI,
retries: CI ? 1 : 0,
workers: "50%",
// Serial in CI. Every test drives the same authentik instance and the same database,
// and several of them mutate `akadmin` — the groups and users suites both edit its
// group membership — so concurrent workers contend for the same records on top of
// competing for one server. Locally that mostly shows up as slowness; in CI it showed
// up as timeouts and missing rows that pass on their own. The full suite runs in about
// three minutes serially.
workers: CI ? 1 : "50%",
maxFailures: CI ? 5 : 2,
reporter: CI
? [

View File

@@ -61,6 +61,11 @@ test.describe("Session Lifecycle", () => {
});
test("Remember me persists username", async ({ navigator, session, page }) => {
// This one walks the whole loop — sign in with remember-me, sign out, and come
// back to a pre-filled form — so it pays the flow executor's startup cost twice
// and doesn't fit the default budget.
test.setTimeout(60_000);
await test.step("Verify identification stage", async () => {
await expect(
session.$rememberMeCheckbox,
@@ -102,7 +107,9 @@ test.describe("Session Lifecycle", () => {
await signOutItem.click();
await navigator.waitForPathname("/if/flow/default-authentication-flow/?next=%2F");
await session.$identificationStage.waitFor({ state: "visible" });
// The shell is served before the executor has resolved the first stage, so
// the pathname landing is not enough to act on.
await session.$identificationStage.waitFor({ state: "visible", timeout: 20_000 });
const passwordEmbedded = await session.$passwordField.isVisible();