ScreenshotNeo

BlogHow-to

How to Run Changed Cypress Specs First in a Pull Request

Run Cypress specs touched by a pull request for faster early feedback, then run the full suite to retain regression coverage.

By the ScreenshotNeo team4 October 20269 min read

To run changed Cypress specs first in a pull request, compare the pull request’s base and head revisions with Git, keep only paths that match the repository’s configured Cypress spec patterns, run those files with Cypress’s --spec option, and then run the full suite. The first run can surface relevant failures sooner; it does not replace the full run because changes to application code, shared fixtures, support files, or configuration can affect specs that were not edited.

1. Confirm the spec paths Cypress recognizes

Cypress’s specPattern configuration defines which files count as specs. The Git paths you select must match that pattern. The older Cypress example used cypress/integration; use your repository’s actual configured path, such as cypress/e2e, instead of copying that historical location blindly.

Check the Cypress configuration file and run the suite locally to confirm the matched paths. Cypress’s test organization guide documents specPattern and explains that --spec selects a subset of the configured specs.

2. Make the pull request’s base and head available in CI

The comparison should include the entire pull request change set. A comparison against only the latest commit can miss earlier changes in the pull request. Fetch the base and head refs your workflow will compare; checkout defaults and available refs vary by CI configuration.

The example below uses GitHub Actions, where github.base_ref and github.head_ref identify the pull request branches. It fetches those branches, compares their remote-tracking refs, filters for Cypress specs, runs the selected specs if there are any, and finally runs every spec.

3. Add a changed-spec run followed by the full suite

Set CYPRESS_SPEC_DIR to your configured spec directory and adjust the find patterns to match your actual spec extensions. This example assumes spec paths do not contain newlines. It uses NUL-delimited Git output and Bash arrays so spaces in filenames are preserved.

name: Cypress

on:
  pull_request:

jobs:
  cypress:
    runs-on: ubuntu-latest
    steps:
      - name: Check out pull request
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

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

      - name: Install dependencies
        run: npm ci

      - name: Run changed Cypress specs first
        shell: bash
        env:
          BASE_REF: ${{ github.base_ref }}
          HEAD_REF: ${{ github.head_ref }}
          CYPRESS_SPEC_DIR: cypress/e2e
        run: |
          set -euo pipefail

          git fetch origin "$BASE_REF" "$HEAD_REF"
          base="origin/$BASE_REF"
          head="origin/$HEAD_REF"

          changed_specs=()
          while IFS= read -r -d '' path; do
            case "$path" in
              "$CYPRESS_SPEC_DIR"/*.cy.js|"$CYPRESS_SPEC_DIR"/*.cy.jsx|"$CYPRESS_SPEC_DIR"/*.cy.ts|"$CYPRESS_SPEC_DIR"/*.cy.tsx)
                changed_specs+=("$path")
                ;;
            esac
          done < <(git diff --name-only -z "$base...$head")

          if ((${#changed_specs[@]})); then
            printf 'Running %s changed Cypress spec(s) first.\n' "${#changed_specs[@]}"
            npx cypress run --spec "$(IFS=,; printf '%s' "${changed_specs[*]}")"
          else
            echo "No changed Cypress specs matched; skipping targeted run."
          fi

      - name: Run all Cypress specs
        run: npx cypress run

The three-dot Git diff compares the merge base of the base and head refs to the head, which is generally useful for identifying the pull request’s changes. If your CI system checks out a synthetic merge commit or uses a different ref arrangement, verify the compared commits and adapt the ref fetch and diff accordingly.

Cypress accepts comma-separated patterns for --spec. The array expansion above preserves whitespace within individual paths before joining them, but a comma in a filename is ambiguous in Cypress’s comma-separated input. If your repository permits commas in filenames, use a tested invocation strategy compatible with your Cypress version and path conventions, or disallow commas in spec filenames.

4. Run it locally before relying on CI

To inspect changed spec paths on a local branch, fetch the target branch and compare it with your current branch. Replace the directory and extensions to match your config.

git fetch origin main

git diff --name-only origin/main...HEAD -- 'cypress/e2e/*.cy.js' 'cypress/e2e/*.cy.ts'
npx cypress run --spec 'cypress/e2e/login.cy.ts,cypress/e2e/cart.cy.ts'
npx cypress run

The final command runs the full suite regardless of whether the targeted command found files. To test the no-match case, open a pull request that changes no spec files and confirm the targeted step skips cleanly while the full-suite step still runs.

5. Configure the Cypress GitHub Action instead of invoking the CLI directly

The official Cypress GitHub Action supports a spec input for targeted runs. The current Cypress guide recommends the v7 major tag. You can keep the Git diff selection step and pass its result to the action, or use the action’s setup and caching features alongside direct CLI runs. See the official GitHub Actions guide for current inputs and setup options.

An action-based sequence still needs to handle the empty-list case and run the full suite afterward. Ensure dependencies and Cypress are installed in the setup step; avoid repeating installation in later action steps when the workflow already completed it.

Options and decisions to make

Choice Recommendation Reason
Base and head Compare the pull request base to its head over the full PR diff. A latest-commit comparison can omit earlier PR changes.
Spec directory and patterns Use the paths and extensions included by your Cypress specPattern. --spec cannot select files Cypress does not recognize as specs.
No changed spec files Skip only the targeted run; retain the full run. Application or shared-code changes may affect tests even when no spec changed.
Failure behavior Let a failed targeted step fail the job. That provides earlier failure feedback; the full-suite step is still needed on successful targeted runs.
Shared dependencies Run the full suite, or maintain a tested dependency map if selecting related specs. Git path changes alone cannot infer which specs depend on changed application code or fixtures.
Parallelization Start with one ordered job; add CI-level parallelism only if it preserves the required sequencing. Independent jobs may run concurrently, defeating the goal of getting changed-spec feedback first.

Why changed specs first is not the same as spec prioritization

This workflow chooses specs from paths changed in the pull request. Cypress Cloud Spec Prioritization uses prior run failures to decide which specs run first. These approaches use different signals. If you use both, decide whether you still need a full-suite run and check the current Cloud feature availability and terms directly; the selection behavior alone does not establish plan eligibility or pricing.

Troubleshooting

The targeted run says no specs found

Cause: Git paths do not match the configured specPattern, the file extension is missing from the filter, or the path is outside the configured spec directory.

Fix: Inspect git diff --name-only output, confirm the Cypress config’s specPattern, and align the directory and extension filters. A spec outside the pattern cannot be selected by --spec.

The Git diff is empty in CI

Cause: The base or head branch ref was not fetched, the workflow used the wrong ref names, or the chosen comparison points at commits that do not represent the pull request.

Fix: Fetch the needed refs, print the resolved refs and commit SHAs in the job log, and verify the three-dot diff locally against the same SHAs.

The shell reports an ambiguous redirect or breaks on spaces

Cause: A newline-delimited list was expanded as shell words or redirected using unsupported process substitution.

Fix: Use Bash explicitly, NUL-delimited Git output, and an array as in the workflow above. If using a different shell, rewrite and validate the list handling for that shell.

The full suite does not run after changed specs

Cause: The targeted step failed and GitHub Actions skipped later steps by default.

Fix: Decide whether you want the full run after an early failure. If so, set an appropriate condition such as if: ${{ always() }} on the full-suite step and ensure the job still reports failure when either run fails. Most teams can save runner time by stopping after the targeted failure; the suite can run on the next push or in a separate required job if policy demands it.

The changed-spec run passes but the PR still has a regression

Cause: Changed-file selection is not dependency analysis. A changed shared component, fixture, support file, or Cypress configuration can impact specs that were untouched.

Fix: Keep the full-suite run as shown. If you later select related specs instead, define and maintain an explicit dependency map and periodically validate it against full-suite results.

The action runs tests before the changed-spec step

Cause: The Cypress action defaults to running tests when used without the setup-only option.

Fix: Follow the current Cypress GitHub Actions guide. For a setup or install-only action step, configure it not to run tests, then run targeted and full commands in the intended order. Historical examples may use old action versions and inputs.

Performance, reliability, and cost

Changed-first ordering improves the time to feedback only when the targeted specs execute before the suite and the CI workflow does not run them concurrently with the full suite. It does not reduce total test work when the full suite still follows. Fetching refs, installing dependencies, and starting the browser also take time; cache dependencies through your CI provider or the official action’s supported setup where appropriate.

For reliability, keep the full run as a required check if full-suite coverage is part of your merge policy. Verify that the base/head comparison behaves for new branches, force pushes, renamed specs, and pull requests with no Cypress spec changes. Git reports renames as path changes depending on diff settings; selecting the new path is usually sufficient, while deleted paths should not be passed to Cypress.

CI cost depends on runner time, browser startup, install time, and whether the suite runs after the targeted batch. This workflow does not imply a specific time or cost reduction. It prioritizes earlier relevant feedback while retaining the full test run.

Or skip the browser setup

This Cypress workflow is for browser tests in CI. If your task is to capture a website screenshot rather than test your application, ScreenshotNeo is a website screenshot API and MCP server: make one GET request with a URL to receive a PNG, JPEG, WebP, or PDF. Its parameters include options for full-page capture, element selection, viewport and device presets, waits, and more. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.

FAQ

Should I run only changed Cypress specs on a pull request?

Only if your team accepts the coverage tradeoff. Changed specs do not reveal every test affected by shared code, so this guide runs the full suite afterward.

Does --spec accept a directory?

It accepts spec paths or patterns, but selection is constrained by the configured specPattern. Confirm the exact syntax for your Cypress setup and version.

Can I use this approach outside GitHub Actions?

Yes. The core steps are Git diff, filter to configured spec paths, run Cypress with --spec when the list is non-empty, then run the full suite. Adapt ref discovery and shell syntax to your CI provider.

Will changed-first ordering lower my CI bill?

Not necessarily. If every spec still runs, total runner work may be similar or higher due to a second Cypress invocation. The goal is earlier feedback, not a guaranteed cost reduction.