E2Eテスト実装のベストプラクティスとされているPageObjectパターンを試してみました。
テストケース
Djangoの管理画面からユーザ作成を行うテストを実装しました。
流れは次のとおりです。
- ログイン画面でユーザ名、パスワードを入力して[ログイン]をクリック
- 管理画面でユーザーの[+追加]をクリック
- ユーザ管理画面でユーザ名、パスワード、パスワードの確認を入力して[保存]をクリック
- ユーザ管理画面にユーザ作成完了のメッセージが表示されていることをテスト
PageObjectの実装
PageObjectはページ単位で作成するので、今回のケースだと次のPageObjectが必要になります。
-
LoginPage
- ログイン画面
-
AdminPage
- ログイン後に表示される管理画面
-
AddUserPage
- 管理画面でユーザーの[+追加]をクリックした後に表示されるユーザ追加画面
-
UserPage
- ユーザ追加完了後に表示される画面
ページ内で実行する操作をメソッドとして実装しますが、フォームへの入力、クリックなどの細かい単位ではなく、ある程度まとまったタスクをメソッドにします。
具体的には次のようにしました。
-
LoginPage
-
login
メソッド- ユーザ名、パスワードを入力して[ログイン]をクリック
-
-
AdminPage
-
go_to_add_user_page
メソッド- ユーザーの[+追加]をクリック
-
-
AddUserPage
-
add_user
メソッド- ユーザ名、パスワード、パスワードの確認を入力して[保存]をクリック
-
-
UserPage
- 今回は特に操作しないのでメソッドはなし
コード
実装したコードは次のとおりです。以下のリポジトリにも同じコードがあります。
https://github.com/shiimaxx/page-object-pattern-example
ログインページ
login.py
from pages.admin import AdminPage
class LoginPage(object):
def __init__(self, driver):
self.driver = driver
def login(self, username, password):
login_form = self.driver.find_element_by_id('login-form')
username_ = login_form.find_element_by_name('username')
password_ = login_form.find_element_by_name('password')
button = login_form.find_element_by_css_selector('input[value="ログイン"]')
username_.send_keys(username)
password_.send_keys(password)
button.click()
return AdminPage(self.driver)
管理ページ
admin.py
from pages.user import AddUserPage
class AdminPage():
def __init__(self, driver):
self.driver = driver
def go_to_add_user_page(self):
self.driver.find_element_by_css_selector('a[href="/admin/auth/user/add/"]').click()
return AddUserPage(self.driver)
ユーザ管理ページ
user.py
class AddUserPage(object):
def __init__(self, driver):
self.driver = driver
def add_user(self, username, password):
user_form = self.driver.find_element_by_id('user_form')
username_ = user_form.find_element_by_name('username')
password1 = user_form.find_element_by_name('password1')
password2 = user_form.find_element_by_name('password2')
button = user_form.find_element_by_name('_save')
username_.send_keys(username)
password1.send_keys(password)
password2.send_keys(password)
button.click()
return UserPage(self.driver)
class UserPage(object):
def __init__(self, driver):
self.driver = driver
テスト
test_django_admin.py
import unittest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from pages.login import LoginPage
class TestDjangoAdmin(unittest.TestCase):
def setUp(self):
options = Options()
options.binary_location = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
options.add_argument('--headless')
options.add_argument('--disable-gpu')
self.driver = webdriver.Chrome(chrome_options=options)
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(5)
self.driver.set_page_load_timeout(30)
self.driver.set_window_size(1920, 1080)
def test_add_user(self):
self.driver.get('http://127.0.0.1:8000/admin')
login_page = LoginPage(self.driver)
admin_page = login_page.login('admin', 'p@ssword')
add_user_page = admin_page.go_to_add_user_page()
user_page = add_user_page.add_user('testuser', 'dummy_p@ssword')
self.assertIn('testuser</a>" を追加しました。続けて編集できます。</li>', user_page.driver.page_source)
def tearDown(self):
self.driver.close()
まとめ
今回は1つのテストケースしか実装しなかったのでPageObjectパターンの恩恵はそこまでないかもしれませんが、テストを運用していくことを想定すると、テストケースが読みやすかったり、PageObjectやメソッドの再利用によってテストケースが追加しやすくなっていたりなど、保守性は上がっている状態だと思います。