-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add accessibility Github Actions workflow (#7669)
* add accessibility workflow * Update name * fix function name * add light mode/dark mode testing, clean up logging * add some pages to show violations * add spacing * revert page changes * make sure actions packages are up to date * add some initial readme guidance for accessibility violations * covert chain promises to async/await, some formatting/comments updated
- Loading branch information
Showing
6 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
name: Accessibility Scan | ||
on: | ||
pull_request: | ||
branches: [main] | ||
types: [opened, synchronize] | ||
env: | ||
BUILD_DIR: 'client/www/next-build' | ||
jobs: | ||
accessibility: | ||
name: Runs accessibility scan on changed pages | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout branch | ||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 | ||
- name: Setup Node.js 20 | ||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 | ||
with: | ||
node-version: 20.x | ||
- name: Install dependencies | ||
run: yarn | ||
- name: Build | ||
run: yarn build | ||
env: | ||
NODE_OPTIONS: --max_old_space_size=4096 | ||
- name: Get changed/new pages to run accessibility tests on | ||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | ||
id: pages-to-a11y-test | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
script: | | ||
const { getChangedPages } = require('./.github/workflows/scripts/check_for_changed_pages.js'); | ||
const buildDir = process.env.BUILD_DIR; | ||
return getChangedPages({github, context, buildDir}); | ||
- name: Run site | ||
run: | | ||
python -m http.server 3000 -d ${{ env.BUILD_DIR }} & | ||
sleep 5 | ||
- name: Run accessibility tests on changed/new MDX pages | ||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | ||
id: axeResults | ||
with: | ||
result-encoding: string | ||
script: | | ||
const { runAccessibilityScan } = require('./.github/workflows/scripts/run_accessibility_scan.js'); | ||
const pages = ${{ steps.pages-to-a11y-test.outputs.result }} | ||
return await runAccessibilityScan(pages) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
module.exports = { | ||
getChangedPages: async ({ github, context, buildDir }) => { | ||
const fs = require('fs'); | ||
const cheerio = require('cheerio'); | ||
|
||
const urlList = []; | ||
|
||
const { | ||
issue: { number: issue_number }, | ||
repo: { owner, repo } | ||
} = context; | ||
|
||
const possiblePages = []; | ||
const platforms = [ | ||
'android', | ||
'angular', | ||
'flutter', | ||
'javascript', | ||
'nextjs', | ||
'react', | ||
'react-native', | ||
'swift', | ||
'vue' | ||
]; | ||
|
||
const changedFiles = await github.paginate( | ||
'GET /repos/{owner}/{repo}/pulls/{pull_number}/files', | ||
{ owner, repo, pull_number: issue_number }, | ||
(response) => | ||
response.data.filter( | ||
(file) => file.status === 'modified' || file.status === 'added' | ||
) | ||
); | ||
|
||
// Get only the changed files that are pages and build out the | ||
// possiblePages array | ||
changedFiles.forEach(({ filename }) => { | ||
const isPage = | ||
filename.startsWith('src/pages') && | ||
(filename.endsWith('index.mdx') || filename.endsWith('index.tsx')); | ||
if (isPage) { | ||
const path = filename | ||
.replace('src/pages', '') | ||
.replace('/index.mdx', '') | ||
.replace('/index.tsx', ''); | ||
if (path.includes('[platform]')) { | ||
platforms.forEach((platform) => { | ||
possiblePages.push(path.replace('[platform]', platform)); | ||
}); | ||
} else { | ||
possiblePages.push(path); | ||
} | ||
} | ||
}); | ||
|
||
// Get the sitemap and parse for an array of site URLs | ||
const siteMap = fs.readFileSync(`${buildDir}/sitemap.xml`); | ||
|
||
const siteMapParse = cheerio.load(siteMap, { | ||
xml: true | ||
}); | ||
|
||
siteMapParse('url').each(function () { | ||
urlList.push(siteMapParse(this).find('loc').text()); | ||
}); | ||
|
||
// Filter the possiblePages for only those that are part of the sitemap | ||
const pages = possiblePages.filter((page) => | ||
urlList.includes(`https://docs.amplify.aws${page}/`) | ||
); | ||
|
||
return pages; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
module.exports = { | ||
runAccessibilityScan: (pages) => { | ||
const core = require('@actions/core'); | ||
const { AxePuppeteer } = require('@axe-core/puppeteer'); | ||
const puppeteer = require('puppeteer'); | ||
|
||
const violations = []; | ||
|
||
// When flipping from dark mode to light mode, we need to add a small timeout | ||
// to account for css transitions otherwise there can be false contrast issues found. | ||
// Usage: await sleep(300); | ||
const sleep = ms => new Promise(res => setTimeout(res, ms)); | ||
|
||
const logViolation = (violation) => { | ||
violation.nodes.forEach(node => { | ||
console.log(node.failureSummary); | ||
console.log(node.html); | ||
node.target.forEach( target => { | ||
console.log('CSS target: ', target) | ||
}) | ||
console.log('\n'); | ||
}) | ||
|
||
} | ||
|
||
async function runAxeAnalyze(pages) { | ||
for (const page of pages) { | ||
const browser = await puppeteer.launch(); | ||
const pageToVisit = await browser.newPage(); | ||
await pageToVisit.goto(`http://localhost:3000${page}/`, {waitUntil: 'domcontentloaded'}); | ||
await pageToVisit.click('button[title="Light mode"]'); | ||
await pageToVisit.waitForSelector('[data-amplify-color-mode="light"]'); | ||
await sleep(300); | ||
|
||
|
||
try { | ||
console.log(`\nTesting light mode: http://localhost:3000${page}/`) | ||
const results = await new AxePuppeteer(pageToVisit).analyze(); | ||
if(results.violations.length > 0) { | ||
results.violations.forEach(violation => { | ||
logViolation(violation); | ||
violations.push(violation); | ||
}) | ||
} else { | ||
console.log('No violations found. \n'); | ||
} | ||
|
||
} catch (error) { | ||
core.setFailed(`There was an error testing the page: ${error}`); | ||
} | ||
|
||
await pageToVisit.click('button[title="Dark mode"]'); | ||
await pageToVisit.waitForSelector('[data-amplify-color-mode="dark"]'); | ||
await sleep(300); | ||
|
||
try { | ||
console.log(`\nTesting dark mode: http://localhost:3000${page}/`) | ||
const results = await new AxePuppeteer(pageToVisit).analyze(); | ||
if(results.violations.length > 0) { | ||
results.violations.forEach(violation => { | ||
logViolation(violation); | ||
violations.push(violation); | ||
}) | ||
} else { | ||
console.log('No violations found. \n'); | ||
} | ||
|
||
} catch (error) { | ||
core.setFailed(`There was an error testing the page: ${error}`); | ||
} | ||
|
||
await browser.close(); | ||
} | ||
if(violations.length > 0) { | ||
core.setFailed(`Please fix the above accessibility violations.`); | ||
} | ||
} | ||
|
||
runAxeAnalyze(pages); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters