diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml new file mode 100644 index 0000000..47e04e1 --- /dev/null +++ b/.github/workflows/a11y.yml @@ -0,0 +1,46 @@ +name: Accessibility Checks + +on: + push: + branches: + - master + pull_request: + +jobs: + axe-check: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Installs + run: npm install jupyter-book selenium-webdriver @axe-core/webdriverjs chromedriver http-server wait-on + + - name: Build Jupyter Book + run: npx jupyter-book build --html + + - name: Run Axe Checks + run: | + # A. Start a local server in the background + npx http-server ./_build/html -p 8080 -s & + npx wait-on http://localhost:8080 + + # B. Generate the URL list from the actual build artifacts + cd _build/html + URLS=$(find . -name "*.html" -not -path "*/build/*" | sed 's|^\./||' | sed 's|^|http://localhost:8080/|' | tr '\n' ' ') + cd ../.. + + # C. Run custom script + node axe-scan.js $URLS + + - name: Upload Accessibility Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: axe-report + path: axe-report.json \ No newline at end of file diff --git a/README.md b/README.md index 4e389d3..9aac8b6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ -# textbook -Textbook for Data 88E: Economic Models at UC Berkeley +# Textbook for Data 88E: Economic Models at UC Berkeley -Content is stored in the content folder. Order of textbook can be changed from _toc.yml file. +[![Jupyter Book (via myst) GitHub Pages Deploy](https://github.com/data-88e/textbook/actions/workflows/deploy.yml/badge.svg)](https://github.com/data-88e/textbook/actions/workflows/deploy.yml) [![Accessibility Checks](https://github.com/data-88e/textbook/actions/workflows/a11y.yml/badge.svg)](https://github.com/data-88e/textbook/actions/workflows/a11y.yml) + +Content is stored in the content folder. Order of textbook can be changed from the `myst.yml` file. To build the textbook, run ``` jupyter-book build . ``` -This will output HTML to `_build/html`, which can be copied to `docs` in order to be served on GitHub Pages. +This will output HTML to `_build/html`. # NOTE: Chapter 4 - shifts.ipynb diff --git a/axe-scan.js b/axe-scan.js new file mode 100644 index 0000000..f0feddb --- /dev/null +++ b/axe-scan.js @@ -0,0 +1,90 @@ +const { Builder } = require('selenium-webdriver'); +const chrome = require('selenium-webdriver/chrome'); +const AxeBuilder = require('@axe-core/webdriverjs'); +const fs = require('fs'); + +// Get URLs from command line args +const urls = process.argv.slice(2); +console.log(`\nStarting Axe scan on ${urls.length} pages...\n`); + +(async function scan() { + const options = new chrome.Options(); + options.addArguments('--headless=new'); + options.addArguments('--no-sandbox'); + options.addArguments('--disable-dev-shm-usage'); + options.addArguments('--disable-gpu'); + // see if these help with the production/shifts page crashing + options.addArguments('--window-size=1920,1080'); + options.addArguments('--disable-extensions'); + options.addArguments('--disable-infobars'); + options.addArguments('--disable-software-rasterizer'); + + // Initialize Driver + const driver = await new Builder() + .forBrowser('chrome') + .setChromeOptions(options) + .build(); + + const fullReport = []; + let hasErrors = false; + + try { + // Set a very generous timeout for page loads (3 minutes) + await driver.manage().setTimeouts({ pageLoad: 180000, script: 180000 }); + + for (let i = 0; i < urls.length; i++) { + const url = urls[i]; + console.log(`[${i + 1}/${urls.length}] Testing ${url} ...`); + + try { + await driver.get(url); + + const results = await new AxeBuilder(driver) + .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) + .analyze(); + + if (results.violations.length > 0) { + console.log(` FAILED: ${results.violations.length} violations found.`); + results.violations.forEach((violation) => { + console.log(` [${violation.impact.toUpperCase()}] ${violation.id}: ${violation.help}`); + console.log(` Help URL: ${violation.helpUrl}`); + + violation.nodes.forEach((node) => { + console.log(` - Selector: ${node.target.join(' ')}`); + }); + console.log(''); // Empty line for readability + }); + + fullReport.push({ + url: url, + violations: results.violations + }); + hasErrors = true; + } else { + console.log(` PASSED`); + } + + } catch (e) { + console.error(` CRASHED: Could not scan ${url}. Skipping.`); + console.error(` Reason: ${e.message}`); + fullReport.push({ + url: url, + error: "Browser crashed or timed out on this page", + details: e.message + }); + hasErrors = true; + } + } + + // Save JSON report + fs.writeFileSync('axe-report.json', JSON.stringify(fullReport, null, 2)); + console.log('\nReport saved to axe-report.json'); + + } finally { + await driver.quit(); + } + + if (hasErrors) { + process.exit(1); + } +})(); \ No newline at end of file diff --git a/myst.yml b/myst.yml index b2a5f5c..6e6d9a0 100644 --- a/myst.yml +++ b/myst.yml @@ -84,9 +84,4 @@ site: favicon: content/images/favicon.ico folders: true hide_authors: true - template: book-theme -sphinx: - config: - # This overrides the default theme (Sphinx Book Theme) - # to use your custom theme. - html_theme: quantecon_book_theme + template: book-theme \ No newline at end of file