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).
Install with skills.sh (any agent)
npx skills add testland/qa --skill ci-test-job-conventionsci-test-job-conventions
Overview
Per-CI platform skills (GitHub Actions / GitLab CI / Jenkins / CircleCI) cover how to express workflows. This skill covers what to express - the cross-platform conventions that apply regardless of CI tool.
When to use
How to use this reference
Then re-check the workflow against the conventions below.
§1 - When to shard
Sharding splits a test suite across N parallel jobs. Decision matrix:
| Suite total runtime | Sharding recommendation |
|---|---|
| < 2 min | None. Overhead exceeds benefit. |
| 2-10 min | Optional. Shard if PR feedback time matters. |
| 10-30 min | 2-4 shards. |
| > 30 min | 4-8 shards. Investigate the suite - may be too big. |
| > 60 min | 8+ shards + investigate suite refactoring (per e2e-suite-budget). |
Sharding cost-equivalent is N parallel × ~runtime/N - same total CPU-time, faster wall-clock.
§2 - Retry policy
Distinguish retry classes:
| Failure class | Retry? | Pattern |
|---|---|---|
| Runner died / system failure | Yes (1-2x) | CI platform's retry-on-runner-failure. |
| Network timeout to dependency | Yes (1x) | Test framework retry; flag for analysis. |
| Test flake (passed on retry) | No | Mark + quarantine via flaky-test-quarantine. |
| Test consistently fails | No | Real bug; investigate. |
Rule: Maximum 1 framework-level retry. More retries hide flake.
§3 - Flake quarantine integration
Failed-on-first-run-passed-on-retry tests are flake. Pattern:
Per flaky-test-quarantine (in the qa-flake-triage plugin) for the workflow.
§4 - Artifact lifecycle
# Recommended retention per artifact type
test-results: 14 days # short-term debugging
coverage-reports: 30 days # trend analysis
e2e-screenshots: 30 days # failure debugging
performance-traces: 90 days # historical analysis
deployment-logs: 90 days # audit / compliancePer-CI:
| CI | Retention default |
|---|---|
| GitHub Actions | 90 days; configurable per artifact via retention-days |
| GitLab CI | Per-job expire_in:; 30 days project default |
| Jenkins | Configured via buildDiscarder(logRotator(...)) |
| CircleCI | 30 days; non-configurable on free tier |
Don't retain forever - storage cost.
§5 - Per-trigger filtering
Per-PR (push to PR branch):
- Smoke tests.
- Lint + unit tests.
- Per-changed-files coverage gate.
Per-merge to main:
- Full unit + integration suite.
- Smoke E2E.
- Coverage trend tracking.
Per-deploy to staging:
- Smoke E2E against staging.
- Synthetic monitor smoke.
Nightly scheduled:
- Full E2E across browsers.
- Full security scans (axe, OWASP).
- Mutation testing (per `stryker-mutation`).
Pre-release tag:
- Cross-platform matrix (per `mobile-device-matrix-toolkit`).
- Cross-browser matrix (per `playwright-testing` references/browser-matrix.md).
- Manual UAT sign-off.
Manual / on-demand:
- Specific debug runs.
- Performance / load tests.Tier the cadence to balance feedback latency vs cost.
§6 - Concurrency control
When PRs receive rapid pushes:
| CI | Pattern |
|---|---|
| GitHub Actions | concurrency: group: ${{ github.workflow }}-${{ github.head_ref }} + cancel-in-progress: true |
| GitLab CI | interruptible: true per job |
| Jenkins | disableConcurrentBuilds() in pipeline options |
| CircleCI | auto-cancel-redundant-workflows in project settings |
The pattern: cancel superseded runs. Saves CI cost on stale commits.
§7 - Secret management
Never:
- Commit credentials to .yml / Jenkinsfile
- Use secrets in pull_request from forks
- Use `set -x` in scripts that handle secrets
Always:
- CI platform's secret store
- Mask in logs (`echo "::add-mask::$VALUE"` for GHA)
- Rotate on schedule
- Scope per-job (job-level env > workflow-level env > global)§8 - Per-job timeouts
| Job type | Recommended timeout |
|---|---|
| Lint | 5 min |
| Unit tests | 10 min |
| Integration tests | 20 min |
| E2E (per-browser) | 30 min |
| E2E (full matrix) | 60 min |
| Deploy | 30 min |
| Performance / load | 60 min |
Hard timeouts prevent runaway jobs from consuming runners.
§9 - Cross-CI portability
If the team needs CI portability (multiple CIs in use, or anticipates migration):
The goal: .github/workflows/test.yml, .gitlab-ci.yml, and Jenkinsfile are thin wrappers calling the same scripts.
Worked example: a 25-minute Playwright E2E suite on GitHub Actions
Walk the How-to-use steps for a suite that runs 25 minutes end-to-end.
Deep references
The per-CI reporting mechanism and the per-language reporter and dependency-cache lookups live in one companion reference so this file stays a decision surface:
References
CircleCI test configs
View source (opens in new window)CircleCI test configs
Deep reference for ci-test-job-conventions SKILL.md. 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. Consult for CircleCI-hosted CI when the team values its parallelism + insights features.
Overview
Configuration lives at .circleci/config.yml:
CircleCI's differentiator is test splitting - automatic parallelization based on per-test timing.
When to use
Step 1 - Basic test config
# .circleci/config.yml
version: 2.1
jobs:
test:
docker:
- image: cimg/node:22.0
steps:
- checkout
- run: npm ci
- run: npm test
workflows:
test:
jobs:
- testcimg/node:22.0 is CircleCI's pre-warmed Node image (faster startup than node:22).
Step 2 - Parallelism + test splitting
jobs:
test:
docker:
- image: cimg/node:22.0
parallelism: 4
steps:
- checkout
- run: npm ci
- run:
name: Test (split by timing)
command: |
TESTS=$(circleci tests glob "tests/**/*.spec.ts")
echo "$TESTS" | circleci tests split --split-by=timings | xargs npx jest
- store_test_results:
path: reports/junitcircleci tests split --split-by=timings reads prior run timings and distributes tests evenly across 4 parallel containers.
Step 3 - Multiple executors
executors:
node-22:
docker:
- image: cimg/node:22.0
node-20:
docker:
- image: cimg/node:20.0
with-postgres:
docker:
- image: cimg/node:22.0
- image: cimg/postgres:15.0
environment:
POSTGRES_PASSWORD: test
jobs:
unit:
executor: node-22
steps:
- checkout
- run: npm test
integration:
executor: with-postgres
environment:
DATABASE_URL: postgres://postgres:test@localhost:5432/postgres
steps:
- checkout
- run: npm run test:integration
unit-on-node-20:
executor: node-20
steps:
- checkout
- run: npm testThe second container in the executor (postgres) is reachable as localhost from the primary container.
Step 4 - Workflow orchestration
workflows:
test-and-deploy:
jobs:
- lint
- unit:
requires: [lint]
- integration:
requires: [unit]
- e2e:
requires: [integration]
- deploy:
requires: [e2e]
filters:
branches:
only: mainrequires: builds the DAG; filters: restricts when jobs run.
Step 5 - Orbs (reusable config)
version: 2.1
orbs:
node: circleci/node@5.2.0
codecov: codecov/codecov@4.2.0
jobs:
test:
docker:
- image: cimg/node:22.0
steps:
- checkout
- node/install-packages
- run: npm test -- --coverage
- codecov/upload:
file: coverage/lcov.info
workflows:
test:
jobs: [test]Orbs encapsulate common patterns (npm install, codecov upload, slack notify, etc.). Browse at circleci.com/developer/orbs.
Step 6 - Test results + artifacts
- run:
name: Test
command: npm test -- --reporters=default --reporters=jest-junit
environment:
JEST_JUNIT_OUTPUT_FILE: reports/junit/junit.xml
- store_test_results:
path: reports/junit/
- store_artifacts:
path: coverage/
destination: coveragestore_test_results: parses JUnit XML and renders results in the CircleCI UI (Insights tab tracks flakes / slow tests over time).
store_artifacts: uploads files; viewable via the build's Artifacts tab.
Step 7 - Insights (CircleCI's analytics)
CircleCI Insights tracks per-test metrics:
Available via the project's Insights tab; no extra config needed (uses the data from store_test_results).
Step 8 - Contexts (shared secrets)
workflows:
test:
jobs:
- integration:
context: test-database-credentialsContexts are project-organization-level credential stores - secrets shared across multiple projects without re-entering. Configured in Org Settings.
Step 9 - Conditional / parameterized
parameters:
run-e2e:
type: boolean
default: false
jobs:
e2e:
when: << pipeline.parameters.run-e2e >>
steps:
- run: npx playwright test
# Trigger from API with:
# {"parameters": {"run-e2e": true}}Useful for opt-in expensive tests (cross-browser, full regression).
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
parallelism: N without test splitting | All N containers run all tests; wasted parallelism. | circleci tests split (Step 2). |
| Hardcoded credentials in config.yml | Secret leak. | Contexts or env vars (Step 8). |
version: 2 (deprecated) | Lacks features; orbs / parameters not available. | Always version: 2.1 (Step 1). |
| Not using orbs for common patterns | Boilerplate per project. | Orbs (Step 5). |
Missing store_test_results | Insights doesn't populate; flake tracking missing. | Always store results (Step 6). |
| One mega-job for unit + integration + E2E | No parallelism; slow. | Workflow with parallel jobs (Step 4). |
Limitations
References
CI JUnit and cache-key lookup tables
View source (opens in new window)CI JUnit and cache-key lookup tables
Deep reference for ci-test-job-conventions SKILL.md. Consult by CI platform and language once the workflow's shard / retry / cadence decisions are made - these tables carry no design decisions, only the concrete mechanism per platform and per language.
JUnit XML reporting (cross-CI standard)
Every modern CI accepts JUnit XML via either a native plugin or a third-party action:
| CI | JUnit XML support |
|---|---|
| GitHub Actions | dorny/test-reporter action |
| GitLab CI | artifacts.reports.junit: (native) |
| Jenkins | junit '...' (JUnit Plugin; native) |
| CircleCI | store_test_results: (native; feeds Insights) |
Always emit JUnit XML; the same output feeds every CI's reporting and the downstream junit-xml-analysis parser (in the qa-test-reporting plugin).
Per-language standard reporters
| Language | Default reporter | JUnit XML output |
|---|---|---|
| JavaScript (Jest) | default | jest-junit (separate package) |
| TypeScript | (same as JS) | (same) |
| Python (pytest) | pytest | pytest --junitxml=reports/junit.xml |
| Java (Maven) | Surefire | target/surefire-reports/*.xml (default) |
| Java (Gradle) | Gradle Test | build/test-results/test/*.xml (default) |
| .NET | dotnet test | --logger "junit;LogFilePath=..." |
| Go | go test | gotestsum --junitfile=junit.xml |
| Ruby (RSpec) | RSpec | --format RspecJunitFormatter --out junit.xml |
The same JUnit XML feeds every CI's reporting + downstream analysis tools.
Per-language cache strategies
Per-language cache key recommendations:
Benefits: repeat installs are sub-second vs 30s-2min cold. Trade-off: cache eviction when key changes; extra config to manage.
Related skills
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.
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).