Three separate reasons tests were failing, none of them a defect in the app. The calculators. e2e-harness.html loaded calculators.js and drugs-loader.js with `defer` after they were split into ES modules; index.html was updated at the time and this page was not. A module parsed as a classic script throws "Cannot use import statement outside a module" before a line runs, so no click handler was ever attached: the pills rendered from static HTML and did nothing. Only the first calculator appeared to pass, because it carries `active` in the markup and needs no click. That was 52 failures. Settings and FAQ. Both moved from the tab rail into the account-card menu; the helper still clicked button.tab-btn[data-tab=…] and timed out. Ten more. The AI mocks, which had stopped intercepting for two independent reasons and so were calling the real model on every run — spending credits and comparing genuine output against strings like "MOCK HPI from dictation". A '**/api/x' glob matches no URL on Playwright 1.50, and page.route fails silently when nothing matches; measured against a real URL, that glob and '*/**/api/x' both matched zero times where a regex matched. Fixing that alone was not enough: the app registers a service worker that answers every /api/ request with its own fetch(), and a request made inside a service worker never reaches page.route. Blocking registration in the config puts them back in the page. The mocked dictation test now finishes in 1.6s rather than 7.5s, which is what a real model call costs. Whole suite: 204 passed / 96 failed in 15.8 minutes, now 289 passed / 11 failed in 6.8. The remaining eleven are spread across nine specs with no shared cause and are not touched here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
47 lines
2.1 KiB
JavaScript
47 lines
2.1 KiB
JavaScript
// Playwright config — runs smoke tests against the already-running PedScribe
|
|
// container (no dev server spin-up). Expects BASE_URL, which scripts/e2e.sh
|
|
// supplies.
|
|
const { defineConfig, devices } = require('@playwright/test');
|
|
|
|
// BASE_URL must be a loopback origin (127.0.0.1 / localhost), not a container
|
|
// hostname. The app is a secure context in production and is written on that
|
|
// assumption: AccountBoundary mints a session generation with
|
|
// crypto.randomUUID() on every sign-in. Over plain http on a hostname that is
|
|
// not loopback the browser provides no crypto.randomUUID at all, so that call
|
|
// throws, the boot handler's catch swallows it, and every test lands on the
|
|
// login screen no matter how valid its session is — which is exactly what the
|
|
// whole browser suite was doing.
|
|
//
|
|
// Chrome's --unsafely-treat-insecure-origin-as-secure was tried first and does
|
|
// not work here: Playwright rejects the --user-data-dir it has to be paired
|
|
// with, and the flag alone leaves isSecureContext false. Loopback needs no
|
|
// flags, so scripts/e2e.sh runs the browser on the host network and reaches the
|
|
// app through its published port instead.
|
|
|
|
module.exports = defineConfig({
|
|
testDir: './tests',
|
|
timeout: 30_000,
|
|
expect: { timeout: 5_000 },
|
|
fullyParallel: false,
|
|
retries: 0,
|
|
workers: 1,
|
|
reporter: [['list']],
|
|
use: {
|
|
baseURL: process.env.BASE_URL || 'http://127.0.0.1:3553',
|
|
// The app registers a service worker that answers every /api/ request with
|
|
// its own fetch(). A request made inside a service worker never reaches
|
|
// page.route, so mockAI could not intercept anything while one was running
|
|
// and the tests called the real model. Blocking registration puts the
|
|
// requests back in the page, where the mocks can see them.
|
|
serviceWorkers: 'block',
|
|
trace: 'retain-on-failure',
|
|
screenshot: 'only-on-failure',
|
|
actionTimeout: 5_000,
|
|
navigationTimeout: 15_000,
|
|
},
|
|
projects: [
|
|
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
// Mobile pass — catches layout regressions at ~375 px (iPhone SE)
|
|
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
|
|
],
|
|
});
|