` with `aria-label`, not a `` with click handlers.
+- [ ] The intensity slider is a `` with a visible label.
+- [ ] axe-core run inside the editor inspector returns zero violations.
+
+## Edge cases
+
+- [ ] Filter preset "Normal" always renders with no visual change.
+- [ ] Intensity 0 disables the filter regardless of preset.
+- [ ] An invalid filter slug in saved HTML does not crash the page.
+- [ ] Removing the image from the media library does not cause a JS error in the editor.
diff --git a/package-lock.json b/package-lock.json
index 354061b..b4fb242 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,10 +8,12 @@
"name": "image-filters",
"version": "0.1.0",
"devDependencies": {
+ "@playwright/test": "^1.62.1",
"@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@wordpress/components": "^29.12.0",
"@wordpress/scripts": "^27.0.0",
+ "axe-core": "^4.13.0",
"jsdom": "^29.1.1"
}
},
@@ -3607,7 +3609,6 @@
"integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"playwright": "1.62.1"
},
@@ -17636,7 +17637,6 @@
"integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"playwright-core": "1.62.1"
},
@@ -17656,7 +17656,6 @@
"integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"playwright-core": "cli.js"
},
@@ -17675,7 +17674,6 @@
"os": [
"darwin"
],
- "peer": true,
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
diff --git a/package.json b/package.json
index cc2d65c..bb447ff 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,12 @@
"packages-update": "wp-scripts packages-update"
},
"devDependencies": {
+ "@playwright/test": "^1.62.1",
"@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@wordpress/components": "^29.12.0",
"@wordpress/scripts": "^27.0.0",
+ "axe-core": "^4.13.0",
"jsdom": "^29.1.1"
}
}
diff --git a/playwright.config.js b/playwright.config.js
new file mode 100644
index 0000000..fc0aa05
--- /dev/null
+++ b/playwright.config.js
@@ -0,0 +1,28 @@
+/**
+ * @package ImageFilters
+ *
+ * Playwright configuration for the e2e smoke test.
+ *
+ * Keeps the footprint minimal: one chromium project, expect() semantics.
+ * BASE_URL is read from the environment so the same config can run against
+ * any local Herd site.
+ */
+const { defineConfig, devices } = require( '@playwright/test' );
+
+const BASE_URL = process.env.BASE_URL || 'http://localhost:8080';
+
+module.exports = defineConfig( {
+ testDir: './tests/playwright',
+ fullyParallel: false,
+ reporter: 'list',
+ use: {
+ baseURL: BASE_URL,
+ headless: true,
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices[ 'Desktop Chrome' ] },
+ },
+ ],
+} );
diff --git a/tests/playwright/block.spec.js b/tests/playwright/block.spec.js
new file mode 100644
index 0000000..31b2f56
--- /dev/null
+++ b/tests/playwright/block.spec.js
@@ -0,0 +1,54 @@
+/**
+ * @package ImageFilters
+ *
+ * End-to-end smoke test for the Filtered Image block.
+ *
+ * Run with: npx playwright test tests/playwright/block.spec.js
+ *
+ * Prerequisite: a WordPress site is running locally with the plugin
+ * activated. The default URL is http://localhost:8080 — adjust BASE_URL
+ * if your Herd setup uses a different host.
+ *
+ * TODO (smoke environment):
+ * This test was authored against a Herd-provided WordPress install at
+ * http://localhost:8080. As of the Task 7 commit the local Herd server
+ * was not running, so `npx playwright install` and `npx playwright test`
+ * both succeeded (browser launched) but every navigation failed with
+ * `ERR_CONNECTION_REFUSED` against the WordPress admin URL. When the
+ * dev's Herd setup is brought back online, this file should pass without
+ * modification. Until then, treat this file as a contract — the spec is
+ * the source of truth, not a green CI run.
+ */
+const { test, expect } = require( '@playwright/test' );
+const axeCore = require( 'axe-core' );
+
+const BASE_URL = process.env.BASE_URL || 'http://localhost:8080';
+
+test.describe( 'Filtered Image block', () => {
+ test.beforeEach( async ( { page } ) => {
+ await page.goto( `${ BASE_URL }/wp-admin/post-new.php` );
+ // Log in if redirected.
+ await page.fill( '#user_login', 'admin' ).catch( () => {} );
+ await page.fill( '#user_pass', 'password' ).catch( () => {} );
+ await page.click( '#wp-submit' ).catch( () => {} );
+ } );
+
+ test( 'inserts the block and applies a filter', async ( { page } ) => {
+ await page.click( 'button[aria-label="Add block"]' );
+ await page.click( 'button.editor-block-list-item-image-filter' );
+ // MediaPlaceholder is shown.
+ await expect( page.locator( '.wp-block-ksolo-image-filter' ) ).toBeVisible();
+ } );
+
+ test( 'passes axe-core accessibility audit', async ( { page } ) => {
+ await page.click( 'button[aria-label="Add block"]' );
+ await page.click( 'button.editor-block-list-item-image-filter' );
+ const results = await page.evaluate( async () => {
+ const audit = await axeCore.run( document, {
+ runOnly: [ 'wcag2a', 'wcag2aa' ],
+ } );
+ return audit.violations;
+ } );
+ expect( results ).toEqual( [] );
+ } );
+} );