how to find the text from the multiple same attribute in watir? - watir

I want to find and click the Line Lo value from the following value. The following li had more than 100 records has same class name. How to find the Line Lo and click that value.
<div id="loc">
<ul id="tab">
<li class="criteria">
<div class="bud">
<div class="inner">
<div class="attr">Code Lo</div>
</div>
</div>
</li>
<li class="criteria">
<div class="bud">
<div class="inner">
<div class="attr">Line Lo</div>
</div>
</div>
</li>
<li class="criteria">
<div class="bud">
<div class="inner">
<div class="attr">Add Lo</div>
</div>
</div>
</li>
</ul>
</div>
When I tried, browser.ul(id: "tab").li(class: "criteria").div(text: "Line Lo")
Got the failure error.
Unable to locate element, using(:class => "Criteria", :tag_name=>"li"
I tried different, different attributes to find the Text Line Lo and I failed.
Also, This element is like 45th, so is it possible to scroll down and flash this value before click?
Thanks in advance

The original thing you tried
browser.ul(id: "tab").li(class: "criteria").div(text: "Line Lo")
failed because when there is more than one element that could match the criteria you specify, watir will use the first one it finds. So what that is asking watir to do would equate to the following in english
Find the first un-ordered list with the id 'tab',
then inside that list find the first list item with the class 'criteria',
then inside that list item, find a div with the text 'Line Lo'.
Since the first LI inside that list does not contain a div with that text, it fails.
To click the innermost div (based on comments, this is what you are trying to do)
browser.div(:class => "attr", :text => "Line Lo").click
aka Find me a div with the class "attr" AND the text "Line Lo", and then have the div click itself
these other options were presented when it was not clear exactly which div the OP was after, but I'm leaving them as they might be informative to other folks struggling with similar issues
To click the div that contains the one above (the parent).
browser.div(:class => "inner", :text => "Line Lo").click
if that parent div had no uniqueness (no class etc) you could still get to it like this
browser.div(:class => "attr", :text => "Line Lo").parent.click
To click the li tag that holds all that stuff
browser.li(:class => "criteria", :text => "Line Lo").click
For any of those, if you need to restrict to just looking inside that particular list, then specify like this
browser.ul(:id => "tab").li(:class => "criteria", :text => "Line Lo").click

You could try:
browser.ul(:id => 'tab').div(:class => 'attr', :text => 'Line Lo').click
When .click is used, it should get scrolled into view before being clicked.

Related

Get first element Xpath

I have a HTML like this :
<ol class="list">
<li class="list-item " id="37647629">
<!---->
<div>
<!---->
<div>
<!---->
<book class="book">
<div class="title">
someText
</div>
<div class="year">
2022
</div>
</book>
</div>
<!---->
</div>
<!---->
</li>
<li class="list-item " id="37647778">
<!---->
<div>
<!---->
<div>
<!---->
<book class="book">
<div class="title">
someOtherText
</div>
<div class="year">
2014
</div>
</book>
</div>
</div>
<!---->
</li>
</ol>
I want to get the first book title and year, directly with two xPath expression.
I tried :
$x('//book') => Ok, get the two books list
$x('//book[0]') => Empty list
$x('//book[0]/div[#class="title"]') => Nothing
Seems I have to do this :
$x('//book')[0]
and then process title, but why I can't do this just with Xpath and directly access the first title with a Xpath expression ?
This will give you the first book title
"(//book)[1]//div[#class='title']"
And this gives the first book year
"(//book)[1]//div[#class='year']"
You're missing that XPath indexing starts at 1; JavaScript indexing starts at 0.
$x('//book') selects all book elements in the document.
$x('//book[0]') selects nothing because XPath indexing starts at 1. (It also signifies to select all book elements that are the first among siblings — not necessarily the same as the first of all book elements in the document.)
$x('//book')[0] would select the first book element because JavaScript indexing starts at 0.
$x('(//book)[1]') would select the first book element because XPath indexing starts at 1.
To select the first div with class of 'title', all in XPath:
$x('(//div[#class="title"])[1]')
or, using JavaScript to index:
$x('(//div[#class="title"])')[0]
To return just the string value without the leading/trailing whitespace, wrap in normalize-space():
$x('normalize-space((//div[#class="title"])[1])')
Note that normalize-space() will also consolidate internal whitespace, but that is of no consequence with this example.
See also
How to select first element via XPath? (And be sure not to miss the explanation of the difference between //book[1] and (//book)[1] — they are not the same.)

xpath how to get the last value of first level of children in the case of the number of children is not always the same

With the following code:
data = driver.find_elements(By.XPATH, '//div[#class="postInfo desktop"]/span[#class="nameBlock"]')
I got those html codes below:
<span class="nameBlock">
<span class="name">Anonymous</span>
<span class="posteruid id_RDS8pJvL">(ID:
<span class="hand" title="Highlight posts by this ID" style="background-color: rgb(228, 51,
138); color: white;">RDS8pJvL</span>)</span>
<span title="United States" class="flag flag-us"></span>
</span>
And
<span class="nameBlock">
<span class="name">Pierre</span>
<span class="postertrip">!AYZrMZsavE</span>
<span class="posteruid id_y5EgihFc">(ID:
<span class="hand" title="Highlight posts by this ID"
style="background-color: rgb(136, 179, 155); color: black;">y5EgihFc</span>)</span>
<span title="Australia" class="flag flag-au"></span>
</span>
Now I need to get the "countries" => "United States" and "Australia".
With the whole dataset (more than 120k entries), I was doing:
for i in data:
country = i.find_element(By.XPATH, './/span[contains(#class,"flag")]').get_attribute('title')
But after a while I got empty entries and I figured out than sometime the class of the country was completely changing from "flag something" to "bf something" or "cd something"
This is why I decided to go with the last children for each element:
for i in data:
country = i.find_element(By.XPATH, './/span[3]').get_attribute('title')
But again, after a while I got error again because sometime there were some <span class="postertrip">BLABLA</span> popping, moving the "country" location to "span[4]".
So, I changed for the following one:
for i in data:
country = i.find_element(By.XPATH, './/span[last()]').get_attribute('title')
But this last one always give me the second level child (posteruid child):
<span class="hand" title="Highlight posts by this ID"
style="background-color: rgb(136, 179, 155); color: black;">y5EgihFc</span>)
One thing that I'm certain: the country is ALWAYS the last child (span) of the first level of children.
So I'm out of ideas this is why I'm asking you this question.
Use the following xpath to always identify the last child of parent.
(//span[#class='nameBlock']//span[#title])[last()]
Code block.
for country in driver.find_elements(By.XPATH, "(//span[#class='nameBlock']//span[#title])[last()]"):
print(country.get_attribute("title"))
For this particular case, you can get the titles without calculating the child nodes. Just keep the nameBlock as root and create the xpath to point to the child which class will have the title ( flag, in this case). Like this:
//span[#class='nameBlock']/span[contains(#class,'flag')]

How can I get texts with certain criteria in python with selenium? (texts with certain siblings)

It's really tricky one for me so I'll describe the question as detail as possible.
First, let me show you some example of html.
....
....
<div class="lawcon">
<p>
<span class="b1">
<label> No.1 </label>
</span>
</p>
<p>
"I Want to get 'No.1' label in span if the div[#class='lawcon'] has a certain <a> tags with "bb" title, and with a string of 'Law' in the text of it."
<a title="bb" class="link" onclick="javascript:blabla('12345')" href="javascript:;">Law Power</a>
</p>
</div>
<div class="lawcon">
<p>
<span class="b1">
<label> No.2 </label>
</p>
<p>
"But I don't want to get No.2 label because, although it has <a> tag with "bb" title, but it doesn't have a text of law in it"
<a title="bb" class="link" onclick="javascript:blabla('12345')" href="javascript:;">Just Power</a>
</p>
</div>
<div class="lawcon">
<p>
<span class="b1">
<label> No.3 </label>
</p>
<p>
"If there are multiple <a> tags with the right criteria in a single div, I want to get span(No.3) for each of those" <a>
<a title="bb" class="link" onclick="javascript:blabla('12345')" href="javascript:;">Lawyer</a>
<a title="bb" class="link" onclick="javascript:blabla('12345')" href="javascript:;">By the Law</a>
<a title="bb" class="link" onclick="javascript:blabla('12345')" href="javascript:;">But not this one</a>
...
...
...
So, here is the thing. I want to extract the text of (e.g. No.1) in div[#class='lawcon'] only if the div has a tag with "bb" title, with a string of 'Law' in it.
If inside of the div, if there isn't any tag with "bb" title, or string of "Law" in it, the span should not be collected.
What I tried was
div_list = [div.text for div in driver.find_elements_by_xpath('//span[following-sibling::a[#title="bb"]]')]
But the problem is, when it has multiple tag with right criteria in a single div, it only return just one div.
What I want to have is a location(: span numbers) list(or tuple) of those text of tags
So it should be like
[[No.1 - Law Power], [No.3 - Lawyer], [No.3 - By the Law]]
I'm not sure I have explained enough. Thank you for your interests and hopefully, enlighten me with your knowledge! I really appreciate it in advance.
Here is the simple python script to get your desired output.
links = driver.find_elements_by_xpath("//a[#title='bb' and contains(.,'Law')]")
linkData = []
for link in links:
currentList = []
currentList.append(link.find_element_by_xpath("./ancestor::div[#class='lawcon']//label").text + '-' + link.text)
linkData.append(currentList)
print(linkData)
Output:
[['No.1-Law Power'], ['No.3-Lawyer'], ['No.3-By the Law']]
I am not sure why you want the output in that format. I would prefer the below approach, so that you will get to know how many divs have the matching links and then you can access the links from the output based on the divs. Just a thought.
divs = driver.find_elements_by_xpath("//a[#title='bb' and contains(.,'Law')]//ancestor::div[#class='lawcon']")
linkData = []
for div in divs:
currentList = []
for link in div.find_elements_by_xpath(".//a[#title='bb' and contains(.,'Law')]"):
currentList.append(div.find_element_by_xpath(".//label").text + '-' + link.text)
linkData.append(currentList)
print(linkData)
Output:
[['No.1-Law Power'], ['No.3-Lawyer', 'No.3-By the Law']]
As your requirement is to extract the texts No.1 and so on, which are within a <label> tag, you have to induce WebDriverWait for the visibility_of_all_elements_located() and you will have only 2 matches (against your expectation of 3) and you can use the following Locator Strategy:
Using XPATH:
print([my_elem.get_attribute("innerHTML") for my_elem in WebDriverWait(driver, 5).until(EC.visibility_of_all_elements_located((By.XPATH, "//div[#class='lawcon']//a[#title='bb' and contains(.,'Law')]//preceding::label[1]")))])

How to use aria-attribute (aria-labelledby) for combo box (input+autocomplete list) correctly?

How can I use the aria-attribute aria-labelledby for combo box (input+autocomplete list) correctly?
According to the W3C, the aria-labelledby property provides the user with a recognizable name of the object.
I've found the following example on W3C:
<div class="combobox-wrapper">
<div>
<input type="text"
aria-labelledby="ex1-label">
</div>
<ul aria-labelledby="ex1-label"></ul>
</div>
But I've noticed that aria-labelledby isn't descriptive. Values in aria-labelledby for different element are used the same.
Maybe I can use aria-labelledby like this:
<div class="combobox-wrapper">
<div>
<input type="text"
aria-labelledby="textBox">
</div>
<ul aria-labelledby="autocomplete-list"></ul>
</div>
The WAI ARIA attribute aria-labelledby is used when you can't use the normal <input> + <label> combination to label a form element, e.g. because you are using a custom form element. In other words, it is used in situations where you can't use the <label>'s for attribute to define a label for the input (e.g.
<input id="communitymode" name="communitymode" type="checkbox"> <label for="communitymode">communiti wiki</label>; note that the for attribute's value refers to the input's id attribute.)
With aria-labelledby, your reference works in the opposite direction as the for attibute: you tell the browser or the screen reader where to find the "label" for the form control it has just encountered.
<div class="combobox-wrapper">
<div>
<span id="combolabel">Select your country:</span>
<input type="text"
aria-labelledby="combolabel">
</div>
<ul aria-labelledby="combolabel"></ul>
</div>
In the above code sample, both the <input> element and the <ul> element are labelled by the <span> element with id "combolabel".
Remember the first rule of ARIA is don't use ARIA when native HTML elements exist. If you are trying to create an accessible autocomplete box try this:
http://wet-boew.github.io/v4.0-ci/demos/datalist/datalist-en.html
It does not use ARIA and follows all applicable W3C rules and guidelines.

How can I get de "id" of an "div" element that has a text in <span>

I have the following situation:
- a div (that is a button) with an "id" that is always changing;
- this div has a inside with a text. Please see below the sample code;
<div id="00NU0000002si99" class="item unused">
<span>Button Name</span>
</div>
The only way to for my next scenarios to work is to get the "id" for the div.
I have tried this:
#browser.div(:text, "Button Name").id
It seems that I am doing something wrong because the line above is returning nothing.
Is there a way to get value for this tag?
What is your 'next scenario'? What are you trying to do?
Given that exact HTML you can address that specific div in one of at least two ways
browser.div(:class => 'item unused', :text => 'Button Name')
or
browser.span(:text => 'Button Name').parent
See also the SO questions and their answers, which I linked in my comment above.
If for some strange reason you really need to get the ID (I'm having a difficult time imagining why ) then try
browser.div(:class => 'item unused', :text => 'Button Name').attribute_value('id')

Resources