Using XPath to select element with highest z-index - html

In my Selenium application i try to select an element which has the highest z-index. That value is not defined in the element itself, but on an ancestor node (the nesting level is not known). In addition if an ancestor is not visible by the use of display: none it shouldn't be returned.
Example HTML:
<div class="container" style="z-index: 10">
<div style="display: none">
<!-- this should not be selected because it is invisible (and the z-index is lower than the others) -->
<div myattr="example"></div>
</div>
</div>
<div class="container" style="z-index: 100">
<div>
<!-- this should not be selected because the z-index is lower than the others -->
<div myattr="example"></div>
</div>
</div>
<div class="container" style="z-index: 1000">
<div>
<!-- this should be selected because it is visible and has the highest z-index -->
<div myattr="example"></div>
</div>
</div>
Currently i have a regex that selects all elements with myattr="example" which do not have an ancestor with display: none:
//div[#myattr='example'
and not(ancestor::div[contains(#style,'display:none')])
and not(ancestor::div[contains(#style,'display: none')])]
I need an additional condition to select the element which has the highest z-index, so to speak which is visible on top of others. For each found node it has to be looked at all ancestors until a node with a specific class is found (container in this example). Then return only the element that has highest z-index ancestor.
Is that even possible with XPath?

I tried really hard, but I think you can't achieve this with a single XPath 1.0 expression. You can get close, but not quite there.
You'll need to use some other logic. There's like a thousand different approaches.
For example, get all the container elements, sort them by z-index and test their myattr="example" descendants for visibility:
// Gets all containers - could also be Gets all elements containing z-index
List<WebElement> containers = driver.findElements(By.className("container"));
// Sorts the containers in an descending order by their z-indexes
Collections.sort(containers, Collections.reverseOrder(new Comparator<WebElement>() {
#Override
public int compare(WebElement o1, WebElement o2) {
return getZindex(o1) - getZindex(o2);
}
private int getZindex(WebElement elem) {
String zindex = elem.getAttribute("style").toLowerCase().replace("z-index: ", "");
return Integer.parseInt(zindex);
}
}));
// look for a visible candidate to return as a result
for (WebElement container : containers) {
WebElement result = container.findElement(By.cssSelector("*[myattr='example']"));
if (result.isDisplayed()) {
return result;
}
}
throw new IllegalStateException("No element found.");
EDIT: After you accepted this answer, I returned to the question and came up with an XPath 1.0 solution. It's ugly as hell, will perform poorly and I can't verify its correctness (it works on your example and a few others I tried), so I suggest you to use the WebDriver approach above. Anyway, I'll share it:
Copypastable oneliner:
//div[#myattr='example' and not(ancestor::div[contains(#style,'display: none')])]/ancestor::div[#class='container' and substring-after(#style,'z-index:') > substring-after(../div[not(descendant::div[contains(#style,'display: none')])]/#style,'z-index:')]
Formatted version:
//div
[
#myattr='example'
and not(ancestor::div[contains(#style,'display: none')])
]
/ancestor::div
[
#class='container'
and substring-after(#style,'z-index:')
> substring-after(
../div[not(descendant::div[contains(#style,'display: none')])]/#style,
'z-index:')
]
And a free translation to human language (not a literal one!):
SELECT A VISIBLE <div #myattr='example'> NODE
//div
[
#myattr='example'
and not(ancestor::div[contains(#style,'display: none')])
]
THAT HAS A <div #class='container'> ANCESTOR
/ancestor::div
[
#class='container'
WHOSE z-index IS GREATER THAN z-index...
and substring-after(#style,'z-index:')
> substring-after(
...OF ALL VISIBLE SIBLINGS
../div[not(descendant::div[contains(#style,'display: none')])]/#style,
'z-index:')
]

I assumed you know the highest value of z-index, in that case xpath is
"//div[contains(#style,'1000')]/div[not(contains(#style,'none'))]/div"
Otherwise using below xpath get all style attributes of div
List<WebElement> divTags=driver.findElements(By.xpath("//div[not(contains(#style,'none'))]/parent::div[contains(#style,'z-index')]"))
for(WebElement ele:divTags)
{
ele.getAttribute("style");
}
Store the z-indexes in some variable
Parse stored z-indexes and get the numbers alone
Using some logic find the highest number among those
Doing all the above things have the hold on index of that elements.
After finding highest z-index, get the index of that element
Using that index construct xpath.
I hope above logic will help you to achieve your goal.

Related

Jquery change css class from variable

For my site, I code a button allowing to change the css of a class present in a div card. My button is located in the card-footer. Having several cards, I can't / don't think to retrieve the element with an id (as there will be X times the same ID)
In order to circumvent this system, I therefore use a parentElement which goes up to the div card
<div class="card">
<div class="card-body">
<p class="change">Change one</p>
<p class="change">Change two</p>
<p class="change">Change three</p>
</div>
<div class="card-footer">
<i id="updateData">change</i>
</div>
</div>
jQuery($ => {
$('#updateData').click(e => {
var element = e.target.parentElement.parentElement;
$('.change').css('display','none');
});
});
I would like to indicate that only the class "changes" present in my element variable and not all the classes in the page.
I don't know how to add a variable to my ".css" command, do you know how ?
Thanks in advance !
First of all since you will have multiple elements with same id that means that you should not use ID and use class instead. Id is meant to be unique. So yours id="updateData" should become class="updateData". Now you can grab all of those buttons and assign event to all of them instead of just first like you were by using id selector.
$('.updateData').click(e=> {});
Next in order to be able to use clicked element in jQuery way convert from arrow function to regular anonymous function since arrow function overrides this keyword. And now you can use jQuery to hide like
$('.updateData').click(function() {
element = $(this).parent().parent();
element.hide();
});
If you want more precise selection to hide only .change elements use
$('.updateData').click(function() {
element = $(this).parent().parent();
element.find(".change").hide();
});
Not bad, but more efficient, when you have multiple click targets, is delegation:
$(document).on("click", ".updateData", function() { ... });
Also .hide() is convenient, but rather then "change the css of a class" add a class "hidden" or something! In best case the class further describes what the element is. CSS is just on top.

Selenium test (selection of element having no attribute)

<! DOCTYPE html>
<html>
<head>Sample</head>
<body>
<div class="panelBody">
<div class=panel-section></div>
<div class=panel-section style="display:block"></div>
</div>
</body>
</html>
In given Snippet there are two elements with same class. I have to select the element which does not having style attribute.If i tried to search with panel-section class its giving ambiguity error.So how to select div element which does not having style attribute.i.e
<div class=panel-section></div>
Try this:
//div[#class='panelBody']/div[not(#style)]
Explanation: First find the div with class panelBody, then find child div elements in the panelBody div which doesn't contain #style attribute.
Use findElements method if there are more than one div element without #style attribute, otherwise findElement() method would suffice.
Since there are more than one elements with same class name, you need to use Selenium's driver.findElements() method. I have tried getting this element, but I wonder if it is clickable. Only element can actually be useful here is text Sample.
Check below code. Let me know if it is similar to what you are looking for.
List<WebElement> linksize=null;
String links[]=null;
linksize = driver.findElements(By.cssSelector("div[class=panel-section]"));
int linksCount = linksize.size();
links= new String[linksCount];
for(int i=0;i<linksCount;i++)
{
links[i] = linksize.get(i).getAttribute("style");
if(links[i].isEmpty())
{
System.out.println("I am div without style");
linksize.get(i).click();
}
}

Can i use attributes of element to create style rules?

I'm noot good in english, so the title may seem a bit odd.
I want to use css function attr() like this:
I mean i have a container <div> and an inner <div> that i want to have width depending on data-width attribute. For example this would be great, but this doesnt work:
<div class="container">
<div data-width="70%">
</div
</div>
.container {
width: 600px;
height: 200px;
}
.container div {
width: attr(data-width);
height: 100%;
}
Is there any noJS way to use attributes like that?
UPDATE: Guys convinced me that the JS is the only way to do this :)
That's not a big problem (but that's bad. CSS, why youre so illogical? Is the difference between content:attr(data-width) and width: attr(data-width) so big ?).
One of the guys had an idea to go through the all elements with jQuery.
That's ok, but it is very... local? Don't know how to say it in english.
Anyway, i remaked his code a little bit and here it is:
allowed = ['width','color','float'];
$(document).ready(function () {
$('div').each(function (i, el) {
var data = $(el).data(),style = '';
if (!$.isEmptyObject(data)) {
$.each(data, function (attr, value) {
if (allowed.indexOf(attr) != - 1) {
style += attr + ': ' + value + '; ';
}
})
if (style.length != 0) {
$(el).attr('style', style);
}
}
})
})
Idea is simple:
1. We suppose that style we want to add to an element is the only one. I mean there are no scripts that will try to add some other styles,
2. We create an array of allowed attribute names, we need to avoid using wrong names at the style attribute, for example style="answerid: 30671428;",
3. We go through each element, save its data attributes in an object, check if object is empty, and if not - check every attribute if it is allowed, create a string that contains all styles that we need, and - finally - add our style string to the element as the content of style attribute.
That's all, thanks everybody
I would not advise to use CSS alone since it will not allow you to do what you're looking for... instead use a scripting language (in my case jQuery) to accomplish this functionality for you like so: jsFiddle
jQuery
jQuery(document).ready(function(){
var dataElem; // to store each data attribute we come accross
jQuery('div').each(function(){ //loop through each div (can be changed to a class preferably)
dataElem = jQuery(this); //get the current div
if(dataElem.data('width')){ //make sure it exists before anything further
dataElem.width(dataElem.data('width')); //set the element's width to the data attribute's value
dataElem.css("background-color", "yellow");
}
});
});
HTML
<p>The links with a data-width attribute gets a yellow background:</p>
<div>
w3schools.com
</div>
<div class="me" data-width="50"> <!-- change value to see the difference -->
disney.com
</div>
<div>
wikipedia.org
</div>
Notes on the above:
each, data, width.
Instead of doing data-width, use a class attribute. An html tag can have mutliple classes separated by spaces, so if you wanted to be very precise, you could set up as many classes as you need. For instance:
<div class="w70 h100">
</div>
Then in your css:
.w70{
width: 70%;
}
.h100{
height: 100%;
}
And so on.
Is there any noJS way to use attributes like that?
No, you cannot use CSS to set the width of the element to it's data-width attribute. CSS does not allow for this as attr() is only currently available for the CSS content property which is only available on css pseudo elements (::before and ::after).
How can you achieve this with as little javascript as possible?
This is extremely easy to do using the native host provided DOM API.
Select the elements using Document.querySelectorAll().
Iterate the elements and apply the styles using Element.style which can be retrieved from the data-width attribute using Element.dataset
(Demo)
var items = document.querySelectorAll('#container div'), item, i;
for(i = 0; (item = items[i]); i++) item.style.width = item.dataset.width;

dom element id equality

do two DOM elements have the same id if their ids are set to an empty string?
example:
$('#something').attr('id', '');
$('#something-else').attr('id', '');
//do these now have the same id?
//is there a better way to unset an element's id?
and is this equal to an element that was declared without an id
<div></div>
<div id=''></div>
<div id></div>
<!--how do these compare??-->
The id attribute cannot legally contain an empty string in either HTML 4 or HTML5. That said, the selectors [id] and [id=""] will still match such elements should they appear in a non-conforming document. Note that an empty id attribute is not the same as the lack of an id attribute.
Setting an element's id attribute to the empty string in a script seems to work (DOM level 4 seems to forbid it, but this may be new as it is not specified in DOM level 3 or 2), allowing it to match the aforementioned selectors even when there are other elements with empty id attributes in the same document. This would imply that the elements now do all have the same ID, but considering that elements cannot have empty IDs in a conforming document in the first place I'm not sure how true that statement is. See the following example:
document.getElementById('something').id = '';
document.getElementById('something-else').id = '';
div[id=""] {
color: red;
}
#something {
color: blue;
}
<div>No id</div>
<div id=''>Empty id</div>
<div id='something'>#something</div>
<div id='something-else'>#something-else</div>
//is there a better way to unset an element's id?
To remove an element's id attribute, use removeAttribute() (or removeAttr() in jQuery):
document.getElementById('something').removeAttribute('id');
$('#something-else').removeAttr('id');

Searching in html on the behalf of ID

Is searching possible in html tags on the behalf of ID? for example to find div tag having id="abc".
I can use document.getElementByID("abc"). But i need parent div + its inner HTML in return of searching. i.e if this div has childs
Try this :-
<script >
function showHTML(){
var vinner=document.getElementByID("abc").innerHTML;
var totalinner="<div >"+vinner+"</div>";
alert(totalinner);
}
</script>
HTML part:-
<body onload="showHTML();">
<div id="abc">
Hello inside abc
<div>
Inner div inside abc tag.
</div>
</div>
</body>
Its working fine. You can get Attributes here.
It's hard to understand what you want to achieve:
document.getElementById("abc").parentNode.innerHTML;
//will return <div id="abc"> and other items from parrent
document.getElementById("abc").getAttribute("name");
//will atribute of <div id="abc">
if (document.getElementById("abc").hasChildNodes()) {
// It has at least one
}
Using jQuery is much simplier, you could do that:
$("#abc").attr('id') //retunrs id
$("#abc").attr('class') //returns classes
//or other manipulations
One way to do this is to use outerHTML, which:
gets the serialized HTML fragment describing the element including its descendants.
Given the following HTML:
<div id="abc" data-attr="A custom data-* attribute">Some text in the div.</div>
The following JavaScript will log, in the console, the HTML of the element of id equal to abc:
var htmlString = document.getElementById('abc').outerHTML;
console.log(htmlString);
JS Fiddle demo.
References:
outerHTML.
outerHTML compatibility.