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
mainanddevelop-- 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:
- Unit tests (seconds) -- catch logic errors immediately
- Integration tests (minutes) -- catch wiring and database issues
- 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:7on port 6379 - MySQL:
mysql:8on port 3306 - Elasticsearch:
elasticsearch:8.11.0on port 9200 - RabbitMQ:
rabbitmq:3-managementon 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, notSECRET1 - Document which secrets are required in a
CONTRIBUTING.md - Use environment-specific secrets when possible (e.g.,
STAGING_API_KEYvsPROD_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.