Release browse-gate triage runbook¶
When a Test→Prod (or Dev→Test) promotion shows a red Browse smoke / admin /
payment gate (AzDO release def 31 → browse-ui-tests.yml), do not assume a
product regression and do not silently re-run until green. Triage it: a browse
failure is either a product bug (block the promotion) or a test-harness bug
(fix the test, then promote). This runbook is the procedure for telling them apart
and is the companion to release-test-visibility.md
(which covers where the results show up, not how to act on a red one).
Rule of thumb from the Jun-2026 Release-95 gate: both failing tests were test-harness bugs (a seed payload missing a field that the real UI always sends, and a helper relying on a debug-only AngularJS API). Neither blocked prod. Always run the failing test locally before concluding "the product is broken."
1. Pull the exact failures from the release¶
The consolidated sticky comment on the Test→Prod PR links the GitHub Browse UI
Tests run; open the failing suite's job log, or query AzDO directly:
# Failed cases for a release test run (runId from the release Tests tab / link)
az devops invoke --org https://dev.azure.com/raklet-git --area test --resource results \
--route-parameters project=raklet-git runId=<RUNID> \
--query-parameters outcomes=Failed --api-version 7.1 -o json
Record, per failure: the test basename (e.g. 52-events-manual-ticket-registration),
the first-line error + any errorMessage/stackTrace, and the org it ran
against (single-leg runs use the fallback general-test-account; sharded
admin-regression legs use browse-ci-admin-a/b/c — see
resolve-browse-shard-org.ps1).
2. Reproduce locally against the same environment¶
The release runs against .raklet.net. Run the same test from a fresh worktree
off master, using the raklet-net profile in
browse-tests/local.settings.json:
# Acquire the browse lock first (shared daemon)
pwsh scripts/dev/browse-lock.ps1 acquire -Owner triage -Intent "release gate" -Wait
# Load the raklet-net profile into the env the suite reads, then run just the failing test(s)
$p = (Get-Content browse-tests/local.settings.json -Raw | ConvertFrom-Json).profiles.'raklet-net'
$env:BROWSE_TEST_PERMALINK = $p.permalink; $env:BROWSE_TEST_EMAIL = $p.email
$env:BROWSE_TEST_PASSWORD = $p.password; $env:BROWSE_TEST_ADMIN_URL = $p.adminUrl
$env:BROWSE_LOGIN_MANAGER_TIMEOUT = '180000' # the deployed SPA is cold on first hit
./browse-tests/run.ps1 -Suite admin-regression -TestDomain .raklet.net -TestNameFilter '52-events-manual-ticket-registration'
Notes:
- run.ps1 restarts the daemon + retries once, so a single about:blank nav drop
on a dev box self-heals. If your local daemon is wedged, kill browse procs and
browse restart before relying on a local red.
- For a clean, authoritative re-run, dispatch on your branch instead of trusting a
flaky local box: Actions → Browse UI Tests → Run workflow, suite=admin-regression,
test_name=<basename or wildcard>. This runs on a fresh CI daemon.
3. Classify: test bug or product bug¶
Drive the failing operation directly and capture the real error the harness
hides. Browse logs collapse promise rejections to Object; re-issue the call with
a rejection handler that stringifies status/data:
api.createEvent({organisationId: orgId}, payload).$promise.then(
function(r){ return 'OK:' + JSON.stringify(r); },
function(e){ return 'REJECT:' + JSON.stringify({status: e.status, data: e.data, url: e.config && e.config.url}); });
Then decide:
| Signal | Likely product bug (block) | Likely test bug (fix test, promote) |
|---|---|---|
| API rejection | 500 reproducible with the same payload the real UI sends |
500/400 only with a payload the UI never sends (missing/garbled field) |
AngularJS .scope() / .controller() returns undefined |
n/a | Bundle runs debugInfoEnabled(false) on test/prod — use the $rootScope injector-walk instead (see Set-DirectoryDisplayFields) |
| Selector/timeout only | Element genuinely absent for users | Selector drift, missing wait, or a read-after-write/index lag |
| Recent diff on the promoted branch touches the path | Inspect that PR closely | — |
Sentry shows the same exception on environment:test from real traffic |
Product | If only your synthetic call appears, suspect the test |
Cross-check git log origin/prod..origin/test -- <area> for what's new in this
promotion, and Sentry (environment:test, release:Raklet.Api*) for whether real
users hit the same error.
4. Fix the right layer, re-verify, record¶
- Test bug → fix the helper/test so it exercises the real path (send the field the UI sends; reach the model the way that works on the deployed bundle).
- Product bug → it blocks the promotion. Open a fix PR through the normal
Dev→Test→Prod chain; do not promote until it lands on
test. - Re-verify by dispatching
browse-ui-tests.yml(test_name=<basename>) on the fix branch; confirm green on a clean CI daemon. - If the cause is a chronic, non-product flake unrelated to the change, quarantine it
in
browse-tests/known-failures.json(run-once, non-gating) with a tracking ticket — never just re-run to green.
5. Then re-read the go/no-go¶
A green browse gate is necessary but not sufficient. The promotion also gates on the
promotion-todo-gate (every rolled-up PR's (verify-in-test) todos checked) and
net-new Sentry issues. Clear those before merging the Test→Prod PR — see
reference: master→test→prod promotion and the promotion checklist.