Running Karma Unit Tests in Headless Chrome for Single-Run CI
Configure Karma for deterministic headless Chrome unit tests in CI: set singleRun, target ChromeHeadlessCI, and handle browser launch failures.
17 Aug 2025, 04:43 UTC

The CI Problem with Karma's Default Watch Mode
Karma is built to watch source files and re-run tests whenever code changes. That behavior helps on a developer workstation, but in a continuous integration (CI) pipeline it keeps the job alive indefinitely and makes results non-deterministic. What you need in CI is a single, one-shot execution that starts a headless Chrome browser, runs every spec once, reports the aggregate result, and exits with a status code your pipeline can act on.
Desired Outcome
A deterministic single-run configuration where Karma launches Chrome without a visible window, executes the full test suite, prints a summary reporting the number of specs executed and the result, and terminates. The process must exit 0 when all specs pass and a non-zero code when any spec fails or the browser cannot start. No Chrome or Chromium processes should remain running after the command finishes.
Prerequisites
- Node.js runtime: a version supported by your installed Karma release. Engine requirements differ across major Karma versions, so confirm compatibility before upgrading.
- Local packages:
karmaandkarma-chrome-launcherinstalled as development dependencies in the project. - Browser binary: a Chrome or Chromium executable available on the machine or CI image. Karma's Chrome launcher searches standard locations; if it cannot find the browser, you must point it explicitly.
- Container permissions: if you run inside Docker, the process must have permission to launch Chrome with the flags required by the container environment.
Focused Procedure
Configure the Single-Run Launcher
In karma.conf.js, disable the watch-driven loop and target the headless browser:
module.exports = function(config) {
config.set({
// ... frameworks, files, reporters ...
singleRun: true,
autoWatch: false,
browsers: ['ChromeHeadlessCI'],
customLaunchers: {
ChromeHeadlessCI: {
base: 'ChromeHeadless',
flags: [
'--no-sandbox',
'--disable-gpu',
'--disable-dev-shm-usage'
]
}
}
});
};
singleRun: true tells Karma to run the suite once and exit. autoWatch: false prevents file-system watchers from keeping the process alive. The custom launcher extends the built-in ChromeHeadless launcher with flags commonly needed in containerized CI environments.
Point Karma at the Browser Binary
If Chrome is not auto-detected—common on slim CI images that install Chromium under a non-standard path—set the CHROME_BIN environment variable to the absolute path of the executable before starting Karma. Keep this in your CI configuration rather than hard-coding a developer-specific path in karma.conf.js.
# Run in your CI job shell or pipeline configuration.
# Replace the placeholder with the actual path on your image.
export CHROME_BIN=/usr/bin/chromium
npx karma start karma.conf.js --single-run
Required permissions: the CI user must be able to execute the binary at CHROME_BIN. In Docker images running as root, Chrome's sandbox will commonly crash the browser unless --no-sandbox is present; the custom launcher above includes it.
Expected Checks
After the run, verify the following:
- Exit code: the shell variable
$?(or your CI system's status) is0for a passing suite and non-zero for a failure. - Log summary: the output includes a browser connection message and a final aggregate line reporting the number of specs executed and the result, for example a pattern like
Executed N of M SUCCESSorFAILED. - No lingering processes: in a Docker job, confirm that no
chromeorchromiumprocesses remain after the command returns.
Recovery Options When the Browser Fails to Launch
| Symptom | Likely cause | Practical fix |
|---|---|---|
| Karma reports no browser captured | Chrome binary not found | Install Chromium or set CHROME_BIN to the absolute path. |
| Browser starts then crashes immediately | Running as root without sandbox disabled | Keep --no-sandbox in the CI launcher. Avoid it on developer workstations where the sandbox is not the blocker. |
| Renderer runs out of memory or crashes | Small /dev/shm (Docker's shared-memory filesystem) | Add --disable-dev-shm-usage to force temporary files onto disk. |
| Timeout errors on slow runners | Default Karma timeouts too aggressive | Raise captureTimeout and browserNoActivityTimeout in karma.conf.js. |
Limits and Verification Before You Standardize
Headless flag behavior and Node engine requirements vary across Karma and karma-chrome-launcher releases. Before rolling this configuration out broadly, check the installed versions against their documented support matrices:
# Run in the project directory.
npx karma --version
npm ls karma-chrome-launcher
To confirm the exit-code contract in your own pipeline, deliberately introduce a failing spec in a branch and verify that the CI job exits non-zero and the log names the failing browser and spec. Locally, run the same command and confirm $? is 0 with a summary reporting every spec executed. If /dev/shm warnings appear in Docker logs, treat that as a signal to keep --disable-dev-shm-usage enabled.
This configuration targets CI. Leaving singleRun and autoWatch disabled locally removes the watch-driven feedback loop developers expect, so keep separate configuration files or environment overrides for local development.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.