How does Ruby webdriver get element with hyphen in <name, value> pair - ruby

I have HTML like below and I want to get the element by "sku-code" (there is a hyphen in it)
<div class="leavemessagebox" style="position: relative;" sku-code="m_showcase">
When I used
browser.div(:sku-code=>'m_showcase')
Ruby reported an error
ERROR:undefined local variable or method `code' for #<AUT::WebClient:0x2c59650>
It sames Ruby can't recognize "sku-code" as a name, anyone can give me any suggestion about how to get the element by "sku-code"?
Sorry for not explain myself clearly. There are many elements that have sku-code selector and I want to collect them all in a list, so the class name and tag name isn't stable. How can I do that

Looks like Watir WebDriver. Try use css (preferred) or xpath.
browser.element(:css => "[sku-code='m_showcase']") # single one
browser.elements(:css => "[sku-code='m_showcase']") # a list of all matches
Documention on CSS's attribute selector is here.
So basically the above selector finds all elements with attribute sku-code equals to m_showcase.

:sku-code is not a valid symbol literal. It is however valid Ruby code, it is parsed as:
:sku - code
So, you are trying to call a method named code and subtracting its return value from the symbol :sku, which obviously doesn't make sense.
But you can quote the symbol literal, of course:
:'sku-code'

browser.element(:css => ".leavemessagebox[sku-code='m_showcase']") I think this will still the specific CSS.
Please Let me know is the above CSS is working or Not.

Related

Minitest/Capybara Dropdown Menu Locator Problem

I tried to choose four dropdown menu respectively like this:
find(:xpath, '//select[#id = 'homework_filter_program_id']/option[1]').click_on
find(:xpath, '//select[#id = 'homework_filter_lesson_id']/option[1]').select_option
find(:xpath, '//select[#id = 'homework_filter_unit_id']/option[1]').select_option
find(:xpath, '//select[#id = 'homework_filter_difficulty_level_id']/option[1]').select_option
I tried both click_on and select_option to choose.
I derived the locators from this segment.
enter image description here
But I get these errors:
in block in require
in load_dependency
in <top (required)>
What is the wrong thing in my XPath, it doesn't select. I am newbie in Minitest and Ruby.
I am also open suggestions regarding CSS selectors for dropdown menu.
As Paul points out your quotes are invalid, but beyond that there's no need for XPath here. CSS is easier to read and is generally more efficient for locating elements when it can be used. Something along the lines of
find('select#homework_filter_lesson_id option:first-child').select_option
should work (assuming the select is actually visible on the page). Even easier yet would be to just do
select('text of the first option', from: 'homework_filter_lesson_id')
It seems there's an issue with building the xpath locator string (which you're passing as a second argument to find).
'//select[#id = 'homework_filter_program_id']/option[1]'
This isn't valid ruby code.
What you're looking for is string interpolation. There are two common ways of doing that:
"//select[#id = #{homework_filter_program_id}]/option[1]"
Notice the #{} expression inside the string. The result of the expression is interpolated. Keep in mind that this only works with doubly quoted strings ("like this one")
Or use the string formatting:
'//select[#id = %s]/option[1]' % homework_filter_program_id
Notice the %s placeholder. This works very much like sprintf.
Upd: for some reason as I've read your example I figured you had the local variables called homework_filter_program_id etc. Now that I've also looked at the screenshot I realized that homework_filter_program_id is actually the id of an element you want to find.
Your code will most likely work once you drop the unnecessary quotation marks:
'//select[#id=homework_filter_program_id]/option[1]'
But you probably still want to parameterize this locator, and that's when the interpolation advice will come handy.

Capybara / Ruby - Trying to get only the Text from all ambiguous css selector and convert it to string

I'm trying to get all Texts from a specific CSS Selector that are ambiguous in the HTML. I would like to access these ambiguous css and get the Text and then return all that info.
I've figured out how to find all ambiguous selectors but I dont know how to get just the text from each selector.
The ambiguous selector is (it finds 3 matchers)
.list-card-title .js-card-name
I've already tried commands like:
arr = Array(3)
arr = find_all('.list-card-title.js-card-name').to_a
puts arr.to_s
When I use puts arr
I got the following output
[#<Capybara::Node::Element tag="span" path="/HTML/BODY[1]/DIV[2]/DIV[2]/DIV[1]/DIV[2]/DIV[3]/DIV[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[2]/A[1]/DIV[3]/SPAN[1]">, #<Capybara::Node::Element tag="span" path="/HTML/BODY[1]/DIV[2]/DIV[2]/DIV[1]/DIV[2]/DIV[3]/DIV[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[2]/A[2]/DIV[3]/SPAN[1]">, #<Capybara::Node::Element tag="span" path="/HTML/BODY[1]/DIV[2]/DIV[2]/DIV[1]/DIV[2]/DIV[3]/DIV[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[2]/A[3]/DIV[3]/SPAN[1]">]
To get the text of elements you need to call text on each of the elements. In your case the easiest way to do that would be
find_all('.list-card-title.js-card-name').map(&:text)
which will return an array of the text contained in each of the elements. If you then want all of that concatenated into one string you could do
find_all('.list-card-title.js-card-name').map(&:text).join
Note: you have tagged your questions with automated-tests, are you actually testing an app/site, or are you instead doing web scraping? If you are testing you'd be much better off writing your tests using Capybaras expectation/assertion methods (and the :text options they accept) rather than finding elements, extracting/manipulating contained text and then doing something (I assume asserting on) with that.

Xpath to find an element using partial class name and exact text with capybara in ruby

I have html code with div having same matching text in class name as menu1 and text like
Berlin
and
Berlin Germany
for which when i use below code returns ambiguous elements
find(:xpath, "//div[contains(text(), \"Berlin\") and contains(#class, \"menu1\")]")
Note: I want both class and text to be in my xpath
Suggestions will be appreciated, thanks in advance.
If by partial class name you mean something like <div class="blah menu1 other">Berlin</div> then you could just do it in a readable way with something like
find('div.menu1', exact_text: 'Berlin')
or
find('div.menu1', text: 'Berlin', exact: true)
If it's more like <div class="blah menu1_part other">Berlin</div> you can still do it with a more readable CSS selector like
find('div[class*=menu1]', exact_text: 'Berlin')
If you actually need to do it all in one XPath for performance reasons (a LOT of div.menu1 elements on the page, where you can't scope to a limited section of the page for some crazy reason) then you could do something like
find(:xpath, './/div[text()="Berlin"][contains(#class, "menu1")]')
Note the leading . in the XPath expression. 99.9% of the time when using Capybara, and manually writing your own XPath expressions, you want to start your XPath expressions with .//, otherwise you are defeating any scoping you have done - see https://github.com/teamcapybara/capybara#beware-the-xpath--trap
Another option is to use the xpath gem Capybara uses internally for generating XPaths, which would be something like
find(:xpath, XPath.css('div.menu1')[XPath.string.n.is('Berlin')], exact: true)
or
find(:xpath, XPath.css('div[class *= "menu1"]')[XPath.string.n.is('Berlin')], exact: true)
depending on exactly what you mean by partial class name. The benefit of doing something like that is the meaning of the is method can be changed from contains to equals depending on the value of the exact option, and it also handles all the normalizing and escaping of strings as necessary if your strings weren't as simple as 'Berlin'

getting attribute via xpath query succesfull in browser, but not in Robot Framework

I have a certain XPATH-query which I use to get the height from a certain HTML-element which returns me perfectly the desired value when I execute it in Chrome via the XPath Helper-plugin.
//*/div[#class="BarChart"]/*[name()="svg"]/*[name()="svg"]/*[name()="g"]/*[name()="rect" and #class="bar bar1"]/#height
However, when I use the same query via the Get Element Attribute-keyword in the Robot Framework
Get Element Attribute//*/div[#class="BarChart"]/*[name()="svg"]/*[name()="svg"]/*[name()="g"]/*[name()="rect" and #class="bar bar1"]/#height
... then I got an InvalidSelectorException about this XPATH.
InvalidSelectorException: Message: u'invalid selector: Unable to locate an
element with the xpath expression `//*/div[#class="BarChart"]/*[name()="svg"]/*
[name()="svg"]/*[name()="g"]/*[name()="rect" and #class="bar bar1"]/`
So, the Robot Framework or Selenium removed the #-sign and everything after it. I thought it was an escape -problem and added and removed some slashes before the #height, but unsuccessful. I also tried to encapsulate the result of this query in the string()-command but this was also unsuccessful.
Does somebody has an idea to prevent my XPATH-query from getting broken?
It looks like you can't include the attribute axis in the XPath itself when you're using Robot. You need to retrieve the element by XPath, and then specify the attribute name outside that. It seems like the syntax is something like this:
Get Element Attribute xpath=(//*/div[#class="BarChart"]/*[name()="svg"]/*[name()="svg"]/*[name()="g"]/*[name()="rect" and #class="bar bar1"])#height
or perhaps (I've never used Robot):
Get Element Attribute xpath=(//*/div[#class="BarChart"]/*[name()="svg"]/*[name()="svg"]/*[name()="g"]/*[name()="rect" and #class="bar bar1"])[1]#height
This documentation says
attribute_locator consists of element locator followed by an # sign and attribute name, for example "element_id#class".
so I think what I've posted above is on the right track.
You are correct in your observation that the keyword seems to removes everything after the final #. More correctly, it uses the # to separate the element locator from the attribute name, and does this by splitting the string at that final # character.
No amount of escaping will solve the problem as the code isn't doing any parsing at this point. This is the exact code (as of this writing...) that performs that operation:
def _parse_attribute_locator(self, attribute_locator):
parts = attribute_locator.rpartition('#')
...
The simple solution is to drop that trailing slash, so your xpath will look like this:
//*/div[#class="BarChart"]/... and #class="bar bar1"]#height`

Watir-webdriver, can't "cast" an element to subtype even though it's found

I'm having a really strange issue with watir-webdriver.
Here's a snapshot of the input tag I'm trying to reach (couldn't figure out a way to get the source after the javascripts created the popup, lol)
Anyway here's some of my code that uses xpath to locate these elements (there is two text fields and a select tag)
firstname = b.element(:xpath, "//div[#class='ap_popover']/input[#name='firstName']")
lastname = b.element(:xpath, "//div[#class='ap_popover']/input[#name='lastName']")
authorselector = b.element(:xpath, "//div[#class='ap_popover']/select")
puts firstname
puts lastname
puts authorselector
This code successfully returns the watir element objects. However when I try to cast them:
puts firstname.to_subtype
it freaks out:
C:/Ruby192/lib/ruby/gems/1.9.1/gems/watir-webdriver-0.4.1/lib/watir-webdriver/elements/element.rb:262:in
`assert_exists': unable to locate element, using
{:xpath=>"//div[#class='ap_popover']/input[#name='lastName']"}
(Watir::Exception::UnknownObjectException)
So, what's going on? It can find them via xpath no problem but then when I try to cast them all of a sudden xpath search fails?
It's worth mentioning the html I'm perusing through is created in it's entirety by javascript, hence why I couldn't just copy\paste it here and had to take a screenshot.
Thanks!
xpath is evil avoid it if at all possible. it's too easy to make mistakes, hard to read, and generally slower.
Have you tried something like
b.div(:id => 'contributors-table').textfield(:name => 'firstName')
If you have some wacky invalid HTML where they have two copies of all this stuff (and thus duplicated ID values which is not valid for HTML standard) then you can add in the INDEX of the element, which in this case might be needed both for the div container, and then maybe also for the input field if there are more than one of them.
b.divs(id => 'contributors-table').size #how many are there?
#example, second instance of the contributors table, third instance in that table of an text input field with the name 'firstName'
b.div(:id => 'contributors-table', :index => 1).textfield(:name => 'firstName', :index => 2)

Resources