Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Most Cypress failures in GitHub Actions headless mode are environment or startup problems, not a different test runner. Since Cypress 8.0, cypress run runs headlessly by default. Reproduce that mode locally, then make the browser, Node and Cypress versions, application build, environment variables, server readiness, viewport and runner resources explicit. Start with a health-check URL and failure artifacts before changing assertions or multiplying timeouts.

What headless mode changes

A local cypress open session is interactive and usually headed. A CI command such as cypress run launches a browser without a visible window, often on a fresh GitHub-hosted runner. The test code is the same, but the conditions around it are not. The maintained Cypress GitHub Action README states that, as of Cypress v8.0, cypress run executes in headless mode by default.

Use the headless CI path as the environment to reproduce. A headed run that passes is useful for inspecting a page, but it does not prove that the CI path is fixed.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Compare Why it matters What to record
Browser Rendering, permissions and supported APIs can differ. Name and exact version
Viewport and display Responsive layouts can hide or move elements. Width, height and device scale
Node and Cypress Dependency resolution and browser launch behavior depend on runtime versions. Node, package manager and Cypress versions
Application build Production builds can expose routes, assets or timing issues absent in development. Build command, commit and mode
Configuration Missing secrets, base URLs or feature flags can send the test to the wrong page. Non-secret variable names and resolved URL
Runner resources Memory pressure can crash a browser or server. Runner image, parallel jobs and crash logs

Use a maintained action and a real readiness check

Pin the major version of the official action rather than assembling background processes yourself. The current official guide recommends cypress-io/github-action@v7. This pattern builds the app, starts it, waits for a health endpoint and then selects Chrome:

name: Cypress Tests
on: push

jobs:
  cypress-run:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v7
      - uses: cypress-io/github-action@v7
        with:
          build: npm run build
          start: npm start
          wait-on: 'http://localhost:8080/health'
          browser: chrome

The action installs dependencies and can manage the build, server startup, URL wait and Cypress invocation. Keep its Node runtime and the Node command-layer versions compatible with your repository; the action README documents the v7 Node 24 runtime and its supported Node versions.

Why a health endpoint beats a sleep

Cypress documentation warns that there is no guarantee that your server has booted when cypress run starts. The command npm start & npx cypress run creates a race, while sleep 20 guesses how long a build will take. wait-on polls the URL until it responds or the timeout expires. Its default retry window in the action is 60 seconds. For a legitimately slow build, increase wait-on-timeout rather than adding an arbitrary delay:

- uses: cypress-io/github-action@v7
  with:
    build: npm run build
    start: npm start
    wait-on: 'http://localhost:8080/health'
    wait-on-timeout: 120
    browser: chrome

Make the endpoint inexpensive and deterministic. It should return a successful status only when the process is listening and the application can serve the assets needed by the tests. If the wait still fails, inspect the start-process output and request the URL from the runner before investigating selectors.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make the browser and runtime reproducible

Select the browser intentionally

GitHub-hosted Ubuntu and Windows runners include Chrome, Firefox and Edge; macOS runners also include Safari. The runner images change, so a floating browser can change without a commit to your repository. Set browser: chrome (or the browser you support) and print its version in a diagnostic step. Compare that version with the one used locally.

For stricter repeatability, use a cypress/browsers Docker image and pin an image tag, not latest. Pinning the action major version, container tag, Node version and Cypress package gives you a known combination that can be recreated when a failure appears.

Match viewport and operating-system assumptions

Set the viewport in Cypress configuration or in the test when layout is part of the assertion. A responsive breakpoint can make a button disappear in CI even though the test is correct for a wider local window. Record the operating system, browser, viewport and device scale in the job log. If a test depends on timezone, locale or fonts, configure those explicitly instead of inheriting runner defaults.

Verify the application and environment

  • Confirm the CI build uses the same commit and production/development mode you intend to test.
  • Check that the base URL and API endpoints resolve inside the runner, not only on your workstation.
  • Ensure required secrets and feature flags are present without printing secret values.
  • Use the same lockfile and package-manager command locally and in Actions.

Collect evidence before changing a test

A failure artifact tells you whether the symptom is a missing element, an unexpected URL, a server crash, a browser-launch failure, a timeout or a process killed for resource use. Preserve screenshots and videos as GitHub Actions artifacts:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
- name: Upload Cypress artifacts
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: cypress-artifacts
    path: |
      cypress/screenshots
      cypress/videos
      cypress/logs

If the action itself is the suspect, enable its diagnostics for one run:

- uses: cypress-io/github-action@v7
  env:
    DEBUG: '@cypress/github-action'
  with:
    browser: chrome

Those logs should show the resolved command, wait-on behavior and browser selection. Cypress Cloud can add shareable reports, screenshots, videos, stack traces, Test Replay and flaky-test visibility. Use the artifact that identifies the first wrong event; do not begin by relaxing every assertion.

Fix the common failure classes

“The page is blank” or “element not found”

  • Open the saved screenshot and inspect the URL and document title.
  • Check whether the app returned an error page because an API variable or secret was missing.
  • Verify that the selector exists at the configured viewport and after the application has finished rendering.
  • Wait for a meaningful application condition, such as a status element, instead of a fixed delay.

Cypress commands retry while their built-in conditions are unmet. Prefer a targeted wait for the application state and a selector that represents readiness over a global timeout increase.

“Timed out waiting for the server”

  • Read the server process log for a compilation error, port conflict or missing environment variable.
  • Confirm the health URL uses the port actually bound in CI and that the process listens on the expected interface.
  • Raise wait-on-timeout only after confirming the server is healthy but slow.
  • Request the health URL from a diagnostic shell step on the runner to distinguish networking from application startup.

Browser will not launch or crashes

Check the browser binary and version, Cypress version, operating system and runner logs. A browser crash accompanied by out-of-memory messages points to resource contention rather than a selector defect. Reduce parallel load or select a runner with more memory after confirming the symptom. A pinned browser container can remove image drift.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Only CI assertions fail

Compare URL, viewport, timezone, locale, fonts, network responses and feature flags. A test that relies on animation completion, a third-party service or request ordering may be nondeterministic. Stub external dependencies where appropriate and assert on a stable application state. Do not hide a deterministic defect by multiplying all Cypress timeouts.

Intermittent or flaky failures

Use videos, screenshots and (where enabled) Cloud replay to locate the first divergence. Look for a request that never completed, an element covered by an overlay, a test that shares state with another test, or a server that was not ready. Fix the synchronization or isolation issue at that point; retries can reduce noise but cannot make an incorrect test reliable.

Resource and timing controls

Cypress says hardware requirements depend on the memory used by the browser, the application under test and the local server. If the job shows severe contention, browser crashes or an out-of-memory kill, run fewer jobs concurrently or use a larger runner. Keep the change tied to the observed symptom and record its effect on execution cost.

For timing problems, first establish server readiness and use Cypress command retry behavior. A larger per-command timeout may be appropriate for one known slow operation; a global multiplier can conceal a broken endpoint and make every failure slower.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A repeatable debugging procedure

  1. Reproduce headlessly. Run the same browser and viewport with cypress run locally, preferably in the same container or pinned browser image as CI.
  2. Freeze the inputs. Record commit, Node, Cypress, browser, runner image, viewport, base URL and relevant non-secret configuration.
  3. Prove readiness. Replace background startup and sleeps with the action’s start, wait-on and a real health endpoint.
  4. Capture the first failure. Upload screenshots, videos and logs; enable DEBUG='@cypress/github-action' for action-level detail.
  5. Classify the cause. Separate startup, navigation, browser launch, selector synchronization, application defect and resource failure.
  6. Apply the narrow fix. Change the health check, environment, selector synchronization, browser pin or runner size that matches the evidence.
  7. Run the same path again. Confirm the fix on the pinned CI environment before declaring a headed local run sufficient.

Choose fixes by the trade-off you need

Decision axis Stronger choice Trade-off
Reproducibility Pinned action, Node, browser and container image Updates require deliberate maintenance
Diagnosis Artifacts plus Cypress Cloud replay and traces Storage or Cloud usage can add cost
Startup correctness Health URL with wait-on Requires a reliable endpoint
Execution cost Appropriately sized runner and controlled parallelism Larger runners cost more; fewer jobs take longer
Scope Targeted synchronization or configuration change Requires identifying the actual first failure
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If what you need is a clean screenshot of a deployed page for a test report, visual check or debugging attachment—not another full Cypress browser session—ScreenshotNeo provides a single screenshot API request. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.

See the ScreenshotNeo documentation for all parameters. A cURL call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

And in Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also has an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. It supports full-page and element captures, device presets or custom viewports, dark mode, retina scale, PDF output, custom CSS and JavaScript, clicks, selector waits, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. The parameter names used by other screenshot APIs also work.

The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account to try it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FAQ

Is headless mode itself a Cypress bug?

No. Headless execution is the default for cypress run from Cypress 8.0 onward. A failure usually exposes a difference in environment, readiness, synchronization or resources that a headed local session did not exercise.

Should I use a longer global timeout to make CI pass?

Only when evidence shows a consistently slow, otherwise healthy operation. Fix server startup and command synchronization first; a global increase can conceal a broken application and slow every failure.

When is headed mode useful?

Use it locally to inspect layout and interactively diagnose a failure. Treat the headless, pinned CI path as the acceptance environment because headed success does not validate that path.

Frequently Asked Questions

Can I run Cypress headless on a self-hosted runner?

Yes. Apply the same checks: install a known browser, pin the action and runtime, expose a health endpoint, and monitor memory and browser-launch logs. Self-hosted hardware and images are your responsibility.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What should a health endpoint return?

It should return a successful status only after the server is listening and the application assets needed by the tests are available. Keep it independent of slow, nonessential third-party services.

Do Cypress retries replace fixing flaky tests?

No. Retries can document and reduce transient noise, but artifacts should identify and correct the first synchronization, isolation or dependency problem.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.