ScreenshotNeo

BlogHow-to

How to Fix Cypress Tests That Run Locally but Skip on GitHub Actions

Find out why Cypress tests are pending, skipped, undiscovered, or never run in GitHub Actions—and fix each case with a repeatable checklist.

By the ScreenshotNeo team30 September 202610 min read

How to Fix Cypress Tests That Run Locally but Skip on GitHub Actions

Start with the first meaningful event in the GitHub Actions log. If Cypress reports a failed before, beforeEach, or afterEach hook and then marks dependent tests as skipped, fix that hook. If tests are pending, inspect empty test bodies, .skip, xit, browser restrictions, and filters. If no specs appear, inspect the workflow command, checkout revision, working directory, specPattern, filenames, and --spec. If tests run but fail only in CI, compare the browser, build, server readiness, environment variables, timing, and runner resources.

The word “skip” describes several different states. The correct fix depends on which state your run actually shows.

1. Identify what “skipped” means in your run

What the output shows Likely cause First inspection
No Cypress step or no Cypress command The workflow never invoked tests Job and step conditions, runTests: false, worker jobs, and the command
No expected specs found Discovery or path mismatch Checked-out files, working directory, specPattern, filename, and --spec
Pending tests Intentional omission, browser restriction, or filtering Empty bodies, .skip, xit, .only, browser, and grep settings
A hook failure followed by skipped tests A shared hook prevented dependent tests The earliest hook error and its stack trace
Tests execute but fail only in CI Browser, build, server, timing, environment, or resource difference The actual CI command and runtime configuration

Cypress calls intentionally omitted tests pending. This includes a test with no body, a test marked with .skip or xit, and a test restricted to a different browser. Cypress uses skipped when a test was expected to run but a shared hook failed, so dependent tests could not proceed. See the official guidance on [writing and organizing tests](https://docs.cypress.io/app/core-concepts/writing-and-organizing-tests).

Classify the run state before changing Cypress code.
Classify the run state before changing Cypress code.

2. Confirm that GitHub Actions actually runs Cypress

Open the workflow that ran for the affected event and branch. Follow the job graph, matrix entries, conditions, and step output until you reach the Cypress command.

Check the action mode

The official Cypress GitHub Action supports installing dependencies without running tests. When runTests: false is set, the action installs and caches Cypress but does not execute the suite. That is valid for an install job only if a later worker job runs Cypress. A green install job is not a green test run.

name: E2E

on:
  push:
  pull_request:

jobs:
  cypress:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run Cypress
        uses: cypress-io/github-action@v7
        with:
          start: npm run start:test
          wait-on: http://localhost:3000
          browser: electron

Cypress recommends binding the action to its current major version, such as v7; verify the [GitHub Actions guide](https://docs.cypress.io/app/continuous-integration/github-actions) when you update a workflow.

Inspect conditions and matrices

  • Check if: expressions on the job and test step.
  • Confirm the matrix includes the expected browser, operating system, and test shard.
  • Check whether a dependency job was skipped, which can prevent the Cypress job from starting.
  • In split workflows, verify that worker jobs depend on the install job and contain an actual Cypress command.
  • Confirm the workflow file in the checked-out commit is the one you edited locally.

3. Fix missing or undiscovered specs

Cypress discovers files through specPattern. The documented end-to-end default is cypress/e2e/**/*.cy.{js,jsx,ts,tsx}; component testing defaults to **/*.cy.{js,jsx,ts,tsx}. A different extension, directory, configuration file, checkout revision, or working directory can make a local spec invisible in CI. See [Cypress configuration](https://docs.cypress.io/app/references/configuration).

Verify the files in the Actions checkout

- name: Show test files and location
  run: |
    pwd
    git rev-parse HEAD
    find cypress -type f -maxdepth 4 | sort
    find . -name '*.cy.*' -type f | sort

- name: Show Cypress version and configuration
  run: npx cypress version

- name: Discover specs without running the full suite
  run: npx cypress run --browser electron --spec 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}'

Compare the commit SHA and file list with your local checkout. An uncommitted local file cannot be discovered by Actions.

Check --spec and the working directory

The --spec option narrows the run, but it does not bypass specPattern. The path must both exist in the checkout and match the configured pattern. In monorepos, make the working directory explicit:

- name: Run one spec from the package directory
  uses: cypress-io/github-action@v7
  with:
    working-directory: packages/web
    spec: cypress/e2e/checkout.cy.ts

If you invoke the CLI directly, quote glob patterns so the shell does not expand them before Cypress receives them:

npx cypress run --spec 'cypress/e2e/checkout.cy.ts'

Review the configuration file

import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
    supportFile: 'cypress/support/e2e.{js,ts}',
    baseUrl: 'http://localhost:3000'
  }
})

Check for a CI-only config file, an environment variable that changes the config, or a package script that passes another --config-file.

4. Remove accidental pending tests and filters

Search committed code

rg -n "\.only\b|\.skip\b|\bxit\b|describe\.only|it\.only" cypress test .
  • it.skip(...), it.xit(...), and an empty test body are pending by design.
  • describe.skip(...) leaves every test in that suite pending.
  • .only focuses execution and can make the rest of the suite appear absent.
  • A test restricted to another browser remains pending when CI runs a different browser.

Remove temporary focus and skip markers before committing. If you need a focused local run, pass a spec path or use a temporary branch instead of committing .only.

Compare the browser

npx cypress run --browser electron
npx cypress run --browser chrome

Use the same browser locally and in Actions while diagnosing. Cypress documents browser-specific test restrictions and recommends trying another browser to isolate browser behavior. The [CI overview](https://docs.cypress.io/app/continuous-integration/overview) also lists browser differences, build changes, timing, environment variables, and machine resources as causes of local/CI differences.

Audit grep and tag filters

If you use a grep plugin or another test filter, print the filter variables in the job and inspect the filter configuration. Cypress documents grepFilterSpecs for filtering spec files. Without grepOmitFiltered, nonmatching tests can appear as pending; with it, they are omitted from output. Decide which behavior you want and verify that the CI value is not empty, misspelled, or different from local.

5. Repair the first failing shared hook

When one before, beforeEach, or afterEach hook fails, Cypress marks dependent tests as skipped. Do not debug every later skipped test as an independent failure. Fix the first hook error, rerun, and then inspect any remaining failures.

describe('checkout', () => {
  beforeEach(() => {
    cy.visit('/checkout')
    cy.get('[data-testid="cart"]', { timeout: 10000 }).should('be.visible')
  })

  it('shows the order total', () => {
    cy.get('[data-testid="order-total"]').should('be.visible')
  })
})

Typical shared-hook areas include navigation, authentication, fixtures, API setup, and cleanup. Treat these as investigation points, not assumptions about your repository. Capture the command, URL, response, and stack trace that precede the skipped cases.

6. Make server startup deterministic

A background server and an immediate cypress run create a race. Cypress warns that there is no guarantee the server has booted before the first visit. Use the action’s start and wait-on options or an equivalent readiness tool rather than a fixed sleep. See the [official CI guide](https://docs.cypress.io/app/continuous-integration/github-actions).

A readiness check removes the race between server startup and cypress run.
A readiness check removes the race between server startup and cypress run.
- name: Run Cypress against a ready server
  uses: cypress-io/github-action@v7
  with:
    build: npm run build
    start: npm run start:test
    wait-on: http://localhost:3000/health
    wait-on-timeout: 120
    browser: chrome

Make the health URL return only when the application and required dependencies are ready. Compare local and CI build commands, modes, ports, API base URLs, and feature flags. A build that differs in CI can expose a real setup problem before any test assertion runs.

7. Compare runtime, environment, and resources

  • Browser: Record the exact browser and version in both environments.
  • Build: Confirm the same production or test build mode and asset output.
  • Environment: Check required variables without printing secrets. Missing base URLs, credentials, feature flags, or time zones can alter setup.
  • Timing: Replace arbitrary sleeps with assertions for visible state, network completion, or a readiness endpoint.
  • CPU and memory: A slower runner can expose race conditions and resource-sensitive behavior.
  • Parallelism: Parallel workers may use different browser versions during runner image rollouts. Cypress documents using a browser Docker image to keep versions consistent when that drift matters; container jobs require Linux.

Use the same browser locally, then reproduce with the CI build and environment. Change one variable at a time so the first difference remains visible.

8. Capture evidence from the failing run

Add temporary diagnostics that record:

  • the job, matrix values, commit SHA, and working directory;
  • the Cypress command and all selection options;
  • the number and names of discovered specs;
  • the first assertion or hook error;
  • browser and Cypress versions;
  • server readiness output; and
  • screenshots, videos, or Cypress Cloud links when your project has enabled those artifacts.
- name: Print non-secret CI context
  run: |
    echo "SHA=$GITHUB_SHA"
    echo "OS=$RUNNER_OS"
    node --version
    npx cypress version
    npm run cypress:run -- --reporter spec

Cypress’s GitHub integration can provide run statistics and links to errors, stack traces, screenshots, and video depending on your project and recording configuration. Do not assume those artifacts exist unless the workflow enables them.

9. A repeatable repair checklist

  1. Open the exact Actions run and identify whether Cypress executed.
  2. Read the first error, especially a shared-hook failure.
  3. Confirm the checked-out SHA and list the spec files present.
  4. Compare specPattern, working directory, and --spec.
  5. Search for .only, .skip, xit, empty tests, browser restrictions, and grep filters.
  6. Print the browser, Cypress version, command, and relevant non-secret environment values.
  7. Use start and wait-on for server readiness.
  8. Run the same browser against the same build locally.
  9. Rerun after fixing the earliest cause; only then investigate later failures.

10. Performance, reliability, and cost considerations

Fast local execution can hide CI races. Reliability improves when the workflow waits on a real readiness endpoint, uses deterministic fixtures, avoids fixed sleeps, and keeps browser versions consistent across workers. Parallelization reduces wall-clock time but increases the importance of identical configuration, artifacts, and checkout state on every worker.

For cost control, avoid rerunning an entire matrix while diagnosing one spec. Reproduce with a single browser and --spec, then restore the full matrix after the cause is fixed. Keep diagnostic logging temporary and redact secrets.

11. Or skip the browser setup

If your CI job only needs a clean image of a page for a report or artifact, ScreenshotNeo provides a single HTTP request instead of maintaining browser setup. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for request options.

cURL

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

Python

import requests

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

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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo includes 1,000 screenshots per month on the free plan with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

12. Troubleshooting quick reference

Symptom Cause Fix
Install job passes, no tests appear runTests: false or no worker command Add a worker job that invokes Cypress.
“No specs found” Path, filename, checkout, or pattern mismatch Print pwd, SHA, files, and effective config.
Most tests are pending .skip, xit, empty body, browser restriction, or filter Search committed code and print filter/browser settings.
One hook fails, many tests skipped Shared setup or cleanup failed Fix the first hook error.
First visit fails intermittently Server startup race Use start and wait-on with a readiness endpoint.
Passes in Electron, fails in Chrome Browser-specific behavior Align browsers and isolate the browser-dependent command or assertion.
Only parallel runs differ Worker configuration or browser drift Compare matrix values and pin a suitable browser image if needed.

13. FAQ

Does a skipped test always mean the assertion failed?

No. A skipped test can be blocked by a failed shared hook; a pending test can be intentionally omitted or restricted to another browser.

Does --spec override specPattern?

No. The selected file must still match the configured discovery pattern.

Should I add a longer sleep before Cypress?

Use a readiness check instead. A fixed delay can be too short on one runner and unnecessarily slow on another.

Why does a green GitHub Actions job contain no Cypress results?

The job may only install dependencies, or a condition may have skipped the test step. Confirm that the log contains the Cypress command and discovered specs.

Can I determine the repository-specific cause from the title alone?

No. You need the workflow YAML, Cypress configuration, checked-out commit, and run output. The diagnostic order above narrows the cause without assuming one universal failure.

Primary references: Run Cypress in GitHub Actions, Continuous Integration with Cypress, Writing and organizing tests, Cypress FAQ, Optimizing test performance, and Cypress CLI reference.