PageObject "wait element" method don't work - ruby

I'm working on test automation with cucumber, selenium-webdriver and page-object gem.
When I try to run simple test cucumber catch the following error:
Scenario: Going to billing # features/test.feature:10
When I click 'Платные услуги' # features/step_definitions/test_steps.rb:13
Unable to locate element: {"method":"link text","selector":"Платные услуги"} (Selenium::WebDriver::Error::NoSuchElementError)
[remote server] file:///tmp/webdriver-profile20130412-21410-z4p1ez/extensions/fxdriver#googlecode.com/components/driver_component.js:8405:in `FirefoxDriver.prototype.findElementInternal_'
[remote server] file:///tmp/webdriver-profile20130412-21410-z4p1ez/extensions/fxdriver#googlecode.com/components/driver_component.js:8414:in `FirefoxDriver.prototype.findElement'
[remote server] file:///tmp/webdriver-profile20130412-21410-z4p1ez/extensions/fxdriver#googlecode.com/components/command_processor.js:10421:in `DelayedCommand.prototype.executeInternal_/h'
[remote server] file:///tmp/webdriver-profile20130412-21410-z4p1ez/extensions/fxdriver#googlecode.com/components/command_processor.js:10426:in `DelayedCommand.prototype.executeInternal_'
[remote server] file:///tmp/webdriver-profile20130412-21410-z4p1ez/extensions/fxdriver#googlecode.com/components/command_processor.js:10366:in `DelayedCommand.prototype.execute/<'
./features/pages/job_main_page.rb:38:in `go_to_billing'
./features/step_definitions/test_steps.rb:14:in `/^I click 'Платные услуги'$/'
features/test.feature:11:in `When I click 'Платные услуги''
Here is the cucumber feature:
Scenario: Going to billing
When I click 'Платные услуги'
Then I should see "Коммерческие услуги"
Step definition where test falls:
When(/^I go to billing$/) do
#job_myroom_billing = #job_myroom.billing_element.when_visible.go_to_billing
end
And page object:
class BasePage
include PageObject
include RSpec::Matchers
end
class JobMyroom < BasePage
link :billing, link: 'Платные услуги'
def go_to_billing
billing
JobMyroomBilling.new #browser
end
end
class JobMyroomBilling < JobMyroom
#some code
end
Whats wrong? Driver don't wait the element's presence

I think Sheg is giving some good advice here although I would tend to do it slightly different:
wait_until do
billing_element.visible?
end
Another thing you can do that is very similar is to replace the code in your method with this:
def go_to_billing
billing_element.when_present.click
JobMyroomBilling.new #browser
end
In this case we are waiting until the link is present and when it is the when_present method returns the element and we simply click on it.
Another thing I might suggest is using watir-webdriver as the driver instead of selenium-webdriver. watir-webdriver is built on top of selenium-webdriver but seems the have far better handling of waiting for items to actually be loaded on the DOM. If your link is being added dynamically using Ajax then you will have to write some code to wait until it is there before actually interacting with it. The only thing you will have to do to use the other gem is change the code in your Before block to this:
Before do
#browser = Watir::Browser.new :firefox
end
Another pointer I would give you is to not have one page object return an instance of the next page object. I am eating my own words here because a few years ago this is how I did it but I have found over time that this is a brittle approach. Instead, I would use the PageObject::PageFactory to manage the creation of the correct pages and when you have a step that is a generic step you can simply use the #current_page instance variable.
The last pointer I will give you is to remove the assertions from your page object. I would have the page object simply provide the abstraction and perform all verification in the step definitions. This is a much cleaner separation of concerns and will make maintaining your tests easier over time.

either you can use sleep methode from java with try catch. or implicitwait for selenium webdriver.
Webdriver never wait for the element to be displayed... basically you need the delay.

The page object when_visible method is meant to wait for Ajax events to finish loading, as mentioned here https://github.com/cheezy/page-object/wiki/Ajax-Calls.
If webdriver is trying to click on the link element before it is completely rendered, and is not Ajax related, consider waiting until the element is displayed with webdriver first. For example:
wait.until { driver.find_element(:link_text => "Платные услуги").displayed? }
#job_myroom.go_to_billing
You could (highly recommended) implicitly do this with all elements by invoking the following right after you instantiate your webdriver:
driver.manage.timeouts.implicit_wait = 3
In addition, in many situations, I have found that locating links by href performs a lot better than link_text. Perhaps these could solve your element timing issues.

Related

Error initializing driver method - undefined local variable

In my test automation project, I am using ruby with capybara, cucumber and selenium.
(I already have devkit installed and chrome is starting normally)
When looking for an element in the site to select it, I am using the method driver.findElement (By.xpath (.... etc"), but when executing cucumber, it is indicating the following error:
I already removed and reinstalled the selenium-webdriver gem but it did not resolve.
Can someone help me in resolving why WebDriver does not seem to be valid in this context?
code example
(finding button logout using tag img, because the element don't have name or id)
After('#logout') do
element = driver.findElement(By.xpath("//img[#src='/Portal/img/user.png']"));
element.click
end
Result cucumber execution
Feature: Login
Description feature
DevTools listening on ws://127.0.0.1:60121/devtools/browser/c0bacc6e-697a-4614-b82c-eb324d587df5
#logout
Scenario: Login_OK # features/support/login.feature:14
Given that i access the main portal page HRP # features/step_definitions/login_steps.rb:1
When do login using "abc123" and "abc123password" # features/step_definitions/login_steps.rb:5
Then system do login # features/step_definitions/login_steps.rb:10
And show message "Welcome to Portal." # features/step_definitions/login_steps.rb:14
undefined local variable or method `driver' for # (NameError)
./features/support/hooks.rb:4:in `After'
Failing Scenarios:
cucumber features/support/login.feature:14 # Scenario: Login_OK
1 scenario (1 failed)
4 steps (4 passed)
0m5.457s
If you're using Capybara with Cucumber in a standard setup then you shouldn't be really ever be calling the selenium webdriver instance directly - driver (except for in some very rare circumstances). Instead you should be using the Capybara methods. What is available in your After hook depends on exactly how where you've includeed what but one of these will probably be avaialble
After('#logout') do
element = page.find(:xpath, ".//img[#src='/Portal/img/user.png']"));
element.click
end
or
After('#logout') do
element = Capybara.current_session.findElement(:xpath, ".//img[#src='/Portal/img/user.png']");
element.click
end
Note: there is probably a better way to locate the logout button than an XPath on the contained images source, but without seeing the HTML of the button it's impossible to say for sure (for instance, Capybaras button finder will match against the alt attribute of an img element nested inside a <button> element)

Need help reliably finding asynchronous elements in React with Capybara without using sleep everywhere

Newbie engineer question here, but I've searched and can't find a solution.
Environment: Mac OS, Chrome, Capybara with Selenium Chrome-driver, Ruby with Rspec running the test.
Situation:
Testing a React app where the user logs in using a username and password, followed by clicking on a sidebar nav link that loads up....followed by other actions. Capybara continues to fail to find the sidebar nav link to click on. I believe the sidebar nav is its own React component and loads asynchronously
App & Test Behavior:
Sometimes Capybara finds the link element, clicks the link and the test passes. Other times it completely fails to find the element and fails the test.
Solutions I've tried:
Upping the default time for finder methods to continue to search to 15+ seconds(I've never noticed the app take more than 5 seconds on anything)
I only have used finder methods that SHOULD be repeat-searching for the default time for the element to appear (page.find, page.has_css?, etc)before attempting the .click action. What I have found is that after the login happens, and while the nav is loading, the test just seems to fail with element not found. The page.find does NOT seem to continue to search for 15 seconds before failing - instead, the login happens, then a second later I get a fail with element not found.
I have tried passing a wait into the find (example: page.find(some element, wait:15).click . This runs into the same problem as above where it doesn't seem to continue searching for 15 seconds for the element to appear and then click it.
What does seem to work is adding in sleeps before searching for an element (example: login, sleep(5), page.find(something).click).
Unfortunately I'm having this same problem with other elements all over in the app - for example a div may have a bunch of cards in it. When a new card is added it takes 2-3 seconds to show up in the list (probably due to sending out the card info to the database, and refreshing the cards on the page). If I try and page.find immediately after adding a card, the test will almost immediately fail with an element not found message. If I add the card, sleep(3), THEN try page.find, it will find it.
I can't just add sleep all over the place in the app, because its huge and it would slow down the tests immensely. I think I've tried all the typically suggested fixes for asynchronous loading. Any idea what is going on here and what I can do to fix it
editing to add some requested code.
I'm using capybara 3.2.
We are using a bit of a page object style framework so I"ll try and post the actual test with its methods in bold and helper methods nested in it.
Its getting caught in the before action of this particular feature on the final method to click on the sidebar. I'm a little limited on what I can show, but I think this will make sense....
The actual before action:
before do
**app.launch_app(app.url)**
# this instantiates an instance of the framework and helper methods and # goes to the app url
**app.login.full_login(app.username('Some User'), app.password)**
# def full_login(user, pass)
# enter_email(user)
# def enter_email(user)
# return if already_logged_in?
# def already_logged_in?
# page.has_css?("a[href*='/me']", wait: false)
# end
# fill_field('email', user)
# def fill_field(field, text)
# sleep 0.1
# page.find("input[type=#{field}]").send_keys text
# end
# click_button 'Log In'
# def click_button(text)
# page.click_on text
# end
# end
# login_using_second_auth(user, pass)
# def login_using_second_auth(user, pass)
# page.fill_in 'username', with: user
# page.fill_in 'password', with: pass
# click_button 'Sign In'
# end
# end
app.nav.click_sidebar_link('Admin Account', 'Admin')
# def click_sidebar_link(link, section)
# sleep(2)
# page.find('div[class^=app__wrapper] > div > div > div', text:
# section)
# .find('span[class^=nav-item]', text: link).click
# end
end
Sorry that looks so messy, but I think you guys can make sense of it.
The test is flaky so after running it a few times I can't get the exact error, but its usually element not found on the span with the text Admin

Why do I need to add sleep for rspec to pass with selenium 2.48.0?

Recently we upgraded our selenium web driver from 2.47.1 to 2.48.0.
With this upgrade I need to add sleep for a few seconds in rspec to pass. Spec was working properly without sleep with the older version.
sleep(inspection_time=5) // why do I need this?
my_form_page.save_button.click
// some assertion here
Edit
I tried using implicit wait instead of sleep.But it's not working. Is there any specific reason behind it?
Capybara.current_session.driver.browser.manage.timeouts.implicit_wait = 50
Generally speaking, rspec selenium tests are known to be "flakey". Sometimes rspec tries to search for an element before it appears on page due to many reasons (ie: element appears upon ajax response).
Here's a tip that may help you solve this, if you will wrap your capybara finders inside of a within block, your tests will wait until it finds that within selector FIRST before trying to run the code inside of it.
This more-often-than-not will help solve a test running too fast on a page that takes a while to load and your button or selector or whatever isn't actually on the page yet (which is why it fails).
So take a look at these 2 examples and try the within method...
# spec/features/home_page_spec.rb
require "spec_helper"
describe "the home page", type: :feature do
context "form" do
# THIS MIGHT FAIL!!!!
it "submits the form", js: true, driver: :selenium do
visit "/"
find("#submit_button").click
end
# THIS PROBABLY WILL PASS!!!
it "submits the form", js: true, driver: :selenium do
visit "/"
within "form" do
find("#submit_button").click
end
end
end
end

Using SitePrism with Rspec and Capybara feature specs

I recently discovered SitePrism via the rubyweekly email.
It looks amazing. I can see its going to be the future.
The examples I have seen are mostly for cucumber steps.
I am trying to figure out how one would go about using SitePrism with rspec.
Assuming #home_page for the home page, and #login_page for the login_page
I can understand that
#home_page.load # => visit #home.expanded_url
however, the part I am not sure about, is if I think click on for example the "login" link, and the browser in Capybara goes to the login page - how I can then access an instance of the login page, without loading it.
#home_page = HomePage.new
#home_page.load
#home.login_link.click
# Here I know the login page should be loaded, so I can perhaps do
#login_page = LoginPage.new
#login_page.should be_displayed
#login_page.email_field.set("some#email.com")
#login_page.password_field.set("password")
#login_page.submit_button.click
etc...
That seems like it might work. So, when you know you are supposed to be on a specific page, you create an instance of that page, and somehow the capybara "page" context, as in page.find("a[href='/sessions/new']") is transferred to the last SitePrism object?
I just feel like I am missing something here.
I'll play around and see what I can figure out - just figured I might be missing something.
I am looking through the source, but if anyone has figured this out... feel free to share :)
What you've assumed turns out to be exactly how SitePrism works :) Though you may want to check the epilogue of the readme that explains how to save yourself from having to instantiate page objects all over your test code. Here's an example:
# our pages
class Home < SitePrism::Page
#...
end
class SearchResults < SitePrism::Page
#...
end
# here's the app class that represents our entire site:
class App
def home
Home.new
end
def results_page
SearchResults.new
end
end
# and here's how to use it:
#first line of the test...
#app = App.new
#app.home.load
#app.home.search_field.set "sausages"
#app.home.search_button.click
#app.results_page.should be_displayed

Why the object references are not correctly passed in this RSpec script?

I have to say I am new both to Ruby and to RSpec. Anyway I completed one RSpec script but after refactoring it failed. Here is the original working version:
describe Site do
browser = Watir::Browser.new :ie
site = Site.new(browser, "http://localhost:8080/site")
it "can navigate to any page at the site" do
site.pages_names.each do |page_name|
site.goto(page_name)
site.actual_page.name.should eq page_name
end
end
browser.close
end
and here is the modified version - I wanted to have reported all the pages which were visited during the test:
describe Site do
browser = Watir::Browser.new :ie
site = Site.new(browser, "http://localhost:8080/site")
site.pages_names.each do |page_name|
it "can navigate to #{page_name}" do
site.goto(page_name)
site.actual_page.name.should eq page_name
end
end
browser.close
end
The problem in the latter case is that site gets evaluated to nil within the code block associated with 'it' method.
But when I did this:
...
s = site
it "can navigate to #{page_name}" do
s.goto(page_name)
s.actual_page.name.should eq page_name
end
...
the nil problem was gone but tests failed with the reason "browser was closed"
Apparently I am missing something very basic Ruby knowledge - because the browser reference is not working correctly in modified script. Where did I go wrong? What refactoring shall be applied to make this work?
Thanks for your help!
It's important to understand that RSpec, like many ruby programs, has two runtime stages:
During the first stage, RSpec loads each of your spec files, and executes each of the describe and context blocks. During this stage, the execution of your code defines your examples, the hooks, etc. But your examples and hooks are NOT executed during this stage.
Once RSpec has finished loading the spec files (and all examples have been defined), it executes them.
So...trimming down your example to a simpler form, here's what you've got:
describe Site do
browser = Watir::Browser.new :ie
it 'does something with the browser' do
# do something with the browser
end
browser.close
end
While visually it looks like the browser instance is instantiated, then used in the example, then closed, here's what's really happening:
The browser instance is instantiated
The example is defined (but not run)
The browser is closed
(Later, after all examples have been defined...) The example is run
As O.Powell's answer shows, you can close the browser in an after(:all) hook to delay the closing until after all examples in this example group have run. That said, I'd question if you really need the browser instance at example definition time. Generally you're best off lazily creating resources (such as the browser instance) when examples need them as they are running, rather than during the example definition phase.
I replicated your code above using fake classes for Site and Watir. It worked perfectly. My only conclusion then is that the issue must lie with either one of the above classes. I noticed the Site instance only had to visit one page in your first working version, but has to visit multiple pages in the non working version. There may be an issue there involving the mutation happening inside the instance.
See if this makes a difference:
describe Site do
uri = "http://localhost:8080/site"
browser = Watir::Browser.new :ie
page_names = Site.new(browser, uri).page_names
before(:each) { #site = Site.new(browser, uri) }
after(:all) { browser.close }
pages_names.each do |page_name|
it "can navigate to #{page_name}" do
#site.goto(page_name)
#site.actual_page.name.should eq page_name
end
end
end

Resources