Cucumber Cheat Sheet
Complete reference guide for Cucumber with interactive examples and live playground links
Gherkin Syntax (Cucumber)
Core Keywords
Essential Gherkin keywords for writing BDD scenarios in Cucumber
Feature: Feature description
As a [role]
I want [capability]
So that [benefit]
Scenario: Scenario description
Given [precondition]
When [action]
Then [expected result]
Background:
Given [common precondition]
Scenario Outline: Outline description
Given [precondition]
When [action] with <parameter>
Then [expected result]
Examples:
| parameter |
| value1 |
| value2 |
Keyword Meanings
Understanding what each Gherkin keyword represents in Cucumber
Feature: - Describes a major functionality/feature
Scenario: - Describes a specific behavior/test case
Given: - Sets up the initial context/state
When: - Describes the action/event that occurs
Then: - Describes the expected outcome/result
And: - Continues the previous step type
But: - Continues the previous step type (negative)
Background: - Common setup for multiple scenarios
Examples: - Data table for Scenario Outline
Writing Patterns
User Story Format
Standard format for writing user stories in Cucumber/Gherkin
Feature: User Authentication
As a registered user
I want to log into the system
So that I can access my account
Scenario: Successful login with valid credentials
Given I am on the login page
And I have valid credentials
When I enter my username and password
And I click the "Login" button
Then I should be redirected to the dashboard
And I should see a welcome message
Data-Driven Scenarios
Using Scenario Outline for testing with multiple data sets
Scenario Outline: Login with different user types
Given I am on the login page
When I enter "<username>" and "<password>"
And I click the "Login" button
Then I should see "<expected_message>"
Examples:
| username | password | expected_message |
| admin | admin123 | Welcome Admin |
| user | user123 | Welcome User |
| guest | guest123 | Access Denied |
Step Definitions
TypeScript Step Definitions
Implementing step definitions in TypeScript with Cucumber and Playwright
import { Given, When, Then, Before, After } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { chromium, Browser, Page } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
let browser: Browser;
let page: Page;
let loginPage: LoginPage;
Before(async function() {
browser = await chromium.launch({ headless: false });
page = await browser.newPage();
this.page = page;
});
After(async function() {
await browser?.close();
});
Given('I am on the login page', async function() {
await this.page.goto('https://example.com/login');
loginPage = new LoginPage(this.page);
});
When('I enter {string} and {string}', async function(username: string, password: string) {
await loginPage.enterUsername(username);
await loginPage.enterPassword(password);
});
When('I click the Login button', async function() {
await loginPage.clickLoginButton();
});
Then('I should be redirected to the dashboard', async function() {
const currentUrl = await this.page.url();
expect(currentUrl).toContain('/dashboard');
});
Then('I should see {string}', async function(message: string) {
const pageContent = await this.page.textContent('body');
expect(pageContent).toContain(message);
});
World Context
Using World context to share data between steps
import { setWorldConstructor, World } from '@cucumber/cucumber';
import { Page, Browser } from '@playwright/test';
export class CustomWorld extends World {
public page!: Page;
public browser!: Browser;
public testData: any = {};
async initBrowser() {
this.browser = await chromium.launch();
this.page = await this.browser.newPage();
}
async cleanup() {
await this.browser?.close();
}
}
setWorldConstructor(CustomWorld);
// Using World context in steps
Given('I am logged in as {string}', async function(username: string) {
this.testData.username = username;
await this.page.goto('/login');
// Login logic here
});
When('I perform an action', async function() {
// Access shared data
const username = this.testData.username;
// Action logic here
});
Page Object Model
TypeScript Page Object
Creating page objects in TypeScript with Playwright
import { Page, Locator } from '@playwright/test';
export class LoginPage {
private page: Page;
private usernameField: Locator;
private passwordField: Locator;
private loginButton: Locator;
private errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.usernameField = page.locator('#username');
this.passwordField = page.locator('#password');
this.loginButton = page.locator('#login-button');
this.errorMessage = page.locator('.error-message');
}
async enterUsername(username: string) {
await this.usernameField.clear();
await this.usernameField.fill(username);
}
async enterPassword(password: string) {
await this.passwordField.clear();
await this.passwordField.fill(password);
}
async clickLoginButton() {
await this.loginButton.click();
}
async getErrorMessage(): Promise<string> {
return await this.errorMessage.textContent() || '';
}
async isErrorMessageDisplayed(): Promise<boolean> {
return await this.errorMessage.isVisible();
}
async waitForPageLoad() {
await this.page.waitForLoadState('networkidle');
}
}
Base Page Object
Creating a base page object for common functionality
import { Page, Locator } from '@playwright/test';
export abstract class BasePage {
protected page: Page;
constructor(page: Page) {
this.page = page;
}
async navigateTo(path: string) {
await this.page.goto(path);
await this.page.waitForLoadState('networkidle');
}
async waitForElement(locator: Locator, timeout = 5000) {
await locator.waitFor({ timeout });
}
async takeScreenshot(name: string) {
await this.page.screenshot({
path: `screenshots/${name}-${Date.now()}.png`,
fullPage: true
});
}
async getPageTitle(): Promise<string> {
return await this.page.title();
}
}
export class DashboardPage extends BasePage {
private welcomeMessage: Locator;
private logoutButton: Locator;
constructor(page: Page) {
super(page);
this.welcomeMessage = page.locator('.welcome-message');
this.logoutButton = page.locator('#logout-button');
}
async getWelcomeMessage(): Promise<string> {
return await this.welcomeMessage.textContent() || '';
}
async logout() {
await this.logoutButton.click();
}
}
Configuration & Setup
Package.json Setup
Complete Node.js setup for Cucumber with Playwright
{
"name": "cucumber-playwright-tests",
"version": "1.0.0",
"description": "BDD tests with Cucumber and Playwright",
"scripts": {
"test": "cucumber-js",
"test:parallel": "cucumber-js --parallel 4",
"test:smoke": "cucumber-js --tags @smoke",
"test:regression": "cucumber-js --tags @regression",
"report": "node generate-report.js"
},
"devDependencies": {
"@cucumber/cucumber": "^9.0.0",
"@playwright/test": "^1.35.0",
"@types/node": "^18.0.0",
"ts-node": "^10.9.0",
"typescript": "^4.9.0",
"cucumber-html-reporter": "^7.0.0",
"cucumber-pretty": "^6.0.0"
}
}
Cucumber Configuration
Cucumber.js configuration for TypeScript and Playwright
// cucumber.js
module.exports = {
default: {
requireModule: ['ts-node/register'],
require: [
'src/step-definitions/**/*.ts',
'src/hooks/**/*.ts',
'src/support/**/*.ts'
],
format: [
'progress-bar',
'html:reports/cucumber-report.html',
'json:reports/cucumber-report.json',
'cucumber-pretty'
],
formatOptions: {
snippetInterface: 'async-await',
theme: {
'feature keyword': ['blue', 'bold'],
'scenario keyword': ['blue', 'bold'],
'step keyword': ['blue', 'bold']
}
},
publishQuiet: true,
parallel: 2
},
smoke: {
tags: '@smoke',
format: ['progress-bar', 'html:reports/smoke-report.html']
},
regression: {
tags: '@regression',
format: ['progress-bar', 'html:reports/regression-report.html']
}
};
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
expect: { timeout: 5000 },
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['json', { outputFile: 'test-results/results.json' }]
],
use: {
baseURL: 'https://example.com',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
Best Practices
Writing Guidelines
Best practices for writing effective Cucumber/Gherkin scenarios
✅ DO:
- Write in business language, not technical terms
- Keep scenarios focused and atomic
- Use descriptive step names
- Make scenarios independent
- Use Background for common setup
- Use data tables for complex data
- Tag scenarios appropriately
❌ DON'T:
- Include implementation details
- Write UI-specific steps ("click blue button")
- Make scenarios dependent on each other
- Use technical jargon
- Write overly complex scenarios
- Hardcode test data in scenarios
Example of good vs bad:
✅ "When I click the Login button"
❌ "When I click the blue button with ID 'login-btn'"
✅ "When I enter valid credentials"
❌ "When I enter 'admin' and 'password123'"
Step Definition Patterns
Best practices for implementing step definitions
// Use async/await for all async operations
When('I click the Login button', async function() {
await this.page.click('#login-button');
});
// Use parameters for flexibility
When('I enter {string} and {string}', async function(username: string, password: string) {
await this.page.fill('#username', username);
await this.page.fill('#password', password);
});
// Use data tables for complex data
When('I fill in the user details:', async function(dataTable) {
const userData = dataTable.rowsHash();
await this.page.fill('#name', userData.name);
await this.page.fill('#email', userData.email);
await this.page.selectOption('#role', userData.role);
});
// Use World context for shared state
Given('I am logged in as {string}', async function(username: string) {
this.currentUser = username;
// Login logic here
});
// Handle errors gracefully
Then('I should see an error message', async function() {
await expect(this.page.locator('.error-message')).toBeVisible();
const errorText = await this.page.locator('.error-message').textContent();
expect(errorText).toMatch(/invalid|error|failed/i);
});
CI/CD Integration
GitHub Actions
Setting up Cucumber tests in GitHub Actions with Playwright
name: Cucumber BDD Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18]
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Cucumber tests
run: npm run test
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: cucumber-reports-node-18
path: |
reports/
test-results/
screenshots/
retention-days: 30
- name: Publish test results
uses: dorny/test-reporter@v1
if: always()
with:
name: Cucumber Test Results
path: reports/cucumber-report.json
reporter: cucumber-json
Docker Integration
Running Cucumber tests in Docker containers
# Dockerfile
FROM mcr.microsoft.com/playwright:v1.35.0-focal
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY tsconfig.json ./
# Install dependencies
RUN npm ci
# Copy source code
COPY src/ ./src/
COPY cucumber.js ./
# Install Playwright browsers
RUN npx playwright install --with-deps
# Run tests
CMD ["npm", "run", "test"]
# docker-compose.yml
version: '3.8'
services:
cucumber-tests:
build: .
environment:
- NODE_ENV=test
- BASE_URL=https://example.com
volumes:
- ./reports:/app/reports
- ./screenshots:/app/screenshots
command: npm run test:parallel
# Run with Docker
# docker build -t cucumber-playwright .
# docker run -v $(pwd)/reports:/app/reports cucumber-playwright
Advanced Features
Tags and Organization
Using tags to organize and filter scenarios
@smoke @login
Feature: User Authentication
As a user
I want to authenticate
So that I can access the system
@smoke
Scenario: Basic login
Given I am on the login page
When I enter valid credentials
Then I should be logged in
@regression @edge-case
Scenario: Login with special characters
Given I am on the login page
When I enter username with special chars
Then I should see validation error
@api @integration
Scenario: API authentication
Given I have valid API credentials
When I make an authentication request
Then I should receive a valid token
# Running tagged scenarios:
# npm run test -- --tags @smoke
# npm run test -- --tags "not @slow"
# npm run test -- --tags "@smoke and @login"
# npm run test -- --tags "@api or @integration"
Hooks and Setup
Using hooks for setup and teardown operations
import { Before, After, BeforeAll, AfterAll } from '@cucumber/cucumber';
import { chromium, Browser } from '@playwright/test';
let browser: Browser;
BeforeAll(async function() {
browser = await chromium.launch();
});
Before(async function() {
this.page = await browser.newPage();
await this.page.setViewportSize({ width: 1280, height: 720 });
});
After(async function() {
if (this.page) {
await this.page.close();
}
});
AfterAll(async function() {
await browser?.close();
});
// Tagged hooks
Before('@database', async function() {
// Setup test database
await setupTestDatabase();
});
After('@cleanup', async function() {
// Clean up test data
await cleanupTestData();
});
// Screenshot on failure
After(async function(scenario) {
if (scenario.result?.status === 'FAILED') {
const screenshot = await this.page.screenshot({
path: `screenshots/failure-${scenario.pickle.name}-${Date.now()}.png`,
fullPage: true
});
this.attach(screenshot, 'image/png');
}
});
Cucumber - Interactive Developer Reference
Hover over code blocks to copy or run in live playground