Alp
Alp

Reputation: 29739

Using XPath to select element with highest z-index

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?

Upvotes: 4

Views: 5160

Answers (2)

Petr Janeček
Petr Janeček

Reputation: 38424

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:')
        ]

Upvotes: 2

Santoshsarma
Santoshsarma

Reputation: 5667

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.

Upvotes: 1

Related Questions