Most UAT best practices are advice about the session: who to invite, how to write the scenarios, when to book it. Findings are lost after it, because what a business tester can describe is a fraction of what reproduction needs. The practice missing from those lists is deciding what gets captured before anybody opens the build.
The advice stops at the moment somebody finds something
Read four guides on running user acceptance testing and the shape repeats. Scenarios are written as outcomes rather than as clicks. The invitation goes to the people who do the work rather than to their managers. Acceptance criteria are agreed before the work starts, somebody is named to accept on behalf of the business, and the whole thing is booked early enough that a finding can still change what gets built. All of that is right, and our own UAT test plan template argues for most of it.
Then somebody finds something, and the advice thins to a line about recording defects. Underneath that line is a business user typing a description into a form.
That is the weakest link in the chain and it is load bearing. A finance manager knows the invoice came out wrong. They do not know the request came back with a 500, or that the same click works in one browser and not in another. The ticket says the invoice button did not work. An engineer walks the path, watches it work, and closes the finding as irreproducible. Nothing about the session was run badly.
What a business tester can describe, and what reproduction needs
What separates the two is vocabulary and access rather than effort or care. Steps to reproduce that work on somebody else’s machine need the conditions around the actions, and most of those are invisible from inside the browser window: the exact engine and version, the viewport, the role the account holds, the state of the data, whether this was a cold load or the fifth navigation inside a single-page app.
| What the ticket needs | What a business tester writes | Who can supply the rest |
|---|---|---|
| The failing call | “It timed out” | The page, if something is watching the network |
| The error | “The screen went blank” | The page, if something is wrapping the console |
| The environment | “My laptop” | The page, which already knows |
| The route | “On the review screen” | The page, with the query string cut off |
| The sequence of actions | “I clicked submit” | The page, which watched it happen |
The third column carries the same answer five times, and that is the finding. None of it asks the tester to learn anything; all of it asks for one decision, made before the session, about what the page collects on its own.
Decide what gets captured before the session, not during it
Capture belongs with seeding the test data and preparing the accounts: a setup task, done once, before anybody opens the build.
In practice that means a tag on the pages under test that watches the session and freezes it when a report is filed. Ours flushes what it holds three seconds after capture starts and every ten seconds after that while the tab is visible, so a report filed half a minute in still carries that half minute. It also counts the clicks it cannot interpret: three clicks on one target inside a thousand milliseconds are recorded as a rage click, which is as close as a machine gets to hearing somebody say a button does nothing.
| Capture | The declared contract | Why it sits there |
|---|---|---|
| First flush after capture starts | 3 seconds | An early report still carries what came before it |
| Flush thereafter | 10 seconds | Small buffers lose little when a tab closes |
| Rage click | 3 clicks on one target inside 1,000 ms | Movable between 2 and 10 clicks, across 100 to 10,000 ms |
| Reporter-recorded video | 60 seconds at 15 fps | Chunks leave every 250 ms, so a closed tab keeps the take |
| Typed characters in replay | Never recorded | Fixed in the recorder, not offered as a setting |
| Stored page URL | Query string removed | Cut before the entry is written |
Two of those rows are limits wearing the shape of features. A take that reaches sixty seconds stops and labels itself, and a replay that never records typed characters cannot show which value broke a form. The masking is not adjustable, which is deliberate and is also a real cost during UAT: a finding about one specific input has to be written out in words, because the recording shows the field being typed into and not what went in.
What travels with the report is a closed list and a short one: page path, first seen, browser, operating system, device, screen, viewport, time zone and language, alongside the console and the network calls from the seconds before. Request and response bodies are never captured. What that buys is the first read: with evidence capture in place, an engineer opening a UAT ticket starts at reproduction rather than at a reply asking which browser it was.
Evidence that declares its own gaps
Automatic capture fails, and how a system reports its own failures decides whether the evidence it does produce is worth trusting. A recorder that goes quiet under load and writes a shorter file looks identical to a tester who sat still for a minute. The engineer cannot tell an unperformed step from an unrecorded one, so they guess, and the cheap guess is that the reporter misremembered.
Absence therefore needs a vocabulary. There are 29 named reasons a session can carry no replay, and the one that applied is written onto the ticket instead of being left as a shrug.
| Kind of gap | What it means for a UAT finding |
|---|---|
| The widget stood down to protect the host page | The interval is missing because the page was busy, not because nothing happened |
| The tab was hidden, or restored from the browser’s history cache | The browser paused the page; the tester carried on |
| A buffer or a network budget was spent | Data was dropped on purpose rather than lost |
| The project, the sampling rule or consent excluded the session | No recording was ever coming, and the plan should have said so |
The last row is worth checking before a UAT round rather than after it: a project with recording switched off stays silent for a reason nobody reads until the first finding arrives without one.
The clock nobody puts in the exit criteria
A good test plan names which severities block sign-off. Almost none names how long a finding may sit before somebody reads it, and evidence has a shelf life the ticket holding it does not.
| What is kept | For how long |
|---|---|
| The ticket, with the console and network snapshot frozen onto it | Indefinitely; retention never removes a ticket |
| The captured log stream around the session | 30 days |
| A project’s recordings | 30 days by default, settable from 1 to 365 |
| An idle session, and whatever replay it still holds | 90 days |
That table is a schedule rather than a policy. The frozen snapshot answers the questions the report itself raised; what answers a question nobody thought to ask at submission, meaning the session replay an engineer would scrub through and the log stream around it, expires on the dates above.
That limit is worth stating plainly, because it belongs to us rather than to the practice in general. A UAT finding deferred past 30 days has lost its log stream, and on a project running the default recording window it has lost its replay on the same day; 90 days takes the session itself. What survives is more than most processes leave behind and less than was there on the day. So the exit criterion that follows is a triage window rather than a triage intention: findings get read while their evidence still exists, or the argument about whether to fix them is had with less than the session produced.