I have a watir-webserver Ruby script I am developing, and I am having it first prompt the user for a username and password (with Ruby-Tk).
In any case, I can open the page with this code:
b = Watir::Browser.start 'https://connect.mypage.com/Home'
After that, I call the following:
t1=b.text_field(:id => 'ctl00_cphBody_txtUserName').when_present.set #entry1.textvariable
Nothing is filled in.
I have tried hard coding a name in for ctl00_cphBody_txtUsername, but that did not work either.
Using the Inspector in Firefox, the field is constructed as following:
<input name="ctl00$cphBody$txtUsername" id="ctl00_cphBody_txtUsername" class="firstFocus" maxlength="50" type="text">
The class of firstFocus is:
<script type="text/javascript">
$(document).ready(function () {
jQuery('.firstFocus').focus();
});
</script>
The problem is that the locator is case-sensitive.
Notice that the id attribute value is "ctl00_cphBody_txtUsername" but the locator being used is "ctl00_cphBody_txtUserName". Due to the "N" not matching the "n", the element is never found, which is why the when_present times out.
Correcting the id in the locator will fix the issue:
t1=b.text_field(:id => 'ctl00_cphBody_txtUsername').when_present.set #entry1.textvariable
If I start the browser and open the page like below, I have no problems locating those two text_field's:
browser = Watir::Browser.new
browser.goto 'https://connect.mypage.com/Home'
#Then, the first item actually has a class, so I use that
t1=browser.text_field :class => 'firstFocus'
t1.set #entry1.textvariable
Related
I'm working with a web page with the following HTML where I want to identify the first <input> field inside the <span> using a text_field from within a page-object.
<div id="131:"> Please enter your name:
<span class="autocompspan " style="position:static;">
<input style="position: static;" class="autocompinput yui-ac-input" id="132:" name="132:"
onfocus="juic.fire("132:","_focus",event);"
onchange="juic.fire("132:","_despatchChange",event);"
onblur="juic.fire("132:","_blur",event);" size="60"
onkeydown="juic.fire("132:","_onkeydown",event);"
onkeyup="juic.fire("132:","_onkeyup",event);" aria-disabled="false" value=""
role="combobox" aria-autocomplete="list" aria-owns="132:_divList"
aria-activedescendant="132:_divAD" findtype="proxy" delimchar="" hideusername="false"
fetchusername="false" autocomplete="off" type="text">
<input value="" id="132:_hidden" name="132:_hidden" type="hidden">
</span>
</div>
If I use :id => '132:' to identify the field things work fine. I.e. text_field(:target_user_name, :id => '132:' ) works.
The issue is that this HTML is generated by the underlying app (SAP) which does not always generated the same value for the <input> field id so using the id cannot be relied upon to consistently identify the element.
So, given the above HTML what other ways might I go about reliably finding this <input> field.
I've tried the following, none of which work. Most of them time out waiting for the element to be located.
text_field(:target_user_name, :xpath => "//*[#class='autocompinput yui-ac-input' and #role = 'combobox']" )
text_field(:target_user_name, :xpath => "//*[#class='autocompinput' and #role = 'combobox']" )
text_field(:target_user_name, :xpath => "//span/input[#class='autocompinput yui-ac-input' and #role = 'combobox']" )
text_field(:target_user_name, :xpath => "//input[#class='autocompinput yui-ac-input' and #role = 'combobox']" )
text_field(:target_user_name, :class => 'autocompinput yui-ac-input')
Any thoughts?
When an element does not have unique identifiable attributes, you should look at the elements around it. In this case, there is user visible text that helps a user identify the purpose of the field. That same text can be used to identify the element in Watir.
As the surrounding div only contains the labelling text, you can search for that div by its text and get the only text field in it:
browser.div(text: 'Please enter your name:').text_field
As a page-object accessor:
text_field(:target_user_name) { div_element(text: 'Please enter your name:').text_field_element }
Firstly, Watir is designed to make it so that you shouldn't have to use XPATH.
It depends on how many different elements/ids are on the page, but I've found that using regular expressions often works well with dynamically generated ids. So either grab the id and use it elsewhere:
id = browser.text_field(id: /\d\d\d/).tr(':', '')
or just use it directly:
text_field(:target_user_name, id: /\d\d\d:/)
In this particular case you can check the first input field after 'Please enter your name:' text using below xpath:
//div[text()='Please enter your name:']//following::input[1]
In general if you encounter fields that does not have a unique identifier you can rely on static text or fields and then use xpath functions such as following, preceding etc.
I'm working on a white-hat web-crawler that will periodically log into my account and check some information for me using Ruby with Watir and Nokogiri.
Here's the simplified HTML I'm trying to pull information from:
<div class="navbar navbar-default navbar-fixed-top hidden-lg hidden-md" style="z-index: 1002">
<div class="banner-g">
<div class="container">
<div id="user-info">
<div id="acct-value">
GAIN/LOSS <span class="SPShares">-$12.85</span>
</div>
<div id="committed">
INVESTED <span class="SPPortfolio">$152.11</span>
</div>
<div id="avail">
AVAILABLE <span class="SPBalance">$26.98</span>
</div>
I'm trying to pull the $26.98. at the bottom of the excerpt.
Here are three snippets of code I'm using. They're all pretty much identical except for the XPath. The first two return their values perfectly, but the third always returns a value of "0" even though it 'should' return "$26.98" or "26.98".
val_one = page_html.xpath(".//*[#id='openone']/div/div[2]/div[1]/div/div[2]/table/tbody/tr[2]/td[1]").text.gsub(/\D/,'').to_i
val_two = page_html.xpath(".//*[#id='opentwo']/div/div[2]/div[2]/div/div[2]/table/tbody/tr[2]/td[1]").text.gsub(/\D/,'').to_i
val_three = page_html.xpath(".//*[#id='avail']/a/span").text.gsub(/\D/,'').to_i
puts val_three
I assume it's a problem with the XPath, but I've gone through dozens of XPath troubleshooting questions here and none have worked. I checked the XPath with both FirePath and "XPath Checker". I also tried having the XPath search for the "SPBalance" class but that gave the same result.
When I remove to.i from the end, it returns a blank line instead of a zero.
Elsewhere in the site when using Watir, I was able to fix problems recording a value by calling .focus, but for this piece of the code, which is more Nokogiri, using .focus causes the error message:
undefined method `focus' for []:Nokogiri::XML::NodeSet (NoMethodError)
I assume .focus doesn't work for Nokogiri.
Update: Replaced HTML with a cleaner/more complete version.
I've continued to play around with different ways of reaching that data cell, including xpath, css and a search method. Someone told me xpath wouldn't work for this page so I spent even more time trying to get css to work. Someone else told me the page had Javascript, which would prevent Watir from working. So I tried rewriting the app for Selenium instead. Selenium did not solve the problem, and created a whole host of other problems.
Update: After following advice from the Tin Man, I've found that the node is not actually visible in the HTML when it is downloaded using curl.
I'm now trying to access the node using Watir instead of Nokogiri (as he suggested).
Here's some of what I've tried so far:
avail_funds = browser.span :class => 'SPBalance'
avail_funds.exists?
avail_funds.text
avail_funds = browser.span(:css, 'span[customattribute]').text
avail_funds = browser.div(:id => "avail").a(:href => "/Profile/MyShares").span(:class => "SPBalance").text
avail_funds = browser.span(:xpath, ".//*[#id='avail']/a/span").text
avail_funds = browser.span(:css, 'span[class="SPBalance"]').text
avail_funds = browser.span.text
avail_funds = browser.div.text
browser.span(:class, "SPBalance").focus
avail_funds = browser.span(:class, "SPBalance").text
avail_funds = #browser.span(:class => 'SPBalance').inner_html
puts #browser.spans(:class => "SPBalance")
puts #browser.span(:class => "SPBalance")
texts = #browser.spans(:class => "SPBalance").map do |span|
span.text
end
So far all of the above return either blank lines or an error message.
The div class with the ID "user-info" is visible within the HTML as downloaded via curl. Everything beneath that, however, is not visible.
When I try:
avail_funds = browser.div(:id => "user-info").text
I get only blank lines.
When I try:
avail_funds = browser.div(:class => "navbar navbar-default navbar-fixed-top hidden-xs hidden-sm").text
I get actual text back! But unfortunately the string does not contain the value I want.
I also tried:
puts browser.html
Because I thought if the value where visible in that version of the HTML, as it is through my Firefox plug-in, I could parse down to the value I want. But unfortunately the value is not visible in that version of the HTML.
By first 2 commands you fetch data directly from table cell beginning from the root of the document, and in the last one you starting from the center.
Try out to give span id and get data again, and then grow up the complexity and you will find your error in xpath
The first problem is you're trying to use a long, too-long, selector that is referencing tags that don't exist:
require 'nokogiri'
doc = Nokogiri::HTML(<<EOT)
<head>
<body class="cbp-spmenu-push">
<div id="FreshWidget" class="freshwidget-container responsive" data-html2canvas-ignore="true" style="display: none;">
<div id="freshwidget-button" class="freshwidget-button fd-btn-right" data-html2canvas-ignore="true" style="display: none; top: 235px;">
<link rel="stylesheet" href="/Content/css/NavPushComponent.css"/>
<script src="/Scripts/classie.js"/>
<script src="/Scripts/modernizr.custom.js"/>
<div class="navbar navbar-default navbar-fixed-top hidden-lg hidden-md" style="z-index: 1002">
<div class="banner-g">
<div class="container">
<div id="user-info">
<div id="acct-value">
<div id="committed">
<div id="avail">
<a href="/Profile/MyBalance">
AVAILABLE
<span class="SPBalance">$31.59</span>
EOT
doc.at('tbody') # => nil
".//*[#id='openone']/div/div[2]/div[1]/div/div[2]/table/tbody/tr[2]/td[1]"
".//*[#id='opentwo']/div/div[2]/div[2]/div/div[2]/table/tbody/tr[2]/td[1]"
There is no <tbody> tag in your sample, and there rarely is in HTML created in the wild, especially if people created it manually. We usually see <tbody> in HTML someone grabbed from a browser's "View Source" display, which is the resulting output after their engine has mangled the HTML in an attempt to make it readable. Don't use that output. Instead, ALWAYS go straight to the source and use wget or curl and download the page and inspect it with an editor, or even use nokogiri some_url on the command-line and look at it there.
A second problem is your HTML snippet is invalid because it's full of unterminated tags. Nokogiri will do fixups on bad HTML, which can actually move nodes around, making it difficult to find nodes, especially when debugging. In this particular case Nokogiri is able to terminate them, but it's important to honor tag closures.
Here's what I'd use:
value = doc.at('span.SPBalance').text # => "$31.59"
This is using CSS which is usually much more readable than XPath. at means "find the first occurrence" and is equivalent to search('span.SPBalance').first.
The XPath equivalent would be:
doc.at('//span[#class="SPBalance"]')
doc.at('//span[#class="SPBalance"]').text # => "$31.59"
Once I have the value then it's easy to manipulate it.
value[/[\d.]+/].to_f # => 31.59
Moving on...
the third always returns a value of "0" even though it should return "$31.59" or "31.59"
'$31.58'.to_i # => 0
'$'.to_i # => 0
'31.58'.to_i # => 31
'$31.58'.to_f # => 0.0
'31.58'.to_f # => 31.58
The documentation for to_f and to_i say respectively:
Returns the result of interpreting leading characters in str as a floating point number.
and
Returns the result of interpreting leading characters in str as an integer base base (between 2 and 36).
In both cases "leading characters" is significant.
using .focus causes the error message:
undefined method `focus' for []:Nokogiri::XML::NodeSet (NoMethodError)
I assume .focus doesn't work for Nokogiri.
You could always check the NodeSet documentation, which confirms that focus is not a method.
How do I interact with a file_field thats hidden by its parent?
<span class="btn button-large fileinput-button">
Select files...
<input accept="image/png, image/gif, image/jpeg" id="gallery_files" multiple="multiple" name="gallery_files" type="file">
</span>
The button overlays the input, therefore it's not visible.
Edit
For the record, here's my code:
data[:photos].each do |photo|
$browser.file_field.set photo
end
and the error: Watir::Wait::TimeoutError: timed out after 20 seconds, waiting for {:tag_name=>"input", :type=>"file"} to become present
Workable example in a Gist
I was a bit suprised, but I was able to set the file field in the sample HTML without any issue using:
browser.file_field.set('path/to/file.txt')
From the code, it looks like setting the file field only requires the input to exist. It does not require it to be visible (or present).
Given that you are getting a Watir::Wait::TimeoutError exception, I would guess that your code is actually failing before the file_field.set. As it looks like the page has the input in a dialog, I am guessing your code actually looks more like:
$browser.file_field.wait_until_present
data[:photos].each do |photo|
$browser.file_field.set photo
end
It would be the wait_until_present method that is actually throwing the exception.
Solution 1
Assuming that an explicit wait method is being called for the file field, you should be able to just remove the wait.
If you have the wait because the dialog is being loaded by Ajax, you could try waiting for a different element instead - eg the parent span.
Solution 2
If for some reason you need the file field to be present, you will need to change its CSS. In this case, the opacity:
p $browser.file_field.present?
#=> false
$browser.execute_script('arguments[0].style.opacity = "1.0";', browser.file_field)
p $browser.file_field.present?
#=> true
For my situation, this worked:
$browser.execute_script("jQuery(function($) {
$('.fileinput-button').css('visibility', 'hidden')
$('#gallery_files').css('visibility', 'visible').css('opacity', '1').css('width', '100').css('height', '50')
})")
I had to hide the parent span, then show, resize, and change the opacity of the input
I have the following HTML:
<input type="email" class="form-control" name="email" placeholder="Email">
And I am trying to retrieve this element with the following code:
b = Watir::Browser.new :chrome
b.goto('localhost:3000')
puts b.input(:name => "email").exists?
This returns false, but is most definitely true. I have also tried using b.text_field(:name => 'email').exists?, but it returns false as well.
The end goal is to change the text of the input, but I can't even locate the element right now. The page loads fine, and after loading it outputs false.
The issue is your code is actually being executed before the element is finished loading. You need to chain a few methods (namely .when_present and .exists? to ensure your element is loaded before attempting to check it's existence:
puts b.input(:name => "email").when_present.exists?
Good luck!
Is it possible to return a map of hidden links using watir? I have been trying to find some useful documentation, but have been most unsuccessful.
I need it to be generic enough to return any link thats hidden on page regardless of class, id, etc
style=display: none;
This currently returns me all visible links
full_list = #driver.links.map{|a| a.href}
i'd like to do something like (my syntax is probably way off):
hidden_list = #driver.hiddens.map{:style, a => 'display: none;'}
Please, please let me know if there is a way!
Thanks!
You could find all the links that are not visible? and collect their href attributes:
For example, given the following html:
asdf
<a style="display:none;" href="somewhere/invisible">asdf</a>
<a style="display:none;" href="somewhere/invisible2">asdf</a>
You can do:
hidden_list = #driver.links.find_all{ |a| !a.visible? }.collect(&:href)
#=> ["somewhere/invisible", "somewhere/invisible2"]