pediatric-ai-scribe-v3/e2e/tests/encounter-save-load.spec.js
Daniel 8e1ab2fea3 test(e2e): +120 tests across 8 new specs; baseline fixes
8 new spec files covering sections previously only smoke-tested:
- ai-endpoints-contract.spec.js  — hits 8 real AI endpoints via request
  context and fails if the response leaks TypeError / ReferenceError /
  'Cannot read properties of undefined' / 'is not defined' / 'is not a
  function'. This is the class of bug that shipped the PE-narrative
  regression to prod because every page-level mock prevented the real
  handler from running.
- encounter-workflow.spec.js — generate HPI, refine, clear transcript.
- encounter-save-load.spec.js — save draft, load popover, repopulate.
- wellvisit-workflow.spec.js — byvisit, milestones, SSHADESS (12+
  reveal), visit note.
- vaxschedule-content.spec.js — schedule + catch-up panels populate
  beyond "Loading".
- chart-review-workflow.spec.js — generate + load popover.
- learning-tab.spec.js — search filter, category pills, feed.
- settings-faq-dictation.spec.js — voice/password/2FA/Nextcloud
  sections, FAQ expand/collapse, dictation generate flow.

Baseline fixes:
- Added CORS_ORIGINS + API_RATE_LIMIT_MAX env overrides so the e2e
  container accepts the browser's Origin header and can absorb the
  full suite's API traffic without tripping the 200/min guard.
- Server's /api/ rate limit is now configurable via
  API_RATE_LIMIT_MAX (default stays 200).
- extensions-crud: replaced native page.on('dialog') listeners with
  #confirm-modal-ok clicks (we moved off native confirm()).
- pe-guide-smoke + extensions-crud: mobile viewport opens the hamburger
  before clicking sidebar tabs.
- fixtures.js: /api/refine mock uses 'refined' (real API shape), not
  'content'. /api/chart-review replaced with /api/generate-chart-review.

Suite: 230 passed / 0 failed in 4m36s.
2026-04-23 18:58:38 +02:00

83 lines
3.3 KiB
JavaScript

// ============================================================
// ENCOUNTER SAVE/LOAD — save an encounter draft, then load it back
// and verify the transcript + label repopulate.
// ============================================================
const { test, expect, E2E_BASE, mockAI, getAuthToken } = require('../fixtures');
async function openTab(page) {
await page.goto(E2E_BASE + '/');
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
const vp = page.viewportSize();
if (vp && vp.width <= 768) {
await page.click('#btn-menu-toggle').catch(() => {});
}
await page.click('button.tab-btn[data-tab="encounter"]');
await page.waitForFunction(() => {
const el = document.getElementById('encounter-tab');
return el && el.classList.contains('active') && el.innerHTML.trim().length > 100;
}, { timeout: 15000 });
}
// Purge all saved encounters for the test user so each run starts clean.
async function wipeSaved(request) {
const token = await getAuthToken(request);
const auth = { Authorization: 'Bearer ' + token };
const r = await request.get(E2E_BASE + '/api/encounters', { headers: auth });
if (!r.ok()) return;
const d = await r.json().catch(() => ({ items: [] }));
for (const it of (d.items || d.encounters || [])) {
await request.delete(E2E_BASE + '/api/encounters/' + it.id, { headers: auth }).catch(() => {});
}
}
test.describe('Encounter — save + load saved drafts', () => {
test.beforeEach(async ({ request }) => {
await wipeSaved(request);
});
test.afterAll(async ({ request }) => {
await wipeSaved(request);
});
test('save → load popover lists the saved draft → loading repopulates transcript', async ({ authedPage: _, page }) => {
await mockAI(page);
await openTab(page);
// Build a distinct transcript + label so we can verify round-trip
const label = 'TEST-ENC-' + Date.now();
const transcript = 'Unique transcript content: acute pharyngitis with fever.';
await page.fill('#enc-age', '7 years');
await page.selectOption('#enc-gender', 'Male');
await page.locator('#enc-transcript').click();
await page.keyboard.type(transcript);
await page.fill('#enc-label', label);
await page.click('#btn-enc-save');
// Wait for save toast / API round-trip — watch for a save request
await page.waitForResponse(res =>
/\/api\/encounters/.test(res.url()) && res.request().method() === 'POST',
{ timeout: 10000 }
);
// Clear the form and open load popover
await page.click('#btn-enc-new');
// Transcript should now be empty after 'new'
await expect.poll(async () =>
(await page.locator('#enc-transcript').innerText()).trim(),
{ timeout: 3000 }).toBe('');
await page.click('#btn-enc-load');
await expect(page.locator('#enc-load-popover')).not.toHaveClass(/hidden/, { timeout: 5000 });
// The saved label appears in the popover list
await expect(page.locator('#enc-load-popover')).toContainText(label, { timeout: 5000 });
// Click the row for this encounter
await page.locator('#enc-load-popover').getByText(label).first().click();
// Wait for the transcript contenteditable to repopulate
await expect.poll(async () =>
(await page.locator('#enc-transcript').innerText()),
{ timeout: 5000 }).toContain('acute pharyngitis');
});
});