BrowserStackとSeleniumを使って自動ブラウザテストをしてみた

最近、私の関わっている案件で、ブラウザテストを自動化しようという話が出てきました。  

それで色々と調べてみたところ、どうやらSeleniumを使った自動テストをBrowserStack上で動かすのが良さそうだ、ということになったので、実際にサンプルコードを書いて、どんなものか検証してみました。  

この記事では、そこで得られた知見を共有したいと思います。

## ツールの紹介

はじめに、SeleniumとBrowserStackについて紹介します！

### Selenium

![Selenium-logo](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/big-logo.png)

[Selenium](https://www.seleniumhq.org/)は、ブラウザの操作を自動化するツールです。  

従来のブラウザテストでは、同じような操作（例えば、「フォームを記入して送信ボタンを押す」というような操作）を何度も人の手で行わなければなりませんでした。なかなかつらい作業ですよね。

Seleniumはこれらの作業を全て自動で行ってくれます！  
もちろんテストコードを書く必要はありますが、クロスブラウザテストや回帰テストなどで同じテストを繰り返し実施する労力は、かなり削減できるのではないでしょうか。

Seleniumは、ChromeやSafari、IE、FireFox、Operaなど、主要なブラウザを操作することができます。また、JavaやC#、Python、Ruby、JavaScriptなど、様々な言語に対応しています。

今回のサンプルではJavaScript（Node.js）でコードを書いていきます。  
ただし、Selenium自体はあくまでブラウザ操作のためのツールであり、テスト機能は持ち合わせていません。
テストを書くにあたって、今回は、JavaScriptのテストフレームワークである[Mocha](https://mochajs.org/)を併用します。

### BrowserStack

![BrowserStack-logo](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/browserstack-logo.png)

[BrowserStack](https://www.browserstack.com/)は、様々なブラウザ環境を提供してくれるクラウドサービスです。

ブラウザテストを実施するにあたって意外と手間がかかるのが、テスト環境の用意ではないでしょうか。  
Safariでの動作を確認するためにMacを用意したり、IEでの確認のためにWindows機を用意したりと、結構、大変です。しかもIEの場合、一つのマシンに一つのバージョンしか入れられないため、IE9、10、11をテストしようとしたら、PCなりVMなりを3つ用意しなければならなかったりします:disappointed_relieved:

このような課題を解決してくれるのがBrowserStackです！  
BrowserStackは2000以上のデバイスとブラウザをクラウド上で提供しており、ユーザーはそれらの環境を自由に利用することができます（提供されているOSとブラウザの一覧は[こちら](https://www.browserstack.com/list-of-browsers-and-platforms/automate)）。

しかもそれだけではなく、テスト中の画面のスクリーンショットや動画を自動で撮ってくれたりといった、便利な機能もついています。また今回は扱いませんが、自動テストだけでなく、手動でのテストにも対応しています。

このように至れり尽くせりなサービスなのですが・・・有料です:sweat_smile:  
自動テストプランの場合、1か月1ユーザーにつき、129ドルかかります（年額購入の場合。詳しくは[こちら](https://www.browserstack.com/accounts/subscriptions?tab=browser-plans-tab)）。

今回のデモでは無料トライアルプランを利用します。無料でも100分の自動テストと100枚のスクリーンショットが取れますので、ちょっと試してみる分には十分でしょう。

## デモ概要

それではさっそくデモに移りましょう。  
今回のデモでは、こちらの[フォーム画面](http://glacial-dusk-36576.herokuapp.com/)のIE11動作テストを自動化します。
<img width="256" alt="sampleform.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289490/2afe7e40-db0a-4f5e-f0d8-ad34a2ebb509.png">

以下、
- BrowserStackへのユーザー登録
- テストコード作成
- テスト実施

の順で説明していきます。

## BrowserStackユーザー登録

テストコードを書く前に、BrowserStackのユーザー登録を行っておきます。  
[BrowserStackトップ画面](https://www.browserstack.com/)の`Get started free`もしくは`FREE TRIAL`ボタンからアカウント登録ができます。

ログインすると、下のようなダッシュボード画面が表示されます（表示されない場合はメニューの`Products`から`Automate`を選択してみてください）。

![BSdashboard](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/BSdashboard.png)

テストのログやスクリーンショット、動画はこのダッシュボード画面から確認することができます。

## テストコード作成

テストコードを書いていきます。  
上でも述べたように、今回はSeleniumと合わせて、テストフレームワークのMocahを使用し、JavaScript（Node.js）でコードを書いていきます。[Node.js](https://nodejs.org/ja/download/)がない方はインストールしておきましょう。  

### ディレクトリの作成

まず適当な場所にテストコードを格納するディレクトリ（今回のデモでは`demo-test`）を作成します。  
ディレクトリ構造は任意ですが、今回のデモでは以下のようにします。

```
demo-test/
      ├ conf/  BrowserStackを使うための設定ファイルを格納
      └ test/  テストコードを格納
```

`demo-test`に移動し、`npm init -y`コマンドを実行します。

```
cd /Users/MyName/Project/demo-test
npm init -y
```

このコマンドを実行することで、ディレクトリ内にプロジェクトの情報等が記された`package.json`ファイルが作成されます。

### モジュールのインストール

SeleniumとMochaを使用するために、以下のコマンドでインストールします。

```
npm i -D selenium-webdriver mocha
```

`package.json`を確認して、次のような行が追記されていればOKです！

```json:package.json
"devDependencies": {
  "mocha": "^6.1.4",
  "selenium-webdriver": "^4.0.0-alpha.1"
}
```

### テストコードファイルの作成

`test`ディレクトリにテストコード本体である`form-test.js`を、`conf`ディレクトリにBrowserStackを使用するのに必要な情報をまとめた`test.conf.js`をそれぞれ作成します。  

以下、`test.conf.js`、`form-test.js`の順で見ていきます。

### test.conf.js

```js:test.conf.js
require('dotenv').config();

exports.capabilities = {
    'browserstack.user': process.env.BROWSERSTACK_USERNAME || 'USERNAME',
    'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY || 'ACCESS_KEY',
    'build': 'mocha-browserstack',
    'name': 'form_test',
    'browserstack.debug': 'true',
  
    'browserName': 'internet explorer',
    'version': '11',
    'platform': 'WINDOWS'
  
  };
```

このファイルには、BrowserStackのユーザー名やアクセスキー、テストするOSやブラウザの種類といった、BrowserStack上でテストを動かすのに必要な情報が書かれています。

`capabilities`オブジェクトの下三行に注目してください。  

```js:test.conf.js
'browserName': 'internet explorer',
'version': '11',
'platform': 'WINDOWS'
```

ここでテストに使うブラウザとそのバージョン、OSを指定します。  
他のブラウザでテストしたいときはここを変えればいいんですね。

続いて上二行に注目してください。

```js:test.conf.js
'browserstack.user': process.env.BROWSERSTACK_USERNAME || 'USERNAME',
'browserstack.key': process.env.BROWSERSTACK_ACCESS_KEY || 'ACCESS_KEY',
```

ここにBrowserStackにアクセスするために必要なユーザー名とアクセスキーを記入します。  
ユーザー名およびアクセスキーは、BrowserStackのダッシュボード画面左の`Username and Access Keys`をクリックすると表示されます。

ここで一点、注意事項があります。  
ユーザー名とアクセスキーの値をそのまま張り付けてしまっても動くのですが、こうしたセキュリティ上重要な情報をコードに書いてしまうのはちょっと心配です。  
ローカルでコードを管理する分にはいいのですが、GitHub等で管理する場合、アクセスキーなどの情報がそのままネット上に公開されてしまいます。

これらの情報は環境変数として`.env`ファイルにまとめて、gitの管理からも外してしまうのがよいでしょう。  

`.env`ファイルを作成するために、[dotenv](https://www.npmjs.com/package/dotenv)というモジュールをインストールします。

```
npm i dotenv
```

`test.conf.js`の1行目で`dotenv`を読み込みます。

```js:test.conf.js
require('dotenv').config();

```

`demo-test`直下に`.env`ファイルを作成します。中身は以下のようにしてください。

```ini:.env
BROWSERSTACK_USERNAME=ユーザー名
BROWSERSTACK_ACCESS_KEY=アクセスキー
```

`.gitignore`ファイルに`.env`を追記します。

```:.gitignore
.env
```

これでユーザー名とアクセスキーを安全に使用することができます:blush:

以上、テストに使うブラウザ・OSの設定と、ユーザー名・アクセスキーの設定を行いました。  
他のプロパティについては、[公式ドキュメント](https://www.browserstack.com/automate/capabilities)を参照してみてください。

### form-test.js

```js:form-test.js
const { Builder, By } = require("selenium-webdriver");
const assert = require("assert");
const conf_file = 'conf/test.conf.js';
const caps = require('../' + conf_file).capabilities;

let driver;

describe("入力フォーム デモ", function() {
  before(function() {
    driver = new Builder()
      .usingServer('https://hub-cloud.browserstack.com/wd/hub')
      .withCapabilities(caps)
      .build();
    process.on("unhandledRejection", console.dir);
  });

  after(function() {
    return driver.quit();
  });

  it("コメントの必須入力チェック その1", async function() {
    // テスト対象のページへアクセス
    await driver.get(
      "http://glacial-dusk-36576.herokuapp.com/"
    );

    // 名前だけ入力してSubmitする
    await driver.findElement(By.name("name")).sendKeys("Qiita太郎");
    await driver.findElement(By.id("submit-button")).click();

    // エラーメッセージを取得して、文言が正しいかチェックする
    const errorMsg = await driver
      .findElement(By.className("error-message"))
      .getText();

    assert.equal(errorMsg, "コメントを入力してください。");
  });

  it("コメントの必須入力チェック その2", async function() {
    // テスト対象のページへアクセス
    await driver.get(
      "http://glacial-dusk-36576.herokuapp.com/"
    );

    // 名前とコメントを入力してSubmitする
    await driver.findElement(By.name("name")).sendKeys("Qiita太郎");
    await driver.findElement(By.name("comment")).sendKeys("こんにちは");
    await driver.findElement(By.id("submit-button")).click();

    // 成功メッセージを取得して、文言が正しいかチェックする
    const successMsg = await driver
      .findElement(By.className("success-message"))
      .getText();

    assert.equal(successMsg, "コメントを送信しました。");
  });
});
```

こちらがテストコード本体になります。上から順に見ていきましょう。

まず最初の4行で必要なモジュールを読み込んでいます。

```js:form-test.js
const { Builder, By } = require("selenium-webdriver");
const assert = require("assert");
const conf_file = 'conf/test.conf.js';
const caps = require('../' + conf_file).capabilities;
```

2行目で`require("assert")`していますが、実はMochaにはアサーション機能がついていないため、ここでNode.jsのassertモジュールを読み込んでいます。  
3、4行目では先ほど作成した`test.conf.js`を読み込んでいます。

Mochaの仕様で、テストは`describe`メソッドの中に書きます。  
個々のテストは`it`の中に書いていきます。テスト実施前に行う処理は`before`、すべてのテストが終わった後の処理は`after`の中に書きます。

```js:form-test.js
before(function() {
  driver = new Builder()
    .usingServer('https://hub-cloud.browserstack.com/wd/hub')
    .withCapabilities(caps)
    .build();
  process.on("unhandledRejection", console.dir);
});
```
まず、`before`メソッド内で、`caps`の情報をもとにウェブドライバーを作成しています。

この後、二つの`it`テストが実行され、最後に`after`メソッド内でブラウザを閉じ、テストを終了します。

```js:form-test.js
after(function() {
  return driver.quit();
});
```

`it`テストの中身を見ていきましょう。

```js:form-test.js
it("コメントの必須入力チェック その1", async function() {
  // テスト対象のページへアクセス
  await driver.get(
    "http://glacial-dusk-36576.herokuapp.com/"
  );

  // 名前だけ入力してSubmitする
  await driver.findElement(By.name("name")).sendKeys("Qiita太郎");
  await driver.findElement(By.id("submit-button")).click();

  // エラーメッセージを取得して、文言が正しいかチェックする
  const errorMsg = await driver
    .findElement(By.className("error-message"))
    .getText();

  assert.equal(errorMsg, "コメントを入力してください。");
});
```

コードとコメントを見れば、大体の流れは把握できると思います:sweat_smile:  
`driver`以下の部分がSeleniumが提供しているメソッドですね。

`.findElement(By)`でHTMLの要素を取得します。`By`以下は、idやname属性値、CSSクラス名等を指定することができます。  
さらに`.sendKeys`や`.click`といったメソッドをチェーンすることができます。

最後に`assert.equal`で、出てきたエラーメッセージと第二引数の文字列が等しいかどうか判定しています。

以上が、今回のデモで使用するテストコードになります。

## テスト実施

いよいよテストを実施します！  

### コマンドの設定

コマンドラインからテストを実行できるように、`package.json`の`scripts`に以下のように追記します。

```json:package.json
"scripts": {
  "test:form": "mocha test/form-test.js --no-timeout"
}
```
コマンドラインで`test:form`コマンドを打つと、mochaを使って`test/form-test.js`に書かれたテストが実行されます。  
`--timeout 10000`のようにして、タイムアウト時間（ミリ秒）を設定することもできますが、今回はタイムアウト無し（`--no-timeout`）で実行します。

それではテストを走らせましょう。  
以下のコマンドを実行してください。

```
npm run test:form
```

テストが実行されました！
コマンドラインにテスト結果が表示されていることと思います。

![result](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/result.png)

### BrowserStackで確認

BrowserStackのダッシュボード画面に行ってみましょう。  

![BSresult1](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/BSresult1.2.png)

先ほど実施したテストが表示されています。テスト名をクリックします。

![BSresult2](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/BSresult2.png)

テスト実行結果の詳細が表示されます。テスト実行時の動画も撮られていますね。

![BSresult3](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/BSresult3.png)

ログもきちんと取れています。

![BSresult4](https://cdn.hypertextcandy.com/posts/browserstack-selenium-test/BSresult4.png)

操作ごとのスクリーンショットも撮られています。画像と動画はもちろんダウンロードできます。

とまあこんな感じです。

自動でスクショを撮ってくれるのでエビデンス作成の手間が省けますし、動画を見ればどこでテストがつまづいたか簡単に確認できますね！

## まとめ
以上、SeleniumとBrowserStackを使って、ブラウザテストを自動化してみました。

正直、これくらいのテストであれば手でやった方が早いのですが、今後、どんどんテストが増え、また繰り返し実行しなければならない状況になった時にはやはり、こうした自動化ツールが頼りになるのだと思います。


### 参考
今回、テストコードを書くにあたって、以下のページを参考にしました。

[Node.jsとSeleniumでWebアプリのUIテストを自動化（2018年版）](https://ics.media/entry/5759/)  
[Selenium examples for Mocha and BrowserStack Automate](https://github.com/browserstack/mocha-browserstack)