Selenium (Ruby) やCapybaraの弱いところをPuppeteerで救いに行く

f:id:YusukeIwaki:20200605001527p:plain

「puppeteer-rubyは、Capybaraと共存して動作精度を向上できるのでは?!」っていうことに先週くらいにふと気づいたので、カッとなって実装してみた。

github.com

CapybaraとPuppeteerの共存

そもそもなんで共存させる必要があるのか?

  • Seleniumだと「○○の要素が現れるまで待つ」「△△の要素が消えるまで待つ」みたいなところで、時々DOMの変化通知を拾いそこねて、失敗してしまうことがある。
  • PuppeteerはDOMの変化通知には強い。ただ、全部をPuppeteerで書き直す気力は無い...。

みたいな感じで、SeleniumやCapybaraの既存コードをなるべく書き換えないうえでPuppeteerのを動かしたい需要は、割とある気がした。

で、需要はありそうなのに、世の中にあるのは

のように、Capybara/Chromeドライバを完全に置き換える仕組みばっかりだなぁと。

「おれはべつにSeleniumを否定したいわけじゃないんだ。Seleniumの不満ポイントをどうにかPuppeteerで補強したいだけなんだ」ということで、puppeteer-rubyをそっちの方向で使えるように Puppeteer.connect を実装した。

実際に動作させた例

image

RSpec.describe 'hotel.testplanisphere.dev', type: :feature do
  before {
    visit 'https://hotel.testplanisphere.dev/'
    @browser = Puppeteer.connect(
                browser_url: 'http://localhost:9222',
                default_viewport: Puppeteer::Viewport.new(width: 1280, height: 800))
  }

  after {
    @browser.disconnect
  }

  it 'can be handled with puppeteer and assert with Capybara' do
    # automation with puppeteer >>
    puppeteer_page = @browser.pages.first
    puppeteer_page.wait_for_selector('li.nav-item')

    reservation_link = puppeteer_page.SS('li.nav-item')[1]

    await_all(
      puppeteer_page.async_wait_for_navigation,
      reservation_link.async_click,
    )
    # << automation with puppeteer

    # expectation with Capybara DSL
    expect(page.title).to include('宿泊プラン一覧')
    expect(page).to have_text('宿泊プラン一覧')
  end

  it 'can be handled with Capybara and assert with puppeteer' do
    # automation with Capybara >>
    page.all('li.nav-item')[1].click
    # << automation with Capybara

    # expectation with puppeteer
    puppeteer_page = @browser.pages.first
    expect(puppeteer_page.title).to include('宿泊プラン一覧')
    body_text = puppeteer_page.Seval('body', '(el) => el.textContent')
    expect(body_text).to include('宿泊プラン一覧')
  end
end

詳しいやりかたはこの辺に書いてある→ puppeteer-ruby-example/_with_capybara-rspec at master · YusukeIwaki/puppeteer-ruby-example · GitHub

Seleniumが(DevTools Protocolのポート開放状態で)起動したChromeブラウザに対して、Puppeteer.connectして、あとはSelenium WebDriver経由でもPuppeteer経由でも操作できる状態にした、というだけの話。

使ってみてね。

rubygems.org

2020/06/29現在の最新の0.0.15ではPDF出力にも対応しました。

github.com

まだまだ足りない機能はたくさんありますが、「Selenium使ってみたけど、なんか時々テストが落ちて困ってる・・・」っていう人には、わりと便利なんじゃないかなぁ。

効果については会社のほうでいろいろやって試してから、もう少し丁寧に披露しようとおもう。

(2020.08.30追記) てすらぼ#2っていうイベントで披露した

単体テストを書き始めてそこそこ精度が上がってきたので、てすらぼみーとあっぷ #2 - connpass で困ってる人がいないかなー?と探しに登壇発表してみました。

speakerdeck.com