NightmareでE2Eテストをするときに役立った独自アクションのメモ
アドベントカレンダー ソフトウェアテスト6日目担当の @ks888 です。
ソフトウェアテスト Advent Calendar 2016 - Qiita
最近はNightmareでE2Eテストを書くことが増えてきました。Nightmareを使うと、例えば以下のような感じでテストを書けます。
var Nightmare = require('nightmare'); var expect = require('chai').expect; // jshint ignore:line describe('test yahoo search results', function() { it('should find the nightmare github link first', function(done) { var nightmare = Nightmare() nightmare .goto('http://yahoo.com') .type('form[action*="/search"] [name=p]', 'github nightmare') .click('form[action*="/search"] [type=submit]') .wait('#main') .evaluate(function () { return document.querySelector('#main .layoutMiddle a').href }) .end() .then(function(link) { console.log(link); expect(link).to.equal('https://github.com/segmentio/nightmare'); done(); }) }); });
コード中で出てくるclick
とかwait
とかはNightmareではアクションと呼ばれています。2016年12月時点では以下のようなアクションが用意されています(各アクションの意味は公式のREADMEを参考にして下さい)。
engineVersions title url visible exists click mousedown mouseover type insert check uncheck select back forward refresh wait evaluate inject viewport useragent scrollTo screenshot html pdf cookies.get cookies.set cookies.clear cookies.clearAll authentication
アクションは自作することもできて、いくつか追加しておくと、テストを書くのが更に捗ります。今回はメモがてら、これまでに作った独自アクションを残しておきます。
HTTPヘッダーを付与する(addHeaderアクション)
var Nightmare = require('nightmare'); // アクションを追加する Nightmare.action('addHeader', function (name, options, parent, win, renderer, done) { parent.respondTo('addHeader', function (headerKey, headerValue, done) { win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => { details.requestHeaders[headerKey] = headerValue callback({cancel: false, requestHeaders: details.requestHeaders}) }) done() }) done() }, function (headerKey, headerValue, done) { this.child.call('addHeader', headerKey, headerValue, done) }) // 利用例 var nightmare = Nightmare() nightmare .addHeader('Key', 'Value') .goto('http://localhost:8080') .end() .then()
任意のHTTPヘッダーを付与するアクションです。公式でheader()
というメソッドが既にあるのですが、header()
で指定したヘッダーはgoto()
で指定したページをロードするときしか使われず、JS等で動的にロードする場合には使われません。addHeader()
はJS等で動的にロードする場合にも指定したヘッダーが付きます。
ちなみにaddHeader()
を複数回呼んでも、最後の呼び出しで指定したHeaderしか付きません。ちょっといけてないですが、今のところ困ってないので。。
特定URLへのアクセスを遮断する(blockAccessアクション)
var Nightmare = require('nightmare'); // アクションを追加する Nightmare.action('blockAccess', function (name, options, parent, win, renderer, done) { parent.respondTo('blockAccess', function (blockUrls, done) { win.webContents.session.webRequest.onBeforeRequest({ urls: blockUrls }, (details, callback) => { callback({cancel: true}) }) done() }) done() }, function (blockUrls, done) { this.child.call('blockAccess', blockUrls, done) }) // 利用例 var nightmare = Nightmare() nightmare .blockAccess(['http://localhost:8080/*']) .goto('http://localhost:8080') .end() .then()
指定したパターンにマッチしたURLへのアクセスを全てキャンセルします。 例えばGoogleAnalyticsサーバへのアクセスをキャンセルすると、E2EテストがGoogleAnalyticsのデータに影響を与えるのを防ぐことができます。
条件付きでクリックする(clickIfアクション)
var Nightmare = require('nightmare'); // アクションを追加する Nightmare.action('clickIf', function (condFn, selector, done) { var clickIfFn eval( "clickIfFn = function() {" + " if (" + condFn + "()) {" + " document.activeElement.blur();" + " var element = document.querySelector('" + selector + "');" + " if (!element) {" + " throw new Error('unable to find element by selector: " + selector + "');" + " }" + " var event = document.createEvent('MouseEvent');" + " event.initEvent('click', true, true);" + " element.dispatchEvent(event);" + " }" + "}" ); this.evaluate_now(clickIfFn, done) }) // 利用例 var nightmare = Nightmare() nightmare .goto('https://github.com/') .clickIf(function() { return document.querySelector('#user-links') === null }, '.site-header-actions > a') .end() .then()
第一引数で与えた関数がtrueを返した場合のみ、第二引数が指定する要素をクリックします。関数はElectron内で実行されます。evaluate()
とclick()
を組み合わせても同じことができますが、こちらの方がテストの可読性が高くなるかと思っています。
独自アクションを書く上で参考になる情報
自分でアクションを書く際には、以下のリンクが役立ちました。
参考になれば幸いです。新しいアクションを書いたら、また追記します。