skip to content

In Cucumber, what should a step definition body call instead of driving widgets directly?

level: middleimportance: must knowfreq 74%

answer

  1. Prose above, code below
  2. The step text ships into the report
  3. One call per step body
  4. Selectors live beneath the glue
  5. Re-point the layer, not the feature file

basics

~20 s

A Cucumber step definition should call one intent-shaped method on an automation layer beneath it, such as a service client or a page object. Selectors, waits and driver calls belong in that layer, never in the glue class itself.

solid answer

~40 s

Cucumber matches only the text after the keyword, and that text is what every formatter prints, so the Gherkin line is specification and must speak in the language of the business. The step-definition method is the seam: it translates the captured parameters, calls **one** method on an automation layer that already knows how to do the thing, and stores whatever the `Then` step will need. Anything below that call - locating a control, waiting for it, retrying - lives beneath the seam. Keeping the body one or two lines buys you three things: the same step text can be pointed at a different layer (screen today, API tomorrow) without touching the feature file, control renames never edit a specification, and the failure you read names a business rule rather than a button.

code

java · 14 lines
java
public class SeedOrderSteps {

    private final SeedCatalogue catalogue;
    private OrderRef placed;

    public SeedOrderSteps(SeedCatalogue catalogue) {
        this.catalogue = catalogue;
    }

    @When("the buyer orders {int} packets of {string}")
    public void ordersPackets(int packets, String variety) {
        placed = catalogue.order(variety, packets);
    }
}

go deeper

for a junior

Be ready to say what goes in the .feature line versus what goes in the code behind it. Practise rewriting a click-by-click scenario into two or three sentences a non-programmer would recognise.

for a middle

An interviewer expects you to explain the seam: Cucumber matches the text after the keyword, prints that text in reports, and runs a method that should make one call into a layer beneath. Show a thin body and say what you excluded from it.

for a senior

Demonstrate that you have paid the bill for getting this wrong - a redesign that rewrote feature files, or a suite that could not be re-pointed from screens to an API. Explain how you enforce the layering across a team rather than just describing it.

for a principal

Own the tradeoff between an intent-level suite that needs a real automation layer to exist and a control-level suite that ships faster and rots. Be able to argue when a team is too small for the layer and what you would accept instead.

A Cucumber scenario has two halves that are easy to blur together. The **Gherkin step text** is the line in the `.feature` file; the **step definition** is the method Cucumber runs when that line matches. Deciding what belongs on each side - and how far the method body is allowed to reach - is the single decision that separates a suite that survives a redesign from one that is rewritten with it. ## The seam Cucumber gives you Cucumber matches only the text *after* the keyword. `Given`, `When`, `Then`, `And`, `But` and `*` are stripped before matching, and in Cucumber-JVM the `@Given`, `@When` and `@Then` annotations are interchangeable at match time - the keyword is a readability device for the human, not a selector. What survives into matching is a sentence of prose, and that same sentence is what every formatter prints and what any published report renders. That gives you a seam with prose above and code below. The code side is ordinary classes in whatever package the glue path points at - `cucumber.glue` or `--glue` in Cucumber-JVM, `--require` in cucumber-js, the steps package in Behave, a `[Binding]` class in SpecFlow/Reqnroll. Nothing in Cucumber stops you putting a driver call directly in that class. The discipline is yours to impose. ## What each side owns | Concern | Gherkin step text | Step-definition body | Automation layer beneath | |---|---|---|---| | Business vocabulary | yes - this is the specification | only as parameter names | no | | The value that varies | yes, as `{int}` / `{string}` | translated to a domain type | consumed | | Which screen or endpoint | no | no | yes | | Locating a control | no | no | yes | | Waiting and retrying | no | no | yes | | Remembering state for `Then` | no | yes - one assignment | no | ## Why widget-level glue is expensive 1. **The report stops being a specification.** Because the step text is printed verbatim, a scenario built from control-level steps produces a transcript of clicks. A reader cannot tell from it what rule the system is meant to obey. 2. **A control rename edits the specification.** When "the green Confirm button" moves or is renamed, the feature file changes - so a cosmetic change shows up in the diff of the document you asked the business to trust. 3. **The step cannot be re-pointed.** An intent-level step such as "the buyer places an order for 14 packets" can be driven through the screens today and through the service API tomorrow by swapping the layer beneath. A step that says "the buyer clicks Confirm" is welded to one interface forever. 4. **Failures name the wrong thing.** "the buyer clicks Confirm" failing tells you a click did not land. "the order is rejected when stock is short" failing tells you a rule is broken. 5. **The glue duplicates itself.** Driver code inside step classes cannot be shared between them without inheritance games, so the same wait logic reappears in five classes. ## A worked split A seed-catalogue ordering service had a checkout feature whose 9 scenarios averaged 11 steps each, almost all of them control-level: select a variety, set a quantity field, click through three panels. Two of the panels were merged in a redesign and 63 of the 99 step lines had to be rewritten - in the feature file, not in code. Rewritten at intent level the same feature ran to 4 steps per scenario, and the same redesign touched only the layer beneath the glue. ```gherkin When the buyer orders 14 packets of "Cherokee Trail of Tears" beans Then the order is held until the variety is restocked ``` The step definition for the first line does exactly three things: convert `14` and the variety name into the types the layer wants, call one method, keep the returned order reference for the `Then`. ## How thin is thin A useful floor: if the body has a conditional, a loop, a sleep or a second call that is not bookkeeping, the logic it holds belongs in the layer beneath. The body may translate a parameter into a domain type, call one method that expresses the whole intent, and record a handle for the assertion step. Assertions themselves stay in the `Then` definition, phrased against domain values rather than rendered text. ## The honest exception Scenarios whose *subject* is the interface - a keyboard-navigation rule, a field-level validation message - legitimately mention interface concepts, because that is what the business rule is about. Even there the step text names the affordance ("the quantity field rejects a negative value"), not the mechanism, and the mechanism still lives beneath the seam.

  • Someone proposes one step definition that takes the screen name as a parameter, to cut duplication. Why is that worse rather than better?
    It moves the interface into the specification and calls it reuse. The parameter is not business data - nobody in the domain cares which screen is open - so the feature file now documents navigation, and the single definition grows a switch over screen names. Parameterise the values a rule varies over, never the mechanism that carries them.
  • The Then step needs data that only the automation layer computed. How do you keep the step definition thin?
    Have the layer return a domain object from the action call and keep that reference on the step-definition instance, then assert against its fields in the Then definition. The body stays one call plus one assignment, and the assertion reads against domain values rather than against text scraped from a screen.
  • How do you tell whether a step definition has quietly grown too thick?
    Look for a conditional, a loop, a sleep or a second unrelated call in the body. Any of those means the method is making decisions that the layer beneath should own; extract them and leave a single intent call behind. Bookkeeping - converting a parameter, storing a handle - does not count against the rule.

The Gherkin step is the order slip a customer writes; the step definition is the waiter who reads it and calls one thing through to the kitchen. A waiter who starts cooking at the table is the anti-pattern.

saying these in an interview costs you the question

  • Puts CSS or XPath selectors in the Gherkin step text
  • Calls the browser driver directly from the glue class
  • Thinks more granular steps make a scenario more precise
  • Writes waits and retries inside the step definition body
  • Treats the feature file as a script rather than a specification
  • Asserts against rendered screen text instead of domain values