pediatric-ai-scribe-v3/e2e/tests/auth-gated-smoke.spec.js
Daniel 33826eb818 feat(pe-guide): unified single-play + remove badges; fix(e2e): shared fixture + raised login limit
User-visible changes:
- Removed the REAL / SYNTH badge from each sound card. User feedback:
  "ridiculous". Cards now just show the sound title + description +
  player, no distinction beyond the player type (native <audio controls>
  for recordings, play/stop/progress bar for synth).
- Removed the "real recordings; synth labelled SYNTH" subtitle from
  the sounds library header.
- Single-playback policy across the whole PE Guide: when any sound
  starts (audio or synth), every other playing sound stops. Covers:
  audio → audio (pause the previous), audio → synth, synth → audio,
  synth → synth. Listeners attach on each .pe-audio 'play' event and
  on every synth play-button click.

E2E infrastructure fixes (for the test failures we hit):
- auth-gated-smoke.spec.js now imports test + loginAs from the shared
  fixtures.js so the token cache is unified across every spec. Without
  this, each spec file's module-scoped _tokenCache multiplied logins
  and hit the 10/15min rate limit.
- server.js: /api/auth/login rate limit is now configurable via
  LOGIN_RATE_LIMIT_MAX env var (default 10, prod unchanged).
- docker-compose.e2e.yml: LOGIN_RATE_LIMIT_MAX="500" so Playwright's
  two-project (chromium + mobile-chrome) multi-worker runs can do
  their logins without tripping the cap. Prod container unaffected.
- fixtures.js console.error allowlist expanded to suppress known
  non-bugs: Cross-Origin-Opener-Policy warnings on http:// e2e
  server, transient 401/403/404/503 resource loads, ERR_BLOCKED_BY_CLIENT.
2026-04-22 22:57:11 +02:00

67 lines
3.2 KiB
JavaScript

// Smoke tests for pages behind the auth wall. Runs against the separate
// `pediatric-ai-scribe-e2e` container (port 3553 on host, 3000 internal) which
// has TURNSTILE_SECRET_KEY="" + SMTP_HOST="" so tests can log in without a
// bot challenge and register auto-verifies.
//
// Each test logs in via the API (no UI interaction needed) and injects the
// session cookie into the browser context.
// Uses the shared fixture so the token cache is unified across every spec
// — each Playwright worker does ONE login for the whole run, staying under
// the 10/15min login rate-limit.
const { test, expect, E2E_BASE, loginAs } = require('../fixtures');
// ── Tests ────────────────────────────────────────────────────────────
test.describe('Auth-gated pages — main tabs', () => {
test.beforeEach(async ({ context, request }) => {
await loginAs(context, request);
});
test('Landing page shows tab navigation after login', async ({ page }) => {
await page.goto(E2E_BASE + '/');
await expect(page.locator('button.tab-btn').first()).toBeVisible({ timeout: 15000 });
});
// Each auth-gated tab test: activate the tab, assert its container becomes
// visible + contains the expected anchor string. We use getTabPanel helpers
// because components load lazily from /components/<tab>.html.
const tabs = [
{ name: 'encounter', anchor: /New Encounter|encounter|SOAP/i },
{ name: 'wellvisit', anchor: /Well Visit|well visit/i },
{ name: 'chart', anchor: /Chart|visits|patients/i },
{ name: 'vaxschedule', anchor: /Vaccine|schedule|dose/i },
{ name: 'catchup', anchor: /Catch-up|catch up|schedule/i },
{ name: 'learning', anchor: /Learning|quiz|topic/i },
{ name: 'dictation', anchor: /Dictation|record|transcrib/i },
{ name: 'settings', anchor: /Setting|profile|preferences|account/i },
{ name: 'calculators', anchor: /Pediatric Calculator|BP Percentile|BMI/i },
{ name: 'faq', anchor: /FAQ|question|answer/i },
];
for (const { name, anchor } of tabs) {
test(`${name} tab loads content`, async ({ page, viewport }) => {
await page.goto(E2E_BASE + '/');
// On mobile the sidebar is hidden behind a hamburger. Open it so the
// tab buttons become interactable.
if (viewport && viewport.width <= 768) {
await page.click('#btn-menu-toggle').catch(() => {});
}
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
const tabBtn = page.locator(`button.tab-btn[data-tab="${name}"]`);
const isHidden = await tabBtn.evaluate((el) => el.classList.contains('hidden')).catch(() => true);
test.skip(isHidden, `Tab "${name}" is hidden for this user role`);
await tabBtn.click();
// Wait for lazy component load to complete
await page.waitForFunction(
(t) => {
const el = document.getElementById(t + '-tab');
return el && el.classList.contains('active') && el.innerHTML.trim().length > 100;
},
name,
{ timeout: 15000 }
);
await expect(page.locator(`#${name}-tab`)).toContainText(anchor);
});
}
});