書籍「実践SeleniumWebDriver」のPageObjectパターンをRubyでやってみた。
- 「実践SeleniumWebDriver(初版)」の「9.5 WordPressのエンドトゥエンドのサンプル」がJAVAで書かれてるのでRubyに置き換えようとした。
- だけど私はJAVAもRubyもよくわかんないので、会社にあった既存のテストコードを参考にしながら書いてみた。
- 結果として本に沿ってないし「置き換え」にはなってないっぽい><
- 正直自信ないので改善点をいただけると嬉しいです。(Rubyの言語仕様も理解不足なので...特にRubyとしての作法とか命名のお約束とか)
前置き
- サンプルとして載っていたのは「WordPressでブログを書く」際の操作を自動化する、という話
- 出てくる画面は下記
- ログイン画面 https://xxx.wordpress.com/wp-admin/
- 投稿一覧画面 https://xxxx.wordpress.com/wp-admin/edit.php
- 投稿新規作成画面 https://xxx.wordpress.com/wp-admin/post-new.php
- 投稿編集画面 https://xxx.wordpress.com/wp-admin/post.php?post=xxx&action=edit
- 投稿削除画面 (編集画面から「ゴミ箱へ」をクリック)
- やってるのはこんなこと
- ログインする
- 投稿の追加(新規投稿)
- 投稿の編集
- 投稿の削除
- 投稿数のカウント
- 私の環境
step1:ログインページを操作
- まずはこんなディレクトリ構成にする。2つのフォルダが同じディレクトリにあるっていうことでスタート。
E2Etest └ admin_login_page.rb └ test.rb
- ファイルの中身はこんなん
# admin_logion_page.rb # ログインページのPageObject class AdminLoginPage def initialize(driver) @driver = driver @driver.get("https://xxx.wordpress.com/wp-admin/") end # ページの機能 def set_email email.clear email.send_keys("イーメール") end def set_pwd pwd.clear pwd.send_keys("パスワード") end def click_login_btn login_button.click end # ページの要素 private def email return @driver.find_element(:id, "user_login") end def pwd return @driver.find_element(:id, "user_pass") end def login_button return @driver.find_element(:id, "wp-submit") end end
本のP.183によると
- PageObjectにはユーザーに対するサービスを提供するのであって、ユーザーのアクションを提供するのではない
多分上記のadmin_login_page.rbは「サービス」ではなく「ユーザーのアクション」が書かれてるんだと思う。
- で、test.rbはこんな感じ。
# test.rb require 'selenium-webdriver' require File.expand_path(File.dirname(__FILE__) + '/admin_login_page') driver = Selenium::WebDriver.for :firefox # ログインページにログインする admin_login_page = AdminLoginPage.new(driver) admin_login_page.set_email admin_login_page.set_pwd admin_login_page.click_login_btn
- コンソールから test.rb を実行してみると動くはず。
$ ruby test.rb
step2:投稿作成ページも作ってみる
- 投稿の新規作成画面(add_new_post_page.rb)を作成する。
E2Etest └ add_new_post_page.rb # ←投稿の新規作成page └ admin_login_page.rb └ test.rb
# add_new_post_page.rb # 新規投稿作成画面のPageObject class AddNewPostPage def initialize(driver) @driver = driver @driver.get("https://xxx.wordpress.com/wp-admin/post-new.php") end # ページの機能 # 本文を入力する def set_body(str) @driver.switch_to.frame("content_ifr") # WYSIWYGでの入力はframeの切り替えが必要 body.send_keys(str) end # タイトルを入力する def set_title(str) @driver.switch_to.default_content title.send_keys(str) end # 公開ボタンを押す def click_publish_btn @driver.switch_to.default_content publish_btn.click end # ページの要素 private def body return @driver.find_element(:id, "tinymce") end def title return @driver.find_element(:id, "title") end def publish_btn return @driver.find_element(:id, "publish") end end
- test.rb に記事投稿の処理を追加する
# test.rb require 'selenium-webdriver' require File.expand_path(File.dirname(__FILE__) + '/admin_login_page') require File.expand_path(File.dirname(__FILE__) + '/add_new_post_page') driver = Selenium::WebDriver.for :firefox # ログインページにログインする login_page = AdminLoginPage.new(driver) login_page.set_email login_page.set_pwd login_page.click_login_btn # 新規投稿 new_post_page = AddNewPostPage.new(driver) new_post_page.set_title("タイトルです") new_post_page.set_body("本文です") new_post_page.click_publish_btn
- これで新規投稿が可能になった
- これだと毎回同じタイトル、本文になるので、yyyymmdd_hhmmssとかを追加してユニークな文字列になるようにするtipsはどこかに公開されているはず
- ちなみに本だとAddNewPostPageの中にaddNewPostというメソッドがあって、PageObjectの中で「記事を書く」というアクションを一つのメソッドでまとめています。
step3:だったら同じようにpageに一連の処理をまとめておけばいいんじゃないか?
- http://www.slideshare.net/NozomiIto/gui-42684187 これのP55がまさにそうなんだなと理解。
- page の中で「ログインする」「記事を投稿する」の一連の処理をやってくれるメソッドを定義して、test.rbからはそのメソッドを呼ぶようにする
- まずはadmin_login_page.rbから
- loginを呼ぶと一気にログインしてくれるloginメソッドを追加
# admin_login_page.rb class AdminLoginPage def initialize(driver) @driver = driver @driver.get("https://xxx.wordpress.com/wp-admin/") end # ページの機能 def login email.clear email.send_keys("イーメール") pwd.clear pwd.send_keys("パスワード") login_button.click end # ページの要素 private def email return @driver.find_element(:id, "user_login") end def pwd return @driver.find_element(:id, "user_pass") end def login_button return @driver.find_element(:id, "wp-submit") end end
- 次にadd_new_post_page.rb
- add_new_postメソッドとして処理をまとめる
# add_new_post_page.rb class AddNewPostPage def initialize(driver) @driver = driver @driver.get("https://xxx.wordpress.com/wp-admin/post-new.php") end # ページの機能 def add_new_post(titlestr, description) @driver.switch_to.frame("content_ifr") body.send_keys(description) @driver.switch_to.default_content title.send_keys(titlestr) publish_btn.click end # ページの要素 private def body return @driver.find_element(:id, "tinymce") end def title return @driver.find_element(:id, "title") end def publish_btn return @driver.find_element(:id, "publish") end end
- 続いてこの2つを呼び出すtest.rbを下記のように変更
# test.rb require 'selenium-webdriver' require File.expand_path(File.dirname(__FILE__) + '/admin_login_page') require File.expand_path(File.dirname(__FILE__) + '/add_new_post_page') driver = Selenium::WebDriver.for :firefox # ログインページにログインする login_page = AdminLoginPage.new(driver) login_page.login # 新規投稿 new_post_page = AddNewPostPage.new(driver) new_post_page.add_new_post("タイトルです", "本文です")
- ってした後に、test.rbを実行するとさっきと同じように動くはず。
$ ruby test.rb
- ただこれだと、loginを呼ぶと必ず「正しいID/PWの組み合わせ」でのログイン処理になるので、「間違ったID/PWの組み合わせでログイン出来ないこと」を確認するテストが必要なときは変える必要がありますね。
step4:ページをもっとふやす
残りの下記の画面を追加する
- 一覧画面(all_post_page.rb)
- 編集画面(edit_post_page.rb)
- 削除画面(delete_post_page.rb)
本によると一覧画面( https://xxx.wordpress.com/wp-admin/edit.php )では「以下の6つのサービスを提供します」とのこと
- 投稿の作成
- 投稿の編集
- 投稿の削除
- カテゴリによる投稿のフィルタリング
- 投稿内のテキスト検索
- 投稿数のカウント
ってことでこのページは盛りだくさんだけど書いてみる。
# all_posts_page.rb class AllPostsPage def initialize(driver) @driver = driver @driver.get("https://xxx.wordpress.com/wp-admin/edit.php") end # ページの機能 # 記事の作成 def create_new_post(title, description) # 新規作成画面に遷移 add_new_post.click # 作成画面のPageObjectを作成+投稿 new_post = AddNewPostPage.new(@driver) new_post.add_new_post(title, description) end # 投稿の編集 def edit_post(present_title, new_title, description) # 指定されたタイトルの詳細画面に行く go_to_paticular_post_page(present_title) edit_post_page = EditPostPage.new(@driver) edit_post_page.editPost(new_title, description) end # 投稿の削除 def delete_post(title) # 指定されたタイトルの詳細画面に行く go_to_paticular_post_page(title) delete_post_page = DeletePostPage.new(@driver) delete_post_page.delete_post end # 一覧画面での投稿のフィルタリング def filter_posts_by_category(category) # 本にも載ってないので割愛。あとで書いてみるかもしれない。 end # 投稿の検索 def search_in_posts(search_text) # 本にも載ってないので割愛。あとで書いてみるかもしれない。 end # 投稿数の取得 def get_all_posts_count return posts_container.find_elements(:tag_name, "tr").size end # ページの要素 private def add_new_post return @driver.find_element(:link_text, "新規追加") end # 各記事タイトルの要素(という言い方でいいのかな)をすべて取得 def posts_container return @driver.find_element(:id, "the-list") end # 指定した投稿の編集ページに遷移するメソッド def go_to_paticular_post_page(title) all_posts = posts_container.find_elements(:class_name, "row-title") all_posts.each do |ele| if ele.text == title ele.click break end end end end
# edit_post_page.rb class EditPostPage def initialize(driver) @driver = driver end def editPost(str_title, str_description) @driver.switch_to.frame(content_frame) body.clear body.send_keys(str_description) @driver.switch_to.default_content title.clear title.send_keys(str_title) publish_btn.click end private # WYSIWYGのiframe def content_frame return @driver.find_element(:id, "content_ifr") end # 本文入力欄 def body return @driver.find_element(:id, "tinymce") end # タイトル入力欄 def title return @driver.find_element(:id, "title") end # 更新ボタン def publish_btn return @driver.find_element(:id, "publish") end end
# delete_post_page.rb class DeletePostPage def initialize(driver) @driver = driver end def delete_post move_to_trush.click end private def move_to_trush return @driver.find_element(:link_text, "ゴミ箱へ移動") end end
- ...と、ここで気づいたこと
# admin_login.rb class AdminLoginPage def initialize(driver) @driver = driver @driver.get("https://xxx.wordpress.com/wp-admin/") end # ページの機能 def login email.clear email.send_keys("イーメール") pwd.clear pwd.send_keys("パスワード") login_button.click # ログイン後にはAllPostPageを返す return AllPostsPage.new(@driver) end # ページの要素 private def email return @driver.find_element(:id, "user_login") end def pwd return @driver.find_element(:id, "user_pass") end def login_button return @driver.find_element(:id, "wp-submit") end end
- そしてtest.rbをこうしてみる
# test.rb require 'selenium-webdriver' require File.expand_path(File.dirname(__FILE__) + '/admin_login_page') require File.expand_path(File.dirname(__FILE__) + '/add_new_post_page') require File.expand_path(File.dirname(__FILE__) + '/all_posts_page') require File.expand_path(File.dirname(__FILE__) + '/edit_post_page') require File.expand_path(File.dirname(__FILE__) + '/delete_post_page') driver = Selenium::WebDriver.for :firefox # ログインページにログインする login_page = AdminLoginPage.new(driver) # loginの戻り値は AllPostsPage all_posts_page = login_page.login # 記事の投稿 all_posts_page.create_new_post("タイトル1", "本文1") # 記事の編集 driver.get("https://xxx.wordpress.com/wp-admin/edit.php") all_posts_page.edit_post("タイトル1", "タイトル2", "本文2") # 記事数の取得 driver.get("https://xxx.wordpress.com/wp-admin/edit.php") puts "記事数 = #{all_posts_page.get_all_posts_count}" # 記事の削除 driver.get("https://xxx.wordpress.com/wp-admin/edit.php") all_posts_page.delete_post("タイトル2")
- ここまでのディレクトリの中
E2Etest └ add_new_post_page.rb └ admin_login_page.rb └ all_posts_page.rb └ delete_post_page.rb └ edit_post_page.rb └ test.rb
課題
- 課題1:pageの数だけrequieが増えるのか!? → 調べればすぐできそう
- 課題2:PageObjectすべてにURLをフルパスで書くと変更したとき大変 → 調べればすぐできそう
課題3:画面遷移をするときに、遷移先のpageオブジェクトをnewするとき、しないときってどう使い分けるのかわかってない → これは答えがよくわかんない
これらは「操作の自動化」であって「テストの自動化」ではないので、今後はRSpecをつかってテストの自動化をしてみたいと思います。
- 冒頭にも書きましたが、いい書き方なのか不安なのでアドバイスお願いします!