【FreeBSD】FreeBSDでWebDriver経由でのテスト開発を行う方法

## はじめに

お久しぶりです。久々にコンテナホスティングの開発より、記事が執筆できそうです。今日はChromiumによるDjangoのLiveTestを行なっていきたいと思います。
現在、絶賛開発中のコンテナホスティングPF、`jaisting`はFreeBSDの準仮想化機能である`jail`を用いたコンテナホスティングPFです。イメージ的にはAWS Consoleのコンテナversionと思っていただければ。

何分、必要な環境がFreeBSD専用になっている事もあり、`Linuxならすぐ出来るのに、、、`と思うような事ができない事も多いです。jaistingはそのようなモチベーションから開発を行なっています。

そして、そろそろブラウザ経由によるシミュレーションテストをしておかないと取りこぼしのバグを産み出しそうなので、chromiumによるブラウザテスト駆動の設定・開発を行う事としました。

## selenium-webdriverについて

selenium-webdriverは、ブラウザのオートメーションツールです。W3Cが定義しているWebDriver(昔はSeleniumが定義していた Java Wire Protocolという独自の仕様だった?)の仕様に基づいた通信を各ブラウザで用意されているドライバを経由する事で各ブラウザの操作を自動で行う事ができます。

このドライバが曲者でして、`実は主要ブラウザの公式リポジトリでもBSD系のOSではドライバが用意されていない事が多々あります汗`

筆者もこの事実に気づき、半ば諦めていましたが、オープンソースソフトウェアである`Chromium`であれば、FreeBSDの公式パッケージでも用意されており、かつWebDriver用のドライバも用意されています。そこで、今回の開発ではChromiumによるブラウザテストのコーディングを行なっていきたいと思います。

因みに、Chromiumを使わずにFreeBSDでブラウザテストを行いたいのであれば、試していないのですが以下のようなものが他にも考えられるでしょうか。

1. FreeBSDでLinuxのエミュレータを有効にして、Linux用のWebDriverを動かせるようにする?(未検証、出来たとしてもテスト以上にメンテナンスしなければならない事が多いので、その前に心折れる気がする)。
2. 公式Pkgリポジトリで用意されているFirefox, Operaなどの他のブラウザをインストールする?(こちらも未検証)

## 環境

```
* FreeBSD 12.0-RELEASE-p2
* Chromium 76.0.3809.132
```

## Chromium && 日本語フォントのインストール

早速、chromiumをインストールします。また、日本語のフォントもないためそのままchromiumを起動すると、文字化けが発生するため、日本語フォントもインストールします。

```zsh
% sudo pkg install chromium
% sudo pkg install ja-font-ipa
```

デフォルトのchromium,chromedriverのバイナリファイルの置き場所は/usr/local/binにあります。

### ユーザログインのテスト

実際にWebDriverでブラウザテストが行えるかテストコードを書いていきます。
今回は、ひとまず正常系のテストのみでログイン後に遷移する画面に辿れるかテストします。

```python:users/tests.py
import os
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from django.contrib.auth import get_user_model
from selenium.webdriver.chrome.options import Options

class AccountsLoginTest(StaticLiveServerTestCase):


    @classmethod
    def setUpClass(self):
        super().setUpClass()
        CHROME_BIN = "/usr/local/bin/chrome"
        CHROME_DRIVER = os.path.expanduser('/usr/local/bin/chromedriver')
        options = Options()
        options.binary_location = CHROME_BIN
        options.add_argument('--headless')
        options.add_argument('--disable-gpu')
        self.selenium = webdriver.Chrome(CHROME_DRIVER, options=options)
        self.selenium.implicitly_wait(10)
        User = get_user_model()
        User.objects.create_user(username="test", password="test1234")

    @classmethod
    def tearDownClass(self):
        self.selenium.quit()
        super().tearDownClass()

    def test_login(self):
        self.selenium.get('%s%s' % (self.live_server_url, '/accounts/login/'))
        self.selenium.find_element_by_name("username").send_keys('test')
        self.selenium.find_element_by_name("password").send_keys('test1234')
        self.selenium.find_element_by_xpath('//button').click()
        self.assertEquals('Jail List', self.selenium.title)
```

```python
       options.add_argument('--headless')
       options.add_argument('--disable-gpu')
```

テストコードでは、headlessでブラウザが動くように設定します。headlessモードは、ブラウザのUI無しでCLI上で動かせるように出来るモードの事です。

```python
      self.selenium.implicitly_wait(10)
```

implicity_waitメソッドは、指定したドライバの要素が見つかるまで設定した待ち時間、待つ事ができます。今回はusernameとpassword、ログインボタンのフィールドが完全に読み込まれるまで、待ちたいので、

```python
        self.selenium.find_element_by_name("username").send_keys('test')
        self.selenium.find_element_by_name("password").send_keys('test1234')
        self.selenium.find_element_by_xpath('//button').click()
```

上記3つのフィールドが読み込まれるまで待ち、事前に登録したUserオブジェクトでログインできるか検証します。

一旦、これでちゃんとログインできているかスクショも取っておきます。

```python
        self.selenium.save_screenshot('screenie.png')
```

![screenie.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144991/358abafd-299d-d34e-051a-3e002fa96e72.png)


## 参考文献

* [https://app.codegrid.net/entry/selenium-1](https://app.codegrid.net/entry/selenium-1)
* [https://docs.djangoproject.com/en/2.2/topics/testing/tools/](https://docs.djangoproject.com/en/2.2/topics/testing/tools/)
* [https://docs.djangoproject.com/en/2.2/topics/testing/overview/](https://docs.djangoproject.com/en/2.2/topics/testing/overview/)
* [https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja](https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja)
* [https://www.seleniumqref.com/api/python/time_set/Python_implicitly_wait.html](https://www.seleniumqref.com/api/python/time_set/Python_implicitly_wait.html)