Loading...
Loading...
Use when creating step definitions with Given, When, Then, using createBdd() for step functions, implementing Page Object Model patterns, and sharing fixtures between steps.
npx skill4agent add thebushidocollective/han playwright-bdd-step-definitionscreateBdd()// steps/common.steps.ts
import { createBdd } from 'playwright-bdd';
const { Given, When, Then } = createBdd();
Given('I am on the home page', async ({ page }) => {
await page.goto('/');
});
When('I click the login button', async ({ page }) => {
await page.getByRole('button', { name: 'Login' }).click();
});
Then('I should see the dashboard', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});import { createBdd } from 'playwright-bdd';
const { Given, When, Then } = createBdd();
Given('I am logged in as {string}', async ({ page, context }, username: string) => {
// Access page and context fixtures
await page.goto('/login');
await page.getByLabel('Username').fill(username);
await page.getByRole('button', { name: 'Login' }).click();
});
When('I take a screenshot', async ({ page }) => {
await page.screenshot({ path: 'screenshot.png' });
});
Then('the browser has {int} cookies', async ({ context }, count: number) => {
const cookies = await context.cookies();
expect(cookies).toHaveLength(count);
});// String parameter: {string}
Given('the user name is {string}', async ({}, name: string) => {
console.log(name); // "John"
});
// Integer parameter: {int}
When('I wait {int} seconds', async ({}, seconds: number) => {
await page.waitForTimeout(seconds * 1000);
});
// Float parameter: {float}
Then('the price is {float}', async ({}, price: number) => {
console.log(price); // 19.99
});
// Word parameter: {word}
Given('I am on the {word} page', async ({ page }, pageName: string) => {
await page.goto(`/${pageName}`);
});// Match any text in quotes
Given(/^I enter "(.*)" in the search box$/, async ({ page }, query: string) => {
await page.getByRole('searchbox').fill(query);
});
// Match numbers
When(/^I add (\d+) items to cart$/, async ({ page }, count: string) => {
const quantity = parseInt(count, 10);
for (let i = 0; i < quantity; i++) {
await page.getByRole('button', { name: 'Add to Cart' }).click();
}
});// steps/parameters.ts
import { defineParameterType } from 'playwright-bdd';
defineParameterType({
name: 'color',
regexp: /red|green|blue/,
transformer: (s) => s,
});
defineParameterType({
name: 'boolean',
regexp: /true|false/,
transformer: (s) => s === 'true',
});
defineParameterType({
name: 'date',
regexp: /\d{4}-\d{2}-\d{2}/,
transformer: (s) => new Date(s),
});// steps/ui.steps.ts
import { createBdd } from 'playwright-bdd';
import './parameters'; // Import parameter definitions
const { Given, When, Then } = createBdd();
When('I select the {color} theme', async ({ page }, color: string) => {
await page.getByRole('button', { name: color }).click();
});
Then('dark mode is {boolean}', async ({ page }, enabled: boolean) => {
if (enabled) {
await expect(page.locator('body')).toHaveClass(/dark/);
}
});// steps/fixtures.ts
import { test as base, createBdd } from 'playwright-bdd';
// Define fixture types
type TestFixtures = {
todoPage: TodoPage;
apiClient: ApiClient;
};
// Extend base test with fixtures
export const test = base.extend<TestFixtures>({
todoPage: async ({ page }, use) => {
const todoPage = new TodoPage(page);
await use(todoPage);
},
apiClient: async ({ request }, use) => {
const client = new ApiClient(request);
await use(client);
},
});
// Create BDD functions with custom test
export const { Given, When, Then } = createBdd(test);// steps/todo.steps.ts
import { Given, When, Then } from './fixtures';
Given('I have an empty todo list', async ({ todoPage }) => {
await todoPage.goto();
await todoPage.clearAll();
});
When('I add a todo {string}', async ({ todoPage }, text: string) => {
await todoPage.addTodo(text);
});
Then('I should see {int} todos', async ({ todoPage }, count: number) => {
await todoPage.expectTodoCount(count);
});// steps/fixtures.ts
import { test as base, createBdd } from 'playwright-bdd';
export const test = base.extend<{
authenticatedPage: Page;
}>({
authenticatedPage: async ({ page, context }, use) => {
// Setup: Login before test
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL('/dashboard');
// Use the authenticated page
await use(page);
// Teardown: Logout after test
await page.goto('/logout');
},
});
export const { Given, When, Then } = createBdd(test);// pages/TodoPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class TodoPage {
readonly page: Page;
readonly input: Locator;
readonly list: Locator;
readonly items: Locator;
constructor(page: Page) {
this.page = page;
this.input = page.getByPlaceholder('What needs to be done?');
this.list = page.getByRole('list');
this.items = page.getByTestId('todo-item');
}
async goto() {
await this.page.goto('/todos');
}
async addTodo(text: string) {
await this.input.fill(text);
await this.input.press('Enter');
}
async removeTodo(text: string) {
const item = this.items.filter({ hasText: text });
await item.hover();
await item.getByRole('button', { name: 'Delete' }).click();
}
async toggleTodo(text: string) {
const item = this.items.filter({ hasText: text });
await item.getByRole('checkbox').click();
}
async expectTodoCount(count: number) {
await expect(this.items).toHaveCount(count);
}
async expectTodoVisible(text: string) {
await expect(this.items.filter({ hasText: text })).toBeVisible();
}
async clearAll() {
const count = await this.items.count();
for (let i = count - 1; i >= 0; i--) {
await this.items.nth(i).hover();
await this.items.nth(i).getByRole('button', { name: 'Delete' }).click();
}
}
}// steps/fixtures.ts
import { test as base, createBdd } from 'playwright-bdd';
import { TodoPage } from '../pages/TodoPage';
import { LoginPage } from '../pages/LoginPage';
export const test = base.extend<{
todoPage: TodoPage;
loginPage: LoginPage;
}>({
todoPage: async ({ page }, use) => {
await use(new TodoPage(page));
},
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});
export const { Given, When, Then } = createBdd(test);// steps/todo.steps.ts
import { Given, When, Then } from './fixtures';
Given('I am on the todo page', async ({ todoPage }) => {
await todoPage.goto();
});
When('I add {string} to my todos', async ({ todoPage }, text: string) => {
await todoPage.addTodo(text);
});
When('I complete the todo {string}', async ({ todoPage }, text: string) => {
await todoPage.toggleTodo(text);
});
When('I remove the todo {string}', async ({ todoPage }, text: string) => {
await todoPage.removeTodo(text);
});
Then('I should see the todo {string}', async ({ todoPage }, text: string) => {
await todoPage.expectTodoVisible(text);
});
Then('there should be {int} todos', async ({ todoPage }, count: number) => {
await todoPage.expectTodoCount(count);
});// steps/TodoSteps.ts
import { Fixture, Given, When, Then } from 'playwright-bdd/decorators';
import { test } from './fixtures';
export
@Fixture<typeof test>('todoPage')
class TodoSteps {
@Given('I am on the todo page')
async gotoTodoPage() {
await this.todoPage.goto();
}
@When('I add {string} to my todos')
async addTodo(text: string) {
await this.todoPage.addTodo(text);
}
@Then('I should see {int} todos')
async checkTodoCount(count: number) {
await this.todoPage.expectTodoCount(count);
}
}// steps/AuthenticatedSteps.ts
import { Fixture, Given, When, Then } from 'playwright-bdd/decorators';
import { test } from './fixtures';
export
@Fixture<typeof test>('loginPage')
@Fixture<typeof test>('todoPage')
class AuthenticatedSteps {
@Given('I am logged in')
async login() {
await this.loginPage.login('user@example.com', 'password');
}
@When('I visit my todos')
async visitTodos() {
await this.todoPage.goto();
}
}When I add the following todos:
| Buy milk |
| Buy bread |
| Buy eggs |import { DataTable } from '@cucumber/cucumber';
When('I add the following todos:', async ({ todoPage }, table: DataTable) => {
const todos = table.raw().flat();
for (const todo of todos) {
await todoPage.addTodo(todo);
}
});When I create users:
| name | email | role |
| Alice | alice@test.com | admin |
| Bob | bob@test.com | user |When('I create users:', async ({ page }, table: DataTable) => {
const users = table.hashes(); // [{ name: 'Alice', email: '...', role: 'admin' }, ...]
for (const user of users) {
await page.getByLabel('Name').fill(user.name);
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Role').selectOption(user.role);
await page.getByRole('button', { name: 'Create' }).click();
}
});When I fill the form:
| Name | John Doe |
| Email | john@example.com|
| Password | secret123 |When('I fill the form:', async ({ page }, table: DataTable) => {
const data = table.rowsHash(); // { Name: 'John Doe', Email: '...', Password: '...' }
await page.getByLabel('Name').fill(data.Name);
await page.getByLabel('Email').fill(data.Email);
await page.getByLabel('Password').fill(data.Password);
});When I write the following review:
"""
This product is amazing!
I highly recommend it to everyone.
Five stars!
"""When('I write the following review:', async ({ page }, docString: string) => {
await page.getByRole('textbox', { name: 'Review' }).fill(docString);
});When I send the API request:
"""json
{
"name": "Test Product",
"price": 29.99,
"category": "electronics"
}
"""When('I send the API request:', async ({ request }, docString: string) => {
const data = JSON.parse(docString);
await request.post('/api/products', { data });
});// steps/fixtures.ts
import { test as base, createBdd } from 'playwright-bdd';
type World = {
currentUser?: { id: string; name: string };
createdItems: string[];
};
export const test = base.extend<{ world: World }>({
world: async ({}, use) => {
await use({
createdItems: [],
});
},
});
export const { Given, When, Then } = createBdd(test);// steps/user.steps.ts
import { Given, When, Then } from './fixtures';
Given('I am logged in as {string}', async ({ page, world }, name: string) => {
world.currentUser = { id: '123', name };
await page.goto('/login');
// ... login steps
});
When('I create an item {string}', async ({ world }, item: string) => {
world.createdItems.push(item);
// ... create item
});
Then('I should see my items', async ({ page, world }) => {
for (const item of world.createdItems) {
await expect(page.getByText(item)).toBeVisible();
}
});Given('a feature not yet implemented', async ({}) => {
throw new Error('Step not implemented');
});Given('I am on a supported browser', async ({ browserName }) => {
if (browserName === 'webkit') {
test.skip(true, 'Feature not supported on WebKit');
}
// Continue with test
});// Generic navigation step
Given('I am on the {string} page', async ({ page }, pageName: string) => {
const routes: Record<string, string> = {
home: '/',
login: '/login',
dashboard: '/dashboard',
settings: '/settings',
};
await page.goto(routes[pageName] || `/${pageName}`);
});
// Generic form fill step
When('I fill in {string} with {string}', async ({ page }, label: string, value: string) => {
await page.getByLabel(label).fill(value);
});
// Generic click step
When('I click {string}', async ({ page }, text: string) => {
await page.getByRole('button', { name: text }).or(
page.getByRole('link', { name: text })
).click();
});steps/
├── fixtures.ts # Shared fixtures
├── parameters.ts # Custom parameter types
├── common/
│ ├── navigation.steps.ts
│ └── forms.steps.ts
├── auth/
│ ├── login.steps.ts
│ └── logout.steps.ts
└── products/
├── catalog.steps.ts
└── cart.steps.tsimport { expect } from '@playwright/test';
Then('I should see {string}', async ({ page }, text: string) => {
await expect(page.getByText(text)).toBeVisible();
});
Then('the page title should be {string}', async ({ page }, title: string) => {
await expect(page).toHaveTitle(title);
});
Then('the URL should contain {string}', async ({ page }, path: string) => {
await expect(page).toHaveURL(new RegExp(path));
});