書籍「実践SeleniumWebDriver」のPageObjectパターンをRSpecでテストコードにしてみた。


前回の投稿(http://sakaimo.hatenablog.com/entries/2014/12/29) では、JAVAで書かれていたコードをRubyでやってみました。この段階では「操作」の自動化だったので、今回はRSpecで「テスト」の自動化にチャレンジしてみました。

まだまだリファクタの余地はありそうなのですが、スキル不足によりひとまずここまで。。。

こゆソースコードはGitHubとかで公開したほうがいいのだろうけど、GitHubの使い方がわかんないので後回し＞＜

* 私の環境
  * macOS 10.10.1
  * ruby 2.0.0p481
  * rspec 3.1.7
  * FireFox 34.0.5

試すときはWordPressのアカウトを取って、spec_helper.rb の中身を変えてください。

----

# ディレクトリ構造

```ruby:ディレクトリ構造
E2ETest
 └ pages
   └ add_new_post_page.rb
   └ admin_login_page.rb
   └ all_posts_page.rb
   └ base_page.rb
   └ delete_post_page.rb
   └ edit_post_page.rb
 └ spec
   └ spec_helper.rb
   └ test_spec.rb
```

----
# pagesの中身

```ruby:add_new_post_page.rb
require File.expand_path(File.dirname(__FILE__) + '/base_page')

class AddNewPostPage < BasePage
  def initialize(driver)
    @driver = driver
    @url = "#{BASE_URL}/wp-admin/post-new.php"
  end

  # ページの機能
  # 記事の新規投稿をする
  def add_new_post(titlestr, description)
    @driver.switch_to.frame("content_ifr") #WYSIWYGではframeの切り替えが必要
    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
```

```ruby:admin_login_page.rb
require File.expand_path(File.dirname(__FILE__) + '/base_page')

class AdminLoginPage < BasePage
  def initialize(driver)
    super(driver)
    @url = "#{BASE_URL}/wp-admin/"
  end

  # ページの機能
  # 一気にログイン処理しちゃう
  def login
    email.clear
    email.send_keys(LOGIN_ID)
    pwd.clear
    pwd.send_keys(LOGIN_PW)
    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
```

```ruby:all_posts_page.rb
require File.expand_path(File.dirname(__FILE__) + '/base_page')

class AllPostsPage < BasePage
  def initialize(driver)
    super(driver)
    @url = "#{BASE_URL}/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

  # 指定した文字列のタイトルが存在するかを返す(もっとうまいやり方ありそう)
  def title_present?(title)
    all_posts = posts_container.find_elements(:class_name, "row-title")
    result = false

    all_posts.each do |ele|
      if ele.text == title
        result = true
        break result
      end
    end
    return result
  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
```


```ruby:base_page.rb
class BasePage

  def initialize(driver)
    @driver = driver
    @url    = ""
  end

  # ページを開く
  def open
    @driver.get(@url) unless @driver.current_url == @url
  end
end
```

```ruby:delete_post_page.rb
require File.expand_path(File.dirname(__FILE__) + '/base_page')

class DeletePostPage < BasePage

  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
```

```ruby:edit_post_page.rb
require File.expand_path(File.dirname(__FILE__) + '/base_page')

class EditPostPage < BasePage

  def initialize(driver)
    @driver = driver
  end

  def editPost(str_title, str_description)
    @driver.switch_to.frame(content_frame) #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
```

----
# specの中身

```ruby:spec_helper.rb
require 'selenium-webdriver'
require 'pry'

Dir[File.expand_path("../../pages/", __FILE__) << '/*.rb'].each do | file |
  require file
end

# 定数
BASE_URL = "https://xxx.wordpress.com"
LOGIN_ID = "イーメール"
LOGIN_PW = "パスワード"
```

```ruby:test_spec.rb
describe "ログインして書いて編集して削除するシナリオ" do

  before :all do
    @driver = Selenium::WebDriver.for :firefox
  end

  after :all  do
    @driver.quit
  end

  it "正しいID/PWでログインできること" do
    login_page = AdminLoginPage.new(@driver)

    # ログインページを開く
    login_page.open

    # ログインする
    login_page.login

    # 今回はtitleに「ダッシュボード」という文字列が含まれているかで比較
    expect(@driver.title).to include "ダッシュボード"
  end

  it "記事の投稿ができること" do
    all_posts_page = AllPostsPage.new(@driver)
    all_posts_page.open

    # 記事の投稿
    all_posts_page.create_new_post("タイトル1", "本文1")

    # 記事一覧画面に行って、登録したタイトルが表示されているか、で判定(もっといい方法あるかも)
    all_posts_page.open
    result = all_posts_page.title_present?("タイトル1")
    expect(result).to be_truthy
  end

  it "記事の編集ができること" do
    # 記事の編集
    all_posts_page = AllPostsPage.new(@driver)
    all_posts_page.open

    # 記事の編集
    all_posts_page.edit_post("タイトル1", "タイトル2", "本文2")

    # 記事一覧画面に行って、編集したタイトルが表示されているか、で判定(もっといい方法がありそう)
    all_posts_page.open
    result = all_posts_page.title_present?("タイトル2")
    expect(result).to be_truthy
  end

  it "記事数の取得" do
    all_posts_page = AllPostsPage.new(@driver)
    all_posts_page.open
    result = all_posts_page.get_all_posts_count
    expect(result).to eq 1 # テスト開始時に投稿数がゼロとした
  end

  it "記事の削除ができること" do
    # 記事の削除
    all_posts_page = AllPostsPage.new(@driver)
    all_posts_page.open
    all_posts_page.delete_post("タイトル2")

    # タイトル2へのリンクが無いことをもって判断
    all_posts_page.open
    result = all_posts_page.title_present?("タイトル2")
    expect(result).to be_falsey
  end
end
```

----

# 実行する

E2Etestディレクトリにいる状態で実行
前提として、既存の投稿数はゼロにしておく

```
E2Etest sakaimo$ rspec -fd spec/test_spec.rb 

ログインして書いて編集して削除するシナリオ
  正しいID/PWでログインできること
  記事の投稿ができること
  記事の編集ができること
  記事数の取得
  記事の削除ができること

Finished in 49.57 seconds (files took 0.32554 seconds to load)
5 examples, 0 failures
```
----

* 絶対もっと良い書き方があるはず。。。