Skip to article
TestAutomate Join the waitlistWaitlist

Get notified at launch

TestAutomate isn't released yet. Leave your name and email and we'll notify you when it launches.

regression

Why Regression Suites Break: 5 Failure Modes

Why regression suites break: the five failure modes I keep seeing in real suites, and which are architectural rather than fixable with discipline.

The first version of this post went up in early June and blamed three things. Selectors that encode markup, waits that encode luck, and red results that arrive with no explanation attached. The months since, spent building TestAutomate’s runner and reading the recorded verdicts it produced against a real HCM test environment, convinced me that list was short by two, and that I’d been treating why regression suites break as the wrong kind of question. The interesting part isn’t the inventory. It’s which entries discipline can fix and which are baked into the test artifact itself.

Regression suites break in five distinct ways. Selectors couple tests to markup instead of behavior. Fixed waits couple them to one machine’s timing. Shared data couples tests to each other. Reporting that can’t tell a defect from an environment problem makes every red an investigation. And maintenance cost compounds across the first four until repair can’t keep pace with change. Three of the five are architectural. Discipline can’t fix them, because the brittleness lives in the artifact.

That sorting matters because teams spend years applying discipline to architectural problems, and the discipline keeps almost working. So let me take the five one at a time, with the test I use to tell one kind from the other.

Why regression suites break: the five failure modes

Here’s the map, in the order I meet them when I read a decaying suite:

  1. Selector coupling. Tests assert markup, so refactors fail them without a defect.
  2. Timing assumptions. Fixed waits encode one machine’s speed and lose the race everywhere else.
  3. Shared test data. Tests lean on state other tests created, so order and history decide the verdict.
  4. Verdict-free reds. The harness reports exceptions, not classifications, so every failure costs an investigation.
  5. Maintenance economics. The repair bill compounds across the first four until neglect becomes rational.

The first four are causes. The fifth is what they add up to, and it’s usually the one that actually kills the suite. Teams rarely decide to abandon a regression suite. They fall behind on it, one skipped red at a time.

Why do selectors break tests nobody touched?

Because a selector is a claim about the DOM at the moment of authoring, and the DOM is the part of an application that changes most freely. A test that clicks .btn-primary-2.css-1x9f isn’t testing your product. It’s testing your markup. That example survives from the original version of this post because nothing about the pattern has changed since. Rename a class, restructure a component, upgrade the UI library, and the test fails even though no behavior a user could notice is different.

The direction of the coupling is the whole problem. The application is allowed to change its implementation at will. The test froze one implementation detail and made it load-bearing. Modern frontends make this strictly worse, since CSS-in-JS tooling hashes class names per build, which turns routine deploys into locator invalidation events.

My test for this mode is simple. If a diff that changes zero user-visible behavior can fail a test, that test asserts implementation, and it will keep breaking for as long as engineers keep refactoring. Page objects are the discipline answer, and they’re worth having, but look at what they actually do. They centralize the coupling so each repair is cheaper. The breakage rate doesn’t move, because the selectors are all still there, just tidier. That’s why I file this mode as architectural. The artifact has to stop containing selectors, which is the case I make in full in selectorless testing, explained.

Why do automated UI tests keep breaking on timing?

Because a scripted test can’t observe, it can only wait and then act blindly, so every wait is a guess about how fast the app will run on a machine the author never sees. The empirical study of flaky tests by Luo and colleagues found async waits to be the single most common cause in the projects they analyzed, ahead of concurrency and test order. That matches every suite I’ve read. sleep(2000) is a bet that the network settles inside two seconds, and the bet’s loss rate changes with the runner’s CPU, the state of a cache, and the time of day.

Waiting on conditions instead of clocks is real discipline and really helps. Martin Fowler’s essay on eradicating non-determinism in tests laid that playbook out years ago. But conditions are authored guesses too. The author has to predict what “ready” looks like for every step, and applications are inventive about looking ready while they’re not, with spinners that unmount and remount and content that renders before its data arrives.

I call this mode architectural because the durable fix requires the runner to perceive rather than pause. TestAutomate’s agent works in a real browser, through your own Chrome, and observes the page as it acts, proceeding when the app has actually responded rather than when a timer expires. Runs stay bounded, so nothing hangs forever. The execution model is built this way precisely so the pacing can belong to the app instead of the author.

How does shared test data break a suite?

Quietly, and in dependency order. The classic shape is a chain. Test 12 creates a record, test 30 edits it, and everything works until someone runs test 30 alone, or in parallel, or after test 12 failed, at which point test 30 goes red with no defect anywhere in the app. The suite’s pass rate now depends on execution history, which is a property nobody ever meant to test. I walked through the full anatomy of these data cascades separately, because the data slice deserves its own post.

What makes this mode different from the previous two is that it genuinely yields to discipline. Give every test its own data, never let one lean on another’s leftovers, and the mode disappears. The honest caveat is that the discipline is a permanent tax, paid on every test forever, and suites under deadline pressure default on it fast.

Structure can lower the tax. In TestAutomate a precondition lives in an explicit, idempotent setup phase that has to end by declaring itself ready or blocked before the test’s own steps are allowed to run. When the precondition can’t be established, the run records as blocked rather than failed, because the application was never exercised and a failure would be a lie about it. The assumption still exists, but now it’s written down, checked on every run, and incapable of masquerading as a regression.

Why does every red cost an investigation?

Because most harnesses report exceptions, not verdicts. An element-not-found error might mean a real defect, a lost timing race, or a missing fixture, and all three produce the same red X with the same stack trace. The classification work still has to happen. It has simply been shifted onto whichever human reads the report, per red, per morning. That tax is what breaks trust, and broken trust is fatal. Google reported that almost 16% of its tests showed some level of flakiness, and once a team learns that red usually isn’t a defect, rerun-until-green becomes the culture and real regressions ride out the same door.

TestAutomate's suite-run timeline for one app, where blocked runs render in amber as their own outcome and a genuine failure renders in red, each row showing its verdict mix and duration.

That’s a real capture from our QA environment, and the durations tell the story on their own. Run 33 failed 2 of 2 tests in 12 minutes 10 seconds, a genuine investigation. Runs 34 through 36 each ended blocked in about a minute, 36 seconds, 49 seconds, and 1 minute 3 seconds, which says the environment needed fixing and says nothing bad about the app. On a conventional dashboard all four rows would be the same color.

The structural fix is to make the run produce a classification instead of an exception. Every TestAutomate run ends in one of four verdicts, passed, failed, skipped, or blocked, and the grading is done by a separate verifier model that reads the recorded evidence of the run and itemizes which expectations were met and which weren’t, instead of taking the acting agent’s word for it. When a test genuinely fails, the runner re-runs it once on a stronger model before standing behind the failure. And a failure’s evidence only ever becomes a Jira issue after a human reviews the candidate and clicks to file it. None of that comes from writing more careful assertions in a framework whose only outputs are green and an exception.

Why does test maintenance eventually eat the suite?

Because the repair bill is a product of three factors, how fast the app changes, how much coupling each test carries, and how many tests there are, while the capacity to pay it is a fixed slice of team attention. Every mode above is a coupling multiplier. Selectors break on refactors, waits break on infrastructure changes, shared data breaks as the suite grows, and verdict-free reds multiply the cost of every break by the triage it takes to understand it.

The death is rarely a decision. It’s rational neglect. On any given day, skipping the red test is cheaper than repairing it, so skips accumulate, quarantine becomes a one-way door, and the suite’s effective coverage shrinks while its nominal size keeps growing. Six months later there’s a folder of tests nobody believes, which costs more than no tests at all, because it still burns CI minutes and still interrupts people with false alarms.

The diagnostic I trust is the repair audit from the FAQ below. Pull a month of test-repair commits and look at what they touched. Repairs that mostly changed expected behavior mean the app changed and the suite did its job, which is the normal price of coverage. Repairs that mostly changed selectors, waits, and fixtures mean the budget went to incidentals, and no future month will be better, because the artifact guarantees the same repairs recur.

Which failure modes are architectural, and which are discipline?

Three of the five live in the artifact and the harness, one yields to discipline, and one is downstream of the rest. Laid out honestly:

Failure modeWhere the brittleness livesDoes discipline fix it?
Selector couplingIn the test artifactNo. Page objects make repairs cheaper, but the breakage rate stands
Timing assumptionsIn the artifact’s static pacingPartly. Condition waits help until “ready” itself must be guessed per step
Shared test dataBetween testsYes, with isolation and setup hygiene, paid as a permanent tax
Verdict-free redsIn the harness’s reporting modelNo. An exception can’t classify itself
Maintenance economicsDownstream of the other fourOnly by shrinking the per-break cost, never by resolve

Two caveats on that table. Disciplined teams do run long-lived scripted suites, and I won’t pretend otherwise. They manage it by paying the tax continuously, staffing suite ownership like production ownership, and treating every red as tracked work. That’s a real option with a real payroll cost. And “architectural” doesn’t mean hopeless. It means the fix has to change the artifact, not the team’s habits.

What does an architectural fix actually look like?

Like removing the coupled artifact instead of patching it. A TestAutomate test is three required fields, an id, a prompt, and an expected outcome, written in plain natural language, and the generation rules flatly ban code, JSON, YAML, and selectors from appearing in them. You can type or dictate a test the way you’d brief a colleague. There’s no locator for a refactor to invalidate and no sleep to mistune, because there’s nowhere in the format to put either. The agent finds the controls on the live page at run time, in a real browser, and the timing mode dissolves for the same reason, since the agent acts when the app has responded.

The verdict layer covers the fourth mode, with the four outcomes and the skeptical second-model grading described above. What’s left, honestly, is the tradeoffs. An agentic run spends real model tokens on every execution, and it’s slower than a bare scripted assertion, which is part of why the economics of the product start from keys you bring yourself, billed at cost. A verdict is a judgment on recorded evidence, not a mathematical certainty, and the escalation retry exists exactly because a first failed attempt can be the agent’s error rather than the app’s. And nothing here removes nondeterminism that genuinely lives in your app. What it removes is the five ways a suite manufactures failure on its own.

The question I’d put to any regression suite, ours included, is the one this post kept circling. When it breaks, what changed, the product’s behavior or the test’s assumptions about implementation? A suite that mostly breaks for the first reason is an asset. One that mostly breaks for the second is a countdown, however disciplined the team, because the artifact sets the breakage rate, and the artifact is the one thing discipline never touches.

Frequently asked questions

Why do automated UI tests keep breaking after every release?

Because most test artifacts encode how the app is built rather than what it does. Selectors reference markup, waits reference one machine's timing, and fixtures reference one database state. Each release changes those incidentals even when behavior is stable, so the suite breaks without a defect anywhere. The brittleness lives in the test's coupling, not in the app.

What is the difference between brittle tests and flaky tests?

A brittle test breaks deterministically when something incidental changes, like a renamed CSS class, and stays red until someone repairs it. A flaky test fails nondeterministically under identical code, usually from timing, concurrency, or environment. Brittleness is coupling and gets designed out of the artifact. Flakiness is nondeterminism and has to be classified at verdict time.

Can self-healing tests fix a breaking regression suite?

Partially. Self-healing repairs broken selectors by finding a new locator when the old one stops matching, which shortens repairs but keeps selectors as the foundation. It does nothing for timing assumptions, shared data, or unreadable failures. Removing selectors from the artifact entirely, and grading runs on recorded evidence, addresses those causes rather than patching one symptom.

How do I know if a regression suite is worth fixing or rewriting?

Audit a month of repairs and look at what they changed. If fixes mostly touched selectors, waits, and fixture data rather than expected behavior, the suite is spending its budget on incidentals and the artifact itself is the problem, so rewriting at the intent level pays. If fixes mostly tracked genuinely changed behavior, the suite is healthy.