docker-seleniumでSelenium Grid 4の新機能Dynamic Gridを試す

Selenium 4のリリースとともに、
Selenium Grid(以下Grid)もver4にメジャーアップデートされたので新機能を試してみました。

## Selenium Gridとは

> Selenium Grid allows the execution of WebDriver scripts on remote machines (virtual or real) by routing commands sent by the client to remote browser instances. It aims to provide an easy way to run tests in parallel on multiple machines.

https://www.selenium.dev/documentation/grid/

GridはWebDriverスクリプトの実行リクエストを、接続されたクライアント上のブラウザインスタンスに振り分けてくれるサーバです。ロードバランサのイメージが分かりやすいかと思います。主にはテストの並列実行などに使います。

## Dynamic Gridとは

> Grid 4 has the ability to start Docker containers on demand, this means that it starts a Docker container in the background for each new session request, the test gets executed there, and when the test completes, the container gets thrown away.

https://github.com/SeleniumHQ/docker-selenium#dynamic-grid-

Dynamic Gridはリクエストに応じて対応するブラウザコンテナを自動で立ち上げる機能です。
Grid 3まではブラウザが起動できるノードを事前に接続しておくことが必須でしたが、Grid 4からはその手間が不要になるということですね。すごE。

## やってみる

ということで早速やってみます。

### 環境

- macOS Big Sur 11.3.1
- Docker関係
    - Docker Desktop 4.1.1
    - socat 1.7.4.1
      - Docker用のTCPプロキシを立てるのに使います
- テストスクリプト関係
    - Python 3.9
        - selenium 4.0.0
        - pytest 6.2.4
        - pytest-xdist 2.4.0
            - テストの並列実行に使います


### 1. 必要なファイルをDLする

筆者のリポジトリに必要ファイルをまとめてあるのでcloneしてきてください。

https://github.com/Yaboxi/SeGrid4Demo

なお、以下の2ファイルは[SeleniumHQ/docker-selenium](https://github.com/SeleniumHQ/docker-selenium#configuration-example)から拝借しています。必要に応じてアップデートしてください。

- [docker-compose-v3-dynamic-grid.yml](https://github.com/SeleniumHQ/docker-selenium/blob/trunk/docker-compose-v3-dynamic-grid.yml)
- [nodes/config-docker.toml](https://github.com/SeleniumHQ/docker-selenium#configuration-example)

### 2. (macOSのみ） socatでTCPプロキシサーバを立てる

macOSの場合は`nodes/config-docker.toml`のコメントに従い、
以下のコマンドでDockerがTCP通信ができるようにプロキシサーバを立てます。

```shell
socat -4 TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock
```

### 3. docker composeでSelenium Gridを起動する

cloneしてきたディレクトリ直下で以下のコマンドを実行し、Gridサーバを立てます。

```shell
docker-compose -f docker-compose-v3-dynamic-grid.yml up
```

以下のログが表示されていれば起動に成功しています。

```shell
segrid4-node-docker-1  | 07:30:54.734 INFO [NodeServer.lambda$createHandlers$2] - Node has been added
selenium-hub           | 07:30:54.739 INFO [GridModel.setAvailability] - Switching node a3ff3d19-5809-4192-aae9-fb031183d006 (uri: ***) from DOWN to UP
```

この状態で http://localhost:4444/ にアクセスしてみましょう。
以下の画像のようにDockerノードが接続されているのが確認できると思います。

![スクリーンショット 2021-10-27 13.00.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527184/b1e2e95f-3629-d306-8760-1799d9b4a9ff.png)

### 4. pytest-xdistでテストを並列実行する

このノードはChrome, Firefox, Edgeのブラウザコンテナをオンデマンドに展開できます。
ということで、各ブラウザで http://whatsmyuseragent.org/ にアクセスしてスクショを撮る簡単なスクリプトを書いてみました。

```python
from selenium import webdriver


class TestGrid:
    def choose_driver(self, browser: str) -> webdriver:
        command_executor = "http://localhost:4444"

        if browser == "firefox":
            options = webdriver.FirefoxOptions()
        elif browser == "edge":
            options = webdriver.edge.options.Options()
        else: # chrome
            options = webdriver.ChromeOptions()

        options.set_capability("se:recordVideo", "true")
        options.set_capability("se:timeZone", "JST")

        driver = webdriver.Remote(
            command_executor=command_executor,
            options=options,
        )

        driver.set_window_size(1024, 768)

        return driver

    def test_chrome(self):
        driver = self.choose_driver("chrome")

        driver.get('http://whatsmyuseragent.org/')
        driver.save_screenshot("./results/google.png")

        driver.quit()

        assert True

    def test_firefox(self):
        driver = self.choose_driver("firefox")

        driver.get('http://whatsmyuseragent.org/')
        driver.save_screenshot("./results/firefox.png")

        driver.quit()

        assert True

    def test_edge(self):
        driver = self.choose_driver("edge")

        driver.get('http://whatsmyuseragent.org/')
        driver.save_screenshot("./results/edge.png")

        driver.quit()

        assert True
```
https://github.com/Yaboxi/SeGrid4Demo/blob/main/tests/test_grid.py

この各テストメソッドを並列実行してみます。
pytestでテストを並列実行するには[pytest-xdist](https://github.com/pytest-dev/pytest-xdist)プラグインをインストールした上で、以下のようにコマンドを実行すればOKです。

```shell
pytest -n 3 tests/
```

http://localhost:4444/ のサイドバーよりSessionsを確認してみると、3コンテナが同時に動いていることが確認できます。

![スクリーンショット 2021-10-27 13.04.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527184/b1ec2ed7-c3b6-de0c-da4d-7b38427086fa.png)

各セッションのビデオアイコンをクリックするとコンテナにVNC接続をすることができます。
パスワードは`secret`です。

![スクリーンショット 2021-10-27 13.27.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527184/b1772415-2714-477d-ee53-ffbcbfa6b37f.png)

実行が完了し`results/`に各ブラウザで撮影したスクリーンショットが保存されていればデモは成功です！

### 参考 : Video Recording

ちなみに上記スクリプトではもう一つの新機能、Video Recordingも利用しています。
これはその名の通りテスト時の動画を録画する機能です。

choose_driverメソッドに記載の以下の一文でVideo Recordingを有効化しています。

```python
options.set_capability("se:recordVideo", "true")
```

動画は`assets/`以下に保存されているので確認してみてください。

## 余談 : 進化しすぎて手に負えないGrid 4

本記事ではSelenium Grid　4の新機能であるDynamic Gridを試しました。
が、もっと根本的なところとして、Grid 4ではGridの構成要素が変わっていたりと内部的に大きな変更が入っている模様です。
大規模分散型システムに向けた変更だと思いますが、現状そこまでの大規模分散型システムを作る機会がないので詳しくは調査していません。悪しからず。

ご興味のある方は下記記事が参考になるかと思います。

- https://www.selenium.dev/documentation/grid/components_of_a_grid/
- https://applitools.com/blog/selenium-grid-4/