Locating elements on a web page is the life-line to any web based automation tool, which supports UI automation testing. Starting with Selenium, there are various locator strategy, like name, tagname, css , xpaths, for locating a specific element – may be a button, an input box, a combo box, a drop down etc. from a given web page.

One of such selector locating mechanism is CSS Selector. Using attributes like class, tags, specific to an element, we can identify a specific element on the web page.

For e.g. consider this HTML snippet

<div>
<p class="first">I am first</p>
<div>I am second</div>
<p class="second">I am 3rd</p>
</div>

Let’s say I want to identify the first p element inside the first div tag. So I can use this css selector in Selenium ( Python) to identify this element

driver.find_element(By.CSS_SELECTOR,"p.first")

However, when automating applications , we rarely find such simple scenarios. There are lot of web controls on modern web pages, build with front end libraries like React, Angular, which make it hard to use simple CSS locators.

Generally when automating web pages, we come across scenarios, where there is a parent tag, and then there are child elements present inside the parent tag – an example would be a drop down, implemented using a Select tag, or an un-ordered list , inside which there are multiple list elements

<ul class="sample">
   < li class="sample1"> first </li>
   < li class="sample1"> second </li>
   < li class="sample1"> third </li>
   < li class="sample1"> fourth </li>
</ul>

Here, all the li elements have the same class sample1 and neither do they have any distinct id or such attribute. So in order to automate such scenarios, we can use the nth ordering of the element inside the CSS selectors.

Playwright

Playwright is a new e2e automation library, which is currently gaining a lot of attention and tracking in the e2e automation market. Playwright has it’s roots in the erstwhile Puppeteer, which was developed by team at Google. The same team, is behind Playwright, but now with MS.

Playwright, has a lot of features, and recently a lot of comparisons have shown that it is vastly improved in terms of speed, reliability, and execution than it’s peers like Cypress, WebdriverIO, TestCafe etc – here is a detailed comparative study reflecting this. I’m not going to go deep into the features of Playwright. There are many articles on the web, like this, which explains the details of Playwright.

One of the very interesting feature of Playwright is the ease and versatility of how it allows us to locate elements on the web page. Apart from the usual xpaths, css, there are a host of other locator mechanisms, which coupled with css and text attributes, help a lot in identifying elements on the page. You can refer to this detailed documentation on Playwright’s website.

Nth Selectors in Playwright – nth-child and nth-of-type

One of the reasons, why I, personally love Playwright is the number of different ways I can get elements that are not easy to be identified using normal locators. And I’ll take example of the list example that I’ve given above.

Now consider this following two HTML structure –

<section>
   <p>Playwright</p>
   <p>Puppeteer</p>    <!-- We want this one -->
</section>
<section>
   <h1> Selenium Alternatives </h1>
   <p>Playwright</p>
   <h2>WebdriverIO</h2>
   <p>Puppeteer</p>    
</section>

Now, how can we select the second element of the p tag in the first HTML – here we can use the nth-child() concept of CSS selectors.

await page.locator(p:nth-child(2));

The same thing can also be achieved using the nth-of-type() concept of the CSS selectors

so this

await page.locator(p:nth-of-type(2));

basically would return the same thing as the selector with nth-child method.

However, for the second HTML, there is a h1 tag inserted between two p tags, so using the first locator using nth-child method will not return any element. However, the p:nth-of-type() still holds good and will return the second li child here.

Now, since we have talked about the nth-child and the nth-of-type concepts using CSS Selectors, there are a couple more that you can use in Playwright.

Using nth index

Consider this HTML snippet –

<div class="multipleclass secondclass">
   <button type="button" class="btnclass">
      <span>Button</span>
   </button>
   <button type="button" class="btnclass">
     <span>Button</span>
   </button>
</div>

Let’s say we have a scenario, where there are multiple buttons on page, which reside inside a single div with same classes and have same button text, something similar to what is mentioned above. Here we have a div with two buttons inside the div. Both the button have same class and same text too. We could use the nth-of-type to get the second element

OR

we can use the nth index to get the element in Playwright. Suppose we want to click on the second button, we can use nth=1 to click to the button.

await page.locator('button >> nth=1').click();

And

await page.locator('button >> nth=0').click();

to click on the first button.

If you see carefully, the indexing is starts with 0 and not 1 as is generally in case of nth-child concept.

Using nth-match concept

Consider a scenario, where on a single page, there are form elements with same name, for e.g. there are three button elements with the same name Buy but there are under different tree-structure on the page.

  <section> 
          <button>Buy</button> 
  </section>
  <article>
         <div> 
           <button>Buy
           </button> 
         </div>
   </article>

   <div>
          <div> 
            <button>Buy
            </button> 
          </div>
   </div>

Let’s say suppose you want to select the third button – with the text Buy. So in Playwright we can use the :text pseudoclass along with another CSS concept nth-match to directly get to the element like

await page.locator(':nth-match(:text("Buy"), 3)').click();

Here, in this case, unlike the nth selection concept, the indexing starts at 1. So we have to be very careful if you are using the nth vs the nth-match concept in this scenario.

Now, many may argue as to why do this, when instead, I could use any unique identifier from the last div tag and then combine with the button tag to get the element. Something like this

await page.locator('div.someclassinlastdiv > button');

will also work. However, I think it boils down to choice that you can make. I think since Playwright has already given us a specific way to working around this scenario, so we can use this. However, the second one is also fine and would work in the same way as well.

If you want to know how nth-match internally works, you can follow this question I’ve raised on Playwright’s github.

Using nth index with locator

Kaushik, one of my favorite tech-vlogger about Playwright, also pointed out on LinkedIn, there we can use the following syntax also

await page.locator('some_locator').nth(index);

This will also work if you’re trying to get the nth index based element from a list, similar to what we have discussed above. The documentation clearly states about this here.

If you want to deep-dive on how Playwright allows us to locate elements, you should head over to the documentation on selectors in Playwright here.

Hope this post helps people who are starting with Playwright and how to use the different power of CSS Selector methods to locate elements on the page.

Recommended reading/References:

  1. https://css-tricks.com/the-difference-between-nth-child-and-nth-of-type/
  2. https://www.digitalocean.com/community/tutorials/css-css-nth-child-vs-nth-of-type
  3. https://playwright.dev/docs/selectors#pick-n-th-match-from-the-query-result
  4. https://webdesign.tutsplus.com/tutorials/demystifying-css-pseudo-classes-nth-child-vs-nth-of-type–cms-34221