Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 12 additions & 0 deletions src/report/headless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions src/testing/e2e-report.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,26 @@ const PAGES: Record<string, string> = {
</style>
<main><h1>Attribute</h1><p class="note">Theme-dependent text</p></main>`,

// 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': `
<main>
<h1>Overlay</h1>
<div style="height: 1500px"></div>
<p class="below-fold" style="color: #b0b0b0; background: #ffffff">Low contrast, below the fold</p>
</main>
<div id="overlay" data-ngb-a11y-overlay popover="manual"
style="position: fixed; inset: 0; width: auto; height: auto; margin: 0; padding: 0; border: 0; background: transparent; pointer-events: none">
<svg data-ngb-a11y-overlay style="position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible">
<polyline points="10,10 300,1700" fill="none" stroke="teal" stroke-width="2" />
</svg>
</div>
<button data-ngb-a11y-overlay role="switch" aria-checked="true" style="position: fixed; left: 8px; bottom: 8px">a11y</button>
<script>document.getElementById('overlay').showPopover();</script>`,

// Plain axe + layout: a missing alt, and a positive tabindex that jumps up the page.
'/axe': `
<main>
Expand Down Expand Up @@ -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');
Expand Down
Loading