Testland
Browse all skills & agents

github-actions-test-jobs

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.

Install with skills.sh (any agent)

npx skills add testland/qa --skill github-actions-test-jobs
View source

github-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

  • Project on GitHub.
  • Need to set up test CI for a new repo.
  • Existing GitHub Actions workflows need standardization.

How to use

  1. Add .github/workflows/test.yml triggered on pull_request and push to main; check out the repo and set up the runtime (Step 1).
  2. Install dependencies and run the suite (npm ci then npm test).
  3. Add a build matrix over OS and runtime versions with fail-fast: false for multi-target signal (Step 2), and shard large suites across parallel jobs (Step 3).
  4. Wire any service containers the tests need and emit JUnit XML, uploading it with if: always() - references/services-and-reporting.md.
  5. Filter triggers by path and add schedule / workflow_dispatch (Step 7); add a concurrency group so superseded pushes cancel (Step 8).
  6. Move tokens into secrets.* and reference them from step env (Step 9).
  7. Verify the workflow runs (see Verify below), then review the anti-patterns table before merging.

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 test

The 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 test

fail-fast: false ensures one matrix failure doesn't cancel others. Matrix size is OS × Node = 3 × 2 = 6 jobs.

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 }}/4

4 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 test

Use 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: chrome

Path 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: true

When 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.

  1. test.yml triggers on pull_request and push to main, with schedule: cron '0 4 * * *' for the nightly run.
  2. The 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.
  3. A separate integration job (Linux only) declares a postgres:15 service with a pg_isready healthcheck and passes DATABASE_URL into npm test.
  4. Tests emit 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.
  5. A 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-patterns

Anti-patternWhy it failsFix
fail-fast: true on matrixFirst failure cancels all; lose multi-target signal.fail-fast: false (Step 2).
No concurrency groupPRs with rapid pushes pile CI runs.Add concurrency cancel (Step 8).
if: always() everywhereSome steps shouldn't run on failure (deploy).Selective if: always() for upload steps only (services-and-reporting).
Hardcoded secrets in YAMLSecret leak; revocation needed.secrets.X references (Step 9).
Massive single workflow fileHard to navigate; merge conflicts.Split per concern (test.yml, deploy.yml, lint.yml).
Missing actions/checkout stepJob can't access repo files.First step always (Step 1).

Limitations

  • Runner cost. macOS / Windows runners 5x-10x more expensive than Linux (after free quota). Budget accordingly.
  • Per-job 6h timeout. Long jobs need splitting.
  • Service containers Linux-only. macOS / Windows runners don't support services: block.
  • JUnit reporting requires a third-party action. No native.

References

  • gha (opens in new window) - GitHub Actions workflow basics: events + jobs + steps; YAML in .github/workflows/.
  • references/services-and-reporting.md - service container + JUnit reporting recipes.
  • 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.

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:6379

GitHub 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-junit

if: 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/.

circleci-test-configs

Configures CircleCI test workflows - `.circleci/config.yml` with workflows, jobs, executors, parallelism (test splitting), orbs (reusable shared config), insights for analytics, contexts for per-team secrets. Use for CircleCI-hosted CI when the team values its parallelism + insights features.

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).