A build-and-test workflow on every change is the single most valuable thing Actions does for a project.
A workflow file has three top-level keys you will always write: name (optional label), on (the triggering events) and jobs.
Each job needs runs-on, the runner label, and steps. A typical CI job follows the same recipe in every language:
actions/checkoutto clone the commit that triggered the run (the runner starts empty).- A
setup-*action such asactions/setup-nodeoractions/setup-pythonto install a toolchain version. run:steps to install dependencies, lint and test.
A run step executes in bash on Linux and macOS runners (with -e, so the first failing command fails the step) and in PowerShell on Windows. A multi-line command uses the YAML block scalar |. Any step that exits non-zero fails the job, marks the run red and, by default, skips the remaining steps.
Commit the file and push: the run appears under the Actions tab within seconds. With on: [push, pull_request] the result also shows up as a check on pull requests, and branch protection can require it to pass before merging.
Steps can be given a name for readable logs and a working-directory when the project lives in a subfolder. Keep CI workflows fast and boring: the same commands a developer runs locally, in the same order.
Example
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Install
run: npm ci
- name: Lint and test
run: |
npm run lint
npm testRemember
- Checkout, set up a toolchain, then run your commands
- A non-zero exit code fails the step and the job
- Use | for multi-line run scripts
Watch out for
- Forgetting actions/checkout and wondering why the repository files are missing.