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

Thanks Driven Life

日々是感謝

Capybara の稼働状況を animation gif として保存する

いろいろ課題はあるんですが、とりあえず動きました。

書いたもの

Capybara の稼働状況を animation gif として保存するようなやつ

Ruby 2.0 だったら Module#prepend とか使えばいいのかなって想いながら ↓ 書いてた。

module Capybara
  class Session
    SAVE_SCREENSHOT_METHODS = [
      :attach_file, :check, :choose, :click_link_or_button, :click_button,
      :click_link, :fill_in, :select, :uncheck, :unselect, :click_on,
      :evaluate_script, :visit
    ]

    SAVE_SCREENSHOT_METHODS.each do |method|
      alias_method "after_hook_#{method}".to_sym, method

      define_method method do |*args, &block|
        send("after_hook_#{method}", *args, &block)
        CapybaraAnimation::Recorder.add_frame
      end
    end
  end
end

やってることはそこまで複雑ではなくて

  1. SAVE_SCREENSHOT_METHODS のアクションが起きたら save_screenshot を呼び出す
  2. save_screenshot の結果を Tempfile においとく
  3. 最後に全部の screenshot を RMagick 使って Animation Gif に変換して保存

RMagick 使うまでもないって人は Tempfile の部分を File にして一枚ずつ保存するように変えればいいと想います。

課題

confirm や alert などの Modal Dialog が表示されている状態で save_screenshot をすると Selenium::WebDriver::Error::UnhandledAlertError が raise されます。 そんなわけで、例えば dialog が出ている場合はスクリーンショット撮らないとかすると

--- a/capybara_animation.rb
+++ b/capybara_animation.rb
@@ -8,11 +8,29 @@ module CapybaraAnimation
   class Recorder
     class << self
       def add_frame
+        return if alert_present?
+
         tempfile = Tempfile.new(['screenshot', '.gif'])
-        Capybara.current_session.save_screenshot(tempfile.path)
+        session.save_screenshot(tempfile.path)
         frames << tempfile
       end
 
+      def alert_present?
+        # selenium-webdriver じゃなかったらスルー(これもどうかと思うけど…)
+        return false if session.driver.browser.respond_to?(:switch_to)
+
+        begin
+          session.driver.browser.switch_to.alert
+          true
+        rescue Selenium::WebDriver::Error::NoAlertPresentError
+          false
+        end
+      end
+
+      def session
+        Capybara.current_session
+      end
+
       def frames

ってすると、ひとまず dialog でてる時はスルーしてくれるので動く。 でも、今度は driver.browser.switch_to.alert が遅い。 おそらく Capybara.default_wait_time だけアラートが出るのを待ってから raise するっぽいので遅い。 一つ一つのアクションで Capybara.default_wait_time も待っているとだいぶつらい。 というわけでここらへんどうにかしたい(かといって default_wait_time 短かくすると、本当に Alert を待ちたい処理書いてる時に逃してしまいそうなのでそれもなんだかなー。

(追記 2013/06/27)

       define_method method do |*args, &block|
-        send("after_hook_#{method}", *args, &block)
         CapybaraAnimation::Recorder.add_frame
+        send("after_hook_#{method}", *args, &block)
       end

とすれば dialog は関係ない気がしてきました。 この状態だと、dialog が出るのはスクリーンショットを撮ったあと。 もしこのあと CapybaraAnimation::Recorder.add_frame で UnhandledAlertError が出ると言うことは、 実際のコードは Alert/Confirm を考慮していないコード、ということになるため、こいつの出る幕じゃない。

みたいな?というわけでこれでいい気がしました。 ちなみにコメントに書いた wait_time は効きませんでした。どうやっても時間かかったので諦め

おまけ

作ったあとに同じようなことしてる人いるんだろうなって検索したらやっぱり居た

capybara-recording.rb

違いとしては save_screenshot を起動するタイミングを絞ったぐらいかな。