From 7ccb31dc77a8e5b5fa84df7aa98ea26ca011936d Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Mon, 31 Aug 2026 16:09:56 +0200 Subject: [PATCH 01/29] Try adding axe-core. --- package-lock.json | 21 ++++++++++++--- package.json | 1 + tests/e2e/specs/accessibility.spec.js | 38 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/specs/accessibility.spec.js diff --git a/package-lock.json b/package-lock.json index 66c8e2ff74616..91d983c2deed1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@343dev/gifsicle": "1.2.0", + "@axe-core/playwright": "4.13.0", "@lodder/grunt-postcss": "^3.1.1", "@napi-rs/image": "1.14.0", "@playwright/test": "1.61.1", @@ -134,6 +135,19 @@ "dev": true, "license": "ISC" }, + "node_modules/@axe-core/playwright": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.13.0.tgz", + "integrity": "sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "axe-core": "~4.13.0" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -9406,10 +9420,11 @@ "dev": true }, "node_modules/axe-core": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", - "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", "dev": true, + "license": "MPL-2.0", "engines": { "node": ">=4" } diff --git a/package.json b/package.json index 02255a40b245e..88bdb4f987b12 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ ], "devDependencies": { "@343dev/gifsicle": "1.2.0", + "@axe-core/playwright": "4.13.0", "@lodder/grunt-postcss": "^3.1.1", "@napi-rs/image": "1.14.0", "@playwright/test": "1.61.1", diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js new file mode 100644 index 0000000000000..252cdf20e806e --- /dev/null +++ b/tests/e2e/specs/accessibility.spec.js @@ -0,0 +1,38 @@ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; +const AxeBuilder = require( '@axe-core/playwright' ).default; + +test.describe( 'PHP Page Accessibility Tests', () => { + test( 'should not have any automatically detectable accessibility violations', async ( { admin, page, requestUtils } ) => { + + // await requestUtils.login(); + // await page.goto( 'http://localhost:8889/wp-admin/edit.php'); + await admin.visitAdminPage( '/edit.php' ); + + // 2. Run the axe scan on the rendered page + const scanResults = await new AxeBuilder({ page }).analyze(); + + // 3. Format and print violations individually if any are found + if (scanResults.violations.length > 0) { + console.log(`\n❌ Found ${scanResults.violations.length} accessibility violation(s):\n`); + + scanResults.violations.forEach((violation, index) => { + console.log(`--- Violation #${index + 1} ---`); + console.log(`Rule ID: ${violation.id}`); + console.log(`Impact: ${violation.impact.toUpperCase()}`); + console.log(`Failure: ${violation.description}`); + console.log(`Help Link: ${violation.helpUrl}`); + + // List every specific HTML element failing this rule + console.log('Failing Elements:'); + violation.nodes.forEach((node) => { + console.log(` - Target Selector: ${node.target.join(', ')}`); + console.log(` HTML Snippet: ${node.html}`); + }); + console.log('\n'); + }); + } + + // 3. Assert that the violations list is empty + expect(scanResults.violations).toEqual([]); + }); +}); From 1a162ef588375f698fa8214f66c57a39b3fe1457 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 1 Sep 2026 11:57:52 +0200 Subject: [PATCH 02/29] Clean up. --- src/js/media/views/attachments/browser.js | 3 +- tests/e2e/specs/accessibility.spec.js | 91 +++++++++++++++-------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/js/media/views/attachments/browser.js b/src/js/media/views/attachments/browser.js index 8d7d4dca51b76..fc791b1b51d89 100644 --- a/src/js/media/views/attachments/browser.js +++ b/src/js/media/views/attachments/browser.js @@ -254,7 +254,8 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro }).render() ); // DateFilter is a , a label element needs to be rendered before. // 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({ + this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({ value: l10n.filterByDate, attributes: { 'for': 'media-attachment-date-filters' From 1bd9db0f1ab42b9c1f38d6d5fd03e502ae13a099 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 8 Sep 2026 16:52:03 +0200 Subject: [PATCH 11/29] Create a few posts to test the Posts page. --- tests/e2e/utils/accessibility-pages.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/e2e/utils/accessibility-pages.js b/tests/e2e/utils/accessibility-pages.js index f8a6f916c5cb9..43ae8cc4049b5 100644 --- a/tests/e2e/utils/accessibility-pages.js +++ b/tests/e2e/utils/accessibility-pages.js @@ -71,6 +71,20 @@ const pages = [ stateVariants: [ { name: 'default', + setup: async ( page, requestUtils ) => { + // Create a published post. + await requestUtils.createPost( { + title: 'Test Published Post', + status: 'publish', + } ); + // Create a pending review post so there's something to filter. + await requestUtils.createPost( { + title: 'Test Pending Review Post', + status: 'pending', + } ); + // Reload the page to show the draft. + await page.reload(); + }, }, { name: 'draft-filter', @@ -82,7 +96,7 @@ const pages = [ } ); // Reload the page to show the draft. await page.reload(); - // Ensure table is visible before filtering. + // Ensure table is visible before filtering. const tableVisible = await page.locator( 'table.wp-list-table' ).isVisible(); if ( tableVisible ) { await filterByStatus( page, 'draft' ); From 1ab6745724ec71a50cbb3b650d57451455e6ca5b Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 9 Sep 2026 10:54:37 +0200 Subject: [PATCH 12/29] Rename accessibility-pages file. --- tests/e2e/specs/accessibility.spec.js | 4 ++-- .../{accessibility-pages.js => accessibility-pages-matrix.js} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename tests/e2e/utils/{accessibility-pages.js => accessibility-pages-matrix.js} (98%) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index 2ad784c3ed34b..325a31e0314f2 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -1,11 +1,11 @@ import { test, expect } from '@wordpress/e2e-test-utils-playwright'; const AxeScanner = require( '../utils/accessibility-axe-scanner' ); -const { pages } = require( '../utils/accessibility-pages' ); +const { pages } = require( '../utils/accessibility-pages-matrix' ); const ignoresAndknownFalsePositives = require( '../utils/accessibility-ignores-and-false-positives' ); /** * Global accessibility scan rules. - * Applied to all pages defined in utils/accessibility-pages. + * Applied to all pages defined in utils/accessibility-pages-matrix. * * See the Axe Options parameter documentation. * See: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter diff --git a/tests/e2e/utils/accessibility-pages.js b/tests/e2e/utils/accessibility-pages-matrix.js similarity index 98% rename from tests/e2e/utils/accessibility-pages.js rename to tests/e2e/utils/accessibility-pages-matrix.js index 43ae8cc4049b5..cfbcdf0707b0d 100644 --- a/tests/e2e/utils/accessibility-pages.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -1,5 +1,5 @@ /** - * Accessibility test pages registry. + * Accessibility test pages matrix. * * Defines WordPress admin pages to scan for accessibility violations. * Each page spec can have multiple state variants for testing different UI states. @@ -49,7 +49,7 @@ * - To change rule config per-page, modify the `rules` property. * * IMPORT IN TESTS: - * const { pages } = require( './accessibility-pages' ); + * const { pages } = require( './accessibility-pages-matrix' ); * pages.forEach( (pageSpec) => { ... } ); */ From 19ff7222ce2532092ae4b7f15c73f8693f8d1411 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 9 Sep 2026 11:07:10 +0200 Subject: [PATCH 13/29] Exclude post titles with surrounding text in list tables for link-in-text-block rule. --- tests/e2e/utils/accessibility-ignores-and-false-positives.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/utils/accessibility-ignores-and-false-positives.js b/tests/e2e/utils/accessibility-ignores-and-false-positives.js index 5420388255d1d..e235dd9ffda9a 100644 --- a/tests/e2e/utils/accessibility-ignores-and-false-positives.js +++ b/tests/e2e/utils/accessibility-ignores-and-false-positives.js @@ -43,6 +43,8 @@ module.exports = { // Post title links in list tables may have surrounding text and are only distinguished by color. // We consider this acceptable as these links can be distinguished by the context. '.wp-list-table strong .row-title', + // Em dashes for the pages hierarchy and other text e.g. post status are considered 'surrounding text'. + '.wp-list-table #the-list #post-* th strong a', ], 'aria-allowed-role': [ // The "group" role for the admin bar list items is flagged as invalid by axe-core. From f88015ffab2fe2c23202e81b22e4608cb67fe282 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Sun, 13 Sep 2026 10:05:51 +0200 Subject: [PATCH 14/29] Add more pages to test and two comments. --- tests/e2e/utils/accessibility-pages-matrix.js | 78 ++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/tests/e2e/utils/accessibility-pages-matrix.js b/tests/e2e/utils/accessibility-pages-matrix.js index cfbcdf0707b0d..006cd52f0a116 100644 --- a/tests/e2e/utils/accessibility-pages-matrix.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -62,6 +62,11 @@ const pages = [ path: '/', name: 'Dashboard', }, + { + id: 'updates', + path: '/update-core.php', + name: 'Updates', + }, // Posts & Pages. { @@ -105,11 +110,6 @@ const pages = [ }, ], }, - { - id: 'pages-list', - path: '/edit.php?post_type=page', - name: 'Pages', - }, { id: 'post-categories-list', path: '/edit-tags.php?taxonomy=category', @@ -120,6 +120,11 @@ const pages = [ path: '/edit-tags.php?taxonomy=post_tag', name: 'Tags', }, + { + id: 'pages-list', + path: '/edit.php?post_type=page', + name: 'Pages', + }, // Media. { @@ -153,6 +158,34 @@ const pages = [ id: 'comments', path: '/edit-comments.php', name: 'Comments', + stateVariants: [ + { + name: 'default', + setup: async ( page, requestUtils ) => { + // Create a post to attach comments to. + const { id: postId } = await requestUtils.createPost( { + title: 'Post for comments', + status: 'publish', + } ); + + // Create an approved comment. + await requestUtils.createComment( { + content: 'Test Approved Comment', + status: 'approve', + post: postId, + } ); + // Create a comment awaiting moderation. + await requestUtils.createComment( { + content: 'Test Comment Awaiting Moderation', + status: 'hold', + post: postId, + } ); + + // Reload the page to show the comments. + await page.reload(); + }, + }, + ], }, // Appearance. @@ -196,6 +229,13 @@ const pages = [ name: 'Add User', }, + // Admin profile. + { + id: 'profile', + path: '/profile.php', + name: 'Profile', + }, + // Tools. { id: 'tools', @@ -277,12 +317,32 @@ const pages = [ name: 'Settings - Privacy', }, - // Admin profile. + // About. { - id: 'profile', - path: '/profile.php', - name: 'Profile', + id: 'about', + path: '/about.php', + name: 'About', + }, + { + id: 'credits', + path: '/credits.php', + name: 'Credits', }, + { + id: 'freedoms', + path: '/freedoms.php', + name: 'Freedoms', + }, + { + id: 'privacy', + path: '/privacy.php', + name: 'Privacy', + }, + { + id: 'contribute', + path: '/contribute.php', + name: 'Get involved', + } ]; module.exports = { From 0d12e03acc7dae4bab5e29e77a8d1509fe3ff42c Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Sun, 13 Sep 2026 10:42:24 +0200 Subject: [PATCH 15/29] Speed up tests by using the wait interval only when necessary. --- tests/e2e/specs/accessibility.spec.js | 4 +++- tests/e2e/utils/accessibility-pages-matrix.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index 325a31e0314f2..a0663217517de 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -158,7 +158,9 @@ test.describe( 'Admin Pages Accessibility', () => { // 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 ); + if ( pageSpec.waitInterval ) { + await page.waitForTimeout( 500 ); + } // Run state setup if provided. if ( variant.setup ) { diff --git a/tests/e2e/utils/accessibility-pages-matrix.js b/tests/e2e/utils/accessibility-pages-matrix.js index 006cd52f0a116..a6cc381d2041d 100644 --- a/tests/e2e/utils/accessibility-pages-matrix.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -203,6 +203,7 @@ const pages = [ id: 'fonts-list', path: '/font-library.php?p=%2Ffont-list', name: 'Fonts', + waitInterval: true, }, // Plugins. @@ -279,6 +280,7 @@ const pages = [ id: 'settings-connectors', path: '/options-connectors.php', name: 'Settings - Connectors', + waitInterval: true, }, { From b43e5a5d762202d053ef22043d463d1e221fadaf Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Sun, 13 Sep 2026 13:02:45 +0200 Subject: [PATCH 16/29] Explain waitInterval. --- tests/e2e/utils/accessibility-pages-matrix.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/e2e/utils/accessibility-pages-matrix.js b/tests/e2e/utils/accessibility-pages-matrix.js index a6cc381d2041d..14fc6198d282a 100644 --- a/tests/e2e/utils/accessibility-pages-matrix.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -9,6 +9,11 @@ * - `path`: WordPress admin path relative to /wp-admin/ (e.g., '/upload.php?mode=grid') * - `name`: Human-readable name (shown in test output) * - `rules`: (optional) Per-page axe rules override. Merged with global rules. + * - `waitInterval`: (optional) Boolean indicating if the test should wait for a + * short interval before scanning. 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. * - `stateVariants`: (optional) Array of UI states to test. Each variant has: * - `name`: State identifier (shown in test name) * - `setup`: (optional) Async function to set up the state. Receives (page, requestUtils) objects. From fecb89a11621b0cf099b5c277df8cec9f3c18253 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Sun, 13 Sep 2026 13:54:54 +0200 Subject: [PATCH 17/29] Use media requestUtils. --- tests/e2e/specs/accessibility.spec.js | 29 +++++++++------------------ 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index a0663217517de..549f665472144 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -1,3 +1,4 @@ +import path from 'path'; import { test, expect } from '@wordpress/e2e-test-utils-playwright'; const AxeScanner = require( '../utils/accessibility-axe-scanner' ); const { pages } = require( '../utils/accessibility-pages-matrix' ); @@ -110,33 +111,21 @@ test.describe( 'Admin Pages Accessibility', () => { 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', - }, - } ); + const imagePath = path.resolve( __dirname, '../assets/sample.png' ) + const uploadedMedia = await requestUtils.uploadMedia( + imagePath + ); // Store the ID for cleanup later. - mediaAttachmentId = response.id; + mediaAttachmentId = uploadedMedia.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`, - } ); + await requestUtils.deleteMedia( + mediaAttachmentId + ); } } ); From 737885eeecea758c0cbd4d5bbfa9a81182c7c8ab Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Sun, 13 Sep 2026 15:01:50 +0200 Subject: [PATCH 18/29] Simplify beforeAll and afterAll. --- tests/e2e/specs/accessibility.spec.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index 549f665472144..1fec7b5b73a1e 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -112,21 +112,16 @@ test.describe( 'Admin Pages Accessibility', () => { test.beforeAll( async ( { requestUtils } ) => { // Upload sample image to media library for testing. const imagePath = path.resolve( __dirname, '../assets/sample.png' ) - const uploadedMedia = await requestUtils.uploadMedia( + await requestUtils.uploadMedia( imagePath ); - - // Store the ID for cleanup later. - mediaAttachmentId = uploadedMedia.id; } ); test.afterAll( async ( { requestUtils } ) => { - // Delete the uploaded image to restore initial state - if ( mediaAttachmentId ) { - await requestUtils.deleteMedia( - mediaAttachmentId - ); - } + // Delete all media. + await requestUtils.deleteAllMedia(); + // Delete all posts of type 'post' and all associated comments. + await requestUtils.deleteAllPosts(); } ); pages.forEach( ( pageSpec ) => { From 4082f9e2e315da3278fbbe7fad7fbe12e598b494 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Mon, 14 Sep 2026 11:27:37 +0200 Subject: [PATCH 19/29] Add ignore for the media attachments browser. --- tests/e2e/utils/accessibility-ignores-and-false-positives.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/utils/accessibility-ignores-and-false-positives.js b/tests/e2e/utils/accessibility-ignores-and-false-positives.js index e235dd9ffda9a..94f24c34e525b 100644 --- a/tests/e2e/utils/accessibility-ignores-and-false-positives.js +++ b/tests/e2e/utils/accessibility-ignores-and-false-positives.js @@ -51,6 +51,8 @@ module.exports = { // This is a known false positive in axe-core and can be ignored. Note // that the 'aria-allowed-role' rule is part of the 'best-practice' rules group. '#wp-toolbar #wp-admin-bar-*', + // Same known false positive in axe-core for the media attachments list browser. + '.attachments-browser .attachments-wrapper #__attachments-view-* .attachment', ], 'region': [ // Visually hidden text that clarifies what the ARIA live regions are about. From 0ad0ef384dab3e522ad9d7cc98636ea5b5323036 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 15 Sep 2026 23:22:45 +0200 Subject: [PATCH 20/29] Add the fonts Upload and Install Fonts tabs to the pages matrix. --- tests/e2e/utils/accessibility-pages-matrix.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/e2e/utils/accessibility-pages-matrix.js b/tests/e2e/utils/accessibility-pages-matrix.js index 14fc6198d282a..13f8ee862fc76 100644 --- a/tests/e2e/utils/accessibility-pages-matrix.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -209,6 +209,27 @@ const pages = [ path: '/font-library.php?p=%2Ffont-list', name: 'Fonts', waitInterval: true, + stateVariants: [ + { + name: 'default', + }, + { + name: 'fonts-upload-tab', + setup: async ( page ) => { + const uploadTab = page.getByRole( 'tab', { name: 'Upload' } ); + await uploadTab.click(); + await page.getByRole( 'tabpanel', { name: 'Upload' } ).isVisible(); + }, + }, + { + name: 'fonts-install-fonts-tab', + setup: async ( page ) => { + const installFontsTab = page.getByRole( 'tab', { name: 'Install Fonts' } ); + await installFontsTab.click(); + await page.getByRole( 'tabpanel', { name: 'Install Fonts' } ).isVisible(); + }, + }, + ], }, // Plugins. From eb2476a80d17bf2f4e9b37088fc6be3fe61c7fe2 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 23 Sep 2026 14:33:08 +0200 Subject: [PATCH 21/29] Pass more params to the setup helper in a destructured object. --- tests/e2e/specs/accessibility.spec.js | 4 ++-- tests/e2e/utils/accessibility-pages-matrix.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index 1fec7b5b73a1e..6706d80f2b1ce 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -133,7 +133,7 @@ test.describe( 'Admin Pages Accessibility', () => { const variantName = variant.name === 'default' ? '' : ` [${ variant.name }]`; const testName = `${ pageSpec.name }${ variantName } should not have violations`; - test( testName, async ( { admin, page, requestUtils } ) => { + test( testName, async ( { admin, editor, page, requestUtils } ) => { // Navigate to page. await admin.visitAdminPage( pageSpec.path ); @@ -148,7 +148,7 @@ test.describe( 'Admin Pages Accessibility', () => { // Run state setup if provided. if ( variant.setup ) { - await variant.setup( page, requestUtils ); + await variant.setup( { admin, editor, page, requestUtils } ); } // Scan and assert. diff --git a/tests/e2e/utils/accessibility-pages-matrix.js b/tests/e2e/utils/accessibility-pages-matrix.js index 13f8ee862fc76..23626be75ecb7 100644 --- a/tests/e2e/utils/accessibility-pages-matrix.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -37,7 +37,7 @@ * { name: 'default' }, * { * name: 'with-filter', - * setup: async (page, requestUtils) => { + * setup: async ( { admin, editor, page, requestUtils } ) => { // Create test data if needed. await requestUtils.createPost({ title: 'Test', status: 'draft' }); // Apply UI state. @@ -81,7 +81,7 @@ const pages = [ stateVariants: [ { name: 'default', - setup: async ( page, requestUtils ) => { + setup: async ( { page, requestUtils } ) => { // Create a published post. await requestUtils.createPost( { title: 'Test Published Post', @@ -98,7 +98,7 @@ const pages = [ }, { name: 'draft-filter', - setup: async ( page, requestUtils ) => { + setup: async ( { page, requestUtils } ) => { // Create a draft post so there's something to filter. await requestUtils.createPost( { title: 'Test Draft Post', @@ -142,7 +142,7 @@ const pages = [ }, { name: 'image-modal-open', - setup: async ( page ) => { + setup: async ( { page } ) => { // Click the image to open the attachment modal. const imageLink = page.locator( '.attachment' ).first(); await imageLink.click(); @@ -166,7 +166,7 @@ const pages = [ stateVariants: [ { name: 'default', - setup: async ( page, requestUtils ) => { + setup: async ( { page, requestUtils } ) => { // Create a post to attach comments to. const { id: postId } = await requestUtils.createPost( { title: 'Post for comments', @@ -215,7 +215,7 @@ const pages = [ }, { name: 'fonts-upload-tab', - setup: async ( page ) => { + setup: async ( { page } ) => { const uploadTab = page.getByRole( 'tab', { name: 'Upload' } ); await uploadTab.click(); await page.getByRole( 'tabpanel', { name: 'Upload' } ).isVisible(); @@ -223,7 +223,7 @@ const pages = [ }, { name: 'fonts-install-fonts-tab', - setup: async ( page ) => { + setup: async ( { page } ) => { const installFontsTab = page.getByRole( 'tab', { name: 'Install Fonts' } ); await installFontsTab.click(); await page.getByRole( 'tabpanel', { name: 'Install Fonts' } ).isVisible(); From a61b972d759c00a35f949256756e58e52be07581 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Fri, 25 Sep 2026 16:13:29 +0200 Subject: [PATCH 22/29] Add npm script to run the a11y test. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 88bdb4f987b12..382617cf7ac32 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,7 @@ "test:php": "node ./tools/local-env/scripts/docker.js run --rm php ./vendor/bin/phpunit", "test:coverage": "npm run test:php -- --coverage-html ./coverage/html/ --coverage-php ./coverage/php/report.php --coverage-text=./coverage/text/report.txt", "test:e2e": "wp-scripts test-playwright --config tests/e2e/playwright.config.js", + "test:e2e:a11y": "npm run test:e2e -- tests/e2e/specs/accessibility.spec.js", "test:visual": "wp-scripts test-playwright --config tests/visual-regression/playwright.config.js", "typecheck:php": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan --", "typecheck:php:baselines": "node ./tools/local-env/scripts/docker.js run --rm php composer phpstan:baselines --", From efe8f3fa5042af116ff2110f31309c842bb0df72 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Fri, 25 Sep 2026 17:12:48 +0200 Subject: [PATCH 23/29] Use toBeVisible. --- tests/e2e/specs/accessibility.spec.js | 2 +- tests/e2e/utils/accessibility-pages-matrix.js | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index 6706d80f2b1ce..697d38db0fa8d 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -148,7 +148,7 @@ test.describe( 'Admin Pages Accessibility', () => { // Run state setup if provided. if ( variant.setup ) { - await variant.setup( { admin, editor, page, requestUtils } ); + await variant.setup( { admin, editor, page, requestUtils, expect } ); } // Scan and assert. diff --git a/tests/e2e/utils/accessibility-pages-matrix.js b/tests/e2e/utils/accessibility-pages-matrix.js index 23626be75ecb7..8384d59d37815 100644 --- a/tests/e2e/utils/accessibility-pages-matrix.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -98,7 +98,7 @@ const pages = [ }, { name: 'draft-filter', - setup: async ( { page, requestUtils } ) => { + setup: async ( { page, requestUtils, expect } ) => { // Create a draft post so there's something to filter. await requestUtils.createPost( { title: 'Test Draft Post', @@ -107,10 +107,9 @@ const pages = [ // Reload the page to show the draft. await page.reload(); // Ensure table is visible before filtering. - const tableVisible = await page.locator( 'table.wp-list-table' ).isVisible(); - if ( tableVisible ) { - await filterByStatus( page, 'draft' ); - } + const draftPostsTable = page.locator( 'table.wp-list-table' ); + await expect( draftPostsTable ).toBeVisible(); + await filterByStatus( page, 'draft' ); }, }, ], @@ -215,18 +214,20 @@ const pages = [ }, { name: 'fonts-upload-tab', - setup: async ( { page } ) => { + setup: async ( { page, expect } ) => { const uploadTab = page.getByRole( 'tab', { name: 'Upload' } ); await uploadTab.click(); - await page.getByRole( 'tabpanel', { name: 'Upload' } ).isVisible(); + const uploadTabPanel = page.getByRole( 'tabpanel', { name: 'Upload' } ); + await expect( uploadTabPanel ).toBeVisible(); }, }, { name: 'fonts-install-fonts-tab', - setup: async ( { page } ) => { + setup: async ( { page, expect } ) => { const installFontsTab = page.getByRole( 'tab', { name: 'Install Fonts' } ); await installFontsTab.click(); - await page.getByRole( 'tabpanel', { name: 'Install Fonts' } ).isVisible(); + const installFontsTabPanel = page.getByRole( 'tabpanel', { name: 'Install Fonts' } ); + await expect( installFontsTabPanel ).toBeVisible(); }, }, ], From 48d4eebd8f062b1b82a6758d7881b4bb9553dd45 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Fri, 25 Sep 2026 17:35:59 +0200 Subject: [PATCH 24/29] Scan the Site Health Info page with WP section open. --- tests/e2e/utils/accessibility-pages-matrix.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/e2e/utils/accessibility-pages-matrix.js b/tests/e2e/utils/accessibility-pages-matrix.js index 8384d59d37815..fbcb772d9e83e 100644 --- a/tests/e2e/utils/accessibility-pages-matrix.js +++ b/tests/e2e/utils/accessibility-pages-matrix.js @@ -281,9 +281,14 @@ const pages = [ name: 'Tools - Export', }, { - id: 'site-health', + id: 'site-health-status', path: '/site-health.php', - name: 'Tools - Site Health', + name: 'Tools - Site Health Status', + }, + { + id: 'site-health-info-wp-section', + path: '/site-health.php?tab=debug#health-check-section-wp-core', + name: 'Tools - Site Health Info WP section open', }, { id: 'export-personal-data', From 70bcd4e213c9cf0417254863c5b99e1e94dacdd7 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Thu, 1 Oct 2026 12:37:01 +0200 Subject: [PATCH 25/29] Avoid deleting all existing attachments. --- tests/e2e/specs/accessibility.spec.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index 697d38db0fa8d..202046b3dc7fd 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -38,7 +38,7 @@ function filterIgnoresAndFalsePositives( results, exclusions ) { violation.nodes = violation.nodes.filter( ( node ) => { const targetSelector = node.target[ 0 ]; - /* + /* * Normalize the received Axe-core Target Selector: remove combinators, * attributes, and collapse spaces. This allows us to match patterns * against the normalized selector using token-based matching. For example: @@ -112,14 +112,20 @@ test.describe( 'Admin Pages Accessibility', () => { test.beforeAll( async ( { requestUtils } ) => { // Upload sample image to media library for testing. const imagePath = path.resolve( __dirname, '../assets/sample.png' ) - await requestUtils.uploadMedia( + const response = await requestUtils.uploadMedia( imagePath ); + + // Store the sample image ID for later cleanup. + mediaAttachmentId = response.id; } ); test.afterAll( async ( { requestUtils } ) => { - // Delete all media. - await requestUtils.deleteAllMedia(); + // Delete the uploaded sample image. + if ( mediaAttachmentId ) { + await requestUtils.deleteMedia( mediaAttachmentId ); + } + // Delete all posts of type 'post' and all associated comments. await requestUtils.deleteAllPosts(); } ); From 3dbf965d3995d961c71bffc87af483d497c84a0c Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Mon, 12 Oct 2026 21:35:31 +0200 Subject: [PATCH 26/29] Add links with surrounding text in the Media Library list to the ignored violations. --- tests/e2e/utils/accessibility-ignores-and-false-positives.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/utils/accessibility-ignores-and-false-positives.js b/tests/e2e/utils/accessibility-ignores-and-false-positives.js index 94f24c34e525b..7876cec8aa946 100644 --- a/tests/e2e/utils/accessibility-ignores-and-false-positives.js +++ b/tests/e2e/utils/accessibility-ignores-and-false-positives.js @@ -43,6 +43,8 @@ module.exports = { // Post title links in list tables may have surrounding text and are only distinguished by color. // We consider this acceptable as these links can be distinguished by the context. '.wp-list-table strong .row-title', + // Same for the Media Library list mode. + '#posts-filter #the-list #post-* .has-media-icon a', // Em dashes for the pages hierarchy and other text e.g. post status are considered 'surrounding text'. '.wp-list-table #the-list #post-* th strong a', ], From 0814a9f584852620f2db84352e4cc94eaed5e813 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 13 Oct 2026 20:56:14 +0200 Subject: [PATCH 27/29] Rename import to ignoresAndFalsePositives. --- tests/e2e/specs/accessibility.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/specs/accessibility.spec.js b/tests/e2e/specs/accessibility.spec.js index 202046b3dc7fd..c4ac17ac1f13e 100644 --- a/tests/e2e/specs/accessibility.spec.js +++ b/tests/e2e/specs/accessibility.spec.js @@ -2,7 +2,7 @@ import path from 'path'; import { test, expect } from '@wordpress/e2e-test-utils-playwright'; const AxeScanner = require( '../utils/accessibility-axe-scanner' ); const { pages } = require( '../utils/accessibility-pages-matrix' ); -const ignoresAndknownFalsePositives = require( '../utils/accessibility-ignores-and-false-positives' ); +const ignoresAndFalsePositives = require( '../utils/accessibility-ignores-and-false-positives' ); /** * Global accessibility scan rules. @@ -98,7 +98,7 @@ async function scanAndAssert( page, pageSpec, variant = null ) { let results = await scanner.scan(); // Filter out known ignores and false positives. - results = filterIgnoresAndFalsePositives( results, ignoresAndknownFalsePositives ); + results = filterIgnoresAndFalsePositives( results, ignoresAndFalsePositives ); scanner.formatResults( results ); From afbafb880aa1e9ce53ae0e7a1aea4de8eb8d29ad Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 13 Oct 2026 20:59:18 +0200 Subject: [PATCH 28/29] Remove leftover comment. --- src/js/media/views/attachments/browser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/media/views/attachments/browser.js b/src/js/media/views/attachments/browser.js index 51a44624e0bea..8d7d4dca51b76 100644 --- a/src/js/media/views/attachments/browser.js +++ b/src/js/media/views/attachments/browser.js @@ -254,7 +254,6 @@ AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.pro }).render() ); // DateFilter is a