Files
HealthLog/e2e/achievements.spec.ts
T
Marc-André Bombeck 00848b7ed5 fix(e2e): skip desktop-sidebar specs on mobile + stub /api/dashboard/widgets
Three remaining e2e failures after the global-setup tour-completed fix
all came from cross-project sharing — specs that target desktop-only
DOM (the `aside[aria-label="Sidebar"]` logo + Achievements link)
were running in the chromium-mobile profile too, where the sidebar
is `hidden md:flex` by design. Added `test.skip(viewport.width < 768,
...)` guards so each spec only runs in the project that can satisfy
its contract.

The fourth failure was the `/api/dashboard/widgets` endpoint that the
A5 tile-strip work added since `onboarding-flicker.spec.ts` was
written. Without a stub the unauthenticated dashboard 500s on this
query and React Query's error boundary swallows the page render
before `<GettingStartedChecklist>` mounts. Added the stub to both
flicker specs (complete + incomplete-onboarding paths).
2026-05-10 00:50:00 +02:00

87 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from "@playwright/test";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
/**
* v1.4.15 phase-B4 — /achievements navigation smoke.
*
* The page itself has unit-test coverage for the locked-vs-unlocked
* card render; this spec answers the higher-level "can the user reach
* the page from the global nav?" question. We mock
* `/api/gamification/achievements` so the assertion does not depend on
* the seed user actually having any achievement data — the routing +
* sidebar wiring is what matters here, not the data freshness.
*/
test.describe("achievements page is reachable from the sidebar", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test.beforeEach(async ({ page }) => {
await page.route("**/api/gamification/achievements", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
data: {
summary: {
unlockedCount: 0,
totalCount: 38,
earnedPoints: 0,
totalPoints: 9999,
completionPercent: 0,
nextAchievement: null,
},
achievements: [
{
id: "intake-total-1",
metric: "totalTakenIntakes",
category: "medication",
titleKey: "achievements.badges.intakeTotal1.title",
descriptionKey: "achievements.badges.intakeTotal1.description",
icon: "Pill",
format: "count",
target: 1,
current: 0,
points: 8,
unlocked: false,
progressPercent: 0,
completedAt: null,
},
],
metrics: {},
},
error: null,
}),
}),
);
});
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" });
// 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();
await expect(page).toHaveURL(/\/achievements\b/);
// Page title from the loaded route. The unit test guards the
// grouped + locked-card render shape; here we only assert the route
// mounted and rendered the heading.
await expect(
page.getByRole("heading", { name: "Achievements" }),
).toBeVisible({ timeout: 5000 });
});
});