Skip to content
Open
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
44 changes: 38 additions & 6 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"last 2 Opera versions"
],
"devDependencies": {
"@axe-core/playwright": "^4.13.0",
"@lodder/grunt-postcss": "^3.1.1",
"@playwright/test": "1.61.1",
"@pmmmwh/react-refresh-webpack-plugin": "0.6.2",
Expand Down
3 changes: 2 additions & 1 deletion src/js/media/views/attachments/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro
}).render() );

// DateFilter is a <select>, a label element needs to be rendered before.
this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
// Re-introduce the bug fixed in https://core.trac.wordpress.org/changeset/62851 for axe-core testing purposes.
this.toolbar.set( 'dateFilter', new wp.media.view.Label({
value: l10n.filterByDate,
attributes: {
'for': 'media-attachment-date-filters'
Expand Down
Binary file added tests/e2e/assets/sample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
155 changes: 155 additions & 0 deletions tests/e2e/specs/accessibility.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
const AxeScanner = require( '../utils/accessibility-axe-scanner' );
const { pages } = require( '../utils/accessibility-pages' );
const ignoresAndknownFalsePositives = require( '../utils/accessibility-ignores-and-false-positives' );

/**
* Global accessibility scan rules.
* Applied to all pages defined in utils/accessibility-pages.
*
* See the Axe Options parameter documentation.
* See: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter
*/
const globalRules = {
runOnly: [ 'wcag2a', 'wcag2aa', 'best-practice' ],
absolutePaths: true, // Report the absolute CSS target selector for better exclusions mechanism.
};

/**
* Filters violations to remove violations we want to intentionally ignore and known false positives.
* Only returns violations for nodes that don't match excluded selectors.
*
* See more details in tests/e2e/utils/accessibility-ignores-and-false-positives.js.
*
* @param {Object} results Axe results object.
* @param {Object} exclusions Explicit exclusions and known false positives config (rule ID → selectors[]).
* @return {Object} Filtered results.
*/
function filterIgnoresAndFalsePositives( results, exclusions ) {
results.violations = results.violations.map( ( violation ) => {
const excludedSelectors = exclusions[ violation.id ] || [];

if ( excludedSelectors.length === 0 ) {
return violation;
}

// Filter out nodes matching excluded selectors.
violation.nodes = violation.nodes.filter( ( node ) => {
const targetSelector = node.target[ 0 ];

// Check if target selector matches any excluded selector pattern.
const isExcluded = excludedSelectors.some( ( excluded ) => {
const selectors = excluded.trim().split( ' ' ).filter( s => s.length > 0 );
return selectors.every( selector => {
// Escape special regex chars, then replace \* with .* for wildcard matching
const escaped = selector.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
const wildcard = escaped.replace( /\\\*/g, '.*' );
const pattern = `(?:^| )${ wildcard }(?=[\\s>+~\\[]|$)`;
return new RegExp( pattern ).test( targetSelector );
} );
} );

return ! isExcluded;
} );

return violation;
} ).filter( ( violation ) => violation.nodes.length > 0 );

return results;
}

/**
* Scans a page for accessibility violations and asserts no violations found.
*
* @param {Object} page Playwright page object.
* @param {Object} pageSpec Page specification object.
* @param {Object} [variant] Optional state variant spec.
* @return {Promise<void>}
*/
async function scanAndAssert( page, pageSpec, variant = null ) {
// Merge rules: global < page < variant.
const mergedRules = {
...globalRules,
...( pageSpec.rules || {} ),
...( variant?.rules || {} ),
};

// Scan and assert.
const scanner = new AxeScanner( { page, globalRules: mergedRules } );
let results = await scanner.scan();

// Filter out known ignores and false positives.
results = filterIgnoresAndFalsePositives( results, ignoresAndknownFalsePositives );

scanner.formatResults( results );

// Use expect for cleaner error reporting at test location.
expect( scanner.getViolationsCount( results ) ).toBe( 0 );

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations

4) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations

4) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations

3) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations

3) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations

2) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations

2) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations

1) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 3 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG enabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations

1) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 3 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations

4) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations

4) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Settings - Connectors should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations

3) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations

3) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Add Themes should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations

2) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations

2) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) [image-modal-open] should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations

1) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 3 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5

Check failure on line 87 in tests/e2e/specs/accessibility.spec.js

View workflow job for this annotation

GitHub Actions / E2E Tests / SCRIPT_DEBUG disabled

[chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations

1) [chromium] › tests/e2e/specs/accessibility.spec.js:134:8 › Admin Pages Accessibility › Media Library (Grid View) should not have violations Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 3 85 | 86 | // Use expect for cleaner error reporting at test location. > 87 | expect( scanner.getViolationsCount( results ) ).toBe( 0 ); | ^ 88 | } 89 | 90 | test.describe( 'Admin Pages Accessibility', () => { at scanAndAssert (/home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:87:50) at /home/runner/work/wordpress-develop/wordpress-develop/tests/e2e/specs/accessibility.spec.js:151:5
}

test.describe( 'Admin Pages Accessibility', () => {
let mediaAttachmentId = null;

test.beforeAll( async ( { requestUtils } ) => {
// Upload sample image to media library for testing.
const fs = require( 'fs' );
const path = require( 'path' );

const imagePath = path.join( __dirname, '../assets/sample.png' );
const imageBuffer = fs.readFileSync( imagePath );

const response = await requestUtils.rest( {
method: 'POST',
path: 'wp/v2/media',
data: imageBuffer,
headers: {
'Content-Disposition': 'attachment; filename="sample.png"',
'Content-Type': 'image/png',
},
} );

// Store the ID for cleanup later.
mediaAttachmentId = response.id;
} );

test.afterAll( async ( { requestUtils } ) => {
// Delete the uploaded image to restore initial state
if ( mediaAttachmentId ) {
await requestUtils.rest( {
method: 'DELETE',
path: `wp/v2/media/${ mediaAttachmentId }?force=true`,
} );
}
} );

pages.forEach( ( pageSpec ) => {
// Normalize: pages without stateVariants get a default variant.
const variants = pageSpec.stateVariants || [ { name: 'default' } ];

variants.forEach( ( variant ) => {
// Generate test name: omit [variant.name] if default.
const variantName = variant.name === 'default' ? '' : ` [${ variant.name }]`;
const testName = `${ pageSpec.name }${ variantName } should not have violations`;

test( testName, async ( { admin, page, requestUtils } ) => {
// Navigate to page.
await admin.visitAdminPage( pageSpec.path );

// Wait for any async content to render. Some pages load content
// dynamically after the DOM is ready. This is particularly important
// for pages rendered via React components like the Settings > Connectors
// page or the Fonts page. Without this wait, the scan may run before all
// content is fully rendered.
await page.waitForTimeout( 500 );

// Run state setup if provided.
if ( variant.setup ) {
await variant.setup( page, requestUtils );
}

// Scan and assert.
await scanAndAssert( page, pageSpec, variant );
} );
} );
} );
} );
135 changes: 135 additions & 0 deletions tests/e2e/utils/accessibility-axe-scanner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* AxeScanner utility for accessibility scanning with axe-core.
*
* Encapsulates axe-core scanning logic, result formatting, and assertions.
* Can be configured with global rules and logger functions.
*
* @example
* const scanner = new AxeScanner({ page, globalRules: { runOnly: ['wcag2a', 'wcag2aa'] } });
* const results = await scanner.scan();
* scanner.formatResults(results);
* scanner.assertNoViolations(results);
*/

const AxeBuilder = require( '@axe-core/playwright' ).default;

class AxeScanner {
/**
* Create an AxeScanner instance.
*
* @param {Object} options
* @param {Object} options.page Playwright page object.
* @param {Object} [options.globalRules] Rules config passed to axe (e.g., { runOnly: ['wcag2a', 'wcag2aa'], rules: {...} }).
* @param {Function} [options.logger] Custom logger function (defaults to console.log).
*/
constructor( { page, globalRules = {}, logger = console.log } ) {
this.page = page;
this.globalRules = globalRules;
this.logger = logger;
}

/**
* Runs axe-core scan on the current page.
*
* @param {Object} [overrideOptions] Additional options to merge with globalRules (useful for per-page overrides).
* @returns {Promise<Object>} Axe results object (violations, passes, incomplete, inapplicable).
*/
async scan( overrideOptions = {} ) {
const options = this._mergeRulesOptions( this.globalRules, overrideOptions );

const results = await new AxeBuilder( { page: this.page } )
.options( options )
.analyze();

return results;
}

/**
* Formats and logs scan results to console.
* Outputs violation details, element selectors, and HTML snippets.
*
* @param {Object} results Axe results object.
*/
formatResults( results ) {
const violationsAmount = results.violations.length;

if ( violationsAmount === 0 ) {
this.logger( 'No accessibility violations found!' );
return;
}

this.logger( `\nFound ${ violationsAmount } accessibility violation(s):\n` );

/*
* Result Object documentation: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#results-object
* This object has four components:
* - a `passes` array: keeps track of all the passed tests,
* along with detailed information on each one.
* - a `violations` array: keeps track of all the failed tests,
* along with detailed information on each one.
* - an `incomplete` array: indicates which nodes could neither be
* determined to definitively pass or definitively
* fail. They are separated out in order that
* a user interface can display these to the
* user for manual review
* - an `inapplicable` array: lists all the rules for which no matching
* elements were found on the page.
*/
results.violations.forEach( ( violation, index ) => {
this.logger( `--- Violation #${ index + 1 } ---` );
this.logger( `Rule ID: ${ violation.id }` );
this.logger( `Impact: ${ violation.impact.toUpperCase() }` );
this.logger( `Failure: ${ violation.description }` );
this.logger( `Help: ${ violation.help }` );
this.logger( `Help link: ${ violation.helpUrl }` );

// List every specific HTML element failing a failing rule.
this.logger( 'Failing elements:' );
violation.nodes.forEach( ( node ) => {
this.logger( ` - Target Selector: ${ node.target.join( ', ' ) }` );
this.logger( ` - HTML Snippet:\n${ node.html }` );
} );
this.logger( '\n' );
} );
}

/**
* Asserts that there are no accessibility violations.
* Returns the violations amount; let the test use expect() for cleaner error reporting.
*
* @param {Object} results Axe results object.
* @returns {number} Number of violations found.
*/
getViolationsCount( results ) {
return results.violations.length;
}

/**
* Merges rules options, with overrides taking precedence over global rules..
*
* @private
* @param {Object} globalRules Global rules config.
* @param {Object} overrideRules Override rules config.
* @returns {Object} Merged rules object.
*/
_mergeRulesOptions( globalRules, overrideRules ) {
// Deep merge rules object to allow per-rule overrides
if ( globalRules.rules && overrideRules.rules ) {
return {
...globalRules,
...overrideRules,
rules: {
...globalRules.rules,
...overrideRules.rules,
},
};
}

return {
...globalRules,
...overrideRules,
};
}
}

module.exports = AxeScanner;
Loading
Loading