Skip to content

Commit a320a89

Browse files
test(unity-react-core): add playwright tests for accessibility
1 parent f946583 commit a320a89

File tree

8 files changed

+1502
-34
lines changed

8 files changed

+1502
-34
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
"@commitlint/config-lerna-scopes": "^19.7.0",
5151
"@semantic-release/changelog": "^6.0.3",
5252
"@semantic-release/git": "^10.0.1",
53+
"@siteimprove/alfa-playwright": "^0.78.1",
54+
"@siteimprove/alfa-test-utils": "^0.78.1",
5355
"@storybook/addons": "^7.6.14",
5456
"@storybook/api": "^7.6.14",
5557
"@storybook/blocks": "^7.6.14",

packages/unity-react-core/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"create": "plop",
3232
"dev": "yarn storybook & yarn testui",
3333
"lint": "eslint --fix 'src/**/*.{js,jsx}' --ignore-path ../../.eslintignore",
34-
"test": "vitest --watch=false",
34+
"test": "vitest --watch=false && npx playwright test",
35+
"test:accessibility": "playwright test tests/accessibility.spec.js --config playwright.config.js",
3536
"testui": "vitest --ui",
3637
"start:dev": "webpack-dashboard -- webpack serve -c webpack/webpack.dev.js",
3738
"build": "vite build -c vite.config.js && yarn loop && yarn postbuild && tsc",
@@ -54,6 +55,7 @@
5455
"@babel/plugin-transform-runtime": "^7.19.6",
5556
"@babel/preset-env": "^7.20.2",
5657
"@fortawesome/fontawesome-free": "^5.15.3",
58+
"@playwright/test": "1.50.1",
5759
"@storybook/addon-a11y": "^8.5.4",
5860
"@storybook/addon-essentials": "^8.5.4",
5961
"@storybook/addon-interactions": "^8.5.4",
@@ -82,6 +84,7 @@
8284
"axe-playwright": "^2.0.3",
8385
"babel-jest": "^26.6.3",
8486
"babel-loader": "^8.2.2",
87+
"chromium-bidi": "^3.0.0",
8588
"css-loader": "^5.2.0",
8689
"eslint": "^8.37.0",
8790
"eslint-plugin-storybook": "^0.11.2",
@@ -97,7 +100,7 @@
97100
"jsdoc": "4",
98101
"jsdom": "^24.0.0",
99102
"jsdom-screenshot": "^4.0.0",
100-
"playwright": "^1.48.0",
103+
"playwright": "1.50.1",
101104
"plop": "2.7.6",
102105
"postcss": "^8.4.19",
103106
"prettier": "^2.8.1",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { defineConfig } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
testMatch: /.*\.spec\.m?js$/,
6+
timeout: 30000,
7+
webServer: {
8+
command: 'yarn storybook',
9+
port: 9200,
10+
reuseExistingServer: !process.env.CI,
11+
},
12+
use: {
13+
baseURL: 'http://localhost:9200',
14+
trace: 'on-first-retry',
15+
},
16+
projects: [
17+
{
18+
name: 'chromium',
19+
use: { browserName: 'chromium' },
20+
},
21+
],
22+
});
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { test, expect } from "@playwright/test";
2+
import { Audit, Logging, Rules } from "@siteimprove/alfa-test-utils";
3+
import { Playwright } from "@siteimprove/alfa-playwright";
4+
import fetch from "node-fetch";
5+
import path from "path";
6+
import fs from "fs";
7+
import { storiesToTest } from "./stories-to-test.mjs";
8+
9+
const STORYBOOK_URL = "http://localhost:9200";
10+
11+
const reportDir = path.join(process.cwd(), "accessibility-reports");
12+
if (!fs.existsSync(reportDir)) {
13+
fs.mkdirSync(reportDir, { recursive: true });
14+
}
15+
16+
const timestamp = new Date().toISOString().replace(/:/g, "-");
17+
18+
test.describe("Storybook Accessibility Tests with Siteimprove", () => {
19+
let storyIndex;
20+
21+
test.beforeAll(async () => {
22+
try {
23+
const response = await fetch(`${STORYBOOK_URL}/index.json`);
24+
if (!response.ok) {
25+
throw new Error(
26+
`Failed to fetch storybook index: ${response.statusText}`
27+
);
28+
}
29+
storyIndex = await response.json();
30+
} catch (error) {
31+
console.error("Error fetching storybook index:", error);
32+
}
33+
});
34+
35+
test(
36+
"Components should pass Siteimprove accessibility tests",
37+
async ({ browser, page }) => {
38+
if (!storyIndex) {
39+
console.error(
40+
"Skipping test because storybook index could not be fetched"
41+
);
42+
return;
43+
}
44+
45+
const accessibilityViolations = [];
46+
const reportFilePath = path.join(
47+
reportDir,
48+
`siteimprove-report-${timestamp}.json`
49+
);
50+
51+
const allResults = [];
52+
53+
let count = 0;
54+
const stories = Object.entries(storyIndex.entries).filter(([key]) =>
55+
storiesToTest.some(story => key.includes(story))
56+
);
57+
const totalStories = stories.length;
58+
59+
for (const [storyId, story] of stories) {
60+
const encodedStoryId = encodeURIComponent(story.id);
61+
const storyUrl = `${STORYBOOK_URL}/iframe.html?id=${encodedStoryId}&viewMode=story`;
62+
63+
count++;
64+
console.log(`Testing (${count}/${totalStories}): ${story.title}`);
65+
66+
const context = await browser.newContext();
67+
const page = await context.newPage();
68+
69+
try {
70+
await page.goto(storyUrl);
71+
72+
const document = await page.evaluateHandle(() => window.document);
73+
const alfaPage = await Playwright.toPage(document);
74+
75+
const alfaResult = await Audit.run(alfaPage, {
76+
rules: { include: Rules.wcag21aaFilter },
77+
});
78+
79+
Logging.fromAudit(alfaResult).print();
80+
81+
const failingRules = alfaResult.resultAggregates.filter(
82+
aggregate => aggregate.failed > 0
83+
);
84+
85+
const violations =
86+
Logging.fromAudit(alfaResult).toJSON().logs[1].logs;
87+
88+
if (failingRules.size > 0) {
89+
console.log(
90+
`Found ${violations.length} violations in ${story.title}`
91+
);
92+
93+
const result = {
94+
component: story.title,
95+
storyId: storyId,
96+
url: storyUrl,
97+
violations: violations,
98+
};
99+
100+
accessibilityViolations.push(result);
101+
allResults.push(result);
102+
} else {
103+
allResults.push({
104+
component: story.title,
105+
storyId: storyId,
106+
url: storyUrl,
107+
violations: [],
108+
});
109+
}
110+
} catch (error) {
111+
console.error(`Error testing ${story.title}:`, error);
112+
accessibilityViolations.push({
113+
component: story.title,
114+
storyId: storyId,
115+
error: error.message,
116+
});
117+
118+
allResults.push({
119+
component: story.title,
120+
storyId: storyId,
121+
url: storyUrl,
122+
error: error.message,
123+
});
124+
}
125+
}
126+
127+
// Save the full report as JSON only in local development
128+
if (!process.env.CI) {
129+
fs.writeFileSync(reportFilePath, JSON.stringify(allResults, null, 2));
130+
console.log(`Saved detailed JSON report to: ${reportFilePath}`);
131+
}
132+
133+
if (accessibilityViolations.length > 0) {
134+
console.error("Accessibility violations found:");
135+
136+
const totalViolations = accessibilityViolations.reduce(
137+
(acc, result) =>
138+
acc + (result.violations ? result.violations.length : 0),
139+
0
140+
);
141+
142+
console.error(
143+
`\nTotal components with issues: ${accessibilityViolations.length}`
144+
);
145+
console.error(`Total violations: ${totalViolations}`);
146+
147+
expect(
148+
accessibilityViolations.length,
149+
`The test found ${accessibilityViolations.length} components with accessibility issues`
150+
).toBe(0);
151+
} else {
152+
console.log("No accessibility violations found! 🎉");
153+
}
154+
},
155+
{ timeout: 30000 }
156+
);
157+
});

packages/unity-react-core/tests/percyTests/testPage.percy.js

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const storiesToTest = [
2+
"components-accordion--default",
3+
"components-anchormenu--default",
4+
"components-article--news",
5+
"components-article--event",
6+
"components-card--default",
7+
// "components-card-carousel--three-item-carousel", // This story fails. TODO: Fix
8+
"components-image-gallery-carousel--image-gallery-carousel-default",
9+
"components-testimonial-carousel--testimonial-carousel-default",
10+
"components-list--unordered-list",
11+
"components-list--ordered-list",
12+
"components-pagination--default",
13+
"components-ranking-card--large",
14+
"components-ranking-card--small",
15+
"components-tabbedpanels--default",
16+
];

packages/unity-react-core/vitest.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export default defineConfig({
77
test: {
88
environment: "jsdom",
99
setupFiles: ["./vitest.setup.ts"],
10+
include: ["./src/**/*.{test,spec}.{js,ts,jsx,tsx}"],
11+
},
12+
define: {
13+
"process.env": JSON.stringify({}),
1014
},
1115
resolve: {
1216
alias: {

0 commit comments

Comments
 (0)