CodeceptJSでクラウドのモバイル実機テスト、BrowserStack vs DeviceFarm

# 概要
CodeceptJSを使って[BrowserStack](https://www.browserstack.com/automate)および[AWS DeviceFarm](https://aws.amazon.com/jp/device-farm/)で用意されているモバイルデバイスに対して自動テストを実施して比較したという話です。両サービスともに”お試し時間”が設けられており、CodeceptJSとの組み合わせの場合、導入の容易さや準備内容について、把握するにはお試し時間でも有効に試せた感あります。
参考にした資料は、

1. [テスト自動化関連ツール・ライブラリまとめ](https://qiita.com/YoshikiIto/items/2b3ab5ff84610d0f74bd)
2. [AWS Device FarmでAppiumを使ったE2Eテストを動かす](https://qiita.com/ryoyakawai/items/30d0c3e2da81b34f61bf)
3. [CodeceptJSのマニュアル（英文）](https://codecept.io/mobile/#browserstack-configuration)

ありがとうございます。

お題：[Selenium練習サイト](http://example.selenium.jp/reserveApp_Renewal/)　<--- 近々、引っ越しの噂も聞いてます。
環境：
Windows10 64bit 20H2 64GByte 第10世代Core i7
Node v15.8.0
Appium v1.21.0
# 両者比較
割と手軽な点でBrowserStack がお薦めです。（珍しく結論）それぞれの準備から、テスト実行と結果確認までを紹介します。
実機を手元に用意せず、ネットワークの向こうにあるデバイスに対して操作と検証をするという点では、BrowserStackもDeviceFarmも似ているのですが、自動テストする場合には以下のような違いがあります。

|サービス名|テスト実行|結果の確認|複数台同時テスト|お試し時間
|:---|:---|:---|:---|:---|
|BrowserStack|手元のツール|自前のレポートツール|基本、1台ずつ。工夫次第かもしれない|100分|
|AWS Device Farm|クラウド上|コンソールにて確認|デバイスプールにて実施|1000分|
エラー発生時のスクリーンショットは、両サービスとも用意されています。BrowserStackではCodeceptJSにバンドルされているスクリーンショット機能、DeviceFarmではDeviceFarmのスクリーンショット機能になります。
テスト実行中の様子は、以下のようなものです。
BrowserStack：手前のPowerShellで向こう側のBrowserStack画面にてテスト中
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">codeceptJSでBrowserStackのデバイスを使ったe2eテスト <a href="https://t.co/jWvuFQQ3ms">pic.twitter.com/jWvuFQQ3ms</a></p>&mdash; kazuhiroYoshino (@XSyrIQwKY7Yuy6m) <a href="https://twitter.com/XSyrIQwKY7Yuy6m/status/1435827833738186752?ref_src=twsrc%5Etfw">September 9, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
手順を簡単に言うと、

1. BrowserStackのアカウント作成して、codecept.conf.js 修正
2. テスト開始

DeviceFarm：DeviceFarmの画面
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">codeceptJSでDeviceFarmのデバイスをe2eテスト<br>BrowserStackに比べて、たいへん。 <a href="https://t.co/Di0yeeiuQy">pic.twitter.com/Di0yeeiuQy</a></p>&mdash; kazuhiroYoshino (@XSyrIQwKY7Yuy6m) <a href="https://twitter.com/XSyrIQwKY7Yuy6m/status/1435832075416862726?ref_src=twsrc%5Etfw">September 9, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

こちらの手順は、AWSアカウントを作成したら

1. テストプロジェクトをローカルに作成。動作確認
2. DeviceFarmから環境変数をCodeceptJSの設定ファイルに書きだすNodeのスクリプトをテストプロジェクトに追加作成
3. テストプロジェクトをパッケージ化。zipファイルにしておく
4. ymlファイルにテスト環境の構築とテスト実行を記述してDeviceFarmにアップロード
5. テスト開始

お試し時間の違い以上に、苦労しました。
いろいろあって、できあがったレポートは、
BrowserStackの場合は、allureのレポートで、
![browserStack013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939617/7c10a90a-4bb2-82da-85bb-3b2e3d584524.png)
意図的にエラー（宿泊料金間違い）を混入させましたが、CodeceptJSのスクリーンショットは無事？モバイルデバイスの画面を捉えています。（BrowserStack画面だったら、どうしようと心配したが大丈夫でした。）

一方、DeviceFarmでは、1つのデバイスのレポートを見ると
![DeviceFarm007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939617/12fc5c7a-a36a-99e1-ad04-b8a2496972bb.png)
DeviceFarmでは、allureのように、テストデータ別のレポートにはなっていません。テストログとスクリーンショットで確認することになります。DeviceFarm CLIというAPIも用意されていますので、今回のようにDeviceFarm画面でのテスト結果確認ではない方法も、あるいは可能性有るかもしれませんね、現状は、マルっと合否判定のみです。
両サービス共に、テスト中の画面キャプチャ（動画）がありますが、両方とも間欠記録のように見えます。またBrowserStackでは、テスト中の画面は確認できません。記録された動画はコマ落ちが多く参考程度の扱いになるかもしれません。実機の画面に拘る場合は、本件で紹介した方法によるテストは適当では無いかと思います。
# BrowserStackを利用した場合のテスト準備
ここから、もう少しテスト準備や、ローカル環境（USB接続した実機またはEmulatorを使用）からの差分について紹介します。
### 1. BrowserStackにアカウントを作成する
CodeceptJSの codecept.conf.js にアカウントを記述しますので、事前に準備します。必要な情報はBrowserStack画面のここです。
![browserStack002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939617/11ed38d9-c878-88da-9ba0-8214fafc42ca.png)
UserNameとAccessKeyをクリップボードにコピーするUIが画面上にありますので、それを利用するのが手っ取り早いと思います。

```codecept.conf.js
  helpers: {
   Appium: {
        host: "hub-cloud.browserstack.com",
        port: 4444,
            user: 'ここにUser Name',
            key: 'ここにAccess Key',
            platform: "iOS",
            url: 'http://example.selenium.jp/reserveApp_Renewal/',
//            waitForTimeout: 20000,
            desiredCapabilities: {
                  realMobile: "true",
                  device: "iPhone 8",
                  os_version: "12",
                  browserName: "safari"
                }
    }
   },
```
Appium以下に記述します。
### 2. codecept.conf.js の修正とテストシナリオの修正
基本的にはcodeceptJSサイトで紹介されている内容に沿った修正になります。
BrowserStackで用意されているデバイスとOS・OSのバージョンを記述するだけです。
ただし、iPad系についてはタイムアウトの調整が必要でした。iPadについては、waitForTimeout: 90000,　としたら動き出しました。デフォルトの 1000 （1秒）ではダメでした。
またタブレット端末については、デバイスによっては縦にしていてもナビゲーションボタンが表示されるようなデバイス（iPad Pro）もありますので、テストシナリオも修正します。以下抜粋。
テストシナリオ最後のHOMEボタン押すところだけですが。

```ReserveTest_test.js
    I.click('確定');
    I.see('ご来館、心よりお待ちしております。');
//iPad 8th と iPad Air 4 と iPad Mini4のみ
//    if(current.向き == '縦向き'){
//        I.click('body > div.navbar.navbar-fixed-top > div > div > a.btn.btn-navbar');
//    }
    I.click('Home');

```
iPhoneは全て、ナビゲーションボタン押してからの操作でした。
このあたりもテストシナリオで対応できる点、CodeceptJSは楽ちんです。
今回選んだ実機と codecept.conf.js に記述するdevice名は、BrowserStackのLiveで用意されているデバイス一覧のデバイス名で動きました。
Windows環境にてAndroidのEmulatorでテスト稼働した時のテストコードは、そのままiOS実機でもBrowserStackの場合は使えました。当たり前のように思えますが、この後、DeviceFarmで苦労したのでサクっとできてしまう素晴らしさを実感しました。
### 3. テスト実行
あとは、実行するだけです。Appiumの起動は必要有りません。
例によって、宿泊初日の曜日や連泊数、宿泊人数とオプションプランで網羅したテストデータをPictmasterで生成して加工して用いています。1台のデバイスにつき、7件のテストデータとなりました。
8種類の携帯端末を使っての場合、PowerShellで一気にテスト実行で、40分くらいの所要時間となりました。デバイスを1個ずつテストしたので、それなりに時間かかったように思います。後述のDeviceFarmは複数デバイスを同時並列にテストする点が異なります。
iOS系でテストするブラウザをChrome指定することが、何故かできませんでした。リモート操作可能なデバイスではChrome on iOSも用意されているようなのですが、safariでのテストになりました。Input CapabilityよりもDevice Capabilityが優先されているように見えます。（継続調査）
![browserStack014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939617/764c271d-2ac0-742e-e7e2-fdaebaf97eb0.png)

# DeviceFarmを利用した場合のテスト準備
前述のようにDeviceFarmとBrowserStackとでは、仕組みが異なるので準備についても差異があります。また、ローカル環境で稼働したことが確認できること（Emulatorでもいいので）旨く接続できない場合などで原因の特定が容易なのですが、DeviceFarmでは固有の癖があって苦労しました。

1. iOS系が「An unknown server-side error occurred while processing the command. Original error: '11.0' does not exist in the list of simctl SDKs. Only the following Simulator SDK versions are available on your system: 5.3, 12.4」のエラーになり稼働しない。（調査中）
2. CodeceptJSのメソッドのいくつかが、ローカル環境では動作確認できるのに、DeviceFarm上では動かなく、仕方ないので、カスタムStepで対応した。

### 1. ymlファイルの準備
テスト実行が手元PCではなくクラウド上になるので、テスト実行環境をクラウド上に構築する必要があります。DeviceFarmでは、JUnit / TestNGといったFrameworkも予め用意されており、CodeceptJSを利用する場合は、その中からNode.jsをFrameworkとして用意している環境を選択することになります。ymlファイルにはCodeceptJSでテスト実行するまでのインストール手順と、テスト前の準備、テスト実行を記述します。DeviceFarmにはymlファイルのテンプレートのようなものがありましたので、これをよくよく読みながら、カット＆トライ作業になりました。こんな感じです。

```MyTestPack.yml
version: 0.1

# Phases are collection of commands that get executed on Device Farm.
phases:
  # The install phase includes commands that install dependencies that your tests use.
  # Default dependencies for testing frameworks supported on Device Farm are already installed.
  install:
    commands:
      # install node stable versoin
      - nvm install stable

      # Install npm packages by npm install to both globally and locally
      - echo ">> Install npm packages by npm install to both globally and locally"
      - cd $DEVICEFARM_TEST_PACKAGE_PATH
      - export APPIUM_VERSION=1.16.0
      - avm $APPIUM_VERSION
      - ln -s /usr/local/avm/versions/$APPIUM_VERSION/node_modules/.bin/appium  /usr/local/avm/versions/$APPIUM_VERSION/node_modules/appium/bin/appium.js
      - npm install -g chai
      - npm install -g @wdio/cli
      - npm install

  # The pre-test phase includes commands that setup your test environment.
  # !!! This section has not modified from default/original template. (as of 2020/07) !!!
  pre_test:
    commands:
      # We recommend starting appium server process in the background using the command below.
      # Appium server log will go to $DEVICEFARM_LOG_DIR directory.
      # The environment variables below will be auto-populated during run time.
      - >-
        if [ $DEVICEFARM_DEVICE_PLATFORM_NAME = "Android" ];
        then
        echo "Start appium server for android";
        (appium --log-timestamp
        --default-capabilities "{\"deviceName\": \"$DEVICEFARM_DEVICE_NAME\", \"platformName\":\"$DEVICEFARM_DEVICE_PLATFORM_NAME\",
        \"app\":\"$DEVICEFARM_APP_PATH\", \"udid\":\"$DEVICEFARM_DEVICE_UDID\", \"platformVersion\":\"$DEVICEFARM_DEVICE_OS_VERSION\",
        \"browserName\":\"Chrome\", \"chromedriverExecutable\":\"$DEVICEFARM_CHROMEDRIVER_EXECUTABLE\"}"
        >> $DEVICEFARM_LOG_DIR/appiumlog.txt 2>&1 &);
        fi

      # For IOS, Device farm starts the ios-webkit-debug-proxy before starting the appium server.
      # So don't start ios-webkit-debug-proxy in the yaml file.
      # The default WDA used is at DEVICEFARM_WDA_DERIVED_DATA_PATH_V1 (Supports versions iOS 12 and below), it is using commit f865d3. See (https://github.com/appium/appium-xcuitest-driver/tree/f865d32e78a5a8a15469bee30ed2f985d378575d)
      # If you need an older WDA version or need support for node modules, use the WDA at DEVICEFARM_WDA_DERIVED_DATA_PATH_V0. (This version does not suport iOS 12)

      - >-
        if [ $DEVICEFARM_DEVICE_PLATFORM_NAME = "iOS" ];
        then
        echo "Start appium server for iOS";
        (appium --log-timestamp
        --default-capabilities "{\"usePrebuiltWDA\": true, \"derivedDataPath\":\"$DEVICEFARM_WDA_DERIVED_DATA_PATH\",
        \"deviceName\": \"$DEVICEFARM_DEVICE_NAME\", \"platformName\":\"$DEVICEFARM_DEVICE_PLATFORM_NAME\", \"app\":\"$DEVICEFARM_APP_PATH\",
        \"automationName\":\"XCUITest\", \"udid\":\"$DEVICEFARM_DEVICE_UDID_FOR_APPIUM\", \"platformVersion\":\"$DEVICEFARM_DEVICE_OS_VERSION\", \"browserName\":\"Safari\"}"
        >> $DEVICEFARM_LOG_DIR/appiumlog.txt 2>&1 &);
        fi

      - >-
        start_appium_timeout=0;
        while [ true ];
        do
            if [ $start_appium_timeout -gt 60 ];
            then
                echo "appium server never started in 60 seconds. Exiting";
                exit 1;
            fi;
            grep -i "Appium REST http interface listener started on 0.0.0.0:4723" $DEVICEFARM_LOG_DIR/appiumlog.txt >> /dev/null 2>&1;
            if [ $? -eq 0 ];
            then
                echo "Appium REST http interface listener started on 0.0.0.0:4723";
                break;
            else
                echo "Waiting for appium server to start. Sleeping for 1 second";
                sleep 1;
                start_appium_timeout=$((start_appium_timeout+1));
            fi;
        done;

  # The test phase includes commands that start your test suite execution.
  test:
    commands:
      # Go into the root folder containing your source code and node_modules
      - echo ">> Execute test with CodeceptJS"
      - echo ""
      - cd $DEVICEFARM_TEST_PACKAGE_PATH
      - pwd
      - ls -al
      - echo "  [CHECK PARAMETERS]"
      - echo "   >> DEVICEFARM_LOG_DIR=[" $DEVICEFARM_LOG_DIR/results "]"
      - echo "   >> DEVICEFARM_APP_PATH=[" $DEVICEFARM_APP_PATH "]"
      - echo "   >> DEVICEFARM_DEVICE_PLATFORM_NAME=[" $DEVICEFARM_DEVICE_PLATFORM_NAME "]"
      - echo "   >> DEVICEFARM_DEVICE_OS_VERSION=[" $DEVICEFARM_DEVICE_OS_VERSION "]"
      - echo "   >> DEVICEFARM_DEVICE_NAME=[" $DEVICEFARM_DEVICE_NAME "]"
      - echo "[TestConf]"
      - node -v
      - appium -v

      - node ./bin/pre_update_config.js $DEVICEFARM_LOG_DIR/output $DEVICEFARM_APP_PATH $DEVICEFARM_DEVICE_PLATFORM_NAME $DEVICEFARM_DEVICE_NAME $DEVICEFARM_DEVICE_OS_VERSION
      - cat ./codecept.android.conf.json
      - echo ""
      - node ./node_modules/codeceptjs/bin/codecept.js run --debug --plugins retryFailedStep --steps --config=./codecept.android.conf.js

  # The post test phase includes are commands that are run after your tests are executed.
  post_test:
    commands:

# The artifacts phase lets you specify the location where your tests logs, device logs will be stored.
# And also let you specify the location of your test logs and artifacts which you want to be collected by Device Farm.
# These logs and artifacts will be available through ListArtifacts API in Device Farm.
artifacts:
  # By default, Device Farm will collect your artifacts from following directories
  - $DEVICEFARM_LOG_DIR

```
ローカル環境がWindowsで、クラウドがLinuxというギャップをまともに受けた感がありますね。
もっと手軽にできると思って臨みましたがたいへんでした。インストール時にerrなら明確なのですが、インストールはできても動かないという場合、ローカル環境でいくら確認しても、こちらは動くので進展しないです。
Appiumのバージョンについてはymlに記載されているavm（Appium Version Manager）を使ったインストールが旨くいくようです。ローカル環境だと最新ので確認していますが、DeviceFarmはおとなしくPreインストールされているのから選択するのが結果的にうまくいきました。
### 2. デバイス接続情報をcodecept.android.conf.jsに反映させる
DeviceFarm上で以下の変数が参照可能ですので、それを使います。
* デバイス名は、\$DEVICEFARM_DEVICE_NAME
* OS名は、\$DEVICEFARM_DEVICE_PLATFORM_NAME
* OSバージョンは、\$DEVICEFARM_DEVICE_OS_VERSION
これをcodecept.android.conf.jsに反映させるために、一旦jsonファイルに反映させてから、codecept.android.conf.jsに読み込むといったことをしています。jsonファイルはこんな感じ

```codecept.android.conf.json　から抜粋
{
  "tests": "./reserve_test/*_test.js",
  "output": "./output",
  "helpers": {
    "Appium": {
      "platform": "Android",
      "waitForTimeout": "120000",
      "desiredCapabilities": {
        "automationName": "UiAutomator2",
        "platformVersion": "11",
        "deviceName": "device",
        "udid": "emulator-5554"
      }
    }
  },
```
これをDeviceFarm上の変数で上書きするNodeスクリプトは、

```pre_update_config.js　から抜粋
const target_filename = 'codecept.android.conf.json'
console.log('')
console.log('-----')
console.log(` [target file] ${target_filename}`)
console.log('-----')
console.log('')

const process_argv = process.argv
const env_params = process.env

try {
  const fs = require('fs')
  const path_to_config_json = `${process.cwd()}/${target_filename}`
  const obj_config_json = JSON.parse(fs.readFileSync(path_to_config_json, 'utf8'));

  let DEVICEFARM_LOG_DIR = process.argv[2]
//  let DEVICEFARM_APP_PATH = process.argv[3]
  let DEVICEFARM_DEVICE_PLATFORM_NAME = process.argv[3]
  let DEVICEFARM_DEVICE_NAME = process.argv[4]
  let DEVICEFARM_DEVICE_OS_VERSION = process.argv[5]
    if(process.argv[2].length != 0) {
      DEVICEFARM_LOG_DIR = process.argv[2]
      obj_config_json.output = DEVICEFARM_LOG_DIR
    }

    if(process.argv[3].length != 0) {
      DEVICEFARM_DEVICE_PLATFORM_NAME = process.argv[3]
      obj_config_json.helpers.Appium.platform = DEVICEFARM_DEVICE_PLATFORM_NAME
    }

    if(process.argv[4].length != 0) {
      DEVICEFARM_DEVICE_NAME = process.argv[4]
      obj_config_json.helpers.Appium.desiredCapabilities.deviceName = DEVICEFARM_DEVICE_NAME
      obj_config_json.helpers.Appium.desiredCapabilities.udid = DEVICEFARM_DEVICE_NAME
      obj_config_json.helpers.Appium.desiredCapabilities.platformVersion = DEVICEFARM_DEVICE_OS_VERSION
    }
```
ymlファイルの中で、node ./bin/pre_update_config.js \$DEVICEFARM_LOG_DIR/output \$DEVICEFARM_APP_PATH \$DEVICEFARM_DEVICE_PLATFORM_NAME \$DEVICEFARM_DEVICE_NAME \$DEVICEFARM_DEVICE_OS_VERSION
　を実行する部分で、上書きをしています。
codecept.android.conf.jsは、更新されたjsonファイルを読み込んでいます。

```codecept.android.conf.js
const target_filename = 'codecept.android.conf.json';

///////
const fs = require('fs')
const path_to_codecept_json = `${process.cwd()}/${target_filename}`;
const obj_codecept_json = JSON.parse(fs.readFileSync(path_to_codecept_json, 'utf8'));
exports.config = obj_codecept_json;
```
ymlファイルの中で、
node ./node_modules/codeceptjs/bin/codecept.js run --debug --plugins retryFailedStep --steps --config=./codecept.android.conf.js
とすることで、confファイルを codecept.android.conf.js で指定してcodeceptJSを起動します。
-config で指定しなければ、codecept.conf.js で起動します。ローカル環境ではこちらを使いました。
### 3. カスタムステップの準備
詳しい理由はわからないのですが、ローカル環境では問題なく動作しても、DeviceFarmだと、以下のメソッドがどうしても動きませんでした。

* JQueryのカレンダーが表示されるテキストボックス入力をセレクターで指定できない。xpathで指定したら動いた。
* ドロップダウンメニューを、I.selectOption を使って操作できない。executeScriptで対応。
* ドロップダウンメニューで選択されている値を、I.grabValueFrom を使って取得できない。executeScriptで対応。
* Click操作を、セレクタ指定やボタン表示指定で操作できない。executeScriptで対応。

executeScriptメソッドを多用する結果になってしまいました。「利用規約に同意して次へ」ボタン、「確定」ボタン、ナビゲーションボタン（画面右上の 三 ）などについてカスタムstepに別途記述することになった点、CodeceptJSの魅力半減な気もします。

```steps_file.js（抜粋）
        getHeadCount: function(){
            let hcString = null;
            hcString = this.executeScript(function(){
                var selector = 'headcount';
                var hcText = document.getElementById(selector).value;
                return hcText;
            });
            return hcString;
        },

        clickNavButton: function(){
            this.executeScript(function(){
                var nav = document.querySelector('body > div.navbar.navbar-fixed-top > div > div > a.btn.btn-navbar');
                nav.click();
            });
        },
```
### 3. デバイスプールの設置
DeviceFarmでは、同じテストシナリオをデバイスプールに登録した実機に対して同時にテスト実行することができます。折角ですから、OSのバージョン / PhoneかTabletか　でグループ化したプールを作成して、何台か登録した後、テスト実行してみました。
![DeviceFarm008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939617/02d07d30-5350-d765-5616-2fc5203da433.png)
Androidのバージョン10系・11系とタブレットでDeviceFarmのプロジェクトを作成した後、それぞれのプロジェクトにデバイスプールを作成して端末を登録します。各プロジェクトにはymlファイルとCodeceptJSで稼働するテストプロジェクトをzip化して（npm-pack-zipを使いました）アップロード後、それぞれのプロジェクトでテストを走らせます。
![device_android10_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939617/21850cd8-96e9-3321-1e77-30bb812897d2.png)
Phone_10プロジェクト以下にて、5台の登録したモバイル端末がテスト実行している様子です。
各プロジェクトと、宿泊初日の曜日や連泊数、宿泊人数とオプションプランで網羅したテストデータをPictmasterで生成して加工したものをテストデータとして用いました。こちらも1デバイスあたり7件のテストデータでテストを行っています。
Running をクリックすると端末毎にテスト中の様子が観察できます。
![device_androidTablet_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939617/8d94755e-7533-8073-d9fd-ea74db017083.png)
テストログを標準出力にだすために、CodeceptJSを --steps オプションで起動していますが、ローカル実行に比べると、かなり実行速度が遅い様子が端末の実行画面から判ります。12台の端末を使ったテストでしたが40分ほどかかりました。BrowserStackも1台ずつで40分ほどかかりましたので、一度に複数台のデバイスをテストできる点では、DevicePoolも使い方次第かと思います。
2021年9月9日現在、両サービスで用意されているモバイルデバイスを比較してみました。用意されている全機種ではありません。（参考まで）

|実機iOS系|BrowserStackでのOS version|Device FarmでのOS version|
|:---|:---|:---|
|iPhone 5S|7||
|iPhone 6|8 / 11|11.4.1 / 14.4.1|
|iPhone 6 Plus|8|11.4.1|
|iPhone 6S|9 / 11 / 12|14.4.2|
|iPhone 6S Plus|9 / 11||
|iPhone 7|10 / 12|10.3.3 / 11 / 12|
|iPhone 7 Plus||11 / 12|
|iPhone 8|11 / 12 / 13|11 / 11.0.3 / 12 / 14.4.2|
|iPhone 8 Plus|11 / 12|11 / 12.1|
|iPhoneX|11|11.4 / 12 / 14.6|
|iPhoneXS|11 / 13 / 14|12 / 12.1 / 13.6|
|iPhoneXS Max|12||
|iPhoneXR|12|12 / 12.4.1 / 14|
|iPhone11|13 / 14|13.1.3 / 13.3.1 / 14|
|iPhone11 Pro|13|13.1.3 / 13.6|
|iPhone11 Pro Max|13 / 14|13.1.3|
|iPhone12|14|14.2 / 14.6|
|iPhone12 Pro|14|14.5.1|
|iPhone12 Pro Max|14|14.5.1|
|iPhone12 Mini|14|14.2|
|iPhone SE (2020)||13.6|
|iPad Mini4|11||
|iPad mini (5th gen)||14.6|
|iPad 7th Gen (2019)||13.3|
|iPad 8th|14||
|iPad Air2||11.1 / 13.6|
|iPad Air4|14||
|iPad Pro 12.9 2021|14||

BrowserStackだけですが、デスクトップPCも用意されています。WindowsからMac上で稼働するSafariについてテストが可能です。

|実機Android系|BrowserStackでのOS version|Device FarmでのOS version|
|:---|:---|:---|
|Galaxy S20|10|10|
|Galaxy S20+|10|10|
|Galaxy S20 Ultra|10|10|
|Galaxy A10s||10|
|Galaxy A11|10||
|Galaxy A51|10||
|Galaxy Tab A 10.1"||10|
|Galaxy Note20|10||
|Galaxy Note20 Ultra|10||
|Galaxy Tab S7|10|11|
|Galaxy A71||11|
|Galaxy S21+|11|11|
|Galaxy S21 Ultra|11|11|
|Galaxy S21|11|11|
|Pixel 3|10|10|
|Pixel 3a|9|10|
|Pixel 3 XL|9|10|
|Pixel 4|10 / 11|10 / 11|
|Pixel 4a||11|
|Pixel 4 XL|10|10|
今回は、Webアプリケーションのみのお試しでしたが、両サービスとも、ネイティブ・アプリケーションのテストもできるようです。
# 総括
両サービスともに、お試し期間を使ってテスト開始までの流れと結果確認の方法について、大まかに掴むことはできました。若干の調査余地も残りましたが、CodeceptJSを使ってクラウド上の実機を有効活用することもありなのではないかと思います。

|サービス名|実機|テスト環境|テスト中の画面|テスト結果|
|:---|:---|:---|:---|:---|
|お手元でテスト|手元またはEmulator|手元|見たまま|レポート等|
|BrowserStack|クラウド|手元|記録映像|レポート等|
|DeviceFarm|クラウド|クラウド|記録映像|コンソール画面|

とにかく始めてみようの場合ならば、現時点では、CodeceptJSとBrowserStackの組み合わせでちいさく始めるのが簡単だと思います。DeviceFarmもhelper以下に簡単な記述で開始できるといいのになぁ　とは思います。