見出し画像

GitHub ActionsでE2Eのクロスブラウザテストを実装してみる

ごあいさつ

はじめまして!テストの自動化屋さんのイシイです。このたび、ほかの社員もすなるnoteといふものを私もしてみむと試みる次第です。

さて、本記事で取り上げるトピックですが、表題の通り、
「GitHub Actions」で「クロスブラウザテスト」を実装するには?
というテーマで筆を執らせていただきます。

対象読者は「自動テストスクリプトは書けるけど、CI/CDはこれからやってみる。テスト対象の環境はすでに整っていて、気にしなくて大丈夫」という方です。ばっちり対象にハマらなくても、お時間ありましたらお読みいただけますと幸いです!

まずはじめに(1/2):GitHub Actionsとは?

この記事に辿り着かれた方にはおそらく説明不要とは思いますが、念のため。GitHub Actionsは、GitHubが提供するYAMLファイル1つで動くCI/CDサービスです。

GitHubへのプッシュをトリガーとして、ワークフローと呼ばれる一連の処理を実行する、というのがもっとも一般的な使われ方ですね。

このワークフロー、もちろんそれ以外の実行方法も用意されており、

・マニュアル実行
・スケジュール実行
・API打鍵によるリモート実行

などが可能です!そして無料!!(※実行時間などに制限あり)
素晴らしいですね。

実行環境としてUbuntu, Windows, Macのエミュレータを公式に用意してくれているのもありがたいです。 
※ https://github.com/actions/virtual-environments
メジャーなブラウザや、ブラウザを自動操作するためのWebdriver、複数バージョンのXcodeなどを用意してくれているため、無料ツールにありがちな複雑な環境構築をせずに始められます。まさに、初めてのCI/CDにうってつけなツールですね。

ちなみに、エミュレータにインストールされているサービス一覧は https://github.com/actions/virtual-environments/blob/main/images

配下の.mdファイルから確認できます。

例)Ubuntu 18.04.5 LTS
https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu1804-README.md


まずはじめに(2/2):E2Eテストで使うツールは何?

今回は弊社が提供しているRacineという自動テストツールを用いてご説明いたします。

「いや、Racine興味ないし。知らんし。」という方も大勢いるかと思いますが、ご安心を。どのツールを使っていても、GitHub Actions上で自動テストを実施するのにあたって、大部分は共通しています。

そしてRacineはSelenium(Selenide)/Appiumをベースとした自動テストツールです。日夜Seleniumと向き合っているSeleniumerのみなさんの、きっとお役に立てるかと思います。

過去記事:SHIFTのGUIテストツールRacineとその開発プロセス
※もっと言うとこの記事でもRacineについてはほとんど言及していません…



本題:必要なSTEPについて

GitHub Actionsを実施するためのSTEPは、大まかに以下の流れになるかと思います。

1. GitHubにアカウントを作る
2. GitHubリポジトリに、自動テストに必要なあれこれをプッシュする
3. GitHub Actionsで実行したい処理(ワークフロー)を実装する
4. GitHub Actionsでワークフローを実行する
5. ワークフローの実行結果を取得・確認する

このうち3以外、つまり1,2,4,5はクロスブラウザだから、という部分がないGitHub Actions共通の作業になりますし、他で多く語られているところかと思いますので、本記事では詳細を割愛いたします。
※ニーズがあれば書きますよ!


それでは、“3. GitHub Actionsで実行したい処理(ワークフロー)を実装する”のSTEPをもう少し細分化してみましょう。

3.1.ワークフローを記述するファイルを用意する
3.2.用意したファイルに最低限のテンプレートを記述する
3.3.テンプレートを書き換えて自動テストのための記述にする
3.4.さらに書き換えてクロスブラウザのための工夫を加える

こんなところでしょうか。
さて、ではここから上記の手順を1つずつ見ていきますね!



・STEP1.ワークフローを記述するファイルを用意する

GitHubに作成したリポジトリのルート直下に、以下のようにファイルを生成(push)しましょう。

.github/workflows/crossBrowser.yml

楽勝ですね!STEP1 done!


・STEP2.用意したファイルに最低限のテンプレートを記述する

STEP.1で生成したcrossBrowser.ymlに以下の文章をコピペしましょう。
※YAMLファイルなのでインデントには十分注意してください!

name: ワークフロー サンプル
on:
  workflow_dispatch: 
  # 今回はマニュアル実行しかしないので全てコメントアウトしているが、
  # ここ(on.workflow_dispatch)でイベントのトリガーを定義する
  # pushをトリガーにする場合は下記の通り
  # push:
  #   branches:
  #     - "master"
  #     - "release/*"
  #     - "*/*feature*"
  # 日時をトリガーにする場合は下記の通り記述する
  # なお、Linuxのcronと同じ記法。UTC時刻なので、日本時間-9時間すること
  # 例)火曜日の 12:30 (JST) に処理を実行する。
  # schedule: 
  #  - cron: '30 3 * * 2'

jobs:
  First_job:
    name: ジョブ1個目
    # 実行環境とするエミュレータを指定 
    runs-on: ubuntu-latest 
    # 処理を定義
    steps:
      # githubリポジトリのファイルをGithub Actionsの実行環境にチェックアウトする
      - name: checkout
      # usesは公式と有志による公開アクション。よく行う操作は大抵提供されており、
      # これを活用することで、自前でコーディングする作業量を減らせる
        uses: actions/checkout@v1

詳しい説明はコメントアウトで記載した内容の通りです。
ざっくりまとめると、

・トリガーの指定
・実行環境の指定
・実際の処理(リポジトリから実行環境へのチェックアウト)の定義

をしていますね。
コピペするだけですし、まだまだ楽勝ですね!STEP2 だーん


・STEP3.テンプレートを書き換えて自動テストのための記述にする

書き換え、あるいは、テンプレートのファイルを別名保存して、今度は自動テストのためのワークフローを作成してみましょう!こんな感じになります↓

name: GUI自動テスト サンプル
on:
  workflow_dispatch: 
  # 今回はマニュアル実行しかしないので全てコメントアウトしているが、
  # ここ(on.workflow_dispatch)でイベントのトリガーを定義する
  # pushをトリガーにする場合は下記の通り
  # push:
  #   branches:
  #     - "master"
  #     - "release/*"
  #     - "*/*feature*"
  # 日時をトリガーにする場合は下記の通り記述する
  # なお、Linuxのcronと同じ記法。UTC時刻なので、日本時間-9時間すること
  # 例)火曜日の 12:30 (JST) に処理を実行する。
  # schedule: 
  #  - cron: '30 3 * * 2'

jobs:
  iOS_Auto_Test:
    name: iOS Mobile Test
    # 実行環境とするエミュレータを指定 
    runs-on: macos-latest 
    # 処理を定義
    steps:
      # githubリポジトリのファイルをGithub Actionsの実行環境にチェックアウトする
      - name: checkout
      # usesは公式と有志による公開アクション。よく行う操作は大抵提供されており、
      # これを活用することで、自前でコーディングする作業量を減らせる
        uses: actions/checkout@v1

      # XCodeのバージョンを指定する
      - name: specify valid Xcode Version、
      # 自分で処理を書く場合はrunパラメータを使う。選択したエミュレータによって
      # コマンドが違うので注意 ※WindowsはPowershellで書く必要がある
        run: sudo xcode-select -s /Applications/Xcode_12.2.app

      # Appiumをセットアップするために、前段階のNode.jsをセットアップする
      - name: Set up Node.js
        uses: actions/setup-node@v1
        # usesを使う場合はwithで引数を与える必要がある場合も
        with:
          node-version: '10.16.3'

      # Appiumをセットアップする
      - name: Set up Appium
        run: npm install -g appium@1.18.3

      # Appiumを起動する
      - name: Run Appium Server
        run: |
          cd pc
          appium --log-timestamp --log-no-colors > appium.log &

      # 必要に応じてモバイルエミュレータを起動する(racineの場合不要)
      - name: Run mobile emulator
        run: |
          hogehoge

      # 必要に応じてSUT(テスト対象システム)を起動する
      - name: Run SUT 
        run: |
          fugafuga

      # テスト実行:
      # ディレクトリ移動⇒実行権限変更⇒gradleコマンドによるtest実行~report出力
      - name: run test
        run: |
          cd pc
          chmod +x ../master/gradlew
          ../master/gradlew \
          -D target=iosNative \
          -D appium.capabilities.path=iOS.yml \
          -D selenide.browser=jp.shiftinc.automation.driver.AppiumDriverProvider \
          clean test \
          --tests $TEST_CASE \
          allureReport
        # envを使うことで、run内の文字列を変数化し可読性を上げることが可能
        # 変数の書き方もエミュレータによって違うことに注意
        env:
          TEST_CASE: "*sample_test*"

      # 出力されたログをダウンロードできるようにartifactに格納
      - name: appium log
        uses: actions/upload-artifact@v1
        with:
          name: appium.log
          path: pc/appium.log

      # 同様に、レポートをダウンロードできるようにartifactに格納
      - name: jUnit Report
        uses: actions/upload-artifact@v1
        with:
          name: test_rep
          path: pc/build/reports/tests

      # さらに同様に、レポートをダウンロードできるようにartifactに格納
      - name: Allure Report
        uses: actions/upload-artifact@v1.0.0
        with:
          name: allure-report
          path: pc/build/reports/allure-report
        # 「手前の処理でエラーが発生していても常に実施する」というオプション
        if: always()

これも、詳しい説明はコメントアウトで記載した内容の通りです。

実際の処理の定義が、先ほどはリポジトリのチェックアウトだけでしたが、今回はエミュレータ上での環境構築⇒テスト実行⇒レポート保存と、複数の作業を行っています。

サンプルなので、実際には動かない処理(hogehoge,fugafuga)も記載しています。ご自身で使うときには適切な状態に修正して活用してください。※Appiumを使わない場合はさらにシンプルになりますね!

なお、1点だけ補足すると、上記のサンプルでいろいろなところに出てくるnameですが、これはGitHub Actionsのコンソールに出力される表示名です。任意につけていただいて問題ありません。ただし、一番上の階層のname(”GUI自動テスト サンプル”のところ)については、他と被るとワークフローの一覧にうまく表示されなくなります。名前被りにはご注意ください

ついてこれていますか?大丈夫ですか?私は書き疲れました!

はい、楽勝ですね!STEP3 完了!


・STEP4.さらに書き換えてクロスブラウザのための工夫を加える

では次に、クロスブラウザ対応をしていきましょう!
STEP3ではiOSの1端末(エミュレータ)でのみテストを実施していました。もちろん、同じ要領でワークフローを複数用意しても、OKです。

トリガーを同じにすれば一緒に動いてくれるので、それはそれで問題ありません。ただそれだと、ワークフローの保守が大変になりますよね…

そこで使いたいのが、マトリクスビルド機能です!データ駆動(データドリブン、パラメタライズド)テストは作成したことありますか?まさにそれをワークフロー内で実現できるとお考え下さい!

では早速以下にサンプルを掲載!するのですが…すみません、STEP3のものをそのままクロスブラウザ化すると、Appium設定の記述などが少し冗長になってしまいます。そのため、もっと簡略なデスクトップブラウザのクロスブラウザサンプルを記載させていただきますm(_ _)m

name: クロスブラウザー テスト サンプル
on:
  workflow_dispatch: 
  # 今回はマニュアル実行しかしないので全てコメントアウトしているが、
  # ここ(on.workflow_dispatch)でイベントのトリガーを定義する
  # pushをトリガーにする場合は下記の通り
  # push:
  #   branches:
  #     - "master"
  #     - "release/*"
  #     - "*/*feature*"
  # 日時をトリガーにする場合は下記の通り記述する
  # なお、Linuxのcronと同じ記法。UTC時刻なので、日本時間-9時間すること
  # 例)火曜日の 12:30 (JST) に処理を実行する。
  # schedule: 
  #  - cron: '30 3 * * 2'
jobs: 
  # Mac環境でのテストはSafariのみ。今まで説明した以上のことはしていません
  Mac_Browser_Test:
    name: Mac Safari Test
    runs-on: macos-latest
    steps:
      - name: checkout
        uses: actions/checkout@v1

      - name: run test
        run: |
          cd pc
          chmod +x ../master/gradlew
          ../master/gradlew \
          -D selenide.browser=jp.shiftinc.automation.driver.SafariDriverProvider \
          clean test \
          --tests $TEST_CASE \
          allureReport
        env:
          TEST_CASE: "*crossBrowserTest*"

      - name: jUnit Report
        uses: actions/upload-artifact@v1
        with:
          name: test_rep_Mac
          path: pc/build/reports/tests

      - name: Allure Report
        uses: actions/upload-artifact@v1.0.0
        with:
          name: allure-report_Mac
          path: pc/build/reports/allure-report
  Win_Browser_Test: 
    # needsを使うと、指定(Mac_Browser_Test)したJOBが成功しないと、
    # このJOB(Win_Browser_Test)は実施されない。
    # 時系列が重要であるとき、並列にJOBを実行したくない時に使う
    needs:
      - Mac_Browser_Test
    # strategy.matrixで定義した変数はJOB内のどこでも呼び出せる
    name: Win ${{ matrix.browser }} Test
    runs-on: windows-latest
    # マトリクスビルド機能を用いて、ブラウザChrome、Firefoxを展開します。
    # ここで定義したたものは${{ matrix.driver }}などの変数と置換されて実行される
    # driver、browserはユーザーが定義したパラメータ(変数)
    # ~DriverProviderはRacineで使用する引数のため、あまり気にしなくてよい
    strategy:
      matrix:
        driver: [ "ChromeDriverProvider","GeckoDriverProvider" ]
        include:
          - driver: "ChromeDriverProvider"
            browser: "Chrome"
          - driver: "GeckoDriverProvider"
            browser: "Firefox"
      # max-parallelはどれだけJOBを並列に実行するかを定義する
      max-parallel: 1
    steps: 
      - name: checkout
        uses: actions/checkout@v1

    # WinとMacで実行環境が違うので、変数:TEST_CASEの呼び出し方が違う点に注意
      - name: run test
        run: |
          cd pc
          chmod +x ../master/gradlew
          ../master/gradlew `
          -D selenide.browser=jp.shiftinc.automation.driver.${{ matrix.driver }} `
          clean test `
          --tests $ENV:TEST_CASE `
          allureReport
        env:
          TEST_CASE: "*crossBrowserTest*"

      - name: jUnit Report
        uses: actions/upload-artifact@v1
        with:
          name: test_rep_${{ matrix.browser }}
          path: pc/build/reports/tests

      - name: Allure Report
        uses: actions/upload-artifact@v1.0.0
        with:
          name: allure-report_${{ matrix.browser }}
          path: pc/build/reports/allure-report


いかがでしょうか?コメントを適宜入れていますので、サンプルだけでもなんとなく勘所が掴めたのではないかと思います。

この記事をご覧の皆様には釈迦に説法になるかと思いますが、クロスブラウザテストを実施するときのポイントは以下の4つにまとめられます。

1.なるべく共通化する
2.共通部と固有部を明確にし、固有部を変数化する
3.並列に実行可能なように作る(アカウント、シナリオを独立させる)
 ※それが難しければ、並列実行を制御して直列に実行させる
4.実行環境が異なる場合は使えるコマンドや表現が違うことに気を付ける

今回のサンプルでは並列に実行可能なように作りこむのに失敗していますね。もちろん、現場ではちゃんと並列に実行できるように作りこみましたよ!
…作りこんだ、ということにしておきましょう?

さて、もう少し書いておくべきことはあるものの、ここまででクロスブラウザテストの実装が完了できました。
楽勝だった!…ということにしておきましょう!STEP4 FINISH!

・つまずき(つまずいた)ポイント

ここではクロスブラウザテストをGitHub上で実行するのにあたって、私がはまったポイントをご紹介します!

1. 時間制約 GitHub Actionsですが、オープンなリポジトリで実行する分には制限がないそうですが、プライベートなリポジトリで実行する場合は実行環境となるエミュレータの稼働時間に制限があります。 その時間、月間で2000分。

そこそこあるじゃんって思いますよね。でもWindowsだと消費時間が2倍、Macだと10倍なんです。 そして、モバイルのエミュレータは基本的にMacに制限されます。 iOS系は言わずもがな、なんですが、どうにもAndroid系も基本はMacでの実行になってしまうご様子。詳しく調べきれてないので、ひょっとしたら情報が古いかもしれませんが… そんなわけで、実行回数の節約だ、月額プランの申請だと、ドタバタしました。

場合によっては環境の手間が少なく、実行時のビデオ録画もしてくれるBrowserStackなどのTaaSを利用したほうが幸せになれるかもしれませんね。それはそれで実行時間の遅さや費用の高さなどに難はありますが…

2. マトリクスビルドの組み合わせ
先ほどの組み合わせを覚えておりますでしょうか

strategy:

 matrix:
   driver: [ "ChromeDriverProvider","GeckoDriverProvider"]
   include:
     - driver: "ChromeDriverProvider"
       browser: "Chrome"
     - driver: "GeckoDriverProvider"
       browser: "Firefox"

なんか冗長だな、↓これじゃダメなの?って思った方もいらっしゃるかもしれません。

strategy:

 matrix:
   driver: [ "ChromeDriverProvider","GeckoDriverProvider"]
   browser: [ " Chrome "," Firefox "]

結論から言うと、この書き方もできます。ただ動きが違います。
上は2パターンのJOB実行で、下は4パターンのJOB実行になります。
具体的にはこう↓ 

上のパターン1⇒driver: "ChromeDriverProvider"、browser: "Chrome"
上のパターン2⇒driver: "GeckoDriverProvider"、browser: "Firefox"

下のパターン1⇒driver: "ChromeDriverProvider"、browser: "Chrome"
下のパターン2⇒driver: "ChromeDriverProvider"、browser: "Firefox"
下のパターン3⇒driver: "GeckoDriverProvider"、browser: "Chrome"
下のパターン4⇒driver: "GeckoDriverProvider"、browser: "Firefox"

つまり、Includeを使うとmatrixで定義した変数(driver)がAの時、matrixで定義していない変数(browser)はBという書き方になるのに対し、matrixで定義した変数は掛け算(driver * browser)で組み合わせが生成されます。

機能としては非常に賢くてありがたいのですが、時間の制約がある中、知らずに使ってた身としては、嘘でしょ…って気持ちでいっぱいでした…

3. Appiumのバージョン
GitHub Actionsはほとんど関係ないのですが…モバイルエミュレータのバージョンを上げたところ、モバイルエミュレータが動かなくなり、困ったことがありました。

ローカルでは正常に動いていたこともあり、Xcodeのバージョン指定が上手くできてないのか…?などとしばらく見当違いの検証を行い、ソコソコの時間を無駄にしました。

結果から言うとインストールしていたAppiumのバージョンが古かっただけだったのですが、リモート環境だとトラブルが起こった時、原因特定に時間がかかるのが困りますね。

ローカルで実行するように対話モードでコマンドラインを操作することもできるらしいのですが、それはそれでごりごり使用時間が減っていってしまう問題があります。

・おわりに

かなり長い記事になってしまいました!当初の想像の3倍ぐらい長いですね!すみません!!

でもその分GitHub Actions色々できそうだなというのは伝わったかなと思います。もっとも、私はGitHubの回し者ではないので、GitHubを殊更に持ち上げる必要はないのですが…

GitHub Actionsをはじめ、自動テストを行っていくのにあたって便利なツールが増えています。まだまだテストの自動化にアプローチできている案件というのは世の中的に多くないのかなと思いますが、これを機に是非とも触っていただいて、ともに自動化の闇に挑む同志が増えればうれしいなと思います!

次は「GitHub ActionsでVPN環境構築してみた」とか書けるといいなと思います。よろしくお願いします!

__________________________________

執筆者プロフィール:石井 一成
業界経験5年弱。SHIFT入社から2年弱が経過。
まだまだ若手と思っていたが、そろそろ若手は苦しいかもしれない。

SHIFTでは、API~GUIテストの自動化アーキテクトを主に担当しているが、 効率的になるなら自動化でもローコードでも、マクロ屋さんでもなんでもやりたい人。 一方で効率化はツールだけではなく思いやりや他者理解などの気づかいやコミュニケーションも含めたものだと思っているので、技術だけにこだわりたくはないとも思っている。

最近は在宅期間が長すぎて住環境がどんどん充実し、引きこもりが悪化してきた。

公式noteお問合せ画像

​お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/