Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/app-webdir-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"scripts": {
"lint": "eslint --fix 'src/**/*.{js,jsx}' ",
"test": "jest --config=./jest.config.js --passWithNoTests --silent --coverage",
"test:accessibility": "playwright test",
"test-update-snapshot": "yarn test -- -u",
"prebuild": "rm -rf ./dist",
"build": "vite build && cp -r src/assets dist/",
Expand Down
25 changes: 25 additions & 0 deletions packages/app-webdir-ui/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: './tests',
testMatch: /.*\.spec\.m?js$/,
timeout: 60000,
workers: process.env.CI ? 2 : 1,
webServer: {
command: 'yarn storybook',
port: 9030,
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: 'http://localhost:9030',
trace: 'on-first-retry',
actionTimeout: 20000,
navigationTimeout: 45000,
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' },
},
],
});
142 changes: 142 additions & 0 deletions packages/app-webdir-ui/tests/accessibility.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { test, expect } from "@playwright/test";
import { Audit, Logging, Rules } from "@siteimprove/alfa-test-utils";
import { Playwright } from "@siteimprove/alfa-playwright";
import path from "path";
import fs from "fs";
import searchPageJsonData from "../__mocks__/api/feeds.json" assert { type: "json" };

const STORYBOOK_URL = "http://localhost:9030";

const reportDir = path.join(process.cwd(), "accessibility-reports");
if (!fs.existsSync(reportDir)) {
fs.mkdirSync(reportDir, { recursive: true });
}

const timestamp = new Date().toISOString().replace(/:/g, "-");

const Search_Story_ID = ["organisms-search-page-templates--search-page-example" ,
"organisms-search-page-templates--search-page-example&args=searchParams.search-tabs:web_dir_faculty_staff;searchParams.q:ia",
];

test.describe("SearchPage Accessibility Tests with Siteimprove", () => {
let storyIndex;
let storiesToTestArray = [];

test.beforeAll(async () => {
try {
const response = await fetch(`${STORYBOOK_URL}/index.json`);
if (!response.ok) throw new Error("Could not load Storybook index.json");

storyIndex = await response.json();

storiesToTestArray = Object.entries(storyIndex.entries).filter(([key]) =>
Search_Story_ID.some(story => key.includes(story))
);
} catch (err) {
console.error("Failed to fetch Storybook index:", err);
}
});

for (const storyToTest of Search_Story_ID) {
test(`${storyToTest} should pass accessibility tests`, async ({ page }) => {
if (!storyIndex) {
test.skip("Storybook index could not be fetched");
return;
}

const [baseStoryId, argsString] = storyToTest.split('&args=');

const storyEntry = storiesToTestArray.find(([key]) =>
key.includes(baseStoryId)
);

if (!storyEntry) {
test.skip(`Story ${storyToTest} not found in storybook index`);
return;
}

const [storyKey, storyDef] = storyEntry;
const encodedStoryId = encodeURIComponent(storyDef.id);
const storyUrl = argsString
? `${STORYBOOK_URL}/iframe.html?id=${encodedStoryId}&viewMode=story&args=${argsString}`
: `${STORYBOOK_URL}/iframe.html?id=${encodedStoryId}&viewMode=story`;

console.log(`Testing story: ${storyDef.title}`);

try {
// Mock API for SearchPage (adjust if needed)
await page.route("**/api/**", route => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(searchPageJsonData),
});
});

await page.goto(storyUrl);
await page.locator('#storybook-root').waitFor({ state: 'visible' });

const document = await page.evaluateHandle(() => window.document);
const alfaPage = await Playwright.toPage(document);

const alfaResult = await Audit.run(alfaPage, {
rules: { include: Rules.wcag21aaFilter },
});

Logging.fromAudit(alfaResult).print();

const failingRules = alfaResult.resultAggregates.filter(
aggregate => aggregate.failed > 0
);

// Save individual report locally
if (!process.env.CI && failingRules.length > 0) {
const individualReportPath = path.join(
reportDir,
`${storyToTest}-${timestamp}.json`
);

fs.writeFileSync(
individualReportPath,
JSON.stringify(
{
component: storyDef.title,
storyId: storyDef.id,
url: storyUrl,
failingRules: failingRules.map(rule => ({
ruleId: rule.rule.uri,
failed: rule.failed,
passed: rule.passed,
cantTell: rule.cantTell,
})),
},
null,
2
)
);

console.log(`Saved report for ${storyDef.title} at: ${individualReportPath}`);
}

if (failingRules.length > 0) {
console.error(`Found ${failingRules.length} failing rules in ${storyDef.title}`);

const rulesSummary = failingRules
.map(rule => `- ${rule.rule.uri}: ${rule.failed} failed`)
.join("\n");

expect(
failingRules.length,
`Accessibility violations found in ${storyDef.title}:\n${rulesSummary}`
).toBe(0);
} else {
console.log(`✅ No accessibility violations found in ${storyDef.title}`);
}
} catch (error) {
console.error(`Error testing ${storyDef.title}:`, error);
throw new Error(`Failed to test ${storyDef.title}: ${error.message}`);
}
});
}
});

3 changes: 3 additions & 0 deletions packages/unity-react-core/tests/dataLayer.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ for (const config of testUrls) {
test('should push correct events when opening and closing accordion', async ({ page }) => {
await page.goto(config.url);

// Wait for at least one accordion opener to be visible
await page.getByTestId('accordion-opener').first().waitFor({ state: 'visible' });

const openers = await page.getByTestId('accordion-opener').all();
expect(openers.length).toBeGreaterThan(0);

Expand Down