Configures GitHub Actions test workflows - `.github/workflows/test.yml` with matrix builds (OS × runtime), JUnit XML artifact upload, retry/sharding, services (PostgreSQL, Redis), per-trigger filtering (pull_request, push, schedule, workflow_dispatch). Use when the project hosts on GitHub and the team wants idiomatic GitHub Actions patterns for test workflows.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Test workflows are YAML files in .github/workflows/ that run jobs on
trigger events (gha). This skill sets up the idiomatic patterns -
matrix builds, sharding, service containers, JUnit reporting, trigger
filtering, concurrency, and secrets. Start with the minimal pattern in
Step 1.
.github/workflows/test.yml triggered on pull_request
and push to main; check out the repo and set up the runtime
(Step 1).npm ci then
npm test).fail-fast: false for multi-target signal (Step 2), and shard
large suites across parallel jobs (Step 3).if: always() -
references/services-and-reporting.md.schedule / workflow_dispatch
(Step 7); add a concurrency group so superseded pushes cancel
(Step 8).secrets.* and reference them from step env
(Step 9).# .github/workflows/test.yml
name: test
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm ci
- run: npm testThe minimal pattern: trigger on PR + push-to-main, install deps, run tests.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [20, 22]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with: { node-version: ${{ matrix.node }} }
- run: npm ci
- run: npm testfail-fast: false ensures one matrix failure doesn't cancel
others. Matrix size is OS × Node = 3 × 2 = 6 jobs.
For large suites:
jobs:
test:
strategy:
matrix:
shard: [1, 2, 3, 4]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: npx jest --shard=${{ matrix.shard }}/44 parallel jobs, each running 1/4 of the test suite. Faster than serial execution; cost-equivalent (same total CPU-time).
Wiring the service containers the tests depend on (PostgreSQL, Redis) and publishing JUnit XML as artifacts plus PR-check summaries are in references/services-and-reporting.md.
GitHub Actions doesn't ship native test-retry; use the framework's
retry mechanism (e.g., Playwright's retries config) or a wrapper
action:
- uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 2
command: npm testUse sparingly - retries hide flake. Prefer
flaky-test-quarantine (in the qa-flake-triage plugin).
on:
pull_request:
paths:
- 'src/**'
- 'tests/**'
- 'package.json'
push:
branches: [main]
paths-ignore:
- 'docs/**'
- '*.md'
schedule:
- cron: '0 4 * * *' # daily 4am UTC
workflow_dispatch: # manual trigger
inputs:
target_browser:
description: 'Browser to test'
type: choice
options: [chrome, firefox, safari]
default: chromePath filters skip workflows when only docs change - saves CI budget.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: trueWhen a PR receives multiple pushes, the older runs cancel - saves CI cost on superseded commits.
env:
CI: true
steps:
- run: npm test
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}Secrets configured in repo settings; never committed.
Run the workflow once before merging - push the branch or trigger it
manually via workflow_dispatch (or dry-run locally with act).
Verify: the matrix expands into the expected job count (e.g. OS × Node =
3 × 2 = 6 jobs) and each job uploads its JUnit artifact. If a job fails on
a missing secret, confirm NPM_TOKEN / TEST_DATABASE_URL exist under
repo Settings -> Secrets and variables -> Actions, add any that are
missing, and re-run the job.
A Node service needs PR tests on Linux + macOS, plus a nightly full run against PostgreSQL.
test.yml triggers on pull_request and push to main,
with schedule: cron '0 4 * * *' for the nightly run.test job runs a matrix of os: [ubuntu-latest, macos-latest] × node: [20, 22] with fail-fast: false - 4
jobs, and no target cancels another.integration job (Linux only) declares a
postgres:15 service with a pg_isready healthcheck and
passes DATABASE_URL into npm test.test-results/junit.xml; actions/upload-artifact@v4
with if: always() keeps the report even when tests fail, and
dorny/test-reporter@v1 renders it in the PR check summary.concurrency group keyed on the ref cancels superseded runs,
so rapid pushes don't pile up billable minutes.Result: PR authors get fast cross-platform signal, the nightly catches DB-integration regressions, and failed runs still surface their JUnit report.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
fail-fast: true on matrix | First failure cancels all; lose multi-target signal. | fail-fast: false (Step 2). |
No concurrency group | PRs with rapid pushes pile CI runs. | Add concurrency cancel (Step 8). |
if: always() everywhere | Some steps shouldn't run on failure (deploy). | Selective if: always() for upload steps only (services-and-reporting). |
| Hardcoded secrets in YAML | Secret leak; revocation needed. | secrets.X references (Step 9). |
| Massive single workflow file | Hard to navigate; merge conflicts. | Split per concern (test.yml, deploy.yml, lint.yml). |
Missing actions/checkout step | Job can't access repo files. | First step always (Step 1). |
services: block..github/workflows/.gitlab-ci-test-jobs,
jenkinsfile-test-stages,
circleci-test-configs -
per-platform alternatives.ci-test-job-conventions - cross-CI conventions.junit-xml-analysis - downstream JUnit XML parser.flaky-test-quarantine - preferred over retries.