Skip to content

Karma with GitHub Actions

This lesson provides a complete, working GitHub Actions workflow for a typical Karma project, covering dependency caching, headless test execution, and uploading generated artifacts.

A Complete Workflow Example

GitHub Actions' standard ubuntu-latest runners already include a recent Chrome/Chromium install, so most projects don't need to install a browser manually — just configure Karma to use it headlessly with container-safe flags.

# .github/workflows/test.yml
name: Karma Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Run Karma tests
        run: npx karma start --single-run --browsers ChromeHeadlessCI

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: karma-results
          path: test-results/

cache: 'npm' on setup-node handles dependency caching automatically; if: always() ensures artifacts upload even when tests fail.

The customLaunchers Piece Referenced Above

// karma.conf.js
config.set({
  customLaunchers: {
    ChromeHeadlessCI: {
      base: 'ChromeHeadless',
      flags: ['--no-sandbox', '--disable-gpu']
    }
  },
  reporters: ['progress', 'junit'],
  junitReporter: { outputDir: 'test-results' }
});
  • The workflow file and karma.conf.js work together — the workflow just invokes what the config already defines.
  • --no-sandbox is typically required even on GitHub-hosted runners due to their containerized execution environment.
  • Uploading test-results/ as an artifact makes the generated JUnit XML downloadable from the workflow run's summary page.
  • if: always() on the upload step is important so you still get results/logs even when the test step itself fails.

GitHub Actions Cheat Sheet for Karma

Key workflow pieces and what they're for.

Step/Setting Purpose
actions/setup-node with cache: 'npm' Caches dependencies between workflow runs
npm ci Installs exact locked dependency versions
--browsers ChromeHeadlessCI Runs with the container-safe custom launcher
actions/upload-artifact Makes generated reports downloadable from the run summary
if: always() Runs a step regardless of earlier step failures

Running a Browser Matrix

GitHub Actions' matrix strategy can run the same suite across several browsers in parallel jobs, giving broader coverage without serializing every browser into one long-running job.

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        browser: [ChromeHeadlessCI, FirefoxHeadlessCI]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npx karma start --single-run --browsers ${{ matrix.browser }}

Each matrix entry runs as its own separate job, visible individually in the workflow run's UI.

Surfacing Failures Directly on Pull Requests

A dedicated test-reporting action (several exist in the GitHub Marketplace) can parse the generated JUnit XML and annotate failing lines directly on a pull request's Checks tab, which is often more actionable for reviewers than reading raw log output.

Common Mistakes

  • Forgetting --no-sandbox and getting a Chrome launch failure on GitHub-hosted runners.
  • Not caching dependencies, adding unnecessary minutes to every single workflow run.
  • Uploading artifacts only on success, losing all diagnostic output on the failures that matter most.
  • Running a full browser matrix on every single push when it's only truly needed before merging to a main branch.

Key Takeaways

  • GitHub-hosted Ubuntu runners already include Chrome; configure Karma to use it headlessly with --no-sandbox.
  • actions/setup-node's built-in caching handles dependency caching with minimal extra configuration.
  • if: always() ensures diagnostic artifacts upload even when the test step itself fails.
  • Matrix strategies enable parallel cross-browser runs without serializing them into one slow job.

Pro Tip

Add if: always() to your artifact upload step from day one, even before you've ever needed it. The first time a CI failure needs deeper investigation, having historical JUnit/coverage artifacts already available — instead of needing to reproduce the failure again — saves real time.