The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use a tag-conditioned After hook to limit screenshot handling to selected Cucumber scenarios, then check the scenario’s result if you want images only for failures. The tag decides whether the hook runs; the status check decides whether it captures an image. In the hook, take the screenshot from the still-open browser and attach it to the scenario with the binding’s image attachment API.
How tag-based screenshot capture works
A tag is a way to mark the scenarios that should receive special handling. A conditional After hook matches that tag. Within the hook, a separate result check can restrict capture to failed scenarios. Keeping these filters separate makes the behavior easy to change: remove the result check to capture every tagged run, or change the hook’s tag expression to broaden or narrow its scope.
@capture_screenshot
Scenario: A tagged browser scenario
Given the application is open
When I perform an action
Then the expected result appears
The conceptual hook is:
After hook selected by @capture_screenshot:
if scenario failed:
image = screenshot from the browser driver
attach image to the Cucumber result as image/png
Cucumber documents tag expressions for selecting hooks and scenarios, and its browser automation guide demonstrates failure screenshots and image attachments across Java, Kotlin, JavaScript, and Ruby. See the Cucumber reference and browser automation guide.
Choose where the tag belongs
Put the tag at the narrowest level that includes exactly the scenarios you want to capture. Cucumber tags can appear above a Feature, Rule, Scenario, Scenario Outline, or Examples element. Tags on a parent are inherited by its descendants; tags cannot be placed above a Background or an individual step.
#1 Best Overall
| Placement | Effect | Use it when |
|---|---|---|
| Scenario | Marks that scenario only. | Only a few individual scenarios need screenshots. |
| Examples | Marks the examples set and its generated cases. | Only one data set in a Scenario Outline should be selected. |
| Scenario Outline | Applies to the outline’s generated scenarios. | Every example row should use the hook. |
| Rule | Inherited by scenarios under that rule. | A coherent group of related scenarios needs the same behavior. |
| Feature | Inherited by the feature’s descendant scenarios. | All scenarios in that feature should be selected. |
For example, a hook expression of @capture_screenshot matches the marker. A compound expression such as @browser and not @headless can select browser scenarios while excluding those marked headless. Tag expressions are boolean filters; use the syntax documented for your Cucumber binding and version.
Decide whether to capture every tagged run or failures only
These are distinct policies. A tag-conditioned hook by itself runs after every matching scenario, whether it passed or failed. Add a result-status check inside the hook for failure-only evidence. That check should use the status API for your binding, not an inferred condition such as whether a step threw an exception; hooks and scenario outcomes can involve more than one step result.
- Failure-only: Match the tag, then capture only if the final scenario result is failed.
- Every tagged run: Match the tag and capture without a failure condition. This is useful when comparing visual outcomes or recording successful flows.
The code examples below show the failure-only version. Names such as driver, browser, and page refer to the browser session your project already manages. Connect the example to your existing World, test context, or dependency-injection setup; the hook must use the same live session as the steps.
Java: take WebDriver bytes and attach them
With Cucumber-JVM and Selenium WebDriver, implement an After hook whose tag expression selects the marked scenarios. The example assumes your project’s hook class can access its active Selenium driver.
import io.cucumber.java.After;
import io.cucumber.java.Scenario;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
public class ScreenshotHooks {
private final WebDriver driver;
public ScreenshotHooks(WebDriver driver) {
this.driver = driver;
}
@After("@capture_screenshot")
public void attachScreenshotOnFailure(Scenario scenario) {
if (scenario.isFailed()) {
byte[] image = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BYTES);
scenario.attach(image, "image/png", "failure-screenshot");
}
}
}
The constructor is appropriate when your Cucumber-JVM project uses dependency injection to provide the scenario’s driver. If your project creates the driver in a different context, retrieve that same instance there instead. Do not create a new browser in the hook: a new session would not show the page state that failed.
Kotlin: use the same driver and attachment pattern
The Kotlin version follows the same lifecycle: the tag selects the hook, scenario.isFailed gates capture, and Selenium supplies image bytes for Cucumber’s attachment API.
Rank #3
import io.cucumber.java.After
import io.cucumber.java.Scenario
import org.openqa.selenium.OutputType
import org.openqa.selenium.TakesScreenshot
import org.openqa.selenium.WebDriver
class ScreenshotHooks(private val driver: WebDriver) {
@After("@capture_screenshot")
fun attachScreenshotOnFailure(scenario: Scenario) {
if (scenario.isFailed) {
val image = (driver as TakesScreenshot)
.getScreenshotAs(OutputType.BYTES)
scenario.attach(image, "image/png", "failure-screenshot")
}
}
}
As in Java, arrange for the active scenario’s driver to be injected into the hook. Match the imports and hook configuration to the Cucumber-JVM version already used by the project.
JavaScript: attach a WebDriver screenshot buffer
For Cucumber-JS, the hook receives the scenario result, and the World’s attach method adds the image to the Cucumber message stream. This example assumes a Selenium WebDriver instance is available as this.driver on the World.
const { After, Status } = require('@cucumber/cucumber');
After('@capture_screenshot', async function (scenario) {
if (scenario.result.status === Status.FAILED) {
const base64 = await this.driver.takeScreenshot();
await this.attach(Buffer.from(base64, 'base64'), 'image/png');
}
});
If your World exposes a different property or your driver returns a buffer rather than base64, adapt that conversion to the driver API in use. Cucumber-JS documents image and binary attachments in its attachment documentation.
Ruby: save a Capybara screenshot and attach it
The Cucumber browser guide’s Ruby pattern uses Capybara to save the active browser image and attaches the path with its MIME type. Keep this hook in a support file loaded by your Cucumber run.
After('@capture_screenshot') do |scenario|
if scenario.failed?
path = 'tmp/failure-screenshot.png'
page.save_screenshot(path)
attach(path, 'image/png')
end
end
Ensure the destination directory exists and that a repeated run will not overwrite an image you still need; for parallel runs, use a unique path per scenario. The attachment is still the Cucumber result artifact—the saved file is the input to the attachment call.
Recommended Free Tools
Keep capture ahead of browser teardown
The screenshot must be taken while the scenario’s browser session is alive. Arrange hook execution so the screenshot hook runs before any teardown hook quits the driver or closes the page. Hook ordering and dependency setup vary by binding and project, so check the lifecycle for your version rather than relying on incidental file order. Cucumber’s browser automation examples capture in an After hook while the WebDriver or Capybara session is available.
Best Value
Capture from the existing scenario session: that is what preserves the failure state, including the current URL, rendered page, and browser viewport. A separate session or a later request to the same URL may show a different state and is not a substitute for the failed run’s browser image.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Understand what the attachment does—and what it does not
Attaching image bytes or a path makes the image part of the Cucumber result stream. It does not by itself guarantee that a particular HTML report will display or retain the image. The formatter and runner determine how attachments are emitted and presented. Confirm the output in the report your CI job actually publishes, and ensure the result artifact is retained long enough for the team to inspect it.
Cucumber-JS describes how attachments flow through its formatter infrastructure in its attachment documentation. Other bindings also depend on their runner and formatter configuration, so verify the generated report rather than assuming that a successful attach call means a durable artifact is available.
Free tools Windows power users keep installed
One-click scans. No signup required.
Troubleshoot missing or unusable screenshots
- The hook never runs: Confirm the exact tag spelling, that the tag is attached to the scenario or an ancestor, and that the hook expression matches. Tags on a Feature or Rule flow down to child scenarios, but a tag above a Background or step is not valid placement.
- A passed scenario has no image: That is expected with a failure-only status check. Remove the check if every tagged run should be captured.
- The hook runs, but screenshot capture throws: Verify that the driver or page belongs to the active scenario and has not already been closed. Check the screenshot method and return type against the browser driver and version.
- The image is empty or shows the wrong page: Capture before teardown or navigation in another hook, and use the session that executed the scenario steps. A fresh browser does not contain the failed scenario state.
- The report omits the image: Inspect the raw Cucumber output and formatter configuration. The attachment API emits an artifact; report rendering and retention are runner-specific.
- Parallel runs overwrite Ruby image files: Use a unique output filename for each scenario or worker, and confirm that the artifact path is writable.
- The snippet does not compile: Check that the imports and status API match your installed Cucumber binding, that the hook is loaded, and that your project’s driver-access mechanism matches the example. These are binding-specific APIs, not interchangeable snippets.
Or skip the browser setup
For a screenshot of a public page by URL—not the transient state inside a failed Cucumber browser session—ScreenshotNeo offers a one-request API. It accepts a URL and returns an image or PDF; its parameter names also work with those used by other screenshot APIs. See the ScreenshotNeo website and 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
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server provides screenshot tools for AI agents, and the free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. It is useful for URL-based captures and automation, but it does not attach the live failure-state image from your Cucumber session to that scenario.
Sign up for 1,000 free screenshots a month, with no card required.
Quick Recap
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.

