codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
EDU›Testing strategy and quality gates›Step 6

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 pass
  • Require 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

← Step 5

Playwright E2E

🎉 You finished Testing strategy and quality gates

What's next? Pick another course below.

Next: Web security foundations — JWT · OAuth · OWASP →Browse all courses