Skip to content
Laviyx Learn

Try

Git and GitHub course menu

GitHub Actions Tutorial: Run Tests Automatically with CI

Learn CI with GitHub Actions: write your first workflow file, run tests on every pull request, understand jobs, steps and triggers, use secrets safely, and block failing merges.

Intermediate7 min readUpdated
On this page

Continuous integration (CI) means that every change is built and tested automatically. GitHub Actions is GitHub's built-in CI service. When you push, GitHub starts a fresh virtual machine, runs your commands, and shows a green tick or a red cross on the commit and on the pull request.

That catches problems early, and it means that reviewers do not have to run the tests by hand.

Your first workflow#

A workflow is a YAML file in the .github/workflows/ folder of your repository. This one runs the tests of a Node.js project:

.github/workflows/ci.yml (YAML)
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm test

Commit the file and push it. GitHub finds it automatically.

Terminal
git add .github/workflows/ci.yml
git commit -m "Run tests in CI"
git push

Open the Actions tab of your repository to watch it run. Click a job to read the log of each step.

What each part means#

PartMeaning
nameThe name shown in the Actions tab
onThe events that start the workflow
jobsOne or more jobs; they run at the same time unless you say otherwise
runs-onThe type of machine to use
stepsCommands run in order; if one fails, the job fails
usesRuns a ready-made action written by someone else
runRuns a shell command

actions/checkout copies your repository onto the machine. Without it, there are no files to test. The @v7 is the version of the action, and you can check the action's page for the latest one.

A Python example#

.github/workflows/ci.yml (YAML)
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: "3.13"
      - run: pip install -r requirements.txt
      - run: pytest

Common triggers#

YAML
on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: "0 2 * * *"   # every day at 02:00 UTC
  workflow_dispatch:      # adds a Run workflow button

Test on several versions#

A matrix runs the same job with different settings:

YAML
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [22, 24]
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: ${{ matrix.node }}
      - run: npm ci
      - run: npm test

Secrets#

Never put passwords or API keys in a workflow file. Store them under Settings, then Secrets and variables, then Actions, and read them like this:

YAML
      - run: npm run deploy
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

GitHub hides the value in the logs, and it does not pass secrets to workflows started by pull requests from forks.

Block merges when checks fail#

Red crosses are only a warning until you enforce them. In the repository's branch rules, turn on Require status checks to pass before merging for main, and choose your CI job. A pull request with failing tests can then no longer be merged.

Cost#

GitHub Actions is free on public repositories using GitHub's standard machines. Private repositories get a monthly allowance of free minutes, and you can check the current limits in GitHub's documentation.

Common mistakes#

Practice#

Practice exercise

Write the on: section for a workflow that runs on pull requests, on pushes to main, and every Monday at 06:00 UTC.

Show hint

In a cron expression, the fields are minute, hour, day of month, month, and day of week. Monday is 1.

Show solution
YAML
on:
  pull_request:
  push:
    branches: [main]
  schedule:
    - cron: "0 6 * * 1"

Key takeaway#

Next: Rebasing.

Finished this lesson?