Node.js test & build pipeline — every CI platform

Node.js test & build pipeline — every CI platform

Almost every Node.js project has the same baseline pipeline: install dependencies, lint, run unit tests across two or three Node versions, and surface JUnit-format test reports back into the CI UI. Here is that pipeline implemented identically (semantically) on each of the four major CI platforms.

Four things change between the implementations: the YAML keys themselves, where caching is declared, how the version matrix is expressed, and how test reports surface in the UI.

Conversion notes

  • GitHub Actions and CircleCI declare the matrix at the top of the job; GitLab uses parallel:matrix; Bitbucket has no native matrix and the same pipeline is repeated as parallel steps.
  • Caching is a step on GitHub Actions, a job-level block on GitLab, an explicit pair of restore_cache/save_cache on CircleCI, and a step-level caches: list on Bitbucket.
  • Test reports surface natively only on GitLab (artifacts.reports.junit) and Bitbucket (test-reports/ folder). On GitHub and CircleCI you upload the file as an artifact and consume it via dashboards.

Side-by-side implementation

GitHub Actions
name: Test
on:
  push:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node: [18, 20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm test -- --reporter=junit --reporter-options output=report.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: junit-${{ matrix.node }}
          path: report.xml
GitLab CI
stages:
  - test

variables:
  npm_config_cache: $CI_PROJECT_DIR/.npm

cache:
  key: $CI_COMMIT_REF_SLUG
  paths: [.npm/]

test:
  stage: test
  image: node:${NODE}
  parallel:
    matrix:
      - NODE: ["18", "20", "22"]
  script:
    - npm ci --cache .npm
    - npm run lint
    - npm test -- --reporter=junit --reporter-options output=report.xml
  artifacts:
    when: always
    reports:
      junit: report.xml
    paths: [report.xml]
CircleCI
version: 2.1

jobs:
  test:
    parameters:
      node:
        type: string
    docker:
      - image: cimg/node:<< parameters.node >>
    steps:
      - checkout
      - restore_cache:
          keys:
            - npm-{{ checksum "package-lock.json" }}-<< parameters.node >>
      - run: npm ci
      - save_cache:
          key: npm-{{ checksum "package-lock.json" }}-<< parameters.node >>
          paths: [node_modules]
      - run: npm run lint
      - run: npm test -- --reporter=junit --reporter-options output=report.xml
      - store_test_results:
          path: report.xml
      - store_artifacts:
          path: report.xml

workflows:
  main:
    jobs:
      - test:
          matrix:
            parameters:
              node: ["18", "20", "22"]
Bitbucket Pipelines
image: node:20

definitions:
  caches:
    npm: ~/.npm

pipelines:
  default:
    - parallel:
        - step:
            name: test (Node 20)
            caches: [npm]
            script:
              - npm ci
              - npm run lint
              - npm test -- --reporter=junit --reporter-options output=test-reports/report.xml
            artifacts:
              - test-reports/**
        - step:
            name: test (Node 22)
            image: node:22
            caches: [npm]
            script:
              - npm ci
              - npm test
            artifacts:
              - test-reports/**

Related Tools