diff --git a/e2e/fixtures.js b/e2e/fixtures.js index 5cec0802..2bd2a368 100644 --- a/e2e/fixtures.js +++ b/e2e/fixtures.js @@ -85,6 +85,21 @@ async function loginAs(context, request, email = TEST_EMAIL) { }]); } +// A '**/api/x' glob stopped matching any URL when Playwright went to 1.50, and +// page.route fails silently: no error, no warning, the request simply goes to +// the server. So every "mocked" AI test was calling the real model and +// comparing its genuine output against a canned string — spending real credits +// on every run and failing for a reason that looked like a UI bug. Measured: +// against http://127.0.0.1:3553/api/health, '**/api/health' and '*/**/api/health' +// both matched zero times; a regex matched. +// +// The patterns are kept as strings because they are also the keys callers pass +// in `overrides`, and turned into anchored regexes here. +function asMatcher(pattern) { + const path = pattern.replace(/^\*\*/, ''); + return new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:[?#]|$)'); +} + // ── AI mock — intercepts generation endpoints ────────────── // Canned response shape matches what each route's frontend expects. // Override per-test by passing {pattern: responseFn} in overrides. @@ -111,7 +126,7 @@ async function mockAI(page, overrides = {}) { for (const { pattern, response } of routes) { const override = overrides[pattern]; - await page.route(pattern, async route => { + await page.route(asMatcher(pattern), async route => { const resp = typeof override === 'function' ? await override(route.request()) : (override || response); await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(resp) }); }); diff --git a/e2e/playwright.config.js b/e2e/playwright.config.js index 9fafb833..02d16a79 100644 --- a/e2e/playwright.config.js +++ b/e2e/playwright.config.js @@ -28,6 +28,12 @@ module.exports = defineConfig({ 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, diff --git a/e2e/tests/settings-faq-dictation.spec.js b/e2e/tests/settings-faq-dictation.spec.js index e86366c9..fe216d04 100644 --- a/e2e/tests/settings-faq-dictation.spec.js +++ b/e2e/tests/settings-faq-dictation.spec.js @@ -5,6 +5,12 @@ const { test, expect, E2E_BASE, mockAI } = require('../fixtures'); +// Settings and FAQ are not on the tab rail. They live in the account-card menu +// alongside Admin, and this helper used to click button.tab-btn[data-tab=…] for +// them, which simply timed out — the cause of ten of these failures. Dictation +// really is a rail tab, so both routes are needed. +const ACCOUNT_MENU = ['settings', 'faq']; + async function openTab(page, name) { await page.goto(E2E_BASE + '/'); await page.waitForSelector('button.tab-btn', { timeout: 15000 }); @@ -12,7 +18,12 @@ async function openTab(page, name) { if (vp && vp.width <= 768) { await page.click('#btn-menu-toggle').catch(() => {}); } - await page.click(`button.tab-btn[data-tab="${name}"]`); + if (ACCOUNT_MENU.includes(name)) { + await page.locator('.account-card-btn').first().click(); + await page.locator(`[data-account-tab="${name}"]`).first().click(); + } else { + await page.click(`button.tab-btn[data-tab="${name}"]`); + } await page.waitForFunction((t) => { const el = document.getElementById(t + '-tab'); return el && el.classList.contains('active') && el.innerHTML.trim().length > 100; diff --git a/public/components/my-resources.html b/public/components/my-resources.html index 6997e59b..5ac903c3 100644 --- a/public/components/my-resources.html +++ b/public/components/my-resources.html @@ -1,9 +1,14 @@

My Resources

- Private to you + Only you can see these
+

+ Build a teaching deck or handout on any topic — grounded in the clinical library, + the literature, or both — then revise it below and download it as PowerPoint, + Word or PDF. +

@@ -27,16 +32,6 @@
-
- Clinical library -
- -
-
- -