Day 3 of 25
BeginnerCI/CD

Day 3 - Add GitHub Actions CI

Set up continuous integration for a repository without automation. Create a GitHub Actions workflow that runs tests on every push.

December 3, 2025
Challenge
Day 3 - Add GitHub Actions CI

Description

Your team has been manually testing code before merging pull requests. This is slow, error-prone, and doesn't scale. You need to set up automated testing that runs on every push.

Task

Add a GitHub Actions workflow that runs tests on every push.

Requirements:

  • Trigger on push and pull request events
  • Run tests for the application
  • Report test results
  • Fail the build if tests fail

Target

  • ✅ Workflow runs automatically on push
  • ✅ All tests execute successfully
  • ✅ Clear pass/fail status in GitHub UI
  • ✅ Runs in under 2 minutes

Sample App

Simple Node.js App (app.test.js)

// app.js
function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

module.exports = { add, multiply };
// app.test.js
const { add, multiply } = require('./app');

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

test('multiplies 2 * 3 to equal 6', () => {
  expect(multiply(2, 3)).toBe(6);
});

package.json

{
  "name": "advent-ci",
  "version": "1.0.0",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^29.0.0"
  }
}

View Solution

Solution

GitHub Actions Workflow (.github/workflows/ci.yml)

name: CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Upload coverage reports
        uses: codecov/codecov-action@v3
        if: matrix.node-version == '20.x'
        with:
          files: ./coverage/coverage-final.json
          fail_ci_if_error: false

  lint:
    name: Lint Code
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint
        continue-on-error: false

Explanation

Workflow Breakdown

1. Trigger Events

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]
  • Runs on pushes to main/develop
  • Runs on PRs targeting these branches
  • Can add more triggers as needed

2. Matrix Strategy

strategy:
  matrix:
    node-version: [18.x, 20.x]
  • Tests against multiple Node.js versions
  • Ensures compatibility
  • Jobs run in parallel

3. Key Steps

Checkout Code

- uses: actions/checkout@v4
  • Fetches repository code
  • Required as first step

Setup Node.js

- uses: actions/setup-node@v4
  with:
    node-version: ${{ matrix.node-version }}
    cache: 'npm'
  • Installs specified Node version
  • Caches npm dependencies
  • Speeds up subsequent runs

Install & Test

- run: npm ci
- run: npm test
  • npm ci for clean, deterministic installs
  • Runs test suite

Best Practices Applied

  1. Use npm ci instead of npm install: Faster, more reliable
  2. Cache Dependencies: Speeds up workflow significantly
  3. Matrix Testing: Test multiple versions
  4. Separate Jobs: Lint and test independently
  5. Descriptive Names: Clear job and step names

Try to solve the challenge yourself first!

Click "Reveal Solution" when you're ready to see the answer.

Result

After setup, you'll see:

  • ✅ Green checkmark on passing commits
  • ✅ Red X on failing tests
  • ✅ Test results in PR checks
  • ✅ Automatic testing on every push

GitHub UI Shows:

✓ CI / Run Tests (18.x) — 1m 23s
✓ CI / Run Tests (20.x) — 1m 19s
✓ CI / Lint Code — 45s

Validation

Test the Workflow

# Clone your repository
git clone <your-repo>
cd <your-repo>

# Make a change
echo "test('example', () => expect(true).toBe(true));" >> app.test.js

# Commit and push
git add .
git commit -m "test: add example test"
git push origin main

# Watch workflow run in GitHub Actions tab

Check Workflow Status

# Using GitHub CLI
gh run list --workflow=ci.yml

# View latest run
gh run view --log

Advanced Features

Add Code Coverage

- name: Generate coverage
  run: npm test -- --coverage

- name: Upload to Codecov
  uses: codecov/codecov-action@v3

Add Build Caching

- name: Cache build output
  uses: actions/cache@v3
  with:
    path: |
      ~/.npm
      node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

Conditional Steps

- name: Deploy
  if: github.ref == 'refs/heads/main' && success()
  run: npm run deploy

Share Your Success

Got your CI pipeline running? Share it!

Tag @thedevopsdaily on X with:

  • Screenshot of your passing workflow
  • Number of tests running
  • Workflow execution time

Use hashtags: #AdventOfDevOps #GitHubActions #CI #Day3

Ready to complete this challenge?

Mark this challenge as complete once you've finished the task. We'll track your progress!

Completed this challenge? Share your success!

Tag @thedevopsdaily on X (Twitter) and share your learning journey with the community!

Proudly Sponsored By

These amazing companies help us create free, high-quality DevOps content for the community

Want to support DevOps Daily and reach thousands of developers?

Become a Sponsor

Found an issue?