From f8d3fc18074c02daf9f6f753573beeb7c6e9d43b Mon Sep 17 00:00:00 2001 From: Maximilian Mayer Date: Thu, 10 Sep 2026 14:07:06 +0200 Subject: [PATCH 1/3] test: add axe and keyboard accessibility checks Signed-off-by: Maximilian Mayer --- e2e/a11y.spec.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 25 ++++++++++++++++++++++++ package.json | 1 + 3 files changed, 75 insertions(+) create mode 100644 e2e/a11y.spec.ts diff --git a/e2e/a11y.spec.ts b/e2e/a11y.spec.ts new file mode 100644 index 0000000..5a41014 --- /dev/null +++ b/e2e/a11y.spec.ts @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 LibreCode coop and contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { test, expect } from "@playwright/test"; +import AxeBuilder from "@axe-core/playwright"; + +test.describe("axe", () => { +test.beforeEach(async ({page}) => { + await page.goto("/"); + await page.getByRole("button", {name: "Load sample PDF"}).click(); + await expect(page.locator("canvas").first()).toBeVisible(); + await page.getByRole("button", {name: "Add Signature"}).click(); + await expect(page.getByRole("button", {name: "Click to place"})).toBeVisible(); + await page.locator(".overlay").first().click({position: {x:100,y:100}}); + await expect(page.locator(".resize-handle").first()).toBeVisible(); +}) + +test("has no accessibility violations", async({page})=>{ + const results = await new AxeBuilder({page}).analyze(); + const summary = results.violations.map(v => `${v.id} (${v.impact}): ${v.nodes.length} nodes`); + expect(summary).toEqual([]); + expect(results.violations).toEqual([]); +}) +}) + +test.describe("keyboard", () => { +test("toolbar buttons can be opened with the keyboard", async({page})=>{ + await page.goto("/"); + const btn = page.getByRole("button", { name: "Load sample PDF" }); + await btn.focus(); + await expect(btn).toBeFocused(); + await page.keyboard.press("Enter"); + await expect(page.locator("canvas").first()).toBeVisible(); + +}) + test("focus is visible on toolbar buttons", async ({ page }) => { + await page.goto("/"); + const btn = page.getByRole("button", { name: "Load sample PDF" }); + const focusStyles = () => btn.evaluate(el => { + const style = getComputedStyle(el); + return [style.outlineStyle, style.outlineWidth, style.outlineColor, style.boxShadow].join(" | "); + }); + const unfocused = await focusStyles(); + await page.keyboard.press("Tab"); + await expect(btn).toBeFocused(); + const focused = await focusStyles(); + expect(focused).not.toBe(unfocused); + }) +}) diff --git a/package-lock.json b/package-lock.json index 512be64..a46645f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "vue": "^3.5.28" }, "devDependencies": { + "@axe-core/playwright": "^4.13.0", "@eslint/js": "^10.0.1", "@nextcloud/browserslist-config": "^3.1.2", "@playwright/test": "^1.63.0", @@ -41,6 +42,19 @@ "npm": "^11" } }, + "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/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", @@ -1614,6 +1628,16 @@ "node": ">=12" } }, + "node_modules/axe-core": { + "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" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -3347,6 +3371,7 @@ "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, diff --git a/package.json b/package.json index c2ae096..2b892fa 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "vue": "^3.5.28" }, "devDependencies": { + "@axe-core/playwright": "^4.13.0", "@eslint/js": "^10.0.1", "@nextcloud/browserslist-config": "^3.1.2", "@playwright/test": "^1.63.0", From 19b9fa3dad62770a23133b35300146e9878016a6 Mon Sep 17 00:00:00 2001 From: Maximilian Mayer Date: Thu, 10 Sep 2026 14:07:06 +0200 Subject: [PATCH 2/3] fix(a11y): name resize handles, drop invalid overlay label, add demo landmarks Signed-off-by: Maximilian Mayer --- examples/basic/App.vue | 3 +++ examples/basic/components/DocumentsList.vue | 2 +- src/components/DraggableElement.vue | 1 + src/components/PDFElements.vue | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/basic/App.vue b/examples/basic/App.vue index 15df9bc..f611fe4 100644 --- a/examples/basic/App.vue +++ b/examples/basic/App.vue @@ -4,6 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later -->