Resource library

QA How-To

How to Take a screenshot on failure in Cypress (2026)

Learn cypress how to take a screenshot on failure: automatic run captures, CI artifacts, blackout, Cypress.Screenshot.defaults, and after:screenshot uploads.

22 min read | 2,835 words

TL;DR

Keep screenshotOnRunFailure enabled for cypress run, archive the screenshotsFolder in CI, black out sensitive selectors with Cypress.Screenshot.defaults, and use cy.screenshot only for rare intentional checkpoints. Failure images plus clear assertions are the standard triage pair.

Key Takeaways

  • Cypress captures failure screenshots during cypress run when screenshotOnRunFailure is enabled (default true).
  • Use screenshotsFolder and CI artifact uploads so reviewers can open PNGs without local reproduction.
  • cy.screenshot() is for intentional checkpoints; it does not replace automatic failure captures or assertions.
  • Cypress.Screenshot.defaults centralizes capture mode, blackout selectors, and overwrite behavior.
  • after:screenshot in setupNodeEvents can copy or annotate files; prefer post-job uploads for large transfers.
  • Blackout hides PII pixels but does not scrub videos, logs, or network dumps by itself.
  • Blank failure screenshots usually mean the app never loaded, not that screenshotting is broken.

The practical answer to cypress how to take a screenshot on failure is that Cypress already captures failure screenshots during cypress run by default. You configure when they are taken, where they are written, how they are named, and how CI attaches them. Manual cy.screenshot() is for intentional checkpoints, not for replacing the failure path.

This guide covers automatic failure screenshots, headless versus headed behavior, naming and folder layout, Cypress.Screenshot.defaults(), after:screenshot hooks for uploads, video versus screenshot tradeoffs, and patterns that keep failure artifacts useful instead of noisy. You will also see how to diagnose a missing screenshot and how interviewers expect you to talk about failure evidence.

TL;DR

Goal Cypress mechanism Notes
Screenshot when a test fails in CI Default screenshotsOnRunFailure: true Works with cypress run
Disable automatic failure shots screenshotsOnRunFailure: false Rarely needed for suites that ship artifacts
Manual checkpoint cy.screenshot('name') Use sparingly; not a substitute for assertions
Global capture options Cypress.Screenshot.defaults({...}) Blackout, clip, scale, capture mode
Post-process or upload on('after:screenshot', ...) in plugins Add metadata or push to storage
Full page versus viewport capture: 'fullPage' | 'viewport' | 'runner' Choose based on what failed

For most teams, leave failure screenshots on, name them consistently, black out secrets, and publish the screenshots folder from CI.

1. Cypress How to Take a Screenshot on Failure: What Cypress Does by Default

When you execute tests with cypress run, Cypress writes a PNG for each failed test (or failed hook, depending on configuration and version behavior) into the screenshots directory. The default is on: you do not need a custom command just to see the page at the moment of failure.

// cypress.config.ts
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    screenshotsFolder: 'cypress/screenshots',
    videosFolder: 'cypress/videos',
    screenshotOnRunFailure: true, // default is true; explicit for clarity
    video: true,
    trashAssetsBeforeRuns: true,
  },
})

In older docs you may still see screenshotsOnRunFailure. Current Cypress configuration documents screenshotOnRunFailure on the config object. Prefer the name your installed Cypress version documents in its type definitions (Cypress.ConfigOptions). Do not invent alternate keys.

cypress open is interactive. Failure screenshots during interactive debugging are less central than the Command Log and time-travel snapshots. Treat automatic failure artifacts as a CI and headless concern first.

A failure screenshot is evidence, not a proof of correctness. It shows the last visible browser state when Cypress recorded the failure. Assertions still own the pass or fail decision.

2. Where Screenshots Land and How They Are Named

By default Cypress nests failure images under cypress/screenshots using the spec path and test title. That structure helps you map an artifact back to a failing case without opening the video.

cypress/screenshots/
  auth/login.cy.ts/
    shows validation when password is empty (failed).png
  checkout/cart.cy.ts/
    keeps cart total after coupon removal (failed).png

Control the root with screenshotsFolder. Keep it inside the repo workspace that CI can archive. Do not point it at a temporary path that disappears before the upload step.

export default defineConfig({
  e2e: {
    screenshotsFolder: 'artifacts/cypress/screenshots',
  },
})

When you call cy.screenshot('checkout-summary') intentionally, Cypress uses that name. Failure screenshots use the test title and a failed marker. Avoid overwriting intentional baselines with failure dumps by separating folders if you also do visual regression.

If trashAssetsBeforeRuns is true (common default), previous run artifacts are cleaned before the next run. That is usually correct for CI. Locally, turn it off only when you need to compare successive failure images side by side.

3. Manual Screenshots Versus Failure Screenshots

Manual captures are useful for long flows where intermediate state is hard to reconstruct from a final failure alone.

it('completes checkout with saved address', () => {
  cy.visit('/checkout')
  cy.get('[data-cy=address-card-home]').click()
  cy.screenshot('checkout-address-selected')

  cy.get('[data-cy=pay-now]').click()
  cy.get('[data-cy=order-confirmation]')
    .should('contain', 'Order confirmed')
})

Use manual screenshots sparingly. Every PNG costs storage and review time. Prefer strong assertions, network aliases, and DOM state checks over screenshot spam. Failure screenshots already cover the unhappy path.

When you need a screenshot of a specific element:

cy.get('[data-cy=invoice-preview]').screenshot('invoice-preview')

Element screenshots help isolate a component. They do not replace component tests for pure UI logic.

For visual regression workflows (baseline compare, pixel diffs), pair intentional screenshots with a dedicated visual tool or service. See Cypress visual testing examples for that path. Failure screenshots and visual baselines solve different problems.

4. Configure Capture Defaults With Cypress.Screenshot.defaults

Cypress.Screenshot.defaults sets suite-wide options for both manual and automatic screenshots where those options apply.

// cypress/support/e2e.ts
Cypress.Screenshot.defaults({
  capture: 'viewport',
  scale: false,
  blackout: ['[data-cy=ssn]', '[data-cy=card-number]'],
  overwrite: true,
  onBeforeScreenshot($el) {
    // optional: hide fixed chat widgets that add noise
  },
  onAfterScreenshot($el, props) {
    // props includes path, size, dimensions, test name metadata
  },
})

Important options:

  • capture: 'viewport', 'fullPage', or 'runner' (includes Cypress runner UI when relevant).
  • blackout: CSS selectors whose content is covered before capture. Use this for PII and secrets.
  • clip: { x, y, width, height } when you need a fixed region.
  • scale: whether to scale for device pixel ratio differences.
  • overwrite: whether a same-named file is replaced.

Blackout is a privacy control, not a security boundary. Tokens in network logs, cookies, and videos can still leak. Treat artifact handling as part of your data policy.

it('shows billing summary', () => {
  cy.visit('/billing')
  cy.screenshot('billing-summary', {
    blackout: ['[data-cy=account-email]'],
    capture: 'fullPage',
  })
})

Per-call options override defaults for that capture.

5. Cypress How to Take a Screenshot on Failure in CI Pipelines

CI is where failure screenshots earn their keep. A red job without artifacts forces engineers to reproduce locally. Archive the screenshots directory on failure (and often always, so flaky intermittents remain available).

Example GitHub Actions snippet:

- name: Run Cypress
  uses: cypress-io/github-action@v6
  with:
    build: npm run build
    start: npm run preview
    wait-on: 'http://localhost:4173'
    browser: chrome

- name: Upload Cypress screenshots
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: cypress-screenshots
    path: cypress/screenshots
    if-no-files-found: ignore

Also upload videos if enabled. Screenshots show a single frame; videos show timing and intermediate transitions. Storage costs rise with video, so some teams keep screenshots always and videos only on failure or for critical specs.

For debugging strategy beyond artifacts, see how to debug a failing Cypress test in VS Code. Artifacts tell you what the browser showed; a debugger tells you why the command chain failed.

6. after:screenshot Hooks for Upload and Metadata

The Node event after:screenshot runs in the plugins or config setupNodeEvents process when a screenshot is taken. Use it to copy files, attach metadata, or upload to object storage.

// cypress.config.ts
import { defineConfig } from 'cypress'
import fs from 'node:fs'
import path from 'node:path'

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      on('after:screenshot', (details) => {
        // details.path, details.size, details.dimensions, details.testFailure, etc.
        const destDir = path.join(config.projectRoot, 'artifacts', 'shots')
        fs.mkdirSync(destDir, { recursive: true })
        const dest = path.join(destDir, path.basename(details.path))
        fs.copyFileSync(details.path, dest)

        return { path: dest }
      })
      return config
    },
  },
})

Return an updated path if you moved the file so Cypress and reporters stay consistent. Keep the handler fast. Long network uploads inside the hook can slow the suite; prefer copying locally and uploading in a post-job step when possible.

Do not put secrets in screenshot filenames or custom JSON sidecars committed to the repo. CI secrets belong in the environment, not in PNG names.

7. Failure Screenshots, Retries, and Flaky Tests

When test retries are enabled, a eventually-passing test may still produce intermediate failure screenshots depending on configuration and reporter behavior. That is useful: you can see what failed before the retry passed.

export default defineConfig({
  e2e: {
    retries: {
      runMode: 1,
      openMode: 0,
    },
    screenshotOnRunFailure: true,
  },
})

Do not use retries as a substitute for fixing flakes. Screenshots of retry failures often reveal race conditions, missing intercepts, or animation timing issues. Pair retries with quarantine and root-cause work described in Cypress handling flaky tests.

If every failure screenshot shows a spinner, your assertions race the UI. Prefer cy.intercept plus cy.wait or retryable assertions over fixed cy.wait(5000). See Cypress cy.wait with alias for deterministic network synchronization.

8. Headed, Headless, Viewports, and Capture Modes

Headless Chrome and headed Electron can differ slightly in font rendering and pixel density. For failure diagnosis that is usually fine. For visual baselines it is not. Keep failure screenshots and visual regression baselines conceptually separate.

it('renders dashboard widgets', () => {
  cy.viewport(1440, 900)
  cy.visit('/dashboard')
  cy.get('[data-cy=revenue-widget]').should('be.visible')
})

A failure at mobile viewport with a desktop-only bug will screenshot the mobile layout. Always record the viewport in the test or use a shared command so reviewers know what they are looking at.

Capture mode guide:

Mode Includes Best for
viewport Current viewport pixels Most failure debugging
fullPage Scrollable document Long forms, multi-section pages
runner Cypress runner chrome Rare; teaching or runner bugs

Prefer viewport for failure defaults. Switch to fullPage when the defect is below the fold and you need context in one image.

9. Blackout, Secrets, and Compliance in Artifacts

Failure screenshots can capture emails, phone numbers, tokens rendered in debug panels, and customer names from staging data. Treat the screenshots folder as sensitive.

Practical controls:

  1. Blackout known PII selectors in defaults.
  2. Use synthetic data in E2E accounts.
  3. Restrict artifact access in CI.
  4. Expire artifacts automatically (for example 7 to 14 days).
  5. Avoid screenshotting pages that dump JWTs into the DOM.
Cypress.Screenshot.defaults({
  blackout: [
    '[data-cy=patient-name]',
    '[data-cy=ssn]',
    '[data-private]',
  ],
})

If a developer tools panel or feature flag overlay injects secrets into the page in lower environments, hide that overlay in test builds. Automation-friendly environments should not require screenshot redaction of production secrets because those secrets should not be present.

10. Troubleshooting Missing or Useless Failure Screenshots

When people say screenshots do not work, common causes include:

  1. Looking only at cypress open. Automatic failure artifacts are primarily a cypress run concern.
  2. screenshotOnRunFailure: false in config or environment overrides.
  3. Custom screenshotsFolder and CI uploading the wrong path.
  4. trashAssetsBeforeRuns wiping local folders between experiments.
  5. Crashes outside Cypress control (browser OOM, runner kill) where no after-test hook runs.
  6. Spec not actually failing but the pipeline failing on lint or typecheck after Cypress passed.
  7. Container without required dependencies so the browser never rendered the app (blank page screenshots are still screenshots, but look empty).

Diagnosis checklist:

npx cypress run --spec cypress/e2e/smoke/login.cy.ts
ls -R cypress/screenshots

If the test fails and the folder is empty, print resolved config:

npx cypress info
# or inspect defineConfig output / debug logs for the resolved screenshotOnRunFailure flag

If screenshots exist but are blank white pages, the app likely never booted. Check baseUrl, wait-on, and network errors in the video or Command Log export.

11. Combining Screenshots With Logging and Network Evidence

A screenshot without request context is incomplete for API-driven UIs. On failure you want:

  • the PNG of the UI
  • the failed command and assertion message
  • recent network calls (HAR, Cypress log, or intercepts)
  • environment identifiers (app version, test user, seed)
it('loads invoices for the current org', () => {
  cy.intercept('GET', '/api/invoices*').as('listInvoices')
  cy.visit('/invoices')
  cy.wait('@listInvoices').its('response.statusCode').should('eq', 200)
  cy.get('[data-cy=invoice-row]').should('have.length.greaterThan', 0)
})

When this fails, the screenshot shows empty state or error banner, while the intercept (or missing intercept) explains whether the API failed. For deeper network capture patterns, use Cypress how to capture network traffic.

Custom commands can attach extra context, but keep them lightweight. Prefer first-class Cypress features over reinventing a screenshot framework.

12. Team Conventions That Keep Failure Artifacts Valuable

Agree on conventions early:

  1. Leave screenshotOnRunFailure enabled in shared config.
  2. Archive screenshots on CI failure for every pull request job.
  3. Blackout PII selectors in support/e2e.ts.
  4. Do not commit failure screenshots to git.
  5. Use manual cy.screenshot only with a written reason (visual review, long multi-step proof).
  6. Name intentional screenshots by feature, not by random timestamps.
  7. Separate visual baseline directories from failure dump directories.
  8. Link the artifact URL in flaky-test tickets.

A short pull request template line helps: "If Cypress fails, download the screenshots artifact before re-running blindly."

Reviewers should open the PNG before asking for a re-run. Many failures are obvious from a single image: wrong environment banner, auth redirect, empty fixture, overlapping modal, or 500 error page.

13. Naming Strategies, Spec Organization, and Reporter Integration

Failure screenshots become hard to navigate when specs use vague titles. Prefer test names that state the expected behavior and the condition, because those names appear in the PNG path.

// weak title -> weak artifact name
it('works', () => { /* ... */ })

// strong title -> searchable artifact
it('shows an inline error when the password field is empty', () => {
  cy.visit('/login')
  cy.get('[data-cy=submit-login]').click()
  cy.get('[data-cy=password-error]').should('be.visible')
})

Organize specs by product area so screenshot folders group naturally: auth/, checkout/, settings/. When a CI job fails, engineers can open the artifact tree and jump to the relevant domain without reading the entire log.

Reporters such as mochawesome, junit, or cloud dashboard products may link to screenshots differently. Keep the on-disk folder canonical. If a reporter expects a relative path, configure it explicitly rather than moving files ad hoc after the run. When using Cypress Cloud or similar services, still retain local artifacts for air-gapped or policy-restricted environments.

Also document who owns cleanup. Long-lived self-hosted runners accumulate PNGs if trashAssetsBeforeRuns is false and jobs never wipe workspaces. A weekly cleanup policy prevents disk exhaustion that can itself cause false failures.

Pairing Screenshots With Structured Failure Messages

A screenshot without a clear assertion message forces viewers to guess. Write assertions that name the business expectation:

cy.get('[data-cy=order-total]')
  .should('have.text', '$42.00')

When this fails, the Command Log says the expected text, and the screenshot shows the wrong total or a loading skeleton. Together they answer what and often why. Avoid soft checks that log but do not fail; they produce no failure screenshot when the soft check is the only signal.

If you use custom commands that throw, ensure the error message includes the entity id or environment name. That metadata rarely fits into a PNG filename but appears beside the artifact in CI logs.

14. Cross-Browser Failure Captures and Device Emulation

Cypress can run across Electron, Chrome, Firefox, and WebKit (where supported in your version and plan). Failure screenshots inherit browser differences in fonts, subpixel rendering, and form controls. That is acceptable for functional triage. Do not treat a Chrome failure PNG as a pixel oracle for Firefox.

npx cypress run --browser chrome
npx cypress run --browser firefox

When a bug is browser-specific, label the CI artifact with the browser name:

name: cypress-screenshots-${{ matrix.browser }}

Device emulation through cy.viewport changes what the failure image contains. A mobile viewport failure may show a hamburger menu covering a control that is visible on desktop. Include the viewport in the test title or in a beforeEach log so reviewers do not misread the layout.

describe('checkout mobile', () => {
  beforeEach(() => {
    cy.viewport('iphone-x')
  })
  // ...
})

If you maintain visual baselines, isolate them per browser and viewport. Failure screenshots from functional runs should never overwrite those baselines. Separate directories prevent accidental pollution during a red build.

15. Security Review of Screenshot Pipelines

Treat the screenshot pipeline as part of your security and privacy design. Staging environments sometimes display real customer samples, internal-only feature flags, or debug tokens in the DOM. A public CI artifact bucket without access control is a data leak.

Review checklist:

  1. Who can download Cypress artifacts?
  2. How long do artifacts live?
  3. Are production URLs ever tested with real sessions?
  4. Do blackout selectors cover every known PII field on captured routes?
  5. Are screenshot filenames free of emails or account ids?
  6. Do after:screenshot uploads use short-lived credentials?
// avoid encoding PII into names
cy.screenshot(`user-${userEmail}-profile`) // bad if email is real
cy.screenshot('profile-settings-panel') // better

For regulated domains (health, finance), prefer dedicated synthetic tenants and block routes that render government ids from E2E entirely. If a test must visit such a page, blackout is mandatory and artifact access should be limited to the security group that already may see that data.

Finally, remember that open source forks of your config can copy blackout lists without the private route inventory. Keep a private appendix of sensitive routes if needed, and do not assume public config is complete.

Interview Questions and Answers

Q: Does Cypress take a screenshot automatically when a test fails?

Yes, during cypress run when screenshotOnRunFailure is enabled (the default). The image is written under the configured screenshotsFolder, typically nested by spec and test title. I still write clear assertions so the failure message and screenshot work together.

Q: What is the difference between cy.screenshot() and failure screenshots?

cy.screenshot() is an intentional command in the test. Failure screenshots are produced by the runner when a test fails. I use intentional screenshots sparingly for checkpoints or visual workflows, and I rely on automatic captures for CI triage.

Q: How do you hide sensitive data in screenshots?

I configure blackout selectors via Cypress.Screenshot.defaults or per-call options, use synthetic test data, and restrict CI artifact access. Blackout covers pixels; it does not remove secrets from logs or videos by itself.

Q: How would you upload screenshots from CI?

I keep them on disk during the run, then use the CI artifact upload step on failure. For custom storage I may use after:screenshot to copy or annotate files, but I prefer post-job upload for large transfers.

Q: Why might a failed pipeline have no screenshots?

Common reasons include disabled screenshotOnRunFailure, uploading the wrong folder, cleaning artifacts too early, or the job failing outside Cypress. I verify with a local cypress run and inspect the resolved config path.

Q: Should every test call cy.screenshot() at the end?

No. That creates noise and storage cost without improving assertions. End-state assertions plus automatic failure screenshots are enough for most suites.

Q: How do screenshots interact with test retries?

Retries can produce multiple failure images for intermediate attempts. That is valuable for diagnosing flakes, but retries should not replace fixing race conditions revealed by those images.

Common Mistakes

  • Disabling screenshotOnRunFailure to "speed up CI" and then losing the only evidence of a production-like failure.
  • Committing PNG failures into git instead of CI artifacts.
  • Assuming cypress open will always produce the same artifact layout as cypress run.
  • Screenshotting every step in every test.
  • Forgetting to blackout PII on staging data that looks real.
  • Uploading cypress/videos but not cypress/screenshots, or the reverse path mismatch after renaming folders.
  • Using failure screenshots as visual regression baselines.
  • Ignoring blank screenshots that actually mean the app never loaded.
  • Adding long blocking uploads inside after:screenshot without timeouts.
  • Fixing flakes by increasing retries without reading the failure image.

Conclusion

For cypress how to take a screenshot on failure, start from the built-in runner behavior: keep screenshotOnRunFailure enabled, know your screenshotsFolder, black out sensitive selectors, and publish artifacts from CI. Use cy.screenshot() only when an intentional checkpoint adds clarity, and use Cypress.Screenshot.defaults plus after:screenshot when you need consistent capture policy or post-processing.

Next step: run one deliberately failing spec with npx cypress run, open the generated PNG, and wire that folder into your CI upload step if it is not already connected.

Interview Questions and Answers

How does Cypress handle screenshots on test failure?

In run mode with screenshotOnRunFailure enabled, Cypress writes a PNG for the failed test under screenshotsFolder. I configure naming via defaults when needed, black out secrets, and publish the folder from CI so engineers can triage without re-running first.

When do you use cy.screenshot() versus automatic failure screenshots?

Automatic captures cover unexpected failures. I use cy.screenshot for deliberate checkpoints in long flows or visual review. I do not screenshot every step, because assertions and failure artifacts already provide most of the signal.

How do you keep PII out of Cypress artifacts?

I blackout known sensitive selectors, seed synthetic users, limit who can download CI artifacts, and set short retention. I also remember videos and logs can still contain secrets even when PNGs are redacted.

How would you integrate failure screenshots into GitHub Actions?

I run Cypress, then on failure upload the screenshots directory with actions/upload-artifact. I verify the path matches screenshotsFolder and allow if-no-files-found to ignore empty cases carefully so misconfiguration still gets noticed in review.

What does Cypress.Screenshot.defaults buy you?

Suite-wide capture policy: viewport versus fullPage, blackout lists, scale, overwrite, and before/after hooks. Per-call options can still override for special cases.

A failure screenshot is blank. What do you check?

I check whether the app booted: baseUrl, wait-on, server logs, and network errors. A blank PNG often means navigation never reached the UI, not that the screenshot API failed.

How do retries affect screenshots?

Intermediate failed attempts can still produce screenshots, which helps diagnose flakes that pass on retry. I use that evidence to fix races rather than raising retry counts alone.

Frequently Asked Questions

Does Cypress automatically take a screenshot when a test fails?

Yes during cypress run when screenshotOnRunFailure is true, which is the default. Images land under screenshotsFolder nested by spec and test title. Interactive cypress open is mainly for live debugging rather than the same artifact workflow.

Where are Cypress failure screenshots saved?

By default under cypress/screenshots, configurable via screenshotsFolder. CI must upload that exact path. trashAssetsBeforeRuns may clean previous local images before a new run.

How do I disable screenshots on failure?

Set screenshotOnRunFailure to false in Cypress config. Most teams should leave it enabled and manage storage with CI retention policies instead.

How do I hide sensitive data in Cypress screenshots?

Pass blackout selectors in Cypress.Screenshot.defaults or per cy.screenshot options so matching elements are covered before capture. Also use synthetic data and restrict artifact access.

What is after:screenshot used for?

It is a Node event in setupNodeEvents that runs after a screenshot is written. Teams use it to copy files, add metadata, or integrate with custom storage. Return an updated path if you move the file.

Should every test call cy.screenshot()?

No. Automatic failure screenshots cover failing tests. Manual screenshots add storage and review cost and should be reserved for intentional checkpoints or visual workflows.

Why is my screenshots folder empty after a failed pipeline?

Common causes include wrong upload path, screenshotOnRunFailure disabled, the job failing outside Cypress, or assets cleaned before upload. Reproduce with npx cypress run and inspect the resolved folder.

Related Guides