Slide 1

Slide 1 text

Effective E2E Test In An Electron Application @joe­re

Slide 2

Slide 2 text

Who am I? twitter: @joe_re github: @joe­re 西日暮里.rb オーガナイザ working in freee.K.K

Slide 3

Slide 3 text

最近本を書きました

Slide 4

Slide 4 text

今日はElectron の E2E テストの書き方について 3 つのポイントをお話します

Slide 5

Slide 5 text

Spectron

Slide 6

Slide 6 text

What is Spectron? Electron アプリケーションのE2E テストを実行するための テストツール テストコードの中でElectronAPI を叩くことができる ChromeDriver + WebDriverIO を通じてアプリケーションの 操作、情報取得が行える

Slide 7

Slide 7 text

Demo CafePitch https://github.com/joe­re/cafe­pitch Electron 製のマークダウンで書けるプレゼンツール このスライドもCafePitch で作っています

Slide 8

Slide 8 text

Example const app = new spectron.Application({ path: 'path/to/your/app' }); describe('application launch', function () { this.timeout(10000); beforeEach(function () { return app.start(); }); afterEach(function () { return app.stop(); }); it('shows an initial window', function () { return app.client.getWindowCount().then(function (count) { assert.equal(count, 1); }); }); });

Slide 9

Slide 9 text

Strong Points Webdriver.io を通じて、任意のDOM 要素にアクセスして テストが書ける Electron のAPI をテストコードの中から透過的に呼び出す ことができる Electron 内部のChromium をそのまま使うのでセットアップ の手間がない( 基本的にパスを指定するだけ) Travis やAppVeyor などのCI サービスをサポートしている

Slide 10

Slide 10 text

しかし、E2E テストは 壊れやすくメンテナンス が大変...

Slide 11

Slide 11 text

Effective1: Using Page Object Pattern

Slide 12

Slide 12 text

Why maintenance of E2E tests are difficult? E2E テストは画面変更に弱い 画面を変更すると関連するすべてのテストケースに 影響がある 画面要素へのアクセスが大量に発生するので 修正すべき箇所を把握しづらい

Slide 13

Slide 13 text

Page Object Pattern is... テストコードをページとシナリオに分ける アプリケーションの1 つの画面をオブジェクトと捉える (Page Object) Page Object にはアサーションは含まず、 ページ操作を振る舞いとして記述する アサーションはシナリオに記述する (Page Object はライブラリとして利用されるイメージ) PageObject にはアサーションは含まない 他にも原則があるので、詳しくはSelenium の公式ページへ (https://github.com/SeleniumHQ/selenium/wiki/PageObjects)

Slide 14

Slide 14 text

Strong Points シナリオから煩雑な画面操作が取り除かれるので、 見通しが良くなる 複数のシナリオから1 つのPage Object を利用するので、 画面変更時にはPage Object を変更するだけで良い ( 画面変更に強い) Page Object の振る舞い === 画面操作となるので、 変更箇所の把握が用意になる

Slide 15

Slide 15 text

Example export default class SlideEditorPage { constructor(private client: Client) {} inputText(text: string): WebdriverIO.Client { return this.client.waitForExist('#editor').then(() => { this.client.setValue('#editor textarea', text); }); } getSlideHtml(): WebdriverIO.Client { return this.client.waitForExist('.slide-content') .then(() => this.client.getHTML('.slide-content')) .then((html) => typeof html === 'string' ? html : html.join()); } ... }

Slide 16

Slide 16 text

話は変わって

Slide 17

Slide 17 text

皆さんE2E テスト 書いてますか

Slide 18

Slide 18 text

テストが失敗したとき どうしてますか

Slide 19

Slide 19 text

E2E テストの失敗は 原因を追うのが難しい...

Slide 20

Slide 20 text

そこで

Slide 21

Slide 21 text

Effective2: Record the state at the time of test failure

Slide 22

Slide 22 text

Electron + Spectron のAPI を利用して 失敗時の状態を取得して記録する 画面キャプチャ BrowserWindow.capturePage() ログ client.getRenderProcessLogs() client.getMainProcessLogs()

Slide 23

Slide 23 text

Demo

Slide 24

Slide 24 text

Example function capturePage(app: Application, testName: string) { return app.browserWindow.capturePage().then((img) => { fs.writeFileSync(`${outputDir}/capture_${testName}.png`, img); }); } function reportLog(app: Application, testName: string) { return Promise.all([ app.client.getRenderProcessLogs(), app.client.getMainProcessLogs() ]).then(([ rendererLogs, mainLogs ]) => { const logs = JSON.stringify({ renderer: rendererLogs, main: mainLogs }); fs.writeFileSync(`${outputDir}/logs_${testName}.txt`, logs, "utf8"); }); }

Slide 25

Slide 25 text

Effective3: Mock a native API

Slide 26

Slide 26 text

現在のSpectron(v3.6.2) では メニューの操作やファイルダイアログの 操作はできない https://github.com/electron/spectron/issues/21 Menu のAPI がJSON にserialize されていないので、process 間通信が必要なSpectron では操作が難しい アプリケーションの操作をChrome Driver を通じて行うのが Spectron の基本なので、これらのnative なAPI を必要とする 操作に対するサポートはまだ不十分

Slide 27

Slide 27 text

ところでElectron は ネイティブなAPI 呼び出しも、 JavaScript から 扱えるようにするため、 JavaScript のレイヤーで ラップしている

Slide 28

Slide 28 text

つまりElectron のAPI を 局所的に差し替えてしまえば テストができる

Slide 29

Slide 29 text

Electron のrequire オプションを利用する Electron は --require オプションで、起動直前にスクリプト を読み込ませることができる これを利用して、テスト実行時だけ1 部のElectron のAPI を 書き換えるスクリプトを読み込む

Slide 30

Slide 30 text

Menu は作ってライブラリにした spectron­fake­menu https://github.com/joe­re/spectron­fake­menu Spectron から任意のラベルを持つメニューの クリックができる fakeMenu.apply(app); // apply fake menu fakeMenu.clickMenu('Config'); // 'Config' Menu click fakeMenu.clickMenu('File', 'CloseTab'); // File->CloseTab Menu click

Slide 31

Slide 31 text

ダイアログはこんな感じで差し替える const { dialog } = require('electron'); function mockShowSaveDialog() { return 'sandbox/test.md'; }; function mockShowOpenDialog() { return [ 'sandbox/test.md' ]; }; dialog.showSaveDialog = mockShowSaveDialog; dialog.showOpenDialog = mockShowOpenDialog;

Slide 32

Slide 32 text

Demo

Slide 33

Slide 33 text

E2E テストでモックを使うのは 基本的にマナー違反なので、 あくまで必要最小限にしましょう

Slide 34

Slide 34 text

おさらい Effective1: Using Page Object Pattern Effective2: Record the state at the time of test failure Effective3: Mock a native API

Slide 35

Slide 35 text

ところでこの話は全部本 に書いてあります( 宣伝)

Slide 36

Slide 36 text

Thanks for your attention!