How can I iterate through an array of links and check information on the page to which each refers? - ruby

I have a page that lists job openings at my company. I want to use Capybara tests to click on each of the links and determine if the target page contains certain content.
I have the step below. The next step in the process checks for the content on the new page.
When("I click on each job title") do
page.all('.job-box').each do |item|
within(item) do
find('a').click
end
end
end
Once the first link is clicked the browser goes to a different page. At that point, I would like to check the content on that page, then return to the previous step to check the next link. What actually happens is that the code clicks the first link, and then attempts to click the second link. Since the first link sent it to another page, the second link is no longer present, so it returns a stale element error.
Obviously, I need a way to deal with multiple pages before continuing with the next step in the iteration, but I haven't found any documentation that addresses that problem.

You probably want to use page.go_back (or page.driver.go_back depending on the driver you're using). Something like this:
When("I click on each job title") do
page.all('.job-box').each do |item|
within(item) do
find('a').click
# do stuff
end
# roll back (granting you went but one click away)
page.go_back
end
end

As you've recognized the issue here is that the elements are stale when you return to the previous page. Depending on what limitations you're willing to accept there are multiple ways to deal with this. The bigger issue you have is that you're doing this in Cucumber which really doesn't have support for scoping/looping in its test structure. If you're willing for this to only work on a specific browser/platform you can use the respective key modifiers on click to have the browser open a new tab for each job entry and then your next step could run through all the open tabs checking those
When("I click on each job title") do
page.all('.job-box').each do |item|
item.find('a').click(:alt, :cmd) # alt,cmd click in Chrome MacOS opens in new tab
end
end
But really you're going to be better off doing something like
When("I click on each job title and verify info") do
page.all('.job-box').each do |item|
window = page.window_opened_by do
item.find('a').click(:alt, :cmd)
end
page.within_window(window) do
# verify whatever needs to be verified
end
window.close
end
end

Thomas Walpole's answer got me close enough to figure it out. Here is what ended up working:
When("I click on each job title and verify info") do
page.all('.job-box').each do |item|
new_window = window_opened_by do
url = item.find('a')[:href]
within_window open_new_window do
visit url
end
end
page.within_window(new_window) do
# do stuff
end
end
end
It doesn't close each new tab after it checks it, which could be a problem if you are working with a lot of links. I only have about 15, so it wasn't an issue for me. They all closed when the browser closed after the test finished.

Related

How to iterate with more than one element on the page

I have several buttons to click on the same page. How do I iterate and click on each of them?
def btnConectar()
elements = all("button[data-control-name='srp_profile_actions']").count
puts elements
first("button[data-control-name='srp_profile_actions']").click
find("section[class=modal]")
find("button[class='button-primary-large ml1']").click
end
all returns an Array like Capybara::Result object. You can iterate through that using the standard ruby enumerable methods.
all("button[data-control-name='srp_profile_actions']").each do |el|
el.click
find("section[class=modal]") # Not sure what this is for - if it's an expectation/assertion it should be written as such
click_button(class: %w(button-primary-large ml1)
end
That will work as long as clicking on the button doesn't cause the browser to move to another page.
If clicking does cause the browser to move to another page then all the rest of the elements in the Capybara::Result object will become stale (resulting in a stale element reference error on the next iteration) and you won't be able to iterate any more. If that is your case then details on what exactly you're doing will be necessary. Questions like does the original button still exist on the page after clicking the button-primary-large button, or can you iterate by just clicking the first matching button over and over? If it does still exist is it changed in any way to indicate it's already been clicked, or is the number/order of buttons on the page guaranteed to be stable? It would probably help to understand if you posted a fragment of the HTML for the first and second iteration.
def btnConectar()
page.all("button[data-control-name='srp_profile_actions']").each do |el|
while page.has_css?("button[data-control-name='srp_profile_actions']")
el.click #Click the button
find("section[class=modal]") #Modal mapping
click_button(class: %w(button-primary-large ml1)) #Click the button
sleep 3
end
end
end

Watir-webdriver throws 'not clickable' error even when element is visible, present

I am trying to automate tests in Ruby using the latest Watir-Webdriver 0.9.1, Selenium-Webdriver 2.53.0 and Chrome extension 2.21. However the website that I am testing has static headers at the top or sometimes static footers at the bottom. Hence since Watir auto-scrolls an element into view before clicking, the elements get hidden under the static header or the static footer. I do not want to set desired_capabitlites (ElementScrollBehavior) to 1 or 0 as the websites I am testing can have both - static header or static footer or both.
Hence the question are:
1) Why does Watir throw an exception Element not clickable even when the element is visible and present? See ruby code ( I have picked a random company website for an example) and the results below.
2) How can I resolve this without resorting to ElementScrollBehaviour?
Ruby code:
require 'watir-webdriver'
browser = Watir::Browser.new :chrome
begin
# Step 1
browser.goto "shop.coles.com.au/online/mobile/national"
# Step 2 - click on 'Full Website' link at the bottom
link = browser.link(text: "Full website")
#check if link exists, present and visible?
puts link.exists?
puts link.present?
puts link.visible?
#click on link
link.click
rescue => e
puts e.inspect
ensure
sleep 5
end
puts browser.url
browser.close
Result:
$ ruby link_not_clickable.rb
true
true
true
Selenium::WebDriver::Error::UnknownError: unknown error: Element is not clickable at point (460, 1295). Other element would receive the click: div class="shoppingFooter"...div
(Session info: chrome=50.0.2661.75)
(Driver info: chromedriver=2.21.371459 (36d3d07f660ff2bc1bf28a75d1cdabed0983e7c4),platform=Mac OS X 10.10.5 x86_64)>
http://shop.coles.com.au/online/mobile/national
thanks!
You can do a click at any element without getting it visible. Check this out:
link.fire_event('click')
BUT It is very very very not good decision as far as it will click the element even if it is not actually visible or in case when it is just impossible to click it (because of broken sticky footer for example).
That's why much better to wait the fooler, scroll the page and then click like:
browser.div(id: "footerMessageArea").wait_until_present
browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
link.click
The sticky footer is blocking webdriver from performing the click, hence the message that says 'other element would receive the click'.
There are several different ways you can get around this.
Scroll down to the bottom of the page before the click
Hide/Delete the sticky footer before any/all link clicks
Focus on an element below the element you want to click before you perform the click
I Guess your element is visible in the screen.
Before clicking on the element first you have to scroll the webpage so that element is visible then perform the click. Hope it should work.
I had similar issue,
I just used following javascript code with watir:
link = browser.link(text: "Full website")
#browser.execute_script("arguments[0].focus(); arguments[0].click();", link)
Sometimes I have to use .click! which i believe is the fire_event equivalent. Basically something is layered weird, and you just have to go around the front end mess.

Reusing a cucumber step definition on a single page Angular app

I'm using watir-webdriver with the page-object gem to drive cucumber tests for a single web page app built in angular js.
The site uses a multiple stage registration process, filling a series of details each time then clicking a 'next' button. (which is enabled when all details are completed for that page)
The issue occurs when I'm attempting to reuse the following step definition:
And(/^I click next$/) do
#registration = RegistrationPage.new(#browser)
#registration.click_next
end
Which calls the page-object:
class RegistrationPage
include PageObject
button(:next, :value => "Next")
def click_next
#browser.wait_while { next_element.disabled? }
next_element.click
end
end
For the first section, the Next button is found correctly, however on the second call to the step definition, the button cannot be found, despite the button being verifiably enabled and otherwise identical.
I see that you've answered your question but in case it helps to explain it to anyone else:
The issue is likely that to watir it isn't the same next button on each page and all of the next buttons are in scope all of the time. Therefore to click the correct one you would have to use the index property of the button you want i.e. index: 1 for the second page and index 2 for the the button on the third page.

Ruby: Selenium: Webpage has strange page behavior for list, want to loop through each link in list

So I have the following code:
list = [#array of xpaths pointing to each of the 41 links]
begin
list.each do |entry|
wait.until {browser.find_element(xpath: entry)}
browser.find_element(xpath: entry).click
... #do what needs to be done inside link
browser.find_element(xpath: #location of back button) #goes back to list
end
wait.until{browser.find_element(css: ".next>a")}
browser.find_element(css: ".next>a").click
# ^ clicks the next button to get to next page
sleep 2
end while browser.find_element(class_name: "next").displayed?
# ^ end when there is no next button because were on the last page.
I am looping through each link on the page, doing what I need to do with it, and returning to the list. When all 41 links have been hit on the page, I tell it to load the next page. The issue is that for some reason going back to the list always goes back to the first page. So if I am on the third page and I click the link, do what I have to do, and return to the list I am back on the first page of the list. Anyone have an ideas on how to deal with this?

Ruby Selenium Webdriver - Need to wait / sleep while page redirects (refreshes)

I'm using the Ruby selenium-webdriver gem to create a web-crawling/scraping script. The page that I'm scraping is loaded via AJAX and displays information for a certain account. If you select a second account number on a dropdown menu, the page redirects very briefly to another URL and back to the original URL, just with different information loaded via AJAX. I want to be able to scrape info for both the account numbers listed on the dropdown options. The problem is that Selenium performs the scrape faster than the page can redirect/reload on the dropdown click, so I don't end up getting the second account's information.
def crawl_page
browser = Selenium::WebDriver.for :firefox
browser.manage.timeouts.implicit_wait = 10 # seconds
browser.navigate.to 'http://www.foobar.com'
account_dropdown = Selenium::WebDriver::Support::Select.new(browser.find_element(:id, 'account'))
account_dropdown.options.each do |option|
option.click
wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
# this wait is not working because option is selected before redirect/refresh:
wait.until { option.selected? }
html = browser.page_source
scrape_page(html)
end
browser.quit
end
I've tried putting a sleep(3) on the line after the click, but get the following error message:
[remote server] resource://fxdriver/modules/web_element_cache.js:8180:in `fxdriver.cache.getElementAt': Element not found in the cache - perhaps the page has changed since it was looked up (Selenium::WebDriver::Error::StaleElementReferenceError)
I've also tried using Selenium's explicit wait code, but the ids of the elements appear to dynamically change on the updated page so something like:
wait = Selenium::WebDriver::Wait.new(:timeout => 10) # seconds
wait.until { browser.find_element(:id, 'titlexyz').displayed? }
results in an error message that says it's timed out and can't find the element:
~lib/selenium/webdriver/common/wait.rb:57:in `until': timed out after 10 seconds (Unable to locate element: {"method":"id","selector":"titlexyz"}) (Selenium::WebDriver::Error::TimeOutError)
Is there some way to get it to sleep or wait without having to look for a specific element on the page?
OK, so I finally figured it out thanks to SiKing's answer. The trick was to count the number of options for the dropdown menu, and put that into a .times loop. Then on each iteration, I instantiated a new Selenium object for the dropdown menu, found the correct option number and clicked it. I also put the script to sleep for 5 seconds to give it a chance to do the reload/redirect.
def crawl_page
browser = Selenium::WebDriver.for :firefox
browser.navigate.to 'http://www.foobar.com'
account_dropdown = Selenium::WebDriver::Support::Select.new(browser.find_element(:id, 'account'))
count = account_dropdown.options.count
count.times do |option_num|
account_dropdown = Selenium::WebDriver::Support::Select.new(browser.find_element(:id, 'account'))
account_dropdown.options[option_num].click
sleep 5
html = browser.page_source
scrape_page(html)
end
browser.quit
end
I do not do ruby, so I cannot help you with the ruby syntax.
Every time a page reloads (in your code every time you do option.click), all WebElements (your account_dropdown.find_elements(:css, 'option')) that you have are no longer valid! You will have to base your loop on something else - perhaps the count of the items in the pulldown - and find each of the elements you want to interact with inside the loop!

Resources