Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a Selenium/Ruby bot to wait before performing an action?

I'm building a Selenium/Ruby web bot that clicks on elements. The problem is, sometimes there isn't enough time for the page to load before the bot decides it can't find the element.

What's the Ruby way to get Selenium to wait before performing an action? I would prefer explicit waiting, but I'm fine with implicit waiting too.


I tried to use the wait.until method:

require "selenium-webdriver"
require "nokogiri"
driver = Selenium::WebDriver.for :chrome
wait = Selenium::WebDriver::Wait.new(:timeout => 15)
driver.navigate.to "http://google.com"
driver.wait.until.find_element(:class, "gb_P").click

But I'm getting the following error:

Undefined method 'wait' for <Selenium::WebDriver>

I also tried:

require "watir-webdriver/wait"
...
driver.find_element(:class, "gb_P").wait_until.click

but that's also giving me an undefined method error:

undefined method `when_present' for #<Selenium::WebDriver...>
like image 873
Joe Morano Avatar asked Mar 03 '16 01:03

Joe Morano


2 Answers

Okay. This answer has been asked many times in many different contexts. So I just want to answer it here once and for all.

There are three ways this can be done. And each is useful in certain contexts.

First, you can use an EXPLICIT wait. This has nothing to do with whether the page loads or not. It simply tells the script to wait. In other words, if your page loads in 11 seconds and your explicit wait is 10 seconds, the clickable element still won't be available. You can work around this inefficiency by using an expected condition. See, e.g., the Selenium manpages:

require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.get "http://google.com"

wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
begin
  element = wait.until { driver.find_element(:class => "gb_P") }
ensure
  driver.quit
end

^ This will wait either: 10 seconds OR until the element is found.

Second, you can use an implicit wait. This is very similar to the explicit wait with expected condition. However, where the explicit waits applies to the element being queried, implicit wait applies to the WebDriver Object. In other words, if your script only uses a single webdriver, it will wait either: the implicit wait time for EVERY element OR until each element is found (until failure). For example:

require 'selenium-webdriver'

driver = Selenium::WebDriver.for :firefox
driver.manage.timeouts.implicit_wait = 10 # seconds

driver.get "http://google.com"
element = driver.find_element(:class => "gb_P")

Third, you can call a Javascript function to click the page. The advantage of this is that once the page's Javascript loads, the item will be clickable and you don't have to actually wait for the page to render. A lot of time when you're "waiting for the page" you're actually waiting on the client side for the rendering engine to construct the page. You can bypass that process by simply clicking the underlying element before the page is actually rendered.

The drawback of this is that it doesn't mirror an actual human clicking the page. For example, if the button you want to click is hidden by a popup. Selenium won't allow you to click it, but a JS function will.

You can use this method by doing:

click = driver.execute_script("document.getElementsByClassName('gb_P')[0].click();")
like image 51
franklin Avatar answered Nov 03 '22 02:11

franklin


You are using wait as WebDriver function, but it isn't. Try this

element = wait.until { driver.find_element(:class => "gb_P") }
element.click
like image 21
Guy Avatar answered Nov 03 '22 02:11

Guy