How to determine that element is clickable using Selenium WebDriver with Ruby? - ruby

On my web page, page loads but sub-tabs of page aren't clickable for 20 seconds (sometimes more than this). Page contents are -
<nav id="subTabHeaders">
<div class="selected" data-name="ab">AB</div>
<div class="" data-name="cd">CD</div>
<div class="" data-name="ef">EF</div>
<div class="" data-name="gh">GH</div>
</nav>
I've to click on sub-tab, hence I tried this in following way -
Put sleep & then element.click
But sleep is not ideal way to deal because sometimes it may happen that sub-tab element is clickable before or after the time given to sleep.
Using sleep, I did following -
element = WAIT.until { driver.find_element(:xpath, ".//*[#id='subTabHeaders']/div[3]")}
sleep 20
element.click
If element is clickable after more than the sleep time & we click on element immediate after sleep time expires, (I mean (using above code) suppose element becomes clickable after 30 seconds but we click on element immediate after 20 seconds), actual click action doesn't happen & also click doesn't return any error.
Is there Ruby method to check whether element is clickable or not? So that we'll get to know when to click.

From the ruby bindings page: (see driver examples)
# wait for a specific element to show up
wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
wait.until { driver.find_element(:id => "foo") }
So ordinarily you could do something like:
wait = Selenium::WebDriver::Wait.new(:timeout => 40)
wait.until do
element = driver.find_element(:xpath, ".//*[#id='subTabHeaders']/div[3]")
element.click
end
Or more succinctly
wait = Selenium::WebDriver::Wait.new(:timeout => 40)
wait.until { driver.find_element(:xpath, ".//*[#id='subTabHeaders']/div[3]").click }
However, since you say that the click doesn't raise an error, it sounds like the click is in fact working, just your page isn't really ready to display that tab. I'm guessing there's some async javascript going on here.
So what you can try is inside the wait block, check that the click caused the desired change. I'm guessing, but you could try something like:
wait = Selenium::WebDriver::Wait.new(:timeout => 40)
wait.until do
driver.find_element(:xpath, ".//*[#id='subTabHeaders']/div[3]").click
driver.find_element(:xpath, ".//*[#id='subTabHeaders']/div[3][#class='selected']")
end
The important thing here is that #until will wait and repeat until the block gets a true result or the timeout is exceeded.

How about
WebDriverWait wait = new WebDriverWait(driver,30);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(subTabHeaders)));

Hope it would help you:
begin
element = WAIT.until { driver.find_element(:xpath, ".//*[#id='subTabHeaders']/div[3]")}
sleep 20
element.click
rescue Selenium::WebDriver::Error::StaleElementReferenceError
p "Indicates that a reference to an element is now “stale” - the element no longer appears in the DOM of the page."
end
OR
you could try this one:
begin
wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
wait.until { driver.title.include? "page title" }
driver.find_element(:xpath, ".//*[#id='subTabHeaders']/div[3]")}.click
rescue Selenium::WebDriver::Error::StaleElementReferenceError
p "element is not present"
end

Here is what I use - to test if the link is clickable, else go to another URL:
if (logOutLink.Exists() && ExpectedConditions.ElementToBeClickable(logOutLink).Equals(true))
{
logOutLink.Click();
}
else
{
Browser.Goto("/");
}

Related

Element is no longer attached to the DOM selenium

I have the following problem with my Selenium in Ruby. It generates the error, that the element is no longer attached to the DOM. I found some solutions to wait, but I wasn`t able to figure out if I can wait for an element which has no ID. Can i wait for an element if I only have the className?
require 'selenium-webdriver'
#require Firefox installation !!
browser = Selenium::WebDriver.for :firefox
browser.get <URL>
wait = Selenium::WebDriver::Wait.new(:timeout => 20)
js_code = "return document.getElementsByClassName('Cell ')"
rawdata = Array.new
puts rawdata.size
elements = browser.execute_script(js_code)
elements.each{|e| rawdata.push(e.text) }
puts rawdata.size
arrSize = rawdata.length
puts rawdata.at(5) + " " + rawdata.at(4) + " " + rawdata.at(9) + " " + rawdata.at(6)
This answers your question but not necessarily resolves your exception. If it doesn't, you might want to post HTML snippets and stacktrace.
Here is how to use WebDriverWait in Ruby:
# create wait like you have already done
wait = Selenium::WebDriver::Wait.new(:timeout => 20)
# wait until something, you can use any locators you want, not just ids
# don't inject JavaScript directly, unless you have to
wait.until { driver.find_element(:class => "dojoxGridCell") }
# do stuff to your raw data

Wait until particular element to disappear

Using Ruby, we can wait for particular element by doing following:
wait = Selenium::WebDriver::Wait.new(:timeout => 10)
wait.until { driver.find_element(:class, 'gritter-item') }
but if I want particular element to disappear from DOM, I write method like:
def disappear_element
begin
driver.find_element(:class, 'gritter-item')
rescue Selenium::WebDriver::Error::NoSuchElementError
true
else
false
end
end
and called it like:
wait.until { disappear_element }
This way I could achieve absence of element. Is there any better way in Ruby to achieve the same?
You can write disappear_element as follow (using find_elements instead of find_element):
def disappear_element
driver.find_elements(:class, 'gritter-item').size == 0
end

driver.navigate.refresh not working as expected with selenium-webdriver

Please follow the code below:
driver.get "https://example.com/"
driver.find_element(:class, "button").submit
driver.navigate.refresh
wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
element = wait.until { driver.find_element(:name => "username") }
I wrote the code keeping in my mind that till the page which contains element : username comes, continue the previous page to refresh. But it seems my code not meeting that requirement. Thus script throwing error as below "
Error
C:/Ruby193/lib/ruby/gems/1.9.1/gems/selenium-webdriver-2.27.2/lib/selenium/webdr
iver/common/wait.rb:57:in `until': timed out after 10 seconds (Unable to locate
element: {"method":"name","selector":"username"})} (Selenium::WebDriver::Error::
TimeOutError)
Any good idea to meet my requirement,please?
Thanks,
I have not come across a built-in way to do this in selenium-webdriver, so I would do the following:
#Submit your first page
driver.get "https://example.com/"
driver.find_element(:class, "button").submit
#Refresh page until your element appears
end_time = Time.now + 10 #seconds
begin
element = driver.find_element(:name => "username")
rescue Selenium::WebDriver::Error::NoSuchElementError
if Time.now < end_time
driver.navigate.refresh
retry
end
end
Basically this is attempting to find the element. If it is not found, catches the exception, refreshes the page and retries again. This is repeated until the time limit hsa been reached.

selenium-webdriver and wait for page to load

I'm trying to write simple test. My problem is, that i want to wait until the page is loaded completly. At the moment i'm waiting until some elements are presen, but that is not really what i want. Is it possible to make something like this:
driver = Selenium::WebDriver.for :chrome
driver.navigate.to url
driver.wait_for_page_to_load "30000"
With Java isn't problem, but how to make it with ruby?
This is how the Selenium docs () suggest:
require 'rubygems'
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :firefox
driver.get "http://google.com"
element = driver.find_element :name => "q"
element.send_keys "Cheese!"
element.submit
puts "Page title is #{driver.title}"
wait = Selenium::WebDriver::Wait.new(:timeout => 10)
wait.until { driver.title.downcase.start_with? "cheese!" }
puts "Page title is #{driver.title}"
driver.quit
If that is not an option you can try the suggestion from this SO post though it would require some Javascript on top of the Ruby/Rails.
It seems that wait.until is being/has been phased out. The new suggested process it to look for the page to have an element you know will be there:
expect(page).to have_selector '#main_div_id'
As far as I understand webdriver, you dont need to wait for page loads because WebDriver has a blocking API but you can sure set a page load timeout.
driver.manage.timeouts.page_load = 10 # seconds
So in Ruby, whenever you use get to open a URL, the ruby script proceeds ONLY when the page completely loads.
So in your case you would simply do :-
driver.get url
That's not needed with WebDriver anymore.
WebElement click() and Actions click() both "wait for page load" if needed automatically.
You can use imclicit and explicit (in this order) wait instead (described at seleniumhq) if you need to wait for some ajax content for instance.
There have been instances where either AJAX or CSS changes caused my tests to fail at times. I added these methods to my static driver instance so that I can have the test wait for certain conditions if needed. (c#)
TimedWait in the WaitForCssChange Method is basically just a Threading.Thread.Sleep This is not the most beautiful way I guess, but it works well for my needs.
For Ajax wait:
public static void WaitForAjax()
{
var wait = new WebDriverWait(Driver.Instance, TimeSpan.FromSeconds(25));
wait.Until(d => (bool)(d as IJavaScriptExecutor).ExecuteScript("return jQuery.active == 0"));
}
For CSS Changes:
public static void WaitForCssChange(IWebElement element, string value)
{
int counter = 0;
while (true)
{
if(element.GetAttribute("style").Contains(value) || counter > 50)
{
break;
}
TimedWait(20);
counter++;
}
}

Using DOM inspector to find the ID of a button

I'm trying to use the DOM inspector to find the ID of the "More" button at the bottom of this page that reveals more results.
I'm trying to do something like this example:
require 'watir-webdriver'
b = Watir::Browser.new
b.goto 'svpply.com/editors_pick'
#count products
puts b.elements(:xpath => '//li[#data-class="Product"]').count
#=> 30
#Now click button
show_all = b.button(:id => "btn_all")
show_all.click
sleep 4
#count products again
puts b.elements(:xpath => '//li[#data-class="Product"]').count
#=>60
However, I'm unclear on how to search for that particular id within the DOM structure. Can someone also explain the difference between an attribute, element, id, and node?
To use the DOM Inspector for the More button:
Open the DOM Inspector (Ctrl+Shift+I)
Click the [More] button that you want to inspect
On the right side of the DOM Inspector bar, click the [HTML] button
This should show the HTML for the page, which will include the details of the [More] control. You'll notice that the element is actually a DIV not a button. As well that the ID is in the form "_more".
--> This should show the HTML for the page, which will show the details of the [More] control. You'll notice that the element is actually a DIV not a button. As well that the ID is in the form "_more".
So to do your example with the Quora page, you would do something like:
require 'watir-webdriver'
class QuoraPage
def initialize(browser)
#browser = browser
end
def goto()
#browser.goto 'http://www.quora.com/Startups/best_questions'
wait_questions_loaded
end
def click_more()
#browser.div(:id, /_more/).click
wait_questions_loaded
end
def questions_count()
#browser.links(:class, 'question_link').count{ |x| x.visible? }
end
def wait_questions_loaded()
begin
questions_start_count = questions_count()
sleep(2)
end while questions_start_count != questions_count()
end
end
page = QuoraPage.new(Watir::Browser.new :chrome)
page.goto
puts page.questions_count
page.click_more
puts page.questions_count
Note that I had to put the sleeps in otherwise webdriver hangs like anonygoose mentioned. I tried different wait_untils, but did not manage to find something that worked (other than sleep which is not very robust).
Regarding your question about nodes, elements, etc. I think you are best to look at http://www.w3schools.com/dom/default.asp.
To press the button on svpply you can use simply
b.button(:text => "Show All").click
Counting all the products that appear on the page could potentially be done with
b.lis(:class => "grab large").count
This is all for the svpply site. I can't get quora to automate at all, it just stalls my watir-webdriver indefinitely.
You'll also want to wait before you have watir count the products. This can be done with:
b.wait_until{b.lis(:class => "grab large").count > 30}

Resources