Step 6
GitHub Actions quality gate
0 views
GitHub Actions quality gate
Block merges until the tests pass. The most reliable way to end "works on my machine".
1. Minimal workflow
name: test
on:
pull_request:
push:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "pnpm" }
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "pnpm" }
- run: pnpm install --frozen-lockfile
- run: pnpm test --run
--frozen-lockfile prevents silent dependency mutation.
2. Matrix
strategy:
matrix:
node: [20, 22]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
Budget carefully; free minutes run out.
3. Cache
- uses: actions/cache@v4
with:
path: ~/.cache/pnpm
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
4. Playwright E2E
e2e:
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "pnpm" }
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm exec playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
5. Real DB (testcontainers)
GitHub Actions ships Docker, so testcontainers works without services:.
6. Secrets
- run: pnpm test
env:
DATABASE_URL: ${{ secrets.TEST_DB_URL }}
Never echo secrets to logs.
7. Branch protection
Require status checks to passRequire branches to be up to date- Disable admin bypass
Makes a red build structurally unmergeable.
8. A convention-audit step
Blocking items are not only build, test, and lint. A shell script that checks a team convention slots in as an ordinary step. Exit code 1 fails the job — and with the branch protection from §7 in place, the merge is blocked.
scripts/audit.sh — find forbidden patterns in the changed files:
#!/usr/bin/env bash
set -euo pipefail
# only the files changed in the PR
files=$(git diff --name-only origin/main... -- '*.ts' '*.tsx')
[ -z "$files" ] && exit 0
# debug console.log · leftover TODO markers
if echo "$files" | xargs grep -nE 'console\.log|// *TODO' 2>/dev/null; then
echo "::error::forbidden pattern found — clean up before merge"
exit 1
fi
echo "audit ok"
Add it as a job, with fetch-depth: 0 so the diff has history to compare against:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # history for the origin/main diff
- run: bash scripts/audit.sh
One last step — add the audit job to the Require status checks list in the Settings → Branches protection rule. Now a PR carrying a forbidden pattern is blocked mechanically, even if a reviewer forgets. Invariant checks like i18n key parity or internal-link validation ride the same frame.
A caution: the audit script must be fast and deterministic. An audit that goes red at random makes the team distrust the gate, and that ends in workarounds.
9. Alerts
- name: Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{ "text": "${{ github.repository }} main build failed: ${{ github.run_id }}" }
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Alert on main only; PR-level pings are noise.
10. Gotchas
- Wrong cache key
- Tight timeouts → blocked CI
- Missing
playwright install --with-deps - No artifact upload → you can't debug failures
11. Cost
Private repos: 2000 free minutes/month. Monitor matrix jobs closely.
Closing
Half a day of setup returns weeks of avoided "works on my machine" debates. The larger the team, the bigger the compounding.
Next
- backend/06-api-handler-pattern
- philosophy/02-ssot-everywhere
🎉 You finished Testing strategy and quality gates
What's next? Pick another course below.