patorashのブログ

方向性はまだない

Selenium WebDriverからCupriteへの移行は難しい件

この記事を読んで、SeleniumからCupriteに移行できたらテストがまた速くなるんじゃないか?と思ってここ数日動作検証してるのですが、思った以上にうまくいかないので、一旦移行を諦めようかなという気持ちになっています。

techracho.bpsinc.jp

とはいえ、今までやってきたことを何もメモに残さないのも勿体ないので、とりあえずそれらは今後のために残しておこうかなと。

バージョン情報

この記事の執筆時点での私の環境です。開発環境はDockerにしています。

  • Mac Catalina
  • Ruby 2.7.3
  • Rails 6.0.3.7
  • Capybara 3.35.3
  • Cuprite 0.13

Cupriteとは?

CupriteはCapybaraのドライバーで、Chromeを直接操作するCDPプロトコルを使えるFerrumを使ってブラウザを操作します。

cuprite.rubycdp.com

Selenium経由じゃない分、速いという話です。

設定類については、一番最初に貼った記事に説明があるので、そこらへんはここでは言及しません。

Cupriteへの移行に躓いた点

JavaScriptの実行方法がselenium-webdriverと違う

JavaScriptの実行方法が違ったため、wait_for_ajaxなどが動かなくなりました。Ferrumを使ってbrowserからevaluateメソッドやexecuteメソッドを使います。

# selenium-webdriver
page.evaluate_script('jQuery.active').zero?

# cuprite
page.driver.browser.evaluate('jQuery.active').zero?

まぁこの辺りは一括置換で対応できるので序の口です。

click_linkとclick_buttonがよくコケる

click_linkとclick_buttonが結構な頻度で落ちました。原因はfixed指定している要素の下に対象のリンクやボタンがあるからと言われました。いや、selenium-webdriverのときはそうならなかったし…。うちのプロジェクトでfixed指定している要素ってのはつまり、bootstrapのナビゲーションバーとかです。あと検索結果の件数表示とか。Cupriteよ、fixedな要素の下に行くまでスクロールすな!!という気持ちです。

仕方ないのでモンキーパッチをあてました。

def click_link(target)
  failed ||= 0
  super(target)
rescue Capybara::Cuprite::MouseEventFailed
  if failed.zero?
    # 固定ナビバーが重なってクリックできないケースがあるので、その際はナビバー分を移動して再度実行するようにした
    scroll_x = page.driver.browser.evaluate('window.scrollX')
    page.driver.scroll_to(0, scroll_x - 60)
  else
    # それ以外の場合に押せない場合は、検索結果のフッター要素が重なっているとみなして、最後までスクロールさせる
    page.driver.scroll_to(0, 9999)
  end
  failed += 1
  retry if failed < 3
end

これで、大体はクリックできるようになりました(が、例外拾ってるからコスト大きそう)

入力結果が反映されない

fill_inで入力してすぐにclick_buttonでsubmitしてるんですが、なぜかfill_inで入力した内容が反映されません。sleepかませるとうまく動いたのですが、そんなことしていたら速くなるわけがありません…。本末転倒です。それに何個fill_inするところがあるというのか…。

調べたら、:slowmoというオプションがあることがわかりました。コマンドを送る前に指定した秒数だけ遅延させるようです。試しに、0.1を指定したら、まともに動き始めました。

Capybara.register_driver(:cuprite) do |app|
  Capybara::Cuprite::Driver.new(
    app,
    **{
      window_size: [1200, 800],
      browser_options: remote_chrome ? {"no-sandbox" => nil} : {},
      process_timeout: 10,
      inspector: true,
      headless: !ENV['HEADLESS'].in?(%w(n 0 no false)),
      pending_connection_errors: false,
      slowmo: 0.1, # <= これ!
      timeout: 15, # <= これもないと割りかし落ちる…
    }.merge(remote_options)
  )
end

しかし、毎回の処理で0.1秒遅延させることになるので、こんなのしていたら相当遅くなります…。

現状でベンチマークを取る

結構ボリュームのあるシステムテストselenium-webdriverとcupriteでローカルで実行してみました。 cupriteは、上記の設定をしたままです。

結果、cupriteは10分。selenium-webdriverは6分という結果となり、圧倒的にselenium-webdriverのほうが速いということに…😅

これは別にcupriteが遅いということではなく、私のcupriteの設定が悪いという話ではありますが、じゃあ適切な設定の落とし所はどういう設定なのか?を手探りで探さなければならないしそれは辛い。

まとめ

2021年6月時点では、ちょっとcupriteにサクッと移行できるわけでもなさそうだし、しかもテストも速くなるための調整が難しそうなので、移行へのモチベーションが上がりませんでした。 しかし、テスト高速化はコスト削減にも繋がるので、引き続き、時々調査してみようと思っています。

「いやいや、そんなのしなくてもこうすればCupriteでイケるよ!!」という情報をご存知の方は是非とも教えてください!お願いします。