Introduction

Automated testing is a cornerstone of modern software development, ensuring that your application is robust and reliable. However, the effectiveness of your testing suite is only as good as its weakest link, which often turns out to be how you target elements for testing. In this blog post, we’ll delve into why using data-* attributes is a superior approach compared to relying on CSS attributes like id, class, and tag.

The Problem with CSS Attributes

Scenario 1: The Shifting Class Names

Imagine you have a button in your HTML with a specific class name, and you target this in your test.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<button class="btn-submit">Submit</button>
<button class="btn-submit">Submit</button>
<button class="btn-submit">Submit</button>
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Test Code
const button = document.querySelector('.btn-submit');
// Test Code const button = document.querySelector('.btn-submit');
// Test Code
const button = document.querySelector('.btn-submit');

Now, what happens if a developer changes the class name for styling purposes?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<button class="btn-primary">Submit</button>
<button class="btn-primary">Submit</button>
<button class="btn-primary">Submit</button>

Your test will break, unable to find the .btn-submit class.

Scenario 2: The Unstable IDs

Similarly, targeting elements by their id attributes can be problematic. Consider this example:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div id="user-info">John Doe</div>
<div id="user-info">John Doe</div>
<div id="user-info">John Doe</div>
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Test Code
const userInfo = document.querySelector('#user-info');
// Test Code const userInfo = document.querySelector('#user-info');
// Test Code
const userInfo = document.querySelector('#user-info');

If the ID changes for any reason:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div id="profile-info">John Doe</div>
<div id="profile-info">John Doe</div>
<div id="profile-info">John Doe</div>

Your test will fail, searching in vain for #user-info.

Scenario 3: The Tag Swap

Even targeting by tag can be risky. For instance:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<p>Some text here</p>
<p>Some text here</p>
<p>Some text here</p>
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Test Code
const text = document.querySelector('p');
// Test Code const text = document.querySelector('p');
// Test Code
const text = document.querySelector('p');

If the tag changes:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<span>Some text here</span>
<span>Some text here</span>
<span>Some text here</span>

Your test will break, unable to find a p tag.

The Solution: Data-* Attributes

Why Use Data-* Attributes?

data-* attributes are designed for storing custom data private to the page or application. They are stable and won’t interfere with styling or behavior, making them ideal for robust testing.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<button data-test="submit-button">Submit</button>
<button data-test="submit-button">Submit</button>
<button data-test="submit-button">Submit</button>
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Test Code
const button = document.querySelector('[data-test="submit-button"]');
// Test Code const button = document.querySelector('[data-test="submit-button"]');
// Test Code
const button = document.querySelector('[data-test="submit-button"]');

Proactive Strategies for Bulletproof Testing

  1. Use a Test Attribute Prefix: Standardize a common prefix like data-test or data-qa for test-specific attributes.
  2. Automate Attribute Addition: In frameworks like React, create higher-order components that automatically add data-* attributes.
  3. Dynamic Elements: Ensure your logic adds data-* attributes to dynamically generated elements.
  4. Custom Selectors: Write custom selectors that internally use data-* attributes for complex queries.
  5. Attribute Versioning: Consider versioning your data-* attributes if you anticipate structural changes.
  6. CSS Variables for Dynamic Styling: Use CSS variables to keep data-* attributes solely for testing.
  7. Tooling: Opt for tools like TestCafe or Cypress that facilitate easy targeting of data-* attributes.

Conclusion

By using data-* attributes for targeting elements in your automated tests, you can create a test suite that is robust, maintainable, and less prone to breakage due to UI changes. Adopt this best practice and make your automated testing truly bulletproof.