diff --git a/e2e/a11y.spec.ts b/e2e/a11y.spec.ts index c25b66e1e..64787e12a 100644 --- a/e2e/a11y.spec.ts +++ b/e2e/a11y.spec.ts @@ -119,7 +119,19 @@ test.describe("axe-core authenticated surfaces", () => { // bounding box must accept the click. test("skip-link does not block logo click outside focus", async ({ page, + viewport, }) => { + // The desktop sidebar (and its logo) is `hidden md:flex` — the + // mobile profile (Pixel 5) hides it by design. The skip-link + // regression Marc originally reported in v1.4.14 was a desktop- + // only artifact (mobile uses the topbar logo, which is a + // different DOM node). Skip the mobile project so we don't + // flake on a contract that doesn't apply there. + test.skip( + (viewport?.width ?? 0) < 768, + "desktop sidebar logo is hidden on mobile by design", + ); + await page.goto("/", { waitUntil: "domcontentloaded" }); await page.waitForLoadState("networkidle"); diff --git a/e2e/achievements.spec.ts b/e2e/achievements.spec.ts index 219ec7cb6..507869f11 100644 --- a/e2e/achievements.spec.ts +++ b/e2e/achievements.spec.ts @@ -55,11 +55,23 @@ test.describe("achievements page is reachable from the sidebar", () => { ); }); - test("desktop sidebar exposes the achievements link", async ({ page }) => { + test("desktop sidebar exposes the achievements link", async ({ + page, + viewport, + }) => { + // The sidebar nav is hidden on small viewports (`md:flex`). The + // chromium-mobile profile (Pixel 5, 393 px wide) cannot satisfy + // this contract by design — skip there so the spec only runs in + // the chromium-desktop project (1280×720). Without this guard + // every push fails 1 / 4 specs purely because the same suite is + // sharded across both projects. + test.skip( + (viewport?.width ?? 0) < 768, + "sidebar is hidden on mobile by design (md:flex breakpoint)", + ); + await page.goto("/", { waitUntil: "domcontentloaded" }); - // The sidebar nav is hidden on small viewports (`md:flex`); the - // chromium-desktop project uses 1280×720 so it's visible here. Match - // by aria-label so we don't accidentally pick up a tooltip. + // Match by aria-label so we don't accidentally pick up a tooltip. const sidebar = page.locator('aside[aria-label="Sidebar"]'); await expect(sidebar).toBeVisible(); await sidebar.getByRole("link", { name: "Achievements" }).click(); diff --git a/e2e/onboarding-flicker.spec.ts b/e2e/onboarding-flicker.spec.ts index b3fef3e94..936cb7d28 100644 --- a/e2e/onboarding-flicker.spec.ts +++ b/e2e/onboarding-flicker.spec.ts @@ -94,6 +94,19 @@ test.describe("dashboard onboarding card flicker guard", () => { }), }), ); + // Same `/api/dashboard/widgets` stub as the incomplete-onboarding + // test below — keeps the dashboard from error-boundary-bailing + // when this query 500s in CI without a real session. + await page.route("**/api/dashboard/widgets", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + data: { tilesVisible: { weight: true, bp: true, pulse: true } }, + error: null, + }), + }), + ); const card = page.locator('[data-testid="onboarding-card"]'); @@ -192,6 +205,21 @@ test.describe("dashboard onboarding card flicker guard", () => { body: JSON.stringify({ data: { channels: [] }, error: null }), }), ); + // v1.4.16 Wave-C — the dashboard added `/api/dashboard/widgets` + // (A5 tile-strip persistence) since this spec was written. Without + // a stub the unauthenticated layout query 500s in CI and React + // Query's error boundary swallows the page render before + // `` can mount. + await page.route("**/api/dashboard/widgets", (route) => + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + data: { tilesVisible: { weight: true, bp: true, pulse: true } }, + error: null, + }), + }), + ); // Make sure no localStorage carries an `expanded=1` from a previous // session — the spec asserts the *default* is collapsed.