A minimal GitHub Actions workflow that runs your tests
Add one YAML file under .github/workflows and GitHub runs your tests on every push and pull request. Here is a small, explained example for Node.js and one for Python.
GitHub Actions runs jobs on GitHub’s machines when something happens in your repository. The most useful first job is continuous integration (CI): run the test suite on every push and every pull request, so a broken change shows a red mark before anyone merges it.
Create the workflow file
Workflows live in .github/workflows/ and can have any name ending in .yml. Create .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22, 24]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm test
Commit and push it. The run appears under the repository’s Actions tab, and on pull requests as a check next to the merge button.
What each part does
on: run on pushes tomainand on every pull request.permissions: contents: read: the job’s automatic token can read the code and nothing more. Granting only what a job needs limits the damage if a step misbehaves.concurrency: if you push again while a run for the same branch is still going, the old run is cancelled.runs-on: ubuntu-latest: a fresh Linux virtual machine for each run.strategy.matrix: runs the job once per listed Node.js version, in parallel.actions/checkout: copies your repository onto the machine. Without it the job has no code.actions/setup-node: installs Node.js;cache: npmreuses downloaded packages between runs.npm ciinstalls exactly whatpackage-lock.jsonlists (the lock file must be committed), andnpm testruns thetestscript frompackage.json. A non-zero exit code fails the job.
The same idea for Python
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip
- run: pip install -r requirements.txt
- run: python -m pytest
Quote version numbers such as "3.13". YAML reads an unquoted 3.10 as the number 3.1, which selects the wrong Python.
Version tags for actions
@v7 pins the action to a major version, which receives fixes but no breaking changes. Each action’s README lists its current major version; update the tag when a new one comes out. For extra safety, some teams pin to a full commit hash instead.
Make the check required
A red check alone does not stop anyone from merging. In the repository settings, add a branch ruleset (or a branch protection rule) for main that requires status checks to pass, and pick the test job. When you use a matrix, each version shows up as its own check.
When a run fails
- Open the failed run and expand the red step; the log shows the same output you would see locally.
- “Works on my machine” failures are often a missing committed file, such as a lock file, or a different runtime version. Match the matrix to what you use locally.
- Use the Re-run jobs button for flaky network failures, but fix flaky tests rather than re-running them forever.
Start with this file, then add steps such as a linter or a build once the tests run green.
See also
- 0256 Manage infrastructure as code with Terraform: from first file to plans on pull requests Learn the full Terraform workflow without a cloud account: variables, a module, outputs, state, plan, apply and destroy, a backend for shared… Beginner · Tutorial · Linux, macOS, Windows Article GitHub Actions · Terraform 15 Oct 2026
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- commitpedia-admin +3,469 chars