Tips for identifying the most effective element locator in the DOM with Playwright

Currently, I am in the process of incorporating Playwright tests for a website that supports multiple locales. The majority of the page content is dynamically generated from CMS content (Contentful).

I am hesitant about using hardcoded text locators like

await page.getByLabel('Blog Article Foo').click();
, as they appear to be quite fragile, particularly when dealing with multiple languages or regions.

Would it be better to steer clear of any text-based locators and instead rely on generic ones like

await page.getByRole('heading').click();
?

Although this approach might not align perfectly with typical Playwright testing practices of verifying user interactions based on visible elements, it seems like the most practical solution to avoid creating numerous unreliable tests for each locale and having to constantly maintain them.

Could there be something essential that I am overlooking?

Answer №1

Automation is primarily focused on the user flow rather than just what the user sees. It involves guiding the user through completing tasks that align with the product logic, utilizing user resources such as browsers.

Rendered components are typically validated using visual (screenshot) tests, and this should be a small part of overall test coverage according to the Testing Pyramid concept, where end-to-end testing can be divided into functional and visual aspects.

In most cases, the focus is on whether an element exists, is rendered, visible, clickable, and has certain styles and properties, rather than the text itself. Text is usually defined by content keys, negating the need to test it. Units can verify that an element has a key and that the key has a value, but this isn't the main purpose of end-to-end testing.

  1. If you have access to the source code and can modify it, it's advisable to assign unique locators to elements for use in end-to-end testing.

For example:

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

If you cannot add attributes yourself but see existing ones, it's better to leverage them as they are likely intended for automation purposes and are less likely to change.

  1. You can then rely on the element's id, ensuring it is unique and not auto-generated.

  2. Rely on attribute values representing the type or purpose of elements, such as input[type=search], [role=heading].

  3. Rely on classes that appear unique or combinations of attributes and classes like .headingContainer, div.headingContainer.

  4. Utilize custom tags like name, title, etc., bearing in mind they may vary between different localizations.

  5. Explore unique tags like those used by YouTube, e.g., ytb-video-container, ytb-video-player.

  6. Rely on the href or src attribute, for instance, img[src*=imgur], [href*=imgur], if other options are exhausted.

  7. Only consider using text in locators as a last resort. Text can easily change due to product logic adjustments and localization differences.

When constructing locators, aim for simplicity whenever possible, preferring single-level structures. If not feasible, utilize component containers to access child elements.

Example:

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Experiencing a bug in the production build of my application involving Webpack, React, postCSS, and potentially other JavaScript code not injecting correctly

I've encountered an issue with my webpack.prod.config when building my assets, which may also be related to the JS Babel configuration. While I can successfully get it to work in the development build by inline CSS, the problem arises when attempting ...

I am looking for guidance on how to effectively utilize a JSON object that is stored in the constructor of my component, particularly when triggering

Below is the object I have in my constructor. I am passing a value from a previous component to the one below. I receive the item json object, but I need to use it when I click. constructor(public navCtrl: NavController, public navParams: NavParams) { ...

Execute the eslint loader within the node_modules of a specific directory that is npm linked and has not been compiled

One of the benefits of using webpack 4 is the ability to run eslint across the entire project folder with a specific configuration. { enforce: 'pre', test: /\.js|ts$/, exclude: /node_modules/, loader: 'eslin ...

Combining all elements of my application into a single HTML, JS, and CSS file

My AngularJS app has a specific structure that includes different directories for each component: theprojectroot |- src | |- app | | |- index.html | | |- index.js | | |- userhome | | | |- userhome.html | | | ...

Display the new data from an array that has been created following a subscription to Angular Firestore

I am struggling to access the content of a variable that holds an array from a Firebase subscription. The issue I am facing is that I am unable to retrieve or access the value I created within the subscription. It seems like I can only use the created valu ...

Encountered issue while initializing object from controller in AngularJS

Here is the demonstration on how the fiddle appears: var app = angular.module('testApp', []); app.controller = angular.('testAppCtrl', function ($scope) { $scope.vehicle = { type: 'car', color: 're ...

Unique hover tags such as those provided by Thinglink

Looking for a way to replicate the functionality of Thinglink? Imagine a dot on an image that, when hovered over, displays a text box - that's what I'm trying to achieve. I thought about using tooltips in Bootstrap, but I'm not sure if it ...

Change elements in real-time

I have a requirement to adjust elements with the class .super-elem by adding an attribute adjusted="true". Initially, I can easily achieve this within the document's ready event : $(".super-elem").attr("adjusted", "true"); However, I may add more . ...

After updating to the latest npm version, the NodeJS server continues to display the error message "Upgrade Required" when loading pages

After developing a Node project using NodeJS version 5.4.x and NPM version 3.3.12 on Windows, I encountered an issue where the project throws an "Upgrade Required" message (HTTP Error code - 426) upon loading the page after some time of inactivity. To add ...

Custom styling options for Google Chart

I have been attempting to dynamically adjust the width and height of a google-chart directive by utilizing input field values. However, I am encountering an issue where the width and height are not updating as expected. Check out the Plunker here $scope. ...

Click on the image to resize the div accordingly

I have a scenario where I need to achieve the following functionality: Two divs are present, with an image inside the first div. When the image is clicked, the left div should resize, and the content on the right side should expand to show full page cont ...

How can you troubleshoot code in NextJS API routes similar to using console.log?

Incorporating nextjs API routes into my upcoming project has been a priority, and I recently encountered an issue with code execution upon sending a POST request. Debugging has proven to be challenging since I am unable to use conventional methods like co ...

Is there a way to ensure that an external file function completes before proceeding with the main code execution?

In my project, I have two key JavaScript files: main.js and load.js. Within load.js, there are two essential functions - getData, which makes an AJAX post call, and constHTML, which appends data based on the JSON array returned from the AJAX call. This app ...

Ways to display all current users on a single page within an application

I have come across a project requirement where I need to display the number of active users on each page. I am considering various approaches but unsure of the best practice in these scenarios. Here are a few options I am considering: 1. Using SignalR 2. ...

What is the best way to fetch all Firebase database IDs using Angular?

Is there a way to fetch all data from Firebase database along with their respective IDs? Currently, I have two functions - getAll() and get(input) that retrieve specific products based on the given ID. However, my current implementation only returns obje ...

PHP and AJAX concurrent session issue causing difficulties specifically in Chrome browser

TL;DR. I'm encountering an issue in Chrome where different requests are seeing the same value while incrementing a session counter, despite it working fine in Firefox and Internet Explorer. I am attempting to hit a web page multiple times until I rec ...

Can a variable be initialized with a concealed or additional argument?

After just 2 weeks of coding, I'm struggling to find information on how to initialize a variable with an extra argument in a recursive function call. Is this even possible? And if it is, are there any scenarios where it's considered best practice ...

Unlocking the potential: passing designated text values with Javascript

In my current React code, I am retrieving the value from cookies like this: initialTrafficSource: Cookies.get("initialTrafficSource") || null, Mapping for API const body = {Source: formValue.initialTrafficSource} Desired Output: utmcsr=(direct)|utmcmd=(n ...

Ways to ensure certain code is executed every time a promise is resolved in Angular.js

Within my Angular.js application, I am executing an asynchronous operation. To ensure a smooth user experience, I cover the application with a modal div before initiating the operation. Once the operation is complete, regardless of its outcome, I need to r ...

Execute a post request upon clicking with NEXT JS, while also firing off a get request

I'm facing an issue where I need to post and get my data when clicking on the same button (similar to writing and displaying comments). However, whenever I click the button, everything seems to be working fine but a request with a 304 status code star ...