Q29 of 38 · Test design
How do you design tests that remain maintainable as the system evolves?
Short answer
Short answer: Design tests against behaviour and outcomes, not implementation details. Use the Page Object (or equivalent) pattern to isolate selectors, keep test data in fixtures, and name tests so that a failure message explains the broken behaviour without reading the code.
Detail
The main cause of brittle tests is coupling to implementation: asserting on DOM structure, internal state, or specific element IDs rather than observable behaviour. When the UI is redesigned or an API endpoint is refactored, every coupled assertion breaks simultaneously.
Practical design rules:
Test through the public interface. API tests call endpoints, not internal functions. UI tests interact through visible controls, not implementation details like data-testid tied to component internals.
Centralise selectors. In UI automation, all selectors live in Page Objects or component locator files. When a selector changes, one file changes — not 30 tests.
Make test intent readable. A failing test message should describe the broken behaviour: "user cannot log in with valid credentials" not "element .btn-primary not found at line 42."
Avoid tight data coupling. Tests that depend on a specific database row with a specific ID break whenever the seed changes. Use factories or test fixtures that create their own known state.