How can I get Watir to make a fresh reference on a non-stale element? - ruby

A portion of some tests I am writing calls for checking if an option gets removed from a select list once that option has been used. I am inconsistently getting this error: timed out after 60 seconds, waiting for {:xpath=>"//select[#id = 'newIdentifierType']//option", :index=>31} to be located (Watir::Exception::UnknownObjectException)
It causes my test to fail maybe 2-3 times out of 10 runs and seems kind of random. I think Watir is looking for the "old" select list with this ID since it caches the element and may also include that it had 32 items, but it times out since a select list with this ID and 32 items no longer exists. The new select list has the same ID but only 31 items.
Is there a way to always get a new reference on this element even though it's not technically going stale? Am I experiencing this problem due to a different issue?
My current code for getting the options in the select list:
#browser.elements(:xpath => "//select[#id = 'newIdentifierType']//option")
I am using Ruby/Cucumber with Selenium and Watir Webdriver level. I first tried defining the element as a select_list in a page-object but moved it to the step definitions using #browser.element to see if that would stop the timeout. I thought it may ignore Watir's cached elements and get the most current one with the ID, but that does not appear to be the case.

Please avoid using XPath with Watir. Everything you can do with XPath, Watir has a much more readable API to handle.
To check for a specific option not being there, you should avoid collections and locate directly:
el = browser.select_list(id: "newIdentifierType").option(value: "31"))
# or
el = browser.select_list(id: "newIdentifierType").option(text: "This one"))
Then to see if it has gone away:
el.stale?
# or
el.wait_until(:stale?)
That won't test the right thing if the entire DOM has changed, though, so you might need to just relocate:
browser.select_list(id: "newIdentifierType").option(text: "This one")).present?
If you are intent on using a collection, the correct way to get the list of options is:
options = #browser.select(id: 'newIdentifierType').options
el = options.find { |o| o.text == 'This one' }
# Do things
el.stale?

Related

Logging and asserting the number of previously-unknown DOM elements

I'ts my first tme using Cypress and I almost finalized my first test. But to do so I need to assert against a unknown number. Let me explain:
When the test starts, a random number of elements is generated and I shouldn't have control on such a number (is a requirement). So, I'm trying to get such number in this way:
var previousElems = cy.get('.list-group-item').its('length');
I'm not really sure if I'm getting the right data, since I can not log it (the "cypress console" shows me "[Object]" when I print it). But let's say such line returns (5) to exemplify.
During the test, I simulate a user creating extra elements (2) and removing an (1) element. Let's say the user just creates one single extra element.
So, at the end os the test, I need to check if the number of eements with the same class are equals to (5+2-1) = (6) elements. I'm doing it in this way:
cy.get('.list-group-item').its('length').should('eq', (previousTasks + 1));
But I get the following message:
CypressError: Timed out retrying: expected 10 to equal '[object Object]1'
So, how can I log and assert this?
Thanks in advance,
PD: I also tryed:
var previousTasks = (Cypress.$("ul").children)? Cypress.$("ul").children.length : 0;
But it always returns a fixed number (2), even if I put a wait before to make sure all the items are fully loaded.
I also tryed the same with childNodes but it always return 0.
Your problem stems from the fact that Cypress test code is run all at once before the test starts. Commands are queued to be run later, and so storing variables as in your example will not work. This is why you keep getting objects instead of numbers; the object you're getting is called a chainer, and is used to allow you to chain commands off other commands, like so: cy.get('#someSelector').should('...');
Cypress has a way to get around this though; if you need to operate on some data directly, you can provide a lambda function using .then() that will be run in order with the rest of your commands. Here's a basic example that should work in your scenario:
cy.get('.list-group-item').its('length').then(previousCount => {
// Add two elements and remove one...
cy.get('.list-group-item').its('.length').should('eq', previousCount + 1);
});
If you haven't already, I strongly suggest reading the fantastic introduction to Cypress in the docs. This page on variables and aliases should also be useful in this case.

Ruby Watir if then statement

I've looked around the internet but without much success as well as checked through here.
I'm new to ruby watir and mini test but i'm trying to write some automation that checks the value in a select list drop down and if it finds a value it changes it a 2nd value. If it finds the 2nd value the next time it runs i want it to change the first value.
I've created variables of;
$Title1 = "Dr"
$Title2 = "Mr"
and the code I'm trying to run is attempting to use an if, then, else series of statements;
#Title
if $browser.select_list(id: "title") == $Title2 then
$browser.select_list(:id, "title").select($Title1)
else
if $browser.select_list(id: "title") == $Title1 then
$browser.select_list(:id, "title").select($Title2)
end
however, nothing happens and no errors are triggered.
Any help would be greatly appreciated
Problem
The problem is that the if statements are comparing apples to oranges (ie two different things). You are comparing a Watir::SelectList, from $browser.select_list(id: "title"), with a string, from $Title2.
For Watir elements, the == is used to check if two elements are the same element on the page. The element will never equal a string.
Solution
What you actually want to compare to the Title2 is the selected options of the select list. Watir select lists have a selected? method for checking this.
You could do something like:
if $browser.select_list(id: "title").selected?($Title2)
$browser.select_list(:id, "title").select($Title1)
else
$browser.select_list(:id, "title").select($Title2)
end

How to handle security alert in Firefox with Selenium

I'm using selenium-webdriver with ruby to write automated tests.
Chrome and the chromedriver binary work really well, but I have an issue with Firefox that is related to the configuration of the browser and that's making my tests fail, whereas they pass with Chrome.
When executing the tests in Firefox, sometimes I get an alert with this message:
Although this page is encrypted, the information you have entered is
to be sent over an unencrypted connection and could easily be read by
a third party
And it breaks the execution. Is there a way of disabling this warning in recent Firefox versions (10+) or handling this behavior with Selenium?
In the process of finding a solution for this, I think I might have found a bug in Capybara (v1.1.2).
I managed to get around this problem using the following approach, instead of using the click from capybara (which would not allow me to capture an exception), I started using the click method from selenium-webdriver.
It seems that when Firefox triggers this alertbox, a string with the message
Although this page is encrypted, the information you have entered is
to be sent over an unencrypted connection and could easily be read by
a third party
is returned as a result of object.native.click, otherwise the string
ok
is returned.
# Define the click method in ruby and call it when you're executing a 'click'
# Public: Click the object.
#
# object - Capybara::Node::Element object to click.
def click object
return_string = object.native.click
check_firefox_alertbox if return_string == "ok"
end
def check_firefox_alertbox
if #current_browser == :firefox
#session.driver.browser.switch_to.alert.accept
end
rescue Exception => e
puts e
end
Here is what you can do. Type about:config in the firefox. You would be presented a number of options (once you pass through a warning message).
You have to look for security.warn_leaving_secure; and security.warn_leaving_insecure. Make both of them false. And you would be good to go.
Please note: This would work only on the FF instance you have made modification to, so you will need to use firefox profile to launch this instance.
Hope this helps.
Actually this meant to be a comment but I need to go above 50 in order to be able to comment..I suppose by 'breaking' the execution you mean that of the Ruby Script right? What happens to Firefox? Needs a click to proceed? If that is the case you can improvise by capturing the Ruby error after inserting the sensitive code (where it breaks) between a BEGIN and a RESCUE clause..Something like this..
BEGIN
.
.
Code that is giving you a headache
.
.
RESCUE
.
Capture the exception and give Ruby a chance to continue the script normally.
.
.
END
Alternatively if you don't fancy the above solution you can go to Firefox and then type in the address box about:config. Filter by 'security.warn' and set to false all the boolean variables you see there. Good riddance, fingers crossed ;)

HtmlUnit getByXpath returns null

I am coding with Groovy, however, I don't believe its a language specific set of questions.
I actually have two questions
First Question
I've run into an issue while using HtmlUnit. It is telling me that what I am trying to grab is null.
The page I'm testing it on is:
http://browse.deviantart.com/resources/applications/psbrushes/?order=9&offset=0#/dbwam4
My code:
client = new WebClient(BrowserVersion.FIREFOX_3)
client.javaScriptEnabled = false
page = client.getPage(url)
//coming up as null
title = page.getByXPath("//html/body/div[4]/div/div[3]/div/div/div/div/div/div/div/div/div/div/h1/a")
println title
This simply prints out: []
Is this because the page uses onclick()? If so, how would I get around that? Enabling javascript creates a mess in my cmd prompt.
Second Question
I am wanting to also get the image but am having trouble because when I attempt to get the XPath (via firebug) it shows up as: //*[#id="gmi-ResViewSizer_img"]
How do I handle that?
First Answer:
/html/body/div[3]/div/div[3]/div/div/div/div/div/div/div/div/div/div/h1/a
Your XPATH was off by one in the predicate filter for the 4th div of the body, it should be the 3rd div. It appears the HTML for the site can/does change from when you had origionally snagged the XPATH using Firebug. You may need to adjust your XPATH to accommodate for potential change and be less sensitive to some differences in document structure.
Maybe something like this:
/html/body//div/h1/a
Second Answer: The XPATH that you listed will work. It may look odd/short(and may not be the most efficient), but // starts at the root node and looks throughout every node in the tree, * matches on any element(to include the img) and the [] predicate filter restricts it to those that have an id attribute who's value equals "gmi-ResViewSizer_img".
There are many other options for XPATHs that could work as well. It will also depend on how often the HTML structure changes. This is one that also works for the page referenced to select that img:
/html/body/div/div/div/div/img[1]
I had the same problem, I solved when I realize iframe tags on page, try call
((HtmlPage)current_page.getFrames()[n].getEnclosedPage()).getElementByXPath(...
where n is the position in frame in iframe collection. It's work for me !!!
Thanks a lot.

Issues with Sinatra and Heroku

So I've created and published a Sinatra app to Heroku without any issues. I've even tested it locally with rackup to make sure it functions fine. There are a series of API calls to various places after a zip code is consumed from the URL, but Heroku just wants to tell me there is an server error.
I've added an error page that tries to give me more description, however, it tells me it can't perform a `count' for #, which I assume means hash. Here's the code that I think it's trying to execute...
if weather_doc.root.elements["weather"].children.count > 1
curr_temp = weather_doc.root.elements["weather/current_conditions/temp_f"].attributes["data"]
else
raise error(404, "Not A Valid Zip Code!")
end
If anyone wants to bang on it, it can be reached at, http://quiet-journey-14.heroku.com/ , but there's not much to be had.
Hash doesn't have a count method. It has a length method. If # really does refer to a hash object, then the problem is that you're calling a method that doesn't exist.
That # doesn't refer to Hash, it's the first character of #<Array:0x2b2080a3e028>. The part between the < and > is not shown in browsers (hiding the tags themselves), but visible with View Source.
Your real problem is not related to Ruby though, but to your navigation in the HTML or XML document (via DOM). Your statement
weather_doc.root.elements["weather"].children.count > 1
navigates the HTML/XML document, selecting the 'weather' elements, and (tries to) count the children. The result of the children call does not have a method count. Use length instead.
BTW, are you sure that the document contains a tag <weather>? Because that's what your're trying to select.
If you want to see what's behind #, try
raise probably_hash.class.to_s

Resources