为 Watir 添加超时重试机制

Adding Retry mechanism to Watir in case of Timeout

我有一系列使用 Ruby 和 Watir gem 开发的脚本。那些被菠菜包裹着,但这不是我要问的。

这些脚本的目的是进行一些功能抽查或简单地减轻一些非常重复的任务。

他们已经 运行 好一段时间了,但最近,我开始看到很多由于 Chrome 驱动程序 / Geckodriver(尝试了两种浏览器)和脚本。当然,我可以简单地重新启动脚本,但是当成功率低于 70% 时,它真的开始恶化了。

我最后做的是在 Proc 中结束我对 Watir 的所有调用,并在出现超时时进行重试。

这很丑陋并且违反了很多 规则 以至于我几乎羞于不得不求助于这个解决方案,但至少使用这个我的脚本现在正在完成。

我是这样解决这个问题的:

# takes a proc and wraps it around a series of rescue
def execute_block_and_rety_if_needed
  yield
rescue Net::ReadTimeout
  puts 'Read Timeout detected, retrying operation'
  retry
rescue Net::HTTPRequestTimeOut
  puts 'Http Request Timeout detected, retrying operation'
  retry
rescue Errno::ETIMEDOUT
  puts 'Errno::ETIMEDOUT detected, retrying operation'
  retry
end

示例使用如下所示:

execute_block_and_rety_if_needed { @browser.link(name: 'OK').wait_until_present.click } # click the 'OK' button

如您所见,这显然违反了 DRY 原则,因为我需要每次都调用此过程。

我的问题是:如何将它作为 Watir 的模块/功能移动,以便它自动拾取它。 (理想情况下,我会添加最大重试次数以防止无限循环)。

版本信息: - Chrome 驱动程序 => 2.29.461585 - 壁虎驱动程序 => 0.16.1 - Firefox => ESR 52 - Chrome => 58 - 瓦提尔 => 6.2.1

至于 DRY 评论,我提到了一个事实,即我必须用 proc 包装我的所有 Watir 调用,如果不清楚,请见谅。

execute_block_and_rety_if_needed { @browser.link(name: 'User').wait_until_present.click } # click the 'Edit' button 
execute_block_and_rety_if_needed { @browser.link(name: 'Cancel').wait_until_present.click } # click the 'Cancel' button 
execute_block_and_rety_if_needed { @browser.link(name: 'OK').wait_until_present.click } # click the 'OK' button

以上只是一个例子,如果我要使用重试机制就必须发生

您不需要为此使用块。您可以实现一个执行类似操作的方法:

def ensure_click(element, retries = 3)
  @retries ||= retries
  element.click
rescue Net::ReadTimeout, Net::HTTPRequestTimeOut, Errno::ETIMEDOUT => ex
  raise unless @retries > 0
  @retries = @retries - 1
  puts "#{ex.class} detected, retrying"
  retry
end

...
ensure_click(@browser.link(name: 'User'))
...

也就是说,这些异常通常不是驱动程序错误,而是某种网络问题。不正常。

鉴于您想要重试发送到浏览器的每个命令,您可能需要考虑在底层 Selenium-WebDriver 而不是 Watir 中解决该问题。 Watir 命令被发送到 Selenium-WebDriver,后者又将它们发送到 browser/driver.

每个命令(或至少大多数)当前都通过 Selenium::WebDriver::Remote::Http:Default#request 发送。您可以修补该方法以将其包装在重试中。不仅您的点击会超时重试,其他所有命令也会重试 - 例如导航、设置字段、获取值等。

# Patch to retry timeouts during requests
require 'watir'
module Selenium
  module WebDriver
    module Remote
      module Http
        module DefaultExt
          def request(*args)
            tries ||= 3
            super
          rescue Net::ReadTimeout, Net::HTTPRequestTimeOut, Errno::ETIMEDOUT => ex
            puts "#{ex.class} detected, retrying operation"
            (tries -= 1).zero? ? raise : retry            
          end
        end
      end
    end
  end
end
Selenium::WebDriver::Remote::Http::Default.prepend(Selenium::WebDriver::Remote::Http::DefaultExt)

# Then you can use Watir as usual
browser = Watir::Browser.new :chrome   # this will retry timeouts
browser.goto('http://www.example.com') # this will also retry timeouts
browser.link.click                     # this will also retry timeouts