使用describe()接口
从1.3版开始,Nightwatch 原生支持使用流行的 BDD 接口编写测试。无需进一步配置。也可以一起运行用 BDD describe 和 Exports 接口编写的测试。
Nightwatch 中的 BDD(行为驱动开发) 接口提供了常用的describe(), context(), test(), it(), specify(), before(), after(), beforeEach(), afterEach()
功能。
Nightwatch 不支持嵌套describe/context
声明。仅支持顶层,它定义了测试套件的名称。
例子:
describe('Ecosia', function() {
// 也可使用 test() and specify()
it('demo test', function(browser) {
browser
.url('https://www.ecosia.org/')
.setValue('input[type=search]', 'nightwatch')
.click('button[type=submit]')
.assert.containsText('.mainline-results', 'Nightwatch.js')
.end();
});
});
除了通用的BDD语法,nightwatch还提供了一些定义自己的行为方式
测试用例特定功能
describe('homepage test with describe', function() {
// 特定功能的测试用例
this.desiredCapabilities = {
browserName: 'firefox'
};
it('...', function() {...});
});
测试用例特定标签
describe('homepage test with describe', function() {
// 定义使用bdd的标签
this.tags = ['login', 'authentication''];
it('...', function() {...});
});
测试用例特定重试
describe('homepage test with describe', function() {
// 当前测试重试失败所使用的时间
this.retries(3);
// 在断言失败或错误的情况下重试当前测试的次数
this.suiteRetries(2);
it('...', function() {...});
});
完整的BDD语法
describe('homepage test with describe', function() {
// 所有配置项均可通过this.settings获取
// console.log('Settings', this.settings);
// 测试用例指定功能
// this.desiredCapabilities = {};
// 若当前测试为单元/集成测试则开启(即不会创建Webdriver会话)
// this.unitTest = false
// 若希望浏览器窗口在测试失败或出错时仍开启,则设置为false(通常用于debug)
// this.endSessionOnFail = true
// 若你希望在当前测试失败或出错时继续指向后续测试,则设置为false
// this.skipTestcasesOnFail = true
// 是否跳过此测试
// this.disabled = false
// this.retries(3);
// this.suiteRetries(2);
// 控制断言或元素命令超时时间
// this.timeout(1000)
// 控制重试的间隔
// this.retryInterval(100);
before(function(browser) {
this.homepage = browser.page.home();
});
it('startHomepage', () => {
this.homepage.navigate();
this.homepage.expect.section('@indexContainer').to.be.not.visible;
});
// Run only this testcase
/
it.only('startHomepage', () => {
this.homepage.navigate();
});
/
// 跳过此测试用例: 等价于: test.skip(), it.skip(), and xit()
xtest('async testcase', async browser => {
const result = await browser.getText('#navigation');
console.log('result', result.value)
});
test('version dropdown is enabled', browser => {
const navigation = this.homepage.section.navigation;
const navbarHeader = navigation.section.navbarHeader;
navbarHeader.expect.element('@versionDropdown').to.be.enabled;
});
after(browser => browser.end());
});
使用“导出”接口
基础示例
该测试打开搜索引擎Ecosia.org.搜索“nightwatch”,然后验证搜索结果第一个是否为Nightwatch.js网站
module.exports = {
'Demo test ecosia.org' : function(browser) {
browser
.url('https://www.ecosia.org/')
.waitForElementVisible('body')
.assert.titleContains('Ecosia')
.assert.visible('input[type=search]')
.setValue('input[type=search]', 'nightwatch')
.assert.visible('button[type=submit]')
.click('button[type=submit]')
.assert.containsText('.mainline-results', 'Nightwatch.js')
.end();
}
};
将一个测试分为多个步骤:
module.exports = {
'step one: navigate to ecosia.org': function(browser) {
browser
.url('https://www.ecosia.org')
.waitForElementVisible('body')
.assert.titleContains('Ecosia')
.assert.visible('input[type=search]')
.setValue('input[type=search]', 'nightwatch')
.assert.visible('button[type=submit]');
},
'step two: click submit' : function (browser) {
browser
.click('button[type=submit]')
.assert.containsText('.mainline-results', 'Nightwatch.js')
.end();
}
};
使用ES6 async/await
从v1.1开始可使用es6 async函数,此方式可使api命令返回promise对象,便于await操作符操作结果。从1.7开始可以使用链式api命令。
module.exports = {
'demo test async': async function (browser) {
// get the available window handles
const result = await browser.windowHandles();
console.log('result', result);
// switch to the second window
// await is not necessary here since we're not interested in the result
browser.switchWindow(result.value[1]);
}
};
回调和async测试用例
若回调返回promise,则仍可使用,promise的结果会作为命令的结果返回。如下实例:
module.exports = {
'demo test async': async function (browser) {
// get the available window handles
const value = await browser.windowHandles(function(result) {
// we only want the value, not the entire result object
return Promise.resolve(result.value);
});
console.log('value', value);
// switch to the second window
browser.switchWindow(value[1]);
}
};
断言
断言用于判断特色是否通过,与命令不同,断言可能使用一个或多个命令。
断言失败: 当断言失败,则会抛出AssertionError,单个测试中可能有多个断言,若一个断言失败,则该测试会终止并失败。若该测试包含多个步骤或多个测试用例,则剩下的步骤/测试用例均会呗跳过。
控制失败后的行为: 若希望一个断言失败后剩下的断言继续,可使用几种方式:
- 使用.verify而不是.assert- 断言失败会被记录但测试仍继续执行,且最终测试仍标记为失败。
- 使用元素选择器对象并设置
abortOnFailure
为false
。如:
browser.setValue({selector: 'input[type=search]', abortOnFailure: false}, 'nightwatch')
在配置文件中设置"skip_testcases_on_fail"
为false
也可使余下的测试步骤/用例继续
断言失败后终止会话 通常,断言失败后会终止会话并关闭浏览器,因此无法继续余下的测试用例。可通过设置"end_session_on_fail"
为false
来保持浏览器开启,通常在调试时很有用。
使用.expect()断言
expect
断言使用Chai Framework中的Expect
api,可用于元素、cookie、page title 和url
元素 expect 示例: expect.element([…])使用
module.exports = {
'Demo test Ecosia.org': function (browser) {
browser.url('https://www.ecosia.org/');
// expect element header to be present in 1000ms
browser.expect.element('header').to.be.present.before(1000);
// expect element header to have css property 'display'
browser.expect.element('header').to.have.css('display');
// expect element header to have attribute 'class' which contains text 'index-header'
browser.expect.element('header').to.have.attribute('class').which.contains('index-header');
// expect element .search-form to be an input tag
browser.expect.element('.search-form').to.be.a('form');
// expect element header to be visible
browser.expect.element('header').to.be.visible;
browser.end();
}
};
expect指定元素个数: 如下示例,期望此页面有指定的元素个数,使用expect.elements([...]).count
module.exports = {
'demo test ecosia.org'(browser) {
browser
.url('https://www.ecosia.org/')
.expect.elements('section').count.to.equal(5);
},
after(browser) {
browser.end();
}
};
编写单元测试
nightwatch从0.9版本开始改进了单元测试,并与Mocha的Exports接口兼容。
单元测试模式: Nightwatch 会自动尝试连接到 WebDriver 服务器并创建会话。运行单元测试时,需要禁用此功能,并且需要让运行者知道它正在单元测试模式下运行。可通过以下两种方式完成:
- 在nightwatch.conf,js中设置
unit_tests_mode=true
- 为每个测试添加
@unitTest
属性:如果希望某个测试为单元测试则设置@unitTest属性为true
const assert = require('assert');
module.exports = {
'@unitTest': true,
'demo UnitTest' : function (done) {
assert.equal('TEST', 'TEST');
setTimeout(function() {
done();
}, 10);
}
};
断言框架
对于单元测试,browser对象不会作为参数传递给测试用例。传递的唯一参数是done用于异步测试的回调。如下为nightwatch模块红单元测试的utils.js的子集:
var assert = require('assert');
var common = require('../../common.js');
var Utils = common.require('util/utils.js');
module.exports = {
'test Utils' : {
testFormatElapsedTime : function() {
var resultMs = Utils.formatElapsedTime(999);
assert.equal(resultMs, '999ms');
var resultSec = Utils.formatElapsedTime(1999);
assert.equal(resultSec, '1.999s');
var resultMin = Utils.formatElapsedTime(122299, true);
assert.equal(resultMin, '2m 2s / 122299ms');
},
testMakeFnAsync : function() {
function asyncFn(cb) {
cb();
}
function syncFn() {}
var convertedFn = Utils.makeFnAsync(1, syncFn);
var called = false;
convertedFn(function() {
called = true;
});
assert.equal(Utils.makeFnAsync(1, asyncFn), asyncFn);
assert.ok(called);
}
}
};
异步单元测试
异步测试完成的标志为可选的done
回调,若显示done,则此回调在异步操作完成后必须调用
如下为一个单元测试示例:检查当你没有在设置的时间(10ms)内调用done回调时,nightwatch是否抛出错误
module.exports = {
const assert = require('assert');
module.exports = {
'demo UnitTest' : function (done) {
assert.equal('TEST', 'TEST');
setTimeout(function() { done();
}, 10);
}
};
};
合并单元测试和e2e测试配置
如下为合并的nightwatch.json配置文件,注意exclude
和filter
属性的使用。exclude
值为空表示重置它的值且仅依赖于filter
{
"src_folders" : ["./examples/tests", "./examples/unittests"],
"output_folder" : "./examples/reports",
"webdriver" : {
"start_process": true,
"server_path": "node_modules/.bin/chromedriver",
"port": 9515
},
"test_settings" : {
"default" : {
"launch_url" : "http://localhost",
"desiredCapabilities": {
"browserName": "chrome"
},
"exclude" : "./examples/unittests/"
},
"unittests" : {
"unit_tests_mode" : true,
"filter" : "./examples/unittests/",
"exclude" : ""
}
}
}