Built for developers, by XinhND

v2.1.0

Ready

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

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

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

Cucumber
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

Cucumber
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

Cucumber
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

Cucumber
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

Cucumber
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

Cucumber
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

Cucumber
{
  "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
// 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

Cucumber
✅ 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

Cucumber
// 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

Cucumber
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

Cucumber
# 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

Cucumber
@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

Cucumber
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