Modern QA2026Line-by-Line Breakdown — tiles
Log inJoin
7 / 48 · 16 CI/CD Pipelines · GitHub Actions: A Practical Example← prev⊞ allnext →☰ Read as one page

2.2Line-by-Line Breakdown

Triggers

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1-5'

Three trigger types cover three different feedback loops:

  • Push runs on direct pushes to main and develop -- catches issues immediately after merge
  • Pull request runs when PRs target main -- catches issues before merge
  • Schedule runs weekday mornings -- catches environment drift, expired tokens, or flaky tests that only show up intermittently

Global Environment Variables

env:
  NODE_ENV: test
  BASE_URL: https://staging.example.com

Set at the workflow level, these are available to all jobs. Use global env vars for configuration that applies everywhere. Use job-level or step-level env vars for more specific settings.

Job Dependencies

needs: unit-tests

The needs keyword creates a pipeline where unit tests gate integration tests gate browser tests. If unit tests fail, integration tests never start -- saving compute time and providing faster feedback.

Why this order matters:

  1. Unit tests (seconds) -- catch logic errors immediately
  2. Integration tests (minutes) -- catch wiring and database issues
  3. Browser tests (minutes) -- catch UI and end-to-end issues

If you run browser tests first and they fail because of a broken utility function, you waste 15 minutes before getting the same feedback that unit tests would have given in 30 seconds.

Services (Sidecar Containers)

services:
  postgres:
    image: postgres:16
    env:
      POSTGRES_DB: test_db
      POSTGRES_PASSWORD: ${{ secrets.DB_PASSWORD }}
    ports:
      - 5432:5432

GitHub Actions spins up a Postgres container alongside your test runner. The database is accessible at localhost:5432 from within the job. This is how you run integration tests against real databases without managing external infrastructure.

Other common services:

  • Redis: redis:7 on port 6379
  • MySQL: mysql:8 on port 3306
  • Elasticsearch: elasticsearch:8.11.0 on port 9200
  • RabbitMQ: rabbitmq:3-management on port 5672

Secrets

${{ secrets.DB_PASSWORD }}

Secrets are encrypted and never printed in logs. GitHub automatically masks them in output. Configure secrets in your repository settings under Settings > Secrets and variables > Actions.

Best practices:

  • Use descriptive names: DB_PASSWORD, not SECRET1
  • Document which secrets are required in a CONTRIBUTING.md
  • Use environment-specific secrets when possible (e.g., STAGING_API_KEY vs PROD_API_KEY)

Matrix Strategy

strategy:
  fail-fast: false
  matrix:
    browser: [chromium, firefox, webkit]
    shard: [1, 2, 3]

This creates 3 browsers x 3 shards = 9 parallel jobs. Each combination runs independently. The fail-fast: false setting means all 9 jobs complete even if some fail, giving you the full picture of failures across browsers.

When to use matrices:

  • Cross-browser testing (Chromium, Firefox, WebKit)
  • Cross-platform testing (ubuntu, windows, macos)
  • Test sharding for parallelization
  • Multi-version testing (Node 22, 24, 26)

Artifacts

- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: unit-coverage
    path: coverage/

The if: always() condition uploads artifacts even when the job fails. For coverage reports, you always want the data. For traces, you may use if: failure() to only collect artifacts when something goes wrong.