Why your end-to-end tests keep breaking (and what to do about it)
You wrote end-to-end tests. They were green, you felt good, you shipped. Six weeks later half of them are red, nothing is actually broken, and the team has quietly agreed to stop looking at the results. This is how most test suites die: not because the app got worse, but because the tests got too expensive to keep alive. The failure is not laziness. It is the way the tests were written.
Selectors rot because they encode the wrong thing
A traditional end-to-end test finds elements by CSS selector or XPath: #signup-form > button.primary, //div[3]/form/input[2]. That works until the day it does not, and that day comes fast. A designer renames a class. A component library bumps a version and restructures the markup. A button moves into a new wrapper. The product still works perfectly for a human, but the selector points at something that is no longer there, and the test goes red.
The root problem is that the selector describes how the page is built, not what the user does. Users do not click button.primary. They click the button that says "Create account." When your test is tied to the structure instead of the intent, every cosmetic change to your UI is a potential test failure, even though nothing a customer cares about has changed.
Flakiness burns down the trust
Brittleness makes tests fail when the UI changes. Flakiness makes them fail at random. Modern apps load content asynchronously, animate transitions, and render on the client, so a test that does not wait for exactly the right moment will pass nine times and fail the tenth. The usual fixes make it worse: hard-coded sleep calls that slow every run, retry loops that paper over real bugs, ever-more-specific waits that are themselves fragile.
The damage is not just the wasted time. It is trust. Once a suite cries wolf a few times, people stop believing the red. A test suite you do not trust is worse than no suite at all, because it costs you maintenance and gives you false confidence in return.
The maintenance tax compounds
Put brittleness and flakiness together and you get a tax on every change. Ship a redesign, spend a day fixing tests. Rename a field, patch ten selectors. Because the tax scales with how much you change the UI, it hits hardest exactly when you are moving fast, which is exactly when you most need the safety net. So teams do the rational thing: they write fewer tests, skip the flaky ones, or delete the suite. Coverage decays as the app grows, which is precisely backwards.
Describe intent, not structure
The fix is to stop hard-coding the DOM. Instead of "click #submit-btn," the test should say "click the Create account button," and something should figure out which element that is on the live page, the same way a person would: by its label, its role, its position, the text around it. When the test describes the user's intent, a renamed class or a restructured wrapper does not break it, because the intent has not changed. The element gets re-resolved on every run against the page as it actually is that day.
This is what AI-driven browser testing does. A model reads the rendered page and maps "the Create account button" to the real element, run after run, so your tests survive the redesigns, refactors, and library bumps that used to break them. You stop maintaining selectors and start maintaining a plain description of the flow, which changes far less often because it is written in terms of what the product is for.
What you get back
Tests that describe intent instead of structure change the economics. Cosmetic UI changes stop breaking them, so the maintenance tax drops toward zero. The red means something again, so people act on it. And because writing a test is now describing a flow in plain language rather than hunting for stable selectors, you actually cover more of the app instead of less.
How VeriWasp fits
A VeriWasp playtest is written as plain-English steps: "load the pricing page, start checkout, click Subscribe, confirm the account unlocks." Claude reads your live page on every run and resolves each step to the real element, so there are no selectors to babysit and a renamed button does not turn your suite red. It runs in a real browser against your real deployment, captures the console and network traffic, and ties any failure to the step that caused it.
You can describe a flow and run it against your own app with a few free runs, no card required. Write the test the way you would explain the flow to a teammate, and let the tool worry about the DOM.
