ウェブサイトのテストプログラムは、Page Object Modelで書くのが良いとされている。
SitePrismとCapybaraを使うことで、画面をコード化する部分と画面を操作する部分を綺麗に分けてプログラムコードにすることができる。その結果、変化に強いテストプログラムを作ることが可能になる。

ぺージオブジェクトパターンでないテストプログラム
Seleniumを使ってWebサイトのテストコードを書くのテスト対象は、ページ数が少なく、かつ、シンプルなページのため、Page Object Modelにする良さがわかりにくい。そこで、複数のページ遷移を確認するテストを用意した。Yahoo! JAPANから三重県津市の天気のページへ移動できるかというテストだ。
#! ruby
# -*- mode:ruby; coding:utf-8 -*-
require 'capybara/rspec'
require 'selenium-webdriver'
RSpec.configure do |config|
config.include Capybara::DSL
end
Capybara.default_driver = :selenium
Capybara.app_host = 'http://www.yahoo.co.jp/'
describe "Yahoo Weather" do
before do
visit '/'
end
it "Tsu weather" do
# トップページを表示しているか確認
expect(page).to have_content("主なサービス")
click_on '天気'
# 天気のページに移動したかを確認
expect(page).to have_content("全国の天気")
within('#adrssrch') do
fill_in "p", with: '津'
click_button "検索"
end
# 検索結果のページかを確認
expect(page).to have_content("三重県津市")
end
end
これも大したことないが、
- 意図したページに移動しているかを、have_content()を使ってページ中の文言で判断している
- 検索フォームに「津」を入力する際、within()を使ってページの構造を辿って入力エリアを決めている
という問題がある。
ページ中の文言で判断する問題
もし意図しないページに移動していても、そのページに同じ文言が存在していると、テストをパスしてしまう。そのページ固有の(ユニークな)文言が存在するのであれば、判断に使えるだろう。だが、そういった文言は普通ない。URLを使えば、単純なページ遷移のチェックには十分だろう。
セッション情報によって、同じURLであっても表示内容が異なるというのはよくあることなので、決して万能な解決策ではないが。
ページの構造を辿る問題
「三重県津市の天気のページに移動できるか?」というテストがページ構造に影響されるというのは、テストとしてどうであろう?テストの目的からは外れているのではないか?adrssrchというidを持つ要素が存在しているかどうかは、「ページのテスト」で行うべき項目であって、「ページの移動のテスト」で行うテスト項目ではない。「ついでにテストできる」という意見もあるが、それは、テストの管理が煩雑になって漏れを生む原因である。
ぺージオブジェクトパターンで書き直す
SitePrismを使って、このテストをPage Object Modelに書き直してみる。
#! ruby
# -*- mode:ruby; coding:utf-8 -*-
#
# Page Object Model version of test2_spec.rb
require 'capybara/rspec'
require 'selenium-webdriver'
require 'site_prism'
Dir[File.join(File.dirname(__FILE__), "*_page.rb")].each{ |f| require f }
RSpec.configure do |config|
config.include Capybara::DSL
end
Capybara.default_driver = :selenium
Capybara.app_host = 'http://www.yahoo.co.jp/'
describe "Yahoo Weather" do
before do
@top = TopPage.new
@top.load
end
it "Tsu weather" do
# トップページを表示しているか確認
expect(@top).to be_displayed
weather_page = @top.click_weather
# 天気のページに移動したかを確認
expect(weather_page).to be_displayed
result_page = weather_page.weather_search('津')
# 検索結果のページかを確認
expect(result_page).to be_displayed(loc: '津')
# expect(page).to have_content("三重県津市")
end
end
続いて、各ページの要素を定義するコード。上の書き直したコードと同じディレクトリに配置する。
Topページ
#! ruby
# -*- mode:ruby; coding:utf-8 -*-
# トップページ
class TopPage < SitePrism::Page
set_url "/"
# ページ中の要素を定義する
element :weather_service, '#yahooservice > ul > li:nth-child(6) > a'
# 要素に対する操作を定義する
def click_weather
weather_service.click
# ページ遷移が発生する場合、新しいPage Objectを返す
WeatherPage.new
end
end
天気予報のページ。
#! ruby
# -*- mode:ruby; coding:utf-8 -*-
# 天気・災害ページ
class WeatherPage < SitePrism::Page
set_url "http://weather.yahoo.co.jp/weather/"
# ページ中の要素を定義する
element :weather_search_field, '#searchText'
element :search_button, '#yjw_button_search'
# 要素に対する操作を定義する
def weather_search(param)
weather_search_field.set param
search_button.click
WeatherSearchResultPage.new
end
end
検索結果のページ。
#! ruby
# -*- mode:ruby; coding:utf-8 -*-
# 天気・災害ページ-検索結果
class WeatherSearchResultPage < SitePrism::Page
set_url "http://weather.yahoo.co.jp/weather/search/?p={loc}"
element :search_word, '#main > div.yjw_main_md.yjw_clr > div.serch-keyword-frame01 > div > strong'
end
最後にGemfileの修正。SitePrismを使うよう gem ‘site_prism’ の1行を追加して、bundle installしておく。
# frozen_string_literal: true # A sample Gemfile source "https://rubygems.org" # gem "rails" gem "rspec" gem "selenium-webdriver" gem "capybara" gem "site_prism"
まずはこのテストを bundle exec rspec test3_rspec.rb として実行してみよう。ページのコードではなく、ぺージオブジェクトパターンで書き直したテストコードを実行する。
テスト全体で見ると、行数は増えている。しかし、テスト内容をコードにした部分とページに関する内容をコードにした部分が分離できているため、テスト内容が変わらない限り、テストコードを変更する必要はない。
ページの移動の確認
expect().to be_displayed を使ってURLで確認するため、ページ中の文言に依存しない。URLのパラメータを渡すこともできるため、パラメータまで含めて確認することができる。
ページ構造の変更への追従
基本的に、「ページのデザインが変わった場合はページに関するコードを修正する」ことで対応できる。テストコードへの影響は生じない。
まとめ
Seleniumデザインパターン&ベストプラクティスには、Page Objectパターンで作っていく過程が書かれている。しかし、ページの各要素に対して行いたい基本的な操作は決まっているので、既存のライブラリを使う方がよいだろう。SitePrismは、そういったものの1つである。
