Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

Gherkin Scenarios in Automation

The retail test guild maintains checkout automation in SpecFlow (.NET) and inventory automation in Behave (Python), but both teams keep drifting the same scenarios apart by hand. You are building the Java-based Scenario Registry the guild's Jenkins pipeline calls before either stack runs: it parses the shared .feature text once, feeds identical scenarios into stand-in SpecFlow and Behave binding adapters, keeps setup hooks out of the Background and application code, lets engineers slice a run by nested capability tags, tracks when a scenario last passed Three Amigos review, and turns the run results into a Cucumber-style report scored for coverage and maintainability.

Lab platform
Lab Info
Level
Intermediate
Last updated
Nov 13, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. Challenge

    Step 1: Meet the Scenario Registry

    Two stacks, one drifting truth

    The retail test guild automates checkout in SpecFlow (.NET) and inventory in Behave (Python). Both teams started from the same hand-written Gherkin scenarios, but every time one team tweaks a step to fix a binding, the other team's copy quietly falls out of sync. Six months in, @checkout in SpecFlow and @checkout in Behave no longer test the same behavior, and nobody notices until a release breaks.

    The fix isn't a new automation stack — it's a Java service that sits in front of both: parse the .feature text exactly once, hand identical scenarios to each stack's binding layer, and let every downstream concern (hooks, tags, review status, reporting) work off that one shared representation. ## What you will build

    Across the next four steps you will build the Scenario Registry, one file at a time:

    • Step 2 — a FeatureParser that turns raw feature text into Scenario records, plus SpecFlow-style and Behave-style adapters that both replay the same parsed scenarios.
    • Step 3 — a HookRegistry and BackgroundRunner that keep setup hooks and shared Background steps cleanly separated from the checkout application logic they surround.
    • Step 4 — a CapabilityIndex and a nested boolean TagExpressionMatcher that let an engineer run just the slice of the suite a capability filter selects.
    • Step 5 — a ReviewTracker that records Three Amigos sign-off and flags stale scenarios, feeding a ReportPublisher that scores a run for coverage.

    Each file builds on the Scenario record the parser produces in Step 2, so the registry stays one coherent pipeline rather than five unrelated exercises. ## The shared building block

    Every file you write operates on the same small record:

    public record Scenario(String name, List<String> tags, List<String> steps) {}
    

    A Scenario carries its name, the capability and review tags it was written with (@checkout, @smoke, and so on), and the ordered Given/When/Then lines that make up its body. This record is already in the workspace — you won't edit it — but recognizing its three fields now will make every task ahead easier to reason about, since parse(), both adapters, the hooks layer, the tag matcher, and the reporter all read or produce lists of it.

    info> If you get stuck, you can refer to the provided solution code for each task, available in the solution folder.

  2. Challenge

    Step 2: Parse and reuse a shared feature file across bindings

    From feature text to structured scenarios

    Before either stack can run anything, the guild's raw .feature files — plain text with @tag lines, Scenario: lines, and step lines — need to become data a Java program can work with. That conversion happens exactly once, in one file, so both stacks are guaranteed to see identical scenarios rather than two hand-parsed approximations of the same text. ## Binding one scenario list to two stacks

    With parse() producing a shared List<Scenario>, the next question is proof: does that one list actually drive both automation stacks identically? Each stack has its own binding conventions — SpecFlow logs steps joined with semicolons, Behave with pipes — but the scenarios themselves must be the same objects, untouched between the two adapters. ## The second stack, same scenarios

    Now repeat the proof from the other side. The Behave-style adapter takes the exact same Scenario objects — no separate parse, no separate feature file — and only its formatting differs. If both adapters run cleanly off one List<Scenario>, the guild's drift problem is solved at the source.

  3. Challenge

    Step 3: Keep hooks, Background, and application logic apart

    Hooks are not Background steps

    It's tempting to treat "stuff that runs before a scenario" as one bucket, but Cucumber-style frameworks separate two distinct concepts. Hooks are framework-level setup/teardown (spinning up a browser, resetting a database) that never appears in the feature file and knows nothing about scenario content. Background steps are Gherkin steps the author wrote, shared by every scenario in a feature, and they read like any other step. Conflating them is how teams end up with hooks that quietly call application methods directly — exactly the coupling the guild wants to eliminate. ## Background steps run for everyone

    With hooks handled as pure bracketing around an action, the second half of the split is making sure Background steps — the ones authored in the feature file — always execute ahead of a scenario's own steps, in a way that's driven by the shared step list rather than by any one binding's convention. ## Application logic stays reachable only through steps

    The last piece of the separation is the checkout logic itself. It must do real work — sum a basket, apply a discount — but it must never be something a hook calls directly. By keeping CheckoutService a plain calculation with no dependency on HookRegistry or BackgroundRunner, the only path into it is through a bound step, which is exactly the boundary the guild's two stacks need to respect.

  4. Challenge

    Step 4: Tag scenarios by capability and filter with nested expressions

    Organizing by business capability

    As the shared scenario list grows past a handful of features, engineers stop wanting "run everything" and start wanting "run everything for checkout" or "run everything tagged smoke." The first step toward that is indexing: grouping every parsed scenario under each business-capability tag it carries, so a lookup by tag is a map access instead of a full scan. ## Nested tag expressions

    A flat index answers "which scenarios have @checkout" but not "which scenarios are @checkout and (@smoke or @regression)" — the kind of nested filter a real CI pipeline actually needs. That requires treating the expression as a small boolean grammar: tokens are tag lookups, and/or/not combine them, and parentheses override the default evaluation order. ## From matching to running

    A matcher that answers true/false per scenario is only useful once something applies it across the whole suite and reports which scenarios survived. That's the last piece: a runner that filters by the expression and produces the run log the guild's pipeline actually reads.

  5. Challenge

    Step 5: Track Three Amigos review and publish the run report

    Three Amigos and living specifications

    A Gherkin scenario is only trustworthy as a "living specification" if a business owner, a developer, and a tester actually reviewed it together — the Three Amigos practice. Automation without a review trail just runs whatever text happens to be in the file, stale or not. The registry needs to remember, per scenario, when that review last happened. ## Overdue reviews

    Recording a review date only pays off once something checks it against the calendar. A scenario reviewed eight months ago is functionally the same risk as one never reviewed at all, so the tracker needs a single rule that catches both cases the same way. ## Publishing a scored report

    The guild's Jenkins job doesn't want a pass/fail count — it wants a Cucumber-style report that also communicates how much of what ran is actually backed by a current Three Amigos review. That means the report's coverage figure isn't "scenarios executed," it's "scenarios executed and currently reviewed," which is what makes the number meaningful for maintainability rather than just volume.

  6. Challenge

    Step 6: Watch one feature file drive the whole registry

    Every task is done and graded. Now run the registry end to end: one block of feature text with four scenarios goes through both stacks, a hooked checkout scenario, a tag filter, and the review-scored report, all in code you wrote.

    See one parse feed both stacks

    Open the Terminal tab and run:

    bash runApp.sh
    

    Section 1. One feature file, two stacks starts with Parsed 4 scenarios once. and then prints every scenario twice. Compare the two lines for the same scenario:

    [SpecFlow] Restock a shelf: Given a shelf is empty; When stock arrives; And the shelf is scanned; Then the shelf is full
    [Behave] Restock a shelf -> Given a shelf is empty | When stock arrives | And the shelf is scanned | Then the shelf is full
    

    The names and steps match exactly, And line included, because your SpecFlowAdapter (Task 2.2) and BehaveAdapter (Task 2.3) both read the one List<Scenario> your FeatureParser (Task 2.1) produced. Only the formatting differs. Edit a step in the feature text and both stacks get the change, which is the drift problem from Step 1 solved at the source. ### Watch hooks bracket a scenario without touching checkout code

    In the same output, section 2. Hooks around "Apply a discount code" prints step: Given the store is open ahead of the scenario's own three steps. That's your BackgroundRunner (Task 3.2) putting the shared Background first. Next comes bound step: checkoutTotal([20.0, 10.0], "SAVE10") = 27.0: your CheckoutService (Task 3.3), called from the scenario's step.

    The last line is everything your HookRegistry (Task 3.1) did:

    Hook log: [before:Apply a discount code, after:Apply a discount code]
    

    It recorded the bracket and ran the scenario once. It never called the checkout code itself, so setup, Background, and application logic each stay in their own layer. ### Slice the run and watch coverage follow

    Section 4. Cucumber report as of 2024-03-01 of that run lists all four scenarios and ends with Coverage: 50% | Scenarios: 4. Pay with a gift card [OVERDUE] was last reviewed on 2023-12-01, more than 30 days earlier, and Flag low stock [OVERDUE] was never reviewed. Now run only one slice of the suite:

    bash runApp.sh "@checkout and not @smoke"
    

    Section 3 prints only Pay with a gift card: RAN, and the report drops to Coverage: 0% | Scenarios: 1. Try a nested expression:

    bash runApp.sh "@smoke and (@checkout or @inventory)"
    

    This time Apply a discount code: RAN and Restock a shelf: RAN, and the report reads Coverage: 100% | Scenarios: 2. Your TagExpressionMatcher (Task 4.2) evaluated the not and the parentheses, your TaggedRunner (Task 4.3) kept the matches in file order, and your ReportPublisher (Task 5.3) scored only what ran, using the dates your ReviewTracker (Tasks 5.1 and 5.2) holds. The same suite can be fully reviewed for one slice and not at all for another, and the report tells you which.

    To rerun every check, use bash runTest.sh 'Step*Test'. You now have one registry that keeps two stacks in sync, keeps hooks out of application code, and tells the guild which slice of the suite it can trust.

About the author

Pluralsight’s AI authoring technology is designed to accelerate the creation of hands-on, technical learning experiences. Serving as a first-pass content generator, it produces structured lab drafts aligned to learning objectives defined by Pluralsight’s Curriculum team. Each lab is then enhanced by our Content team, who configure the environments, refine instructions, and conduct rigorous technical and quality reviews. The result is a collaboration between artificial intelligence and human expertise, where AI supports scale and efficiency, and Pluralsight experts ensure accuracy, relevance, and instructional quality, helping learners build practical skills with confidence.

Real skill practice before real-world application

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Learn by doing

Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.

Follow your guide

All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.

Turn time into mastery

On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.

Get started with Pluralsight