selenium-webdriverxpath

xpath finding H3 element by text content and then clicking it's parent anchor


our search engine has been replaced and the new engine returns an adapted html response. I am adapting a Test automation but I have difficulties finding the correct H3 element by text content and then clicking it's parent anchor.

The targeted element is as followed:

<article _ngcontent-ng-c949029947="" class="pw-article-item">
    <!---->
    <div _ngcontent-ng-c949029947="" class="pw-article-item__text ng-star-inserted"><!---->
        <a _ngcontent-ng-c949029947="" href="https://acme.org/our-offering/cash-management/integration-services" target="">
            <h3 _ngcontent-ng-c949029947="">
                Integration services
                <i aria-hidden="true"></i>
            </h3>
        </a>
    <!---->
    </div>
    <!----><!---->
</article>

The following absolute xpath ref works:

Taf.ui().web().element().findLink(By.xpath("//*[@id=\"maincontent\"]/div/pw-lazy-element/pwng-search-result-page/div[2]/section/div[1]/pwng-article-item[10]/article/div/a")).click(); //OK

I have set some alternative, more relative queries but none of them works:

Taf.ui().web().element().findLink(By.xpath("//*article[contains(@class,'pw-article-item')]/div/a/h3[@contains(text(),'Integration services')]/parent::a")).click();


Taf.ui().web().element().findLink(By.xpath("//*article[contains(@class,'pw-article-item')]//h3[text()='Integration services']/ancestor::a")).click();

Taf.ui().web().element().findLink(By.xpath("//div[@class='pw-serp__result-list']//h3[contains(text(),'Integration services')]")).click();

Can someone explain where I miss the boat and what a correct xpath query could be?


Solution

  • I suggest

    //article[@class='pw-article-item']//a[contains(h3, 'Integration services')]
    

    You can restrict the search to the <article class="pw-article-item"> by starting the XPath with

    //article[@class='pw-article-item']
    

    NB there's no need to use the contains() function unless the class attribute also contains other text (i.e. other class names). If the class attribute is literally equal to "pw-article-item" then you can just use the XPath = operator in the predicate.

    To return an a element if it has a child h3 element whose text value contains a specific string, you would normally apply a predicate to the a in your XPath, which constrains the h3 element's string value:

    a[contains(h3, 'Integration services')]
    

    Again, if the h3 element contained absolutely nothing except Integration services (i.e. no extra white space characters, or anything), then you would not need to use the contains() function and could instead write:

    a[h3 = 'Integration services']
    

    NB something to understand about the XPath expression text() is that it's a test for a text node. The expression a[contains(h3/text(), 'Integration services')] will be satisfied only if the a element has a child h3 element with a child text node which contains the string 'Integration services'. e.g. this would match:

    <a>
       <h3>Integration services</h3>
    </a>
    

    But that expression would not be satisfied by a h3 element which contained some other element (e.g. a span) which in turn contained a text node Integration services, because that text node would be a grand-child rather than a child, e.g.

    <a>
       <h3><span>Integration services</span></h3>
    </a>
    

    This XML would also not match the expression:

    <a><h3>Integration <br/>services</h3></a>
    

    ... because there are two text nodes; one before the <br/> with the string value Integration , and another after the <br/> element, with the string value services. This expression would match the above case:

    a[h3='Integration services']
    

    ... because the effect of the = operator is to convert the h3 element into a string, to match the string value on the right-hand side of the expression. When an element is converted to a string value, that value is the concatenation of all the element's descendant text nodes.