Documentation
BDD EXAMPLES
BDD Examples - Gherkin vs Playwright
This document shows side-by-side comparisons of the same test written in both styles.
Example 1: Generate AI Response
BDD (Gherkin) - Business Perspective
Feature: AI Prompt Generation
As a user
I want to generate AI responses
So that I can interact with the AI
Scenario: Generate a streaming AI response
Given I am on the AI Prompt Pad page
When I enter the prompt "What is machine learning?"
And I click the Generate button
Then I should see my message in the conversation
And I should see the streaming indicator
And I should see the AI response streaming
And the streaming should complete successfullyWho reads this: Product managers, stakeholders, QA, developers Focus: What the user experiences Language: Business terminology
Playwright - Technical Perspective
test('should generate and display streaming response', async ({ page }) => {
await page.goto('/')
const promptInput = page.getByTestId('prompt-input')
const generateButton = page.getByTestId('generate-button')
// Enter prompt
await promptInput.fill('Tell me about AI')
// Click generate
await generateButton.click()
// Wait for user message to appear
await expect(page.locator('.message.user')).toBeVisible()
await expect(page.locator('.message.user .message-content'))
.toContainText('Tell me about AI')
// Wait for assistant response to start streaming
await expect(page.locator('.message.assistant')).toBeVisible({
timeout: 10000
})
// Verify streaming indicator (cursor) appears
await expect(page.locator('.cursor')).toBeVisible({
timeout: 5000
})
// Wait for response to complete (cursor should disappear)
await expect(page.locator('.cursor')).not.toBeVisible({
timeout: 15000
})
// Verify assistant message has content
const assistantMessage = page.locator('.message.assistant .message-content')
await expect(assistantMessage).not.toBeEmpty()
})Who reads this: Developers, QA engineers Focus: How it's implemented, timing, selectors Language: Code, technical details
Example 2: Stop Generation
BDD (Gherkin)
Scenario: Stop generation mid-stream
Given I am on the AI Prompt Pad page
When I enter the prompt "Long response test"
And I click the Generate button
And I wait for streaming to start
And I click the Stop button
Then the streaming should stop immediately
And the response should contain the stopped markerClarity: Clear user workflow Abstraction: High-level actions Implementation: Hidden in step definitions
Playwright
test('should stop generation when stop button is clicked', async ({ page }) => {
await page.goto('/')
const promptInput = page.getByTestId('prompt-input')
const generateButton = page.getByTestId('generate-button')
await promptInput.fill('Test stopping generation')
await generateButton.click()
// Wait for generation to start
await expect(page.locator('.cursor')).toBeVisible({ timeout: 5000 })
// Click stop button
const stopButton = page.getByTestId('stop-button')
await stopButton.click()
// Verify generation stopped
await expect(page.locator('.cursor')).not.toBeVisible({
timeout: 2000
})
await expect(page.locator('.message.assistant'))
.toContainText('[stopped]')
})Clarity: Shows exact timing and selectors Abstraction: Low-level browser interactions Implementation: Visible and explicit
Example 3: Multi-turn Conversation
BDD (Gherkin)
Scenario: Have a multi-turn conversation
Given I am on the AI Prompt Pad page
When I enter the prompt "First question"
And I click the Generate button
And I wait for the response to complete
And I enter the prompt "Second question"
And I click the Generate button
And I wait for the response to complete
Then I should see 2 user messages
And I should see 2 assistant messagesBenefits:
- Easy to understand by anyone
- Documents user flow clearly
- Can be written before implementation
Playwright
test('should allow multiple prompts in conversation', async ({ page }) => {
await page.goto('/')
const promptInput = page.getByTestId('prompt-input')
const generateButton = page.getByTestId('generate-button')
// First prompt
await promptInput.fill('First question')
await generateButton.click()
await expect(page.locator('.message.user').first())
.toContainText('First question')
await expect(page.locator('.cursor')).not.toBeVisible({
timeout: 15000
})
// Second prompt
await promptInput.fill('Second question')
await generateButton.click()
await expect(page.locator('.message.user').nth(1))
.toContainText('Second question')
await expect(page.locator('.cursor')).not.toBeVisible({
timeout: 15000
})
// Verify we have 2 user messages and 2 assistant messages
await expect(page.locator('.message.user')).toHaveCount(2)
await expect(page.locator('.message.assistant')).toHaveCount(2)
})Benefits:
- Full control over assertions
- Can test specific message order
- Easy to debug failures
Example 4: Keyboard Shortcuts
BDD (Gherkin)
Scenario: Use keyboard shortcuts
Given I am on the AI Prompt Pad page
When I enter the prompt "Test Enter key"
And I press Enter
Then I should see my message in the conversation
Scenario: Create multiline prompt with Shift+Enter
Given I am on the AI Prompt Pad page
When I enter "Line 1"
And I press Shift+Enter
And I enter "Line 2"
Then the prompt should contain multiple lines
And no message should be sent yetNatural language: Describes behavior, not implementation Reusable steps: "I press Enter" can be used anywhere
Playwright
test('should support Enter key to submit prompt', async ({ page }) => {
await page.goto('/')
const promptInput = page.getByTestId('prompt-input')
await promptInput.fill('Test with Enter key')
await promptInput.press('Enter')
// Verify message was sent
await expect(page.locator('.message.user'))
.toContainText('Test with Enter key')
})
test('should support Shift+Enter for new line without submitting',
async ({ page }) => {
await page.goto('/')
const promptInput = page.getByTestId('prompt-input')
await promptInput.fill('Line 1')
await promptInput.press('Shift+Enter')
await promptInput.type('Line 2')
// Verify textarea contains multiline text
const textareaValue = await promptInput.inputValue()
expect(textareaValue).toContain('\n')
// Verify no message was sent yet
await expect(page.locator('.message')).toHaveCount(0)
}
)Detailed checks: Verifies exact textarea value Technical assertions: Tests implementation specifics
Example 5: Error Handling
BDD (Gherkin)
Feature: Error Handling
As a user
I want graceful error handling
So that I understand what went wrong
Scenario: Handle backend server errors
Given I am on the AI Prompt Pad page
And the backend returns an error
When I enter the prompt "Test error"
And I click the Generate button
Then I should see an error message
And the error message should be helpfulUser-focused: Describes what user sees Behavioral: Tests error handling from user perspective
Playwright
test('should handle backend errors gracefully', async ({ page }) => {
await page.goto('/')
// Intercept API call and return error
await page.route('**/api/generate', route => {
route.fulfill({
status: 500,
body: 'Internal Server Error',
})
})
const promptInput = page.getByTestId('prompt-input')
const generateButton = page.getByTestId('generate-button')
await promptInput.fill('This should fail')
await generateButton.click()
// Verify error message appears
await expect(page.locator('.message.assistant')).toContainText(
'Sorry, there was an error generating the response.'
)
})Network mocking: Simulates specific error conditions Technical setup: Shows how errors are triggered
Step Definitions (The Bridge)
Step definitions connect Gherkin to Playwright:
Gherkin Step
When I click the Generate button
Step Definition (TypeScript)
import { When } from '@cucumber/cucumber'
When('I click the Generate button', async function () {
const button = this.page.getByTestId('generate-button')
await button.click()
})
What It Executes (Playwright)
// Under the hood, it runs this Playwright code:
const button = page.getByTestId('generate-button')
await button.click()
When to Choose Which?
Choose BDD (Gherkin) When:
# ✅ Good for BDD
Scenario: User completes checkout process
Given I have items in my cart
When I proceed to checkout
And I enter payment information
And I confirm the order
Then I should see an order confirmation
And I should receive a confirmation email
- Multi-step user journeys
- Business acceptance criteria
- Cross-team collaboration
- Living documentation
Choose Playwright When:
// ✅ Good for Playwright
test('should debounce search input by 300ms', async ({ page }) => {
const searchInput = page.getByTestId('search')
await searchInput.type('test')
// Should not trigger search immediately
await page.waitForTimeout(100)
await expect(page.locator('.search-results')).not.toBeVisible()
// Should trigger after 300ms
await page.waitForTimeout(250)
await expect(page.locator('.search-results')).toBeVisible()
})
- Timing and performance
- Browser-specific behavior
- Complex state management
- Technical edge cases
Hybrid Example
Combine both for comprehensive coverage:
BDD (User Journey)
Scenario: Search for a product
When I search for "laptop"
Then I should see search results
And results should be relevant
Playwright (Technical Details)
test('should debounce search requests', ...)
test('should show loading spinner during search', ...)
test('should handle empty results gracefully', ...)
test('should support keyboard navigation in results', ...)Result:
- BDD covers the happy path
- Playwright covers edge cases and technical requirements
Summary Comparison
| Aspect | BDD (Gherkin) | Playwright |
|---|---|---|
| Audience | Everyone | Developers |
| Language | Natural | Code |
| Abstraction | High | Low |
| Detail Level | User behavior | Implementation |
| Setup Required | Step definitions | None |
| Debugging | Harder | Easier |
| Collaboration | Excellent | Limited |
| Speed to Write | Slower (need steps) | Faster |
| Maintenance | 2 files (feature + steps) | 1 file |
| CI Reporting | Business-friendly | Technical |
- Write BDD scenarios for acceptance criteria
- Write Playwright tests for technical coverage