xpath for accessing JS generated class instance - xpath

I need to use xpath as a locator for Selenium Webdriver to click on a button. It is in a dialog that is dynamically generated. Firebug/firepath gives div references with numbers that will change. I've read a lot of great tips here and am close but can't seem to get the exact specification. I need xpath to access Close and Cancel:
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
<button class="ui-button ui-widget ui-state-default ui-corner-all
ui-button-text-only ui-state-hover" type="button" role="button" aria-disabled="false">
<span class="ui-button-text">Close</span>
</button>
<button class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"
type="button" role="button" aria-disabled="false">
<span class="ui-button-text">Create</span>
No success with either:
xpath="//*[#class='ui-button-text' and #value='Close'")
xpath="//span[contains(#class='ui-button-text' and #value='Close')]")

Use:
//span[#class = 'ui-button-text' and . = 'Close']
These selects all span elements in the XML document, with string value "Close" and the string value of whose class attribute is "ui-button-text" .

Use #Dimitre's expression:
//span[#class='ui-button-text' and .='Close']
I'm answering to explain where you went wrong with your original expressions.
First expression:
//*[#class='ui-button-text' and #value='Close'")]
This selects all elements (*) anywhere in the document (//) that have an attribute (#) named class whose value is ui-button-text and an attribute named value whose value is Close. The attribute axis specifier is the # symbol. This is short for attribute::. The following expressions are equivalent:
//*[#class='ui-button-text' and #value='Close'")]
//*[attribute::class='ui-button-text' and attribute::value='Close'")]
The above expressions can be fully expanded to:
/descendant-or-self::node()/child::*[attribute::class='ui-button-text' and
attribute::value='Close'")]
In short: try to understand XPath's syntactic abbreviations when constructing expressions.
Second expression:
//span[contains(#class='ui-button-text' and #value='Close')]
The XPath contains function has the following signature:
boolean contains(string, string)
...and is described in the spec like this:
The contains function returns true if the first argument string
contains the second argument string, and otherwise returns false.
You seem to be trying to treat it as a more general, magic function that checks whether the element "contains" some attributes, but it is fundamentally a string function.
I recommend a quick (or not so quick) read of the XPath 1.0 Recommendation:
http://www.w3.org/TR/xpath/
Until then you'll just be guessing.

Related

:class not adding class

So I have this button, and x-data for a parent div had this: days: {SUN: false}. It finds the variable so no problem. Here is the button:
<button type="button" class="date-btn" #click="days[$el.textContent] = !days[$el.textContent]; alert(days[$el.textContent])" :class="{'bg-blue-800' ? days[$el.textContent]: ''}">SUN</button>
The alert shows that the value switches from true and false on click, but the color of the button never changes.
Here is my .date-btn styles, but even stripping it dry except of m and px gives the same result:
#layer components {
.date-btn {
#apply m-2 px-7 py-3 text-white font-medium text-sm leading-snug
uppercase rounded shadow-md hover:shadow-lg
focus:shadow-lg focus:outline-none focus:ring-0
active:shadow-lg transition duration-150 ease-in-out drop-shadow-lg;
}
}
I also did :class="'bg-blue-800' ? days[$el.textContent]: ''" but no banana.
What is wrong with my :class call?
The input of the ternary operator should be a condition:
:class="days[$el.textContent] ? 'bg-blue-800' : ''"
You can also use a shorthand version:
:class="days[$el.textContent] && 'bg-blue-800'"
I'd have a read in the docs. x-bind, what is what you're using when using the shorthand :class, uses booleans to define what class to bind. As per example you've given, you've mixed up the ternary statement and object syntax. This won't ever result in anything. However, say you hadn't wrapped it in an object, then it would still fail, since 'bg-blue-800' is always truthy, which will always bind '' as class.
If you want to use a ternary statement, instead you'd have to write it as such: :class="days[$el.textContent] ? 'bg-blue-800' : ''".
Because AlpineJS aims at making life easy, you can also use shorthand syntax to replace the ternary operator. In this case, you could rewrite above example to :class="days[$el.textContent] && 'bg-blue-800'"
The last method you already mention in your own answer. The object class: boolean syntax, which (in my opinion) is the most logical way to write the x-bind is the last possible method to bind elements with. By passing a class as key, and a boolean as value, AlpineJS knows which class(es) to bind.
Ok for some reason this worked :class="{'bg-blue-800' : days[$el.textContent]}"
Shoutout to https://daily.dev/blog/alpine-js-the-ultimate-guide

xPath - Why is this exact text selector not working with the data test id?

I have a block of code like so:
<ul class="open-menu">
<span>
<li data-testid="menu-item" class="menu-item option">
<svg>...</svg>
<div>
<strong>Text Here</strong>
<small>...</small>
</div>
</li>
<li data-testid="menu-item" class="menu-item option">
<svg>...</svg>
<div>
<strong>Text</strong>
<small>...</small>
</div>
</li>
</span>
</ul>
I'm trying to select a menu item based on exact text like so in the dev tools:
$x('.//*[contains(#data-testid, "menu-item") and normalize-space() = "Text"]');
But this doesn't seem to be selecting the element. However, when I do:
$x('.//*[contains(#data-testid, "menu-item")]');
I can see both of the menu items.
UPDATE:
It seems that this works:
$x('.//*[contains(#class, "menu-item") and normalize-space() = "Text"]');
Not sure why using a class in this context works and not a data-testid. How can I get my xpath selector to work with my data-testid?
Why is this exact text selector not working
The fact that both li elements are matched by the XPath expression
if omitting the condition normalize-space() = "Text" is a clue.
normalize-space() returns ... Text Here ... for the first li
in the posted XML and ... Text ... for the second (or some other
content in place of ... from div/svg or div/small) causing
normalize-space() = "Text" to fail.
In an update you say the same condition succeeds. This has nothing to
do with using #class instead of #data-testid; it must be triggered
by some content change.
How can I get my xpath selector to work with my data-testid?
By testing for an exact text match in the li's descendant strong
element,
.//*[#data-testid = "menu-item" and div/strong = "Text"]
which matches the second li. Making the test more robust is usually
in order, e.g.
.//*[contains(#data-testid,"menu-item") and normalize-space(div/strong) = "Text"]
Append /div/small or /descendant::small, for example, to the XPath
expression to extract just the small text.
data-testid="menu-item" is matching both the outer li elements while text content you are looking for is inside the inner strong element.
So, to locate the outer li element based on it's data-testid attribute value and it's inner strong element text value you can use XPath expression like this:
//*[contains(#data-testid, "menu-item") and .//normalize-space() = "Text"]
Or
.//*[contains(#data-testid, "menu-item") and .//*[normalize-space() = "Text"]]
I have tested, both expressions are working correctly

How to write dynamic xpath for a webElement?

I have gone through from a lot of question on stackoverflow but didn't find the solution for my problem
-There is an element on webpage which has text= "Success"
Now I want to locate that element, id of the elements keeps changing. There are some other elements with a similar name say - successful, unsuccessful, successfully etc.. on the same page but I want to search the particular element "Success". How should I do it? In this case,
I don't think- startsWith() and contains() methods of XPath will work.
You can use the following xpath to find it. i) //[text()='Success'] or ii) //[.='Success'].
You can always use other attributes. You are not limited to id.
A few examples below:
Basic xpath syntax:
//tag[#tag-attribute='arrtibute-value']
//*[contains(#tag-attribute,'attribute-value')]
//tag[text()='inner-text']
//*[contains(text(),'inner-text')]
Where:
tag = the html tag
* = tag wildcard - matches any tag
tag-attribute = any attributes within the tag (name,id,class...)
attribute-value = the value of the specified tag-attribute
Consider this sample:
<div id="countryDiv1">
<button type="button" class="btn btn-default" data-toggle="dropdown" data-id="ConsignorAddressCountryId" tabindex="1" id="randomGenerated">Text1</button>
</div>
<div id="countryDiv2">
<button type="button" class="btn btn-default" data-toggle="dropdown" data-id="ConsignorAddressCountryId" tabindex="2" id="randomGenerated">Text2</button>
</div>
Here we have 2 buttons having the same random generated IDs but a few differences like tabindex, inner text, id of parent div.
Sample xpath(s) for button 1:
//button[#tabindex='1']
//button[#data-id='ConsignorAddressCountryId'][#tabindex='1']
//button[#data-id='ConsignorAddressCountryId'][text()='Text1']
//div[#id='countryDiv1']//button
Personally, I prefer using other attributes instead of text mainly because texts can have reserved characters that might mess up your xpath.
You can also check your xpath validity using the following:
Chrome Dev Tools (F12) > Elements Tab > Hit CTRL+F while under elements tab > paste your xpath > valid elements will be highlighted
Chrome Dev Tools (F12) > Console Tab > Enter: $x("yourxpath") > hit enter key > matching elements will be displayed on the console.
You can try following:-
//*[#text()='Success']

How to write the single xpath when the text is in two lines

How to write the single xpath for this
<div class="col-lg-4 col-md-4 col-sm-4 profilesky"> <div class="career_icon">
<span> Boost </span> <br/>
Your Profile </div>
I am able to write by two line using "contains" method.
.//*[contains(text(),'Boost')]
.//*[contains(text(),'Your Profile')]
But i want in a single line to write the xpath for this.
You can try this way :
.//*[#class='career_icon' and contains(., 'Boost') and contains(., 'Your Profile')]
Above xpath check if there is an element having class attribute equals career_icon and contains both Boost and Your Profile texts in the element body.
Note that text() only checks direct child text node. To check entire text content of an element simply use dot (.).
You can combine several rules just by writing them one after another since they refer to the same element:
.//[contains(text(),'Boost')][contains(text(),'Your Profile')]

XPath / Selenium can't locate an element using a partial id with contains / start-with

I have the following HTML generated with an AjaxFormLoop.
<div id="phones">
<div class="t-forminjector tapestry-forminjector" id="rowInjector_13b87fdd8b6">
<input id="number_13b87fdd8b6" name="number_13b87fdd8b7" type="text"/>
<a id="removerowlink_13b87fdd8b6" href="#" name="removerowlink_13b87fdd8b6">remove</a>
</div>
<div class="t-forminjector tapestry-forminjector" id="rowInjector_13b87fdda70" style="background-image: none; background-color: rgb(255, 255, 251);">
<input id="number_13b87fdda70" name="number_13b87fdda70" type="text" />
<a id="removerowlink_13b87fdda70" href="#" name="removerowlink_13b87fdda70">remove</a>
</div>
</div>
I'm trying to access the second input field in child 2 using a partial ID, however I have not been successful in getting this to work.
What I've tried thus far.
String path = "//input[contains(#id,'number_')][2]";
String path = "(//input[contains(#id,'number_')])[2]";
I can't even access input 1 using 1 instead of 2, however if I remove [2] and only use
String path = "//input[contains(#id,'number_')]";
I'm able to access the first field without issue.
If I use the exact id, I'm able to access either field without issue.
I do need to use the id if possible as there is many more fields in each t-forminjector row that are not present in this example.
Implementation with Selenium.
final String path = "(//input[starts-with(#id,'quantity_')])[2]";
new Wait() {
#Override
public boolean until() {
return isElementPresent(path);
}
}.wait("Element should be present", TIMEOUT);
Resolved
I'm noticing I can't seem to use the following starts-with / contains to locate any element within to dom, however if I use a complete id, it works.
//Partial ID - fails
//*[starts-with(#id,"quantity_")]
//Exact ID - works
//*[starts-with(#id,"quantity_-112409575185705")]
The generated output you pasted here simply does not contain the string number_ anywhere in it. It does contain Number_ -- note the capital N -- but it's not the first part of the string. Perhaps you meant something like this (which at least selects something):
(//input[contains(#id, 'Number_')])[2]
Or:
(//input[starts-with(#id,'catalogNumber_')])[2]
As Iwburk stated, this was a namespace issue. According to the Selenium API,
http://release.seleniumhq.org/selenium-remote-control/0.9.0/doc/java/com/thoughtworks/selenium/Selenium.html
while using an xpath expression, I needed to used xpath=xpathExpression changing my query string to:
String path = "xpath=(//input[starts-with(#id,'quantity_')])[2]";
I found a related post here,
Element is found in XPath Checker but not in Selenium
you can't access it because you are not locating the element as to be unique in the page.
use an xpath that makes it unique ,
- you're xpath look ok .
more info here
http://www.seleniumhq.org/docs/appendix_locating_techniques.jsp
Besides the selenium syntax problem there's an xpath issue related to markup structure.
xpath 1: //input[starts-with(#id,'number_')][1]
xpath 2: (//input[starts-with(#id,'number_')])[1]
In the sample below xpath 1 will return 2 nodes (incorrect) and xpath 2 will be correct because input nodes are not siblings so surrounding parenthesis are needed to refer to the resulting nodeset
<div id="phones">
<div>
<input id="number_1" name="number_1" type="text"/>
</div>
<div>
<input id="number_2" name="number_2" type="text" />
</div>
</div>
Result without parenthesis
/ > xpath //input[starts-with(#id,'number_')][1]
Object is a Node Set :
Set contains 2 nodes:
1 ELEMENT input
ATTRIBUTE id
TEXT
content=number_1
ATTRIBUTE name
TEXT
content=number_1
ATTRIBUTE type
TEXT
content=text
2 ELEMENT input
ATTRIBUTE id
TEXT
content=number_2
ATTRIBUTE name
TEXT
content=number_2
ATTRIBUTE type
TEXT
content=text
In this next sample, parenthesis will not make a difference because nodes are siblings
<div id="other">
<input id="pre_1" type="text"/>
<input id="pre_2" type="text" />
<div>a</div>
</div>
With parenthesis
/ > xpath (//input[starts-with(#id,'pre_')])[1]
Object is a Node Set :
Set contains 1 nodes:
1 ELEMENT input
ATTRIBUTE id
TEXT
content=pre_1
ATTRIBUTE type
TEXT
content=text
Without parenthesis
/ > xpath //input[starts-with(#id,'pre_')][1]
Object is a Node Set :
Set contains 1 nodes:
1 ELEMENT input
ATTRIBUTE id
TEXT
content=pre_1
ATTRIBUTE type
TEXT
content=text
Testing was done with xmllint shell
xmllint --html --shell test.html

Resources