WCAG Accessibility Tools for Watch My Domains SED
A native WMD SED extension that scans domains for WCAG accessibility issues using Puppeteer and axe-core, stores summary results in custom domain columns, and provides a browser-based report tool.
Overview
This extension consists of two parts with different deployment paths.
Runtime tools ship as part of the WMD SED application in tools/wcag/:
wcag-gather.php- CLI data gathererwcag-report.php- browser report toolaxe-scan.js- Node.js/Puppeteer scanner script
Setup script is downloaded separately and run once by the administrator:
wcag-setup.sh- available at learn.domainpunch.com/static/downloads/wcag-setup.sh
Standard Installation Layout
For a standard WMD SED installation created by the WMD SED install script:
/home/wmdsed/
sites/
wmdsed60/ <- WMD SED root
tools/
wcag/
axe-scan.js
wcag-gather.php
wcag-report.php
webdata/ <- real Node/npm folder
node_modules/ <- Puppeteer + @axe-core/puppeteer
websites.js <- existing screenshot script (untouched)
axe-scan.js <- copied here by wcag-setup.sh
logs/
wmdsed/
websites -> /home/wmdsed/webdata <- symlink (used by WMD SED)
wcag-data/ <- created on first scan run
example.com.json
anotherdomain.com.json
The websites folder under the log path is a symlink to /home/wmdsed/webdata/. The setup script resolves this automatically.
Requirements
- WMD SED already installed with shell access
- Node.js and Puppeteer already installed (done automatically by the WMD SED install script)
@axe-core/puppeteerinstalled viawcag-setup.sh
Setup
Step 1 - Download and run wcag-setup.sh
Download from learn.domainpunch.com/static/downloads/wcag-setup.sh and run as the wmdsed user:
bash wcag-setup.sh
For a standard installation no editing is required. The script defaults to:
WMD_ROOT=/home/wmdsed/sites/wmdsed60NODE_DIR=/home/wmdsed/webdata
For non-standard installations, edit the variables at the top of the script before running. Run with --dry-run first to verify what will happen without making any changes:
bash wcag-setup.sh --dry-run
The script will:
- Run pre-flight checks on all required paths and report any problems
- Install
@axe-core/puppeteerinto the Node folder if not already present - Copy
axe-scan.jsfromtools/wcag/to the Node folder if not already present - Add a cron job for automatic scanning (configurable via
INSTALL_CRONat the top of the script)
Step 2 - Create custom WMD columns (one time only)
php /home/wmdsed/sites/wmdsed60/tools/wcag/wcag-gather.php --setup-columns
This creates four custom columns in WMD SED:
| Column | Type | Content |
|---|---|---|
wcag_status | string | pass / fail / partial / error |
wcag_level | string | A / AA / AAA / none |
wcag_issue_counts | text | JSON e.g. {"critical":2,"serious":5,"moderate":1,"minor":3} |
wcag_scanned_at | datetime | 2026-03-26 14:32:00 |
Safe to run multiple times - skips columns that already exist.
Step 3 - Create the WCAG category
In WMD SED, create a category named WCAG Test Domains and assign the domains you want to scan to it. This is the default category used by wcag-gather.php. The name can be changed via the WCAG_DEFAULT_CATEGORY constant at the top of wcag-gather.php.
Step 4 - Dry run to verify domain selection
php /home/wmdsed/sites/wmdsed60/tools/wcag/wcag-gather.php --dry-run
Lists every domain that would be scanned without actually scanning anything.
Step 5 - Run the first scan
php /home/wmdsed/sites/wmdsed60/tools/wcag/wcag-gather.php php /home/wmdsed/sites/wmdsed60/tools/wcag/wcag-gather.php --batch=3
Configuration
All constants are at the top of wcag-gather.php:
| Constant | Default | Purpose |
|---|---|---|
WCAG_NODE_FOLDER | websites | Subfolder name under log path where Node scripts live |
WCAG_OUTPUT_FOLDER | wcag-data | Subfolder name under log path where JSON output is stored |
WCAG_DEFAULT_CATEGORY | WCAG Test Domains | WMD category name to scan by default |
WCAG_PROTOCOL_COLUMN | '' | Custom column holding protocol per domain, or empty for auto |
WCAG_SKIP_RECENT_DAYS | 7 | Skip domains scanned within this many days |
WCAG_LEVEL | AA | WCAG level to test against: A, AA, or AAA |
WCAG_TIMEOUT_MS | 30000 | Puppeteer navigation timeout in milliseconds |
WCAG_LOCK_FILE | wcag-gather.lock | Lock file name under log path |
WCAG_OUTPUT_FOLDER must match the constant of the same name in wcag-report.php.
Protocol resolution
If WCAG_PROTOCOL_COLUMN is set to a custom column name (e.g. site_protocol), the scanner reads the protocol from that column for each domain. If empty, the scanner tries https:// first and falls back to http:// automatically on failure.
CLI Reference - wcag-gather.php
| Argument | Type | Description |
|---|---|---|
--category="Name" | key=value | Override default category. Case-sensitive. |
--domain=example.com | key=value | Scan one domain only. Ignores skip-recent rule. |
--batch=3 | key=value | Run N Puppeteer processes in parallel (default: 1). |
--skip-recent=14 | key=value | Override WCAG_SKIP_RECENT_DAYS for this run. |
--force | bare flag | Re-scan all matching domains regardless of wcag_scanned_at. |
--setup-columns | bare flag | Create the four wcag_* custom columns and exit. |
--dry-run | bare flag | Show what would be scanned. No scans, no DB writes. |
--debug | bare flag | Verbose output including SQL, params, and raw results. |
Re-scan behaviour
| Scenario | Behaviour |
|---|---|
wcag_scanned_at is empty | Always scanned |
Scanned within WCAG_SKIP_RECENT_DAYS | Skipped |
--force flag present | Always scanned |
--domain=x specified | Always scanned (force implied) |
Domain eligibility
A domain is excluded from scanning if all three of the following are true:
availability = 'available'registry_expiryis NULL or emptyregistrar_expiryis NULL or empty
Domains with availability = 'Not Available' or 'possibly available' are always included.
Concurrency lock
wcag-gather.php uses a lock file (wcag-gather.lock in the log folder) to prevent concurrent runs. If a second instance is started while one is already running, it exits immediately with a warning. Stale locks from crashed processes are detected via PID check and removed automatically.
What axe-scan.js Does
Called once per domain by wcag-gather.php via shell_exec() or proc_open(). Executed from the Node folder so it resolves node_modules correctly.
- Launches headless Chromium via Puppeteer
- Navigates to the domain URL - tries
https://first, falls back tohttp://on failure - Runs axe-core at the configured WCAG level
- Outputs a single JSON object to stdout - always valid JSON even on error
Scan result status values
| Status | Meaning |
|---|---|
pass | No violations found at the tested WCAG level |
fail | One or more violations found |
partial | No violations but some items could not be fully evaluated |
error | Navigation failed - site unreachable or timed out |
wcagLevel reflects the highest WCAG level at which the page fully passes. A page with no AA violations but some AAA violations returns AA. Full violation JSON files are saved to [log folder]/wcag-data/[domain].json and loaded on demand by the browser report popup.
Browser Report - wcag-report.php
Access as an authenticated WMD administrator via the Custom Built-in Tools page, or directly at https://yourserver/tools/wcag-report.php.
Features
- Summary stat cards: total, scanned, pass, fail, partial, error, not scanned
- Filterable table: filter by domain name, status, or WCAG level
- Sortable columns: click any column header
- Reset button per row: clears
wcag_scanned_atso the domain is re-scanned on the next gather run - Click any row: opens a popup with the full violation report loaded from disk
- Violations tab: all axe violations with impact, WCAG tags, description, and affected HTML
- Incomplete tab: items axe could not fully evaluate
- Export CSV: summary columns for all domains
- Export JSON: full data including violations for all domains
- Session expiry handling: clear prompt to log in again if the WMD session ends
If the WCAG columns have not been created yet, the report page shows a setup guide with the exact commands to run.
Cron Job
The wcag-setup.sh script automatically adds a cron job when INSTALL_CRON="true" (the default). The default schedule scans every 30 minutes with a batch size of 3. Both settings are configurable at the top of wcag-setup.sh before running.
The lock file prevents re-entry if a previous run is still in progress, so overlapping cron runs are not a concern.
To add or edit the cron job manually:
crontab -e
Add a line in the form:
*/30 * * * * php /home/wmdsed/sites/wmdsed60/tools/wcag/wcag-gather.php --batch=3 >> /home/wmdsed/logs/wmdsed/wcag-cron.log 2>&1
Security Notes
wcag-gather.phpis CLI only - it exits immediately if accessed via browserwcag-report.phprequires a valid WMD administrator session - non-admin users are rejected even with a valid login; expired sessions show a clear prompt rather than a silent failure- The
wcag-data/output folder sits inside the WMD log folder which is outside the web root by default and is not web-accessible axe-scan.jsruns headless Chromium with--no-sandbox- ensure the OS user running PHP has appropriate permissions