cucumber-step-definitions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCucumber Step Definitions
Cucumber 步骤定义
Master writing maintainable and reusable step definitions for Cucumber tests.
掌握为Cucumber测试编写可维护、可复用的步骤定义的方法。
Basic Step Definitions
基础步骤定义
Define steps that match Gherkin syntax:
编写匹配Gherkin语法的步骤:
JavaScript/TypeScript (Cucumber.js)
JavaScript/TypeScript (Cucumber.js)
javascript
const { Given, When, Then } = require('@cucumber/cucumber');
Given('I am on the login page', async function() {
await this.page.goto('/login');
});
When('I enter valid credentials', async function() {
await this.page.fill('#username', 'testuser');
await this.page.fill('#password', 'password123');
});
Then('I should be logged in', async function() {
const welcomeMessage = await this.page.textContent('.welcome');
expect(welcomeMessage).toContain('Welcome, testuser');
});javascript
const { Given, When, Then } = require('@cucumber/cucumber');
Given('I am on the login page', async function() {
await this.page.goto('/login');
});
When('I enter valid credentials', async function() {
await this.page.fill('#username', 'testuser');
await this.page.fill('#password', 'password123');
});
Then('I should be logged in', async function() {
const welcomeMessage = await this.page.textContent('.welcome');
expect(welcomeMessage).toContain('Welcome, testuser');
});Java (Cucumber-JVM)
Java (Cucumber-JVM)
java
import io.cucumber.java.en.*;
import static org.junit.Assert.*;
public class LoginSteps {
@Given("I am on the login page")
public void i_am_on_login_page() {
driver.get("http://example.com/login");
}
@When("I enter valid credentials")
public void i_enter_valid_credentials() {
driver.findElement(By.id("username")).sendKeys("testuser");
driver.findElement(By.id("password")).sendKeys("password123");
}
@Then("I should be logged in")
public void i_should_be_logged_in() {
String welcome = driver.findElement(By.className("welcome")).getText();
assertTrue(welcome.contains("Welcome, testuser"));
}
}java
import io.cucumber.java.en.*;
import static org.junit.Assert.*;
public class LoginSteps {
@Given("I am on the login page")
public void i_am_on_login_page() {
driver.get("http://example.com/login");
}
@When("I enter valid credentials")
public void i_enter_valid_credentials() {
driver.findElement(By.id("username")).sendKeys("testuser");
driver.findElement(By.id("password")).sendKeys("password123");
}
@Then("I should be logged in")
public void i_should_be_logged_in() {
String welcome = driver.findElement(By.className("welcome")).getText();
assertTrue(welcome.contains("Welcome, testuser"));
}
}Ruby
Ruby
ruby
Given('I am on the login page') do
visit '/login'
end
When('I enter valid credentials') do
fill_in 'username', with: 'testuser'
fill_in 'password', with: 'password123'
end
Then('I should be logged in') do
expect(page).to have_content('Welcome, testuser')
endruby
Given('I am on the login page') do
visit '/login'
end
When('I enter valid credentials') do
fill_in 'username', with: 'testuser'
fill_in 'password', with: 'password123'
end
Then('I should be logged in') do
expect(page).to have_content('Welcome, testuser')
endParameterized Steps
参数化步骤
Capture values from Gherkin steps:
javascript
// Scenario: I search for "Cucumber" in the search bar
When('I search for {string} in the search bar', async function(searchTerm) {
await this.page.fill('#search', searchTerm);
await this.page.click('#search-button');
});
// Scenario: I add 5 items to my cart
When('I add {int} items to my cart', async function(quantity) {
for (let i = 0; i < quantity; i++) {
await this.addItemToCart();
}
});
// Scenario: The price should be $99.99
Then('the price should be ${float}', async function(expectedPrice) {
const actualPrice = await this.page.textContent('.price');
expect(parseFloat(actualPrice)).toBe(expectedPrice);
});从Gherkin步骤中捕获值:
javascript
// 场景:我在搜索栏中搜索"Cucumber"
When('I search for {string} in the search bar', async function(searchTerm) {
await this.page.fill('#search', searchTerm);
await this.page.click('#search-button');
});
// 场景:我向购物车中添加5件商品
When('I add {int} items to my cart', async function(quantity) {
for (let i = 0; i < quantity; i++) {
await this.addItemToCart();
}
});
// 场景:价格应为$99.99
Then('the price should be ${float}', async function(expectedPrice) {
const actualPrice = await this.page.textContent('.price');
expect(parseFloat(actualPrice)).toBe(expectedPrice);
});Regular Expressions
正则表达式
Use regex for flexible matching:
javascript
// Matches: "I wait 5 seconds", "I wait 10 seconds"
When(/^I wait (\d+) seconds?$/, async function(seconds) {
await this.page.waitForTimeout(seconds * 1000);
});
// Matches: "I should see a success message", "I should see an error message"
Then(/^I should see (?:a|an) (success|error) message$/, async function(type) {
const message = await this.page.textContent(`.${type}-message`);
expect(message).toBeTruthy();
});使用正则表达式实现灵活匹配:
javascript
// 匹配:"I wait 5 seconds"、"I wait 10 seconds"
When(/^I wait (\d+) seconds?$/, async function(seconds) {
await this.page.waitForTimeout(seconds * 1000);
});
// 匹配:"I should see a success message"、"I should see an error message"
Then(/^I should see (?:a|an) (success|error) message$/, async function(type) {
const message = await this.page.textContent(`.${type}-message`);
expect(message).toBeTruthy();
});Data Tables
数据表格
Handle tabular data in steps:
javascript
When('I create a user with the following details:', async function(dataTable) {
// dataTable.hashes() converts to array of objects
const users = dataTable.hashes();
for (const user of users) {
await this.api.createUser({
firstName: user['First Name'],
lastName: user['Last Name'],
email: user['Email']
});
}
});
// Alternative: dataTable.raw() for raw 2D array
When('I select the following options:', async function(dataTable) {
const options = dataTable.raw().flat(); // ['Option1', 'Option2']
for (const option of options) {
await this.page.check(`input[value="${option}"]`);
}
});在步骤中处理表格数据:
javascript
When('I create a user with the following details:', async function(dataTable) {
// dataTable.hashes() 转换为对象数组
const users = dataTable.hashes();
for (const user of users) {
await this.api.createUser({
firstName: user['First Name'],
lastName: user['Last Name'],
email: user['Email']
});
}
});
// 替代方案:使用dataTable.raw()获取原始二维数组
When('I select the following options:', async function(dataTable) {
const options = dataTable.raw().flat(); // ['Option1', 'Option2']
for (const option of options) {
await this.page.check(`input[value="${option}"]`);
}
});Doc Strings
文档字符串
Handle multi-line text:
javascript
When('I submit a message:', async function(messageText) {
await this.page.fill('#message', messageText);
await this.page.click('#submit');
});处理多行文本:
javascript
When('I submit a message:', async function(messageText) {
await this.page.fill('#message', messageText);
await this.page.click('#submit');
});World Context
World上下文
Share state between steps using World:
javascript
const { setWorldConstructor, World } = require('@cucumber/cucumber');
class CustomWorld extends World {
constructor(options) {
super(options);
this.cart = [];
this.user = null;
}
async login(username, password) {
this.user = await this.api.login(username, password);
}
addToCart(item) {
this.cart.push(item);
}
}
setWorldConstructor(CustomWorld);
// Use in steps
Given('I am logged in', async function() {
await this.login('testuser', 'password');
});
When('I add an item to my cart', async function() {
this.addToCart({ id: 1, name: 'Product' });
});使用World在步骤之间共享状态:
javascript
const { setWorldConstructor, World } = require('@cucumber/cucumber');
class CustomWorld extends World {
constructor(options) {
super(options);
this.cart = [];
this.user = null;
}
async login(username, password) {
this.user = await this.api.login(username, password);
}
addToCart(item) {
this.cart.push(item);
}
}
setWorldConstructor(CustomWorld);
// 在步骤中使用
Given('I am logged in', async function() {
await this.login('testuser', 'password');
});
When('I add an item to my cart', async function() {
this.addToCart({ id: 1, name: 'Product' });
});Hooks
钩子函数
Set up and tear down test state:
javascript
const { Before, After, BeforeAll, AfterAll } = require('@cucumber/cucumber');
BeforeAll(async function() {
// Runs once before all scenarios
await startTestServer();
});
Before(async function() {
// Runs before each scenario
this.browser = await launchBrowser();
this.page = await this.browser.newPage();
});
Before({ tags: '@database' }, async function() {
// Runs only for scenarios with @database tag
await this.db.clear();
});
After(async function() {
// Runs after each scenario
await this.browser.close();
});
AfterAll(async function() {
// Runs once after all scenarios
await stopTestServer();
});设置和清理测试状态:
javascript
const { Before, After, BeforeAll, AfterAll } = require('@cucumber/cucumber');
BeforeAll(async function() {
// 在所有场景运行前执行一次
await startTestServer();
});
Before(async function() {
// 在每个场景运行前执行
this.browser = await launchBrowser();
this.page = await this.browser.newPage();
});
Before({ tags: '@database' }, async function() {
// 仅在带有@database标签的场景前执行
await this.db.clear();
});
After(async function() {
// 在每个场景运行后执行
await this.browser.close();
});
AfterAll(async function() {
// 在所有场景运行后执行一次
await stopTestServer();
});Step Organization
步骤组织
Page Object Pattern
页面对象模式
javascript
// pages/LoginPage.js
class LoginPage {
constructor(page) {
this.page = page;
}
async navigate() {
await this.page.goto('/login');
}
async fillCredentials(username, password) {
await this.page.fill('#username', username);
await this.page.fill('#password', password);
}
async submit() {
await this.page.click('#login-button');
}
}
// step-definitions/login-steps.js
const LoginPage = require('../pages/LoginPage');
Given('I am on the login page', async function() {
this.loginPage = new LoginPage(this.page);
await this.loginPage.navigate();
});
When('I enter {string} and {string}', async function(username, password) {
await this.loginPage.fillCredentials(username, password);
await this.loginPage.submit();
});javascript
// pages/LoginPage.js
class LoginPage {
constructor(page) {
this.page = page;
}
async navigate() {
await this.page.goto('/login');
}
async fillCredentials(username, password) {
await this.page.fill('#username', username);
await this.page.fill('#password', password);
}
async submit() {
await this.page.click('#login-button');
}
}
// step-definitions/login-steps.js
const LoginPage = require('../pages/LoginPage');
Given('I am on the login page', async function() {
this.loginPage = new LoginPage(this.page);
await this.loginPage.navigate();
});
When('I enter {string} and {string}', async function(username, password) {
await this.loginPage.fillCredentials(username, password);
await this.loginPage.submit();
});Helper Functions
辅助函数
javascript
// support/helpers.js
async function waitForElement(page, selector, timeout = 5000) {
await page.waitForSelector(selector, { timeout });
}
async function takeScreenshot(page, name) {
await page.screenshot({ path: `screenshots/${name}.png` });
}
module.exports = { waitForElement, takeScreenshot };
// Use in steps
const { waitForElement } = require('../support/helpers');
Then('I should see the dashboard', async function() {
await waitForElement(this.page, '.dashboard');
});javascript
// support/helpers.js
async function waitForElement(page, selector, timeout = 5000) {
await page.waitForSelector(selector, { timeout });
}
async function takeScreenshot(page, name) {
await page.screenshot({ path: `screenshots/${name}.png` });
}
module.exports = { waitForElement, takeScreenshot };
// 在步骤中使用
const { waitForElement } = require('../support/helpers');
Then('I should see the dashboard', async function() {
await waitForElement(this.page, '.dashboard');
});Best Practices
最佳实践
- Keep steps simple and focused - One action or assertion per step
- Reuse steps - Write generic steps that work for multiple scenarios
- Avoid implementation details - Don't expose internal structure in step names
- Use the World - Share state through World, not global variables
- Organize by domain - Group related steps together
- Don't duplicate logic - Extract common functionality to helpers
- Make steps readable - Step definitions should read like documentation
- Handle async properly - Use async/await consistently
- 保持步骤简洁且聚焦 - 每个步骤对应一个操作或断言
- 复用步骤 - 编写可在多个场景中使用的通用步骤
- 避免暴露实现细节 - 步骤名称中不要体现内部结构
- 使用World - 通过World共享状态,而非全局变量
- 按领域组织 - 将相关步骤分组
- 不要重复逻辑 - 将通用功能提取到辅助函数中
- 保持步骤可读性 - 步骤定义应像文档一样易懂
- 正确处理异步 - 始终一致地使用async/await
Anti-Patterns to Avoid
需要避免的反模式
❌ Don't create overly specific steps:
javascript
Given('I am on the login page as a premium user with valid credentials')✅ Create composable steps:
javascript
Given('I am on the login page')
And('I am a premium user')
And('I have valid credentials')❌ Don't put assertions in Given/When:
javascript
When('I click login and see the dashboard')✅ Separate actions and assertions:
javascript
When('I click login')
Then('I should see the dashboard')❌ Don't use steps as functions:
javascript
// Don't call steps from within steps
When('I log in', async function() {
await this.Given('I am on the login page'); // Bad!
await this.When('I enter credentials'); // Bad!
});✅ Extract to helper functions:
javascript
// support/auth-helpers.js
async function login(world, username, password) {
await world.page.goto('/login');
await world.page.fill('#username', username);
await world.page.fill('#password', password);
await world.page.click('#login-button');
}
// Use in steps
When('I log in', async function() {
await login(this, 'user', 'pass');
});Remember: Step definitions are the glue between readable scenarios and automation code. Keep them clean, maintainable, and focused.
❌ 不要创建过于具体的步骤:
javascript
Given('I am on the login page as a premium user with valid credentials')✅ 创建可组合的步骤:
javascript
Given('I am on the login page')
And('I am a premium user')
And('I have valid credentials')❌ 不要在Given/When中加入断言:
javascript
When('I click login and see the dashboard')✅ 分离操作和断言:
javascript
When('I click login')
Then('I should see the dashboard')❌ 不要将步骤当作函数调用:
javascript
// 不要在步骤内部调用其他步骤
When('I log in', async function() {
await this.Given('I am on the login page'); // 错误!
await this.When('I enter credentials'); // 错误!
});✅ 提取到辅助函数中:
javascript
// support/auth-helpers.js
async function login(world, username, password) {
await world.page.goto('/login');
await world.page.fill('#username', username);
await world.page.fill('#password', password);
await world.page.click('#login-button');
}
// 在步骤中使用
When('I log in', async function() {
await login(this, 'user', 'pass');
});记住:步骤定义是可读场景与自动化代码之间的纽带。要保持其简洁、可维护且聚焦。