Designing automated tests is a fundamental part of software development today, but their maintenance can often become nightmarish. Whenever the UI changes, such as renaming a button, changing a CSS class, or HTML structure, test locators will no longer work, which means the tests must be updated. This leads to constant redo work and a high maintenance burden. In this blog, we’ll explore a zero-maintenance approach to conducting Playwright tests using data-testid attributes and centralized test IDs. This method helps limit overworking, along with improving the overall reliability and robustness of the software.

The Problem: Why Test Maintenance Is Challenging

An automated test is difficult to maintain because the application does not remain static. Here are some common pain points:

The Solution: Say No to Locator Methods, Use data-testid from one centralized location

To solve these issues, we can take a zero-maintenance solution by utilizing data-testid attributes. Here’s how it works:

Centralized Test IDs: Declare all test IDs in one, centralized place (e.g., utils/testIds). These IDs are utilized both in the application code and the test code.

Decouple Tests from UI Structure: Avoid using CSS selectors or XPath, but use data-testid (page.getByTestId) attributes to identify elements uniquely. This decouples tests from the UI structure and makes them more stable against changes.

Reusable Test IDs: By sharing the same test IDs throughout the application and tests, you ensure consistency and reduce duplication.

How to Implement This Approach

Step 1: Define Test IDs in a Centralized Location

Create a file (e.g., utils/testIds.ts) to store all your test IDs. For example:

				
					// utils/testIds.ts
export const USERS_TEST_IDS = {
  userTable: {
    userMenuButton: 'user-menu-button',
    removeButton: 'remove-button',
  },
};

				
			

Step 2: Use Test IDs in Application Code

In your application code, use these test IDs as data-testid attributes. For example:

				
					import { USERS_TEST_IDS } from "@/utils/testIds";

<IconButton
  onClick={handleMenuClick}
  data-testid={USERS_TEST_IDS.userTable.userMenuButton}
>
  <MoreHorizontalIcon />
</IconButton>

				
			

Step 3: Use Test IDs in Playwright Tests

In your Playwright tests or page objects, import the same test IDs and use them to locate elements. For example:

				
					import { USERS_TEST_IDS } from "@/utils/testIds";
class UsersPage {
  constructor(private page: Page) {
    this.adminMenuButton = page.getByTestId(
      USERS_TEST_IDS.userTable.userMenuButton
    );
    this.menuRemoveButton = page.getByTestId(
      USERS_TEST_IDS.userTable.removeButton
    );
  }
  async removeSpecificAdmin(userName: string) {
    await this.searchUser(userName);
    await expect(this.page.getByText(userName)).toBeVisible();
    await expect(this.adminMenuButton).toBeVisible();
    await this.adminMenuButton.click();
    await expect(this.menuRemoveButton).toBeVisible();
    await this.menuRemoveButton.click();
    await this.commonPageUtil.clickOnButton("Yes, Remove");
  }
}

				
			

Benefits of This Approach

Best Practices

Conclusion

By going with a zero-maintenance model for Playwright tests through data-testid attributes, you can substantially lower the amount of effort you need to spend maintaining your test suite. This not only gets your tests stronger and more dependable but also simplifies collaboration among developers and QA engineers. By having centralized test IDs and separation of concerns, you can now concentrate on delivering features instead of repairing busted tests.

Explore how centralized data-testid can make your Playwright test UI automation truly zero-maintenance, robust, and future-proof.