Software delivery

UAT sign-off you can defend: go/no-go verdicts built on evidence

Many UAT sign-off disputes are really evidence problems. Here is how we turn a client handoff into a go/no-go verdict both sides can defend, with screenshots that hide form data.

On this page
  1. Why UAT sign-off ends in “works on our side”
  2. What UAT sign-off templates miss
  3. Go/no-go criteria agreed before the run
  4. When UAT fails: retests that cannot drift, and cleanup that proves itself
  5. Screenshot evidence that protects customer data
  6. How we fence in automated tests against staging
  7. Tradeoffs, and a UAT sign-off checklist you can use tomorrow
  8. Questions we hear

The short version

  • A UAT sign-off holds up when scope is a fixed set of named workflows and devices, and failure policies are agreed before the run.
  • Keep blocked separate from failed: a run that could not finish never counts as an app bug, and any blocked slot gives INCONCLUSIVE, not a false GO.
  • Lock retests to a fingerprint (a SHA-256 hash) of the test plan, so they prove exactly the original scope.
  • Hide form inputs before every screenshot, refuse to capture unknown embedded content, and discard the image if the checked result changed mid-capture.
  • Fence the test browser in with origins approved outside the plan, and switch staging email and payments to sandboxes.

Why UAT sign-off ends in “works on our side”

Picture a Monday, three days after handoff: your operations lead opens a teammate invitation on her phone, and the Join button sits off-screen. The agency says it works on their side, the final invoice goes on hold, and the UAT sign-off turns into an argument.

User acceptance testing (UAT) was supposed to prevent this. But the agency tested on a desktop, logged in as the owner, and their QA sheet says “Invites: pass”, with no device, no role and no date.

Then the screenshot attached to the sign-off email turns out to show a real customer’s email address.

63%

of global organizations ship code changes without fully testing them

Tricentis, 2025 Quality Transformation Report
42%

of global organizations believe poor software quality costs them $1M or more a year

Tricentis, 2025 Quality Transformation Report

The bug is real, but the dispute is about evidence: no shared scope, no agreed rule for what a failure means, and proof that leaks data.

We built a prototype release verification tool, a command-line runner that drives Chrome, to fix this. Its rules apply to any custom software acceptance.

What UAT sign-off templates miss

Search for UAT sign-off or a software handover checklist and you find templates: criteria, a severity scale, a named approver. Those help, but none make the evidence trustworthy.

A typical sign-off vs evidence you can defend
Typical sign-off
  • Scope is “the app”, so nobody can prove coverage
  • Severity argued after the bug appears
  • A crashed run reads as an app bug, or vanishes
  • The retest quietly checks less than the first run
  • Screenshots show real inputs and embedded widgets
Evidence you can defend
  • Three named workflows, each on desktop and mobile
  • Each check’s failure policy agreed before the run
  • Blocked results kept apart from findings
  • Retests locked to the plan’s fingerprint
  • Inputs hidden, unknown embeds refuse capture

The common thread is timing.

A release verdict is only as strong as the rules you agreed before you saw the result.

Go/no-go criteria agreed before the run

The Test Plan is the agreement both sides approve before the run: a JSON file naming exactly three workflows, such as “sign up and confirm an account”, “invite a teammate” and “place a test order”.

Every workflow runs in desktop Chrome and in an emulated mobile viewport (1440 by 900 and 390 by 844 in our example plan): six slots. Each slot starts with fresh browser sessions, so login state never carries over.

Each check also carries a failure policy, and both sides approve it before the first click:

  • No-go: “New account dashboard loads”. If it fails, the release stops.
  • Conditional-go: “Join button is visible on mobile”. If it fails, you ship with a known issue on record.

Under this plan, the off-screen Join button from the opening is caught before handoff and recorded as a known issue, instead of freezing the final invoice.

From there, the verdict is arithmetic. A finding is a check that ran and saw something other than agreed. A blocked slot could not finish: staging went down, a credential expired, or a button or field the script needed to use could not be found.

How the release verdict is worked out
  1. Check the run finished

    If preflight, Chrome startup or the report fails, the run stops with an error and publishes no verdict.

  2. Count the slots

    If any of the six slots is blocked or missing, the verdict is INCONCLUSIVE.

  3. Apply no-go failures

    Otherwise, any failed check marked no-go makes the verdict NO-GO.

  4. Apply conditional failures

    Failed checks marked conditional-go, and nothing worse, give CONDITIONAL GO.

  5. Everything passed

    Only six completed, passing slots produce GO.

INCONCLUSIVE is the important one. A blocked slot is never counted as a finding, so a run that could not finish never reaches the client as their bug. It is never hidden either, so a flaky run cannot become a false GO.

When UAT fails: retests that cannot drift, and cleanup that proves itself

Scope drift often happens on the retest. A “flaky” step gets deleted, and the second pass covers less than the first.

A fingerprint is a short code computed from the plan’s exact contents: change one step or one policy and the code changes. Every run stores a SHA-256 hash of the validated, normalized plan.

A retest is accepted only when:

  • The source is a complete initial run, not an earlier retest, from the last seven days.
  • Its fingerprint matches the current plan exactly.
  • Its summary matches its own results when recomputed, so a summary hand-edited without its results is rejected.
  • None of its cleanup was left unverified.

The retest then reruns only the failed or blocked workflows, but on both profiles. A mobile fix can break desktop, and we want to see it.

Cleanup is part of the test

Workflows that create data must declare how to remove it. Cleanup is armed the moment the creating step is attempted, and it runs even if later steps fail. A half-created order is still an order.

Cleanup must end in a zero-count check, such as “test order was removed: count 0”. A visible or text check must come first, because a page that failed to load also contains zero orders.

Screenshot evidence that protects customer data

Typical advice on how to mask sensitive data in test screenshots is to box the elements you remember to list. Anything you forgot, like a chat widget in an iframe, lands in the PDF your client forwards.

Verizon’s 2025 breach report found that third-party involvement in breaches doubled to 30%. An agency holding screenshots of your staging data is a third party.

So our capture fails closed, refusing the picture when in doubt. Before each screenshot, the runner:

  1. Hides every input, textarea, select, editable region and element marked for redaction (a forced display: none), so form fields never appear in the image.
  2. Walks the full page tree, including web components’ hidden sub-trees (shadow roots), and blocks capture if any iframe, embed or component host is unmarked.
  3. Takes a viewport-only PNG, then confirms the same elements are still attached and hidden and nothing new appeared.
  4. Re-evaluates the check before and after capture. If the outcome changed, it deletes the image and blocks the slot.

That last step keeps the evidence honest. A screenshot showing “passed” next to a failed result is worse than no screenshot.

Everything else visible on the page is captured, including values the app echoes back, so plans run on synthetic data only.

Text output gets the same care: plans refer to passwords by name, fields that look like secrets are rejected, and secret values are scrubbed from the run file and reports before saving.

How we fence in automated tests against staging

A browser script on client staging can still send a real email, call a live payment provider or follow a redirect into production.

An allowlist is a guest list for network traffic: if an origin (a site address such as staging.example.com) is not on it, the browser’s request never leaves the machine.

The allowlist only governs the test browser. If staging’s own server sends email or charges cards, switch those integrations to sandboxes before the run.

What happens before the first click
  1. Approve origins twice

    The plan lists its origins and the operator repeats them on the command line, so a plan cannot authorize itself.

  2. Check staging

    One request to the staging home page without following redirects; a redirect off the list stops the run.

  3. Fence the browser

    Chrome starts behind a local proxy that refuses other hosts, and request interception fails anything off the list.

  4. Smoke-test Chrome

    Actions, masking, screenshots and PDF output are tried on a local page before anything on staging changes.

  5. Lock the origin

    A local lock file per origin stops two runs from this machine hitting one staging site at once.

During the run, any popup, new window or download blocks the slot, and the page’s origin is rechecked after every action. API checks are read-only (GET or HEAD), capped at 1 MB, and raw responses are never stored.

Each run is built in a pending folder and published only after the report renders and every lock is released, so a half-written report never reaches a client.

Tradeoffs, and a UAT sign-off checklist you can use tomorrow

The narrow scope is deliberate, and it has costs:

  • Preparation is the real work. A run needs stable selectors, repeatable synthetic data and temporary credentials, or you get honest INCONCLUSIVE reports and little else.
  • Three workflows is a hard limit. More flows, browsers or native apps are a new scope, never a quiet addition.
  • Embedded content is out of reach. The runner cannot act inside iframes or shadow DOM, so a hosted checkout widget is beyond what it can test.
  • Our own tests stop short of a real browser. The 19 regression tests use a simulated browser, so every run starts with a live smoke test in Chrome.

Next, we would run the real-browser checks in our build pipeline (CI) and delete delivered evidence when the agreed retention period ends. Gates that cannot be faked also shape how we keep AI coding agents in check.

Go/no-go release checklist for UAT sign-off
  • Name the workflows in scope and state that nothing else is covered.
  • List the devices or viewports every workflow must pass on.
  • Mark each check no-go or conditional-go before anyone runs it.
  • Agree that a blocked result makes the verdict inconclusive, never a pass.
  • Fingerprint or version the plan, and refuse retests against a changed one.
  • Require cleanup for any workflow that creates data, ending in proof it is gone.
  • Hide form inputs in screenshots and block capture of unknown embedded content.
  • Approve network origins outside the plan, sandbox staging email and payments, use synthetic data, and revoke test credentials afterward.
Download this checklist (PDF)

If you are commissioning an app from an agency, or handing one over, book a free 30-minute discovery call. Bring your next release. We will pick its three riskiest workflows with you and mark each check no-go or conditional-go, so “done” is agreed before testing starts.

Questions we hear

Who should sign off UAT?

The business owner of the workflows should sign off, not the team that built them: usually a product owner or founder on the client side, with the agency’s delivery lead confirming the evidence. Both sides should approve the scope and failure policies before testing begins.

What is the difference between blocked and failed test cases?

A failed test case ran to completion and the app did something other than expected: a finding for developers. A blocked test case could not finish, for example because staging went down or a button the script needed to click could not be found. Blocked results should make the verdict inconclusive, never count as bugs or vanish.

What happens if UAT fails?

The release either stops or ships with a recorded known issue, depending on the failure policy agreed before testing. A no-go failure blocks go-live until it is fixed; a conditional-go failure does not. Either way, fix the issue and retest the affected workflows on every device profile against the unchanged plan.

Is it safe to run automated tests against staging?

It can be, if you fence the browser in. Allow only the staging, login, API and asset origins you list, block everything else, block popups and downloads, and keep API checks read-only. Staging often still connects to live email or payment services, so switch those to sandboxes before the run.

Sources

  1. Two-thirds of global organizations significantly at risk of software outage within next year (2025 Quality Transformation Report)Tricentis, via Silicon UK
  2. 2025 Data Breach Investigations ReportVerizon
Written by the SeriusTech engineering team

We build custom software, business systems, applied AI and localization technology for growing businesses. Every technical claim in this note was checked against the code it describes. Products and clients stay unnamed.

Custom software servicesFollow for new field notes
Free 30-minute discovery call

Want this solved in your business?

  1. Send a short request. We reply within 1 business day with times that suit your time zone.
  2. Talk it through for 30 minutes, free and with no obligation.
  3. Leave with a suggested first step, such as a fixed-price discovery sprint.
Free, 30 minutes, no obligation

Request your discovery call

Tell us a little about your project. We reply within 1 business day with times that suit your time zone.

Open email draft

This opens your email app with a draft to contact@seriustech.com. Nothing is sent until you send it.