test(e2e): +44 tests — sick visit, hospital course, auth screen,
session persistence, per-tab model selector Brings detailed coverage to sections that were previously only smoke- tested: - sickvisit-workflow.spec.js: generate → mocked note, refine bar round-trip, load popover, New button clears demographics + transcript. - hospitalcourse-workflow.spec.js: fill H&P → generate → mocked narrative renders via /api/generate-hospital-course, refine updates text, load popover open/close. - auth-screen.spec.js: unauthenticated landing visible, login form structure, forgot-password swap + return, register link is intentionally display:none on this instance (pinned), register form DOM still wired correctly if the link is manually unhidden, reg- password has minlength=8 + type=password. - session-persistence.spec.js: clear cookie simulates logout (UI login is gated by Turnstile which can't be completed in the e2e container); re-login via loginAs restores the last tab + sub-pill via localStorage. - model-selector.spec.js: tab-model-select dropdowns render with >0 options across 7 tabs. Fixture corrections: - /api/sick-visit/note and /api/well-visit/note were not being mocked at all — the wrong /api/generate-sick-visit pattern was intercepting nothing, so tests fell through to the real AI backend. Both now have correct patterns and response shapes (sick-visit returns `note`, well-visit note returns `note`). - /api/generate-hospital-course response key updated from `narrative` to `hospitalCourse` to match what the frontend actually reads. wellvisit-workflow: the Visit Note test now asserts a concrete waitForResponse on /api/well-visit/note + text render, instead of the previous "hit either endpoint" fallback. Suite: 294 passed / 0 failed in 5m30s.
This commit is contained in:
parent
69dedbb635
commit
3e3b4866be
7 changed files with 374 additions and 7 deletions
|
|
@ -81,8 +81,9 @@ async function mockAI(page, overrides = {}) {
|
|||
{ pattern: '**/api/generate-soap', response: { success: true, soap: 'MOCK SOAP NOTE.\nSubjective: ...\nObjective: ...\nAssessment: ...\nPlan: ...', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/generate-hpi-encounter', response: { success: true, hpi: 'MOCK HPI from encounter.', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/generate-hpi-dictation', response: { success: true, hpi: 'MOCK HPI from dictation.', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/generate-sick-visit', response: { success: true, note: 'MOCK sick visit note.', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/generate-hospital-course', response: { success: true, narrative: 'MOCK hospital course narrative.', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/sick-visit/note', response: { success: true, note: 'MOCK sick visit note.', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/well-visit/note', response: { success: true, note: 'MOCK well visit note.', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/generate-hospital-course', response: { success: true, hospitalCourse: 'MOCK hospital course narrative.', format: 'auto', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/generate-milestone-narrative', response: { success: true, narrative: 'MOCK developmental narrative.', model: 'mock-gpt', summary: { achieved: 3, notAchieved: 0, notAssessed: 0 } } },
|
||||
{ pattern: '**/api/generate-milestone-summary', response: { success: true, summary: 'MOCK 3-sentence summary.', model: 'mock-gpt' } },
|
||||
{ pattern: '**/api/generate-pe-narrative', response: { success: true, narrative: 'Technique:\nMOCK technique.\n\nFindings:\nMOCK findings.', model: 'mock-gpt', summary: { normal: 2, abnormal: 0, notAssessed: 0 } } },
|
||||
|
|
|
|||
73
e2e/tests/auth-screen.spec.js
Normal file
73
e2e/tests/auth-screen.spec.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// ============================================================
|
||||
// AUTH SCREEN — unauthenticated landing page structure.
|
||||
// These tests do NOT use the `authedPage` fixture; they visit the
|
||||
// app with a fresh context (no cookie) and assert the sign-in
|
||||
// form + register + forgot-password transitions render correctly.
|
||||
// ============================================================
|
||||
|
||||
const { test, expect, E2E_BASE } = require('../fixtures');
|
||||
|
||||
test.describe('Unauthenticated auth screen', () => {
|
||||
|
||||
// Use the base test that doesn't auto-login.
|
||||
test('landing shows login form with email + password fields', async ({ page }) => {
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await expect(page.locator('#auth-screen')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('#login-email')).toBeVisible();
|
||||
await expect(page.locator('#login-password')).toBeVisible();
|
||||
await expect(page.locator('#btn-local-login')).toBeVisible();
|
||||
// main app body must be hidden while unauthenticated
|
||||
await expect(page.locator('#main-app')).toBeHidden();
|
||||
});
|
||||
|
||||
test('register link is present but currently disabled (display:none)', async ({ page }) => {
|
||||
// Daniel's instance has invite-only registration — the "Create account"
|
||||
// link is explicitly hidden via inline style, so the HTML is there but
|
||||
// users can't reach the register form through the UI. Verify the hidden
|
||||
// state so flipping the style to re-enable it fails loudly.
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('#auth-screen', { timeout: 10000 });
|
||||
const display = await page.locator('#show-register').evaluate(el => el.style.display);
|
||||
expect(display).toBe('none');
|
||||
// The register form element still exists in the DOM for programmatic access
|
||||
await expect(page.locator('#register-form')).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('register form DOM is wired correctly if manually unhidden', async ({ page }) => {
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('#auth-screen', { timeout: 10000 });
|
||||
// Force the link visible so we can exercise the swap path — useful for
|
||||
// future tests that want to validate the full register flow.
|
||||
await page.locator('#show-register').evaluate(el => { el.style.display = ''; });
|
||||
await page.click('#show-register');
|
||||
await expect(page.locator('#register-form')).toBeVisible();
|
||||
await expect(page.locator('#reg-name')).toBeVisible();
|
||||
await expect(page.locator('#reg-email')).toBeVisible();
|
||||
await expect(page.locator('#reg-password')).toBeVisible();
|
||||
await page.click('#show-login');
|
||||
await expect(page.locator('#login-form')).toBeVisible();
|
||||
await expect(page.locator('#register-form')).toBeHidden();
|
||||
});
|
||||
|
||||
test('clicking "Forgot password?" swaps to forgot form', async ({ page }) => {
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('#auth-screen', { timeout: 10000 });
|
||||
await page.click('#show-forgot');
|
||||
await expect(page.locator('#forgot-form')).toBeVisible();
|
||||
await expect(page.locator('#forgot-email')).toBeVisible();
|
||||
await expect(page.locator('#login-form')).toBeHidden();
|
||||
// Back link returns to login
|
||||
await page.click('#show-login-2');
|
||||
await expect(page.locator('#login-form')).toBeVisible();
|
||||
await expect(page.locator('#forgot-form')).toBeHidden();
|
||||
});
|
||||
|
||||
test('password minlength enforces 8 chars in register form', async ({ page }) => {
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('#auth-screen', { timeout: 10000 });
|
||||
// Attribute check doesn't require the element to be visible.
|
||||
const pw = page.locator('#reg-password');
|
||||
await expect(pw).toHaveAttribute('minlength', '8');
|
||||
await expect(pw).toHaveAttribute('type', 'password');
|
||||
});
|
||||
});
|
||||
76
e2e/tests/hospitalcourse-workflow.spec.js
Normal file
76
e2e/tests/hospitalcourse-workflow.spec.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// ============================================================
|
||||
// HOSPITAL COURSE — full workflow: fill inputs → generate → verify
|
||||
// mocked narrative renders → refine → verify rendered replaces.
|
||||
// The older soap-hospital-workflow.spec.js only smokes the save bar;
|
||||
// this one drives the actual AI generate path.
|
||||
// ============================================================
|
||||
|
||||
const { test, expect, E2E_BASE, mockAI } = 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="hospital"]');
|
||||
await page.waitForFunction(() => {
|
||||
const el = document.getElementById('hospital-tab');
|
||||
return el && el.classList.contains('active') && el.innerHTML.trim().length > 100;
|
||||
}, { timeout: 15000 });
|
||||
}
|
||||
|
||||
test.describe('Hospital Course — generate + refine', () => {
|
||||
|
||||
test('minimum inputs → generate → mocked narrative renders', async ({ authedPage: _, page }) => {
|
||||
await mockAI(page);
|
||||
await openTab(page);
|
||||
|
||||
await page.fill('#hc-age', '9 years');
|
||||
await page.selectOption('#hc-gender', 'Male');
|
||||
await page.fill('#hc-pmh', 'Asthma, mild intermittent');
|
||||
// H&P is the minimum "some note" the route needs so it doesn't reject
|
||||
await page.locator('#hc-hp-content').click();
|
||||
await page.keyboard.type('Admitted for status asthmaticus. Started on continuous albuterol and steroids.');
|
||||
|
||||
const [resp] = await Promise.all([
|
||||
page.waitForResponse('**/api/generate-hospital-course', { timeout: 15000 }),
|
||||
page.click('#hc-generate-btn'),
|
||||
]);
|
||||
expect(resp.status()).toBe(200);
|
||||
await expect(page.locator('#hc-output')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('#hc-course-text')).toContainText('MOCK hospital course narrative');
|
||||
});
|
||||
|
||||
test('refine button on generated course fires /api/refine + updates text', async ({ authedPage: _, page }) => {
|
||||
await mockAI(page, {
|
||||
'**/api/refine': { success: true, refined: 'REFINED-HC course — emphasised hospital day 1 events.', model: 'mock-gpt' },
|
||||
});
|
||||
await openTab(page);
|
||||
|
||||
await page.fill('#hc-age', '5 years');
|
||||
await page.locator('#hc-hp-content').click();
|
||||
await page.keyboard.type('Admission for pneumonia, improving on IV cefriaxone.');
|
||||
await page.click('#hc-generate-btn');
|
||||
await expect(page.locator('#hc-course-text')).toContainText('MOCK hospital course', { timeout: 10000 });
|
||||
|
||||
// Refine with an instruction
|
||||
const refineInput = page.locator('#hc-refine-input, [id*="hc-refine"]').first();
|
||||
await refineInput.fill('Tighten the prose.');
|
||||
const [resp] = await Promise.all([
|
||||
page.waitForResponse('**/api/refine'),
|
||||
page.click('#hc-refine-btn'),
|
||||
]);
|
||||
expect(resp.status()).toBe(200);
|
||||
await expect(page.locator('#hc-course-text')).toContainText('REFINED-HC course', { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('load popover: opens and closes from both triggers', async ({ authedPage: _, page }) => {
|
||||
await openTab(page);
|
||||
await page.click('#btn-hosp-load');
|
||||
await expect(page.locator('#hosp-load-popover')).not.toHaveClass(/hidden/, { timeout: 3000 });
|
||||
await page.locator('#hosp-load-popover .enc-pop-close').click();
|
||||
await expect(page.locator('#hosp-load-popover')).toHaveClass(/hidden/);
|
||||
});
|
||||
});
|
||||
43
e2e/tests/model-selector.spec.js
Normal file
43
e2e/tests/model-selector.spec.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// ============================================================
|
||||
// MODEL SELECTOR — each tab has its own <select.tab-model-select>
|
||||
// populated by window._buildModelOptions. Verify the dropdowns
|
||||
// render across tabs that should have one.
|
||||
// ============================================================
|
||||
|
||||
const { test, expect, E2E_BASE } = require('../fixtures');
|
||||
|
||||
async function openTab(page, name) {
|
||||
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="${name}"]`);
|
||||
await page.waitForFunction((t) => {
|
||||
const el = document.getElementById(t + '-tab');
|
||||
return el && el.classList.contains('active') && el.innerHTML.trim().length > 100;
|
||||
}, name, { timeout: 15000 });
|
||||
}
|
||||
|
||||
test.describe('Per-tab model selector', () => {
|
||||
const tabsWithModelPicker = ['encounter', 'dictation', 'chart', 'soap', 'hospital', 'sickvisit', 'wellvisit'];
|
||||
|
||||
for (const tab of tabsWithModelPicker) {
|
||||
test(`${tab}: model picker renders and has at least one option`, async ({ authedPage: _, page }) => {
|
||||
await openTab(page, tab);
|
||||
// Well Visit has four sub-panels each with their own picker; three of
|
||||
// them are hidden until you switch to that sub-tab, so visibility is
|
||||
// unreliable. Instead: require that the active tab contains at least
|
||||
// one picker AND that at least one picker inside the tab has >0
|
||||
// options populated by window._buildModelOptions.
|
||||
const pickers = page.locator(`#${tab}-tab select.tab-model-select`);
|
||||
await expect.poll(async () => pickers.count(), { timeout: 10000 })
|
||||
.toBeGreaterThan(0);
|
||||
await expect.poll(async () => {
|
||||
return await pickers.evaluateAll(list => list.reduce((max, el) =>
|
||||
Math.max(max, el.options ? el.options.length : 0), 0));
|
||||
}, { timeout: 10000 }).toBeGreaterThan(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
86
e2e/tests/session-persistence.spec.js
Normal file
86
e2e/tests/session-persistence.spec.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// ============================================================
|
||||
// SESSION PERSISTENCE — full logout → login → still on the same
|
||||
// tab + same sub-pill.
|
||||
//
|
||||
// The UI's login form is gated by a Cloudflare Turnstile token
|
||||
// whose site key is hardcoded in index.html, which can't be
|
||||
// completed in the e2e container (Turnstile rejects the non-prod
|
||||
// origin). So the test does a programmatic logout (clear the
|
||||
// ped_auth cookie, same effect server-side as clicking Logout)
|
||||
// followed by a fresh programmatic login — this exercises the
|
||||
// same localStorage persistence path a real logout/login would,
|
||||
// without depending on the bot challenge.
|
||||
// ============================================================
|
||||
|
||||
const { test, expect, E2E_BASE, loginAs } = require('../fixtures');
|
||||
|
||||
async function openDesktopTab(page, name) {
|
||||
const vp = page.viewportSize();
|
||||
if (vp && vp.width <= 768) {
|
||||
await page.click('#btn-menu-toggle').catch(() => {});
|
||||
}
|
||||
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;
|
||||
}, name, { timeout: 15000 });
|
||||
}
|
||||
|
||||
async function logoutClearsCookie(context) {
|
||||
// Remove the ped_auth cookie — identical server-side to hitting /logout.
|
||||
const cookies = await context.cookies();
|
||||
const keep = cookies.filter(c => c.name !== 'ped_auth');
|
||||
await context.clearCookies();
|
||||
if (keep.length) await context.addCookies(keep);
|
||||
}
|
||||
|
||||
test.describe('Logout → login restores last tab + sub-pill', () => {
|
||||
|
||||
test('tab choice + calc pill survive a full logout/login cycle', async ({ context, request, page }) => {
|
||||
await loginAs(context, request);
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
|
||||
await openDesktopTab(page, 'calculators');
|
||||
await page.click('button.calc-nav-pill[data-calc="bili"]');
|
||||
await expect(page.locator('button.calc-nav-pill[data-calc="bili"].active')).toBeVisible();
|
||||
|
||||
// Simulate logout (clear session cookie)
|
||||
await logoutClearsCookie(context);
|
||||
// Visiting the app with no cookie lands on the auth screen
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await expect(page.locator('#auth-screen')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Log back in (fresh cookie) and revisit the app
|
||||
await loginAs(context, request);
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
|
||||
|
||||
// Restore must put us back on Calculators / Bilirubin pill
|
||||
await expect(page.locator('#calculators-tab.active')).toHaveCount(1, { timeout: 10000 });
|
||||
await expect(page.locator('button.calc-nav-pill[data-calc="bili"].active')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('#calc-bili')).not.toHaveClass(/hidden/);
|
||||
});
|
||||
|
||||
test('bedside sub-pill survives logout/login', async ({ context, request, page }) => {
|
||||
await loginAs(context, request);
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
|
||||
await openDesktopTab(page, 'bedside');
|
||||
await page.click('button.calc-pill[data-em="sepsis"]');
|
||||
await expect(page.locator('button.calc-pill[data-em="sepsis"].active')).toBeVisible();
|
||||
|
||||
await logoutClearsCookie(context);
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await expect(page.locator('#auth-screen')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await loginAs(context, request);
|
||||
await page.goto(E2E_BASE + '/');
|
||||
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
|
||||
|
||||
await expect(page.locator('#bedside-tab.active')).toHaveCount(1, { timeout: 10000 });
|
||||
await expect(page.locator('button.calc-pill[data-em="sepsis"].active')).toBeVisible({ timeout: 10000 });
|
||||
await expect.poll(async () =>
|
||||
await page.locator('#em-sepsis').evaluate(el => el.style.display),
|
||||
{ timeout: 5000 }).not.toBe('none');
|
||||
});
|
||||
});
|
||||
89
e2e/tests/sickvisit-workflow.spec.js
Normal file
89
e2e/tests/sickvisit-workflow.spec.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// ============================================================
|
||||
// SICK VISIT — detailed workflow tests
|
||||
// Fills the form, generates a mocked note, verifies output render
|
||||
// and the refine + clear flows.
|
||||
// ============================================================
|
||||
|
||||
const { test, expect, E2E_BASE, mockAI } = 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="sickvisit"]');
|
||||
await page.waitForFunction(() => {
|
||||
const el = document.getElementById('sickvisit-tab');
|
||||
return el && el.classList.contains('active') && el.innerHTML.trim().length > 100;
|
||||
}, { timeout: 15000 });
|
||||
}
|
||||
|
||||
test.describe('Sick Visit — generate workflow', () => {
|
||||
|
||||
test('fill demographics + CC + transcript → mocked note renders', async ({ authedPage: _, page }) => {
|
||||
await mockAI(page);
|
||||
await openTab(page);
|
||||
|
||||
await page.fill('#sick-age', '6 years');
|
||||
await page.selectOption('#sick-gender', 'Male');
|
||||
await page.fill('#sick-cc', 'Sore throat x 3 days');
|
||||
await page.locator('#sick-transcript').click();
|
||||
await page.keyboard.type('6yo with 3 days sore throat, fever, fatigue. No rash.');
|
||||
|
||||
const [resp] = await Promise.all([
|
||||
page.waitForResponse('**/api/sick-visit/note', { timeout: 15000 }),
|
||||
page.click('#btn-sick-generate'),
|
||||
]);
|
||||
expect(resp.status()).toBe(200);
|
||||
await expect(page.locator('#sick-note-output')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('#sick-note-text')).toContainText('MOCK sick visit note');
|
||||
});
|
||||
|
||||
test('refine button fires /api/refine and updates the rendered note', async ({ authedPage: _, page }) => {
|
||||
await mockAI(page, {
|
||||
'**/api/refine': { success: true, refined: 'REFINED sick-visit note — added return precautions.', model: 'mock-gpt' },
|
||||
});
|
||||
await openTab(page);
|
||||
await page.fill('#sick-age', '4 years');
|
||||
await page.fill('#sick-cc', 'Cough');
|
||||
await page.locator('#sick-transcript').click();
|
||||
await page.keyboard.type('Cough x 2 days, no fever, hydrating OK.');
|
||||
await page.click('#btn-sick-generate');
|
||||
await expect(page.locator('#sick-note-text')).toContainText('MOCK sick visit note', { timeout: 10000 });
|
||||
|
||||
await page.fill('#sick-refine-input', 'Add return precautions.');
|
||||
const [resp] = await Promise.all([
|
||||
page.waitForResponse('**/api/refine'),
|
||||
page.click('#sick-refine-btn'),
|
||||
]);
|
||||
expect(resp.status()).toBe(200);
|
||||
await expect(page.locator('#sick-note-text')).toContainText('REFINED sick-visit note', { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('load popover opens and closes', async ({ authedPage: _, page }) => {
|
||||
await openTab(page);
|
||||
await page.click('#btn-sick-load');
|
||||
await expect(page.locator('#sick-load-popover')).not.toHaveClass(/hidden/, { timeout: 3000 });
|
||||
// Close by clicking the close button inside the popover
|
||||
await page.locator('#sick-load-popover .enc-pop-close').click();
|
||||
await expect(page.locator('#sick-load-popover')).toHaveClass(/hidden/);
|
||||
});
|
||||
|
||||
test('New button clears demographics + transcript for a new patient', async ({ authedPage: _, page }) => {
|
||||
await mockAI(page);
|
||||
await openTab(page);
|
||||
await page.fill('#sick-age', '5 years');
|
||||
await page.selectOption('#sick-gender', 'Male');
|
||||
await page.locator('#sick-transcript').click();
|
||||
await page.keyboard.type('Temporary transcript');
|
||||
await page.click('#btn-sick-new');
|
||||
// clearTab() resets demographics + transcript (chief complaint is left so a
|
||||
// rapid "next patient with same complaint" doesn't have to retype it).
|
||||
await expect(page.locator('#sick-age')).toHaveValue('');
|
||||
await expect(page.locator('#sick-gender')).toHaveValue('');
|
||||
const txt = await page.locator('#sick-transcript').innerText();
|
||||
expect(txt.trim()).toBe('');
|
||||
});
|
||||
});
|
||||
|
|
@ -123,12 +123,11 @@ test.describe('Well Visit — detailed workflows', () => {
|
|||
await page.keyboard.type('Parent reports child is doing well; no concerns.');
|
||||
|
||||
const [resp] = await Promise.all([
|
||||
page.waitForResponse('**/api/generate-sick-visit', { timeout: 15000 }).catch(() => null),
|
||||
// Note: the well-visit generate button hits either /generate-sick-visit or a
|
||||
// dedicated endpoint depending on the code — just ensure it doesn't crash.
|
||||
page.waitForResponse('**/api/well-visit/note', { timeout: 15000 }),
|
||||
page.click('#btn-wv-generate'),
|
||||
]);
|
||||
// The output container should become visible with SOME text (mocked)
|
||||
await expect(page.locator('#wv-note-output')).toBeVisible({ timeout: 15000 });
|
||||
expect(resp.status()).toBe(200);
|
||||
await expect(page.locator('#wv-note-output')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('#wv-note-text')).toContainText('MOCK well visit note', { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue