概述

大多数情况下,您需要扩展 Nightwatch 命令以适应您自己的应用程序需求。为此,创建一个新文件夹(例如 nightwatch/commands)并在其中开始定义您自己的命令,每个命令都在其自己的文件中。

然后在 nightwatch.json 文件中指定该文件夹的路径,作为 custom_commands_path 属性。

nightwatch.json
{
  "custom_commands_path" : "nightwatch/commands"
}

命令名称是文件本身的名称。

定义自定义命令

您可以通过两种主要方式定义自定义命令

1) 类样式命令

这是编写自定义命令的推荐样式,也是 Nightwatch 自身大多数命令的编写方式。您的命令模块需要导出一个带有一个 command 实例方法的类构造函数,该方法代表命令函数。

所有 Nightwatch 命令都是异步的,这意味着自定义命令必须发出完成信号(在 command 方法中)。这可以通过两种方式实现

  1. 返回一个 Promise
  2. 发出 "complete" 事件(在这种情况下,该类需要继承自 Node 的 EventEmitter

返回一个 Promise 是推荐的方式。基于类的 command 方法在类的实例的上下文中(this 的值)执行。browser 对象可通过 this.api 获取。

以下示例执行与 .pause() 命令等效的操作。请注意有关完成的两种变体。

返回值

您还可以指定一个返回值,作为 Promise 将解析的论据,或者作为 "complete" 事件调用中的论据。

通过 Promise 完成
nightwatch/commands/customPause.js
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。

以下是一个示例

nightwatch/commands/customCommand.js
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" 事件完成
nightwatch/commands/customPause.js
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 JsonWireW3C Webdriver 协议端点,具体取决于当前使用哪个协议。

以下是一个示例

nightwatch/commands/customCommand.js
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

以下是一个示例

nightwatch/commands/customCommand.js
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() 调用中。客户端命令(如 executeperform)可通过 this 获取。

nightwatch/commands/customCommand.js
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())的方法,该方法在应用程序中定义。

使用此命令,测试将类似于

tests/sampleTest.js
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'); } };