ウェブサイトのテストプログラムは、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つである。
コメント