Files
HealthLog/e2e/locale-switch.spec.ts
T
Marc-André Bombeck fdf8dd6eb9 test(e2e): add Playwright smoke suite + axe-core a11y gate + CI (#146)
Builds out the foundation for end-to-end tests against the production
build: Playwright with desktop + mobile projects, axe-core for WCAG
2.1 AA gating, and a GitHub Actions workflow that caches the browser
download and uploads the HTML report on failure.

What lands now (public surfaces only — no DB seed required):

  e2e/version.spec.ts          /api/version returns shape, no auth
  e2e/auth-redirect.spec.ts    proxy gate bounces / → /auth/login etc.
  e2e/login.spec.ts            login form renders + autoComplete hints
                               + invalid-credential error surfaces
  e2e/locale-switch.spec.ts    DE/EN cookie switch, no raw i18n keys
                               leak into the DOM
  e2e/a11y.spec.ts             axe-core fails on serious or critical
                               WCAG 2.1 A/AA violations on /auth/login

The four authenticated flow specs from the v1.4.1 plan
(quick-entry, doctor-report, settings-roundtrip, test-buttons,
onboarding) need a seeded test user; they're tracked as a follow-up
because the seeding pipeline + WebAuthn polyfill they require warrant
their own PR. The smoke specs above cover the proxy, the login surface,
the version healthcheck endpoint, and the locale machinery — i.e.
every path a v1.4.X regression could realistically break without
deeper UAT.

CI workflow (.github/workflows/e2e.yml):
  - postgres:16-alpine service container
  - prisma migrate deploy seeds an empty schema
  - playwright install --with-deps chromium (cached)
  - next build, then `playwright test` against `next start`
  - playwright-report uploaded as an artifact on failure

Quality gates:
  - pnpm typecheck — clean
  - pnpm test — 658/658 pass (vitest excludes e2e/**)
  - pnpm lint — 0 errors
2026-05-08 16:08:02 +02:00

50 lines
1.7 KiB
TypeScript

import { expect, test } from "@playwright/test";
/**
* Locale-key drift smoke check — switch the cookie between EN and DE
* and assert the login page (the only public surface that's translated)
* never shows raw i18n keys like `auth.welcomeBack`.
*
* Drift catches: a typo'd t("foo.bar") with no key in either locale
* file would make the raw key surface in the DOM. messages/de.json and
* messages/en.json key parity is enforced by unit tests; this is the
* runtime cousin.
*/
test.describe("locale switch", () => {
for (const locale of ["en", "de"] as const) {
test(`renders /auth/login in ${locale} without raw i18n keys`, async ({
page,
context,
}) => {
await context.addCookies([
{
name: "healthlog_locale",
value: locale,
url: page.url() || "http://localhost:3000",
},
]);
await page.goto("/auth/login");
const bodyText = await page.locator("body").innerText();
// i18n keys look like `section.subkey` — letters, numbers,
// underscores, dots — and are never wrapped in normal sentences,
// so a regex match anywhere in body text is a sign the lookup
// fell through.
const rawKeyPattern = /\b[a-z]+(?:[A-Z][a-z]+)?\.[a-z][A-Za-z0-9_.]+\b/;
const match = bodyText.match(rawKeyPattern);
// Whitelist: filenames or version strings can look like keys,
// skip those by requiring at least one camelCase segment.
if (match) {
const candidate = match[0];
const looksLikeKey = /\.[a-z][A-Z]/.test(candidate);
expect(
looksLikeKey,
`Possible raw i18n key in ${locale}: ${candidate}`,
).toBe(false);
}
});
}
});