open from the Bedside tab
The #img-lightbox overlay markup was sitting at the bottom of
calculators.html. Before the reorg it was fine — the calculators tab
was always the only home for bedside, so by the time a user clicked
the seizure or NRP pathway button the lightbox HTML was guaranteed to
be in the DOM. After promoting Bedside to its own tab, a user can
open Bedside -> Seizures without having visited Calculators first;
the lightbox JS's getElementById('img-lightbox') then returns null
and the click silently no-ops.
Moved the overlay markup to the bottom of index.html so it exists
from page load regardless of which tab has been lazy-loaded. The e2e
harness gets a duplicate copy so the existing lightbox smoke test
keeps working.
Added a regression test for NRP (bedside-smoke.spec.js:141) to catch
any future breakage — the prior seizure-only test didn't exercise the
second pathway button and so the neonatal/NRP path had never been
clicked in CI.
Suite: 252 passed / 0 failed.
161 lines
7.4 KiB
JavaScript
161 lines
7.4 KiB
JavaScript
// Bedside module smoke tests.
|
|
// The harness renders the Calculators + Bedside components side-by-side, so
|
|
// each test selects the target sub-pill (if any) and asserts a known string
|
|
// is rendered. Bedside was promoted to a top-level tab, so there is no longer
|
|
// a calc-nav-pill[data-calc="bedside"] — the panel is always visible in the
|
|
// harness and always the full tab in the live app.
|
|
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
async function openCalculators(page) {
|
|
await page.goto('/e2e-harness.html');
|
|
await page.waitForFunction(() => window.__harnessReady === true);
|
|
// Wait for the bedside component to finish injecting — #bedside-age is the
|
|
// first input in the shared age→weight estimator at the top of the tab.
|
|
await page.waitForSelector('#bedside-age');
|
|
}
|
|
|
|
async function openBedside(page, subPill) {
|
|
await openCalculators(page);
|
|
if (subPill) {
|
|
await page.click(`button.calc-pill[data-em="${subPill}"]`);
|
|
}
|
|
}
|
|
|
|
test.describe('Bedside — top-level', () => {
|
|
test('Calculators tab shows age-weight estimator', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await expect(page.locator('#bedside-age')).toBeVisible();
|
|
await expect(page.locator('#bedside-formula')).toBeVisible();
|
|
await expect(page.locator('#bedside-weight')).toBeVisible();
|
|
});
|
|
|
|
test('Age → Weight: typing 3y auto-fills weight (APLS)', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await page.fill('#bedside-age', '3y');
|
|
await expect(page.locator('#bedside-weight')).toHaveValue('14');
|
|
await expect(page.locator('#bedside-estimate-note')).toContainText('APLS');
|
|
});
|
|
|
|
test('Formula switch to Best Guess updates weight', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await page.fill('#bedside-age', '3y');
|
|
await page.selectOption('#bedside-formula', 'bestguess');
|
|
await expect(page.locator('#bedside-weight')).toHaveValue('16'); // 2 * (3+5) = 16
|
|
await expect(page.locator('#bedside-estimate-note')).toContainText('Best Guess');
|
|
});
|
|
|
|
test('Clear button resets the estimator', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await page.fill('#bedside-age', '5y');
|
|
await page.click('#btn-bedside-clear');
|
|
await expect(page.locator('#bedside-age')).toHaveValue('');
|
|
await expect(page.locator('#bedside-weight')).toHaveValue('');
|
|
});
|
|
});
|
|
|
|
test.describe('Bedside — every sub-pill renders', () => {
|
|
const subPills = [
|
|
{ key: 'neonatal', expected: /Neonatal Assessment/i },
|
|
{ key: 'airway', expected: /Airway Management/i },
|
|
{ key: 'cardiac', expected: /Cardiac Arrest/i },
|
|
{ key: 'respiratory', expected: /Respiratory Management/i },
|
|
{ key: 'ventilation', expected: /Oxygen & Ventilation/i },
|
|
{ key: 'seizure', expected: /Status Epilepticus/i },
|
|
{ key: 'sepsis', expected: /Sepsis & Fever/i },
|
|
{ key: 'anaphylaxis', expected: /Anaphylaxis/i },
|
|
{ key: 'sedation', expected: /Procedural Sedation/i },
|
|
{ key: 'agitation', expected: /Acute Agitation/i },
|
|
{ key: 'antiemetics', expected: /Antiemetics/i },
|
|
{ key: 'antimicrobials', expected: /Empiric Antimicrobials/i },
|
|
{ key: 'burns', expected: /Burn Management/i },
|
|
{ key: 'toxicology', expected: /Toxicology/i },
|
|
{ key: 'trauma', expected: /Trauma/i },
|
|
];
|
|
|
|
for (const { key, expected } of subPills) {
|
|
test(`${key} sub-pill shows header`, async ({ page }) => {
|
|
await openBedside(page, key);
|
|
await expect(page.locator(`#em-${key}`)).toContainText(expected);
|
|
});
|
|
}
|
|
});
|
|
|
|
test.describe('Bedside — dose calculators fire', () => {
|
|
test('Status Epilepticus: Show Pathway renders timeline with weight', async ({ page }) => {
|
|
await openBedside(page, 'seizure');
|
|
await page.fill('#seizure-weight', '20');
|
|
await page.click('#btn-seizure-calc');
|
|
await expect(page.locator('#seizure-result')).toContainText(/20 kg/);
|
|
await expect(page.locator('#seizure-result')).toContainText(/Lorazepam/);
|
|
await expect(page.locator('#seizure-result')).toContainText(/0\.1 mg\/kg/); // per-kg visible
|
|
});
|
|
|
|
test('Sepsis: Show Approach renders Phoenix criteria + first-hour bundle', async ({ page }) => {
|
|
await openBedside(page, 'sepsis');
|
|
await page.fill('#sepsis-weight', '25');
|
|
await page.click('#btn-sepsis-show');
|
|
await expect(page.locator('#sepsis-result')).toContainText(/Phoenix/);
|
|
await expect(page.locator('#sepsis-result')).toContainText(/first-hour/i);
|
|
});
|
|
|
|
test('Anaphylaxis: Calculate Doses shows weight-based epinephrine', async ({ page }) => {
|
|
await openBedside(page, 'anaphylaxis');
|
|
await page.fill('#anaph-weight', '25');
|
|
await page.click('#btn-anaph-calc');
|
|
await expect(page.locator('#anaph-result')).toContainText(/Epinephrine/);
|
|
await expect(page.locator('#anaph-result')).toContainText(/0\.25 mg/); // 25*0.01
|
|
});
|
|
|
|
test('Burns: body-parts calculator + Parkland', async ({ page }) => {
|
|
await openBedside(page, 'burns');
|
|
await page.fill('#burn-weight', '20');
|
|
await page.fill('input[data-burn-region="head"]', '50'); // 50% of 13 (young) = 6.5
|
|
await page.fill('input[data-burn-region="ant_trunk"]', '100'); // 100% of 13 = 13
|
|
await page.click('#btn-burn-calc');
|
|
await expect(page.locator('#burn-result')).toContainText(/Parkland/);
|
|
await expect(page.locator('#burn-result')).toContainText(/20 kg/);
|
|
});
|
|
|
|
test('Airway: Calculate renders RSI drugs with per-kg', async ({ page }) => {
|
|
await openBedside(page, 'airway');
|
|
await page.fill('#airway-weight', '20');
|
|
await page.fill('#airway-age', '5');
|
|
await page.click('#btn-airway-calc');
|
|
await expect(page.locator('#airway-result')).toContainText(/Ketamine/);
|
|
await expect(page.locator('#airway-result')).toContainText(/mg\/kg/); // per-kg visible
|
|
await expect(page.locator('#airway-result')).toContainText(/ETT/);
|
|
});
|
|
});
|
|
|
|
test.describe('Bedside — interactive widgets', () => {
|
|
test('Lightbox: seizure pathway image opens and closes', async ({ page }) => {
|
|
await openBedside(page, 'seizure');
|
|
await page.click('button[data-img-src="/img/epilepsy_eiic_pathway.png"]');
|
|
await expect(page.locator('#img-lightbox')).toBeVisible();
|
|
await expect(page.locator('#img-lightbox-img')).toHaveAttribute('src', /epilepsy_eiic_pathway/);
|
|
await page.click('#img-lightbox-close');
|
|
await expect(page.locator('#img-lightbox')).toBeHidden();
|
|
});
|
|
|
|
test('Lightbox: NRP pathway image opens on neonatal sub-pill', async ({ page }) => {
|
|
await openBedside(page, 'neonatal');
|
|
// The "View pathway image" button sits inside a collapsed <details>
|
|
// titled "NRP Resuscitation Pathway" — expand it first.
|
|
await page.getByText('NRP Resuscitation Pathway').click();
|
|
await page.click('button[data-img-src="/img/nrp_pathway.png"]');
|
|
await expect(page.locator('#img-lightbox')).toBeVisible();
|
|
await expect(page.locator('#img-lightbox-img')).toHaveAttribute('src', /nrp_pathway/);
|
|
await page.click('#img-lightbox-close');
|
|
await expect(page.locator('#img-lightbox')).toBeHidden();
|
|
});
|
|
|
|
test('Ventilation: Show Reference renders pressure-time SVG', async ({ page }) => {
|
|
await openBedside(page, 'ventilation');
|
|
await page.fill('#vent-weight', '20');
|
|
await page.click('#btn-vent-show');
|
|
await expect(page.locator('#vent-result svg')).toBeVisible();
|
|
await expect(page.locator('#vent-result')).toContainText(/Target SpO2/);
|
|
await expect(page.locator('#vent-result')).toContainText(/PEEP/);
|
|
});
|
|
});
|