Some checks failed
Quality gates / quality (pull_request) Failing after 2m25s
Strict RED-GREEN per defect, verified in a real browser:
- Nav buttons expose accessible names ('Today navigation item') and
the active view carries aria-current=page
- Urgent red-flag box is an assertive role=alert live region so screen
readers announce 'Pause and get medical help' the moment a red-flag
symptom is checked
- Log sheet dismisses on Escape (keyboard path out of the modal)
- --muted darkened #756e68 -> #6a635c: muted text now meets WCAG AA
4.5:1 on paper, surface, and soft backgrounds
- New npm run test:a11y gates (Playwright, mobile viewport):
landmarks/labels/focus-ring/touch-targets/reduced-motion,
urgent-alert announcement + dialog semantics, token contrast math
and 200% text-zoom resilience; each writes a synthetic screenshot
to artifacts/a11y-*.png for human inspection
- CI runs test:a11y and uploads the screenshots as artifacts
- sleek-chat selector updated to the new accessible nav name
Privacy/safety paths unchanged: local-first storage, consent-gated AI,
deterministic urgent override all still pass existing suites.
66 lines
3.1 KiB
JavaScript
66 lines
3.1 KiB
JavaScript
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');
|