Skip to content

Commit a5aac8b

Browse files
Merge pull request #1521 from ASU/uds-1892
Uds 1892: Add Siteimprove testing to unity react core and degree page
2 parents b512a85 + 7a2b924 commit a5aac8b

File tree

20 files changed

+1831
-66
lines changed

20 files changed

+1831
-66
lines changed

Jenkinsfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,5 +266,27 @@ spec:
266266
}
267267
}
268268
}
269+
stage('Accessibility testing') {
270+
when {
271+
branch 'dev'
272+
}
273+
steps {
274+
container('playwright') {
275+
script {
276+
def accessibilityTestResults = sh(
277+
script: 'yarn test:accessibility',
278+
returnStatus: true
279+
)
280+
if (accessibilityTestResults != 0) {
281+
slackSend(
282+
channel: '#prdfam-uds-ci',
283+
color: 'warning',
284+
message: "@uds-developers Accessibility tests failed.: ${env.RUN_DISPLAY_URL} \n Pull Branch and run tests locally to see report"
285+
)
286+
}
287+
}
288+
}
289+
}
290+
}
269291
}
270292
}

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"prebuild": "lerna run build --scope=@asu/unity-react-core && yarn lint",
2020
"postbuild": "node -e \"process.env.CI && require('child_process').execSync('lerna run --ignore=@asu/unity-bootstrap-theme docs', {stdio: 'inherit'})\"",
2121
"test": "lerna run test --stream --parallel",
22+
"test:accessibility": "lerna run test:accessibility --parallel",
2223
"test:e2e": "jest",
2324
"prepublish-packages": "node scripts/copy-license.js && node scripts/copy-release-rc.js",
2425
"publish-packages": "yarn prepublish-packages && lerna exec --ignore=@asu/static-site --concurrency 1 -- npx --no-install semantic-release --debug -e semantic-release-monorepo",
@@ -48,8 +49,11 @@
4849
"@commitlint/cli": "^19.7.1",
4950
"@commitlint/config-conventional": "^19.7.1",
5051
"@commitlint/config-lerna-scopes": "^19.7.0",
52+
"@playwright/test": "1.50.1",
5153
"@semantic-release/changelog": "^6.0.3",
5254
"@semantic-release/git": "^10.0.1",
55+
"@siteimprove/alfa-playwright": "^0.78.1",
56+
"@siteimprove/alfa-test-utils": "^0.78.1",
5357
"@storybook/addons": "^7.6.14",
5458
"@storybook/api": "^7.6.14",
5559
"@storybook/blocks": "^7.6.14",
@@ -84,6 +88,7 @@
8488
"lerna": "^6.4.1",
8589
"mini-css-extract-plugin": "^2.0.0",
8690
"nunjucks": "^3.2.0",
91+
"playwright": "1.50.1",
8792
"prettier": "^2.2.1",
8893
"purgecss-webpack-plugin": "^4.0.3",
8994
"react": "^18.3.1",

packages/app-degree-pages/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"scripts": {
2727
"lint": "eslint --fix 'src/**/*.{js,jsx,ts,tsx}' --ignore-path ../../.eslintignore",
2828
"test": "vitest --watch=false",
29+
"test:accessibility": "playwright test",
2930
"test-update-snapshot": "yarn test -- -u",
3031
"e2e-ci": "concurrently --kill-others \"yarn storybook\" \"yarn cy:run\"",
3132
"e2e": "yarn cy:open",
@@ -63,6 +64,7 @@
6364
"@testing-library/jest-dom": "^6.6.3",
6465
"@testing-library/react": "^16.0.0",
6566
"@vitejs/plugin-react": "^4.3.1",
67+
"@vitest/browser": "^2.1.2",
6668
"babel-jest": "^27.0.6",
6769
"babel-loader": "^8.2.2",
6870
"babel-plugin-dynamic-import-node": "^2.3.3",
@@ -86,7 +88,7 @@
8688
"style-loader": "^2.0.0",
8789
"terser-webpack-plugin": "^5.1.1",
8890
"vite": "^5.3.5",
89-
"vitest": "^3.0.5",
91+
"vitest": "^2.1.1",
9092
"webpack-merge": "^5.8.0"
9193
},
9294
"resolutions": {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { defineConfig } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
testMatch: /.*\.spec\.m?js$/,
6+
timeout: 30000,
7+
workers: process.env.CI ? 2 : 1,
8+
webServer: {
9+
command: 'yarn storybook',
10+
port: 9010,
11+
reuseExistingServer: !process.env.CI,
12+
},
13+
use: {
14+
baseURL: 'http://localhost:9010',
15+
trace: 'on-first-retry',
16+
},
17+
projects: [
18+
{
19+
name: 'chromium',
20+
use: { browserName: 'chromium' },
21+
},
22+
],
23+
});

packages/app-degree-pages/src/components/ListingPage/components/Filters/components/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const SelectFormGroup = ({
3838
value={selected}
3939
>
4040
{options?.map(option => (
41-
<option key={option.id} id={option.id} value={option.value}>
41+
<option key={option.id} id={`${option.value}-${option.id}`} value={option.value}>
4242
{option.text}
4343
</option>
4444
))}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 path from "path";
5+
import fs from "fs";
6+
import { storiesToTest } from "./stories-to-test.mjs";
7+
import detailPageJsonData from "../__mocks__/data/degree-search-detail.json" assert { type: "json" };
8+
9+
const STORYBOOK_URL = "http://localhost:9010";
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+
20+
test(`Component ${storiesToTest[0]} should pass accessibility tests`, async ({ page }) => {
21+
await page.route("**/api/**", route => {
22+
route.fulfill({
23+
status: 200,
24+
contentType: "application/json",
25+
body: JSON.stringify(detailPageJsonData),
26+
});
27+
});
28+
const storyId = storiesToTest[0];
29+
const encodedStoryId = encodeURIComponent(storyId);
30+
const storyUrl = `${STORYBOOK_URL}/iframe.html?id=${encodedStoryId}&viewMode=story`;
31+
32+
await page.goto(storyUrl);
33+
await page.waitForTimeout(2000);
34+
35+
const document = await page.evaluateHandle(() => window.document);
36+
const alfaPage = await Playwright.toPage(document);
37+
38+
const alfaResult = await Audit.run(alfaPage, {
39+
rules: { include: Rules.wcag21aaFilter },
40+
});
41+
42+
await page.screenshot({
43+
fullPage: true,
44+
path: path.join(reportDir, `screenshot-${storyId}-${timestamp}.png`),
45+
});
46+
47+
Logging.fromAudit(alfaResult).print();
48+
49+
const failingRules = alfaResult.resultAggregates.filter(
50+
aggregate => aggregate.failed > 0
51+
);
52+
53+
if (!process.env.CI) {
54+
const reportFilePath = path.join(reportDir, `siteimprove-report-${storyId}-${timestamp}.json`);
55+
fs.writeFileSync(
56+
reportFilePath,
57+
JSON.stringify(Logging.fromAudit(alfaResult).toJSON(), null, 2)
58+
);
59+
console.log(`Saved detailed JSON report to: ${reportFilePath}`);
60+
}
61+
62+
expect(failingRules.size,
63+
`Found ${failingRules.size} accessibility rule violations in ${storyId}`
64+
).toBe(0);
65+
});
66+
67+
test(`Component ${storiesToTest[1]} should pass accessibility tests`, async ({ page }) => {
68+
// await page.route("**/api/**", route => {
69+
// route.fulfill({
70+
// status: 200,
71+
// contentType: "application/json",
72+
// body: JSON.stringify(searchPageJsonData),
73+
// })
74+
// });
75+
const storyId = storiesToTest[1];
76+
const encodedStoryId = encodeURIComponent(storyId);
77+
const storyUrl = `${STORYBOOK_URL}/iframe.html?id=${encodedStoryId}&viewMode=story`;
78+
79+
await page.goto(storyUrl);
80+
await page.waitForTimeout(2000);
81+
82+
const document = await page.evaluateHandle(() => window.document);
83+
const alfaPage = await Playwright.toPage(document);
84+
85+
const alfaResult = await Audit.run(alfaPage, {
86+
rules: { include: Rules.wcag21aaFilter },
87+
});
88+
89+
await page.screenshot({
90+
fullPage: true,
91+
path: path.join(reportDir, `screenshot-${storyId}-${timestamp}.png`),
92+
});
93+
94+
Logging.fromAudit(alfaResult).print();
95+
96+
const failingRules = alfaResult.resultAggregates.filter(
97+
aggregate => aggregate.failed > 0
98+
);
99+
100+
if (!process.env.CI) {
101+
const reportFilePath = path.join(reportDir, `siteimprove-report-${storyId}-${timestamp}.json`);
102+
fs.writeFileSync(
103+
reportFilePath,
104+
JSON.stringify(Logging.fromAudit(alfaResult).toJSON(), null, 2)
105+
);
106+
console.log(`Saved detailed JSON report to: ${reportFilePath}`);
107+
}
108+
109+
expect(failingRules.size,
110+
`Found ${failingRules.size} accessibility rule violations in ${storyId}`
111+
).toBe(0);
112+
});
113+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const storiesToTest = [
2+
"program-detail-page--default",
3+
"program-listing-page--default"
4+
];

packages/app-degree-pages/vitest.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
/// <reference types="vitest/config" />
22

3-
import { defineConfig } from 'vite';
3+
import { defineConfig } from 'vitest/config';
44
import { resolve } from "path";
55
import { expect, describe, it, afterEach, beforeEach } from "vitest";
66

77
export default defineConfig({
88
test: {
99
environment: 'jsdom',
1010
setupFiles: ['./vitest.setup.ts'],
11-
globals: true
11+
globals: true,
12+
include: ['./src/**/*.{test,spec}.{js,ts,jsx,tsx}'],
1213
},
1314
resolve: {
1415
alias: {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!-- FONTAWESOME loaded from CDN 'kit' URL -->
2+
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/js/all.min.js" integrity="sha512-uKQ39gEGiyUJl4AI6L+ekBdGKpGw4xJ55+xyJG7YFlJokPNYegn9KwQ3P8A7aFQAUtUsAQHep+d/lrGqrbPIDQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
3+
4+
<!-- Initialize dataLayer for GA. Implementing sites will need this above GTM snippet. More info: https://developers.google.com/tag-manager/devguide -->
5+
<script>
6+
window.global = window;
7+
dataLayer = [];
8+
</script>
9+
10+
<!-- Google Tag Manager ASU Universal-->
11+
<script>
12+
(function (w, d, s, l, i) {
13+
w[l] = w[l] || [];
14+
w[l].push({
15+
"gtm.start": new Date().getTime(),
16+
"event": "gtm.js",
17+
});
18+
var f = d.getElementsByTagName(s)[0],
19+
j = d.createElement(s),
20+
dl = l != "dataLayer" ? "&l=" + l : "";
21+
j.async = true;
22+
j.src = "//www.googletagmanager.com/gtm.js?id=" + i + dl;
23+
f.parentNode.insertBefore(j, f);
24+
})(window, document, "script", "dataLayer", "GTM-KDWN8Z");
25+
</script>
26+
<!-- Google Tag Manager (noscript) -->
27+
<!-- <noscript>
28+
<iframe src="//www.googletagmanager.com/ns.html?id=GTM-KDWN8Z" height="0" width="0"
29+
style="display: none; visibility: hidden"></iframe>
30+
</noscript> -->
31+
<!-- End Google Tag Manager ASU Universal -->
32+
33+
<style>
34+
div.sbdocs.sbdocs-wrapper {
35+
gap: 0rem!important;
36+
}
37+
</style>

packages/component-header-footer/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
},
3434
"scripts": {
3535
"lint": "eslint --fix 'src/**/*.{js,jsx}' --ignore-path ../../.eslintignore",
36-
"test": "jest --config=./jest.config.js --silent --coverage",
36+
"test": "jest --config=./jest.config.js --silent --coverage && yarn test-datalayer",
37+
"test-datalayer": "playwright test tests/dataLayer-header.spec.mjs",
3738
"test-update-snapshot": "yarn test -- -u",
3839
"build": "vite build && yarn postbuild",
3940
"build:stats": "webpack -c webpack/webpack.prod.js --profile --json=compilation-stats.json",

0 commit comments

Comments
 (0)