Trace an action to a failure
Follow recorded actions, requests, and errors while keeping causality explicit.
Start with the tester’s expected and actual result, then trace the retained evidence around the failing action.
Worked example: the sticky Blocked tag
Section titled “Worked example: the sticky Blocked tag”Download and extract the real sample bundle. Open the recording folder described in bundle structure. This investigation uses the supplied files directly.
The application in this recording is Quackban, a card board on SpaceDucking. See About Quackban and credits for attribution to Ministry of Testing and the MoTaverse creators.
Establish the expected result
Section titled “Establish the expected result”In session.md, QA expects moving a card out of Blocked to clear its Blocked tag. The recorded path creates QB-DEMO sticky Blocked in Pond Redesign, moves it to Blocked, then to In progress. The session starts at 2026-10-07T04:43:24.309Z and lasts 42 seconds.
The description also mentions Done and a reload in earlier runs. Those are tester observations about other attempts: this recording does not contain those checks.
Match actions to the HAR
Section titled “Match actions to the HAR”Action offsets in session.md are relative to the session start. HAR startedDateTime values are absolute UTC times. Add the offset to the start to locate nearby traffic, then confirm the card ID and payload rather than relying on timing alone.
All HAR indexes below are zero-based; Markdown action numbers are one-based.
| Evidence | Time (UTC, 2026-10-07) | What to inspect |
|---|---|---|
| Action 17: Clicked Blocked | 04:43:49.834 (+25.525s) |
The first lane change. |
log.entries[2]: POST |
04:43:49.837 |
Move payload contains the card ID and status: "blocked". |
log.entries[3]: GET |
04:43:50.465 |
Board response after entering Blocked. |
| Action 20: Clicked In progress | 04:43:56.535 (+32.226s) |
The move that should clear the tag. |
log.entries[4]: POST |
04:43:56.539 |
Move payload contains the same ID and status: "in_progress"; no blocked field is sent. |
log.entries[5]: GET |
04:43:57.141 |
Board response after leaving Blocked. |
The move endpoint ends in ef480cff6458da319ee4e576095f30ac06003e3931bf8be09ee209dea582919f; the board endpoint ends in e4c54e0e02fd6c771bbd5710d32e931705db77533c1189bdcba83ed643a2f8b2. These are recorded application routes, not Recorder APIs or stable endpoint names for other builds.
Read the serialized payload
Section titled “Read the serialized payload”In the HAR, parse request.postData.text and response.content.text as JSON for these entries. Their bodies use an application serialization format with parallel p.k (keys) and p.v (values) arrays. This wrapper belongs to the captured application, not HAR or Recorder’s Report schema.
For the second move, t.p.v[0].p.k is ["id", "status"] and t.p.v[0].p.v[1].s is "in_progress". The card ID is 3229d3de-fb6d-4196-8f89-f0bf94d63392.
In the last board response, p.v[0].p.v[2].a contains the cards. The sample card is at index 44. Match its ID when inspecting another capture; array positions can change. Within that card, key 4 is status and key 10 is blocked. The status value is {"t":1,"s":"in_progress"} and the blocked value is {"t":2,"s":2}, unchanged from the preceding board response. The initial board response has {"t":2,"s":3} for blocked. QA’s note interprets this transition as false → true → true; use the application’s serializer to decode these tags in application tooling.
Save this TypeScript as inspect-sample.mts in the extracted recording folder. With Node.js 24 or later, run node inspect-sample.mts to print the relevant raw fields without sending any requests:
import { readFileSync } from "node:fs";
type EncodedField = { t: number; s?: string | number };type EncodedCard = { p: { k: string[]; v: EncodedField[] } };
const { log: { entries } } = JSON.parse(readFileSync("network.har", "utf8"));const cardId = "3229d3de-fb6d-4196-8f89-f0bf94d63392";const move = JSON.parse(entries[4].request.postData.text);console.log("move fields:", JSON.stringify(move.t.p.v[0].p.k));console.log("new status:", move.t.p.v[0].p.v[1].s);
for (const index of [1, 3, 5]) { const body = JSON.parse(entries[index].response.content.text); const cards: EncodedCard[] = body.p.v[0].p.v[2].a; for (const card of cards) { const fields = Object.fromEntries( card.p.k.map((key, i) => [key, card.p.v[i]]), ); if (fields.id.s === cardId) { console.log(index, fields.status.s, JSON.stringify(fields.blocked)); } }}Expected output:
move fields: ["id","status"]new status: in_progress1 backlog {"t":2,"s":3}3 blocked {"t":2,"s":2}5 in_progress {"t":2,"s":2}This is a reader for this sample’s payload shape, not a general HAR or application-protocol decoder.
Separate the finding from the hypothesis
Section titled “Separate the finding from the hypothesis”screenshots/003-area.png shows the card with a red Blocked badge. Its crop does not show the lane heading; use the action sequence and final board response for the In progress state. 002-full.png shows an earlier state with the card in the Blocked lane.
The evidence supports investigating the lane-change update and the relationship between status and blocked. Check the application contract: should the server clear the flag when a card leaves Blocked, should the client send another field, or can the flag be managed independently? The missing field alone does not decide that design question or prove a database defect.
All six requests returned HTTP 200. No console.log is included, and the summary reports zero console errors. The creation payload is redacted. Record these limits, then inspect application code and add a regression assertion for the agreed behavior.
Find the action in the bundle
Section titled “Find the action in the bundle”Locate the action in the numbered Recorded actions section of session.md. Confirm its label, relative offset, and surrounding sequence. Use steps.json or a generated script when you need candidate selectors or converted input values. If the action is outside the retained interval, ask for the missing context or a focused new recording.
Follow related traffic
Section titled “Follow related traffic”Use the action’s relative offset and the session start to locate nearby entries in network.har. Check method, URL, status, payload, response, and timing. Confirm the same subject with an identifier or value before treating the request as related.
Follow related console entries and available stack traces. Keep background requests, polling, and unrelated warnings in view: nearby activity can be coincidental.
Treat associations as leads
Section titled “Treat associations as leads”Artifact relationships reconstructed from timestamps are leads, not proven causes. The bundle does not preserve the Viewer’s inferred-link control or every association detail.
For example, a save action followed by a 500 response supports investigating that request. It does not by itself establish whether the failure originated in validation, a downstream service, or a separate concurrent operation.
Test the explanation
Section titled “Test the explanation”Use backend correlation where configured, or reproduce the request in an appropriate test environment. Compare the evidence with application code and server logs before naming a cause.
Leave a useful handoff
Section titled “Leave a useful handoff”Record the failing action, relevant request and error timestamps, confirmed observations, and remaining hypotheses in the issue or investigation record. Cite filenames and HAR entry indexes so another reader can verify the reasoning. Ask the tester for a new notes.md or Description when missing context must be preserved in another bundle.
Missing bodies, redacted values, or absent earlier events limit the conclusion. State those gaps rather than treating missing evidence as proof of absence.