Now we've got broken links sorted, we're in a better state to start accessibility testing using the Accessibility Insights Action. This is available as both a GitHub Action and an Azure DevOps extension. I'll be using the GitHub Action version.


name: Accessibility

on:
  workflow_dispatch:
    
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - uses: ./.github/actions/jekyll-build-pages
        with:
          verbose: false

      - run: |
          sudo find -type f ! -regex ".*\.\(html\|svg\|gif\|css\|jpg\|png\)" -delete
        name: Remove non-HTML
        
        # https://github.com/microsoft/accessibility-insights-action
      - name: Scan for accessibility issues
        uses: microsoft/accessibility-insights-action@v2
        with:
            repo-token: ${{ secrets.GITHUB_TOKEN }}
            site-dir: ${{ github.workspace }}/_site
            scan-timeout: 6000000
            #max-urls: 1500
            localhost-port: 12345
            scan-url-relative-path: /

      - name: Upload report artifact
        uses: actions/upload-artifact@v2
        with:
            name: accessibility-reports
            path: ${{ github.workspace }}/_accessibility-reports/index.html

The action can either scan a local directory or a URL. In my case, I want it to scan all the files that make up my blog. My blog content is written in Markdown (.md) files and uses the Jekyll engine to render those pages into .html. It's the latter that should be scanned for accessibility compliance.

To generate the .html files, I make use of the Jekyll-Build-Pages action. This will generate a bunch of files under the _site directory.

My initial testing with the scanning tool revealed it was triggering on some pages I was including but had no control over (eg. Google Ads and the Disqus comments). I wanted to exclude those from the scanning, and one way to do that is to make the content conditional. eg.


{%- if jekyll.environment == "production" -%}
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-999999999" crossorigin="anonymous"></script>
{%- endif -%}

To exclude this block, I then needed to ensure that jekyll.environment was not set to production. I achieved this by using a local copy of the action in which I set the value of the JEKYLL_ENV environment variable to development. To facilitate that I need to make a few other changes which you can see on this branch.

To help focus the scanning just on the files I care about, I added an extra step to the workflow to remove any files that weren't one of .html, .svg, .gif, .jpg or .png.

Depending on how many files you have in your website, scanning can take quite.

It's probably a good idea to not set max-urls the first time. This uses the default of 100, which will be enough to give you an idea of the kinds of problems you need to fix.

The reason for starting small is if you have a template or CSS that are used across every page, then every page will trigger errors, and your scan will take ages to finish and contains heaps of the same error(s).

Once you've resolved those common errors, then you can ramp up your max-urls to cover all the pages on your site (if it didn't already).

Running the workflow above also produces an 'Accessibility Checks' report. You can use this to get a quick overview of the results. To drill in to the details, you should download the build artifact view the index.html file in your browser.

Accessibility report screenshot

Fixing the errors

The scan flagged 5 rule violations. Expanding each rule lists the URLs that exhibited the problem, plus a suggestion on how to resolve the issue. Sometime there are multiple suggestions.

color-contrast: Ensure the contrast between foreground and background colors meet WCAG 2 AA contrast ration thresholds

Example

<a href="/tag/Family.html">Family</a>

Fix the following:

  • Element has insufficient color contrast of 4.14 (foreground color: #2a7ae2, background color: #fdfdfd, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1Element has insufficient color contrast of 4.14 (foreground color: #2a7ae2, background color: #fdfdfd, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1

    • Use foreground color: #2773d6 and the original background color: #fdfdfd to meet a contrast ratio of 4.58:1.
  • Element has insufficient color contrast of 3.77 (foreground color: #828282, background color: #fdfdfd, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1Element has insufficient color contrast of 3.77 (foreground color: #828282, background color: #fdfdfd, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1

    • Use foreground color: #747474 and the original background color: #fdfdfd to meet a contrast ratio of 4.59:1.

These were common across all pages, so fixing these early is a quick win. I searched for the hex color (usually it was defined in one of the .scss files that generate the CSS) and replaced it with the suggested colour

Fix the following:

  • Element is in tab order and does not have accessible text

Fix ONE of the following:

  • Element does not have text that is visible to screen readers
  • aria-label attribute does not exist or is empty
  • aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
  • Element has no title attribute

image-alt: Ensures <img> elements have alternate text or a role of none or presentation

Fix ONE of the following:

  • Element does not have an alt attribute
  • aria-label attribute does not exist or is empty
  • aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
  • Element has no title attribute
  • Element's default semantics were not overridden with role="none" or role="presentation"

html-has-lang: Ensure every HTML document has a lang attribute

Fix the following:

  • The <html> element does not have a lang attribute

This was a false positive as for some reason it was being flagged on images.

frame-title: Ensures <iframe> and <frame> elements have an accessible name

Fix ONE of the following:

  • Element has no title attribute
  • aria-label attribute does not exist or is empty
  • aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
  • Element's default semantics were not overridden with role="none" or role="presentation"

This was being flagged by some older YouTube embedded player HTML.

I've raise a bug to report the problem with links to images being flagged. It feels to me like the scanner is trying to scan image URLs (which it shouldn't). Whether that's a bug in the action, or in how I'm using it, I hope to find out soon.