ð cucumber-best-practices
Cucumber best practices, patterns, and anti-patterns
Overview
Master patterns and practices for effective Cucumber testing.
Scenario Design Principles
1. Write Declarative Scenarios
Focus on what needs to happen, not how it happens.
â Imperative (implementation-focused):
Scenario: Add product to cart
Given I navigate to "http://shop.com/products"
When I find the element with CSS ".product[data-id='123']"
And I click the button with class "add-to-cart"
And I wait for the AJAX request to complete
Then the element ".cart-count" should contain "1"
â Declarative (business-focused):
Scenario: Add product to cart
Given I am browsing products
When I add "Wireless Headphones" to my cart
Then my cart should contain 1 item
2. One Scenario, One Behavior
Each scenario should test exactly one business rule or behavior.
â Multiple behaviors in one scenario:
Scenario: User registration and login and profile update
Given I register a new account
When I log in
And I update my profile
And I change my password
Then everything should work
â Separate scenarios:
Scenario: Register new account
When I register with valid details
Then I should receive a confirmation email
Scenario: Login with new account
Given I have registered an account
When I log in with my credentials
Then I should see my dashboard
Scenario: Update profile
Given I am logged in
When I update my profile information
Then my changes should be saved
3. Keep Scenarios Independent
Each scenario should set up its own preconditions.
â Dependent scenarios:
Scenario: Create order
When I create order #12345
Scenario: View order
When I view order #12345 # Depends on previous scenario!
â Independent scenarios:
Scenario: View order
Given an order exists with ID "12345"
When I view the order details
Then I should see the order information
4. Use Background Wisely
Use Background for common setup, but don't overuse it.
â Good use of Background:
Feature: Shopping Cart
Background:
Given I am logged in as a customer
Scenario: Add product to cart
When I add a product to my cart
Then my cart should contain 1 item
Scenario: Remove product from cart
Given I have a product in my cart
When I remove the product
Then my cart should be empty
â Background doing too much:
Background:
Given I am on the homepage
And I click the menu
And I navigate to products
And I filter by category "Electronics"
And I sort by price
# Too much setup! Not all scenarios need all of this
Feature Organization
Group Related Scenarios
Feature: User Authentication
Scenario: Successful login
...
Scenario: Failed login with wrong password
...
Scenario: Account lockout after multiple failures
...
Use Tags Effectively
@smoke @critical
Scenario: Login with valid credentials
...
@slow @integration
Scenario: Password reset email workflow
...
@wip
Scenario: OAuth login
# Work in progress
...
Run specific tags:
# Run smoke tests
cucumber --tags "@smoke"
# Run all except WIP
cucumber --tags "not @wip"
# Run smoke AND critical
cucumber --tags "@smoke and @critical"
# Run smoke OR critical
cucumber --tags "@smoke or @critical"
Writing Good Gherkin
Use Domain Language
Write in the language of the business domain, not technical terms.
â Technical language:
Scenario: POST request to /api/users
When I send a POST to "/api/users" with JSON payload
And the response status is 201
â Domain language:
Scenario: Register new user
When I register a new user account
Then the user should be created successfully
Keep Steps at the Same Level
Don't mix high-level and low-level details.
â Mixed levels:
Scenario: Purchase product
Given I am logged in
When I add a product to cart
And I click the element with ID "checkout-btn" # Too detailed!
And I enter credit card "4111111111111111" # Too detailed!
Then I complete the purchase
â Consistent level:
Scenario: Purchase product
Given I am logged in
And I have a product in my cart
When I checkout with a credit card
Then my order should be completed
And I should receive a confirmation email
Avoid Conjunctive Steps
Don't use "And" to combine multiple distinct actions in prose.
â Conjunctive step:
When I log in and add a product to cart and checkout
â Separate steps:
When I log in
And I add a product to my cart
And I proceed to checkout
Scenario Outlines
Use for True Variations
Use Scenario Outlines when you need to test the same behavior with different data.
â Good use:
Scenario Outline: Login validation
When I log in with "<username>" and "<password>"
Then I should see "<message>"
Examples:
| username | password | message |
| valid | valid | Welcome |
| invalid | valid | Invalid username |
| valid | invalid | Invalid password |
| empty | empty | Username required |
â Overusing Scenario Outline:
# Don't use Scenario Outline for unrelated test cases
Scenario Outline: Multiple features
When I use feature "<feature>"
Then result is "<result>"
Examples:
| feature | result |
| login | success |
| registration | success |
| cart | empty | # These are different behaviors!
Keep Examples Meaningful
Scenario Outline: Discount calculation
Given a customer with "<membership>" status
When they purchase items totaling $<amount>
Then they should receive a $<discount> discount
Examples: Standard discounts
| membership | amount | discount |
| silver | 100 | 5 |
| gold | 100 | 10 |
| platinum | 100 | 15 |
Examples: Minimum purchase thresholds
| membership | amount | discount |
| silver | 49 | 0 |
| silver | 50 | 2.50 |
Step Definition Patterns
Create Reusable Steps
// Generic, reusable
When('I fill in {string} with {string}', async function(field, value) {
await this.page.fill(`[name="${field}"]`, value);
});
// Used in multiple scenarios:
When('I fill in "email" with "test@example.com"')
When('I fill in "password" with "secure123"')
When('I fill in "search" with "products"')
Avoid Over-Generic Steps
Balance reusability with readability.
â Too generic:
When('I do {string} with {string} and {string}', ...)
â Specific and readable:
When('I log in with {string} and {string}', ...)
When('I search for {string} in {string}', ...)
Data Management
Use Factories for Test Data
// support/factories.js
const faker = require('faker');
class UserFactory {
static create(overrides = {}) {
return {
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
email: faker.internet.email(),
password: 'Test123!',
...overrides
};
}
}
// Use in steps
Given('I register a new user', async function() {
const user = UserFactory.create();
this.currentUser = user;
await this.api.register(user);
});
Avoid Hardcoded IDs
â Hardcoded:
Given user "12345" exists
When I view order "67890"
â Named entities:
Given a user "john@example.com" exists
When I view my most recent order
Error Handling
Test Happy and Unhappy Paths
@happy-path
Scenario: Successful checkout
Given I have items in my cart
When I complete the checkout process
Then my order should be confirmed
@error-handling
Scenario: Checkout with expired card
Given I have items in my cart
When I checkout with an expired credit card
Then I should see an error message
And my order should not be processed
@edge-case
Scenario: Checkout with insufficient inventory
Given I have a product in my cart
But the product is out of stock
When I attempt to checkout
Then I should be notified about stock unavailability
Performance
Tag Slow Tests
@slow @integration
Scenario: Full order workflow with email notifications
# Takes 30 seconds to run
...
Parallel Execution
Ensure scenarios can run in parallel:
// cucumber.js
module.exports = {
default: '--parallel 4'
};
Maintenance
Regular Review
- Remove obsolete scenarios
- Update scenarios when requirements change
- Refactor duplicate steps
- Keep features organized
Version Control
features/
authentication/
login.feature
registration.feature
shopping/
cart.feature
checkout.feature
admin/
user-management.feature
Common Anti-Patterns
â Testing implementation details:
Then the database should have 1 record in the users table
â UI-specific assertions in business scenarios:
Then I should see a red error message in the top right corner
â Using Given for actions:
Given I click the submit button # This is a When, not a Given!
â Technical jargon:
When I POST to /api/v1/users with JSON body
Testing Pyramid
Use Cucumber appropriately in your test strategy:
- E2E Cucumber Tests: Critical user journeys (20%)
- Integration Tests: API/service interactions (30%)
- Unit Tests: Business logic (50%)
Don't try to test everything with Cucumber. Use it for high-value acceptance tests.
Remember: Cucumber tests should document behavior, facilitate collaboration, and provide confidence that the system works as expected from a business perspective.