はじめに
こんにちは、ラクマの@itinaoです。
E2Eテストについて、概要からお手軽に試す方法までを全5編で記載しています。
- E2Eテスト: 導入の必要性・何を導入するのか
- E2Eテスト: TestCafeを試す
- E2Eテスト: Github Actions上でTestCafeを試す(PCブラウザ編)
- E2Eテスト: Github Actions上でTestCafeを試す(モバイルブラウザ編) ← 今回はココ
- E2Eテスト: Selenium Gridを試す
前回の記事の続きで、Github Actions上でTest Cafeを実行させます。
今回はモバイル端末の実行に関して説明していきます。
Github Actions上でTestCafeを実行する
ローカルでの実行について記載した記事では3パターン記しましたが、 Github Actions上で完結する手法としてBrowser Stack以外について書いていきます。
Android: Google Chrome Mobile
Emulatorの起動について
Github Actions上でEmulatorを起動させる必要があります。
AzureのDevOpsを参考にしていますが、このようなスクリプトで起動することができます。
#!/usr/bin/env bash # Install AVD files echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;android-27;google_apis;x86' # Create emulator echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n xamarin_android_emulator -k 'system-images;android-27;google_apis;x86' --force $ANDROID_HOME/emulator/emulator -list-avds echo "Starting emulator" # Start emulator in background nohup $ANDROID_HOME/emulator/emulator -avd xamarin_android_emulator -no-snapshot > /dev/null 2>&1 & $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82' $ANDROID_HOME/platform-tools/adb devices echo "Emulator started"
実行する
ローカルで動かした内容の2パターンでも、安定して動作しました。
- Appium
- TestCafeのテスト対象をremoteにする
「TestCafeのテスト対象をremoteにする」についてコードを載せていきます。
name: E2E Test on: [push] jobs: install-apps: name: install strategy: matrix: os: [macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Cache node modules uses: actions/cache@v2 env: cache-name: cache-node-modules with: path: '**/node_modules' key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - name: yarn install run: yarn install --frozen-lockfile e2e-mobile-chrome: name: Run tests mobile chrome on macos runs-on: macos-latest timeout-minutes: 20 needs: [install-apps] env: emulator-name: 'android_emulator' system-image: 'system-images;android-30;google_apis;x86_64' steps: - uses: actions/checkout@v2 - name: Cache node modules uses: actions/cache@v2 env: cache-name: cache-node-modules with: path: '**/node_modules' key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - name: Run Android Emulator run: | echo "Creating emulator" echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install "${{ env.system-image }}" echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n "${{ env.emulator-name }}" -k "${{ env.system-image }}" --force $ANDROID_HOME/emulator/emulator -list-avds echo "Starting emulator" nohup $ANDROID_HOME/emulator/emulator -avd "${{ env.emulator-name }}" -no-snapshot > /dev/null 2>&1 & $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82' $ANDROID_HOME/platform-tools/adb devices # Chrome初回起動時の画面起動をなくす(Azureの例にはない $ANDROID_HOME/platform-tools/adb shell 'echo "chrome --disable-fre --no-default-browser-check --no-first-run" > /data/local/tmp/chrome-command-line' echo "Emulator started" - name: Run TestCafe run: yarn run e2e env: BROWSER: mobile-chrome - uses: actions/upload-artifact@v2 if: always() with: name: reports-macos-mobile-chrome path: reports/
BROWSERを mobile-chrome
として書いたので、
e2e/runner.jsの起動部分をこのように追記します。
const runE2E = (served) => { let command switch (`${process.env.BROWSER}`) { case 'safari': command = `./e2e/run_safari.sh` break; case 'mobile-chrome': command = `./e2e/run_mobile_chrome.sh` break; default: command = `yarn run testcafe ${process.env.BROWSER}` } const e2e = spawn(command) e2e.stdout.on('data', (data) => { consola.log(`${data}`) }) e2e.stderr.on('data', (data) => { consola.error(`${data}`) }) e2e.on('close', (code) => { served.kill() process.exit(code) }) }
そして、上記で指定しているスクリプトを追加します。
e2e/run_mobile_chrome.sh
export HOSTNAME=`/sbin/ifconfig en0 inet | grep 'inet ' | sed -e 's/^.*inet //' -e 's/ .*//'` export PORT1=1337 export PORT2=1338 yarn run testcafe remote --hostname ${HOSTNAME} --ports ${PORT1},${PORT2} & pid=$! sleep 5 adb shell am start http://${HOSTNAME}:${PORT1}/browser/connect wait $pid
これで動きました ○
ただし、Emulatorの作成・起動に6分くらいかかるので、作成したEmulatorはキャッシュできたら良いかなと思いました。
iPhone: Mobile Safari
Simulatorについて
こちらに記載のあるSimulatorは自由に扱うことができます。
https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md
実行する
ローカルで動かした内容の2パターンで検証しましたが、どちらも安定的に動かすことができませんでした。
- Appium
- TestCafeのテスト対象をremoteにする
Appium
何度も試してみましたが、TestCafeの実行で下記のエラーが発生して失敗します。
ERROR Unable to establish one or more of the specified browser connections. This can be caused by network issues or remote device failure.
一つのエラーが発生するだけであれば調査をしやすいのですが、 Appium側のログにはエラーが出ていない場合もあり、エラーが出ている場合は下記のどれかが表示されていました。 (これらがTestCafe失敗の直接原因かも判断つかず)
[XCUITest] UnknownError: An unknown server-side error occurred while processing the command. Original error: Could not proxy command to the remote server. Original error: connect ECONNREFUSED 127.0.0.1:8100
[W3C] Encountered internal error running command: Error: Missing parameter: appIdKey
[W3C] Encountered internal error running command: Error: Could not navigate to webview! Err: Command 'lsof -aUc launchd_sim' exited with code 1
ログが出ていないということはタイムアウトか?という可能性もあるので、設定できるパラメータを色々試してみましたが、どれも解決には至りませんでした。
http://appium.io/docs/en/writing-running-appium/caps/ http://appium.io/docs/en/writing-running-appium/server-args/ https://github.com/appium/appium-xcuitest-driver
TestCafeのテスト対象をremoteにする
こちらも安定して実行することはできませんでした。。
name: E2E Test on: [push] jobs: install-apps: name: install strategy: matrix: os: [macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Cache node modules uses: actions/cache@v2 env: cache-name: cache-node-modules with: path: '**/node_modules' key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - name: yarn install run: yarn install --frozen-lockfile e2e-mobile-safari: name: Run tests mobile safari on macos runs-on: macos-latest timeout-minutes: 20 needs: [install-apps] steps: - uses: actions/checkout@v2 - name: Cache node modules uses: actions/cache@v2 env: cache-name: cache-node-modules with: path: '**/node_modules' key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - name: Run iPhone Emulator run: nohup xcrun simctl boot "iPhone 12" > /dev/null 2>&1 & - name: Run TestCafe run: yarn run e2e env: BROWSER: mobile-safari - uses: actions/upload-artifact@v2 if: always() with: name: reports-macos-mobile-safari path: reports/
BROWSERを mobile-safari
として書いたので、
e2e/runner.jsの起動部分をこのように追記します。
const runE2E = (served) => { let command switch (`${process.env.BROWSER}`) { case 'safari': command = `./e2e/run_safari.sh` break; case 'mobile-safari': command = `./e2e/run_mobile_safari.sh` break; case 'mobile-chrome': command = `./e2e/run_mobile_chrome.sh` break; default: command = `yarn run testcafe ${process.env.BROWSER}` } const e2e = spawn(command) e2e.stdout.on('data', (data) => { consola.log(`${data}`) }) e2e.stderr.on('data', (data) => { consola.error(`${data}`) }) e2e.on('close', (code) => { served.kill() process.exit(code) }) }
そして、上記で指定しているスクリプトを追加します。
e2e/run_mobile_safari.sh
export HOSTNAME=localhost export PORT1=1337 export PORT2=1338 yarn run testcafe remote --hostname ${HOSTNAME} --ports ${PORT1},${PORT2} & pid=$! sleep 30 xcrun simctl openurl booted http://${HOSTNAME}:${PORT1}/browser/connect wait $pid
こちらも色々試しましたが、成功率は3割くらいでした。
このようなエラーが表示されます。 Github Actions上のMacのSafari実行時にも起こるエラーなので、根本原因は一緒かもしれません。
... [log] Connecting 1 remote browser(s)... Navigate to the following URL from each remote browser. [log] Connect URL: http://localhost:1337/browser/connect [log] CONNECTED Safari 14.0.3 / iOS 14.4 [log] ERROR NativeBinaryHasFailedError: The find-window process failed with the 1 exit code. Process output: LSOpenURLsWithRole() failed for the application /Users/runner/work/hogehoge/hogehoge/node_modules/testcafe-browser-tools/bin/mac/TestCafe Browser Tools.app/Contents/MacOS/testcafe-browser-tools with error -10810. at ChildProcess.<anonymous> (/Users/runner/work/hogehoge/hogehoge/node_modules/testcafe-browser-tools/src/utils/exec.js:73:24) at ChildProcess.emit (events.js:315:20) at ChildProcess.EventEmitter.emit (domain.js:467:12) at Process.ChildProcess._handle.onexit (internal/child_process.js:277:12) at Process.callbackTrampoline (internal/async_hooks.js:131:14) Type "testcafe -h" for help. Error: error Command failed with exit code 1. [log] info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
テスト実行のトリガー
今回は push
のタイミングでGithub Actionsを起動させてましたが、
workflow dispatch
で任意のタイミングで起動させることもできるので、Slackから特定コマンドで実行させるとか、そういう連携もできます。
https://docs.github.com/ja/actions/reference/events-that-trigger-workflows
おわりに
モバイルの実行は不安定ということもあり、素直にBrowserStack使ったほうがベターかなという印象でした。
何にせよ、こんなことまで出来るGithub Actionsスゴイ。。ってなりました。
おまけ
これを試してる時に得た知識を書き残しておきます。
Github Actionsの実行は、無料枠で収まるように注意する
プランによりけりですが、無料枠で収まるように実行した時間はチェックする必要があります。
https://github.com/settings/billing
GitHub Enterprise Cloud なら5万分だし余裕じゃん、とか思うかも知れませんが、 Macで何も考えずに実行しまくると余裕で上限にいってしまいます。
リセットのタイミングは「毎月1日の17時」でした。
JOBのタイムアウトを指定する
デフォルトのタイムアウトは各ジョブで360分です。
意図せず実行が長くなることもあるので、timeout-minutes
は指定した方が安全です。
https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions
Github Actionsのキャッシュについて
- キャッシュ上限: 1つのリポジトリ内のすべてのキャッシュで5GBまで
- 上限を超えた場合、合計サイズが5GB以下になるまでキャッシュを退去させる
- 削除タイミング: 7日間以上アクセスされないとキャッシュが削除される
Github ActionsのJOB間でのファイル共有、ファイル出力について
artifactsという機能で行えます。 設定で変えることもできますが、デフォルトでは90日間保存されます。
https://docs.github.com/ja/actions/guides/storing-workflow-data-as-artifacts