github-actions-test-jobs
Configures GitHub Actions test workflows - `.github/workflows/test.yml` with matrix builds (OS × runtime, with per-OS quirks - path separators, line endings, shells - and per-language runtime matrices in references/os-matrix.md), 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, or needs continuous cross-platform OS / runtime coverage.
Install with skills.sh (any agent)
npx skills add testland/qa --skill github-actions-test-jobsgithub-actions-test-jobs
Overview
Test workflows are YAML files in .github/workflows/ that run jobs on trigger events (gha (opens in new window)). 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.
When to use
How to use
Step 1 - Basic test workflow
# .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.
Step 2 - Matrix builds
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.
Runner pinning, per-language runtime matrices (Node / Python / Java / .NET), OS-specific quirks (path separators, line endings, case sensitivity, shells), per-OS conditional steps and tests, and the tiered cost-management cadence are in references/os-matrix.md.
Step 3 - Sharding for parallel execution
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).
Service containers and reporting
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.
Step 6 - Retry policy
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).
Step 7 - Per-trigger filtering
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.
Step 8 - Concurrency control
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.
Step 9 - Secrets + env
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.
Verify before merge
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.
Worked example
A Node service needs PR tests on Linux + macOS, plus a nightly full run against PostgreSQL.
Result: PR authors get fast cross-platform signal, the nightly catches DB-integration regressions, and failed runs still surface their JUnit report.
Anti-patterns
| 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). |
Limitations
References
OS / runtime matrix
View source (opens in new window)OS / runtime matrix
Reference for github-actions-test-jobs: run tests across operating systems (Linux / macOS / Windows) and runtime versions (Node 18/20/22; Python 3.10/3.11/3.12; Java 17/21; .NET 6/8) with GitHub Actions matrix syntax, and address the OS-specific quirks (path separators, line endings, file permissions, shells) that break cross-platform suites.
When to use
For browsers specifically, see the qa-web-e2e plugin's playwright-testing (references/browser-matrix.md).
Step 1 - Define the OS matrix
# .github/workflows/os-matrix.yml
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]GitHub Actions provides:
| Runner | Use |
|---|---|
ubuntu-latest | Default; cheapest; most CI runs here. |
ubuntu-22.04 | Pin specific Ubuntu LTS. |
macos-latest | macOS; needed for iOS / Safari testing. |
macos-15 | Pin macOS version. |
windows-latest | Windows; tests Windows-specific paths. |
windows-2022 | Pin Windows version. |
Step 2 - Define the runtime matrix
Per language:
# Node.js
strategy:
matrix:
node: [18, 20, 22]
os: [ubuntu-latest, macos-latest, windows-latest]# Python
strategy:
matrix:
python: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, macos-latest, windows-latest]# Java
strategy:
matrix:
java: [17, 21]
os: [ubuntu-latest, macos-latest, windows-latest]# .NET
strategy:
matrix:
dotnet: ['6.0.x', '8.0.x']
os: [ubuntu-latest, macos-latest, windows-latest]The full cross-product is 3 OSes × 3 runtimes = 9 jobs. For larger matrices, use include + exclude to skip uninteresting combinations.
Step 3 - Address OS-specific quirks
Path separators
// Bad - hardcoded /
const configPath = projectRoot + '/config/app.json';
// Good - path.join
const path = require('node:path');
const configPath = path.join(projectRoot, 'config', 'app.json');Line endings
# .gitattributes
*.sh text eol=lf
*.bat text eol=crlf
*.json textWithout .gitattributes, Windows users may commit CRLF; tests that compare output strings break.
Case sensitivity
// On Linux: import './Utils' fails if file is './utils'
// On macOS / Windows (default): both work
// Always match file case exactly:
import { foo } from './utils'; // matches utils.jsShell
- name: Run script (cross-platform)
shell: bash
run: ./scripts/setup.shshell: bash works on Linux + macOS + Windows (via Git Bash on Windows runners).
Step 4 - Per-OS conditional steps
When OS-specific setup is needed:
- name: Install Linux deps
if: runner.os == 'Linux'
run: sudo apt-get install -y libssl-dev
- name: Install macOS deps
if: runner.os == 'macOS'
run: brew install openssl
- name: Install Windows deps
if: runner.os == 'Windows'
run: choco install opensslStep 5 - Aggregate per-OS results
## OS / runtime matrix results - `<sha>`
| OS | Runtime | Tests | Pass | Fail | Time |
|-----------|----------|------:|-----:|-----:|-----:|
| Linux | Node 22 | 142 | 142 | 0 | 2m |
| Linux | Node 20 | 142 | 142 | 0 | 2m |
| Linux | Node 18 | 142 | 140 | 2 | 2m | ← Node 18 incompat
| macOS | Node 22 | 142 | 141 | 1 | 3m | ← macOS path issue
| macOS | Node 20 | 142 | 141 | 1 | 3m |
| Windows | Node 22 | 142 | 140 | 2 | 4m | ← Windows path issue
| ...Step 6 - Per-OS conditional tests
Some tests are OS-specific:
// jest.config.js
module.exports = {
testPathIgnorePatterns: process.platform === 'win32'
? ['unix-only.test.js']
: ['windows-only.test.js'],
};Or via test framework conditionals:
test.skipIf(process.platform === 'win32')('uses fork()', () => {
// POSIX-specific test
});Step 7 - Cost management
Matrix size grows multiplicatively. Manage cost:
| Tier | Cadence | Matrix size |
|---|---|---|
| Per-PR (smoke) | Per push | 1 × 1 = 1 job (Linux + latest runtime). |
| Per-merge to main | Per merge | 3 × 1 = 3 jobs (3 OSes + latest runtime). |
| Nightly | Cron | 3 × 3 = 9 jobs (full matrix). |
| Pre-release | Tag | Full matrix + extra exotic combinations. |
The "smoke matrix" per-PR keeps CI cheap; the full matrix runs less frequently.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Hardcoded / path separators | Breaks on Windows. | path.join (Step 3). |
fail-fast: true on the matrix | One OS fails; can't see others. | fail-fast: false. |
| Same matrix every commit | CI cost explodes; team disables. | Tiered cadence (Step 7). |
| Per-OS code in production | If/else by OS; high maintenance. | Cross-platform abstractions in production; OS-specific code in glue layer only. |
Skipping .gitattributes | CRLF / LF mixing; tests fail mysteriously. | Always set (Step 3). |
Limitations
References
GitHub Actions - service containers and reporting
View source (opens in new window)GitHub Actions - service containers and reporting
Deeper recipes split out of github-actions-test-jobs SKILL.md: wiring the service containers the tests depend on, and publishing JUnit results as artifacts plus PR-check summaries.
Service containers (PostgreSQL, Redis, etc.)
jobs:
integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports: [5432:5432]
redis:
image: redis:7
ports: [6379:6379]
steps:
- uses: actions/checkout@v5
- run: npm ci
- run: npm test
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/test
REDIS_URL: redis://localhost:6379GitHub Actions provides container-based services on Linux runners. Healthcheck options ensure tests don't start before the DB is ready.
JUnit reporting + artifacts
- run: npm test -- --reporters=default --reporters=jest-junit
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: test-results/
- uses: dorny/test-reporter@v1
if: always()
with:
name: Test results
path: test-results/junit.xml
reporter: java-junitif: always() ensures artifacts upload even on test failure. The dorny/test-reporter action surfaces results in the PR check summary.
Related skills
ci-test-job-conventions
Pure-reference for cross-CI test workflow conventions - when to shard (and how many shards), retry policy (which failures are safe to retry), flake-quarantine integration, artifact retention, per-trigger cadence (per-PR vs per-merge vs nightly), concurrency-cancel patterns, per-job timeouts, secret management, and cross-CI portability. Use as the team's reference for CI test-workflow design across GitHub Actions / GitLab CI / Jenkins / CircleCI; per-CI reporting and per-language reporter / cache-key lookups live in references/, as do the CircleCI test-config patterns (.circleci/config.yml workflows, test splitting, orbs, contexts - references/circleci.md).
gitlab-ci-test-jobs
Configures GitLab CI/CD test stages - `.gitlab-ci.yml` with parallel matrix, artifact reports (junit, coverage), services (postgres, redis), needs / dependencies between jobs, only/except + rules for trigger filtering, retry policy. Use when the project hosts on GitLab and the team wants idiomatic GitLab CI patterns.
jenkinsfile-test-stages
Configures Jenkins declarative pipeline test stages - `Jenkinsfile` with stages, parallel + per-agent execution, post-actions (always / failure / success), pipeline-junit-plugin for test reports, lockable resources for shared infra. Use for Jenkins-based CI (common in enterprise / regulated environments).