【免费下载链接】cucumber-js
Cucumber for JavaScript
Cucumber-js 是 Cucumber 官方 JavaScript 实现,它让你用团队任何人都能读懂的"自然语言"(Gherkin 语法)编写自动化测试,同时用 JavaScript 编写对应的步骤定义(Step Definitions),把"需求描述"和"测试代码"无缝连接起来。本指南将以官方 README 的快速上手路径为主线,完整覆盖从安装、编写第一个 Feature 到配置、执行、报告、并行与扩展的实战全流程,读完即可在本地项目里落地一套 BDD 测试体系。
什么是 Cucumber-js:面向团队协作的行为驱动测试
Cucumber 的核心思想是:用自然语言编写自动化测试。因为这些测试用自然语言写成,团队里的任何人都能读懂;因为人人都能读懂,它们就能帮助改善团队的沟通、协作与信任——产品、开发、测试可以围绕同一份"可执行的规格说明"对齐。
Cucumber-js 是 Cucumber 的 JavaScript 实现,运行在 Node.js 的受维护版本 目录下也存放了大量覆盖各功能的 .feature 验收测试,是绝佳的参考样本。
从仓库结构看,Cucumber-js 的架构大体分为几层:
- src/api:对外暴露的 JavaScript API(加载配置、加载源码、执行测试运行);
- src/configuration:配置文件解析、合并、校验与默认值;
- src/support_code_library_builder:收集你注册的步骤定义、Hooks、参数类型与 World;
- src/runtime:串行/并行执行测试用例的运行时;
- src/formatter:内置的各种输出格式(progress、summary、html、message 等)。
安装与常见坑
Cucumber-js 发布在 npm 上,包名为 @cucumber/cucumber(注意不是旧的 cucumber)。使用 npm 安装:
npm install @cucumber/cucumber
也可以使用 Yarn:
yarn add @cucumber/cucumber
安装后会提供 cucumber-js 可执行文件(见 package.json 中 bin 字段指向 bin/cucumber.js)。当前版本要求 Node.js 22 || 24 || >=26(见 package.json 的 engines 字段)。
无效安装:多实例问题
Cucumber 与其他多数库不同,它是有状态(stateful)的:你调用 Given/When/Then 等函数来注册支持代码(support code),这些状态会一直保留到测试运行时使用。因此,项目里所有与 Cucumber 交互的代码必须操作同一个实例。如果出现类似下面的报错:
You're calling functions (e.g. "Given") on an instance of Cucumber that isn't running.
This means you have an invalid installation, mostly likely due to:
...
说明存在无效安装。常见成因与解法(详见 docs/installation.md):
- 全局安装:Cucumber 不适合全局安装,必须作为项目依赖。若在 debug 模式 下检测到全局安装,会发出警告。
- 重复依赖:项目中某个第三方库自身也依赖了
@cucumber/cucumber(可能版本不同),导致多个实例同时存在——这与 React 的 "invalid hook call" 问题类似。可用npm why @cucumber/cucumber诊断。解决方式是把共享库中对@cucumber/cucumber的依赖改为 peer dependency;若库不受你控制,可用 npm 的 overrides 或 Yarn 的 resolutions 收敛到单一实例。 - 废弃包:检查是否有代码依赖了旧的、已废弃的
cucumber包,所有 Cucumber 相关代码都应改用@cucumber/cucumber(参考 UPGRADING.md)。 - Linking:即使正确使用了 peer dependency,开发时用
npm link/yarn link关联本地共享库也可能触发该问题,可从宿主项目执行npm link ../my-project/node_modules/@cucumber/cucumber绕过。
早期版本中该问题表现为更隐晦的错误(原因与解法相同):
TypeError [ERR_INVALID_ARG_TYPE]: The "from" argument must be of type string. Received type undefined
at validateString (internal/validators.js:125:11)
...
快速上手:从零写出第一个通过的测试
官方 README 用一个经典的 Greeter 例子演示了完整的 BDD 流程:先写业务代码,再写 Feature(自然语言),再实现步骤定义,最后运行。
第一步:编写业务代码 src/index.js
class Greeter {
sayHello() {
return 'hello'
}
}
module.exports = {
Greeter
}
第二步:编写 Feature 文件 features/greeting.feature
Feature: Greeting
Scenario: Say hello
When the greeter says hello
Then I should have heard "hello"
第三步:实现步骤定义 features/support/steps.js
const assert = require('assert')
const { When, Then } = require('@cucumber/cucumber')
const { Greeter } = require('../../src')
When('the greeter says hello', function () {
this.whatIHeard = new Greeter().sayHello()
})
Then('I should have heard {string}', function (expectedResponse) {
assert.equal(this.whatIHeard, expectedResponse)
})
第四步:运行
npx cucumber-js
运行成功后的终端输出大致如下(1 个场景、2 个步骤全部通过):
这个例子背后发生的事值得拆解:npx cucumber-js 启动 CLI(入口为 src/cli/run.ts)→ 按默认 glob features/**/*.{feature,feature.md} 找到 feature 文件 → 由 Gherkin 解析器把场景编译为 Pickle → 加载 features/support/** 下的支持代码 → 用 src/runtime 逐场景执行。若你更喜欢看完整示例工程,官方维护了 cucumber-js-examples 示例仓库可参考。
核心概念:Feature、步骤定义、World 与 Hooks
快速上手之后,你需要理解支撑 Cucumber 的四个核心概念。
Feature 文件与 Gherkin
Feature 文件以 .feature 或 .feature.md 结尾,默认放在 features/ 目录,内容使用 Gherkin 语法(Feature、Scenario、Given/When/Then/And/But 以及 Scenario Outline、Rule 等)。仓库自己的验收测试(如 features/core.feature、features/rule.feature)就是真实可运行的 Gherkin 示例。Cucumber-js 还支持通过 language 配置项指定非英语方言(对应 CLI --language 选项,默认 en)。
步骤定义:Cucumber Expressions 与正则表达式
步骤定义(Given/When/Then)是 Gherkin 文本与实际测试实现之间的"胶水"。Cucumber 支持两种匹配表达式(完整接口见 docs/support_files/step_definitions.md)。
Cucumber Expressions(推荐):
Given I have 42 cucumbers in my belly
import { Given } from '@cucumber/cucumber';
Given('I have {int} cucumbers in my belly', function (cucumberCount) {
assert.equal(this.responseStatus, cucumberCount)
});
{int} 会把捕获到的字符串自动转换成数字传入步骤函数。除 {int} 外还有内置的 float、string(自动去除引号)、word 等参数类型,你也可以用 defineParameterType 自定义(见下文 API 参考)。
正则表达式:正则中的捕获组会按顺序作为参数传给步骤函数:
const { Then, When } = require('@cucumber/cucumber');
const assert = require('assert');
const fs = require('fs');
const seleniumWebdriver = require('selenium-webdriver');
// 同步
Then(/^the response status is (.*)$/, function (status) {
assert.equal(this.responseStatus, status)
});
// 异步 - 回调风格:最后一个参数是 callback,完成后调用
Then(/^the file named (.*) is empty$/, function (fileName, callback) {
fs.readFile(fileName, 'utf8', function(error, contents) {
if (error) {
callback(error);
} else {
assert.equal(contents, '');
callback();
}
});
});
// 异步 - Promise 风格:返回 Promise,resolve/reject 即步骤完成
When(/^I view my profile$/, function () {
return this.driver.findElement({ css: '.profile-link' }).then(function(element) {
return element.click();
});
});
步骤定义函数支持同步、回调、Promise 三种接口;箭头函数无法以 this 引用 World,需要改用 world 代理对象(详见下文 World 一节)。
World:每个场景的隔离上下文
World 是每个场景独立的隔离作用域,通过 this 暴露给步骤与大部分 Hooks。你可以在一个步骤里设置变量,在后面的步骤里读取;场景结束时所有变量随之丢弃。每个场景开始时都会创建全新的 World 实例,即使该场景被重试(Retry)也会重新创建。BeforeAll/AfterAll 运行于任何场景之外,因此拿不到 World。
const { Given, Then } = require('@cucumber/cucumber')
Given("my color is {string}", function(color) {
this.color = color
})
Then("my color should not be red", function() {
if (this.color === "red") {
throw new Error("Wrong Color");
}
});
内置的 World 类会向构造函数传入 attach(附件)、log(日志)、link(链接)、parameters(世界参数)等帮助方法,默认绑定到 this 上(详见 docs/support_files/world.md)。你可以通过 setWorldConstructor 传入自定义 World 类(推荐继承内置 World),并利用 worldParameters 配置项注入 appUrl、browser 之类的运行参数:
- 配置文件
{ worldParameters: { appUrl: 'http://localhost:3000/' } } - CLI
cucumber-js --world-parameters '{"appUrl":"http://localhost:3000/"}'
从 v10.8.0 起,箭头函数中可以用导出的 world 代理对象访问 World;从 v10.1.0 起 BeforeAll/AfterAll 也能通过 this.parameters 读取世界参数(详见 docs/support_files/world.md)。
Hooks:场景生命周期钩子
Hooks 用于在每个场景前后做环境搭建与清理(详见 docs/support_files/hooks.md)。多个 Before 按定义顺序执行,多个 After 按定义顺序的逆序执行。支持同步、回调、Promise 三种风格:
const {After, Before} = require('@cucumber/cucumber');
// 同步
Before(function () {
this.count = 0;
});
// 异步回调
Before(function (testCase, callback) {
var world = this;
tmp.dir({unsafeCleanup: true}, function(error, dir) {
if (error) {
callback(error);
} else {
world.tmpDir = dir;
callback();
}
});
});
// 异步 Promise
After(function () {
return this.driver.quit();
});
Hooks 支持丰富的能力:
- 命名 Hooks(v8.1.0+):
Before({name: "Set up some test state"}, function () {...}),名称会出现在 formatter 输出中便于理解测试; - 标签化 Hooks:
Before({tags: "@foo and @bar"}, function () {...}),用 tag expressions 按场景标签条件执行,也支持字符串简写Before("@foo", ...); - 跳过场景:在
Before中返回'skipped'(或回调/Promise解析为'skipped')即可命令式跳过后续场景; - BeforeAll / AfterAll:所有场景前/后的全局设置,在并行模式下默认每个 worker 执行一次,也可用
{on: HookTarget.COORDINATOR}让钩子只在协调进程执行一次(v13.2.0+,用于启动共享服务器、灌数据库等场景); - BeforeStep / AfterStep:每个步骤前后的钩子,同样支持标签条件,
AfterStep可通过{result}判断Status.FAILED并在失败时截图。
完整的 API 签名(含参数对象结构)见 docs/support_files/api_reference.md,所有方法都可以从 require('@cucumber/cucumber') 解构得到——这一点在 src/index.ts 中可以看到 After、Before、Given、setWorldConstructor、defineParameterType、setParallelCanAssign、Status、World、world 等导出都来自 src/support_code_library_builder 的 methods。
配置:配置文件与全部选项
Cucumber-js 会把"找 Feature、找支持代码、选格式、并行度"等行为集中到一份配置里(详见 docs/configuration.md)。
配置文件与查找顺序
Cucumber 会在项目根目录依次查找以下文件,使用第一个找到的:
cucumber.jsoncucumber.yamlcucumber.ymlcucumber.jscucumber.cjscucumber.mjs
也可以用 --config 指定其他位置:
cucumber-js --config config/cucumber.json
JSON 格式示例:
{
"default": {
"parallel": 2,
"format": ["html:cucumber-report.html"]
}
}
YAML 格式示例:
default:
parallel: 2
format:
- "html:cucumber-report.html"
JavaScript(ESM)格式示例:
export default {
parallel: 2,
format: ['html:cucumber-report.html']
}
JavaScript(CommonJS)格式示例:
module.exports = {
default: {
parallel: 2,
format: ['html:cucumber-report.html']
}
}
Cucumber 也支持把配置写成 CLI 风格的字符串(不推荐):
module.exports = {
default: '--parallel 2 --format html:cucumber-report.html'
}
配置项之所以放在 default 键下,是为了支持 Profiles(配置组合)。此外,你还可以写 .ts/.mts/.cts 后缀的 TypeScript 配置文件,它由 Node.js 内置 TypeScript 支持加载(不会读取你的 tsconfig.json,类型导入需显式使用 import type):
import type { IConfiguration } from '@cucumber/cucumber'
export default {
parallel: 2,
format: ['html:cucumber-report.html']
} satisfies Partial<IConfiguration>
全部配置选项速查表
以下选项既可用于配置文件,也可用于 CLI,或两者同时使用(详见 docs/configuration.md):
| 名称 | 类型 | 可重复 | CLI 选项 | 说明 | 默认值 |
|---|---|---|---|---|---|
paths | string[] | 是 | (作为参数) | Feature 文件路径 | [] |
backtrace | boolean | 否 | --backtrace, -b | 显示错误的完整堆栈 | false |
dryRun | boolean | 否 | --dry-run, -d | 只准备测试运行但不执行 | false |
forceExit | boolean | 否 | --exit, --force-exit | 测试运行结束后显式调用 process.exit() | false |
failFast | boolean | 否 | --fail-fast | 测试失败即停止 | false |
format | string[] | 是 | --format, -f | 每个要使用的 formatter 的名称/路径与(可选)输出文件 | [] |
formatOptions | object | 是 | --format-options | 传给 formatter 的选项 | {} |
import | string[] | 是 | --import, -i | 支持代码路径(ESM 加载) | [] |
language | string | 否 | --language | Feature 文件默认语言 | en |
loader | string[] | 是 | --loader, -l | 加载支持代码前注册的 loader 模块 | [] |
name | string | 否 | --name | 按场景名称正则过滤 | [] |
order | string | 否 | --order | 按定义顺序或随机顺序运行 | defined |
parallel | number | 否 | --parallel | 用给定数量的 worker 并行运行 | 0 |
plugin | string[] | 是 | --plugin | 要加载的插件路径或包名 | [] |
pluginOptions | object | 是 | --plugin-options | 传给插件的选项 | {} |
publish | boolean | 否 | --publish | 发布报告到 Cucumber Reports | false |
require | string[] | 是 | --require, -r | 支持代码路径(CommonJS 加载) | [] |
requireModule | string[] | 是 | --require-module | 用 require() 加载的转译模块名 | [] |
retry | number | 否 | --retry | 失败测试最多重试次数 | 0 |
retryTagFilter | string | 是 | --retry-tag-filter | 可重试场景的标签表达式 | (空) |
shard | string | 否 | --shard | 跨多次运行拆分场景子集 | (空) |
strict | boolean | 否 | --strict, --no-strict | 有 pending 步骤则测试运行失败 | true |
tags | string | 是 | --tags, -t | 过滤运行场景的标签表达式 | (空) |
worldParameters | object | 是 | --world-parameters | 传给 World 的参数 | {} |
规则:可重复选项多次提供时会被追加/合并;不可重复选项以 CLI 为准(CLI 优先于配置文件)。上述默认值可在 src/configuration/default_configuration.ts 中逐一印证(backtrace: false、dryRun: false、parallel: 0、strict: true、order: 'defined' 等)。
查找 Feature 与支持代码
默认情况下,Cucumber 按以下 glob 查找 Feature(相对项目根目录):
features/**/*.{feature,feature.md}
如需覆盖,可在配置中指定 paths 或直接在 CLI 传入 glob/目录:
- 配置文件
{ paths: ['somewhere-else/**/*.feature'] } - CLI
cucumber-js 'somewhere-else/**/*.feature'(注意要加引号,避免 shell 自行展开 glob 导致行为不同)
支持代码(步骤定义等)默认逻辑是:
- 若 Feature 位于
features目录(任意层级):features/**/*.@(js|cjs|mjs) - 否则:为每个包含所选 Feature 的目录加载
<DIR>/**/*.@(js|cjs|mjs)
可用 import 选项覆盖(指定后默认规则不再生效,可重复、多值合并):
- 配置文件
{ import: ['somewhere-else/support/*.js'] } - CLI
cucumber-js --import somewhere-else/support/*.js
import 走 Node.js 新版 ESM API 加载;require 走旧版 CommonJS API(例如与 require-module 转译配合时可能需要改用 require)。
CLI:运行方式与专属选项
安装 @cucumber/cucumber 后即可运行可执行文件(详见 docs/cli.md)。三种方式:
# 直接调用
./node_modules/.bin/cucumber-js
// 放进 package.json scripts
{
"scripts": {
"cucumber": "cucumber-js"
}
}
# 用 npx
npx cucumber-js
所有标准配置选项都可以通过 CLI 提供,此外还有几个 CLI 专属选项:
| 选项 | 类型 | 可重复 | 说明 |
|---|---|---|---|
--config, -c | string | 否 | 配置文件路径 |
--profile, -p | string[] | 是 | 要合并的配置 Profile |
--version, -v | boolean | 否 | 打印当前版本后退出 |
--i18n-keywords | string | 否 | 打印指定 ISO-639-1 语言码的 Gherkin 关键字后退出 |
--i18n-languages | boolean | 否 | 打印 Gherkin 支持的语言列表后退出 |
查看当前版本的全部选项:
cucumber-js --help
退出行为:默认 Cucumber 在事件循环清空后退出。若测试运行结束后进程仍挂起,可用 forceExit 强制关闭事件循环(配置文件 { forceExit: true },CLI cucumber-js --force-exit)。官方不鼓励依赖此选项,建议排查导致挂起的根因(可用 Node.js inspector、why-is-node-running 等工具定位)。
报告与反馈:内置 Formatters
Formatters 实时消费测试运行数据并输出到终端或文件(某些框架称之为 "reporters",详见 docs/formatters.md)。通过 format 配置项指定一个或多个格式:
- 配置文件
{ format: ['progress-bar', ['html', 'cucumber-report.html']] } - CLI
cucumber-js --format progress-bar --format "html":"cucumber-report.html"
每个格式需要一或两个值:第一个(必填)用于标识 formatter,可以是内置 formatter 名称(如 progress-bar)、npm 包名(如 @cucumber/pretty-formatter)、本地实现相对路径(./my-custom-formatter.js)或 file:// 绝对路径;第二个(可选)是输出文件路径,若目录不存在会自动创建。不指定第二个值时输出到 stdout。CLI 中名称与路径用 : 分隔并各自加双引号;配置文件推荐用数组形式 ["html", "cucumber-report.html"]。
注意:多个 formatter 输出到同一目标时只有最后一个生效;若没有为 stdout 指定 formatter,默认使用 progress。
formatter 选项与主题
通过 formatOptions 传入(可重复、多次合并,后值优先)。终端 formatter(summary、progress、progress-bar、pretty)通用选项:
includeAttachments:设为false时不包含附件输出(默认true)theme:定制终端输出样式
theme 的键值大多是 Node.js util.styleText 可接受的样式名数组(bold、italic、red、magenta、bgYellow 等),每个键都可选,只覆盖想改的部分:
{
"formatOptions": {
"theme": {
"affix": ["dim"],
"attachment": ["cyan"],
"dataTable": { "all": [], "border": ["dim"], "content": [] },
"docString": { "all": [], "content": [], "delimiter": ["dim"], "mediaType": ["dim"] },
"feature": { "all": [], "keyword": ["bold"], "name": [] },
"location": ["dim"],
"rule": { "all": [], "keyword": ["bold"], "name": [] },
"scenario": { "all": [], "keyword": ["bold"], "name": [] },
"status": {
"all": { "PASSED": ["green"], "FAILED": ["red"] },
"icon": { "PASSED": "✔", "FAILED": "✘" },
"progress": { "PASSED": ".", "FAILED": "F" }
},
"step": { "argument": [], "keyword": ["bold"], "text": [] },
"symbol": { "bullet": "•" },
"tag": ["cyan"]
}
}
}
某些 formatter 有专属选项,需要放在与 formatter 同名的键下:{ formatOptions: { pretty: { useStatusIcon: false } } }。
颜色输出
内置 formatter 默认会输出彩色文本,Node.js 会检测底层流是否支持颜色。可用 FORCE_COLOR=1 强制开启、FORCE_COLOR=0 强制关闭(跨工具标准,也会影响断言库等其他工具)。旧的 colorsEnabled format 选项已废弃(见 docs/deprecations.md)。
内置 formatter 一览
summary:输出测试运行结果摘要。全部通过时简短干净;有问题时会给出失败 Hook/步骤的错误与堆栈、pending 步骤位置,以及实现未定义步骤的 Snippets。progress:在结束时与 summary 相同,同时每个步骤/Hook 完成时给出简洁的实时反馈(./F等字符)。progress-bar:类似 progress,但提供基于待执行步骤总数的实时进度条。pretty(v12.1.0+,也可作为@cucumber/pretty-formatter包引用):输出场景与示例执行过程的富文本报告,额外支持includeFeatureLine、includeRuleLine、useStatusIcon三个选项(默认均true)。html:生成独立可交互的 HTML 报告页面,可查看错误与堆栈、内联渲染附件、按状态过滤、按关键字或标签表达式搜索。默认附件以内嵌方式打包(文件完全自包含但可能很大),也可通过formatOptions.html.externalAttachments改为外置附件文件(支持["image/*", "video/*"]这类模式,log/link 附件永不外置)。message:输出整个测试运行的 Cucumber Messages(换行分隔的 JSON),可被其他工具消费,是结构化数据表示的首选。json:输出旧版 JSON 格式报告。该 formatter 处于维护模式,不会新增功能,建议迁移到message。junit:输出 JUnit XML 格式,便于 CI 平台接入;可通过formatOptions.junit.suiteName设置testsuite的name属性(默认cucumber-js)。snippets:只打印实现未定义步骤的 Snippets,方便一次性收集待实现代码。usage:列出步骤定义及其使用情况(含每次耗时与未使用的步骤):
┌───────────────────────────────────────┬──────────┬─────────────────────────────────┐
│ Pattern / Text │ Duration │ Location │
├───────────────────────────────────────┼──────────┼─────────────────────────────────┤
│ an empty todo list │ 760.33ms │ support/steps/steps.ts:6 │
│ an empty todo list │ 820ms │ features/empty.feature:4 │
│ ... │ ... │ ... │
│ I remove the todo {string} │ UNUSED │ support/steps/steps.ts:33 │
└───────────────────────────────────────┴──────────┴─────────────────────────────────┘
usage-json:与 usage 相同但输出 JSON,可落盘给其他工具消费。
此外还有 rerun formatter(见下节 Rerun)与 rerun 文件机制配合使用。
测试选择与执行策略
过滤与排序
开发/调试时经常只想跑一个子集(详见 docs/filtering.md):
- 按路径:
{ paths: ['features/my_feature.feature'] }或cucumber-js features/my_feature.feature;也可以用features/my_feature.feature:3精确定位某一行(对应行内的场景)。可重复,多值合并。 - 按名称:
{ name: ['^start.+end$'] }或cucumber-js --name "^start.+end$";场景名特殊字符需转义(如\(Scenario Name\))。可重复,命中任一表达式即运行。 - 按标签:
{ tags: '@foo or @bar' }或cucumber-js --tags "@foo or @bar",使用 Cucumber 标签表达式。可重复,多次提供会被and组合(场景需命中全部)。 - 排序:
order默认为defined(按发现顺序,大致为文件路径字母序 + 文件内顺序;显式传入多个 glob/path 时会遵守给定顺序);可设为random随机打乱,也可带种子如random:234119。
并行执行(Parallel)
Cucumber 支持并行运行场景:主进程作为 "coordinator"(协调者)派生多个 worker 线程(详见 docs/parallel.md):
- 配置文件
{ parallel: 3 } - CLI
cucumber-js --parallel 3
每个 worker 会额外收到 CUCUMBER_PARALLEL=true、CUCUMBER_TOTAL_WORKERS(worker 总数)、CUCUMBER_WORKER_ID(如 '0'/'1'/'2')三个环境变量。并行时 summary 最后一行会区分真实耗时与步骤累计耗时:
73 scenarios (73 passed)
512 steps (512 passed)
0m51.627s (executing steps: 4m51.228s)
worker 以线程形式运行在主进程内,可用 workerOptions 透传给 Node.js Worker 构造器(如设置资源上限):
{
"parallel": 4,
"workerOptions": {
"resourceLimits": {
"maxOldGenerationSizeMb": 2048
}
}
}
并行时 BeforeAll/AfterAll 默认每个 worker 执行一次;如需只跑一次,用 {on: HookTarget.COORDINATOR}。若需限制某些场景不能并行,可用 setParallelCanAssign(fn) 自定义分配规则,官方还提供了 parallelCanAssignHelpers.atMostOnePicklePerTag(['@tag1', '@tag2']) 之类的便捷助手:
setParallelCanAssign(function (pickleInQuestion, picklesInProgress) {
// 名字中含 example 的 pickle 同时只能运行一个
if (pickleInQuestion.name.includes('example')) {
return picklesInProgress.every((p) => !p.name.includes('example'))
}
return true
})
自定义 Formatter 还可以通过 testCaseStarted 信封对象上的 workerId 属性区分消息来自哪个 worker。
重试不稳定场景(Retry)
若某场景偶发失败(比如 10% 概率),可用 Retry 让 Cucumber 多次尝试直到通过或达到上限(详见 docs/retry.md):
- 配置文件
{ retry: 1 } - CLI
cucumber-js --retry 1
数字表示首次失败后允许的重试次数。要点:
- 只重试失败的相关场景,而非整个运行;
- 重试时会用全新的 World 从零执行全部 Hooks 与步骤,失败尝试不保留任何状态;
- 重试后通过则整体计为通过,但每次尝试的细节都会发出,供 formatter 使用。
配合 retryTagFilter 标签表达式可只针对特定场景重试({ retry: 1, retryTagFilter: '@flaky' } 或 cucumber-js --retry 1 --retry-tag-filter @flaky)。Retry 不建议日常使用,仅作为"无法修复但价值高"的权衡手段。
只重跑失败(Rerun)
TDD 工作流(跑全部 → 修失败 → 只跑失败 → 循环)可以用 rerun formatter 自动化(详见 docs/rerun.md)。先启用:
- 配置文件
{ format: ['rerun:@rerun.txt'] } - CLI
cucumber-js --format rerun:@rerun.txt
输出文件名必须以 @ 开头(这样 cucumber-js 才能与 feature 文件区分),并记得加入 .gitignore。文件内容形如:
features/adding.feature:3:19
features/editing.feature:8
之后只需 cucumber-js @rerun.txt 即可只跑上次失败的那些场景;每轮通过后失败条目会自动从文件中消失,直到文件清空(指向空 rerun 文件时不会运行任何场景)。默认条目以换行分隔,可用 formatOptions.rerun.separator 修改(注意解析器目前只支持默认分隔符)。
跨机器分片(Sharding)
Sharding(v12.2.0+)把整套测试拆到多个完全独立的测试运行中,适合多台机器或多个 CI Job 并行(区别于单运行内的 Parallel,详见 docs/sharding.md):
- 配置文件
{ shard: '1/3' } - CLI
cucumber-js --shard 1/3
格式为 INDEX/TOTAL(INDEX 从 1 开始)。例如 90 个场景、3 个分片,则每个分片跑 30 个:1/3 跑第 1、4、7… 个,2/3 跑第 2、5、8… 个,3/3 跑第 3、6、9… 个。常见 CI 用法(GitHub Actions):
jobs:
test:
strategy:
matrix:
shard: [1, 2, 3]
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test -- --shard ${{ matrix.shard }}/3
Sharding 与 Parallel 可以组合:先分片到多台机器,再在每台机器上用 Parallel 用满多核。
快速失败(Fail Fast)
默认 Cucumber 会跑完整个套件并汇报所有失败。failFast 让测试失败即停止,适合"一次只修一个失败"的流程(详见 docs/fail_fast.md):
- 配置文件
{ failFast: true } - CLI
cucumber-js --fail-fast
与 Retry 配合时:重试耗尽后仍失败才算失败;重试后通过则算通过,所以 failFast 不会阻止 Retry 发生。与 rerun 文件结合可以高效地逐批处理失败。
干跑(Dry Run)
dryRun 模式会完成所有聚合工作(解析 feature、加载支持代码等)但不实际执行测试(详见 docs/dry_run.md):
- 配置文件
{ dryRun: true } - CLI
cucumber-js --dry-run
具体行为:不执行任何 Hooks;步骤报告为 "skipped";未定义与歧义步骤会被报告但不会导致进程失败。典型用途:用 usage formatter 找出未使用的步骤定义、用 snippets formatter 批量生成未定义步骤的 Snippets、核对路径/标签表达式命中的场景是否符合预期。
扩展能力:插件、自定义 Formatter 与 JavaScript API
插件系统
从 v12.5.0 起,Cucumber 的功能可通过插件扩展(详见 docs/plugins.md)。插件可以监听 Cucumber 的消息流、钩住核心行为做修改、向用户输出额外信息。通过 plugin 配置项指定(可重复):
- 配置文件
{ plugin: ['@my-org/some-plugin', '@my-org/other-plugin'] } - CLI
cucumber-js --plugin @my-org/some-plugin --plugin @my-org/other-plugin
插件的最简形态是默认导出一个带 type: 'plugin' 与 coordinator 函数的对象:
export default {
type: 'plugin',
coordinator: (context) => {
// do stuff here
}
}
插件函数接收的 context 包含:operation(正在运行的 Cucumber 操作)、on(event, handler)(注册事件处理器)、transform(event, handler)(注册转换器)、options(用户配置的选项)、logger(输出到 stderr 的迷你日志器)、environment(当前环境属性)。生命周期上,插件在配置解析后不久初始化(可做异步设置),测试运行结束后、Cucumber 退出前停止(返回的 cleanup 函数会在退出前执行):
export default {
type: 'plugin',
coordinator: async (context) => {
await doSomeAsyncSetup()
return async () => {
await doSomeAsyncTeardown()
}
}
}
可监听的事件有 message(每个 Cucumber Message)、paths:resolve(路径解析完成)、publish:url(发布报告占位成功)。可注册的转换有 pickles:filter 与 pickles:order,用于过滤/排序将要运行的 Pickle。若插件需要专属配置,可用 optionsKey 指定从 pluginOptions 中取哪一块;TypeScript 用户可用 Plugin<MyPluginOptions> 类型获得完整类型检查(见 src/plugin、src/plugin/plugin_manager.ts)。Cucumber 自带的过滤、发布、分片功能本身就是以插件实现的,可作参考:src/filter/filter_plugin.ts、src/publish/publish_plugin.ts、src/sharding/sharding_plugin.ts。
自定义 Formatter
自定义 formatter 是插件的一种精简形态(详见 docs/custom_formatters.md),默认导出 { type: 'formatter', formatter: (context) => {...} }。context 提供 on('message', handler)、options、logger、write(buffer)(写入输出流)与 stream。最小示例——在测试运行结束时输出结果:
export default {
type: 'formatter',
formatter: ({
on,
write
}) => {
on('message', envelope => {
if (envelope.testRunFinished) {
write(envelope.testRunFinished.success ? 'Success' : 'Failure')
}
})
}
}
同样支持 optionsKey 取配置块、FormatterPlugin<MyOptions> 类型检查。发布为 npm 包时,确保 formatter 插件对象是 package.json 入口的默认导出,用户即可用包名直接引用。上一代 formatter 架构(基于 Formatter 类)仍受支持但官方推荐切换到新架构与 Cucumber Messages。
JavaScript API:在进程内编程式运行
大多数用户用不到,但要把 Cucumber 集成进更大的框架时,可用其 JavaScript API(入口为 @cucumber/cucumber/api,详见 docs/javascript_api.md)。最小示例:
import { loadConfiguration, runCucumber } from '@cucumber/cucumber/api'
const { runConfiguration } = await loadConfiguration()
const { success } = await runCucumber(runConfiguration)
console.log(success)
核心函数 runCucumber、loadConfiguration、loadSources、loadSupport 都接受可选的 environment 参数(cwd、stdout、stderr、env、debug),缺省时自动从 process 取默认值。配置有两种形态:
- "用户"配置:扁平格式,用户在配置文件/CLI 中表达的形态(可部分提供,缺省补默认值);
- "运行"配置:
runCucumber需要的结构化格式,分为sources、support、formats、runtime四个块,所有字段必填。推荐用loadConfiguration帮你完成"默认值 + 用户配置文件 + 额外值"的合并(合并逻辑见 src/api/load_configuration.ts)。
// 计算测试计划(含过滤与排序后的结果)
import { loadConfiguration, loadSources } from '@cucumber/cucumber/api'
const { runConfiguration } = await loadConfiguration()
const { plan } = await loadSources(runConfiguration.sources)
console.dir(plan)
// 预加载并复用支持代码库(同进程多次运行时必须复用,
// 因为 Node.js 会缓存首次 import 的模块)
import { loadConfiguration, loadSupport, runCucumber } from '@cucumber/cucumber/api'
const { runConfiguration } = await loadConfiguration()
const support = await loadSupport(runConfiguration)
const result1 = await runCucumber({ ...runConfiguration, support })
const result2 = await runCucumber({ ...runConfiguration, support })
runCucumber 的完整执行管线(解析路径 → 编译 Pickle → 初始化 formatter/插件 → 执行 → 汇总结果)可以在 src/api/run_cucumber.ts 中看到。
开发实践:转译、ESM、调试、超时与 Snippets
使用 TypeScript 等语言(转译)
步骤定义可以用需要编译为 JavaScript 的语言编写并即时编译(详见 docs/transpiling.md)。以 TypeScript 为例,建议安装 tsx(无需 tsconfig.json 即可工作):
npm install --save-dev tsx
先确认编译目标模块格式(看 package.json 的 type 字段:module 为 ESM,commonjs 或缺失为 CommonJS)。
- CommonJS:用
requireModule注册tsx/cjs,再用require加载 TS 支持代码:- 配置文件
{ requireModule: ['tsx/cjs'], require: ['features/step-definitions/**/*.ts'] } - CLI
npx cucumber-js --require-module tsx/cjs --require 'features/step-definitions/**/*.ts'
- 配置文件
- ESM:需要一个显式注册 tsx 的入口文件,再用
import选项依次加载:// tsx-register.js import { register } from 'tsx/esm/api' register()- 配置文件
{ import: ['./tsx-register.js', 'features/step-definitions/**/*.ts'] } - CLI
npx cucumber-js --import ./tsx-register.js --import 'features/step-definitions/**/*.ts'
- 配置文件
Source maps 能保证报告与堆栈追踪到原始 TS 源码:tsx、ts-node、@babel/register 默认已开启;对"已转译"的共享库代码,需确认产物末尾有 //# sourceMappingURL= 注释,并启用 Node.js 的 --enable-source-maps 标志。
原生 ES Modules
支持代码也可以直接用原生 ESM 语法编写(无需转译,详见 docs/esm.md)。ESM 支持代码需用 import 选项指定(features 目录下的 .mjs 文件会被自动发现导入)。配置文件、自定义 formatter、自定义 snippet 语法也都可以是 ESM,并且可以与 CommonJS 混用(增量迁移)。
调试日志
排查问题时可开启 debug 日志:Cucumber 使用 debug 库的 cucumber scope,设置环境变量即可:
DEBUG=cucumber
启用后 Cucumber 会把配置解析等诊断信息输出到 stderr(详见 docs/debugging.md)。
超时控制
默认异步 Hooks 与步骤的超时是 5000 毫秒(详见 docs/support_files/timeouts.md)。全局修改:
var {setDefaultTimeout} = require('@cucumber/cucumber');
setDefaultTimeout(60 * 1000);
单个 Hook/步骤单独设置:
var {Before, Given} = require('@cucumber/cucumber');
Before({timeout: 60 * 1000}, function() {
// 慢速的浏览器/文件系统/网络操作
});
Given(/^a slow step$/, {timeout: 60 * 1000}, function() {
// ...
});
注意 setDefaultTimeout 必须在全局调用(不要在步骤、Hook 或 World 类里调用)。将超时设为 -1 可禁用(官方强烈不建议,除非你自己实现超时保护),必要时可用同样导出给用户的 wrapPromiseWithTimeout 辅助函数(源码在 src/time.ts)。
未定义步骤的 Snippets
BDD 流程中常先写 Feature 再写实现。运行 cucumber-js 遇到未定义步骤时,会为每个未定义步骤生成一段骨架代码(详见 docs/snippets.md),例如:
Then('I remove the todo {string}', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
默认生成同步风格。可通过 formatOptions.snippetInterface 切换为支持异步的风格:
"async-await":输出async function,可用await(不确定时推荐);"callback":输出带最后一个 callback 参数的普通函数;"promise":输出返回Promise的普通函数;"synchronous":无异步模式的普通函数。
例:--format-options '{"snippetInterface":"async-await"}' 会生成:
Then('I remove the todo {string}', async function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
formatOptions.snippetSyntax 还可指定自定义 snippet 语法模块。
附件、数据表与报告发布
- 附件(Attachments):用
this.attach向 message/json 等 formatter 输出文本、图片等数据(支持指定mediaType、fileName,支持stream.Readable/Buffer,base64 字符串可加base64:前缀,如{ mediaType: 'base64:image/png' });this.log以text/x.cucumber.log+plain类型附加日志;this.link以text/uri-list类型附加链接(详见 docs/support_files/attachments.md)。附件也会被 progress、progress-bar、summary 等 formatter 打印(仅text/plain可见),适合并行模式下调试。 - 数据表(Data Tables):带表头的步骤可调用
hashes()(每行转对象,表头为键)、rows()(不含首行的二维数组);无表头时用raw()(二维数组)、rowsHash()(首列键、次列值的对象)、transpose()(转置后的新实例)。示例见 features/data_tables.feature。 - 发布报告(Publishing):可用
publish: true(CLI--publish,或环境变量CUCUMBER_PUBLISH_ENABLED=true)把报告发布到 Cucumber Reports 免费服务,运行结束会打印报告 URL;报告匿名且 24 小时自毁(除非认领)。私有服务可通过CUCUMBER_PUBLISH_URL、CUCUMBER_PUBLISH_TOKEN指定,代理场景设置标准代理环境变量并加NODE_USE_ENV_PROXY=1(需 Node.js 22.21.0/24.0.0+,详见 docs/publishing.md)。
用 Profiles 组合配置
当项目存在多套运行参数(本地 vs CI、不同浏览器/视口)时,Profiles 可以把配置捆绑成命名切片,用 --profile/-p 引用(可重复、可与 CLI 直传选项叠加,CLI 选项追加在 Profile 之后):
cucumber-js -p my_profile
示例——本地默认配置与 CI 配置:
const common = {
requireModule: ['ts-node/register'],
require: ['support/**/*.ts'],
worldParameters: {
appUrl: process.env.MY_APP_URL || 'http://localhost:3000/'
}
}
module.exports = {
default: {
...common,
format: ['progress-bar', 'html:cucumber-report.html'],
},
ci: {
...common,
format: ['html:cucumber-report.html'],
publish: true
}
}
ESM 下用 export default 表示默认 profile、命名导出表示其他 profile;default 也可以是一个返回 profiles 对象的(异步)函数,用于动态生成配置。由于 Cucumber 不允许自定义 CLI 参数,常见做法是用 profile 作为 --world-parameters 的别名(如 desktop/phone/tablet、chromium/firefox/webkit 各定义一段 JSON),组合调用 cucumber-js -p webkit -p phone 即可,多次提供时参数会合并、后值优先。
进一步阅读与支持
- 完整文档地图见 README.md 的 Documentation 一节;
main分支文档可能包含未发布特性,旧版本见 docs/older_versions.md。 - 按主题深入:安装 docs/installation.md、CLI docs/cli.md、配置 docs/configuration.md、Hooks docs/support_files/hooks.md、步骤定义 docs/support_files/step_definitions.md、World docs/support_files/world.md、API 参考 docs/support_files/api_reference.md、Formatter docs/formatters.md、并行 docs/parallel.md、插件 docs/plugins.md、JavaScript API docs/javascript_api.md,以及 docs/faq.md 常见问题。
- 若想为项目做贡献,先阅读行为准则,再参照 CONTRIBUTING.md 完成环境搭建。
- 需要社区支持时可查阅 Cucumber 官方的开源支持渠道。
【免费下载链接】cucumber-js
Cucumber for JavaScript
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




