ITOO's Blog

好きなことを好きなだけ

Rubyで初めてのSelenium

はじめに

自動化したいことがあったので初めてSeleniumを触りました。スクレイピング感覚でやっていたらいくつか詰まった点が有りましたので記事に残します。これからSelniumを使う方のお役に立てればと思います。 Chromeはインストールされている前提で進めます。

ブラウザ操作の自動化について

SeleniumはUIのテストツールで、テストの難しい動的なサイトの自動テストができるようになったりします。Seleniumは「ブラウザ操作をプログラミング」して自動化するものなのでスクレイプとは勝手が違いました。

まずドライバーの準備

capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    'chromeOptions' => { "args" => [ "--no-startup-window" "--headless"]}
) 
driver = Selenium::WebDriver.for :chrome, :desired_capabilities => capabilities

ポイント1- 新規タブで開くリンクを踏んだときにどうするか

要するにアクティブタブの切り替えや、タブを閉じるのはどうやるのかですが、以下のようにすれば操作できます。 そしてタブを閉じると、元のタブがアクティブになります。

# 下で最後のタブに飛べる
window = @driver.window_handles.last
driver.switch_to.window(window)

# 不要になったら閉じる、
driver.close

ポイント2 - ブラウザの表示領域に入っていない要素を取得できない

調べて見るとFirefoxではディスプレイに入っていない要素も取得できるらしいのですが、Chromeだとうまく取得できませんでした。縦方向にスクロールすることがほとんどだと思いますので、解決方法としてはSeleniumJavascriptを実行して要素が表示される位置までスクロールしてやるとうまくいきます。

@driver.execute_script("window.scrollBy(0, #{y});")

またボタン等をクリックするときにはページが表示されると即時にクリックしようとしてしまうので失敗してしまいます。スクロールしている間待ってやる必要があります。ただsleep関数を使うと余計な時間ができてしまうので使いたくない。そんなのきには、下のようにするとページ初期描画時に表示されていないボタンも効率よくクリックできます。

(0, 1200px)の座標にあるidがbuttonの要素をクリックしたいとき

# 1200pxまでスクロールする
driver.execute_script("window.scrollTo(0, 1200);")

# 要素が表示されるまで10秒待つ
Selenium::WebDriver::Wait.new(timeout: 10).until {
    driver.find_element(:css, '#button').displayed?
}

# クリックする
driver.find_element(:css, '#button').click

ポイント3 - 画像をアップロードする

input要素に値を入れるのと同じようにかけます。また、Ruby__dir__で実行しているスクリプトディレクトリを取得できるのでそこからファイルのパスを取得します。ただし、スクリプトの一つ上の階層に置くと'..'が正しく認識されないらしくうまくいきませんでした。

# input要素のvalue属性に値を入れるとき
driver.find_element(:css, '#inputElem').send_keys('hogehoge')

# 画像をアップロードしたいとき
## スクリプトの一つ上の階層のディレクトリ(同じ階層)を指定したらうまく行かなかった。
driver.find_element(:css, 'inputElem').send_keys(__dir__ + '../img/75x75.png')

## 同じ場所に置いたらうまくいった
driver.find_element(:css, 'inputElem').send_keys(__dir__ + '/75x75.png')

結論:クラスにまとめると便利

クラスを用意しておくと次からやりたいことだけを書けるのでとっても便利。まあseleniumのラッパーみたいなライブラリも探せばありそうですが・・・

class SeleniumUtil

  def initialize()
    capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
      'chromeOptions' => { "args" => [ "--no-startup-window" "--headless"]}
    ) 

    @driver = Selenium::WebDriver.for :chrome, :desired_capabilities => capabilities
  end

  # 最後のタブに移動する
  def switch_last_window()
    window = @driver.window_handles.last
    @driver.switch_to.window(window)
  end

  # 指定したタイトルのタブに移動する
  def switch_window(title)
    window_titles = @driver.window_handles.map do |w|
      @driver.switch_to.window(w)
      [w, driver.title]
    end
    
    selected_id = window_titles.find { |e1, e2| e2 == title }.first
    raise 'Not found window' unless selected_id
    
    driver.switch_to.window(selected_id)
  end

  # 指定した要素が表示されるまでtimeout秒待つ
  def wait_display(selector, timeout=10)
    Selenium::WebDriver::Wait.new(timeout: timeout).until {
      @driver.find_element(:css, selector).displayed?
    }
  end

  # 要素が表示されたらクリックする
  def click(selector)
    wait_display(selector)
    @driver.find_element(:css, selector).click
  end

  # スクロールする関数群
  def scroll_by(y)
    @driver.execute_script("window.scrollBy(0, #{y});")
  end

  def scroll_top()
    @driver.execute_script('window.scrollTo(0, 0);')
  end

  def scroll_bottom()
    @driver.execute_script('window.scrollTo(0, 99999);')
  end

  # 要素が表示されたらチェックを入れる
  def check(selector)
    wait_display(selector)
    unless @driver.find_element(:css, selector).selected?
      @driver.find_element(:css, selector).click
    end
  end
end

最後に

初めてSeleniumを触ってみて、勝手がわかるとサクサクと操作できるようになって楽しいです。自動化したいことがあったらまた使いたいなと思いました。ではでは