Record and playback testing that doesn't rot
"Record and playback" is the oldest idea in test automation and the one with the worst reputation. Both are deserved. Recording is the fastest way anyone has found to describe what a user does; the tests it produced were also the first to break, and the hardest to fix, because the recorder threw away almost everything it saw. Here's what it has to keep.
Why record and playback got its reputation
A classic recorder watches you click and writes down one fact per click: a single selector, chosen at the moment you clicked, for a page in the state it happened to be in. That is three separate bets. The selector will still match next week. The page will be ready as quickly as it was today. And the values you typed — the URL, the login, the order number — are the ones you want on every run, in every environment, forever.
None of those bets is safe. When the first one loses, the test fails with "element not found" and no idea what changed. When the second loses, it fails on Tuesday and passes on Wednesday, which is how a suite earns the word flaky. When the third loses, someone edits a URL by hand in forty test files. And because the recording was the only artefact, a team that outgrew the tool had nothing to take with them. No-code turned out to mean no exit.
What has to be captured at record time
The fix isn't a smarter playback engine bolted onto the same thin recording. The recording itself has to carry enough that playback has something to work with when the page disagrees with it. QAright's recorder stores four kinds of thing per step that a classic recorder never did.
More than one way to find each element
Every element you interact with is described by up to fourteen candidate locators — test ids, ARIA role and name, label association, visible text, scoped CSS paths — and each is scored for how likely it is to survive the next deploy. A generated id like
a7x9f2scores low even though "id" is normally the strongest signal; a human-named one likelogin-btnscores high. The best candidate is the primary. The rest stand behind it, ranked, for the day it breaks.How long the page actually took
When a click causes a navigation, the recorder measures how long the page took to settle and attaches that number to the step. On playback that measurement, not a fixed sleep and not a global default, is the wait budget — and because it is a measurement, the engine is allowed to widen it when the app has a slow day. A wait someone typed in by hand gets no such treatment, because nothing backs it.
What the page looked like
A snapshot of the DOM around each element is stored with the step. It is what makes the later phases of healing possible — you can't score "the element that looks most like the one I recorded" without a record of the one you recorded — and it is what drift detection compares against between runs, so a changed page is reported before a test has to fail on it.
Where a value came from, not just what it was
A recorded test isn't hardwired to the URL, the login and the data you happened to use while recording. Environments hold the base URL and credentials for UAT, staging and production; data profiles hold named sets of test data; a faker engine generates the rest. A step's value can point at any of them with a token, and the choice of environment and profile is made per run — never saved into the test. Forty tests share one setting instead of forty copies of one string.
No-code doesn't mean no code exists
The other half of the reputation is lock-in: a proprietary recording format that only the vendor's player understands. The honest answer to that is an export you'd actually be willing to run without us.
Every suite exports to a self-contained C# Playwright project — the
tests, the resilient execution engine with its locator fallbacks, the data
generators and a project file. It compiles and runs with dotnet test
on a machine that has never heard of QAright. You can choose the shape of the
code: the resilient engine with the full fallback chain, plain Playwright calls
with no helper library at all, or an NUnit class with Given/When/Then structure
and a .feature file for a BDD suite. Page objects are generated
from the app model the recorder built, so the exported code reads the way a
person would have written it. Environment values in the exported bundle come
from the CI machine's own environment variables — the hosted environment
and data-profile configuration stays with the hosted product, on purpose.
What happens when it does break
A test that records enough can repair itself when the page changes, and the order it tries things in matters more than the fact that it tries. The recorded candidates go first, re-checked for uniqueness against the live page; a scan of the page against the recorded snapshot comes after; a model, if you enable one, comes last. Assertions are never healed, ambiguity is never resolved by guessing, and a fallback only replaces the primary after winning three runs in a row. The full chain, and what it refuses to do, is on the self-healing page.
What recording is still bad at
Recording captures what a user does. It doesn't capture what a user is thinking, so branching logic, arithmetic on captured values and "keep checking until the job finishes" are not things a click can express. For those, the recorded steps are editable in place: a poll-until step with its own interval, an API step that calls a backend directly, assertions built by pointing at an element and choosing what should be true of it. The point is that you start from a recording that carries enough to last, and add logic where a recording genuinely can't — not that every test is a recording.
Keep reading. What a recording captures about each element is only as good as the locators; choosing locators that survive UI changes explains the order. How it works follows one recording from the first click to the report.
Record a test against your own app on the free plan, then export it and read what came out.