Behat + Minkで作ったテストをAndroid+Chromeでも動くようにした

状況

  • PHPで作ったアプリのE2EテストをBehat+Minkで実装している
  • アプリがモバイルでも動くかどうか確認する必要が出てきた
  • Behat+Minkで作ったE2Eテストを、Android上のChromeを使って動かして、動作確認をしたい

大まかな仕組み

以下のようになる。 すでにMacbook上でChrome Driverを使ってE2Eテストを動かしているので、adb -> Android -> Chromeのところを準備すればOK。

Behat -> Mink -> Selenium -> Chrome Driver -> adb -> Android -> Chrome

動かすためにやったこと

  • AndroidWifi経由で操作できるようにする
  • behat.ymlにAndroid用のプロファイルを追加
  • Androidからアクセスできるよう、BeforeScenarioでMinkとアプリの設定を切り替え
  • Chromeでのみ発生する要素をクリックできない問題に対処

以下、それぞれの説明

AndroidWifi経由で操作できるようにする

Android 11以降は、Wifi経由で操作ができるようになる。それ以前だとUSB接続のみ。
USBは面倒なのでWifiで接続できるようにした。

Android Debug Bridge(adb)  |  Android デベロッパー  |  Android Developers

ハマった点

  • Android Studioを最新にアップデートする
    • 苦労した覚えはあるんだけどなんだったか忘れた
  • ADBの接続コマンド
    • 公式ドキュメントを見てやれば大丈夫

behat.ymlにAndroid用のプロファイルを追加

extra_capabilitiesにgoog:chromeOptionsのandroidPackageを入れるのがポイント。 base_urlについては後述。

android:
  extensions:
    Behat\MinkExtension:
      base_url: http://#localhostIP#:8090
      selenium2:
        browser: chrome
        capabilities:
          extra_capabilities:
            goog:chromeOptions:
              androidPackage: com.android.chrome

Androidからアクセスできるよう、BeforeScenarioでアプリの設定を切り替え

通常、Macbook上ではlocalhostを使って接続しているので、URLは常に http://localhost で良いのであるが、AndroidからWifi経由で接続する場合は、IPアドレスを使って接続する必要がある。で、MacbookIPアドレスは常に変わる。(固定しろよって話もあるが...)

ちょっと面倒だけど、毎回実行時にIPアドレスを取得して、Minkとアプリの設定を書き換えることにした。

RawMinkContextを継承しているクラスで以下のコードを、@BeforeScenario で実行すればOK。

        $baseUrl = $this->getMinkParameter('base_url');

        if (strpos($baseUrl, "#localhostIP#") > 0) {
            // IPアドレスを取得
            $ipAddressesRC = preg_grep("/^192\./", gethostbynamel(gethostname()));
            $ipAdderss = array_shift($ipAddressesRC);

            // URLを書き換え
            $newBaseUrl = str_replace("#localhostIP#", $ipAdderss, $baseUrl);

            // 全てのContextに設定を反映
            $environment = $scope->getEnvironment();
            foreach ($environment->getContexts() as $context) {
                if ($context instanceof \Behat\MinkExtension\Context\RawMinkContext) {
                    $context->setMinkParameter('base_url', $newBaseUrl);
                }
            }
        }

ポイントは $environment = $scope->getEnvironment(); 以降のコード。
利用している全てのContextに対して、設定を変更する必要がある。

上記で変更したURLを使って、アプリの設定を書き換えればOK。コードは省略。

Chromeでのみ発生する要素をクリックできない問題に対処

ここまでで実行するだけならできる...んだけど、テストが通るようにするために、Chrome特有の問題に対応する必要があった。

問題とは以下のもの。画面外にある要素をクリックしようとするとエラーになる。

seleniumにてButtonがクリックできない時の対処法 - Qiita

解決方法は難しくない。Clickの前に上記のエントリにあるようにスクロールをするか、フォーカスしてあげればOK。
カスタムのContextを使っている場合は、コードにスクロールなり、フォーカスするコードを追加すればよい。

私は、カスタムのContextに加えて、Behat\MinkExtension\Context\MinkContext も使っていたので、メソッドをオーバーライドしてフォーカスするコードを追加した。以下のような感じ。クラスの名前がやっつけすぎるな...。

class MyMinkContext extends MinkContext implements TranslatableContext
{
    public function clickLink($link)
    {
        $link = $this->fixStepArgument($link);
        $elem = $this->getSession()->getPage()->findLink($link);
        $elem->focus();
        $elem->click();
    }