// ============================================================ // PE GUIDE — smoke tests for the Physical Exam Guide tab // ============================================================ // Exercises the full rendering path: tab load → age group selection → // system switching (msk / neuro / resp / cv) → expected per-system // cards (scales, sounds library for resp, APTM image + innocent // murmur panel for cv) → step toggle interaction. // // Uses the shared fixture so any uncaught JS error or console.error // fails the test automatically (catches a repeat of the SSO-bug // class on this page). // ============================================================ const { test, expect, E2E_BASE } = require('../fixtures'); async function openPEGuide(page) { await page.goto(E2E_BASE + '/'); await page.waitForSelector('button.tab-btn', { timeout: 15000 }); await page.click('button.tab-btn[data-tab="peguide"]'); await page.waitForFunction(() => { const el = document.getElementById('peguide-tab'); return el && el.classList.contains('active') && el.innerHTML.trim().length > 100; }, { timeout: 15000 }); } async function selectAge(page, value) { await page.selectOption('#pe-age-group', value); // Wait for components to render (at least one card with a step) await page.waitForFunction(() => { return document.querySelectorAll('#pe-content .pe-step').length > 0; }, { timeout: 5000 }); } async function switchSystem(page, sys) { await page.click('button.wv-subtab-btn[data-pesystem="' + sys + '"]'); // Either steps are there, or the resp/cv cards are (which have no .pe-step) await page.waitForFunction((s) => { const content = document.getElementById('pe-content'); if (!content) return false; if (s === 'resp') return content.innerHTML.includes('Respiratory sounds library'); if (s === 'cv') return content.innerHTML.includes('Auscultation landmarks'); return content.querySelectorAll('.pe-step').length > 0; }, sys, { timeout: 5000 }); } test.describe('PE Guide — smoke', () => { test('tab loads with empty-state message before age group selected', async ({ authedPage: _, page }) => { await openPEGuide(page); await expect(page.locator('#pe-content')).toContainText(/Select an age group/i); }); test('MSK (default) renders with overview and grading scales for adolescent', async ({ authedPage: _, page }) => { await openPEGuide(page); await selectAge(page, 'adolescent'); await expect(page.locator('#pe-content')).toContainText('Musculoskeletal'); await expect(page.locator('#pe-content')).toContainText(/Scoliometer|Beighton/); // At least one step card present await expect(page.locator('.pe-step').first()).toBeVisible(); }); test('switches to Neuro and shows MRC scale + teaching pearl', async ({ authedPage: _, page }) => { await openPEGuide(page); await selectAge(page, 'adolescent'); await switchSystem(page, 'neuro'); await expect(page.locator('#pe-content')).toContainText('Neurologic'); await expect(page.locator('#pe-content')).toContainText(/MRC strength/i); // Teaching pearl rendered (amber block) await expect(page.locator('#pe-content')).toContainText(/Pronator drift/i); }); test('Respiratory system shows the 8-sound library with play buttons', async ({ authedPage: _, page }) => { await openPEGuide(page); await selectAge(page, 'adolescent'); await switchSystem(page, 'resp'); await expect(page.locator('#pe-content')).toContainText('Respiratory sounds library'); // All 8 sounds represented as play buttons const playButtons = page.locator('.resp-sound-play'); await expect(playButtons).toHaveCount(8); // RR scale renders await expect(page.locator('#pe-content')).toContainText(/Respiratory rate/i); // Observation-first inspection card present await expect(page.locator('#pe-content')).toContainText(/Inspection/i); }); test('Cardiovascular system shows APTM image + all 5 landmarks + innocent murmur panel', async ({ authedPage: _, page }) => { await openPEGuide(page); await selectAge(page, 'adolescent'); await switchSystem(page, 'cv'); // APTM diagram image loaded const aptmImg = page.locator('img[src*="aptm.png"]'); await expect(aptmImg).toBeVisible(); // Wait for it to actually have painted pixels (naturalWidth > 0 = fetched successfully) await expect.poll(async () => { return await aptmImg.evaluate(el => el.naturalWidth); }, { timeout: 10000 }).toBeGreaterThan(0); // All 5 landmark letters in the legend for (const letter of ['A', 'P', 'E', 'T', 'M']) { await expect(page.locator('#pe-content')).toContainText(letter); } // Innocent murmur panel await expect(page.locator('#pe-content')).toContainText(/Innocent murmurs/i); await expect(page.locator('#pe-content')).toContainText(/Still.s/i); await expect(page.locator('#pe-content')).toContainText(/Venous hum/i); // 7 "S" criteria footer await expect(page.locator('#pe-content')).toContainText(/7.*S/); }); test('each age group has resp + cv data (no "no data" empty state)', async ({ authedPage: _, page }) => { await openPEGuide(page); const ages = ['newborn', 'infant', 'toddler', 'preschool', 'school', 'adolescent']; for (const age of ages) { await selectAge(page, age); for (const sys of ['resp', 'cv']) { await switchSystem(page, sys); // Must NOT show the "no data" placeholder const content = await page.locator('#pe-content').innerText(); expect(content, `${age}/${sys} should have data`).not.toMatch(/No data for this combination/i); // Should have at least one component card with at least one step const stepCount = await page.locator('.pe-step').count(); expect(stepCount, `${age}/${sys} should have at least 1 step`).toBeGreaterThan(0); } } }); test('step toggle cycles Normal → Abnormal → Skip and updates visual state', async ({ authedPage: _, page }) => { await openPEGuide(page); await selectAge(page, 'adolescent'); await switchSystem(page, 'neuro'); const firstStep = page.locator('.pe-step').first(); // Click Normal (✓) button await firstStep.locator('button[data-pe-status="normal"]').click(); // Background should turn green (#ecfdf5) await expect.poll(async () => { return await firstStep.evaluate(el => el.style.background); }).toMatch(/rgb\(236, 253, 245\)|#ecfdf5/); // Click Abnormal (✗) — note field should appear await firstStep.locator('button[data-pe-status="abnormal"]').click(); await expect(firstStep.locator('.pe-note')).toBeVisible(); // Click Skip (—) — note field hides await firstStep.locator('button[data-pe-status="skip"]').click(); await expect(firstStep.locator('.pe-note')).toBeHidden(); }); test('grading scales card is collapsible and expands on click', async ({ authedPage: _, page }) => { await openPEGuide(page); await selectAge(page, 'adolescent'); await switchSystem(page, 'neuro'); //
element with "Grading scales" summary const details = page.locator('details').filter({ hasText: /Grading scales/ }); await expect(details).toBeVisible(); // Open it await details.locator('summary').click(); // Should now see at least one scale title await expect(details).toContainText(/MRC strength/); }); });