Search code examples
javascripttypescripte2e-testingplaywrightcontentful

How to define the most efficient locator for element in DOM using Playwright


I am adding Playwright tests for a site with multiple locales. Page content is mainly rendered via CMS content (I'm using Contentful).

Using locators with hardcoded text, such as await page.getByLabel('Blog Article Foo').click(); seems very brittle, especially with multiple locales.

Should I just avoid using any text-based locators and just use await page.getByRole('heading').click();?

This doesn't seem the "Playwright" way of testing, as I'm not testing what a user sees / interacts with, however the only alternative I can see is writing lots of very flakey tests for each locale and constantly update them.

Am I missing something?


Solution

  • Automation mostly it's not about user sees. It's about user flow that can be completed from scratch by user that matches product logic, using user resources (browser, etc.) as is.

    Rendered components are usually checked by visual (screenshot) tests, and their part should be the lowest part of all coverage (Testing Pyramid), where e2e can be split by functional and visual.

    In most of cases you care about element that is exist / rendered / visible / clickable / has some styles and properties, but not on it's text itself. Text is usually defined by content keys, so in most of cases there is no need to test it. It can be tested by units, that element has key, and that key has value, but it is not the purpose of e2e testing.

    1. If you have access to source code and can change it, best practice is to assign unique locators to the element and use it in e2e.

    For example

    • [data-testid=heading] or page.getByDataTestId('heading')
    • [data-hook=heading]
    • [data-aid=heading]

    If you can't assign attributes by yourself and see this attributes - it's better to use them, they are for sure used for automation and there is high possibility they wouldn't change.

    1. Then you should rely on element id, but in this case you should consider, that id is unique (it should by best practices, but some resources ignore this pattern) and not auto-generated.

    2. Rely on element attribute that represents it's type or use purpose, input[type=search], [role=heading]

    3. Rely on attribute classes that seemed to be unique or combination between attributes and classes .headingContainer, div.headingContainer

    4. Rely on custom tags like name, title, etc. But be aware, that they can be changed on different localisations. (Sometimes they are not, sometimes they are)

    5. Rely on unique tags (for example, youtube has many unique tags), like ytb-video-container, ytb-video-player, etc.

    6. Rely on href or src part img[src*=imgur], [href*=imgur]. It's not the best practice, but can be used if src part wouldn't be changed.

    7. And only if you don't have any other options, you can use text in construction of your locators. Text is very sensitive area that can be easily changed by product logic and also it differs on site localizations. For example, if you currently have only English, but in future you expect that site would be translated to Portuguese and you should test it, you should avoid any locators based on texts.

    In building locators try to make it as easier as possible, use one-level if it is possible, if it is not - rely on component containers and get child elements from them.

    Example:

    const posts = await page.locator('#blogPostContainer').all();
    for(const post of posts) {
      await post.getByRole('heading').click();
      // do something
    }