为 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
我有一系列使用 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