CI
In CI, run vidimus after the build and let its exit code gate the job:
npm ci
npm run build
npx vidimus
It exits 1 when an audit fails or errors and 2 on a usage or config error, so either stops
the pipeline. Warnings do not, unless you pass --strict. All codes: CLI.
GitHub Actions #
On GitHub Actions (GITHUB_ACTIONS=true, Forgejo sets it too) the github reporter is added
automatically: findings become annotations on the pull request, and a summary table goes to
the job summary. Other CI systems can read JUnit or JSON:
npx vidimus -r pretty -r junit:reports/vidimus.xml -r json:reports/vidimus.json
json or junit without a file writes to stdout, and pretty moves to stderr.
The github reporter is added even when you pass -r or set reporters, so asking for a JUnit
file keeps the annotations:
name: audit
on:
pull_request:
push:
branches: [main]
jobs:
vidimus:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run build
- run: npx vidimus all -r pretty -r junit:reports/vidimus.xml
- if: always()
uses: actions/upload-artifact@v4
with:
name: vidimus
path: |
reports/
.vidimus/
Each finding becomes a ::error or ::warning annotation titled vidimus <audit>, with the
pages, details and fix in its body, and a file= property when the finding names a file. The job
summary gets a table with every audit’s status and summary. .vidimus/ holds the Lighthouse
reports and the screenshot diffs, worth keeping when those audits run.
Caching the browser #
Puppeteer downloads Chrome into ~/.cache/puppeteer when it is installed. Skip that download
during npm ci, cache the directory, and install the browser explicitly; on a cache hit the
install finds Chrome already there:
jobs:
vidimus:
runs-on: ubuntu-latest
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
- run: npm ci
- uses: actions/cache@v4
with:
path: ~/.cache/puppeteer
key: puppeteer-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npx puppeteer browsers install chrome && npx puppeteer browsers install chrome-headless-shell
- run: npm run build
- run: npx vidimus
The key follows the lockfile, so a puppeteer upgrade fetches the Chrome build it expects. This
repository’s own workflows do the same in .github/actions/setup/action.yml, with pnpm and a
key on the installed puppeteer version.
The zero-dependency audits (i18n, csp, seo, security, budget, assets) need no
browser; a job that only runs those can skip this step and the browser peers entirely.
Screenshot baseline #
shots compares against a baseline that has to exist on the runner. Either commit it by
setting shots.baselineDir to a directory outside .vidimus/, or record it on main and
restore it on pull requests:
- uses: actions/cache@v4
with:
path: .vidimus/shots/baseline
key: shots-baseline-${{ github.sha }}
restore-keys: shots-baseline-
- run: npx vidimus shots ${{ github.ref == 'refs/heads/main' && '--update-baseline' || '' }}
See shots.
Auditing the deployed site #
The build answers most questions, but response headers, redirects and CDN behaviour exist
only on the real host. After the deploy job, run the audits that depend on them against the
live URL with --origin. The page list still comes from the build, so rebuild first:
jobs:
# build and deploy jobs as before
verify:
needs: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run build
- name: Audit the deployed site
run: >-
npx vidimus security links privacy
--origin https://user.github.io/project
--set severity.security=warn
This is what this repository’s .github/workflows/docs.yml does after deploying the docs to
GitHub Pages. With --origin:
- no local server is started;
linksandprivacyload pages from the deployed site securityfetches each page’s response headers instead of reading_headers, and also warns aboutX-Powered-Byand a versionedServerheader- the base path is part of the origin:
https://user.github.io/project, nothttps://user.github.io
--set severity.security=warn keeps the job green on a host that cannot set headers, such as
GitHub Pages, while still showing what is missing.
GitLab CI #
GitLab shows JUnit reports in merge requests and on the pipeline’s Tests tab:
vidimus:
image: node:24
variables:
PUPPETEER_CACHE_DIR: $CI_PROJECT_DIR/.cache/puppeteer
cache:
key:
files: [package-lock.json]
paths: [.cache/puppeteer]
script:
- npm ci
- npm run build
- npx vidimus -r pretty -r junit:reports/vidimus.xml
artifacts:
when: always
reports:
junit: reports/vidimus.xml
paths:
- reports/
- .vidimus/
GitLab only caches paths inside the project directory, hence PUPPETEER_CACHE_DIR. The
node images do not include the shared libraries Chrome needs; install them in the image or
use a system Chromium, see below.
In JUnit, each audit is a test suite and each finding a test case: errors are failures, warnings are recorded as output and do not fail the suite.
Forgejo #
Forgejo Actions sets GITHUB_ACTIONS=true, so the github reporter is added by default and
workflows written for GitHub Actions run largely unchanged. The reporter prints workflow
commands to the log; the summary table is written only when GITHUB_STEP_SUMMARY is set. Keep
a JUnit or JSON file as an artifact if your instance does not display annotations.
Other CI #
Any CI that runs Node 22.18 or later works: the exit code gates the job, and the reports go to files.
npx vidimus -r pretty -r junit:reports/vidimus.xml -r json:reports/vidimus.json
prettyis readable in a log; colours are dropped when the output is not a terminaljunitfor systems with a test report view (Jenkins, Azure Pipelines, CircleCI, Buildkite)jsonfor your own scripts:jq '.results[] | select(.status != "passed")' reports/vidimus.json
Configuration from the environment helps when the same config runs in several places:
VIDIMUS_SITE_URL, VIDIMUS_PORT, VIDIMUS_STRICT=true, VIDIMUS_CONFIG=ci/vidimus.config.ts.
Unrelated VIDIMUS_* variables are ignored. See Configuration.
System Chromium and Docker #
Chrome runs with --no-sandbox --disable-dev-shm-usage by default (browser.args). Point
browser.executablePath at a system Chromium to skip puppeteer’s download.
On a Debian-based image:
apt-get update && apt-get install -y chromium
PUPPETEER_SKIP_DOWNLOAD=true npm ci
VIDIMUS_BROWSER__EXECUTABLE_PATH=/usr/bin/chromium npx vidimus
or in the config:
export default defineConfig({
browser: { executablePath: process.env.CHROME_PATH ?? '' },
});
An empty executablePath means puppeteer’s own Chrome. Every browser audit (a11y, r12s,
privacy, shots, lighthouse) launches through the same settings. The Chromium version
should be one your puppeteer version supports.
--no-sandbox is needed when Chrome runs as root, which is the default in most containers,
and --disable-dev-shm-usage avoids crashes on Docker’s small /dev/shm. Setting
browser.args replaces the list, so keep both flags when adding your own:
export default defineConfig({
browser: { args: ['--no-sandbox', '--disable-dev-shm-usage', '--lang=en-US'] },
});
Speeding up CI #
- Run the zero-dependency audits on every push and the browser audits on pull requests only.
sampleona11y,r12s,privacy,shotsandlighthouseaudits one page per template instead of every page.- Split the audits over parallel jobs (the static audits, the browser audits,
lighthouse): each job serves the build on its own runner. - On small runners, lower
a11y.concurrency,r12s.concurrency,privacy.concurrencyandshots.concurrency(default: half the cores, 2 to 8): the non-exclusive audits run at the same time. links.checkExternal: falsekeeps a pull request job independent of other sites; check external links in a scheduled job instead.