GitHub Actions Guide
GitHub Actions is the CI/CD platform built directly into GitHub. Because it lives alongside your code, pull requests, and issues, it provides the shortest path from "code pushed" to "quality checks completed." Every public repository gets generous free minutes, and the marketplace offers thousands of reusable actions for everything from linting to deployment. For quality engineering, GitHub Actions is the natural starting point for automating checks across accessibility, security, performance, and code quality.
This lesson walks through the core concepts of GitHub Actions, shows how to build a workflow that runs quality checks on every push and pull request, and covers advanced techniques like matrix builds, caching, and artifact uploads that make your pipelines fast and informative.
How GitHub Actions Works
GitHub Actions is event-driven. You define workflows in YAML files stored in the .github/workflows/ directory of your repository. Each workflow specifies one or more triggers (events that start the workflow), one or more jobs (groups of steps that run on a virtual machine), and the steps within each job (individual commands or reusable actions).
When a trigger event occurs — a push to a branch, a pull request opened, a schedule firing, or a manual dispatch — GitHub spins up a fresh virtual machine (called a runner), checks out your code, and executes the steps you defined. The results appear as status checks on your commits and pull requests.
Your First Workflow
Here is a minimal workflow that runs on every push and pull request to the main branch:
name: Quality Checks
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
Let us break down each section:
name:A human-readable name that appears in the GitHub UI.on:The events that trigger the workflow. Here, pushes tomainand pull requests targetingmain.jobs:A map of job names to job definitions. Each job runs on a separate runner.runs-on:The operating system for the runner.ubuntu-latestis the most common choice.steps:An ordered list of tasks. Each step eitherusesa reusable action orruns a shell command.
npm ci instead of npm install in CI. The ci command installs from the lockfile exactly, is faster, and fails if the lockfile is out of sync with package.json — which is exactly the behavior you want in a pipeline.
Trigger Events
GitHub Actions supports dozens of trigger events. The most useful for quality engineering are:
push— Runs when code is pushed to specified branches.pull_request— Runs when a PR is opened, synchronized (new commits pushed), or reopened. This is the primary trigger for quality checks because results appear as status checks on the PR.schedule— Runs on a cron schedule. Useful for nightly full security scans or dependency audits that are too slow to run on every push.workflow_dispatch— Allows manual triggering from the GitHub UI. Useful for on-demand tasks like generating a full quality report.
You can combine multiple triggers in a single workflow. For example, run quick checks on every PR but a more comprehensive suite nightly:
on:
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * *' # Every night at 2:00 AM UTC
Matrix Builds
Matrix builds let you run the same job across multiple configurations — different Node.js versions, operating systems, or browser engines. This is essential for ensuring your application works across the environments your users actually use.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test
This configuration creates 9 jobs (3 operating systems multiplied by 3 Node.js versions). Each job runs independently and in parallel, and the results are displayed as a matrix in the GitHub UI. If a test passes on Linux but fails on Windows, you will see exactly which combination broke.
browser: [chromium, firefox, webkit] and run your end-to-end tests across all three engines in parallel.
Caching Dependencies
Installing dependencies from scratch on every run is slow. GitHub Actions provides a built-in caching mechanism that can dramatically speed up your pipelines. The actions/setup-node action supports a cache parameter that handles npm or yarn caching automatically:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
For more control, you can use the actions/cache action directly:
- name: Cache node_modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
The key is based on the lockfile hash, so the cache is invalidated whenever dependencies change. The restore-keys provide fallback keys for partial cache hits.
Running Quality Checks in CI
Here is where quality engineering comes alive. A comprehensive workflow runs multiple types of checks, each targeting a different quality dimension:
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
# Code quality
- name: Lint code
run: npm run lint
- name: Check formatting
run: npx prettier --check .
# Unit and integration tests
- name: Run tests with coverage
run: npm test -- --coverage
# Accessibility
- name: Start dev server
run: npm start &
env:
PORT: 3000
- name: Wait for server
run: npx wait-on http://localhost:3000
- name: Run Pa11y CI
run: npx pa11y-ci
# Security
- name: Audit dependencies
run: npm audit --audit-level=high
- name: Scan for secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Each step targets a different quality dimension: code quality (linting and formatting), functional correctness (tests), accessibility (Pa11y CI), and security (dependency audit and secrets scanning). If any step fails, the entire workflow fails, and the pull request shows a red status check.
Accessibility Testing with Pa11y CI
Pa11y CI is a tool that runs automated accessibility checks against a list of URLs. It is ideal for CI pipelines because it exits with a non-zero code when violations are found, which causes the GitHub Actions step to fail.
You configure Pa11y CI with a .pa11yci JSON file in your repository root:
{
"defaults": {
"standard": "WCAG2AA",
"timeout": 10000
},
"urls": [
"http://localhost:3000/",
"http://localhost:3000/about",
"http://localhost:3000/contact"
]
}
The CodeFrog project itself uses GitHub Actions to run Pa11y CI against its landing page on every pull request. This ensures that accessibility regressions are caught before they reach production — a real-world example of quality engineering in practice.
Note: Automated checks (including Pa11y CI) catch only a subset of WCAG issues. Manual testing with assistive technologies and human judgment is required for full conformance.
Artifact Uploads
Sometimes a quality check produces a report (a coverage HTML file, a Lighthouse JSON, a Pa11y report) that you want to preserve. GitHub Actions artifacts let you upload files from a workflow run and download them later:
- name: Run Lighthouse
run: npx lhci collect --url=http://localhost:3000
- name: Upload Lighthouse report
uses: actions/upload-artifact@v4
with:
name: lighthouse-report
path: .lighthouseci/
retention-days: 30
Artifacts appear on the workflow run summary page. Team members can download them to review detailed reports without needing to reproduce the run locally.
Status Checks and Branch Protection
Running quality checks is only half the equation. The other half is preventing merges when checks fail. GitHub branch protection rules let you require specific status checks to pass before a pull request can be merged:
- Go to your repository's Settings → Branches.
- Add a branch protection rule for
main. - Enable "Require status checks to pass before merging."
- Search for and select the workflow jobs you want to require (e.g.,
quality,test). - Optionally enable "Require branches to be up to date before merging" to ensure the checks ran against the latest
main.
With branch protection in place, no one can merge code that fails your quality checks — not even repository administrators (if you enable the "Include administrators" option). This is the foundation of an automated quality gate, which we will explore in detail in the Automated Quality Gates lesson.
Reusable Workflows and the Marketplace
As your CI/CD setup grows, you may find yourself duplicating workflow logic across repositories. GitHub Actions addresses this with two mechanisms:
- Reusable workflows: You can define a workflow in one repository and call it from workflows in other repositories using the
useskeyword at the job level. This is ideal for organization-wide quality standards. - The GitHub Actions Marketplace: Thousands of community-maintained actions are available for common tasks. Before writing a custom step, check the marketplace — there is likely an action for what you need.
Popular marketplace actions for quality engineering include:
- actions/checkout — Checks out your repository code
- actions/setup-node — Sets up Node.js with caching
- actions/cache — Caches dependencies and build outputs
- actions/upload-artifact — Uploads build artifacts
- gitleaks/gitleaks-action — Scans for secrets in code
- treosh/lighthouse-ci-action — Runs Lighthouse performance audits
Putting It All Together
A mature quality engineering workflow on GitHub Actions typically includes multiple jobs that run in parallel for speed, each focused on a specific quality dimension. The workflow produces clear pass/fail signals that feed into branch protection rules, and it uploads detailed reports as artifacts for deeper investigation when something fails.
The key principles to remember are:
- Fail fast: Put the quickest checks (linting, formatting) first so developers get feedback in seconds, not minutes.
- Cache aggressively: Cache dependencies, build outputs, and tool binaries to keep pipeline times low.
- Use matrix builds when you need to test across multiple environments, but be mindful of the total number of jobs — 3 dimensions of 4 values each creates 64 jobs.
- Upload artifacts for any check that produces a detailed report, so team members can investigate failures without reproducing them locally.
- Enforce with branch protection: Quality checks only matter if they block bad code from being merged.
Resources
- GitHub Actions Documentation — Official documentation covering workflows, syntax, runners, and best practices
- GitHub Actions Marketplace — Thousands of reusable actions for CI/CD, quality checks, deployment, and more