ScreenshotNeo

BlogHow-to

How to Add Failed-Step Screenshots to a Cypress BDD HTML Report

Configure Cypress and the Cucumber preprocessor so failed-run screenshots appear as attachments in your BDD HTML report.

By the ScreenshotNeo team30 September 20267 min read

How to Add Failed-Step Screenshots to a Cypress BDD HTML Report

Short answer: run your Cypress BDD suite with cypress run, keep Cypress failure screenshots enabled, configure @badeball/cypress-cucumber-preprocessor to generate an HTML report, and enable attachments.addScreenshots. Cypress writes the failure image to its screenshots folder, while the preprocessor adds the image as a report attachment. Verify the generated HTML with the exact Cypress and preprocessor versions in your lockfile.

Cypress captures failure screenshots automatically in run mode. It does not do this automatically in cypress open. Also, the preprocessor’s AfterStep() hook does not run when that step itself fails, so a generic Cucumber “capture in AfterStep” recipe is not reliable here.

1. What you are configuring

There are two related outputs:

The capture path: Cypress records the failed run, and the preprocessor attaches the image to the report.
The capture path: Cypress records the failed run, and the preprocessor attaches the image to the report.
Output Owner Purpose
Failure image file Cypress Saved under screenshotsFolder, which defaults to cypress/screenshots.
BDD report attachment @badeball/cypress-cucumber-preprocessor Associates the image with the failed test so the generated report can display it.
HTML report Cucumber preprocessor Human-readable report written to the configured HTML output path.

A screenshot existing on disk does not prove that it is visible in the HTML report. Check both artifacts.

2. Install the BDD integration

Use the current package names documented by the project and pin versions in your lockfile:

npm install --save-dev cypress @badeball/cypress-cucumber-preprocessor @bahmutov/cypress-esbuild-preprocessor

Your project should have feature files matched by specPattern, plus a Cypress configuration that registers the preprocessor in setupNodeEvents.

3. Register Cypress and the Cucumber preprocessor

This is a complete CommonJS configuration shape. Keep the integration code, then add the report settings supported by the preprocessor version installed in your project.

// cypress.config.js
const { defineConfig } = require("cypress");
const {
  addCucumberPreprocessorPlugin,
} = require("@badeball/cypress-cucumber-preprocessor");
const { createBundler } = require("@bahmutov/cypress-esbuild-preprocessor");
const {
  createEsbuildPlugin,
} = require("@badeball/cypress-cucumber-preprocessor/esbuild");

module.exports = defineConfig({
  e2e: {
    specPattern: "cypress/e2e/**/*.feature",
    screenshotOnRunFailure: true,
    screenshotsFolder: "cypress/screenshots",

    async setupNodeEvents(on, config) {
      await addCucumberPreprocessorPlugin(on, config);

      on(
        "file:preprocessor",
        createBundler({
          plugins: [createEsbuildPlugin(config)],
        })
      );

      return config;
    },
  },
});

The Cypress integration shown above is the documented registration pattern. The report keys below are the important part for screenshot attachments.

4. Enable HTML output and screenshot attachments

The preprocessor exposes HTML and JSON report settings. Depending on your installed version, configure these keys in the preprocessor configuration file or the equivalent environment variables:

Purpose Configuration key Environment override
HTML report enabled html.enabled htmlEnabled
HTML destination html.output htmlOutput
JSON report enabled json.enabled jsonEnabled
JSON destination json.output jsonOutput
Add screenshots to reports attachments.addScreenshots attachmentsAddScreenshots

A representative configuration looks like this; adapt the file location and exact syntax to the version in your lockfile:

// cucumber.config.js (illustrative)
module.exports = {
  html: {
    enabled: true,
    output: "reports/cypress-bdd.html",
  },
  json: {
    enabled: true,
    output: "reports/cypress-bdd.json",
  },
  attachments: {
    addScreenshots: true,
  },
};

If your project uses Cypress environment values instead, the equivalent intent is:

// cypress.config.js — equivalent environment shape
const { defineConfig } = require("cypress");

module.exports = defineConfig({
  e2e: {
    env: {
      htmlEnabled: true,
      htmlOutput: "reports/cypress-bdd.html",
      jsonEnabled: true,
      jsonOutput: "reports/cypress-bdd.json",
      attachmentsAddScreenshots: true,
    },
  },
});

Do not blindly combine both forms if your installed release expects one configuration path. Check the package configuration reference and your lockfile version.

5. Add a feature that can fail

# cypress/e2e/login.feature
Feature: Login

  Scenario: Invalid login shows an error
    Given I open the login page
    When I submit invalid credentials
    Then I should see the login error

Run the suite in CI or locally with run mode:

npx cypress run

After a failure, inspect:

  1. The configured HTML report path.
  2. The configured JSON report path, if enabled.
  3. cypress/screenshots, or your custom screenshotsFolder.

6. Understand screenshot names, retries, and failed steps

Cypress bases a failure filename on the test name and adds a (failed) suffix. When retries are enabled, failed attempts receive an attempt suffix such as (attempt 2). A test that fails more than once can therefore produce multiple screenshots.

The screenshot represents the failed test state. It does not require a custom failed-step hook. In this preprocessor, AfterStep() does not run after the step that fails, so code placed there cannot reliably capture that exact failure. Scenario-level cleanup and attachment behavior also differs from cucumber-js; use the preprocessor’s own documented semantics.

7. Verify that the image is embedded in the HTML

  1. Open the generated HTML report in a browser.
  2. Find the failed scenario and expand its step or attachment area.
  3. Confirm that the image is visible, rather than only seeing a path or filename.
  4. If the HTML is missing the image, open the JSON report and check whether the failed test contains an image attachment.

The preprocessor’s feature tests expect an image attachment in JSON for a failed test. HTML rendering can change between releases, so validate the result against the version installed in your project.

8. Run the same workflow in CI

# package.json
{
  "scripts": {
    "e2e:report": "cypress run"
  }
}
# Shell
npm run e2e:report

# Preserve these paths as CI artifacts:
# reports/cypress-bdd.html
# reports/cypress-bdd.json
# cypress/screenshots/

Keep the HTML and image directory together when uploading artifacts. If your report references external image files instead of embedding them, moving only the HTML file can make attachments appear broken.

9. Troubleshooting

No screenshot is created

  • Cause: The suite ran with cypress open. Fix: run npx cypress run; automatic failure screenshots are a run-mode feature.
  • Cause: screenshotOnRunFailure was set to false. Fix: remove the override or set it to true.
  • Cause: You are looking in the wrong directory. Fix: check screenshotsFolder; the default is cypress/screenshots.
  • Cause: The test did not fail. Fix: reproduce a real assertion or command failure; Cypress does not create a failure image for a passing test.

The image exists, but the HTML report has no attachment

  • Confirm addCucumberPreprocessorPlugin(on, config) runs inside setupNodeEvents.
  • Confirm HTML output is enabled and points to the file you opened.
  • Confirm attachments.addScreenshots (or attachmentsAddScreenshots) is enabled.
  • Check the JSON report. If JSON has no image attachment, the attachment setting or plugin registration is wrong. If JSON has one but HTML does not, check HTML rendering support for your installed release.

The report file is empty or missing

  • Check that the output directory exists or that the reporter can create it.
  • Check the exact configuration key spelling for your package version.
  • Run with the same configuration file in CI and locally; environment-specific overrides can disable the reporter.

An AfterStep() capture never runs

This is expected when the step itself fails. Move critical reporting configuration to the preprocessor’s supported attachment path instead of relying on a generic Cucumber hook copied from another runner.

Retries produce confusing files

Each failed attempt can create an image. Use the attempt suffix in filenames to match a screenshot to the corresponding retry, and retain the JSON report when you need an unambiguous mapping.

HTML loads but images are broken

Keep the report and its referenced screenshot assets in their original relative layout. Upload the screenshot directory as a CI artifact and open the report from that preserved directory.

10. Choosing an integration strategy

Strategy Best for Trade-offs
Cypress failure screenshot + preprocessor attachments Standard BDD HTML and JSON reports Requires correct plugin registration and version-aware configuration.
Custom hook or attachment code Special metadata or nonstandard report formats A failed-step hook may not run after the failing step in this preprocessor.
External reporter Centralized reporting across test runners Compatibility, artifact paths, retry mapping, and attachment size need separate validation.

For most Cypress BDD projects, start with Cypress’s built-in run-failure screenshot and the preprocessor’s attachment option. Add custom code only when the generated report cannot represent the metadata you need.

11. Performance, reliability, and artifact size

  • Run mode: Use cypress run in CI so failure capture is deterministic.
  • Retries: More retries can create more images and increase artifact storage.
  • HTML size: The preprocessor describes screenshot and video attachments as base64-encoded inline report attachments. Large artifacts can make reports slow to transfer or render.
  • Retention: Keep HTML, JSON, and screenshots for the same test run together so failures remain traceable.
  • Version drift: Pin Cypress and the preprocessor, then recheck attachment rendering after upgrades.
A clean capture removes common overlays before the image is returned.
A clean capture removes common overlays before the image is returned.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API when you need a capture without maintaining browser setup. It removes cookie banners, newsletter popups, and chat widgets 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 call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for the available options.

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}`);

The free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

FAQ

Does Cypress capture screenshots in interactive mode?

Not automatically for failures. Automatic failure screenshots apply to cypress run.

Do I need an AfterStep() hook?

No. Use Cypress failure capture and the preprocessor attachment setting. The preprocessor’s AfterStep() does not run when that step fails.

Why keep JSON enabled if I only publish HTML?

JSON helps confirm whether the image was attached before HTML rendering. It is useful when diagnosing report-generation problems.

Will retries overwrite the first screenshot?

Failed attempts receive distinct attempt suffixes, so expect multiple artifacts when retries occur.

Can I change the screenshot directory?

Yes. Set Cypress’s screenshotsFolder and preserve that directory with the report artifacts.