CucumberJS

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

cucumber tag expression

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容