一个有意思的Ruby Webdriver超时问题的解决过程

rescue in receive

因为写ruby的时候感觉混身上下都拽起来了,所以比较喜欢用ruby写代码。今天遇到了一个webdriver timeout的问题,问题本身还是因为我对webdriver不了解以及破文档导致的。首先我们把问题简化一下:


driver = Selenium::WebDriver.for :safari
driver.navigate.to "http://www.faraway.com"

wait = Selenium::WebDriver::Wait.new(:timeout => 1000) # seconds
wait.until { driver.find_element(:css, 'input[name="username"]') }

因为考虑到网站太远了,速度比较慢,于是timeout设的值比较大:timeout => 1000

这里我们访问:faraway这个网站,然后等待出现输入框输入用户名。得到了如下错误信息:

/Users/twer/.rvm/gems/ruby-2.1.1@global/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/safari/server.rb:41:in`rescue in receive': timed out waiting for Safari to respond (Selenium::WebDriver::Error::TimeOutError)
from /Users/twer/.rvm/gems/ruby-2.1.1@global/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/safari/server.rb:36:in `receive'
from /Users/twer/.rvm/gems/ruby-2.1.1@global/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/safari/bridge.rb:68:in `raw_execute'
from /Users/twer/.rvm/gems/ruby-2.1.1@global/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/remote/bridge.rb:612:in `execute'
from /Users/twer/.rvm/gems/ruby-2.1.1@global/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/remote/bridge.rb:110:in `get'
from /Users/twer/.rvm/gems/ruby-2.1.1@global/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/common/navigation.rb:14:in `to'
fromfaraway.rb:26:in `<main>'


看了出错的地方就是我wait的地方调用的。我心想我这不是设了:timeout => 1000,wait的等待时间为1000秒吗,怎么还搞不定啊?


Monkey Patch来拯救我了

搞不定啊,上面有人在吹啊,咋办嘛。暴力解决。我找到了上面红色部分标明的文件/selenium/webdriver/safari/server.rb。(https://code.google.com/p/selenium/source/browse/rb/lib/selenium/webdriver/safari/server.rb

找到了出错的地方41行,是在receive函数里:

def receive
   @frame ||= WebSocket::Frame::Incoming::Server.new(:version => @version)
   until msg = @frame.next

   end_time = Time.now + @command_timeout

   begin
      data = @ws.read_nonblock(1)
      rescue Errno::EWOULDBLOCK, Errno::EAGAIN
         now = Time.now
         if now >= end_time
            raise Error::TimeOutError, "timed out waiting for Safari to respond"   #第41行
         end

         IO.select([@ws], nil, nil, end_time - now)
         retry
      end

      @frame << data
   end

   puts "<<< #{msg}" if $DEBUG

   WebDriver.json_load msg.to_s
end


可以看出,是now >= end_time用来计算是否超时,在看前面end_time是由Time.now + @command_timeout得到的,然后进入begin/rescue/retry的。奇怪,我的:timeout => 1000不应该是work的吗?


第一个monkey patch

我把上面那部分代码直接拷到了我的源文件faraway.rb中,做了如下修改:

if now >= end_time
   puts @command_timeout
   raise Error::TimeOutError, "timed out waiting for Safari to respond"
end

发现@command_timeout不是1000,而是60,也就是一分钟


第二个monkey patch

until msg = @frame.next
   end_time = Time.now + 1000
这样强制让@command_timeout设为1000


这样运行,没有问题了


探个究竟

虽然monkey patch可以解决我连接faraway的问题。但是,作为快速试验得到反馈还行,真这样用还是不太愉快。于是我就开始读源代码,webdriver本身的代码还是挺简单的。很容易看懂,具体怎么看我就不讲了。分分钟发现了问题其实是在这里:


driver = Selenium::WebDriver.for :safari, timeout: 1000
driver.navigate.to "http://www.faraway.com"


红色的部分在文档上是没有交代的,那这里的timeout是什么意思呢?我的wait不是已经有timeout了吗,怎么在Driver的创建时还需要呢?

原因是这是两种不同的timeout。wait的timeout表示的是,我们的driver会等待多久知道这个元素出现。而我们出错的地方是driver接收服务器返回信息的timeout。还不清楚?

这样说,在wait的时候,driver会周期轮询的去执行until中的代码,看看until中的情况是否得到了满足。每次执行的时候Driver对会发一个请求,这个请求其实也是有timeout时间的,所以我们最开始遇到的问题并不是wait本身timeout了。而是driver发送命令后接收的时候timeout了。

明了了吧,都是faraway.com搞得怪,想重现这个问题。把command timeout改为1试试


如果我再次不幸。。。

我一只搅得自己是运气比较背的那种人。不过解决问题的过程还是很开心。ruby这种动态语言,可以打monkey patch,对于快速定位问题,解决问题还是很有帮助的。开心。


发布了154 篇原创文章 · 获赞 17 · 访问量 87万+
展开阅读全文

WebDriverWait对象如果多次使用,超时时间是怎么计算的呢?

05-11

大家好! 我现在用Selenium的WebDriver的显式等待来判断元素是否出现,关于WebDriverWait对象定义的超时时间的计算方法我不太明白,请教一下: 如果Java的代码这样写: WebDriver webPage = new InternetExplorerDriver(); //验证网页title是否正确 try{ WebDriverWait waitTitle = new WebDriverWait(webPage,10); waitTitle.until(ExpectedConditions.titleIs("XX平台")); System.out.println("Title正确。"); } catch(TimeoutException t){ System.out.println("Title错误。"); } //检查banner是否可见 try{ WebDriverWait waitBanner = new WebDriverWait(webPage,10); waitBanner.until(ExpectedConditions.visibilityOf(webPage.findElement(By.cssSelector(".banner")))); System.out.println("banner可见。"); } catch(TimeoutException t){ System.out.println("banner不可见。"); } 这样写应该没问题,就是每检查一项内容,都要去new一个WebDriverWait对象。我刚开始学Java,还不太懂内存是怎么回收的。如果检查的内容较多,内存肯定会占用得越来越多。不知这些内存什么时候会被系统回收? 后来我想改成这样的写法: WebDriver webPage = new InternetExplorerDriver(); WebDriverWait wait = new WebDriverWait(webPage,10); //验证网页title是否正确 try{ wait.until(ExpectedConditions.titleIs("XX平台")); System.out.println("Title正确。"); } catch(TimeoutException t){ System.out.println("Title错误。"); } //检查banner是否可见 try{ wait.until(ExpectedConditions.visibilityOf(webPage.findElement(By.cssSelector(".banner")))); System.out.println("banner可见。"); } catch(TimeoutException t){ System.out.println("banner不可见。"); } 这样只实例化了一个WebDriverWait对象,内存倒是不会占用那么多了,但是我不太清楚超时时间是怎样计算的。在new WebDriverWait对象时,定义了超时时间是10秒,先判断网页title是否正确时,会占用一部分时间;那么,再判断banner是否可见,这个时间是重新从0开始计时,还是接着上一次消耗完了的时间继续计时呢?比如判断网页title耗掉了1秒,那么再判断banner是否可见,剩余可用的超时时间,是9秒,还是10秒呢?如果是接着上一次消耗的计时,即只剩9秒,就不是我的本意了,我想让每次判断都有10秒的超时时间,那是不是只能采用第一种写法?如果这样,内存需要控制吗?麻烦高人指教!谢谢!!! 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览