CI Template for GitHub Actions

Updated . Posted . Visible to the public.

Usually our code lives on GitLab, therefore our documentation for CI testing is extensive in this environment. If you are tied to GitHub e.g. because your customer uses it, you may use the following GitHub Actions template for the CI integration. It includes jobs for rspec (parallelized using knapsack, unit + feature specs), rubocop, eslint, coverage and license_finder.

Note that GitHub does not allow the use of YAML anchors and aliases. You can instead use composite actions Show archive.org snapshot to extract and re-use partials between multiple jobs. This template uses two such custom actions: setup-node and setup-ruby

.github/workflows/integration-testing.yml

name: Integration Testing

on:
  push:
    branches: [ main, production ]
  pull_request:
    branches: [ main, production ]
  # Display "Run Workflow" button on the web UI
  workflow_dispatch:
    branches: [ main, production ]

# Cancel existing CI jobs on the same PR when a new commit is being pushed
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  # Run all unit and integration tests
  run_tests:
    name: Run Tests (Partition ${{ matrix.partition }})
    runs-on: ubuntu-latest # NOTE: this image ships with a recent version of chrome!
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_DB: test
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          PGTZ: "Europe/Berlin"

        options: >- # Set health checks to wait until postgres has started
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports: [ '5432:5432' ]

    env:
      DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: 'true'
      DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
      PGTZ: 'Europe/Berlin'
      RAILS_ENV: test
      TZ: 'Europe/Berlin'

    strategy:
      matrix:
        partition: [ 0, 1, 2, 3 ] # Keep in sync with CI_NODE_TOTAL below
      fail-fast: false

    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-node
      - uses: ./.github/actions/setup-ruby

      - name: Setup database schema
        run: bundle exec rails db:create db:schema:load

      - name: Precompile assets
        run: bundle exec rails assets:precompile

      - name: Run parallel tests
        id: rspec
        run: |
          CI_NODE_TOTAL=4 CI_NODE_INDEX=${{ matrix.partition }} \
          bundle exec rails "knapsack:rspec"

      - name: Upload Capybara screenshots
        if: ${{ failure() && steps.rspec.conclusion == 'failure' }} # this should only be done when the previous step fails
        uses: actions/upload-artifact@v4
        with:
          name: capybara-screenshots-${{ matrix.partition }}
          path: tmp/artifacts/capybara

      - name: Upload coverage results
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report-${{ matrix.partition }}
          include-hidden-files: true
          path: tmp/coverage/**/*

  rubocop:
    name: Lint Ruby Code
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-ruby
      - name: Run RuboCop
        run: bundle exec rubocop --color

  eslint:
    name: Lint JavaScript Code
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-node
      - name: Run EsLint
        run: yarn run linter

  # Ensure only permitted licenses are used in the code
  license_finder:
    name: License Finder
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-node
      - uses: ./.github/actions/setup-ruby
      - name: Run License Finder
        run: bundle exec license_finder action_items

  # Run static code analysis checks against the code base
  brakeman:
    name: Brakeman Security Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-ruby
      - name: Run brakeman
        run: bundle exec brakeman --color

  # Combine coverage results from the test runners to a single report
  coverage:
    name: Merge Coverage Reports
    runs-on: ubuntu-latest
    needs: [ run_tests ]
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-ruby

      - uses: actions/download-artifact@v4
        with:
          pattern: coverage-report-*
          merge-multiple: true
          path: tmp/coverage

      - name: Run coverage merger
        run: bundle exec rake merge_coverage_reports --trace
        env:
          RAILS_ENV: test
          TZ: 'Europe/Berlin'

      - name: Upload coverage results
        uses: actions/upload-artifact@v4
        with:
          name: combined_coverage_report
          path: tmp/coverage

.github/actions/setup-node/action.yml

name: Setup Node
description: "Install Node, package manager, and npm packages"

runs:
  using: composite
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version-file: '.nvmrc'
        cache: 'yarn'
        cache-dependency-path: yarn.lock

    - name: Install node modules with yarn
      run: yarn --frozen-lockfile --immutable
      shell: bash

    - name: Verify Node and Yarn versions
      run: |
        node -v
        yarn -v
      shell: bash

.github/actions/setup-ruby/action.yml

name: "Setup Ruby"
description: "Install Ruby and gems with essential system deps for our rails app"

runs:
  using: "composite"
  steps:
    - name: Install system deps
      run: |
        sudo apt-get update
        sudo apt-get install --no-install-recommends --no-install-suggests -y \
          build-essential \
          libffi-dev \
          libxml2-dev \
          pkg-config \
          libxslt1-dev \
          libpq-dev \
          postgresql-client
        sudo apt-get clean
        sudo rm -rf /var/lib/apt/lists/*
      shell: bash

    - name: Setup Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: ".ruby-version"
        bundler-cache: true
        cache-version: 1

    - name: Verify Ruby and Bundler versions
      run: |
        ruby -v
        bundle -v
      shell: bash

    - name: Test postgres connection
      run: "psql $DATABASE_URL -c 'SELECT 1;'"
      shell: bash
Michael Leimstädtner
Last edit
Arne Hartherz
License
Source code in this card is licensed under the MIT License.
Posted by Michael Leimstädtner to makandra dev (2024-02-05 11:10)