Files
HealthLog/e2e/v137-sharing-cross-tab-session.spec.ts
Marc-André Bombeck 6e5b420e4e test(e2e): give every switching journey its own session row
The suite only passed on one worker: four specs shared the scope
delegate's cookie jar and moved the same session's record selector, so
at CI's two workers they moved the epoch under each other and the
failures were the harness racing itself.

- one login per switching spec, which is a separate session row and
  therefore a separate selector; the login bucket is cleared between
  them because the ceiling is five and setup now signs in eight times
- each such spec runs in exactly one project, since a second project
  drives the same row from two browsers
- clear what the browser suite WRITES, so it can be run twice: the
  write journey saves a minute-rounded reading and collided with itself
- wait for adoption after leaving a record, retry a lost switch the way
  a person would, and budget the cross-tab hold for machine load
2026-08-06 23:04:08 +02:00

117 lines
3.7 KiB
TypeScript

import type { Page } from "@playwright/test";
import { expect, test } from "./setup/test";
import { CROSS_TAB_STORAGE_STATE_PATH } from "./setup/test-helpers";
async function openSwitcher(page: Page) {
await page.getByRole("button", { name: "User menu" }).first().click();
await page.locator('[data-slot="account-switcher-trigger"]').click();
}
function deferred() {
let release!: () => void;
const promise = new Promise<void>((resolve) => {
release = resolve;
});
return { promise, release };
}
test.describe.serial("cross-tab shared-record session switches", () => {
test("holds a peer tab before both switch-in and switch-out change the shared session", async ({
browser,
}) => {
const context = await browser.newContext({
storageState: CROSS_TAB_STORAGE_STATE_PATH,
});
const switchingPage = await context.newPage();
const peerPage = await context.newPage();
const pausedSwitches: ReturnType<typeof deferred>[] = [];
const peerRecordReads: string[] = [];
await switchingPage.route("**/api/account/switch", async (route) => {
const pause = pausedSwitches.shift();
if (pause) await pause.promise;
await route.continue();
});
peerPage.on("request", (request) => {
if (request.method() !== "GET") return;
const path = new URL(request.url()).pathname;
if (
[
"/api/dashboard/snapshot",
"/api/insights/targets",
"/api/medications",
"/api/medications/compliance",
"/api/labs",
"/api/labs/ocr/capability",
"/api/insights/coach/nudge-status",
].includes(path)
) {
peerRecordReads.push(path);
}
});
try {
await Promise.all([switchingPage.goto("/"), peerPage.goto("/")]);
await expect(
peerPage.getByRole("button", { name: "User menu" }),
).toBeVisible();
await openSwitcher(switchingPage);
const entry = switchingPage.locator(
'[data-slot="account-switcher-entry"][data-account-username="e2e-scope-labs"]',
);
await expect(entry).toBeVisible();
const switchIn = deferred();
pausedSwitches.push(switchIn);
peerRecordReads.length = 0;
const switchInClick = entry.click({ noWaitAfter: true });
await expect(
peerPage.locator('[data-slot="record-scope-hydration-gate"]'),
).toBeVisible();
await expect(
peerPage.getByRole("button", { name: "User menu" }),
).toHaveCount(0);
await peerPage.waitForTimeout(200);
expect(peerRecordReads).toEqual([]);
switchIn.release();
await switchInClick;
await expect(
switchingPage.locator('[data-slot="shared-record-banner"]'),
).toBeVisible();
await expect(
peerPage.locator('[data-slot="record-scope-hydration-gate"]'),
).toHaveCount(0);
const switchOut = deferred();
pausedSwitches.push(switchOut);
peerRecordReads.length = 0;
const switchOutClick = switchingPage
.locator('[data-slot="shared-record-banner-exit"]')
.click({ noWaitAfter: true });
await expect(
peerPage.locator('[data-slot="record-scope-hydration-gate"]'),
).toBeVisible();
await expect(
peerPage.locator('[data-slot="shared-record-banner"]'),
).toHaveCount(0);
await peerPage.waitForTimeout(200);
expect(peerRecordReads).toEqual([]);
switchOut.release();
await switchOutClick;
await expect(
switchingPage.getByRole("button", { name: "User menu" }),
).toBeVisible();
await expect(
peerPage.getByRole("button", { name: "User menu" }),
).toBeVisible();
} finally {
await context.close();
}
});
});