Cucumber.js是用JavaScript实现的Cucumber,可以运行在Node.js(4.0及以上版本)和现代web浏览器上。
Install
npm install cucumber
Notes: Cucumber不能全局安装,因为使用时需要在support files中引用cucumber,而全局安装的模块是无法被引用的。
CLI
Running specific features
- 运行全部的feature
$ cucumber-js features/**/*.feature
- 运行特定文件夹下的feature
$ cucumber-js features/dir
- 运行特定的feature
$ cucumber-js features/my_feature.feature
- 运行一个特定的Scenario根据它所在的行数
$ cucumber-js features/my_feature.feature:3
- 运行一个特定的Scenario根据它的名字
$ cucumber-js --name "topic 1"
- Tags
Requiring support files
在运行features前,用--require <GLOB|DIR|FILE>
来引用support file。
- node-glob
- 如果不使用GLOB模式,则需要自定义support文件,见下文
Formats
用--format <TYPE[:PATH]>
可以指定输出的格式。如果PATH没有指定,则输出到默认文件中。否则输出到PATH指定的文件。format可以同时指定多个,输出不同格式的结果到不同的文件中。
- Q1: 不指定PATH的时候,stdout指向哪里?
- Q2: 如何同时指定多个format到多个file?
内置的几种格式:event-protocol, json, progress, progress-bar, rerun, snippets, summary, usage, usage-json
rerun -- prints the paths of any non passing scenarios(example)
Format Options
可以使用 --format-options <JSON>
指定format 参数.
建议的用法: 创建profile文件(见后续),在其中定义一些json对象,用
JSON.stringify
调用,避免直接写JSON.
For example:
//cucumber.js
const formatOpt = { "colorEnabled": true };
exports = module.exports = {
default: `--compiler es6:babel-core/register --format-options '${JSON.stringify(formatOpt)}'`
}
Colors
用--format-options '{"colorsEnabled": false}'
可以使colors失效。
Exiting
使用--exit
可以在测试运行结束时强制退出,但是不建议这么使用。
Undefined Step Snippets
未定义的step片段会默认使用回到接口打印到javascript中。
重写代码片段可以使用--format-options '{"snippetInterface": "<interface>"}'
。有效的interface有async-await, callback, generator, promise 或者 synchronous。
Rerun separator
rerun格式的分隔符可以重新指定--format-options '{"rerun": {"separator": "<separator>"}}'
默认的分隔符是一个newline charactor,当我们需要在本地从CLI日志中复制一行重新运行那些失败的测试而分隔符是一个space charactor时,这个属性是有用的。
需要注意的是,rerun file的分隔符只能是默认的分隔符。
Parallel
使用--parallel <NUMBER_OF_SLAVES>
我们可以同时并行多个scenarios。每一个slave都是一个独立的node进程并接受下列变量:
-
CUCUMBER_PARALLEL
- set to 'true' -
CUCUMBER_TOTAL_SLAVES
- set to the number of slaves -
CUCUMBER_SLAVE_ID
- id for slave
**Note: **在使用parallel的时候,打印stdout(比如 console.log)会报错,因为每个从进程会通过stdout与主进程通信。
Profile
为了存放和重用那些常用的CLI options,我们可以在根目录下添加一个cucumber.js的文件,把options写在其中。Profile可以通过-p <NAME>
和--profile <NAME>
来调用。
Tags
Transpilers
Step definitions 和 support file可以用其它语言编写,然后转译成javascript。为了达到这个目的,可以使用--require-module <module_name>
World Parameters
我们可以把一些parameter传递到 world constructor中,使用--world-parameter <JSON>
. The JSON string必须定义一个对象。
Support Files
World
对每个scenario,World都是一个独立的上下文,暴露给hooks和steps就是this。默认的world构造函数:
function World({attach, parameter}) {
this.attach = attach
this.parameters = parameters
}
- attach: 用于向hooks/steps添加attachments
- parameters: 就是通过CLI传递的参数对象
默认的World可以被覆盖通过setWorldConstructor:
var {setWorldConstructor} = require('cucumber');
var seleniumWebdriver = require('selenium-webdriver');
function CustomWorld() {
this.driver = new seleniumWebDriver.Builder()
.forBrowser('firefox')
.build();
}
this.waitForElement = function(locator) {
var condition = seleniumWebdriver.until.elementLocated(locator);
return this.driver.wait(condition);
}
setWorldConstructor(CustomWorld);
Hooks
Hooks用于运行每一个scenario前后setup和teardown环境。Multiple Before hooks可以按定义顺序依次执行,而 Multiple After hooks则按照他们定义的顺序反向执行。
var {After, Before} = require('cucumber');
// Synchronous
Before(function () {
this.count = 0;
});
// Asynchronous Callback
Before(function (testCase, callback) {
var world = this;
tmp.dir({unsafeCleanup: true}, function(error, dir) {
if (error) {
callback(error);
} else {
world.tmpDir = dir;
callback();
}
});
});
// Asynchronous Promise
After(function () {
// Assuming this.driver is a selenium webdriver
return this.driver.quit();
});
Tagged hooks
var {After, Before} = require('cucumber');
Before(function () {
// This hook will be executed before all scenarios
});
Before({tags: "@foo"}, function () {
// This hook will be executed before scenarios tagged with @foo
});
Before({tags: "@foo and @bar"}, function () {
// This hook will be executed before scenarios tagged with @foo and @bar
});
Before({tags: "@foo or @bar"}, function () {
// This hook will be executed before scenarios tagged with @foo or @bar
});
// You can use the following shorthand when only specifying tags
Before("@foo", function () {
// This hook will be executed before scenarios tagged with @foo
});
BeforeAll/AfterAll
如果需要在所有的scenario前后setup/teardown环境,可以使用BeforeAll/AfterAll。像hooks和steps一样,她们可以是异步的?同步的?接收callback,返回一个promise
Notes 不同于Before/After,这些方法不能接收world 实例作为this,这是因为每一个scenario都会拥有一个属于自己的world实例。
var {AfterAll, BeforeAll} = require('cucumber');
// Synchronous
BeforeAll(function () {
// perform some shared setup
});
// Asynchronous Callback
BeforeAll(function (callback) {
// perform some shared setup
// execute the callback (optionally passing an error when done)
});
// Asynchronous Promise
AfterAll(function () {
// perform some shared teardown
return Promise.resolve()
});
Timeout
默认情况下,异步的hooks和steps会在5000ms后超时,这个默认值也可以全局修改:
const setDefaultTimeout = require('cucumber');
setDefaultTimeout(60*1000);
也可以为一个特定的hook或者step设定timeout:
var {Before, Given} = require('cucumber');
Before({timeout: 60 * 1000}, function() {
// Does some slow browser/filesystem/network actions
});
Given(/^a slow step$/, {timeout: 60 * 1000}, function() {
// Does some slow browser/filesystem/network actions
});
也可以选择disable掉timeout设置通过设置timeout的值为-1,但相应的要定义自己的超时保护机制,否则测试会意外退出或永久挂起:
var {Before, Given} = require('cucumber');
var Promise = require('bluebird');
Given('the operation completes within {n} minutes', {timeout: -1}, function(minutes) {
const milliseconds = (minutes + 1) * 60 * 1000
const message = `operation did not complete within ${minutes} minutes`
return Promise(this.verifyOperationComplete()).timeout(milliseconds, message);
});
Data tables
Data tables分为有header和无header两种,具体使用可以参照example
- with column header
Scenario: rows
Given a file named "features/passing_steps.feature" with:
"""
Feature: a feature
Scenario: a scenario
Given a table step
| Vegetable | Rating |
| Apricot | 5 |
| Brocolli | 2 |
| Cucumber | 10 |
"""
Given a file named "features/step_definitions/passing_steps.js" with:
"""
import {Given} from 'cucumber'
import assert from 'assert'
Given(/^a table step$/, function(table) {
const expected = [
['Apricot', '5'],
['Brocolli', '2'],
['Cucumber', '10']
]
assert.deepEqual(table.rows(), expected)
})
"""
When I run cucumber-js
Then it passes
* hashes: returns an array of objects where each row is converted to an object (column header is the key)
* rows: returns the table as a 2-D array, without the first row
- without column header
Scenario: raw
Given a file named "features/passing_steps.feature" with:
"""
Feature: a feature
Scenario: a scenario
Given a table step
| Cucumber | Cucumis sativus |
| Burr Gherkin | Cucumis anguria |
"""
Given a file named "features/step_definitions/passing_steps.js" with:
"""
import {Given} from 'cucumber'
import assert from 'assert'
Given(/^a table step$/, function(table) {
const expected = [
['Cucumber', 'Cucumis sativus'],
['Burr Gherkin', 'Cucumis anguria']
]
assert.deepEqual(table.raw(), expected)
})
"""
When I run cucumber-js
Then it passes
* raw: returns the table as a 2-D array
* rowsHash: returns an object where each row corresponds to an entry (first column is the key, second column is the value)
Attachments
文字、图片和其它类型的数据都可以通过attachments添加到时间协议和json格式的输出结果中。具体使用可以参照Attachments
API Reference
查询CucumberJS的API通过这个链接API Reference