標籤:webdriver ruby wait timeout monkey patch
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/[email protected]/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/[email protected]/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/safari/server.rb:36:in `receive‘
from /Users/twer/.rvm/gems/[email protected]/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/safari/bridge.rb:68:in `raw_execute‘
from /Users/twer/.rvm/gems/[email protected]/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/remote/bridge.rb:612:in `execute‘
from /Users/twer/.rvm/gems/[email protected]/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/remote/bridge.rb:110:in `get‘
from /Users/twer/.rvm/gems/[email protected]/gems/selenium-webdriver-2.42.0/lib/selenium/webdriver/common/navigation.rb:14:in `to‘
from
faraway.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,對於快速定位問題,解決問題還是很有協助的。開心。