Brewing a potion
ChatGPTChatGPT
Gradient background

What is the data-testid attribute?

Clarice Bouwer

Software Engineering Team Lead and Director of Cloudsure

Monday, 30 September 2024 · Estimated 4 minute read

TL;DR

  • data-testid attribute is used in web applications to identify elements specifically for automated testing, without affecting the user experience or design.
  • Manual testing challenges involve clicking through the interface and checking functionality across different environments, which can be fragile with changing designs.
  • Automated testing helps but can be fragile when relying on class names or IDs that change due to design updates.
  • data-testid provides resiliency by giving DOM elements a unique identity for tests, ensuring they remain stable even as the UI changes.
  • Consistency in usage is critical for maintaining a reliable testing strategy; developers must incorporate data-testid from the start.
  • Regular test maintenance is important to ensure tests stay relevant as the application evolves, requiring periodic updates to data-testid attributes.
  • Test visibility and quality are essential; tests should provide meaningful feedback, and CI/CD pipelines should include regular test runs to ensure high code quality.

I recently came across an unfamiliar attribute – data-testid – in a web application I was working on. It turned out that this trusty little fellow is used as a naming convention in automated testing.

Challenge

Once upon a time (like a few minutes-or-so ago), testing a website meant systematically clicking through the interface from one page to the next. “Does this button still work? What happens if I click here? What happens on different environments? How do I check things with A/B testing? What if a feature can be toggled or presented in multiple states?” You get the picture 😄 It’s a little like finding Waldo, except there were more buttons, bugs, and fewer striped shirts.

Automated testing is trying to become the new normal, but there’s a catch. The very thing that makes websites beautiful and user-friendly – those sleek designs – also makes them fragile for testing, especially as designs change over time.

DOM elements often hide deep within nested divs or are dynamically generated. While you could use class names or IDs, this approach often leads to broken tests due to changes in styling or structure.

An option

The data-testid attribute gives elements a secret identity, making it easier to uniquely target them in tests.

Resilience

Unlike class names or IDs, data-testid is more reliable because it’s meant specifically for testing and can be agnostic to design updates. It can be used semantically and it doesn’t affect the user experience.

Since data-testid is used specifically for testing, it won’t change just because someone decides to make buttons round instead of square. It’s there for you through thick and thin, helping you target exactly what you need without getting swept up in design trends.

Consistency

While data-testid can be a lifesaver for automated testing, it requires discipline to ensure it’s consistently used. In fast-paced environments, developers might use ad-hoc class names or IDs. To avoid this, developers need to make a habit of incorporating semantic data-testid values from the start taking a stance at user behavior instead of design.

Maintaining consistency across the team is crucial. A single developer neglecting to use data-testid can weaken the testing strategy. Team leads and code reviewers play a critical role in enforcing this practice by making it part of coding standards.

Maintenance

As your application evolves, it’s easy for tests to fall out of sync with the codebase. Developers need to continually revisit and refactor tests to ensure they reflect the current state of the application. This includes updating data-testid attributes and ensuring tests aren’t overly reliant on UI details that might shift.

Visibility

Visibility into test quality is key. Automated tests should not only pass but also provide meaningful feedback. Well-written tests serve as a safety net for developers making changes, allowing them to refactor confidently without fear of introducing bugs.

By running tests regularly as part of the CI/CD pipeline and ensuring all team members are aware of test failures, you foster a culture where quality is consistently prioritized.

Implementation

Unit tests

End-to-end tests

Examples

These examples focus on behavior rather than design tests. They highlight functionality rather than specific UI elements using class names or IDs.

Button click

In this case, you want to test the behavior triggered by a button click rather than its appearance or specific class.

Copy
<button
  id="btnSubmit"
  class="btn-round"
  data-testid="buy-now-button"
  type="button"
  onclick="submitForm()"
>
  Buy now
</button>

In the test, you'd focus on checking that the submitForm function was called when the button was clicked, regardless of where the button is placed on the page, how the button looks, what it is called or what is used to define how it is styled.

Change the input field value

Here, you test the behavior of an input field updating its value.

Copy
<input
  data-testid="username-input"
  type="text"
  placeholder="Your username"
  oninput="handleInput()"
/>

The test would verify that handleInput is invoked as the user types, focusing on behavior rather than the field’s specific design.

Toggling visibility of an element

In this example, you test the behavior of an element appearing or disappearing.

Copy
<div data-testid="dropdown-content" style="display:none;">
  <p>Dropdown content here.</p>
</div>

<button data-testid="toggle-button" type="button" onclick="toggleDropdown()">
  Toggle Dropdown
</button>

Your test would check that the toggleDropdown() function shows or hides the dropdown-content div, ensuring functionality works regardless of any style changes.

Form submission

Testing form behavior without relying on how it looks visually or is structured with class names.

Copy
<form data-testid="login-form" onsubmit="handleLogin(event)">
  <input
    data-testid="email-input"
    type="email"
    placeholder="Enter email"
    required
  />
  <input
    data-testid="password-input"
    type="password"
    placeholder="Enter password"
    required
  />
  <button data-testid="login-button" type="submit">Login</button>
</form>

In the test, you would verify that the handleLogin() function is triggered on form submission, focusing on the submission process and validation rather than the layout of the form.

Conclusion

data-testid is a powerful tool for enhancing automated testing in web applications. By providing a stable way to identify elements, it ensures that tests remain reliable even as the UI evolves. However, its effectiveness depends on consistent usage and regular maintenance to keep tests aligned with the application’s current state.