定义自定义命令
概述
大多数情况下,您需要扩展 Nightwatch 命令以适应您自己的应用程序需求。为此,创建一个新文件夹(例如 nightwatch/commands
)并在其中开始定义您自己的命令,每个命令都在其自己的文件中。
然后在 nightwatch.json
文件中指定该文件夹的路径,作为 custom_commands_path
属性。
{
"custom_commands_path" : "nightwatch/commands"
}
命令名称是文件本身的名称。
定义自定义命令
您可以通过两种主要方式定义自定义命令
1) 类样式命令
这是编写自定义命令的推荐样式,也是 Nightwatch 自身大多数命令的编写方式。您的命令模块需要导出一个带有一个 command
实例方法的类构造函数,该方法代表命令函数。
所有 Nightwatch 命令都是异步的,这意味着自定义命令必须发出完成信号(在 command
方法中)。这可以通过两种方式实现
- 返回一个
Promise
- 发出 "complete" 事件(在这种情况下,该类需要继承自 Node 的
EventEmitter
)
返回一个 Promise
是推荐的方式。基于类的 command
方法在类的实例的上下文中(this
的值)执行。browser
对象可通过 this.api
获取。
以下示例执行与 .pause()
命令等效的操作。请注意有关完成的两种变体。
返回值
您还可以指定一个返回值,作为 Promise 将解析的论据,或者作为 "complete" 事件调用中的论据。
通过 Promise 完成
module.exports = class CustomPause {
command(ms, cb) {
// If we don't pass the milliseconds, the client will
// be suspended indefinitely
if (!ms) {
return;
}
const returnValue = {
value: 'something'
};
return new Promise((resolve) => {
setTimeout(() => {
// if we have a callback, call it right before the complete event
if (cb) {
cb.call(this.api);
}
resolve(returnValue);
}, ms);
});
}
}
command
方法也可以是 async
。在这种情况下,您只需要返回一个值,因为 async
方法已经返回了一个 Promise。
以下是一个示例
module.exports = class CustomCommand {
async command() {
let returnValue;
try {
returnValue = await anotherAsyncFunction();
} catch (err) {
console.error('An error occurred', err);
returnValue = {
status: -1,
error: err.message
}
}
return returnValue;
}
}
通过 "complete" 事件完成
const Events = require('events');
module.exports = class CustomPause extends Events {
command(ms, cb) {
// If we don't pass the milliseconds, the client will
// be suspended indefinitely
if (!ms) {
return;
}
const returnValue = {
value: 'something'
};
setTimeout(() => {
// if we have a callback, call it right before the complete event
if (cb) {
cb.call(this.api);
}
// This also works: this.complete(returnValue)
this.emit('complete', returnValue);
}, ms);
}
}
使用 Nightwatch 协议操作
从 v1.4 开始,您还可以直接使用 Nightwatch 用于其自身内置 API 的协议操作(通过 this.transportActions
)。这些是直接 HTTP 映射到 Selenium JsonWire 或 W3C Webdriver 协议端点,具体取决于当前使用哪个协议。
以下是一个示例
module.exports = class CustomCommand {
async command() {
let returnValue;
// list all the avaialble transport actions
// console.log(this.transportActions);
try {
returnValue = await this.transportActions.getCurrentUrl();
} catch (err) {
console.error('An error occurred', err);
returnValue = {
status: -1,
error: err.message
}
}
return returnValue;
}
}
直接调用 Selenium/Webdriver 端点
此外,从 v1.4 开始,您可以(通过 this.httpRequest(options)
)从自定义命令直接调用 Selenium/Webdriver 服务器上可用的 HTTP 端点。这可能是扩展提供的 API 协议的一种便捷方式,因为它与其他协议操作使用相同的 HTTP 请求 接口。
当使用提供其他端点的服务时,这将特别有用,例如 Appium。
以下是一个示例
module.exports = class CustomCommand {
async command() {
let returnValue;
try {
returnValue = await this.httpRequest({
// the pathname of the endpoint to call
path: '/session/:sessionId/url',
// the current Selenium/Webdriver sessionId
sessionId: this.api.sessionId,
// host and port are normally not necessary, since it is the current Webdriver hostname/port
//host: '',
//port: '',
// the body of the request
data: {
url: 'https://127.0.0.1/test_url'
},
method: 'POST'
});
} catch (err) {
console.error('An error occurred', err);
returnValue = {
status: -1,
error: err.message
}
}
return returnValue;
}
}
2. 函数样式命令
这是一种更简单的命令定义形式,但它们也很有限。
命令模块需要导出一个 command
函数,该函数需要至少调用一个 Nightwatch api 方法(例如 .execute()
)。这是由于命令的异步队列系统的工作原理的限制。您也可以将所有内容包装在 .perform()
调用中。客户端命令(如 execute
和 perform
)可通过 this
获取。
module.exports.command = function(file, callback) {
var self = this;
var imageData;
var fs = require('fs');
try {
var originalData = fs.readFileSync(file);
var base64Image = new Buffer(originalData, 'binary').toString('base64');
imageData = 'data:image/jpeg;base64,' + base64Image;
} catch (err) {
console.log(err);
throw "Unable to open file: " + file;
}
this.execute(function(data) {
// execute application specific code
App.resizePicture(data);
return true;
},
[imageData], // arguments array to be passed
function(result) {
if (typeof callback === "function") {
callback.call(self, result);
}
});
return this;
};
上面的示例定义了一个命令(例如 resizePicture.js),它将图像文件加载为 data-URI
并调用一个名为 resizePicture
(通过 .execute()
)的方法,该方法在应用程序中定义。
使用此命令,测试将类似于
module.exports = {
"testing resize picture" : function (browser) {
browser
.url("http://app.host")
.waitForElementVisible("body")
.resizePicture("../../../var/www/pics/moon.jpg")
.assert.element(".container .picture-large")
.end();
}
};
使用 async/await
您也可以在函数样式自定义命令中使用 ES6 async
/await
语法。以下是一个自定义命令示例
module.exports = {
command: async function () {
this.url('https://nightwatch.node.org.cn');
this.waitForElementVisible('section#index-container');
const result = await this.elements('css selector', '#index-container ul.features li');
this.assert.strictEqual(result.value.length, 7, 'Feature elements number is correct');
}
};