A website audit with GitHub Actions can check the deployed page behind a pull request before the change reaches users. The useful target is not the source tree alone. It is a public preview or test URL where redirects, response headers, metadata, browser rendering, and runtime requests behave as they will after deployment.
This guide shows how to choose that target, protect the audit token, submit and poll a public-URL audit, and turn the result into a reviewable CI decision. It complements unit, integration, and end-to-end tests. It does not replace them.
Website audit with GitHub Actions checklist
Set up the check in this order:
- Deploy a stable public preview or choose a controlled production URL.
- Define which findings block the pull request.
- Keep the audit token in GitHub Actions secrets.
- Keep the public target URL in a repository variable.
- Run the audit after the deployment is reachable.
- Poll with a deadline instead of waiting without a limit.
- Show scores, coverage, exact findings, and the report link.
- Treat missing measurement as missing evidence, not a pass.
- Skip secret-backed execution for untrusted fork pull requests.
- Save a compatible result as the baseline for later regressions.
The website launch checklist defines the wider release contract. Use the GitHub Actions job for repeatable checks that have a clear result, then keep judgment-heavy content, design, and task review visible to a person.
Audit a deployed URL, not localhost
A source test can confirm that a component renders. It cannot confirm that production DNS, redirects, CDN rules, headers, generated metadata, or third-party requests work on the deployed route. Put the public-URL audit after the preview deployment job and pass the final HTTPS address to it.
Choose one URL that represents the pull request's main visitor task. Good first targets include:
- the homepage of a marketing site;
- the documentation start page;
- a campaign landing page changed by the pull request; or
- a preview route whose host is already registered with the audit project.
Do not send private preview credentials to a public scanner. Slop Audit accepts public HTTP or HTTPS pages and does not bypass authentication. If every preview is private or uses a different origin, run the CI audit against a controlled public test route or after deployment instead.
Keep the target stable enough to compare. A random preview host per pull request may not match an origin-restricted CI token and may make historical results hard to group. A fixed preview origin with unique paths is easier to secure and review.
Separate the website audit from source checks
Keep build, lint, unit tests, browser tests, and the deployed audit as separate jobs or named steps. A reviewer should be able to tell whether a failure came from code quality, an application flow, or the public response.
The deployed audit should inspect signals that only exist after release infrastructure runs, such as:
- the final URL, status, redirects, and content type;
- title, description, canonical URL, crawl controls, and structured data;
- mobile and desktop layout evidence;
- runtime errors and failed same-site requests;
- accessible names, labels, focus, contrast, and target geometry;
- the first visible task and its destination; and
- changes from a compatible prior baseline.
The website regression testing checklist explains how to choose a small baseline across response, content, crawler, browser, accessibility, and performance layers. Avoid putting every possible route into the first CI gate. Start with one critical page and expand when the signal stays useful.
Store the token as a GitHub Actions secret
Create a project in Slop Audit, generate its CI token, and store that token as SLOP_CI_TOKEN. Store the public page as a variable such as SLOP_CI_TARGET_URL. The token is limited to its project's registered origin, but it is still a credential and should never appear in workflow output.
GitHub's Actions secrets documentation covers repository, environment, and organization secrets. Use the narrowest scope that fits the deployment. Restrict the job to contents: read unless it must publish a pull request comment.
GitHub does not pass repository secrets to workflows triggered by pull requests from forks. That is a useful boundary. Do not switch to pull_request_target and execute untrusted pull request code just to regain the secret. GitHub's secure-use reference explains why untrusted input and privileged workflow contexts need extra care.
For a same-repository pull request, a minimal job can skip fork execution explicitly:
name: deployed website audit
on:
pull_request:
permissions:
contents: read
jobs:
website-audit:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, linux, x64, ci]
timeout-minutes: 8
steps:
- name: Submit and wait for Slop Audit
env:
SLOP_AUDIT_URL: https://slopaudit.dev
SLOP_CI_TARGET_URL: ${{ vars.SLOP_CI_TARGET_URL }}
SLOP_CI_TOKEN: ${{ secrets.SLOP_CI_TOKEN }}
shell: bash
run: |
set -euo pipefail
accepted="$(curl --fail-with-body --silent --show-error \
--request POST \
--header "Authorization: Bearer ${SLOP_CI_TOKEN}" \
--header "Content-Type: application/json" \
--data "$(jq -n --arg url "${SLOP_CI_TARGET_URL}" \
--arg key "${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
'{url: $url, idempotency_key: $key}')" \
"${SLOP_AUDIT_URL}/api/ci/scans")"
scan_id="$(jq -r '.scan_id // empty' <<<"${accepted}")"
test -n "${scan_id}"
for attempt in {1..36}; do
result="$(curl --fail-with-body --silent --show-error \
--header "Authorization: Bearer ${SLOP_CI_TOKEN}" \
"${SLOP_AUDIT_URL}/api/ci/scans/${scan_id}")"
if [[ "$(jq -r '.terminal // false' <<<"${result}")" == "true" ]]; then
readiness="$(jq -r '.audit.scores.readiness // "not measured"' <<<"${result}")"
risk="$(jq -r '.audit.scores.slop_risk // "not measured"' <<<"${result}")"
coverage="$(jq -r '.audit.scores.coverage_percent // "not measured"' <<<"${result}")"
echo "### Slop Audit" >> "${GITHUB_STEP_SUMMARY}"
echo "Readiness: ${readiness}/100 · Slop Risk: ${risk}/100 · Coverage: ${coverage}%" >> "${GITHUB_STEP_SUMMARY}"
exit "$(jq -r '.exit_code // 1' <<<"${result}")"
fi
sleep 5
done
echo "Audit did not finish before the CI deadline." >&2
exit 1
This example shows the API contract and the security boundary. A production workflow should also add the returned report URL, top fixes, failed rules, and bounded evidence to the step summary. GitHub documents summaries and error annotations in its workflow commands reference.
Define the gate before the first failure
Write the release policy before enabling a required check. Decide whether the job blocks on a critical failure, a score threshold, a confirmed regression, incomplete coverage, or some combination.
Slop Audit's current CI result passes when Production Readiness is above 90 and Slop Risk is below 10. The response also identifies whether the audit was complete or partial and reports its coverage and confidence. A partial report can meet the numeric threshold, so a team that requires complete browser evidence should add that rule to its own release policy.
Keep these states distinct:
- Failed: the audit measured a release condition and it crossed the gate.
- Partial: a report exists, but some applicable checks did not complete.
- Unable to test: the service could not produce enough evidence for a decision.
- Not run: configuration, fork policy, or job conditions prevented execution.
Do not convert the last three states into a green claim. If the external audit is optional, show NOT RUN or UNAVAILABLE without failing the pull request. If it is required, fail closed and tell the reviewer which evidence is missing.
Put evidence in the pull request
A score without evidence sends the developer to another dashboard to guess what changed. Keep the CI output short but actionable:
- Audit state, readiness, Slop Risk, coverage, and confidence.
- First-task verdict and the observed action.
- Up to three prioritized fixes.
- Exact failed and warning rule IDs.
- A selector, URL, viewport, or response value for each failure.
- Confirmed regressions against the compatible baseline.
- A link to the complete report.
The website audit report example shows the same evidence-to-fix structure for a human report. In GitHub Actions, publish the compact version to GITHUB_STEP_SUMMARY. Add annotations for blocking findings. If you add a pull request comment, update one marker-based comment on reruns instead of posting a new comment each time.
GitHub can require named status checks before a protected branch merges. Its protected-branch documentation explains the repository-side rule. Require the audit only after its target, token, timeout, and failure policy are stable.
Handle baselines and regressions carefully
The first CI run has no comparison point. Save it as evidence, review the result, and let a later compatible run compare the same route and audit matrix.
A useful regression event names the current scan, baseline scan, affected rule, route, and finding. It should fire when a measured check becomes a warning or failure, an existing issue worsens, readiness drops, or Slop Risk rises beyond the accepted threshold. A browser-capacity failure or lower coverage is measurement loss, not proof that the website regressed.
Avoid comparing incompatible audit versions or different routes as if they were the same baseline. Keep visual snapshots under controlled browser conditions and keep metadata, response, and accessibility assertions as structured values. That makes the failure easier to reproduce than a single overall score.
Run the pull request audit with Slop Audit
Run a Slop Audit on the deployed URL before adding it to CI. Confirm that the page is public, the report contains the expected Browser Run and static evidence, and the findings match the route you intend to gate. Then sign in, create a project for that origin, generate a CI token, and add the token and target URL to GitHub Actions.
Slop Audit can return a bounded PR summary with the first-task verdict, readiness, Slop Risk, coverage, top fixes, failed findings, evidence references, and compatible regressions. It audits one public URL per CI request. It does not run your unit tests, access private previews, certify accessibility or security, or prove that a release is safe.
Start with one route and one clear gate. When a failure gives the reviewer enough evidence to fix and rerun the page without guesswork, the automated website audit is doing useful release work.