実験:Nightmare で XHR を待ってみる
Nightmare で click() 等の結果を待つには、以下のメソッドが用意されています。
- wait(): ページロードを待つ。
- wait(ms): ms ミリ秒待つ。
- wait(selector): ページに selector が出現するまで待つ。
- wait(fn, value, [delay]): fn の呼び出し結果が value になるまで待つ。delay が指定された場合には都度ページリフレッシュも行う。
SPA をテストする場合には基本的に wait(selector) で待てばいいのでしょうが、例えば、XHR の結果を待ちたくなったときにどうしたらいいのだろうか、ということで実験してみました。
基本的な方針
Nightmare(Phantomjs)では、SPA が勝手に発行したリクエスト/レスポンスも監視してくれるので、それを利用します。
- resourceReceived で XHR 等のレスポンスを拾い、内部キューに溜める。
-
click()
等の後で、setTimeout
を使って内部キューからレスポンスを取り出し必要な判定を行う。 - コンストラクタでいろいろやりたいので、Nightmare を継承する
実装
testcase.coffee
Nightmare = require 'nightmare'
module.exports = class TestCase extends Nightmare
constructor: (@caseName, @cnt = 0) ->
@__responses = []
super()
@viewport 1024, 768
@on 'resourceReceived', (res) =>
@__responses.push res
waitXHR: (url, status) ->
waitInner = ->
args = arguments
done = arguments[args.length - 1]
getResponse = =>
while @__responses.length > 0
res = @__responses.shift()
if res.url.indexOf(url) != -1 && res.status == status
return true
waitLoop = =>
done() if getResponse()
setTimeout waitLoop, @options.interval
waitLoop()
@queue.push [waitInner, [url, status]]
this
createCaptureFileName: (name) ->
baseDir = process.env.EVIDENCE_DIR
fileName = "00#{++ @cnt}".substr(-3) + "-#{name}.png"
"#{__dirname}/#{baseDir}/#{@caseName}/#{fileName}"
capture: (testName) ->
@screenshot @createCaptureFileName testName
使用例
Nightmare = require 'nightmare'
module.exports = class TestCase extends Nightmare
constructor: (@caseName, @cnt = 0) ->
@__responses = []
super()
@viewport 1024, 768
@on 'resourceReceived', (res) =>
@__responses.push res
waitXHR: (url, status) ->
waitInner = ->
args = arguments
done = arguments[args.length - 1]
getResponse = =>
while @__responses.length > 0
res = @__responses.shift()
if res.url.indexOf(url) != -1 && res.status == status
return true
waitLoop = =>
done() if getResponse()
setTimeout waitLoop, @options.interval
waitLoop()
@queue.push [waitInner, [url, status]]
this
createCaptureFileName: (name) ->
baseDir = process.env.EVIDENCE_DIR
fileName = "00#{++ @cnt}".substr(-3) + "-#{name}.png"
"#{__dirname}/#{baseDir}/#{@caseName}/#{fileName}"
capture: (testName) ->
@screenshot @createCaptureFileName testName
mocha を使ってみました。テスト開始前に RESTful API 用の proxy を備えたテスト用サーバーを起動しておきます。
{startServer} = require '../local-server'
moment = require 'moment'
before ->
startServer 9876, 'public'
process.env.NODE_ENV = 'test'
process.env.DEBUG='*'
now = moment().format("YYYY-MM-DD_HHmmss")
process.env.EVIDENCE_DIR = "evidence@#{now}"
各テストでは、click()
の後で適宜 waitXHR()
で待ちます。wait()
でタイムアウトを待つ必要もなく、wait(ms)
で空振りすることもなく、なかなか良い感じです。
TestCase = require './testcase'
describe "ログイン", ->
caseNumber = 0
newCase = ->
caseString = "00#{++ caseNumber}".substr(-3)
new TestCase(caseString)
.goto('http://localhost:9876')
it "ログイン画面を表示すること", (done) ->
newCase()
.capture('login')
.run(done)
it "ユーザー名とパスワードの未入力でエラーとなること", (done) ->
newCase()
.capture('login')
.click('#login')
.waitXHR('/api/auth/login', 500)
.capture('login-fail')
.run(done)
ポイント
Nightmare はメソッドチェーンを一旦内部キュー(this.queue)に格納します。run()
が呼ばれると、キューからメソッドが一つずつ取り出され呼び出されていきます。このときの引数は、格納時の引数と、キューの次のメソッドです。
/**
* Run all the queued methods.
*
* @param {Function} callback
*/
Nightmare.prototype.run = function(callback) {
var self = this;
debug('run');
this.setup(function () {
setTimeout(next, 0);
function next(err) {
var item = self.queue.shift();
if (!item) {
self.teardownInstance();
return (callback || noop)(err, self);
}
var method = item[0];
var args = item[1];
args.push(once(next));
method.apply(self, args);
}
});
};
最後の callback まで、数珠繋ぎにメソッドが呼ばれていくわけですね。イベント駆動なのにシーケンシャル実行を実現できているのは、こういう仕掛けだったわけです。なるほど。
はまりどころ
当初、XHR のレスポンスを @queue
に格納していて、Nightmare のメソッド呼び出し用キューとかぶっていることにしばらく気づきませんでした。ありがちな名前は衝突事故が発生しやすいわりに発見が遅くなるという嫌なパターンですね。
自分が使う変数は特殊なプレフィクスつけるとかで自衛するとして、それでも衝突しちゃった場合にはどうすればいいんですかねぇ。。。
参考
Author And Source
この問題について(実験:Nightmare で XHR を待ってみる), 我々は、より多くの情報をここで見つけました https://qiita.com/t-suwa/items/a886da112b136c908755著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .