Skip to content

Accessibility checks in CI ​

Lumtera's WP-CLI commands can run in a CI pipeline. The build fails when a page has an accessibility error, and each issue shows up in your CI tool's report.

CommandWhat it checksExit code
wp lumtera check --page=<url>One page of the site, fetched and checked as a visitor gets it, with the theme, menus and footer. Nothing is stored.0, 1 or 2 (see below)
wp lumtera issuesEvery issue already stored from checks of saved contentAlways 0

Both commands can write two report formats:

  • --format=sarif: SARIF 2.1.0, for GitHub code scanning and other SARIF viewers.
  • --format=junit: JUnit XML, for GitLab, Jenkins, Azure Pipelines, CircleCI and similar tools.

What each format contains is described in WP-CLI: SARIF and JUnit.

Exit codes ​

wp lumtera check exits with:

CodeMeaning
0No issue at or above --fail-on (default: error)
1At least one issue at or above --fail-on
2Lumtera couldn't use the input: the page was refused, couldn't be fetched or didn't return HTTP 200, the input was empty or over 5 MB, or --fail-on was invalid
  • The exit code is the same for every --format, so you can upload the report and still fail the job.
  • --fail-on takes error, warning (also fails on "Needs review"), notice (also fails on tips) or none (never fails).
  • WP-CLI itself exits with 1 for an unknown option or --format value. Test your command once by hand before relying on the difference between 1 and 2.

wp lumtera issues exits with 0 whatever it finds. Use check when a result should fail the build.

Pointing WP-CLI at the site ​

--page only accepts pages of the site WP-CLI runs against: a path such as /pricing/, or a full URL with the same host and port as the site's home URL. The page is fetched by the server that runs WP-CLI, so point WP-CLI at the site you want to check.

For a staging server, add an SSH alias to wp-cli.yml in your repository:

yaml
# wp-cli.yml
@staging:
  ssh: deploy@staging.example.com/var/www/html

Then wp @staging lumtera check --page=/ runs on the staging server. The report comes back over SSH on standard output, with the exit code.

Requirements:

  • WP-CLI is installed in CI and on the staging server (as wp on the server's PATH).
  • Lumtera is active on the staging site.
  • The staging server can load its own pages over HTTP. If staging is behind HTTP authentication or an IP allow list that blocks the server itself, check --page gets an error status and exits with 2.

GitHub Actions: code scanning ​

This workflow checks four pages of a staging site, uploads each report to Security → Code scanning, and fails when a page has an error.

Set these first, under Settings → Secrets and variables → Actions:

  • The STAGING_SSH_KEY secret: a private key the staging server accepts.
  • The STAGING_HOST variable: for example staging.example.com.

Save as .github/workflows/accessibility.yml, next to the wp-cli.yml above:

yaml
name: Accessibility

on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: "0 6 * * 1" # Mondays, 06:00 UTC
  workflow_dispatch:

permissions:
  contents: read
  security-events: write # upload SARIF to code scanning

jobs:
  lumtera:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        page: ["/", "/about/", "/contact/", "/shop/"]
    steps:
      - uses: actions/checkout@v5

      - name: Install WP-CLI
        run: |
          curl -sSLo wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
          chmod +x wp-cli.phar
          sudo mv wp-cli.phar /usr/local/bin/wp

      - name: SSH access to staging
        run: |
          install -m 700 -d ~/.ssh
          echo "${{ secrets.STAGING_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H "${{ vars.STAGING_HOST }}" >> ~/.ssh/known_hosts

      - name: Check ${{ matrix.page }}
        id: check
        run: |
          set +e
          wp @staging lumtera check --page="${{ matrix.page }}" --format=sarif > lumtera.sarif
          echo "code=$?" >> "$GITHUB_OUTPUT"

      - name: Upload to code scanning
        if: always() && steps.check.outputs.code != '2'
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: lumtera.sarif
          # One category per page, so each page's alerts are tracked separately.
          category: lumtera${{ matrix.page }}

      - name: Fail on accessibility errors
        if: steps.check.outputs.code != '0'
        run: |
          echo "::error::Lumtera exit code ${{ steps.check.outputs.code }} for ${{ matrix.page }}"
          exit 1

set +e stops the step from ending at the first non-zero exit, so the exit code is saved for the later steps. When the code is 2, there's no report to upload, and the last step fails the job.

Alerts show up under Security → Code scanning. Filter them by the tool "Lumtera". Each alert's location is the page URL, not a file in your repository, so it has no source line to link to. Use the markup snippet in the alert, and the check's help link, to find the problem in your theme or content.

To fail on "Needs review" findings too, add --fail-on=warning. To report without ever failing, add --fail-on=none.

Code scanning availability

Code scanning is free for public repositories. Private repositories need GitHub Code Security. Without it, keep the report as a build artifact instead of the upload step:

yaml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: lumtera-sarif-${{ strategy.job-index }}
          path: lumtera.sarif

Pull requests from forks don't get security-events: write, so the upload step fails for them. Run the workflow on push and schedule only if your repository takes pull requests from forks.

Report the whole site's stored results ​

wp lumtera issues reports what Lumtera stored when content was saved, or checked from the Overview. To report all of it in one upload, check everything first:

yaml
      - name: Check all content on staging
        run: |
          wp @staging lumtera scan --all --format=summary
          wp @staging lumtera issues --format=sarif > lumtera-content.sarif

      - uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: lumtera-content.sarif
          category: lumtera-content

Each alert's location is the item's permalink. scan stores new results on the staging site, like saving each post would. It doesn't send Pro alerts.

GitHub Actions without a staging server ​

If your theme is in the repository, you can check it in a throwaway WordPress started by @wordpress/env. Add a .wp-env.json:

json
{
  "core": null,
  "themes": ["."],
  "plugins": ["https://downloads.wordpress.org/plugin/lumtera.zip"],
  "mappings": { "wp-content/lumtera-ci": "./lumtera-ci" }
}

wp-env runs WP-CLI in a separate container that can't load the site's own address, so --page doesn't work there. Fetch the page on the runner instead, save it in the mapped folder, and check the file:

yaml
      - uses: actions/setup-node@v5
        with:
          node-version: 22

      - name: Start WordPress
        run: |
          mkdir -p lumtera-ci
          npx @wordpress/env start

      - name: Check the home page
        id: check
        run: |
          set +e
          curl -sSf http://localhost:8888/ -o lumtera-ci/home.html
          npx @wordpress/env run cli wp lumtera check wp-content/lumtera-ci/home.html --format=sarif > lumtera.sarif
          echo "code=$?" >> "$GITHUB_OUTPUT"
          jq -e '.runs[0].tool.driver.name == "Lumtera"' lumtera.sarif > /dev/null && echo "sarif=yes" >> "$GITHUB_OUTPUT"

      - uses: github/codeql-action/upload-sarif@v4
        if: always() && steps.check.outputs.sarif == 'yes'
        with:
          sarif_file: lumtera.sarif
          category: lumtera-home

      - if: steps.check.outputs.code != '0'
        run: exit 1
  • The jq line confirms the file holds only the report, and not wp-env's own messages.
  • A fresh site has little content. Import test content after start if your pages need it, for example with npx @wordpress/env run cli wp import.
  • The alert location is the file path, wp-content/lumtera-ci/home.html.
  • Add lumtera-ci/ to .gitignore.

GitLab CI ​

GitLab shows JUnit reports in merge requests and on the pipeline's Tests tab. This job checks several pages on staging, reports them as JUnit, and keeps SARIF files as artifacts too.

Set these CI/CD variables first:

  • STAGING_SSH_KEY: a private key, of type File.
  • STAGING_HOST: for example staging.example.com.

The wp-cli.yml alias from Pointing WP-CLI at the site must be in the repository.

yaml
# .gitlab-ci.yml
accessibility:
  stage: test
  image: php:8.3-cli
  variables:
    PAGES: "/ /about/ /contact/"
  before_script:
    - apt-get update -qq && apt-get install -y -qq openssh-client curl > /dev/null
    - curl -sSLo /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x /usr/local/bin/wp
    - install -m 700 -d ~/.ssh
    - install -m 600 "$STAGING_SSH_KEY" ~/.ssh/id_ed25519
    - ssh-keyscan -H "$STAGING_HOST" >> ~/.ssh/known_hosts
  script:
    - mkdir -p reports
    - status=0
    - |
      for page in $PAGES; do
        name=$(echo "$page" | tr -c 'a-zA-Z0-9\n' '-')
        wp @staging lumtera check --page="$page" --format=junit > "reports/lumtera${name}.xml" || status=1
        wp @staging lumtera check --page="$page" --format=sarif --fail-on=none > "reports/lumtera${name}.sarif"
      done
    - exit $status
  artifacts:
    when: always
    paths:
      - reports/
    reports:
      junit: reports/*.xml
  • JUnit failures are the issues at or above --fail-on. Everything else is listed as skipped, with its details in the test's output.
  • Each page is fetched twice: once for each format. The SARIF run uses --fail-on=none, so only the JUnit run decides the result.
  • If a page can't be fetched, the SARIF line exits with 2 and the job stops there.

Other CI tools ​

Any tool that reads JUnit XML works the same way. Examples are Jenkins' junit step, Azure Pipelines' PublishTestResults task and CircleCI's store_test_results. Run:

sh
wp @staging lumtera check --page=/ --format=junit > lumtera.xml

Then publish lumtera.xml as a test result, and use the exit code to pass or fail the build.

Limits ​

  • check reads the HTML as served. It doesn't run JavaScript or load CSS, so it can't measure rendered color contrast or layout. Pro page checks and review mode measure those in a browser.
  • Automated checks find only some accessibility barriers. A clean report doesn't mean a page is accessible or conforms to WCAG. Keep testing key pages by hand: use only the keyboard, zoom to 200% and try a screen reader. See Manual checks.