Teams budget for writing tests. Almost nobody budgets for keeping them green. That accounting error is why so many suites end up half-muted, skipped in CI, or quietly deleted a year after someone wrote them.
Authoring is the cheap part
Writing a test happens once. You understand the flow, you have the app in front of you, the selectors are fresh, and you close the PR. That cost is real but bounded.
Maintenance is unbounded. Every test you keep is a standing liability that has to survive every change to the application it covers. A suite of 500 tests isn't 500 units of work done — it's 500 things that can break next Tuesday for reasons that have nothing to do with a bug. The more coverage you add, the more surface you own. Coverage and maintenance load grow together, which is the uncomfortable part: doing the right thing (more coverage) makes the recurring bill larger, not smaller.
The result is a predictable arc. A suite is trustworthy for a few months, then flakes start, then people add reruns to mask them, then a red build stops meaning anything, then the suite is bypassed. It didn't fail because the tests were bad. It failed because nobody funded the upkeep.
Three ways a suite rots
Rot isn't one thing. It's three distinct forces, and they compound.
App drift. The product changes and the test doesn't know. A button is renamed from "Save" to "Save changes," a two-step checkout becomes three steps, a settings page moves under a new menu. The behavior the test asserts is still correct — the path to it moved. This is the most common and most legitimate kind of breakage: the app genuinely changed, and the test is now describing a version of the UI that no longer exists.
Selector decay. The test targets the DOM by structure or styling that was never meant to be a contract. CSS classes, nth-child positions, and auto-generated IDs shift every time someone refactors markup or a component library bumps a version — with no behavior change at all. This is the purest waste: a red test caused by a change that a user would never notice.
// Brittle: coupled to layout and generated class names
await page.locator('div.card > button.btn-primary.css-1x9f2a').click();
// Resilient: coupled to what the user actually sees
await page.getByRole('button', { name: 'Add to cart' }).click();
The second locator survives a restyle. The first breaks the moment a designer touches the component — and someone spends an afternoon rediscovering that the feature works fine.
Data drift. The test depends on state that doesn't hold still. It signs up casey@example.com, and on the second run that email already exists. It asserts "3 orders" against a shared staging database that now has four. It expects a coupon that expired. Data drift produces the worst failures because they're intermittent — green locally, red in CI, green on rerun — which trains everyone to hit "retry" instead of reading the result.
A resilient test generates the state it needs instead of assuming it. Anywhere a test creates something, the value has to be unique per run, or the second run collides with the first:
When the user signs up with email "casey+{{unique}}@example.com"
And the user creates a project named "Project {{unique}}"
Maintenance is the dominant cost
Add the three forces up over a test's life and the ledger inverts. Authoring is a spike at the start; maintenance is a flat tax paid every sprint, forever. A test written in an hour can easily consume that hour again three or four times a year in triage, re-selection, and reruns — and triage is more expensive than authoring, because the person doing it has to first prove the failure isn't a real bug before they're allowed to fix the test.
That last point is the hidden cost. Every red build forces a question: is this the app breaking, or the test breaking? Answering it takes a human, some context, and a few minutes of attention that always arrive at the wrong time. Multiply by a flaky suite and you get alert fatigue, which quietly converts your test suite from an asset into background noise. The failure mode isn't a suite that's wrong — it's a suite nobody reads anymore.
None of this is an argument against testing. It's an argument for counting the whole cost before you commit to coverage you can't maintain.
What regeneration changes
Traditional maintenance means patching: a test broke, so a person opens the file and edits the selector or the step that moved. You're hand-repairing an artifact. The economics only improve if the repair gets cheaper — or disappears.
The shift is generating the executable test from readable intent instead of hand-writing it. In TestVibe, you describe behavior in Gherkin — the entrypoint, the action, and the visible outcome that proves it worked — and AI generates the Playwright code from that intent:
Scenario: User signs in with valid credentials
Given the user is on the sign-in page
When the user enters a valid email and password
And the user clicks Sign in
Then the dashboard should open
And the user name should be visible in the header
When the app drifts — the sign-in label changes, the page moves — you don't hunt for the broken selector. The intent above is still true, so you regenerate against the current app instead: labels, pages, or behavior changing since the last generation is a standard reason to regenerate. That re-derives the executable code from the app as it is now, which is why the guidance is to move any manual fix up into the Gherkin rather than patch the generated file — a hand-edit can be overwritten on the next regeneration, but clearer intent survives it.
Two honest caveats keep this from being a silver bullet. First, generated code is not trusted blindly — it's verified by actually running it against your site in an isolated cloud session, and you review the result before relying on it. A failed run can still mean a real bug, missing data, or a test that needs improvement; the tool doesn't decide that for you. Second, regeneration doesn't repeal the three forces — it changes what a fix costs. Selector decay stops mattering because you never wrote the selector. App drift becomes a regenerate instead of an archaeology session. Data drift still needs unique values, but those are declared in intent, not scattered across code.
The other half is catching drift before it catches you. A scheduled automation — a nightly full-suite run against a stable environment — surfaces breakage on its own schedule, so you learn the login flow moved from a 2 a.m. report instead of a customer ticket.
Maintenance never goes to zero; state and intent still need tending. But moving the recurring cost from patching brittle code to restating intent is the difference between a suite that survives its second year and one that gets bypassed in its second quarter. Count that cost before you write the test — and if you'd rather not pay it in selectors, get early access to TestVibe.