diff --git a/CHANGELOG.md b/CHANGELOG.md index 07eb6ba..888081e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to `@ngbracket/a11y-devtools` are documented here. This project adheres to [Semantic Versioning](https://semver.org/). +## 0.15.8 + +### Fixed + +- Report mode now checks colour contrast below the fold when the app runs the + in-app overlay. The overlay's tab-order path is drawn on a fixed SVG that + runs past the bottom of the viewport. axe counted it as covering the text + there, so its contrast check returned "needs review" instead of a result, + and report mode doesn't list those. Contrast problems on parts of a page + below the report's 900px-tall viewport could go unreported, in light and + dark passes. Report mode now hides the overlay before it scans. On the + public demo this brings back two dark-mode contrast failures the dark pass + had missed. + ## 0.15.7 ### Fixed diff --git a/package-lock.json b/package-lock.json index 94b9af8..5b49207 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ngbracket/a11y-devtools", - "version": "0.15.7", + "version": "0.15.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ngbracket/a11y-devtools", - "version": "0.15.7", + "version": "0.15.8", "license": "MIT", "dependencies": { "axe-core": "^4.13.0" diff --git a/package.json b/package.json index e4d462e..aa0ef97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ngbracket/a11y-devtools", - "version": "0.15.7", + "version": "0.15.8", "description": "Finds accessibility problems in Angular apps and names the component behind each one. A dev overlay, or headless reports for CI.", "license": "MIT", "author": "Duncan Faulkner", diff --git a/src/report/headless.ts b/src/report/headless.ts index f52fa23..adf9e30 100644 --- a/src/report/headless.ts +++ b/src/report/headless.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'; import type { RunOptions as AxeRunOptions } from 'axe-core'; import type { Browser, Page } from 'playwright'; import { detectTabTrap, type FocusObservation } from '../keyboard/focus-walk.js'; +import { OVERLAY_EXCLUDE_SELECTOR } from '../overlay.js'; import type { A11yFinding } from '../scan.js'; import { findingsNotIn } from './baseline.js'; import type { PageReport, ScanChecks, ScanReport } from './format.js'; @@ -185,6 +186,16 @@ interface PassConfig { /** Settle time after switching a class/attribute theme, so colour transitions finish before axe reads them. */ const THEME_SETTLE_MS = 300; +/** + * Hides the in-app overlay (if the app runs it) for the headless scan. Its + * tab-order path is a fixed SVG that overflows the viewport, and axe's + * color-contrast counts it as covering text below the fold: that text came + * back "needs review" instead of pass/fail, so its contrast was never + * reported. Excluding the overlay from axe's context isn't enough; the exclude + * stops axe checking the overlay, not seeing it. + */ +const HIDE_OVERLAY_CSS = `${OVERLAY_EXCLUDE_SELECTOR} { display: none !important; }`; + /** Scan every route in one colour scheme, in its own browser context. */ async function scanPass( browser: Browser, @@ -224,6 +235,7 @@ async function scanPass( await page.waitForTimeout(THEME_SETTLE_MS); } if (config.beforeScan) await config.beforeScan(page, { colorScheme, route }); + await page.addStyleTag({ content: HIDE_OVERLAY_CSS }); await page.addScriptTag({ content: inPageScript }); const findings = (await page.evaluate((args) => { const w = window as unknown as { diff --git a/src/testing/e2e-report.spec.ts b/src/testing/e2e-report.spec.ts index 4f92816..c3fa4bb 100644 --- a/src/testing/e2e-report.spec.ts +++ b/src/testing/e2e-report.spec.ts @@ -141,6 +141,26 @@ const PAGES: Record = {

Attribute

Theme-dependent text

`, + // An app running the in-app overlay with its tab-order path drawn. The path's + // SVG is fixed over the viewport and overflows it, which made axe's + // color-contrast give up ("needs review") on text below the fold, so it was + // never reported. Report mode hides the overlay before it scans. The layer is + // a shown popover with a pill button, as keepInTopLayer and the toggle make it. + '/overlay-below-fold': ` +
+

Overlay

+
+

Low contrast, below the fold

+
+
+ + + +
+ + `, + // Plain axe + layout: a missing alt, and a positive tabindex that jumps up the page. '/axe': `
@@ -213,6 +233,11 @@ describe.skipIf(!ready)('report-mode in a real browser (E2E)', () => { expect(report.pages.map((p) => p.label)).toEqual(Object.keys(PAGES)); }); + it('checks contrast below the fold even when the app runs the in-app overlay', () => { + const below = page('/overlay-below-fold').findings.filter((f) => f.html.includes('below-fold')); + expect(below.map((f) => f.id)).toEqual(['color-contrast']); + }); + it('reports axe violations and layout-dependent keyboard findings', () => { expect(ids('/axe')).toContain('image-alt'); expect(ids('/axe')).toContain('ngbr/tab-order-mismatch');