定義自訂命令
概述
大多數情況下,您需要擴充 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
)。這些是到 Selenium JsonWire 或 W3C Webdriver 通訊協定端點的直接 HTTP 對應,取決於目前使用的是哪一個。
以下是一個範例
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.dev.org.tw');
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');
}
};