I have a button
<button type="button" id="saveListing" class="button small save-button" data-bind="enable: saveEnabled, click: save"><i class="fa fa-save"></i> Save</button>
located in the tr of a table.
I wrote a function for testing the button status, simply using:
And(/^...the button "(.*?)" on "(.*?)" page is disabled$/) do |button_name, page|
button_id = ConfigHelper.get_button_info(button_name, page)['id']
button_class = ConfigHelper.get_button_info(button_name, page)['class']
if !button_id.nil?
find_button(button_id)[:disabled].should eq 'true'
elsif !button_class.nil?
find(button_class)[:disabled].should eq 'true'
else
button_text = ConfigHelper.get_button_info(button_name, page)['text']
find('button', text: button_text)[:disabled].should eq "true"
end
end
However, this block does not work for a button in a table row. I also tried add checking by button id, and it also did not work. How can I implement it without taking table id as a parameter? (As I don't want to write table id inside the feature)
When using id, the error is:
Capybara::ElementNotFound: Unable to find css ".saveListing"
or using text:
Ambiguous match, found 4 elements matching css "button" (Capybara::Ambiguous)
Thanks.
Capybaras find_button doesn't search css classes at all so unless you have overwritten find_button I'm not sure why you would be getting that error from using it with an id. find_button will search by the id, value, text content, or title attribute of the button, and also supports a disabled filter for searching. More stable (if the status of the button is changing due to JS) versions of those checks would be
find_button('saveListing', disabled: true).should be #Note: no # in front of the id here since its not a css selector
find_button('button text', disabled: true).should be
These would be more stable because they will utilize Capybaras waiting behavior to find the disabled button, whereas the way they were written previously would immediately find the button and error if they weren't yet disabled.
saveListing is the id of your button, not a class. In css selectors, dot is used for classes and hash sign is used for ids.
Therefore, you should either be using #saveListing or .save-button, but not .saveListing. This is why your first matching fails.
As to why the second one does - I guess there are 4 rows, each with one button and Capybara doesn't know which one you are referring to. If you want to check this condition for all of them, you could use all instead of find like this:
all('button', text: button_text).each do |button|
button[:disabled].should eq "true"
end
Related
Given I have the following HTML structure:
<button aria-labelledby="ref-1" id="foo" onclick="convey(event)">action 2</button>
<div class="anotherElement">foobar</div>
<div id="ref-1" hidden>target 2</div>
I would like to fetch button by its aria-labelledby attribute. I tried the following options:
//*[#aria-labelledby=string(/div[#id="ref-1"]/#id)]
//*[#aria-labelledby = string(.//*[normalize-space() = "target 2"]/#id)]
//*[#aria-labelledby = .//*[normalize-space() = "target 2"]/#id]
But wasn't able to fetch the element. Anyone has an idea what the right xPath could be?
Edit: simply put: how do I fetch the button element if my only information is "target 2", and if both elements can be randomly located?
//button[#aria-labelledby='ref-1']
or
//button[#aria-labelledby=(//*/#id)]
or
//button[#aria-labelledby=(//*[contains(.,'target 2')]/#id)]
or
//button[#aria-labelledby=(//*[contains(text(),'target 2')]/#id)]
?
Since button and div are the same level siblings here you can use preceding-sibling XPath expression like this:
//div[text()='target 2']//preceding-sibling::button
pay attention with with your actual XML this will match 2 button elements.
To make more precise math I think we will need to be based on more details, not only the target 2 text
I have a button that is disabled and it has also a child element. Now I need to skip the disabled button as well as its child element when the button is disabled but the following selector is only skipping the button element but not its child elements. So when the parent element that button is disabled along with that the child elements should be skipped for my case. The following selector should work for both the buttons below.
Selector:
//*[(#type='button' or #type='submit' or contains(#class,'btn') or contains(#class,'Button')) and (#data-nw-id='Add Time' or text()='Add Time' or #value='Add Time' or #title='Add Time' or .//*='Add Time') and not(#disabled)]
Button with child elements:
<button disabled type="button" data-nw-id="Add Time" data-nw-node="ButtonTag" data-nw-file="Button">
<span data-nw-node="span" data-nw-file="Button">Add Time</span>
</button>
Button Only:
<button disabled type="button" data-nw-id="Add Time" data-nw-node="ButtonTag" data-nw-file="Button">
Add Time
</button>
Can anyone help me, please?
Thanks in advance!!
XPath is rather used to select elements not skip them.
Regarding your long XPath expression, you want to select 'enabled' element which contains specific attributes. Child of these elements must be unselected.
Your XPath expression works correctly since the button nor the child element of the button are selected.
You can use an axis predicate to build a "safer" XPath (I've cleaned the expression a bit) :
//*[not(parent::*[#disabled]) and not(#disabled)][#type='button' or #type='submit' or contains(#class,'btn') or contains(#class,'Button')][#*='Add Time' or text()='Add Time']
This will select only "enabled" elements with specific attributes and for which their parent not contain a #disabled attribute.
I really love this answer and grab it from the last comment of #E.Wiest. I wish to post it for other people.
//*[#type='button' or #type='submit' or contains(#class,'btn') or contains(#class,'Button')][#data-nw-id='Add Time' or text()='Add Time' or #value='Add Time' or #title='Add Time' or .//*[.="Add Time"]][not(#disabled)]
Or
//*/descendant-or-self::*[#type='button' or #type='submit' or contains(#class,'btn') or contains(#class,'Button')][#data-nw-id='Add Time' or text()='Add Time' or #value='Add Time' or #title='Add Time'][not(#disabled)]
I need to click this input only if it has "aria-checked ="true"
<input class="mat-checkbox-input cdk-visually-hidden" type="checkbox" id="mat-checkbox-131-input" tabindex="0" aria-checked="true" style="" xpath="1">
Ruby:
aria_checked = true
if aria_checked = true
impressora_etiqueta = "//mat-checkbox[#id='mat-checkbox-23']/label/div"
page.find(:xpath, impressora_etiqueta).click
end
There are many ways to do what you want - simplest is probably
page.first('#mat-checkbox-23[aria-checked="true"]', minimum: 0)&.click
which will look for the first element with the given id and aria-checked = "true" and click it if one exists.
Note: the id in your test and sample HTML didn't match so I went the id from your test, adjust as needed. Also you have a class of cdk-visually-hidden shown -- If that's actually making the element not visible on the page, then this won't work and you'll need to add more surrounding HTML to your question with a better description of exactly what you're trying to do (you can't click on non-visible elements)
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
The issue I'm dealing with, and unable to solve due to my ignorance, is that I have a page on a browser with different education requirements which each have four radio buttons. I want to be able to select a certain radio button for each education requirement.
The code I have is like this:
radios = browser.radios
radios.each do |radio|
radio.input(:value => "very").set
end
However this keep giving me an error message saying: "undefined method 'set' for #Watir::Input:0x103a5d508"
I did something similar for select_lists where I changed the option of all select_lists on a page to the 2nd option which worked:
lists = browser.select_lists
lists.each do |list|
list.option(:index, 1).select
end
For my code for the radio buttons I tried using radio.option but it gave me a similar error: "NoMethodError: undefined method `set' for #Watir::Option:0x103a466a0"
Problem
The code
radios = browser.radios
radios.each do |radio|
radio.input(:value => "very").set
end
Says that for each radio button on the page, set the first input element with value "very". This suggests that you are looking for html like:
<radio>
<input value="very" />
</radio>
This is probably not what the html looks like.
Solution
I assume the html you really want to set is like:
<input type="radio" value="very" />
To set each radio button with value "very", the code should be:
# Get a collection of all radios with value "very"
radios = browser.radios(:value => "very")
# Iterate through each radio button in the collection and set it
radios.each do |radio|
radio.set
end
This can be shortened to simply:
browser.radios(:value => "very").each(&:set)