Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a11y tests and GitHub action #460

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
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
30 changes: 27 additions & 3 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy('./src/scripts/code-showcase.js');
eleventyConfig.addPassthroughCopy('./src/scripts/search.js');
eleventyConfig.addPassthroughCopy('./src/scripts/code-copy.js');
eleventyConfig.addPassthroughCopy('./src/admin/config.yml');
eleventyConfig.addPassthroughCopy('./src/scripts/component-preview.js');
eleventyConfig.addPassthroughCopy('./src/scripts/component-preview-iframe.js');
eleventyConfig.addPassthroughCopy('./src/favicon.ico');
eleventyConfig.addPassthroughCopy({ './src/variables/': 'variables' });
eleventyConfig.addPassthroughCopy({
Expand Down Expand Up @@ -197,7 +198,7 @@ module.exports = function (eleventyConfig) {

return `
<div class="code-showcase mb-300">
<textarea class="showcase text-light mb-300 p-300" id="${id}" rows="8" aria-label="${langStrings[lang].code} - ${name}" aria-hidden="true" readonly>${content}</textarea>
<textarea class="showcase text-light mb-300 p-300" id="${id}" rows="8" aria-label="${langStrings[lang].code} - ${name}" tabindex="-1" aria-hidden="true" readonly>${content}</textarea>
<div>
<gcds-button
class="showcase-view-button"
Expand Down Expand Up @@ -283,7 +284,7 @@ module.exports = function (eleventyConfig) {
const content = children;

return `
<div class="${margin} b-sm b-default component-preview" id="component-preview">
<div class="${margin} b-sm b-default component-preview">
<p class="container-full font-semibold px-225 py-150 bb-sm b-default bg-light">
${title}
</p>
Expand All @@ -295,6 +296,29 @@ module.exports = function (eleventyConfig) {
},
);

eleventyConfig.addPairedShortcode(
'baseComponentPreview',
(children, title, url) => {
return `
<div class="my-600 b-sm b-default component-preview">
<h2 class="container-full font-text font-semibold px-225 py-150 bb-sm b-default bg-light">
${title}
</h2>
<div>
<iframe
title="${title}"
src="${url.replace('/base', '/preview')}"
style="display: block; margin: 0 auto;"
frameBorder="0"
width="100%"
id="component-preview"
></iframe>
</div>
</div>
`;
},
);

/*
* Display tokens in tables based on passed name
*/
Expand Down
40 changes: 20 additions & 20 deletions .github/workflows/a11y-tests.yaml
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
name: A11y test
name: A11y test documentation site

on:
workflow_dispatch:
push:
paths-ignore:
- 'reports/**'
branches:
- gh-pages
- main
pull_request:
branches:
- main

jobs:
build-deploy:
name: A11y test
a11y-test:
name: A11y test documentation site
runs-on: ubuntu-latest
container:
image: cypress/included:11.2.0
steps:
- name: Git Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install pa11y
run: npm install -g pa11y-ci pa11y-ci-reporter-html
- name: run pa11y test
run: pa11y-ci --sitemap https://cds-snc.github.io/${{ github.event.repository.name }}/sitemap.xml
- name: Commit
if: always()
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "A11y report"
- name: Push changes
if: always()
uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa

- uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
with:
node-version: '18.x'

- name: Cypress run
uses: cypress-io/github-action@v6
with:
github_token: ${{ secrets.ACCESS_TOKEN }}
branch: 'gh-pages'
build: npm run build:eleventy
start: npm run watch:eleventy
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ _site/
src/_includes/partials/templates/en/
src/_includes/partials/templates/fr/

package-lock.json
cypress/screenshots/
25 changes: 25 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { defineConfig } = require("cypress");

module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
log(message) {
console.log(message)

return null
},
table(message) {
console.table(message)

return null
}
})
},
baseUrl: 'http://localhost:8080',
viewportWidth: 1280,
viewportHeight: 850,
screenshotOnRunFailure: false,
video: false,
},
});
75 changes: 75 additions & 0 deletions cypress/e2e/a11y-en.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/// <reference types="cypress" />

const enLinks = require('../../src/en/en.json');

const pagesEn = [];

// Create index of English pages
Object.keys(enLinks.links).forEach(key => {
const url = enLinks.links[key];
if (
!url.includes('coming-soon') &&
!url.includes('https') &&
!url.includes('mailto') &&
!url.includes('demo')
) {
let regex = /components\/[a-z]/;
const pageName = key.replace(/([A-Z])/g, ' $1');
if (regex.test(url)) {
pagesEn.push({
name: `${pageName} - use case`,
url,
});
pagesEn.push({
name: `${pageName} - design`,
url: `${url}/design/`,
});
pagesEn.push({
name: `${pageName} - code`,
url: `${url}/code/`,
});
} else {
pagesEn.push({
name: pageName,
url,
});
}
}
});

describe(`A11Y test English documentation site`, () => {
for (const page of pagesEn) {
it(`${page.name}: ${page.url}`, () => {
cy.visit(page.url, { timeout: 30000 });
cy.get('.hydrated').then(() => {
cy.injectAxe();
cy.checkA11y(null, null, terminalLog);
// skip theme and topic menu since links are pulled from external source
if (!page.url.includes('theme-and-topic-menu')) {
cy.scanDeadLinks();
}
});
});
}
});

// Output a11y errors in table in console
function terminalLog(violations) {
cy.task(
'log',
`${violations.length} accessibility violation${
violations.length === 1 ? '' : 's'
} ${violations.length === 1 ? 'was' : 'were'} detected`,
Copy link
Collaborator

@melaniebmn melaniebmn Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the impact level here as well, please?

);
// pluck specific keys to keep the table readable
const violationData = violations.map(
({ id, impact, description, nodes }) => ({
id,
impact,
description,
nodes: nodes.length,
}),
);

cy.task('table', violationData);
}
76 changes: 76 additions & 0 deletions cypress/e2e/a11y-fr.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/// <reference types="cypress" />

const frLinks = require('../../src/fr/fr.json');

const pagesFr = [];

// Create index of French pages
Object.keys(frLinks.links).forEach(key => {
const url = frLinks.links[key];
if (
!url.includes('developpement-en-cours') &&
!url.includes('https') &&
!url.includes('mailto') &&
!url.includes('demo')
) {
let regex = /composants\/[a-z]/;
const pageName = key.replace(/([A-Z])/g, ' $1');
if (regex.test(url)) {
pagesFr.push({
name: `${pageName} - use case`,
url,
});
pagesFr.push({
name: `${pageName} - design`,
url: `${url}/design/`,
});
pagesFr.push({
name: `${pageName} - code`,
url: `${url}/code/`,
});
} else {
pagesFr.push({
name: `${pageName}`,
url,
});
}
}
});

describe(`A11Y test French documentation site`, () => {
after
for (const page of pagesFr) {
it(`${page.name}: ${page.url}`, () => {
cy.visit(page.url, { timeout: 30000 });
cy.get('.hydrated').then(() => {
cy.injectAxe();
cy.checkA11y(null, null, terminalLog);
// skip theme and topic menu since links are pulled from external source
if (!page.url.includes('menu-thematique')) {
cy.scanDeadLinks();
}
});
});
}
});

// Output a11y errors in table in console
function terminalLog(violations) {
cy.task(
'log',
`${violations.length} accessibility violation${
violations.length === 1 ? '' : 's'
} ${violations.length === 1 ? 'was' : 'were'} detected`,
);
// pluck specific keys to keep the table readable
const violationData = violations.map(
({ id, impact, description, nodes }) => ({
id,
impact,
description,
nodes: nodes.length,
}),
);

cy.task('table', violationData);
}
5 changes: 5 additions & 0 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
42 changes: 42 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************

// keep track of what we test so we dont test the same thing twice
let links_checked = [];

Cypress.Commands.add('scanDeadLinks', () => {
cy.get('body').within(() => {
let checked = 0;

cy.get('a', { includeShadowDom: true })
.each(link => {
if (
link.prop('href').startsWith('mailto') ||
link.prop('href').includes('.pdf')
)
return;

const check_url = link.prop('href');

// skip if its already been checked
if (links_checked.includes(check_url)) return;

cy.request(link.prop('href')).as('link');

links_checked.push(check_url);
checked++;
})
.as('links');

cy.get('@links').then(links => {
cy.log('links checked', checked);
});
});
});
19 changes: 19 additions & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'
import 'cypress-axe'
import 'cypress-html-validate/commands'
Loading