E2Eテスト: Selenium Gridを試す

# はじめに
こんにちは、ラクマの@itinaoです。

E2Eテストについて、概要からお手軽に試す方法までを全5編で記載しています。

1. E2Eテスト: 導入の必要性・何を導入するのか
2. E2Eテスト: TestCafeを試す
3. E2Eテスト: Github Actions上でTestCafeを試す(PCブラウザ編)
4. E2Eテスト: Github Actions上でTestCafeを試す(モバイルブラウザ編)
5. E2Eテスト: Selenium Gridを試す ← 今回はココ

前回まででTestCafeを試していましたが、
いにしえから使われているSeleniumに関しても触れておかないとE2Eテストに関する記事として締めれないぞ。。と思い番外編として記載します。

E2Eテストのツール選定の要件として下記の2点を上げていたので
Dockerを使ってその要件をクリアできるような書き方を試してみたいと思います。

対応ブラウザの種類

並列実行の容易さ

# Selenium Gridを試す
## 構成
このような構成を目指していきます。

### Selenium
Seleniumは、Webアプリケーションをテストするためのポータブルフレームワークです。

### Selenium Grid
Seleniumで実行される動作を管理するツールで、
Node内に起動するブラウザでテスト処理を実行し、HubがNodeを管理してテストスクリプトからの命令を仲介することができます。

ブラウザを複数起動してもHubが接続を管理してくれるので、並列処理なども簡単に行えます。
(図で言うと、真ん中の selenium/hub のことを指します)

### WebdriverIO(wdio)
WebdriverIOはSelenium WebDriverを使用したクロスプラットフォームなテストフレームワークです。

モバイルブラウザに対してもAppiumを利用することでE2Eテストを実施することができます。

## 実行してみる

### ローカルのブラウザで実行する
まずはミニマムで、
下記のようにMac上のGoogle Chromeを使ってテストを実行してみたいと思います。

(空のディレクトリに対して操作を進めるカタチでコマンドを記入していきます。)
```shell
# 初期化
$ npm init

# wdioインストール
$ yarn add -D @wdio/cli

# コンフィグファイルを作成します(質問には下記のように答えています)
$ yarn run wdio
yarn run v1.22.10
$ /Users/xxxxx/work/e2e-selenium-grid-sample/node_modules/.bin/wdio
? Error: Could not execute "run" due to missing configuration. Would you like to create one? Yes

=========================
WDIO Configuration Helper
=========================

? Where is your automation backend located? On my local machine
? Which framework do you want to use? mocha
? Do you want to run WebdriverIO commands synchronous or asynchronous? sync
? Are you using a compiler? TypeScript (https://www.typescriptlang.org/)
? Where are your test specs located? ./test/specs/**/*.ts
? Do you want WebdriverIO to autogenerate some test files? Yes
? Do you want to use page objects (https://martinfowler.com/bliki/PageObject.html)? Yes
? Where are your page objects located? ./test/pageobjects/**/*.ts
? Which reporter do you want to use? spec
? Do you want to add a service to your test setup? chromedriver
? What is the base url? http://localhost
```

すると、
`wdio.conf.js` や必要なパッケージがインストールされます。

`package.json` にインストールされている `chromedriver` のバージョンを確認し、
インストールされているGoogle Chromeのバージョンが異なる場合は `chromedriver` のバージョンを下げます。

```shell=
$ grep 'chrome' package.json
"chromedriver": "^89.0.0",
"wdio-chromedriver-service": "^7.0.0"

# バージョンが合っていないのでバージョンダウン(error Outdated lockfileって出ていたら、yarn installをしておく)
$ yarn upgrade chromedriver@88
```

これでテストできる状態になったので、実行します。

```shell
$ yarn run wdio
...
[chrome 89.0.4389.82 mac os x #0-0] Running: chrome (v89.0.4389.82) on mac os x
[chrome 89.0.4389.82 mac os x #0-0] Session ID: abd90eb7a52b1b86fbf111b5d84cec47
[chrome 89.0.4389.82 mac os x #0-0]
[chrome 89.0.4389.82 mac os x #0-0] My Login application
[chrome 89.0.4389.82 mac os x #0-0] ✓ should login with valid credentials
[chrome 89.0.4389.82 mac os x #0-0]
[chrome 89.0.4389.82 mac os x #0-0] 1 passing (4s)


Spec Files: 1 passed, 1 total (100% completed) in 00:00:10

2021-03-08T09:29:26.033Z INFO @wdio/local-runner: Shutting down spawned worker
2021-03-08T09:29:26.290Z INFO @wdio/local-runner: Waiting for 0 to shut down gracefully
2021-03-08T09:29:26.290Z INFO @wdio/local-runner: shutting down
✨ Done in 11.79s.
```

ブラウザが自動的に起動し、テスト内容の画面遷移を行い、成功しました。

### Selenium Gridを使う

最初に挙げていた、この環境を作っていきます。


#### docker-composeを実行する

こちらを拝借し、実行していきます。

https://github.com/SeleniumHQ/docker-selenium/blob/trunk/docker-compose-v3.yml

docker-compose.yml として下記の内容を保存します。
```yaml
# To execute this docker-compose yml file use `docker-compose -f docker-compose-v3.yml up`
# Add the `-d` flag at the end for detached execution
# To stop the execution, hit Ctrl+C, and then `docker-compose -f docker-compose-v3.yml down`
version: "3"
services:
chrome:
image: selenium/node-chrome:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
ports:
- "6900:5900"

edge:
image: selenium/node-edge:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
ports:
- "6901:5900"

firefox:
image: selenium/node-firefox:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
ports:
- "6902:5900"

opera:
image: selenium/node-opera:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
ports:
- "6903:5900"

selenium-hub:
image: selenium/hub:4.0.0-beta-1-20210215
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
```

起動します。
```shell=
$ docker-compose up
```

`http://localhost:4444/` へアクセスし、下記画面が表示されていれば成功です。


#### `wdio.conf.js` を書き換える

この内容を参考に、一部追記してきます。

https://github.com/17thSep/WDIO6_Docker/blob/master/wdio.conf.js

wdio.conf.js
```javascript
...
// =====================
// Server Configurations
// =====================
// Host address of the running Selenium server. This information is usually obsolete as
// WebdriverIO automatically connects to localhost. Also, if you are using one of the
// supported cloud services like Sauce Labs, Browserstack, Testing Bot or LambdaTest you don't
// need to define host and port information because WebdriverIO can figure that out
// according to your user and key information. However, if you are using a private Selenium
// backend you should define the host address, port, and path here.
//
hostname: 'localhost', // 完全に追記
port: 4444,
path: '/',
...
capabilities: [{
maxInstances: 5, // ※ chromeは最初からあった
browserName: 'chrome',
acceptInsecureCerts: true
},{
maxInstances: 5,
browserName: 'firefox',
acceptInsecureCerts: true
},{
maxInstances: 5,
browserName: 'operablink',
acceptInsecureCerts: true
},{
maxInstances: 5,
browserName: 'MicrosoftEdge',
acceptInsecureCerts: true
}],
...
//
// Test runner services
// Services take over a specific job you don't want to take care of. They enhance
// your test setup with almost no effort. Unlike plugins, they don't add new
// commands. Instead, they hook themselves up into the test process.
services: ['docker'], // dockerに変更
...
```

一部warningが出てますが、実行可能です。

```shell=
$ yarn run wdio
...
[firefox 85.0.2 linux #1-0] Running: firefox (v85.0.2) on linux
[firefox 85.0.2 linux #1-0] Session ID: 983fb080-dfd9-43ab-8351-440759dcf4d4
[firefox 85.0.2 linux #1-0]
[firefox 85.0.2 linux #1-0] My Login application
[firefox 85.0.2 linux #1-0] ✓ should login with valid credentials
[firefox 85.0.2 linux #1-0]
[firefox 85.0.2 linux #1-0] 1 passing (6.5s)


Spec Files: 4 passed, 4 total (100% completed) in 00:00:25

2021-03-08T09:58:21.579Z INFO @wdio/local-runner: Shutting down spawned worker
2021-03-08T09:58:21.831Z INFO @wdio/local-runner: Waiting for 0 to shut down gracefully
2021-03-08T09:58:21.831Z INFO @wdio/local-runner: shutting down
✨ Done in 27.36s.

```

実行中の状況も、このように表示されています。

# おわりに
[docker-android](https://github.com/budtmo/docker-android)もあるので、この構成にAndroidのChromeを追加することもできそうです。

また、iOS版SafariがWebDriverをサポート(iOS 13から)しているそうなので、
Mac上のコマンド実行であれば主要のブラウザを全てカバーできそうだなと思いました。

思ったより手軽だったので、これも有りかな?と思いました。

(ただし、設定する内容が多岐に渡るので複雑で、動作も安定性に欠けるとの記事も見かけるので良し悪しはある。。)

## サンプルコード
https://github.com/itinao/e2e-selenium-grid-sample