Running Karma Unit Tests Headlessly in CI with ChromeHeadless
Configure Karma's ChromeHeadless launcher so your unit tests run in CI without a display server, including container flags, verification steps, and fixes for sandbox crashes and headless-only failures.
31 Jan 2026, 19:01 UTC

Your Karma test suite runs fine on your laptop, but the CI job fails because the build agent has no display server. The fix is Karma's ChromeHeadless launcher: it runs a real Chromium engine without a window, which is exactly what headless CI containers need. This guide configures it, verifies it works, and covers the two failures you are most likely to hit.
Desired outcome
karma start --single-run executes your full suite in a headless Chromium instance, prints results to the console, and exits with code 0 on success or non-zero on failure, so your pipeline can gate on it.
Prerequisites
- Karma 6.x or newer (ChromeHeadless support is stable there). Check with
npx karma --version. - Google Chrome or Chromium 59 or newer installed on the machine or container. Karma finds it via the
CHROME_BINenvironment variable or a standard install path. karma-chrome-launcherinstalled as a dev dependency.- An existing test suite that passes in a headed browser.
Install the launcher from your project root (requires write access to node_modules, i.e. a normal developer or CI user):
npm install --save-dev karma karma-chrome-launcherConfigure the launcher
Edit karma.conf.js in your project root. The minimal working configuration:
module.exports = function (config) {
config.set({
frameworks: ['jasmine'],
files: ['src/**/*.spec.js'],
browsers: ['ChromeHeadless'],
singleRun: true,
reporters: ['dots'],
});
};singleRun: true matters for CI: without it Karma stays in watch mode and your job never finishes. For Docker or root-run CI agents, Chrome's sandbox often breaks startup, so define a custom launcher instead:
customLaunchers: {
ChromeHeadlessCI: {
base: 'ChromeHeadless',
flags: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
},
},
browsers: ['ChromeHeadlessCI'],Understand the trade-offs before copying these flags. --no-sandbox disables Chrome's process isolation; acceptable inside a disposable CI container, not acceptable on a shared machine. --disable-dev-shm-usage works around Chrome crashing in containers where /dev/shm is tiny (Docker's default is 64 MB). --disable-gpu avoids GPU initialization errors on machines with no graphics stack.
Run and verify
Run from the project root as the same user your CI job uses:
npx karma start --single-runCheck three things:
- The output names
ChromeHeadless(or your custom launcher) as the started browser. - The summary line reports all specs as successful, and the shell exit code is 0 (
echo $?on Linux/macOS). - If you enable the coverage reporter, the generated report contains entries for your spec files, proving they actually executed rather than being silently skipped.
If anything is unclear, rerun with detailed logs:
npx karma start --single-run --log-level debugThis prints the exact Chrome binary path and flags used, which resolves most "works locally, fails in CI" mysteries.
Common failures and recovery
Chrome crashes immediately on Linux
Symptom: the log shows the browser starting then disconnecting. Cause is usually sandboxing in a container or running as root. Recovery: switch to the ChromeHeadlessCI custom launcher above. If the binary is not found at all, set CHROME_BIN explicitly, e.g. export CHROME_BIN=/usr/bin/chromium.
Tests pass headed but fail headless
Headless Chromium historically lacked or differed on some features (WebGL, certain rendering paths), and timing-sensitive DOM tests can behave differently without a compositor. Recovery: first confirm the failure is headless-specific by running --browsers Chrome locally. Then either fix the timing dependency (use your framework's async utilities instead of arbitrary setTimeout waits) or isolate feature-dependent tests into a separate suite run only in headed mode.
Flaky results in CI only
Headless mode changes scheduling and rendering timing. Do not mask this with longer sleeps; find the race. The debug log level plus your framework's fake-async or wait-for utilities is the practical path.
Limitations
ChromeHeadless exercises the Blink/V8 engine only, so it gives no cross-browser signal. If you need Firefox or WebKit coverage, add those launchers in parallel in the browsers array. Also note that Karma itself is deprecated by its maintainers; for new projects evaluate Vitest or Web Test Runner, but for an existing Karma suite ChromeHeadless remains the correct CI configuration.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.