import { chromium } from 'playwright'; import assert from 'node:assert/strict'; import { mkdir } from 'node:fs/promises'; await mkdir('artifacts', { recursive: true }); const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2, serviceWorkers: 'block' }); const page = await context.newPage(); const errors = []; page.on('console', message => { if (message.type() === 'error') errors.push(message.text()); }); page.on('pageerror', error => errors.push(error.message)); await page.goto('http://127.0.0.1:4173', { waitUntil: 'networkidle' }); await page.evaluate(() => localStorage.clear()); await page.reload({ waitUntil: 'networkidle' }); // Open the manual logger, walk to the safety-check step. await page.locator('[data-log]').first().click(); await page.locator('[data-type="6"]').click(); await page.locator('#next').click(); await page.locator('#next').click(); // The urgent alert container must be an assertive live region so a screen // reader announces it the moment a red-flag symptom is checked. assert.equal( await page.locator('#urgent-box[role="alert"]').count(), 1, '#urgent-box is an assertive role="alert" live region', ); assert.equal( await page.locator('#urgent-box[aria-live="assertive"]').count(), 1, '#urgent-box carries aria-live="alert"', ); // Check blood → the alert appears and is announced. await page.locator('[data-symptom="blood"]').check(); const alertBox = page.locator('#urgent-box'); assert.equal(await alertBox.getAttribute('role'), 'alert'); assert.match(await alertBox.innerText(), /Pause and get medical help/i); const liveValue = await alertBox.getAttribute('aria-live'); assert.equal(liveValue, 'assertive'); // Dialog semantics: sheet traps description and close button has a name. const sheet = page.locator('.sheet').last(); assert.equal(await sheet.getAttribute('role'), 'dialog'); assert.equal(await sheet.getAttribute('aria-modal'), 'true'); assert.ok(await sheet.getAttribute('aria-labelledby'), 'sheet names itself via aria-labelledby'); assert.equal(await page.locator('.icon-btn[aria-label="Close"]').last().isVisible(), true); // Escape closes the dialog (keyboard path out of the modal). await page.locator('.sheet').last().click(); // ensure focus is inside the dialog await page.locator('.icon-btn[aria-label="Close"]').last().focus(); await page.keyboard.press('Escape'); assert.equal(await page.locator('.sheet-backdrop').count(), 0, 'Escape dismisses the log sheet'); // Re-open and capture the red-flag alert visual for review. await page.locator('[data-log]').first().click(); await page.locator('[data-type="6"]').click(); await page.locator('#next').click(); await page.locator('#next').click(); await page.locator('[data-symptom="blood"]').check(); await page.waitForTimeout(150); await page.screenshot({ path: 'artifacts/a11y-redflag-alert.png', fullPage: true }); assert.equal(await page.locator('#urgent-box .alert').isVisible(), true); assert.deepEqual(errors, []); await browser.close(); console.log('PASS urgent-alert screen-reader announcement + dialog keyboard semantics');