Cucumber best practices, patterns, and anti-patterns
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Master patterns and practices for effective Cucumber testing.
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
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
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
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: User Authentication
Scenario: Successful login
...
Scenario: Failed login with wrong password
...
Scenario: Account lockout after multiple failures
...
@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"
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
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
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
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!
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 |
// 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"')
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}', ...)
// 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);
});
❌ 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
@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
@slow @integration
Scenario: Full order workflow with email notifications
# Takes 30 seconds to run
...
Ensure scenarios can run in parallel:
// cucumber.js
module.exports = {
default: '--parallel 4'
};
features/
authentication/
login.feature
registration.feature
shopping/
cart.feature
checkout.feature
admin/
user-management.feature
❌ 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
Use Cucumber appropriately in your test strategy:
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.