{"slug": "why-your-recorded-ui-tests-break-after-every-redesign-and-how-to-build-replay", "title": "Why your recorded UI tests break after every redesign — and how to build replay that survives", "summary": "A developer detailed the engineering challenges behind building a resilient browser UI testing tool, explaining why recorded tests often break after redesigns and proposing solutions such as storing ranked locator candidates and using browser-level input via the Chrome DevTools Protocol. The developer emphasized graceful degradation and transparent failure logging to maintain test reliability.", "body_md": "If you've ever recorded a UI test, shipped one button change to production, and watched 40 tests explode in CI — you know exactly why \"record and replay\" has a bad reputation.\n\nI've spent the past year building a browser testing tool, and I want to talk about the unglamorous engineering that decides whether a recorded test survives a redesign — or dies on first contact. Not the AI magic. The four problems underneath it.\n\nMost recorders store exactly one locator per element — an XPath or a CSS chain — and freeze it at record time. That's the root of almost every \"my tests broke\" story:\n\n`//div[2]/main/section[3]/button[1]`\n\nbreaks when someone adds one `<div>`\n\nto the layout.`.v-btn.theme--dark > .v-btn__content`\n\nbreak on any styling refactor.`data-testid`\n\n— the community's favorite answer — isn't bulletproof. Third-party components don't have it, and a cleanup sprint that renames IDs silently kills dozens of tests.The fix: don't store one answer, store a ranked list of candidates.\n\n```\n{\n  \"action\": \"click\",\n  \"target\": \"Add to cart button\",\n  \"candidates\": [\n    { \"strategy\": \"test-id\",   \"value\": \"add-to-cart\" },\n    { \"strategy\": \"role-text\", \"value\": \"button 'Add to cart'\" },\n    { \"strategy\": \"text\",      \"value\": \"Add to cart\" },\n    { \"strategy\": \"css\",       \"value\": \".btn-primary.cart-action\" },\n    { \"strategy\": \"xpath\",     \"value\": \"//button[contains(., 'Add to cart')]\" }\n  ]\n}\n```\n\nAt replay time, try the strongest match first and fall through on a miss. Then — and this matters more than people think — record **which candidate matched**. If your test passed via the XPath fallback, the page has changed in a way that deserves human review, even though the run is green.\n\nRanking rules that survived contact with real apps:\n\n`data-testid`\n\n, `aria-label`\n\n, `name`\n\n) — most stable, but often missing.No single strategy wins. The goal is that a test degrades gracefully instead of snapping.\n\n`element.click()`\n\nfrom JavaScript is not a click.\n\nIt invokes the event handlers, but it skips the browser's native input pipeline: no focus management, no `:active`\n\nstate, no scroll-into-view, different behavior with native controls like `<select>`\n\n, date pickers, and file inputs. Tests pass on synthetic events and fail for real users — or the reverse.\n\nThe sturdier path is driving input at the browser level. The Chrome DevTools Protocol dispatches events through the same pipeline a real mouse and keyboard use:\n\n```\nawait cdp.send('Input.dispatchMouseEvent', {\n  type: 'mousePressed', x, y, button: 'left', clickCount: 1,\n});\nawait cdp.send('Input.dispatchMouseEvent', {\n  type: 'mouseReleased', x, y, button: 'left', clickCount: 1,\n});\n```\n\nFor typing, `Input.insertText`\n\nbehaves much closer to a human than setting `.value`\n\nand firing an `input`\n\nevent.\n\nThe trade-off: browser-level input is stricter — and that's the point. If a cookie banner covers your button, a CDP click fails, *correctly*, because a human couldn't click it either. Synthetic events would have \"passed\" while hiding a real bug. The price is that you must handle overlays deliberately instead of pretending they don't exist.\n\nWhen the primary path can't locate an element, naive tools do one of two dumb things: fail instantly (flaky suite), or silently fall back (the test drifts away from what it was testing). Both destroy trust in the suite.\n\nThe distinction that matters is *why* the element wasn't found:\n\nSo the failure ladder looks like:\n\n```\nCDP locate (primary candidates, ranked)\n  → retry within deadline          # \"not there yet\"\n  → DOM-level fallback             # CDP hit-test missed\n  → next candidate in the list     # element changed\n  → fail, with full evidence       # never silently\n```\n\nEvery fallback gets logged and shown in the run report. A green run that used three fallbacks is not the same as a green run that didn't — and your team should see the difference.\n\nDo the math on flaky tests: one ambiguous failure costs a QA engineer 15–30 minutes of \"is this a real bug or is it the test?\" Twenty failures a day is a person-day of triage, most of it wasted on non-bugs.\n\nSo the most valuable feature of a testing tool isn't execution speed — it's the quality of evidence attached to a failure:\n\nWe use AI to summarize the likely cause, but the recorded steps stay the source of truth. That's deliberate: an AI that silently \"fixes\" tests is just moving the drift somewhere you can't see it.\n\nHonest limits, because trust beats hype:\n\nThat's the core of what I've learned building [CueCast](https://www.icuecast.ai) — a no-code tool built around these ideas: multi-candidate matching, browser-level input, and evidence-first failures. The techniques above work in Playwright or Selenium too; steal them either way.\n\n**How do you handle locator brittleness?** `data-testid`\n\neverywhere? Playwright's `getByRole`\n\n? Visual AI matching? Curious what's actually holding up at your scale.", "url": "https://wpnews.pro/news/why-your-recorded-ui-tests-break-after-every-redesign-and-how-to-build-replay", "canonical_source": "https://dev.to/jimtt/why-your-recorded-ui-tests-break-after-every-redesign-and-how-to-build-replay-that-survives-50g6", "published_at": "2026-09-02 10:13:17+00:00", "updated_at": "2026-09-02 10:53:12.322761+00:00", "lang": "en", "topics": ["developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/why-your-recorded-ui-tests-break-after-every-redesign-and-how-to-build-replay", "markdown": "https://wpnews.pro/news/why-your-recorded-ui-tests-break-after-every-redesign-and-how-to-build-replay.md", "text": "https://wpnews.pro/news/why-your-recorded-ui-tests-break-after-every-redesign-and-how-to-build-replay.txt", "jsonld": "https://wpnews.pro/news/why-your-recorded-ui-tests-break-after-every-redesign-and-how-to-build-replay.jsonld"}}