Can't access an element by a data- attribute with an underscore - watir

Good day everyone!
I have an element
<tbody class="cp-ads-list__table-item _sas-offers-table__item cp-ads-list__table- item_state-deposit" data-card_id="16676514">
I'd like to access it by the data-card_id tag, but when I try the following
#browser.tbody(:data_card_id => "16676514").hover
I get an error
unable to locate element, using {:data_card_id=>"16676514", :tag_name=>"tbody"} (Watir::Exception::UnknownObjectException)
I guess my code would have worked if the tag were "data-card-id", but it's "data-card_id".
How do I access my element by this attribute?

Problem
You are right that the problem is the underscore in the data attribute. As seen in the ElementLocator, when building the XPath expression, all underscores are converted to dashes (in the else part of the statement):
def lhs_for(key)
case key
when :text, 'text'
'normalize-space()'
when :href
# TODO: change this behaviour?
'normalize-space(#href)'
when :type
# type attributes can be upper case - downcase them
# https://github.com/watir/watir-webdriver/issues/72
XpathSupport.downcase('#type')
else
"##{key.to_s.gsub("_", "-")}"
end
end
Solution - One-Off
If this is the only data attribute that is using underscores (rather than dashes), I would probably manually build the XPath or CSS expression.
#browser.tbody(:css => '[data-card_id="16676514"]').hover
Solution - Monkey Patch
If using underscores is a standard on the website, I would probably consider monkey patching the lhs_for method. You could monkey patch the method so that you only change the first underscore for data attributes:
module Watir
class ElementLocator
def lhs_for(key)
puts 'hi'
case key
when :text, 'text'
'normalize-space()'
when :href
# TODO: change this behaviour?
'normalize-space(#href)'
when :type
# type attributes can be upper case - downcase them
# https://github.com/watir/watir-webdriver/issues/72
XpathSupport.downcase('#type')
else
if key.to_s.start_with?('data')
"##{key.to_s.sub("_", "-")}"
else
"##{key.to_s.gsub("_", "-")}"
end
end
end
end
end
This would then allow your original code to work:
#browser.tbody(:data_card_id => "16676514").hover

Related

Can't acess dynamic element on webpage

I can't acess a textbox on a webpage box , it's a dynamic element. I've tried to filter it by many attributes on the xpath but it seems that the number that changes on the id and name is the only unique part of the element's xpath. All the filters I try show at least 3 element. I've been trying for 2 days, really need some help here.
from selenium import webdriver
def click_btn(submit_xpath): #clicks on button
submit_box = driver.find_element_by_xpath(submit_xpath)
submit_box.click()
driver.implicitly_wait(7)
return
#sends text to text box
def send_text_to_box(box_xpath, text):
box = driver.find_element_by_xpath(box_xpath)
box.send_keys(text)
driver.implicitly_wait(3)
return
descr = 'Can't send this text'
send_text_to_box('//*[#id="textfield-1285-inputEl"]', descr)' #the number
#here is the changeable part on the xpath
:
edit: it worked now with the following xpath //input[contains(#id, 'textfield') and contains(#aria-readonly, 'false') and contains (#class, 'x-form-invalid-field-default')] . Hopefully I found something specific on this element:
You can use partial string to find the element instead of an exact match. That is, in place of
send_text_to_box('//*[#id="textfield-1285-inputEl"]', descr)' please try send_text_to_box('//*[contains(#id,"inputEl")]', descr)'
In case if there are multiple elements that have string 'inputE1' in id, you should look for something else that remains constant(some other property may be). Else, try finding some other element and then traverse to the required input.

Python tkinter, label has no attribute 'count'

I have an error message saying:
sentences = text.count(".") + text.count("!") + text.count("?")
AttributeError: 'Label' object has no attribute 'count'
What can I use instead of .count to count items?
Accessing the Label object directly will not get the text of it. You'll need to do text["text"].count or text.cget("text").count. Either will extract the actual text from the label. Tkinter objects can be treated as dictionaries, where the lookup keys are their attributes. cget stands for "configuration get" and allows you to more safely retrieve the attributes.
This occurs because the object text does not implement the function count().
Assuming that the text object you are referring to is a tkinter Label, you need to call count() like this:
text["text"].count(string)
So the line you posted would look like this instead:
sentences = text["text"].count(".") + text["text"].count("!") + text["text"].count("?")
Also, I would recommend you change the variable name text to something else that is more descriptive to avoid confusion in the future.

Is it possible to find elements in SitePrism fields with case insensitive values?

I've got the following element texts in two labels of an object I need to find with SitePrism:
'Correct date'
'correct date'
Is it possible to find such an element by using a case insensitive expression in SitePrism? I was trying the following but I'm getting an error:
element :correct_date, :field, "/correct date/i"
ERROR: Unable to find field "/correct date/i" (Capybara::ElementNotFound)
The expression above even fails when it's an exact match, for whatever reason :S
Any idea what I could try?
Short answer : No
Longer answer: The Capybara locators (which is what the string associated with the selector type of :field is) are strings and do not accept regexps. This is because they are implemented via XPath and most (if not all) browsers only support XPath 1.0 so there is no regex support there. Most selector types do support the :text option but that is applied to the result element, not to any other elements that may be used to find the result element (in this case it would apply to the field element not the label). So if you were looking for the actual label elements you could do
element :correct_date_label, :label, nil, text: /correct_date/i
One potential way to get what you want is to just use an xpath selector, either using the current Capybara selectors to help or writing your own custom XPath that would match both case versions, something along the lines of
element :correct_date, :xpath, (Capybara::Selector.all[:field].call('Correct date') + Capybara::Selector.all[:field].call('correct date')).to_s # might need to be | instead of +
or
element :correct_date, :xpath, ".//*[self::input | self::textarea | self::select][not(./#type = 'submit' or ./#type = 'image' or ./#type = 'hidden')][./#id = //label[(contains(normalize-space(string(.)), 'Correct date') or contains(normalize-space(string(.)), 'correct date'))]/#for] | .//label[(contains(normalize-space(string(.)), 'Correct date') or contains(normalize-space(string(.)), 'correct date'))]//.//*[self::input | self::textarea | self::select][not(./#type = 'submit' or ./#type = 'image' or ./#type = 'hidden')]"
but that's starting to get a bit complicated

Watir cycle through class elements

How can I cycle through all the elements?
I'm looking for something like:
brower.text_field[0](:name, "asdf").click # get the first element
brower.text_field[1](:name, "asdf").click # get the second element
Is there a good documentation for more advanced stuff?
I haven't found anything useful all I got is simple stuff but I'm looking for something where I can chain elements like:
browser.tr(:id, "asdf").td.click
Thank you for your time.
For what you have described you can simply use the :index attribute:
brower.text_field(:name => "asdf", :index => 0).click # get the first element
brower.text_field(:name => "asdf", :index => 1).click # get the second element
Or loop over all text_fields with attribute :name => "asdf":
browser.text_fields(:name => "asdf").each { |elem| elem.click }
To cycle through all matching elements, you are looking for "element collections".
Basically you need to pluralize the method used to get elements and then you can use the [] to get a specific index:
brower.text_fields(:name, "asdf")[0].click # get the first element
brower.text_fields(:name, "asdf")[1].click # get the second element
The element collection includes Enumerable, so there are also a variety of methods for iterating.
In terms of documentation, you can look into:
The Ruby Docs - http://rubydoc.info/gems/watir-webdriver
There are also a couple of in progress books on Leanpub if you search for Watir - https://leanpub.com/book_search?search=watir

In Watir, how to get the full text, from a portion of text?

I have a portion of HTML that looks similar to:
<table><tbody><tr>
<td><div> Text Goes Here </div></td>
<td> ... rest of table
There are no IDs, no Titles, no descriptors of any kind to easily identify the div that contains the text.
When an error occurs on the page, the error is inserted into the location where "Text Goes Here" is at (no text is present unless an error occurs). Each error contains the word "valid".
Examples: "The form must contain a valid name" or "Invalid date range selected"
I currently have the Watir code looking like this:
if browser.frame(:index => 0).text.includes? "valid"
msg = # need to get full text of message
return msg
else
return true
end
Is there any way to get the full text in a situation like this?
Basically: return the full text of the element that contains the text "valid" ?
Using: Watir 2.0.4 , Webdriver 0.4.1
Given the structure you provided, since divs are so often used I would be inclined to look for the table cell using a regular expression as Dave shows in his answer. Unless you have a lot of nested tables, it is more likely to return just the text you want.
Also if 'valid' may appear elsewhere then you might want to provide a slightly larger sample of the text to look for
. browser(:cell => /valid/).text
Try this
return browser.div(:text => /valid/).text
or
return browser.table.div(:text => /valid/).text
if the valid is not found, it should return nil.

Resources