読者です 読者をやめる 読者になる 読者になる

いもろぐ

思い立ったら書いていくスタイルで

書籍「実践SeleniumWebDriver」のPageObjectパターンをRubyでやってみた。

  • 「実践SeleniumWebDriver(初版)」の「9.5 WordPressのエンドトゥエンドのサンプル」がJAVAで書かれてるのでRubyに置き換えようとした。
  • だけど私はJAVARubyもよくわかんないので、会社にあった既存のテストコードを参考にしながら書いてみた。
  • 結果として本に沿ってないし「置き換え」にはなってないっぽい><
  • 正直自信ないので改善点をいただけると嬉しいです。(Rubyの言語仕様も理解不足なので...特にRubyとしての作法とか命名のお約束とか)

前置き

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に一連の処理をまとめておけばいいんじゃないか?

# 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_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_pageにあるloginメソッドって戻り値としてAllPostsPageクラスを返してるのね。。。見落としてたわ_| ̄|○
    • ってことで下記のように修正
# 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をつかってテストの自動化をしてみたいと思います。

  • 冒頭にも書きましたが、いい書き方なのか不安なのでアドバイスお願いします!