Nightmare v2.1.0 以降では type() を文字入力のために使ってはいけない
TL;DR
- Nightmare は v2.1.0 から
type()
の挙動が変わったので,文字列入力のために用いてはいけない. - 使用する場合は,キーボードイベントの発火が完了したことが保証されないので,適切に
wait
する必要がある.
詳細
Nightmare の type(selector, text)
は文字の入力を行うための基本的な API の一つであり,
公式サイトのサンプルでも一番初めにこのような例が出てきます:
var Nightmare = require('nightmare'); var vo = require('vo'); vo(function* () { var nightmare = Nightmare({ show: true }); var link = yield nightmare .goto('http://yahoo.com') .type('input[title="Search"]', 'github nightmare') .click('.searchsubmit') .wait('.ac-21th') .evaluate(function () { return document.getElementsByClassName('ac-21th')[0].href; }); yield nightmare.end(); return link; })(function (err, result) { if (err) return console.log(err); console.log(result); });
Search
という title 属性がついた入力欄に github nightmare
という文字列を入力するため, type
関数を使用しています.
しかし,type()
関数の挙動が,一週間前くらいにリリースされた v2.1.0 から変化しており上記例の通りにテストを書いたところハマってしまったので,注意喚起も込めて書きます.
以前の実装では,要素の value
プロパティに与えられたテキストを代入するという実装でした.以下は v2.0.9 時点での type()
関数の実装です.
exports.type = function(selector, text, done) { debug('.type() %s into %s', text, selector); this.evaluate_now(function (selector, text) { var elem = document.querySelector(selector); elem.focus(); elem.value = text; elem.blur(); }, done, selector, text); };
この実装では keyup
などのキーボード関連イベントが発火しないという問題があり,Electron の sendInputEvent を使うようにする pull request により v2.1.0 では以下のように実装が変わりました.
exports.type = function(selector, text, done) { debug('.type() %s into %s', text, selector); var child = this.child; this.evaluate_now(function (selector) { document.querySelector(selector).focus(); }, function() { child.once('type', done); child.emit('type', text); }, selector); };
type
イベントが発火されると,Nightmare は Electron に対して sendInputEvent
というイベントを発行します.
この sendInputEvent
というのがくせもので,こいつは Electron の renderer に対して「イベントを発行せよ」という通信は行うものの,どうやら実際に発火したかまでは待たないようです.すなわち,上記のコードに変化したことによって,type()
関数では文字列が確実に入力されたかどうかが保証されなくなりました.
実験
実際,type
に長い文字列を渡してみると,入力しきれない場合が出てきます.
挙動説明のために以下のサンプルアプリを作成しました.
npm test
すると Nightmare を使ってテストを実施します.
テキストエリアに入力して Submit すると,その内容が上に表示されるというだけの簡単なアプリケーションですが,1000 文字入力しようとするだけで type が追いつかず,テストが失敗してしまうことが分かります.
1) nightmare can type a long string into a textarea: AssertionError: # test/index.js:21 assert(message === MESSAGE) | | | | | "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" | false "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" --- [string] MESSAGE +++ [string] message @@ -878,123 +878,4 @@ 7890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
環境によってはもっと短い文字(10文字程度)でも発生することがあり,例えばログインページのテストでは ID が最後まで入力できなくてログインが成功したりしなかったりするなど,影響はかなり大きいと言えます.
対処
v2.0.9 以前の element.value = text
方式であれば,同期的にテキストを入れるのでこのようなことは起こりません.
幸い,Nightmare は Nightmare.action()
を使うことでカスタムアクションを定義できますので,v2.0.9 方式を再現する以下のようなアクションを追加すれば問題ありません.
Nightmare.action('populate', function(selector, text, done) { debug('populate() %s into %s', text, selector); this.evaluate_now(function(selector, text) { var element = document.querySelector(selector); element.value = text; }, done, selector, text); });
また,この件については Issue に登録し,上記アクションを追加する Pull Request も投げておきました.うまく行けば次のバージョンでは populate
アクションが追加されるでしょう.
結論
- v2.1.0 以降の
type()
は,キーボードショートカットやインクリメンタルサーチのテストなど,キーイベントを発火したいときに使うべきで,文字入力には適さない. - 以前の挙動に戻すカスタムアクションを定義して,そっちを使うべき.
- 運がよければ次バージョンで文字入力のためのアクションが追加されるかも.