test(e2e): PE Guide smoke — 8 tests covering all systems + age-group coverage
Uses the shared fixture so pageerror + console.error fail the test —
would catch any regression of the bug-class that shipped the SSO
ReferenceError. All tests log in as the seeded e2e user and drive the
real UI.
Coverage:
- tab loads with empty-state before age selected
- MSK (default) renders with scales + steps
- switch to Neuro shows MRC scale + teaching pearl
- Respiratory shows 8-sound library with play buttons + RR scale
- Cardiovascular shows APTM image (naturalWidth > 0 — actual network
fetch confirmed), all 5 landmark letters, innocent-murmur panel,
7 "S" criteria footer
- parametrised: every age group × {resp, cv} must NOT show "no data",
must have ≥ 1 step (catches the regression Daniel just flagged)
- step toggle cycles Normal → Abnormal → Skip with visual state change
and note-field show/hide
- grading scales <details> is collapsible and expands on click
This commit is contained in:
parent
8cabe7da4b
commit
abb67bd03a
1 changed files with 157 additions and 0 deletions
157
e2e/tests/pe-guide-smoke.spec.js
Normal file
157
e2e/tests/pe-guide-smoke.spec.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// ============================================================
|
||||
// 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');
|
||||
// <details> 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/);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue