はじめに
Python で E2E テスト書きたいなーと思ってフレームワークを調べてみると、Selenium + Pytest な SeleniumBase なるものを見つけたのですが、なんと日本語情報が全くない…
Google 検索した結果こんな感じ。(Seleniumしか出てこない…)
ということで、せめてインストールから基本的な使い方ぐらいまでは情報があると便利かなと思い、まとめてみます。
SeleniumBaseではまだ3,4ページ遷移する程度の軽いE2Eテストしか書いていないのですが、使い勝手は悪くないので、PythonでE2Eを書きたい方はぜひ使ってみると良いと思います。
インストール
http://seleniumbase.com/#-quick-start にインストール方法が書いてあるんですが、あまりQuickではないので、
$ pip install seleniumbase
でさらにクイックにインストールするのが良いかと思います。
chromedriver
のインストールも必要なのですが、これは環境によって入れ方が異なります。
macOSであれば
$ brew tap caskroom/cask
$ brew cask install chromedriver
でいけます。
Pure Python ベースの軽量 Docker Image も作ったので、仮想環境で試したい場合は https://github.com/showwin/docker-seleniumbase を使ってみてください。
Linuxでインストールしたい方は上のDockerfileを参考にすると良いと思います。
使い方
BaseCase
クラスは unittest.TestCase
を継承しているので、テストの書き方はunittestと同じです。
こんな感じでテストケースを書くと
import time
from seleniumbase import BaseCase
class GitHubTests(BaseCase):
def slow_click(self, css_selector):
time.sleep(1)
self.click(css_selector)
def test_github(self):
self.open("https://github.com/")
self.update_text("input.header-search-input", "SeleniumBase\n")
self.slow_click('a[href="/seleniumbase/SeleniumBase"]')
self.assert_element("div.repository-content")
self.assert_text("SeleniumBase", "h1")
self.slow_click('a[title="seleniumbase"]')
self.slow_click('a[title="fixtures"]')
self.slow_click('a[title="base_case.py"]')
self.assert_text("Code", "nav a.selected")
こんな感じで、実際にブラウザで動いているのが見えます。
コンテナ内で動かす場合にはGUIで見れないのが残念ですが、macOS上では実際の画面遷移を見ることができるので、E2Eを書きながらデバッグしやすいと思います。
E2Eはどうしても不安定になりがちなので、確率的にテストが落ちたり通ったりすることがあるかと思いますが、実際の画面遷移が見えないとどのような状態でテストが落ちたのかわからず、デバッグに意外と時間と取られてしまいます。
よく使うようなクリックやフォームへの埋め込みは self.click()
self.update_text()
など短いメソッド名でかけるので、コードの見た目もスッキリして良いです。
細かな動きが必要な場合は self.driver
でSelenium WebdriverのAPIを触ることもできます。
self.driver.find_element_by_css_selector("textarea").send_keys("text")
これが
self.update_text("textarea", "text")
でかけるのは可読性が高まって嬉しいですね。
Tips
実際にテストを書いていく上での細かなテクニックです。
テストの実行順をコントロールする
E2Eの最初にテスト用のアカウントを作成して、最後にそのアカウントを消して終了したいというようなことがあると思います。
pytest ではファイル名のアルファベット順でテストが実行されるので、ファイル名の先頭に実行したい順で数字を振るのが良さそうです。
テストの中身を他でimportすることはないと思うので、テストが増えてきてやっぱりファイル名の数字を変えたい。となっても困ることはないでしょう。
$ tree e2e
e2e
├── 0100_create_account_test.py
├── 0200_dashboard_test.py
├── 0210_dashboard_left_side_test.py
├── 9999_delete_account_test.py
└── __init__.py
同一ファイル内では、メソッド名のアルファベット順で実行がされるので、こちらも同様にナンバリングしていくとわかりやすいと思います。
from seleniumbase import BaseCase
class CreateAccountTests(BaseCase):
def test_0_open_signup(self):
pass
def test_1_signup(self):
pass
def test_2_1_login_failure(self):
pass
def test_2_2_login_success(self):
pass
おわりに
私もまだ SeleniumBase を深く触ってはいないので、浅い紹介になりましたが、一通り検証するのに必要な情報は提供できたかなと思います。
バグを直した 1行Delete のPullReq出したら、 Contributorランキング3位 になってしまったぐらいだれも貢献してくれていないかわいそうなプロジェクトなので(笑)、ぜひPythonistaの皆さんで育てていきましょう!