skip to content

In Cucumber-JVM, what can an @After hook do with the Scenario object it receives?

level: seniorimportance: should knowfreq 48%

answer

  1. a reporting handle, not a control surface
  2. identity before, outcome after
  3. bytes plus a media type plus a name
  4. guard the attachment on failure
  5. four kinds of not-passed, not one

basics

~20 s

The Scenario object both reports and writes: getName and getSourceTagNames identify the scenario, getStatus and isFailed give the outcome once the steps have run, attach adds bytes with a media type and a name, log adds text.

solid answer

~40 s

Declare a `Scenario` parameter on the hook and Cucumber-JVM passes it in. It is both a read and a write surface. Reading: `getName()`, `getSourceTagNames()`, `getUri()` and `getLine()` identify the scenario, and `getStatus()` / `isFailed()` give the outcome — meaningful only in `@After` or `@AfterStep`, because in `@Before` nothing has run yet. Writing: `scenario.attach(bytes, mediaType, name)` puts a binary artefact into the run's output, and `scenario.log(text)` adds a line of text. That is how a screenshot, an HTTP response body or a correlation id ends up beside the failing scenario instead of buried in a console log. The canonical shape is a conditional one: `if (scenario.isFailed()) { scenario.attach(...); }`, so passing runs stay cheap. cucumber-js hands its `After` hook the scenario's result and exposes `attach` and `log` on the World.

code

java · 19 lines
java
import io.cucumber.java.After;
import io.cucumber.java.AfterStep;
import io.cucumber.java.Scenario;

public class DepotEvidenceHooks {

    @AfterStep
    public void recordStepOutcome(Scenario scenario) {
        scenario.log("status after step: " + scenario.getStatus());
    }

    @After
    public void attachDepotEvidence(Scenario scenario) {
        if (scenario.isFailed()) {
            byte[] png = DepotUi.capturePng();
            scenario.attach(png, "image/png", scenario.getName());
        }
    }
}

go deeper

for a junior

Know that a hook can declare a Scenario parameter, and that it is how you find out whether the scenario failed and attach something to the run's output.

for a middle

Explain the split — identity is readable anywhere, outcome only after the steps have run — and give the attach signature: data, a media type and a name.

for a senior

Show operational judgement: attach on failure, prefer text to images, capture in @AfterStep when teardown would destroy the evidence, and never attach an unredacted payload.

for a principal

Decide what the run's output is for. If it is evidence for someone outside the team, define what is attached, what is redacted, and how long it is retained — before the request arrives.

## What the object gives you A scenario hook in Cucumber-JVM may declare a single parameter of type `io.cucumber.java.Scenario`; the runner supplies it. The useful surface divides in two: **Reading the scenario** * `getName()` — the scenario's title, useful as an attachment name. * `getSourceTagNames()` — the tags on this scenario, including the ones inherited from the feature. * `getUri()` and `getLine()` — which file and line it came from. * `getStatus()` — the outcome, whose values distinguish passed, failed, skipped, pending, undefined and ambiguous. Those are four different kinds of "not passed", and a hook that treats them as one loses the distinction that matters. * `isFailed()` — the convenience form for the common case. **Writing to the run's output** * `attach(byte[] data, String mediaType, String name)` — attaches binary data such as a PNG. * `attach(String data, String mediaType, String name)` — the text form, for JSON or HTML. * `log(String text)` — a plain line of text against the scenario. Whatever you attach travels with the run's output to whichever formatter is configured, so it appears beside that scenario rather than in a console scrollback nobody keeps. ## Status means nothing before the steps run The same `Scenario` type is passed to `@Before`, but there the outcome fields are not yet meaningful — no step has executed, so there is nothing to report. A `@Before` hook uses the object for identity: read `getSourceTagNames()` to branch, or `getName()` to label a fixture. Outcome-driven logic belongs in `@After`, and per-step outcome in `@AfterStep`, which is where a screenshot is closest in time to the failure — before teardown navigates away or rolls the transaction back. | hook | status available? | what it is for | |---|---|---| | `@Before` | no — nothing has run | identity: name, tags, source location | | `@AfterStep` | yes, as of that step | evidence at the moment of failure | | `@After` | yes, final | teardown plus the scenario-level evidence attachment | ## Evidence, worked A scaffolding-hire depot receives a regulator's evidence request: show that the load-certificate check ran for every hire completed in a given quarter. The suite already covers it, but a green tick proves nothing on its own. An `@After` hook that calls `scenario.log()` with the certificate reference the scenario exercised, and `scenario.attach()` with the service's response body, turns the run's output into the artefact the request actually needs — each scenario carrying its own evidence, named and timestamped by the run. The trap is volume. Attaching a screenshot to every scenario of a 1,246-scenario suite, where one returns outline alone expands a 19-row `Examples` table into 19 scenarios, turned a 3.2 MB report into something no browser would open. Rules that keep it sane: 1. **Attach on failure by default.** `if (scenario.isFailed())` is the normal guard; unconditional attachment needs a reason, and "the regulator asked" is one of the few good ones. 2. **Prefer text to images.** A 900-byte response body is more diagnostic than a 400 KB screenshot and costs a fraction of the space. 3. **Set the media type honestly.** A JSON payload attached as `text/plain` renders as a wall; as `application/json` it renders as a payload. 4. **Name the attachment.** With several attachments on one scenario, unnamed blobs are unusable. 5. **Never attach secrets.** Request bodies carry tokens; redact before attaching, because the report is the one artefact that gets emailed outside the team. ## Across the family | implementation | reading the outcome | attaching evidence | |---|---|---| | Cucumber-JVM | `getStatus()` / `isFailed()` on the `Scenario` parameter | `scenario.attach(...)`, `scenario.log(...)` | | cucumber-js | the object passed to the `After` hook carries the scenario's result | `this.attach(...)` and `this.log(...)` on the World | | Behave | the `scenario` argument to `after_scenario` in `environment.py` carries its status | done through the chosen reporting integration | | SpecFlow/Reqnroll | the scenario context exposes the outcome to an after-scenario hook | done through the chosen reporting integration | ## What this is not The `Scenario` object is a reporting handle, not a control surface. You cannot use it to skip the remaining steps, to retry the scenario, or to rewrite its result: a hook reads the outcome and annotates it. Wanting to change the outcome from a hook is nearly always a sign that a conditional belongs in a tag expression — selecting whether the scenario runs at all — rather than in code that runs after it already has.

  • Why is a screenshot taken in @AfterStep often more useful than one taken in @After?
    Because `@AfterStep` fires immediately after the step that failed, before any teardown has navigated away, closed a session or rolled a transaction back. By the time `@After` runs, the state that explains the failure may already be gone, and the screenshot shows a clean page rather than the broken one.
  • Can a hook use the Scenario object to skip the remaining steps or retry the scenario?
    No — it is a reporting handle. It reads identity and outcome and writes attachments; it cannot change control flow or rewrite a result. Conditional execution belongs in a tag expression that decides whether the scenario is selected at all, not in code running after it has already executed.
  • Your report grew from 3.2 MB to hundreds of megabytes after adding attachments. What do you change?
    Guard on failure instead of attaching unconditionally, prefer text payloads over images, and stop attaching per step when per scenario is enough. Remember that every `Examples` row is its own scenario, so an outline multiplies whatever the hook attaches by the number of rows.

saying these in an interview costs you the question

  • Reads the scenario's status inside a @Before hook
  • Attaches a screenshot to every scenario unconditionally
  • Prints evidence to stdout and calls it a report attachment
  • Attaches a request body without redacting its token
  • Expects a hook to skip steps or retry the scenario
  • Treats skipped, pending and undefined as one outcome