ScreenshotNeo

BlogHow-to

How to Use Gherkin and Selenium for Behavior-Driven Development

Learn how to turn collaborative behavior examples into readable Gherkin scenarios, connect them to Cucumber step definitions, and verify browser behavior with Selenium.

By the ScreenshotNeo team4 October 20269 min read

Use Gherkin to describe a behavior in a shared, readable example; use Cucumber to match its steps to code; and use Selenium WebDriver from those step definitions when the behavior needs a real browser. BDD is the collaborative process of discovering and agreeing on examples. Gherkin structures those examples, Cucumber executes them, and Selenium automates the browser. Cucumber explicitly says it is not itself a browser automation tool.

The working loop is: agree on an example with the people who understand the behavior, write a concise .feature scenario, bind its domain-language steps to code, prepare isolated browser state, interact through WebDriver, assert an observable result, and always close the driver.

1. Start with a behavior, not a click script

BDD starts with collaboration. Product, development, and testing collaborators discuss a small behavior and concrete examples until they share an understanding of the rule. Automation can preserve and check those examples, but writing Gherkin is not a substitute for the conversation.

Choose a behavior where the browser-facing path matters. For example, a visitor searches and sees matching content. Agree what counts as a match and what result the visitor can observe before choosing selectors or browser commands. Lower-level implementation behavior may be clearer and faster to cover with unit or component tests; reserve browser scenarios for behavior that benefits from checking the browser path.

2. Write a readable Gherkin feature

A .feature file begins with Feature. Use Scenario (also called Example) for a concrete example. The familiar pattern is Given for context, When for an event or action, and Then for the expected, observable result.

Feature: Search

  Scenario: A visitor finds matching content
    Given I am on the search page
    When I search for "Cheese!"
    Then the page title starts with "cheese"

This mirrors the shape of Cucumber’s Selenium example. Replace the example page with a stable environment and behavior your team owns. A production acceptance check that depends on an unrelated public website can fail because that site changed, even when your product did not.

  • Keep scenarios concise and specific. Cucumber’s Gherkin reference offers 3–5 steps as a guideline; a long step list can hide the rule the example is meant to communicate.
  • Write domain language rather than describing selectors, colors, or click sequences. “When I search for…” communicates intent; “When I click the blue button and type in the third field…” binds the specification to layout.
  • Make Then check an observable result: a visible message, resulting page, report, or other output. Avoid making a shared behavior example depend on a deeply buried database detail.
  • Use And or But to make a continued sequence easier to read. Keywords do not distinguish otherwise identical step text for matching, so avoid duplicate definitions with the same wording.
  • Use Rule to group examples under a business rule. Use Scenario Outline with an Examples table for a small set of meaningful data variations. A Data Table or Doc String fits structured or larger step input.
  • Keep Background brief and relevant. Complicated shared setup makes individual examples harder to understand.
Feature: Search
  Rule: Search results reflect the submitted query

    Scenario Outline: A visitor searches for a term
      Given I am on the search page
      When I search for "<term>"
      Then the results include "<expected>"

      Examples:
        | term    | expected |
        | Cheese! | cheese   |
        | bread   | bread    |

Use outlines for variations that illustrate the same behavior, not as a way to pack unrelated cases into a table. Gherkin syntax and keyword roles are documented in the Cucumber Gherkin reference.

3. Connect Gherkin to Cucumber and Selenium

Cucumber reads the feature text, finds a matching step definition for each step, calls the definitions in sequence, and reports whether the examples passed. The step definition is the bridge from shared wording to implementation. Keep browser details there or in helpers, rather than putting WebDriver mechanics into the feature prose.

The following is a Java-style illustration of the responsibilities: initialize a WebDriver for the scenario, navigate and interact in step definitions, wait for a state that represents the result, assert that result, and quit even if a step fails. Exact annotations, dependency setup, and fixture APIs depend on the Cucumber Java and Selenium versions in your project. The official Cucumber browser guide provides language examples and current integration guidance.

// Illustrative Java step-definition structure; adapt annotations and setup
// to the Cucumber and Selenium versions used by your project.

private WebDriver driver;

@Before
public void startBrowser() {
    driver = new ChromeDriver();
}

@Given("I am on the search page")
public void openSearchPage() {
    driver.get(System.getenv("APP_BASE_URL") + "/search");
}

@When("I search for {string}")
public void searchFor(String term) {
    WebElement input = driver.findElement(By.name("q"));
    input.sendKeys(term);
    input.submit();
}

@Then("the page title starts with {string}")
public void checkPageTitle(String prefix) {
    new WebDriverWait(driver, Duration.ofSeconds(10))
        .until(d -> d.getTitle().toLowerCase().startsWith(prefix));
    assertTrue(driver.getTitle().toLowerCase().startsWith(prefix));
}

@After
public void closeBrowser() {
    if (driver != null) {
        driver.quit();
    }
}

This snippet communicates the structure, but it is not a complete project: the Cucumber annotation imports, Selenium dependencies, browser driver provisioning, assertion library, and hook signatures must match your chosen versions. The documented Java example similarly navigates, locates a search input by name, submits a term, waits for the page title, checks it, and quits. Cucumber’s browser guide also includes Kotlin, JavaScript, and Ruby examples. See the Cucumber browser automation guide for binding-specific setup; remove the space in that URL path when entering it.

Keep scenario state isolated

Create a driver in test support or a scenario-scoped fixture and make it available to that scenario’s step definitions. Ensure teardown runs after failures. For parallel runs, give each scenario or worker its own driver and isolate test data so one browser cannot change another scenario’s state. Do not share a mutable driver across parallel scenarios.

Wait for a meaningful condition, such as a result appearing or a title changing. An arbitrary sleep waits a fixed duration regardless of whether the page is ready, slowing fast runs while still being too short for slow ones. Prefer explicit waits for the state the next step needs.

4. Run the feature and diagnose failures

  1. Run one feature against a known test environment and owned test data.
  2. Check that each step matches exactly one step definition. Resolve undefined or ambiguous steps before diagnosing browser behavior.
  3. Separate setup failures from behavior failures: confirm the application is reachable, the browser starts, and navigation succeeds before interpreting a failed result assertion.
  4. On failure, capture useful browser diagnostics such as a screenshot when your Cucumber binding and reporter support it. Cucumber’s browser guide includes screenshot-on-failure examples.
  5. Keep the failure output tied to the scenario and clean up the driver and test data so a retry starts from a known state.
Symptom Likely cause Fix
Undefined step No step definition matches the text, or wording drifted. Generate or write the missing definition, then align the feature wording with the agreed domain language.
Ambiguous step More than one definition matches the same step text. Remove overlapping patterns or make the step wording and expressions distinct. Changing Given/When/Then alone does not distinguish identical text.
Element not found The page is not at the expected state, a locator changed, or the element has not rendered. Confirm navigation and test data, prefer a stable locator, and wait for the required condition before interacting.
Intermittent timeout The test races dynamic rendering, relies on an external dependency, or shares state with another run. Wait on the expected state, use an owned stable test environment, isolate browser and data state, and inspect failure diagnostics.
Title or text assertion fails The expected behavior changed, the example is unclear, or the assertion checks before the result is ready. Revisit the example with collaborators, assert the user-visible outcome, and wait for that outcome explicitly.
Browser process remains after a failure Teardown was skipped or did not close the driver. Use the framework’s after hook or fixture cleanup and call quit() even when a step throws.
Passes alone, fails in parallel Scenarios share a driver, account, or mutable data. Scope drivers and test fixtures per scenario or worker and allocate isolated test data.

5. Choose the right test layer and keep the suite dependable

A Selenium scenario exercises a browser-facing path, so it needs a running application and browser environment. That makes it appropriate when the browser interaction is part of what must be checked. For internal component rules, a lower-level example often gives a more direct check. A balanced suite uses readable BDD examples where shared understanding and an end-to-end observable outcome matter, while covering implementation details at narrower layers.

For reliability, keep scenarios focused, use owned test environments and data, wait for conditions rather than fixed pauses, and isolate state for parallel work. A browser test that depends on a third-party page or service can report that dependency’s change as a product failure. Treat such dependencies deliberately and make the failure diagnostics clear.

For performance, avoid unnecessary browser scenarios for behavior that can be checked below the browser layer, avoid repeated setup hidden in large backgrounds, and wait only for the condition needed by the next step. The research sources provide no benchmark for expected speed or productivity, so measure the suite in your environment rather than relying on a generic timing claim.

6. Use screenshots to inspect browser outcomes

A screenshot can help explain a failed acceptance scenario, but it is diagnostic evidence rather than a replacement for an assertion. Prefer a screenshot from the same browser state and failure context so a developer can connect what was visible to the scenario’s expected outcome. Capture only where it adds useful information, and follow your team’s policy for pages that can contain private or sensitive data.

Or skip the browser setup

If the job is to capture a page for review or documentation rather than automate an acceptance scenario, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo 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
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}`);
  • Cookie and consent banners are accepted like a visitor would accept them, and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and whether the shot was billed.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents, including Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 shots a month with no card. Paid plans start at $5 for 3,000 shots; all features are on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

Frequently asked questions

Is Gherkin the same thing as BDD?

No. BDD is a collaborative way to discover and develop behavior through examples. Gherkin is a language for writing those examples in a structured form.

Does Cucumber automate the browser?

Cucumber matches feature steps to code and runs them. Selenium WebDriver performs browser automation when your step-definition code calls it.

Should every acceptance test be written in Gherkin?

No. Use Gherkin where examples support collaboration and executable documentation. Choose a narrower test layer when it expresses an implementation rule more directly.

What should a Then step verify?

Verify the expected result an outside observer can see, such as a rendered message, page, or report.

Further learning

Start with Cucumber’s BDD guide, Gherkin reference, and browser automation guide. Its learning page points to free Cucumber School videos and books, while Cucumber School lists courses. For Java readers, the publisher’s The Cucumber for Java Book specifically covers Cucumber with Java, including Selenium and asynchronous Ajax topics; check the publisher for current availability.