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.
| Command | What it checks | Exit 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 issues | Every issue already stored from checks of saved content | Always 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:
| Code | Meaning |
|---|---|
0 | No issue at or above --fail-on (default: error) |
1 | At least one issue at or above --fail-on |
2 | Lumtera 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-ontakeserror,warning(also fails on "Needs review"),notice(also fails on tips) ornone(never fails).- WP-CLI itself exits with
1for an unknown option or--formatvalue. Test your command once by hand before relying on the difference between1and2.
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:
# wp-cli.yml
@staging:
ssh: deploy@staging.example.com/var/www/htmlThen 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
wpon the server'sPATH). - 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 --pagegets an error status and exits with2.
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_KEYsecret: a private key the staging server accepts. - The
STAGING_HOSTvariable: for examplestaging.example.com.
Save as .github/workflows/accessibility.yml, next to the wp-cli.yml above:
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 1set +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:
- uses: actions/upload-artifact@v4
if: always()
with:
name: lumtera-sarif-${{ strategy.job-index }}
path: lumtera.sarifPull 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:
- 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-contentEach 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:
{
"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:
- 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
jqline confirms the file holds only the report, and notwp-env's own messages. - A fresh site has little content. Import test content after
startif your pages need it, for example withnpx @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 examplestaging.example.com.
The wp-cli.yml alias from Pointing WP-CLI at the site must be in the repository.
# .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
2and 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:
wp @staging lumtera check --page=/ --format=junit > lumtera.xmlThen publish lumtera.xml as a test result, and use the exit code to pass or fail the build.
Limits
checkreads 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.