Angular5 + Cordova なアプリで Protractor + Appium による iOS シミュレータ・iOS 実機 E2E テストを実施する際の備忘

以前、AngularJS ベースの Cordova アプリを E2E テストするための記事を書いた。

以下が iOS シミュレータでの実施用。

コチラが iOS 実機で実施するための追加情報。

この頃から時は流れ、Angular5 にバージョンアップした Cordova プロジェクトで、E2E テストをやろうと思ったのだが、少々設定の類に違いがあって苦戦したので備忘をまとめる。

ベースの環境構築は上述の2つの記事がそのまま参考になるはずなので、あわせて読んでいただければ。

目次

プロジェクトのベースは Angular CLI で構築する

Angular5 プロジェクトのベースは Angular CLI の $ ng new で作成しよう。この後に Cordova アプリとしての修正を行う。こうすれば Protractor を動作させる e2e コマンドが最初から用意されているので、カスタマイズがしやすい。

このあたりは、以下に Angular4 版ではあるが Cordova アプリ化したボイラープレートを用意したので、参考にしてほしい。

e2e コマンドを iOS 向けに変える

iOS で E2E テストを動作させるため、Appium を起動した状態で E2E テストを開始する必要がある。そこで、package.json に Appium 起動コマンドと iOS で E2E テストを行うためのコマンドを用意する。

上述の E2E 実行コマンドは若干古い。以下のように変えると余計なビルド処理などが走らず起動が高速化する。

// package.json
"e2e-ios-exec": "ng e2e --config ./protractor.ios.conf.js --no-webdriver-update --serve=false",

アプリは E2E テスト実行前に Cordova ビルドして iOS シミュレータなり iOS 実機なりに入れておかないとダメ。WebDriverAgent に関しては iOS シミュレータは勝手に入れてくれるけど、iOS 実機の場合は手動で事前に入れておかないといけない。

Protractor 設定ファイルにタイムアウト設定を追加すると安定する

Angular5 系にバージョンアップして、protractor.conf.js にてタイムアウト設定を追加してやらないと、テストの開始処理などがうまくいかなかった。

ベースはこの protractor.ios.conf.js の記載のままで良いのだが、capabilities に以下の2つのタイムアウト処理を追加する。

capabilities: {
  // …中略…
  
  // グローバルなタイムアウト設定
  allScriptsTimeout: 600000,
  // レスポンスのタイムアウト設定
  webkitResponseTimeout: 10000
}

browser.get() による画面遷移ができない

AngularJS 時代は、browser.get('') と書くことで、ページを読み直してトップページに戻れたりできたのだが、Angular5 だと一切の browser.get() が正しく動作しなかった。

良い対処法が見つからなかったので、仕方なく各テストの beforeAll() にて、要素クリックなどで頑張って初期画面に戻してから当該画面に移動する、という一連の処理を書いた。何か良い方法はないのだろうか…。

テストケース数が増えると後続のテストが失敗することがある

イマイチ原因がハッキリしていないが、WebDriverAgent がバックグラウンドで起動している状態でアプリを触っていると、段々と動きがもっさりしてくる。

特定の処理を待つために browser.sleep() を使っていたりすると、動作がもっさりしてくるせいで待機時間が不十分になったりする。

自動操作される E2E テストにおいても、テストケース数が増えてくると、後半のテストが失敗しやすくなった。勿論、そのテストだけを動作させた時は成功するので、テストコードに誤りがあるワケではないのだ。

コレも良い解決策が見つからず、テストを分割して実行することで、テストとして一応の担保をとるようにした。自動回帰テストみたいなことがうまくできないのでつらい…。

画面遷移などが高速で行われると HTML 構造に不整合が生じたりする

E2E テストはキー入力やクリック操作が人間の限界を超えた速度で実行されるので、前画面の処理が完了しないまま次画面に遷移してしまったりすることで、HTML の構造に不整合が発生し、正常にテストができなくなることがある。

例えば ngx-bootstrap のモーダルのような、アニメーション動作を含むライブラリを使ったりしていると、この問題に遭遇しやすい。browser.wait()browser.sleep() で待ってやれば良いのかというとそれだけでもなく、前述の「テストケース数に比例して動作がもっさりする問題」も絡んできて、見た目上は表示されている要素が見つからないなど、おかしな状態になりやすい。

コチラも残念ながら良い回避策がなく、テストを分割して実行することで逃げてしまった。

少しだけケース数が多いテストを軽く実行させる方法

テストを分割実行するには、fdescribe() などを手で書き加えて、毎回テストし直すやり方を取ったが、protractor.conf.js でスペックファイルの指定方法を変えるだけでも、少し動作が軽くなった。コレで回避できるなら、楽なのでコチラを選びたい。

// 最初はこうなっていると思うので…
specs: ['./e2e/**/*.e2e-spec.ts'],

// このように画面ごとぐらいでファイルを列挙する
specs: [
  './e2e/login-page/**/*.e2e-spec.ts',
  './e2e/news-page/**/*.e2e-spec.ts',
  './e2e/settings-page/**/*.e2e-spec.ts',
  './e2e/user-page/**/*.e2e-spec.ts'
],

自動操作の処理間隔を開ける方法

以下のようなコードをテストクラスの最上部に書いておくと、Control Flow の処理ごとに動作を止められる。

いくつかのテキストボックスに値が入力されるのをゆっくり見たい時とかに、いちいち browser.sleep() などと書かなくても良くなる。うまく使えば「もっさり問題」対策にもなるかもしれない。

// 前略… import 文など…

const origFn = browser.driver.controlFlow().execute;
browser.driver.controlFlow().execute = () => {
  const args = arguments;
  origFn.call(browser.driver.controlFlow(), () => {
    // 100ms 遅らせる
    return protractor.promise.delayed(100);
  });
  return origFn.apply(browser.driver.controlFlow(), args);
};

describe('各画面のテスト', () => {
  // 以下略…
});

いずれも、良い感じの根本解決作がなく、せっかく楽できそうな E2E テストなのに次から次へと違う問題が出てくる感じでつらみがある…。

今回紹介した事象に関して、より良い対処法をご存知の方は、ぜひ情報をお寄せください。