Building Testable Websites: Empower Your Entire Organization
Imagine this scenario: A development team races to launch a new feature, only to discover that half their automated tests are failing because an unexpected style change broke key selectors. QA scrambles to update the locators, while the product manager worries about delivery deadlines. Suddenly, what should have been a quick UI tweak balloons into a multi-day ordeal. Sound familiar? This story is more common than you might think—but it’s avoidable. By building websites with testability in mind from the very start, you can streamline development, reduce flakiness in your tests, and ship features with greater confidence. The best part? It doesn’t take much extra work—just a few strategic decisions that pay off for your entire organization. Below, we’ll cover ten practical tips for making your website fully testable and resilient to change. You’ll see how these small details can yield big results for Developers, QA engineers, and Product Managers alike. 1. Embrace Test-Friendly Locators Common Pitfall Teams often rely on dynamic IDs or CSS classes for identifying elements (e.g., .btn-primary)—until a designer changes the color scheme and all .btn-primary references vanish. That’s a recipe for flaky tests. What to Do Instead Use data-test attributes for key elements: Sign In Sign In Avoid user-facing text as a locator (e.g., cy.contains("Sign In")) because marketing or UX tweaks might break your test. Why It Helps Developers: Less time fixing broken selectors whenever a designer changes class names. QA: Reliable locators mean fewer false negatives. Product Managers: Feature updates can roll out faster with tests that don’t crumble under small UI tweaks. For deeper guidance, see Cypress Best Practices: Selecting Elements or Playwright: Locators. 2. Maintain a Clear, Consistent HTML Structure Common Pitfall Endless nested tags, random IDs, and unclear naming conventions force testers to craft brittle locators—like div:nth-child(3) > div > button. What to Do Instead Use semantic HTML5 elements (, , , , etc.). Keep nesting shallow and avoid auto-generated IDs that change on each build. Why It Helps Developers: Easier to navigate and maintain code. QA: Predictable DOM structure reduces complexity in writing tests. Organization: Cleaner code base fosters quicker onboarding for new team members. Learn more at MDN: HTML5 Semantic Elements. 3. Separate UI from Business Logic Real-World Example A company had all its domain logic (like user authentication) directly in React components. A single UI refactor caused massive ripple effects in their test suite. By extracting that logic into an API layer, they reduced test failures by over 40% on subsequent refactors. What to Do Expose business rules through a stable API (e.g., REST, GraphQL). Keep front-end code focused on rendering and minimal state handling. Why It Helps Developers: Can test domain logic independently (unit or integration tests) without worrying about UI changes. QA: Gains a consistent backend interface for data setup or validation. Product Managers: Features can be iterated on quickly because UI redesigns won’t derail underlying logic. Reference: Martin Fowler on PresentationDomainSeparation. 4. Provide Utilities for Test Data Setup Common Pitfall Spending 10 steps in the UI to create a test user before testing a core scenario. If something breaks mid-setup, your entire flow is blocked. What to Do Create dedicated test endpoints (e.g., POST /api/test-setup/create-user) to seed users, products, or other data in seconds. Leverage fixture files: Preload databases with known states (like sample user accounts or product listings). Why It Helps Developers: Faster local testing—no need to manually create data repeatedly. QA: Can focus on verifying actual scenarios rather than wrestling with data setup. Product Managers: Quicker test cycles mean rapid feature validation and release. See Cypress Docs on test data requests. 5. Adopt a Page Object or Screenplay Pattern Before vs. After Before (Hardcoded Selectors Everywhere) it('logs in the user', () => { cy.visit('/login'); cy.get('[data-test="username-input"]').type('user1'); cy.get('[data-test="password-input"]').type('pass123'); cy.get('[data-test="login-button"]').click(); // repeated in multiple tests... }); After (Page Object Abstraction) class LoginPage { elements = { usernameInput: () => cy.get('[data-test="username-input"]'), passwordInput: () => cy.get('[data-test="password-input"]'), submitButton: () => cy.get('[data-test="login-button"]'), }; login(username, password) { this.elements.usernameInput().type(username); this.elements.passwordInput().type(password); this.elements.submitB
Imagine this scenario: A development team races to launch a new feature, only to discover that half their automated tests are failing because an unexpected style change broke key selectors. QA scrambles to update the locators, while the product manager worries about delivery deadlines. Suddenly, what should have been a quick UI tweak balloons into a multi-day ordeal. Sound familiar?
This story is more common than you might think—but it’s avoidable. By building websites with testability in mind from the very start, you can streamline development, reduce flakiness in your tests, and ship features with greater confidence. The best part? It doesn’t take much extra work—just a few strategic decisions that pay off for your entire organization.
Below, we’ll cover ten practical tips for making your website fully testable and resilient to change. You’ll see how these small details can yield big results for Developers, QA engineers, and Product Managers alike.
1. Embrace Test-Friendly Locators
Common Pitfall
Teams often rely on dynamic IDs or CSS classes for identifying elements (e.g., .btn-primary
)—until a designer changes the color scheme and all .btn-primary
references vanish. That’s a recipe for flaky tests.
What to Do Instead
-
Use
data-test
attributes for key elements:
-
Avoid user-facing text as a locator (e.g.,
cy.contains("Sign In")
) because marketing or UX tweaks might break your test.
Why It Helps
- Developers: Less time fixing broken selectors whenever a designer changes class names.
- QA: Reliable locators mean fewer false negatives.
- Product Managers: Feature updates can roll out faster with tests that don’t crumble under small UI tweaks.
For deeper guidance, see Cypress Best Practices: Selecting Elements or Playwright: Locators.
2. Maintain a Clear, Consistent HTML Structure
Common Pitfall
Endless nested Why It Helps
Learn more at MDN: HTML5 Semantic Elements.
A company had all its domain logic (like user authentication) directly in React components. A single UI refactor caused massive ripple effects in their test suite. By extracting that logic into an API layer, they reduced test failures by over 40% on subsequent refactors.
Why It Helps
Reference: Martin Fowler on PresentationDomainSeparation.
Spending 10 steps in the UI to create a test user before testing a core scenario. If something breaks mid-setup, your entire flow is blocked.
Why It Helps
Before (Hardcoded Selectors Everywhere) After (Page Object Abstraction) Why It Helps
Explore Selenium’s Page Object Model or the Screenplay Pattern.
Text-based selectors ( Why It Helps
A retail site worked great on Chrome but had major layout issues on Safari iOS. Because they never tested Safari in CI, they found out too late—impacting iPhone users at launch.
Why It Helps
Ever had QA test “the latest build,” only to realize they’re running last week’s code on the staging server?
Why It Helps
Flaky tests that say “element not found” or “element is not clickable” occur because the DOM hasn’t fully loaded or an animation is still running.
Why It Helps
A new hire on the QA team spends days deciphering how to locate elements or seed data, delaying test coverage of new features. This frustration is entirely avoidable.
Why It Helps
Consider Confluence or a GitHub Wiki for structured documentation.
By taking these steps—unique Next Steps
By designing with testability in mind, you’ll transform those dreaded late-night fire drills into smooth, efficient releases—and keep your team moving forward with confidence. div:nth-child(3) > div > button
.
What to Do Instead
, ,
,
, etc.).
3. Separate UI from Business Logic
Real-World Example
What to Do
4. Provide Utilities for Test Data Setup
Common Pitfall
What to Do
POST /api/test-setup/create-user
) to seed users, products, or other data in seconds.
5. Adopt a Page Object or Screenplay Pattern
Before vs. After
it('logs in the user', () => {
cy.visit('/login');
cy.get('[data-test="username-input"]').type('user1');
cy.get('[data-test="password-input"]').type('pass123');
cy.get('[data-test="login-button"]').click();
// repeated in multiple tests...
});
class LoginPage {
elements = {
usernameInput: () => cy.get('[data-test="username-input"]'),
passwordInput: () => cy.get('[data-test="password-input"]'),
submitButton: () => cy.get('[data-test="login-button"]'),
};
login(username, password) {
this.elements.usernameInput().type(username);
this.elements.passwordInput().type(password);
this.elements.submitButton().click();
}
}
export default new LoginPage();
// Test
import LoginPage from '../pageObjects/loginPage';
it('logs in the user', () => {
LoginPage.login('user1', 'pass123');
});
6. Decouple Visual Elements from Automation Locators
Reality Check
cy.contains("Sign In")
) are tempting but risky—marketing might change that button text to “Log In” and break half your tests overnight.
What to Do
data-test="login-button"
) for every clickable element or field you want to automate.
7. Consider Cross-Browser and Cross-Device Early
Real-World Example
What to Do
8. Version and Deploy Consistently
The Danger of “Version Confusion”
What to Do
9. Minimize (or Carefully Handle) Dynamic and Asynchronous Elements
Common Pitfall
What to Do
cy.get('[data-test="spinner"]').should('not.exist')
).
10. Document Your Testing Hooks and Strategy
Real-World Example
What to Do
data-test="..."
).
Conclusion: Small Changes, Big Impact
data-test
attributes, a clean HTML structure, stable test data APIs, and well-documented best practices—you’re not just making QA’s life easier, you’re empowering your entire organization:
data-test
attributes to your critical elements) and implement it this week.