Step Definitions & Feature Files
Writing clean, reusable step definitions is the highest-leverage skill in BDD. Poorly written step definitions — overly specific, coupled to implementation, or duplicated — make the Cucumber suite unmaintainable. Well-written step definitions are small, single-purpose building blocks that compose into any scenario the business needs.
Step Definition Best Practices and Anti-Patterns
# ══════════════════════════════════════════════════════════════
# ANTI-PATTERNS IN STEP DEFINITIONS
# ══════════════════════════════════════════════════════════════
# ❌ BAD: Too implementation-specific (not reusable)
@when('I find element by CSS selector ".btn-submit" and click it')
def bad_step(driver):
driver.find_element("css selector", ".btn-submit").click()
# Business users won't understand this and it breaks on CSS changes
# ❌ BAD: Duplicate steps for same action
@when('I click log in')
@when('I click sign in')
@when('I press the login button')
@when('I submit my credentials')
def too_many_steps(driver):
driver.find_element("id", "submit").click()
# ══════════════════════════════════════════════════════════════
# BEST PRACTICES
# ══════════════════════════════════════════════════════════════
# ✅ GOOD: Business-readable, implementation-agnostic
from pytest_bdd import when, then, given, parsers
from pages.login_page import LoginPage
from pages.product_page import ProductPage
# One action, cleanly named
@when("I submit the login form")
def submit_login(driver):
LoginPage(driver).submit()
# Parameterized with parsers.parse — handles any text value
@when(parsers.parse('I add "{product_name}" to my cart'))
def add_to_cart(driver, product_name):
ProductPage(driver).add_to_cart_by_name(product_name)
@then(parsers.parse('my cart should contain {count:d} item(s)')) # :d = integer type
def verify_cart_count(driver, count):
from pages.cart_page import CartPage
assert CartPage(driver).get_item_count() == count
# ── Feature file organization structure ──────────────────────
# features/
# authentication/
# login.feature
# logout.feature
# password_reset.feature
# shopping/
# product_search.feature
# add_to_cart.feature
# checkout.feature
# account/
# profile_update.feature
# order_history.feature
# step_definitions/
# auth_steps.py ← Steps for authentication features
# shopping_steps.py ← Steps for shopping features
# common_steps.py ← Steps used across multiple features
# conftest.py:
import pytest
from selenium import webdriver
@pytest.fixture(scope="session")
def base_url():
return "https://staging.myapp.com"
@pytest.fixture(scope="function")
def driver():
options = webdriver.ChromeOptions()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
yield driver
driver.quit()Common Mistakes
- All step definitions in one file — separate by feature domain; auth_steps.py, shopping_steps.py keep things manageable
- Exposing WebDriver in feature files — Gherkin should never mention 'click CSS selector'; keep implementation in step definitions via page objects
- Unstable step expressions — 'I click the submit button' doesn't match 'I click the Submit button' (case sensitive); use parsers.re for flexible matching
- Not sharing step definitions — if two features both need 'I am logged in as a user', that step must be in common_steps.py, not duplicated
Tip
Tip
Practice Step Definitions Feature Files in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
More unit tests, fewer E2E tests — the ideal testing balance
Practice Task
Note
Practice Task — (1) Write a working example of Step Definitions Feature Files from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with Step Definitions Feature Files is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready software testing code.
Key Takeaways
- Writing clean, reusable step definitions is the highest-leverage skill in BDD.
- All step definitions in one file — separate by feature domain; auth_steps.py, shopping_steps.py keep things manageable
- Exposing WebDriver in feature files — Gherkin should never mention 'click CSS selector'; keep implementation in step definitions via page objects
- Unstable step expressions — 'I click the submit button' doesn't match 'I click the Submit button' (case sensitive); use parsers.re for flexible matching