Q36 of 48 · Cypress
Walk me through how you'd debug a test that passes locally but fails in CI.
Short answer
Short answer: Reproduce the CI environment as closely as possible: same browser, headless mode, viewport, OS. Inspect the CI video, screenshot, and logs. Compare timing — CI is usually slower. Common causes: race with backend, missing test data, browser version mismatch, viewport difference, animations, env-specific config.
Detail
Local-pass / CI-fail is the most common Cypress debugging scenario. The systematic approach:
1. Match the environment. CI usually runs Linux + headless Chrome at a specific viewport; locally you might be macOS + headed Chrome at 1440px. Reproduce locally with:
docker run -it -v $(pwd):/work -w /work cypress/included:13.6.0 \
cypress run --browser chrome --spec cypress/e2e/checkout.cy.ts
The official cypress/included Docker image matches the CI runner exactly.
2. Inspect the artefacts. Cypress in run mode records video and screenshots on failure. Watch the video at the failure moment — almost always reveals the actual problem. Check the screenshot; check the command log printed in the CI logs.
3. Suspect timing first. CI machines are usually slower than dev laptops. The same animation that completes in 200ms locally may take 600ms in CI. defaultCommandTimeout: 4000 may not be enough. Add explicit waits on aliased requests rather than blanket timeout bumps.
4. Suspect data isolation. Locally, the database is whatever you've been hacking on; CI is fresh. Tests that depend on existing data pass locally and fail in CI. Standardise on cy.task('resetDb') + explicit seeding.
5. Suspect viewport. Headless Chrome defaults to a small viewport that may push elements off-screen. Pin viewportWidth/viewportHeight in config to match local.
6. Suspect browser version. Cypress's bundled Chrome version differs across versions; CI may pick up Chromium while locally you're using Chrome stable. Pin the browser explicitly.
7. Suspect concurrency. If CI runs N specs in parallel and locally you run one, shared state in the test environment will surface in CI. Run locally with --reporter ... and several specs to reproduce.
8. Re-run with logging. Add cy.log() markers, console.log in commands, or DEBUG=cypress:* to get verbose output.
The mental model: "passes locally, fails in CI" is almost always a difference, not flake. Find the difference, and the test will pass in CI too.