Spectron
自己紹介
Ruby書いてない歴1年ちょっと
最後にまともに書いたのはGrapeでWeb API
JavaScript歴だいたい10ヶ月
Lambdaやテストでちょっと書いたことあったくらい
昨年春から某デスクトップアプリ開発へ
優しい先輩から口酸っぱく「テストねぇから!」の脅し
Ruby ‑> JavaScript(Node) あるある
 return を書かない
> hoge = function hoge() { "hogehoge" }
[Function: hoge]
> hoge()
undefined
 0 にハマる
> [0, 1, null].filter(val => { return val })
[ 1 ]
 this にハマる
NG : thisは呼び出し元=== ary1
convert() {
this.ary1.forEach(function(val) {
this.ary2.push(this.str2num(val))
})
}
OK : thisを渡す
convert() {
this.ary1.forEach(val => {
this.ary2.push(this.str2num(val))
}, this)
}
OK : Allow functionを使う(thisを語彙的に束縛する)
convert() {
this.ary1.forEach(val => {
this.ary2.push(this.str2num(val))
})
}
非同期処理がまどろこっしい
promise/async/awaitは最初の難関
これらがある時代に生まれたのでcallback地獄はさほど経験し
なかった
Fileの存在チェックで fs.existsSync() ということにむしろ驚く
HTTPリクエスト一個送るのも辛い
本題
Spectron
Electronアプリのテストフレームワーク(公式)
ElectronのAPIをフルサポート
裏側はWebdriver
Electron
デスクトップアプリケーションフレームワーク
Chromium + Node.js
HTML + CSS + JavaScript
クロスプラットフォーム対応
JS界でのこの分野はまだメジャー (参考)
Electronおさらい
とっとるびーでも何度か発表あり(20th, 25th)
メインプロセス:
Windowを起動したりメニュー操作イベントを受け取ったり
Node.js
レンダープロセス
ブラウザ(HTML + CSS + JavaScript) をレンダリング
Chromium
プロセス間通信はipcを用いる。イベントフックしてやり取りする
Electron App
your-app/
├── package.json
├── main.js
└── index.html
const { app, BrowserWindow } = require('electron')
function createWindow () {
win = new BrowserWindow({ width: 800, height: 600 })
win.loadFile('index.html')
}
app.on('ready', createWindow)
$ npm run start
demo
https://electronjs.org/blog/simple‑samples
https://github.com/electron/simple‑samples
Webdriver
Webdriver
ブラウザのテストツール
ページ上の要素を操作できる(クリック, 入力等)
ページ上の要素の有無や状態をテストできる
WebDriverIO === WebDriver binding for Node.js
ElectronアプリをSpectronでテストする
HTML + CSS + JavaScriptで作られたデスクトップアプリを、
WebDriverでテストする。
単純明快で、普通のアプリならテストしやすいよね~と思うじゃないで
すか。
例えばslack
入力・表示のテストとか
チャンネル切り替えとか
設定変更とか
コマンドとか
=> ブラウザのレンダリング領域のテストは容易
http://webdriver.io/api.html
Demo : npm runしてテストする
https://github.com/electron/spectron#usage
electronアプリを起動してWindowの数をテストするだけ
Demo : インストールした.appをテストする
内容は同じ
某PC用アプリケーション
Viewerアプリ
初手は画像か動画をメニューから開くところから
メニュー操作
メニュー操作vs Spectron
https://github.com/electron/spectron/issues/21
(メニューのテストってどうしたらいいの?)
メニュー操作vs Spectron
(メニュー取得するAPIにはまだ対応してしてないよ )
メニュー操作vs Spectron
SpectronからElectronアプリケーションのMenuを操作する
MenuのAPIがJSONにserializeされていないので、process間通信
が必要なSpectronでは操作が難しいというのがその理由のようで
す。 同じ様に、dialogモジュールの操作も出来ません。
アプリケーションの操作をChrome Driverを通じて行うのが基本理
念のSpectronでは、これらのnativeなAPIを必要とする操作に対す
るサポートはまだ不十分、というのが実情です。
spectron‑fake‑menu
SpectronはElectronのChrome Driverを通じてアプリケーションの
操作を実行できるのですが、メニューの操作には現状対応していま
せん。(詳しくは後述します。)
そこでspectron‑fake‑menuというSpectronからメニューの操作が
できるnpmを作成しました。
https://github.com/joe‑re/spectron‑fake‑menu
spectron‑fake‑menu でやっていること
https://github.com/electron/spectron/issues/94
Electronは‑‑requireオプションで、Electronの起動直前にスクリプ
トを差し込むことができます。
これを利用して、Mainプロセスにspectron‑fake‑menu用のイベン
トを受け取る口を仕込んでいます。
require('electron').ipcMain.on(
'SPECTRON_FAKE_MENU/SEND', (_e, labels) => {
const item = findItem(
require('electron').Menu.getApplicationMenu().items,
labels
);
item.click();
});
イメージ: spectron‑fake‑menu
続spectron‑fake‑menu
つらいところ: メニューの操作は非同期処理になる
https://github.com/joe‑re/spectron‑fake‑menu/tree/master/example
クリックした後の処理が終わったことを知る術がない
HTMLがレンダリングされて、何かしらが変わるのを待つ必要
がある
 setInterval() &  clearInterval() 
変わるものがないような処理は・・・sleepのようなものを使
う・・・?
メニューの数だけクリックした後の処理終了を待つ関数が必要にな
る
もちろん共通化できるものもあるが・・・
spectron‑fake‑menu example
describe('Increment', function() {
this.timeout(10000);
let app;
beforeEach(function() {
app = createApplication();
return app.start();
});
afterEach(function() {
return app.stop();
});
it('increment count', () => {
return app.client.waitForExist('#count')
.then(() => {
fakeMenu.clickMenu('Count', 'Increment');
return waitForChangeCount(app, '1');
})
.then(() => assert.ok(true));
});
});
waitForChangeCount()
export function waitForChangeCount(app, count) {
return new Promise((resolve, _reject) => {
const timer = setInterval(() => {
app.client.getText('#count').then((text) => {
if (text === count) {
clearInterval(timer);
resolve();
}
});
}, 1000);
});
}
とは言え、メニュー操作はクリア
しかし
某PCアプリの最初の操作
1. メニュー : ファイル> 開く
2. ファイル選択のダイアログが開く
3. 画像or 動画を選択する
4. 3が再生される
しかし
MenuのAPIがJSONにserializeされていないので、process間通信
が必要なSpectronでは操作が難しいというのがその理由のようで
す。 同じ様に、dialogモジュールの操作も出来ません。
同じ様に、dialogモジュールの操作も出来ません。
dialogモジュールのテスト時のモックも同じ理屈ででき
る
https://github.com/electron/spectron/issues/112
ただしファイルはほぼ固定になる。
テストによってモックを差し替えるようにする等工夫は必要。
後はテストをするだけ
Electronアプリは基本的にrenderプロセス側に対する操作駆動
HTMLに対する操作に対するテストを書く
WebDriverで大抵カバーできる
WebDriverでテストしにくいことはできない
demo
SpectronでMenuからファイルを開く
Spectron API
ElectronのAPIをフルサポート
アプリの操作, 状態確認
 start() 
 stop() 
 restart() 
 isRunning() 
 getSetting() 
ログ取得
 client.getMainProcessLogs() 
 client.getRenderProcessLogs() 
Windowの状態確認
 client.getSelectedText() 
 client.getWindowCount() 
 client.waitUntilTextExists(selector, text, [timeout]) 
 client.waitUntilWindowLoaded([timeout]) 
 client.windowByIndex(index) 
wait系のAPIが2つしかない
WebDriverを使ったことがある人は「待つ」処理がどれだけ必要か分か
るはず。
普通にテストを書いていると「xxの処理を待つ」が頻発。
 client.waitUntilTextExists(selector, text, [timeout]) 
 client.waitUntilWindowLoaded([timeout]) 
をラップした便利関数が多数必要になる。
例) window数がN個になるまで待つ
継続的インテグレーション
Travis CI
### .travis.yml ###
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3 # give xvfb some time to start
実装例& 一部Demo
まとめ
WebDriverを使ったテストが書ける
メニュー操作・ダイアログ操作は辛い
wait地獄と上手に付き合いましょう
CIもできる
Spectron

Spectron