A GitHub Actions workflow that doesn't block on quality failures isn't a quality pipeline — it's a deployment script with decoration. Quality gates must have teeth: they block the merge, they block the deploy, and they report clearly why.
The Gate Architecture
Design your workflow as a series of required jobs. Each job is a gate. The deploy job only runs if all gates pass.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
unit-tests:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:unit -- --coverage
e2e-tests:
needs: unit-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
deploy:
needs: [lint, unit-tests, e2e-tests]
if: success()
runs-on: ubuntu-latest
steps:
- run: echo "All gates passed — deploying"
Coverage Enforcement
Don't just run tests — enforce a minimum coverage threshold:
- name: Check Coverage
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
Branch Protection Rules
Quality gates only work if the branch is protected. In GitHub repository settings:
- Require status checks before merging
- Mark each workflow job as a required check
- Require branches to be up to date before merging
Without branch protection, developers can bypass the gates with a direct push.
Parallelising for Speed
Run independent gates in parallel. Lint and unit tests can run simultaneously. E2E can shard across multiple runners.
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/4
Conclusion
GitHub Actions is powerful enough to enforce whatever quality bar your team sets. The limiting factor is never the tooling — it's the willingness to make the gates required and hold the line when they fail.