All guides

LEARN \u00b7 DEBUGGING GUIDE

Tests pass locally but fail in CI: how to debug it

You push a commit, the CI run starts, and tests that were green on your machine turn red. The failure is almost never in the test logic — it is in how CI runs it.

IntermediateCI/CD debugging

What this usually means

CI runs your tests in a clean environment. No stale `node_modules`. No cached build artefacts. No lingering processes. No shared state from a previous run. If your tests depend on anything that does not survive a clean checkout and install, they will fail in CI. The most common culprits are: missing env vars, test file ordering assumptions, uncommitted files your local run relies on, and OS-level differences between your machine and the CI runner.

( 01 )Fast diagnosis

The first ten minutes \u2014 establish facts before touching code.

  • 1Check the CI log for the first failing test. Read the exact assertion error — do not assume it is the same root cause as other failures.
  • 2Compare the Node/runtime version your local machine uses against the CI runner. Run `node --version` locally and check the CI config.
  • 3Look for `npm install` warnings or errors in the CI log. A package that failed to install silently can break many tests.
  • 4Check if any test relies on a file that is `.gitignore`d. CI gets a fresh clone — it will not have those files.
  • 5Rerun the failing test in isolation locally (`npx jest -t 'failing test name'`). If it passes, the issue is test ordering or shared state.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchCI pipeline config file (`.github/workflows/*.yml`, `Jenkinsfile`, `.gitlab-ci.yml`) — check the runner image, Node version, and install steps
  • search`package.json` `engines` field and lockfile — version mismatch with CI runner
  • searchTest framework config (`jest.config.js`, `vitest.config.ts`) — test file ordering, timeout settings, parallelisation
  • searchEnvironment variables in CI secrets/variables settings vs local `.env`
  • search.gitignore — any file needed by tests that is not committed
  • searchCI job logs — `npm install` output, test runner summary, and first failure
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningCI runs a different Node version than your local machine
  • warningTest files run in a different order in CI (alphabetical on Linux vs something else locally)
  • warningA test depends on global state set by a previous test that runs before it only locally
  • warningMissing environment variable in CI secrets
  • warningA dependency fails to install in CI (native module, platform-specific binary)
  • warningCI runner has less memory or CPU than your local machine, causing timeouts
  • warningTest snapshots or fixtures reference absolute paths that differ between machines
  • warningCI uses a different timezone or locale than your local machine
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildPin the exact runtime version in CI config — use the same version your team uses locally
  • buildRun tests with `--runInBand` or `--maxWorkers=1` in CI to rule out parallelisation and ordering bugs
  • buildAdd a `test:ci` script that sets `NODE_ENV=test` and any CI-required env vars with safe defaults
  • buildUse `--clearMocks` or equivalent to reset state between every test file
  • buildCommit any fixture files, snapshots, or test data that tests need — never assume they exist only locally
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedPush a commit that only changes the CI config (no code changes) and confirm tests pass.
  • verifiedRun the exact CI command locally: `NODE_ENV=test npx jest --ci --runInBand` or equivalent.
  • verifiedDelete `node_modules` locally, run `npm ci` (not `npm install`), then run tests — this simulates a clean CI environment.
  • verifiedRun tests on a different OS or in a Docker container that matches the CI runner image.
  • verifiedCheck that all tests pass on two consecutive CI runs with no code changes between them.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningRerunning CI until it passes without investigating the failure
  • warningAdding `--forceExit` or `--detectOpenHandles` as a permanent fix instead of closing resources
  • warningAssuming the CI runner is the same OS as your local machine
  • warningSkipping tests locally because 'they always pass on my machine'
  • warningAdding arbitrary timeouts (`setTimeout`) instead of waiting for async operations properly