browser-commander 0.21.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jscpd.json +2 -1
- package/.prettierignore +1 -0
- package/CHANGELOG.md +14 -0
- package/package.json +4 -2
- package/src/browser/attach/snapshot.js +4 -4
- package/src/browser/browser-cookie-cache.js +14 -2
- package/src/browser/profile-directory.js +109 -6
- package/src/browser/real-browser.js +38 -6
- package/src/cli/args.js +47 -0
- package/src/cli/sessions.js +4 -0
- package/src/traces/bundle.js +3 -2
- package/src/traces/observers.js +14 -5
- package/src/traces/recorder.js +3 -0
- package/src/traces/viewer.js +16 -8
- package/tests/e2e/playwright.e2e.test.js +1 -0
- package/tests/e2e/puppeteer.e2e.test.js +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0001.state.json +54 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0002.state.json +60 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.html +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.png +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/checkpoints/0003.state.json +60 -0
- package/tests/fixtures/traces/conformance/expected/bundle/events.ndjson +18 -0
- package/tests/fixtures/traces/conformance/expected/bundle/manifest.json +77 -0
- package/tests/fixtures/traces/conformance/expected/bundle/mutations/0001.ndjson +2 -0
- package/tests/fixtures/traces/conformance/expected/bundle/mutations/0002.ndjson +1 -0
- package/tests/fixtures/traces/conformance/expected/bundle/viewer.html +309 -0
- package/tests/fixtures/traces/conformance/expected/redaction.json +86 -0
- package/tests/fixtures/traces/conformance/expected/trace.lino +27 -0
- package/tests/fixtures/traces/conformance/scenario.json +249 -0
- package/tests/unit/browser/attach/extension-relay.test.js +1 -1
- package/tests/unit/browser/attach/snapshot-launch.test.js +10 -1
- package/tests/unit/browser/attach/snapshot.test.js +23 -4
- package/tests/unit/browser/browser-cookie-lock.test.js +102 -0
- package/tests/unit/browser/launch-building-blocks.test.js +127 -4
- package/tests/unit/browser/migration/index.test.js +1 -1
- package/tests/unit/browser/open-in-user-browser.test.js +1 -1
- package/tests/unit/browser/parity.test.js +1 -1
- package/tests/unit/browser/real-browser.test.js +1 -1
- package/tests/unit/browser/storage-state.test.js +2 -0
- package/tests/unit/cli/main.test.js +30 -2
- package/tests/unit/core/navigation-manager.test.js +11 -1
- package/tests/unit/playwright-protocol-coverage.test.js +231 -0
- package/tests/unit/puppeteer-bindings-coverage.test.js +214 -0
- package/tests/unit/scripts/ci-timeout-budgets.test.js +23 -0
- package/tests/unit/scripts/feature-parity.test.js +111 -5
- package/tests/unit/traces/conformance.test.js +92 -0
- package/tests/unit/traces/reader.test.js +1 -1
- package/tests/unit/traces/recorder.test.js +1 -1
- package/tests/unit/traces/viewer.test.js +78 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<!-- Inert by default: no network, no captured scripts, images only from data URLs. -->
|
|
6
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; frame-src 'self' data:;">
|
|
7
|
+
<title>Browser Commander trace</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root { color-scheme: light dark; font-family: system-ui, sans-serif; }
|
|
10
|
+
body { margin: 0; display: grid; grid-template-columns: 22rem 1fr;
|
|
11
|
+
grid-template-rows: auto 1fr; height: 100vh; }
|
|
12
|
+
header { grid-column: 1 / -1; padding: 0.5rem 1rem; border-bottom: 1px solid #8884; }
|
|
13
|
+
h1 { font-size: 1rem; margin: 0 0 0.25rem; }
|
|
14
|
+
.meta { font-size: 0.75rem; opacity: 0.75; }
|
|
15
|
+
.meta.replay { opacity: 0.9; font-style: italic; }
|
|
16
|
+
aside { overflow: auto; border-right: 1px solid #8884; }
|
|
17
|
+
main { display: grid; grid-template-rows: auto 1fr auto; overflow: hidden; }
|
|
18
|
+
ol { list-style: none; margin: 0; padding: 0; font-size: 0.8rem; }
|
|
19
|
+
li { padding: 0.35rem 0.75rem; border-bottom: 1px solid #8882; cursor: pointer; }
|
|
20
|
+
li.selected { background: #3b82f633; }
|
|
21
|
+
li .kind { font-weight: 600; }
|
|
22
|
+
li .at { opacity: 0.6; float: right; }
|
|
23
|
+
iframe { width: 100%; height: 100%; border: 0; background: #fff; }
|
|
24
|
+
.panel { padding: 0.5rem 1rem; border-top: 1px solid #8884; max-height: 14rem;
|
|
25
|
+
overflow: auto; font-size: 0.8rem; }
|
|
26
|
+
table { border-collapse: collapse; width: 100%; }
|
|
27
|
+
td, th { text-align: left; padding: 0.15rem 0.5rem; border-bottom: 1px solid #8882;
|
|
28
|
+
font-family: ui-monospace, monospace; font-size: 0.75rem; }
|
|
29
|
+
.controls { padding: 0.5rem 1rem; display: flex; gap: 0.5rem; align-items: center; }
|
|
30
|
+
.dropped { color: #b91c1c; }
|
|
31
|
+
button { font: inherit; }
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<header>
|
|
36
|
+
<h1>Browser Commander trace</h1>
|
|
37
|
+
<div class="meta">
|
|
38
|
+
mode continuous · outcome partial · engine playwright ·
|
|
39
|
+
3 checkpoints · 18 events ·
|
|
40
|
+
1 dropped · started 2026-01-01T00:00:00.000Z
|
|
41
|
+
</div>
|
|
42
|
+
<div class="meta replay">partial diagnostic replay: checkpoints, DOM mutations, insertion positions and removals, live control state, page and frame identities</div>
|
|
43
|
+
</header>
|
|
44
|
+
<aside><ol id="timeline"></ol></aside>
|
|
45
|
+
<main>
|
|
46
|
+
<div class="controls">
|
|
47
|
+
<button id="play" type="button">Replay mutations</button>
|
|
48
|
+
<button id="step-forward" type="button">Step</button>
|
|
49
|
+
<button id="reset" type="button">Reset</button>
|
|
50
|
+
<span id="step"></span>
|
|
51
|
+
</div>
|
|
52
|
+
<iframe id="stage" sandbox referrerpolicy="no-referrer" title="captured page"></iframe>
|
|
53
|
+
<div class="panel" id="details"></div>
|
|
54
|
+
<div class="panel" id="diff"></div>
|
|
55
|
+
</main>
|
|
56
|
+
<script id="trace-data" type="application/json">{"manifest":{"schemaVersion":2,"format":"browser-commander-trace","mode":"continuous","outcome":"partial","startedAt":"2026-01-01T00:00:00.000Z","stoppedAt":"2026-01-01T00:00:00.000Z","commanderVersion":"0.0.0-conformance","engine":"playwright","browser":null,"platform":"conformance","runtime":"conformance","events":["navigation","interaction","console","pageerror","dialog","requestfailed","download"],"dom":{"html":true,"liveControlState":true,"liveState":true,"mutations":true,"openShadowRoots":true},"replay":{"checkpoints":true,"mutations":true,"childListPositions":true,"liveState":true,"identifiers":true},"privacy":{"redactSelectors":["input[type=password]","[data-private]","[data-bc-redact]"],"redactAttributes":["authorization","proxy-authorization","cookie","set-cookie","x-api-key","x-auth-token","x-csrf-token"],"redactQueryParams":["access_token","api_key","apikey","auth","code","id_token","password","refresh_token","secret","session","signature","token","ticket"],"hasCallback":false},"limits":{"maxQueuedMutations":100},"counts":{"checkpoints":3,"events":18,"mutationBatches":3},"dropped":1},"events":[{"sequence":1,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"trace.start","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-1","mode":"continuous","engine":"playwright","dom":{"html":true,"liveControlState":true,"liveState":true,"mutations":true,"openShadowRoots":true},"events":["navigation","interaction","console","pageerror","dialog","requestfailed","download"],"initialCheckpoint":true},{"sequence":2,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"checkpoint","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-1","index":1,"name":"initial","actor":"recorder","reason":"initial","url":"https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1","truncated":false,"members":{"html":"checkpoints/0001.html","state":"checkpoints/0001.state.json","screenshot":"checkpoints/0001.png"}},{"sequence":3,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"navigation","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","phase":"framenavigated","mainFrame":true,"url":"https://example.com/next?token=[redacted]&q=a+b"},{"sequence":4,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"navigation","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","phase":"framenavigated","mainFrame":false,"url":"https://ads.example.com/frame"},{"sequence":5,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"console","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","level":"log","text":"key [redacted] loaded\n\ttab \"and\" 'quotes'"},{"sequence":6,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"pageerror","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","message":"boom: (unbalanced","stack":"Error: boom\n at main.js:1:2"},{"sequence":7,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"requestfailed","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","url":"https://api.example.com/v1?api_key=[redacted]&page=2","method":"POST","failure":"net::ERR_FAILED"},{"sequence":8,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"dialog","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","type":"confirm","message":"Continue? #yes"},{"sequence":9,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"download","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","phase":"completed","id":"download-1","suggestedFilename":"report final.csv","path":"/downloads/report final.csv","checksum":"sha256:00ff","bytes":42,"url":"https://files.example.com/r.csv?signature=[redacted]","failure":null},{"sequence":10,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"interaction","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","actionId":"trace-1-action-1","action":"goto","target":"https://example.com/next?code=abc","durationMs":0,"ok":true},{"sequence":11,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"interaction","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","actionId":"trace-1-action-2","action":"clickButton","target":"#go","durationMs":0,"ok":false,"error":"button #go is not visible"},{"sequence":12,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"dropped","reason":"size-limit","member":"mutations","detail":"2 records over the in-page queue limit"},{"sequence":13,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"mutations","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","member":"mutations/0001.ndjson","batches":2,"checkpoint":1,"frames":1},{"sequence":14,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"checkpoint","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","index":2,"name":"after click","actor":"automation","reason":"checkpoint","url":"https://example.com/next?token=[redacted]","truncated":true,"members":{"html":"checkpoints/0002.html","state":"checkpoints/0002.state.json","screenshot":"checkpoints/0002.png"}},{"sequence":15,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"interaction","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","action":"note","actor":"caller","authorization":"[redacted]","detail":"uses [redacted]","pageUrl":"https://example.com/?session=[redacted]","count":3,"ratio":0.25,"whole":2,"nested":{"list":[1,"two",null,true]}},{"sequence":16,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"mutations","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","member":"mutations/0002.ndjson","batches":1,"checkpoint":2,"frames":1},{"sequence":17,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"checkpoint","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","index":3,"name":"final","actor":"caller","reason":"failure","url":"https://example.com/next?token=[redacted]","truncated":true,"members":{"html":"checkpoints/0003.html","state":"checkpoints/0003.state.json","screenshot":"checkpoints/0003.png"}},{"sequence":18,"at":"2026-01-01T00:00:00.000Z","monotonicMs":1235,"kind":"trace.stop","traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","discarded":false}],"checkpoints":[{"index":1,"name":"initial","actor":"recorder","reason":"initial","url":"https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1","at":"2026-01-01T00:00:00.000Z","sequence":2,"members":{"html":"checkpoints/0001.html","state":"checkpoints/0001.state.json","screenshot":"checkpoints/0001.png"}},{"index":2,"name":"after click","actor":"automation","reason":"checkpoint","url":"https://example.com/next?token=[redacted]","at":"2026-01-01T00:00:00.000Z","sequence":14,"members":{"html":"checkpoints/0002.html","state":"checkpoints/0002.state.json","screenshot":"checkpoints/0002.png"}},{"index":3,"name":"final","actor":"caller","reason":"failure","url":"https://example.com/next?token=[redacted]","at":"2026-01-01T00:00:00.000Z","sequence":17,"members":{"html":"checkpoints/0003.html","state":"checkpoints/0003.state.json","screenshot":"checkpoints/0003.png"}}],"html":{"1":"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eStart\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003cform\u003e\u003cinput name=\"user\" value=\"alice\"\u003e\u003cinput type=\"checkbox\" name=\"agree\"\u003e\u003cselect name=\"plan\"\u003e\u003coption\u003ea\u003c/option\u003e\u003c/select\u003e\u003c/form\u003e\u003c/body\u003e\u003c/html\u003e","2":"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\u003cbody\u003e\u003cform\u003e\u003cinput name=\"user\" value=\"bob\"\u003e\u003ctextarea\u003esk-zzz9 note\u003c/textarea\u003e\u003c/form\u003e\u003c/body\u003e\u003c/html\u003e","3":"\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\u003cbody\u003e\u003cform\u003e\u003cinput name=\"user\" value=\"bob\"\u003e\u003ctextarea\u003esk-zzz9 note\u003c/textarea\u003e\u003c/form\u003e\u003c/body\u003e\u003c/html\u003e"},"state":{"1":{"url":"https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1","title":"Start – “quoted” 'single' \\ back","readyState":"complete","scroll":{"x":0,"y":12.5},"viewport":{"width":1280,"height":720},"activeElement":null,"controls":[{"path":"form \u003e input:nth-of-type(1)","tag":"input","type":"text","name":"user","id":null,"focused":false,"value":"alice","redacted":false},{"path":"form \u003e input:nth-of-type(2)","tag":"input","type":"checkbox","name":"agree","id":null,"focused":false,"value":"on","checked":false,"redacted":false},{"path":"form \u003e select","tag":"select","type":null,"name":"plan","id":null,"focused":false,"value":"a","selected":[0],"redacted":false}],"frames":[],"name":"initial","actor":"recorder","reason":"initial"},"2":{"url":"https://example.com/next?token=[redacted]","title":"Next","readyState":"interactive","scroll":{"x":0,"y":0},"viewport":{"width":1280,"height":720},"activeElement":"form \u003e textarea","controls":[{"path":"form \u003e input:nth-of-type(1)","tag":"input","type":"text","name":"user","id":null,"focused":false,"value":"bob","redacted":false},{"path":"form \u003e input:nth-of-type(2)","tag":"input","type":"checkbox","name":"agree","id":null,"focused":false,"value":"on","checked":true,"redacted":false},{"path":"form \u003e textarea","tag":"textarea","type":null,"name":null,"id":"note","focused":true,"value":"[redacted] note","scroll":{"x":0,"y":3},"redacted":false}],"frames":[{"url":"https://ads.example.com/frame","crossOrigin":true}],"name":"after click","actor":"automation","reason":"checkpoint"},"3":{"url":"https://example.com/next?token=[redacted]","title":"Next","readyState":"interactive","scroll":{"x":0,"y":0},"viewport":{"width":1280,"height":720},"activeElement":"form \u003e textarea","controls":[{"path":"form \u003e input:nth-of-type(1)","tag":"input","type":"text","name":"user","id":null,"focused":false,"value":"bob","redacted":false},{"path":"form \u003e input:nth-of-type(2)","tag":"input","type":"checkbox","name":"agree","id":null,"focused":false,"value":"on","checked":true,"redacted":false},{"path":"form \u003e textarea","tag":"textarea","type":null,"name":null,"id":"note","focused":true,"value":"[redacted] note","scroll":{"x":0,"y":3},"redacted":false}],"frames":[{"url":"https://ads.example.com/frame","crossOrigin":true}],"name":"final","actor":"caller","reason":"failure"}},"mutations":{"1":[{"traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","frameId":"main","mainFrame":true,"at":1767225600100,"records":[{"type":"live-state","target":2,"property":"value","value":"bo"},{"type":"childList","target":1,"added":[{"id":9,"html":"\u003cp\u003enew\u2028line\u003c/p\u003e"}],"removed":[],"index":3}]},{"traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","frameId":"main","mainFrame":true,"at":1767225600200,"records":[{"type":"attributes","target":4,"name":"class","value":"busy"}]}],"2":[{"traceId":"trace-1","browserContextId":"context-1","pageId":"page-1","navigationId":"nav-2","frameId":"main","mainFrame":true,"at":1767225600300,"records":[{"type":"characterData","target":7,"value":"done ✓ 😀"}]}],"3":[]},"diffs":{"1":[],"2":[{"path":"form \u003e input:nth-of-type(1)","change":"changed","before":"alice","after":"bob"},{"path":"form \u003e input:nth-of-type(2)","change":"changed","before":false,"after":true},{"path":"form \u003e textarea","change":"added","after":"[redacted] note"},{"path":"form \u003e select","change":"removed","before":"a"}],"3":[]},"elided":[]}</script>
|
|
57
|
+
<script>
|
|
58
|
+
const trace = JSON.parse(document.getElementById('trace-data').textContent);
|
|
59
|
+
const timeline = document.getElementById('timeline');
|
|
60
|
+
const frame = document.getElementById('stage');
|
|
61
|
+
const details = document.getElementById('details');
|
|
62
|
+
const diffPanel = document.getElementById('diff');
|
|
63
|
+
const stepLabel = document.getElementById('step');
|
|
64
|
+
let selectedCheckpoint = null;
|
|
65
|
+
let mutationStep = 0;
|
|
66
|
+
|
|
67
|
+
// Everything in the trace is what the recorded page made it, and the policy
|
|
68
|
+
// allows inline handlers for this script, so recorded text is escaped before
|
|
69
|
+
// it is put into the viewer's markup.
|
|
70
|
+
function escapeHtml(value) {
|
|
71
|
+
return String(value).replace(/[&<>"']/g, (c) => '&#' + c.charCodeAt(0) + ';');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function checkpointFor(event) {
|
|
75
|
+
if (event.kind === 'checkpoint') return event.index;
|
|
76
|
+
let latest = null;
|
|
77
|
+
for (const other of trace.events) {
|
|
78
|
+
if (other.sequence > event.sequence) break;
|
|
79
|
+
if (other.kind === 'checkpoint') latest = other.index;
|
|
80
|
+
}
|
|
81
|
+
return latest;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderHtml(index, html) {
|
|
85
|
+
// The captured document is handed to a sandboxed frame with no
|
|
86
|
+
// allow-scripts and no allow-forms: it is shown, never run.
|
|
87
|
+
frame.srcdoc = html == null ? '<p style="font:1rem system-ui">no HTML captured for this checkpoint</p>' : html;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function nodeOf(parsed, described) {
|
|
91
|
+
if (!described) return null;
|
|
92
|
+
if (described.type === 'text') {
|
|
93
|
+
return parsed.createTextNode(described.text == null ? '' : described.text);
|
|
94
|
+
}
|
|
95
|
+
if (described.type === 'element' && described.html) {
|
|
96
|
+
const holder = parsed.createElement('template');
|
|
97
|
+
holder.innerHTML = described.html;
|
|
98
|
+
return holder.content.firstChild;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function looksLike(node, described) {
|
|
104
|
+
if (!node || !described) return false;
|
|
105
|
+
if (described.type === 'text') return node.nodeType === 3;
|
|
106
|
+
if (described.type !== 'element') return false;
|
|
107
|
+
if (node.nodeType !== 1 || node.localName !== described.tag) return false;
|
|
108
|
+
return described.id ? node.id === described.id : true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function childLike(parent, described) {
|
|
112
|
+
if (!described) return null;
|
|
113
|
+
// An id is identity; an index is only a hint. A node that moved is reported
|
|
114
|
+
// at the position it moved *to*, so the index alone would find the wrong one.
|
|
115
|
+
if (described.id) {
|
|
116
|
+
for (const child of parent.childNodes) {
|
|
117
|
+
if (child.nodeType === 1 && child.id === described.id) return child;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const at = described.index == null ? null : parent.childNodes[described.index];
|
|
121
|
+
if (looksLike(at, described)) return at;
|
|
122
|
+
for (const child of parent.childNodes) {
|
|
123
|
+
if (looksLike(child, described)) return child;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function insertAt(parent, node, index) {
|
|
129
|
+
if (!node) return false;
|
|
130
|
+
const reference = index == null ? null : parent.childNodes[index] || null;
|
|
131
|
+
parent.insertBefore(node, reference);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function applyLiveState(parsed, target, record) {
|
|
136
|
+
const value = record.after;
|
|
137
|
+
const text = value == null ? '' : String(value);
|
|
138
|
+
if (record.property === 'scroll') {
|
|
139
|
+
// A static copy of a document cannot be scrolled, so where it was
|
|
140
|
+
// scrolled to is written onto the element rather than quietly lost.
|
|
141
|
+
const element = target || parsed.documentElement;
|
|
142
|
+
const at = value || {};
|
|
143
|
+
element.setAttribute('data-bc-scroll', at.top + ',' + at.left);
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (!target) return false;
|
|
147
|
+
if (record.property === 'value') {
|
|
148
|
+
// Serialized HTML carries attributes, not properties, so what was typed
|
|
149
|
+
// is written where a re-parse will still find it.
|
|
150
|
+
if (target.localName === 'textarea' || target.hasAttribute('contenteditable')) {
|
|
151
|
+
target.textContent = text;
|
|
152
|
+
} else {
|
|
153
|
+
target.setAttribute('value', text);
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (record.property === 'checked') {
|
|
158
|
+
if (value) target.setAttribute('checked', '');
|
|
159
|
+
else target.removeAttribute('checked');
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
if (record.property === 'selected') {
|
|
163
|
+
const chosen = (Array.isArray(value) ? value : []).map(String);
|
|
164
|
+
for (const option of target.querySelectorAll('option')) {
|
|
165
|
+
if (chosen.indexOf(option.value) !== -1) option.setAttribute('selected', '');
|
|
166
|
+
else option.removeAttribute('selected');
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (record.property === 'focus') {
|
|
171
|
+
for (const had of parsed.querySelectorAll('[data-bc-focus]')) {
|
|
172
|
+
had.removeAttribute('data-bc-focus');
|
|
173
|
+
}
|
|
174
|
+
if (value) target.setAttribute('data-bc-focus', '');
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function applyMutations(index, upTo) {
|
|
181
|
+
const html = trace.html[index];
|
|
182
|
+
if (html == null) return null;
|
|
183
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
184
|
+
const batches = trace.mutations[index] || [];
|
|
185
|
+
let applied = 0;
|
|
186
|
+
let skipped = 0;
|
|
187
|
+
for (const batch of batches.slice(0, upTo)) {
|
|
188
|
+
// The stage holds one document. A batch from an iframe belongs to a
|
|
189
|
+
// different one, so it is counted rather than applied to the wrong page.
|
|
190
|
+
if (batch.mainFrame === false) { skipped += (batch.records || []).length; continue; }
|
|
191
|
+
for (const record of batch.records) {
|
|
192
|
+
const target = record.target && record.target.path
|
|
193
|
+
? parsed.querySelector(record.target.path)
|
|
194
|
+
: null;
|
|
195
|
+
if (record.kind === 'live-state') {
|
|
196
|
+
if (applyLiveState(parsed, target, record)) applied++; else skipped++;
|
|
197
|
+
} else if (!target) {
|
|
198
|
+
skipped++;
|
|
199
|
+
continue;
|
|
200
|
+
} else if (record.kind === 'attributes' && record.attribute) {
|
|
201
|
+
if (record.after == null) target.removeAttribute(record.attribute);
|
|
202
|
+
else target.setAttribute(record.attribute, record.after);
|
|
203
|
+
applied++;
|
|
204
|
+
} else if (record.kind === 'characterData') {
|
|
205
|
+
target.textContent = record.after == null ? '' : record.after;
|
|
206
|
+
applied++;
|
|
207
|
+
} else if (record.kind === 'childList') {
|
|
208
|
+
// Where a node went is as much of the change as what it was: appending
|
|
209
|
+
// every addition to the end replays an insertion before a sibling in
|
|
210
|
+
// the wrong place, and drops removals entirely (issue #93).
|
|
211
|
+
for (const removed of record.removed || []) {
|
|
212
|
+
const node = childLike(target, removed);
|
|
213
|
+
if (node) { target.removeChild(node); applied++; } else { skipped++; }
|
|
214
|
+
}
|
|
215
|
+
const added = (record.added || []).filter(Boolean)
|
|
216
|
+
.slice().sort((a, b) => (a.index == null ? 1e9 : a.index) - (b.index == null ? 1e9 : b.index));
|
|
217
|
+
for (const entry of added) {
|
|
218
|
+
if (insertAt(target, nodeOf(parsed, entry), entry.index)) applied++;
|
|
219
|
+
else skipped++;
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
skipped++;
|
|
223
|
+
}
|
|
224
|
+
if (target && target.style) target.style.outline = '2px solid #f59e0b';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { html: '<!DOCTYPE html>' + parsed.documentElement.outerHTML, applied, skipped };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function stepText(step, total, result) {
|
|
231
|
+
const base = 'batch ' + step + ' of ' + total;
|
|
232
|
+
if (!result || !result.skipped) return base;
|
|
233
|
+
return base + ' · ' + result.skipped + ' not replayable here';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function showCheckpoint(index) {
|
|
237
|
+
selectedCheckpoint = index;
|
|
238
|
+
mutationStep = 0;
|
|
239
|
+
renderHtml(index, trace.html[index]);
|
|
240
|
+
const state = trace.state[index];
|
|
241
|
+
const previous = trace.state[index - 1];
|
|
242
|
+
details.innerHTML = state
|
|
243
|
+
? '<table><tr><th>url</th><td>' + escapeHtml(state.url) + '</td></tr>' +
|
|
244
|
+
'<tr><th>title</th><td>' + escapeHtml(state.title || '') + '</td></tr>' +
|
|
245
|
+
'<tr><th>controls</th><td>' + (state.controls || []).length + '</td></tr></table>'
|
|
246
|
+
: '<p>no state captured</p>';
|
|
247
|
+
const changes = trace.diffs[index] || [];
|
|
248
|
+
diffPanel.innerHTML = previous && changes.length
|
|
249
|
+
? '<table><tr><th>control</th><th>before</th><th>after</th></tr>' +
|
|
250
|
+
changes.map((c) => '<tr><td>' + escapeHtml(c.path) + '</td><td>' +
|
|
251
|
+
escapeHtml(c.before ?? '') + '</td><td>' + escapeHtml(c.after ?? '') +
|
|
252
|
+
'</td></tr>').join('') + '</table>'
|
|
253
|
+
: '<p>no control changes against the previous checkpoint</p>';
|
|
254
|
+
stepLabel.textContent = (trace.mutations[index] || []).length + ' mutation batches';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function select(event, element) {
|
|
258
|
+
for (const li of timeline.children) li.classList.remove('selected');
|
|
259
|
+
element.classList.add('selected');
|
|
260
|
+
const index = checkpointFor(event);
|
|
261
|
+
if (index != null && index !== selectedCheckpoint) showCheckpoint(index);
|
|
262
|
+
if (event.kind !== 'checkpoint') {
|
|
263
|
+
details.innerHTML = '<table>' + Object.entries(event)
|
|
264
|
+
.map(([k, v]) => '<tr><th>' + escapeHtml(k) + '</th><td>' + escapeHtml(String(
|
|
265
|
+
typeof v === 'object' ? JSON.stringify(v) : v).slice(0, 400)) + '</td></tr>')
|
|
266
|
+
.join('') + '</table>';
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const event of trace.events) {
|
|
271
|
+
const li = document.createElement('li');
|
|
272
|
+
if (event.kind === 'dropped') li.className = 'dropped';
|
|
273
|
+
li.innerHTML = '<span class="kind"></span> <span class="at"></span><div class="what"></div>';
|
|
274
|
+
li.querySelector('.kind').textContent = event.kind;
|
|
275
|
+
li.querySelector('.at').textContent = (event.at || '').slice(11, 23);
|
|
276
|
+
li.querySelector('.what').textContent =
|
|
277
|
+
event.name || event.action || event.text || event.message || event.url || event.reason || '';
|
|
278
|
+
li.addEventListener('click', () => select(event, li));
|
|
279
|
+
timeline.append(li);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
document.getElementById('play').addEventListener('click', async () => {
|
|
283
|
+
if (selectedCheckpoint == null) return;
|
|
284
|
+
const batches = trace.mutations[selectedCheckpoint] || [];
|
|
285
|
+
for (let step = 1; step <= batches.length; step++) {
|
|
286
|
+
const result = applyMutations(selectedCheckpoint, step);
|
|
287
|
+
if (result) frame.srcdoc = result.html;
|
|
288
|
+
stepLabel.textContent = stepText(step, batches.length, result);
|
|
289
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
document.getElementById('step-forward').addEventListener('click', () => {
|
|
294
|
+
if (selectedCheckpoint == null) return;
|
|
295
|
+
const batches = trace.mutations[selectedCheckpoint] || [];
|
|
296
|
+
mutationStep = Math.min(mutationStep + 1, batches.length);
|
|
297
|
+
const result = applyMutations(selectedCheckpoint, mutationStep);
|
|
298
|
+
if (result) frame.srcdoc = result.html;
|
|
299
|
+
stepLabel.textContent = stepText(mutationStep, batches.length, result);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
document.getElementById('reset').addEventListener('click', () => {
|
|
303
|
+
if (selectedCheckpoint != null) showCheckpoint(selectedCheckpoint);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (trace.checkpoints.length) showCheckpoint(trace.checkpoints[0].index);
|
|
307
|
+
</script>
|
|
308
|
+
</body>
|
|
309
|
+
</html>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"url": "https://example.com/path?token=abc&keep=1",
|
|
4
|
+
"redacted": "https://example.com/path?token=[redacted]&keep=1"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"url": "https://EXAMPLE.com:443/a/../b/./c?Token=x#frag",
|
|
8
|
+
"redacted": "https://example.com/b/c?Token=[redacted]#frag"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"url": "http://example.com",
|
|
12
|
+
"redacted": "http://example.com/"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"url": "https://name:pw@example.com/?q=1",
|
|
16
|
+
"redacted": "https://[redacted]:[redacted]@example.com/?q=1"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"url": "https://user@example.com/x",
|
|
20
|
+
"redacted": "https://[redacted]@example.com/x"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"url": "https://example.com/?a=1&a=2&code=zz&b=hello%20world",
|
|
24
|
+
"redacted": "https://example.com/?a=1&a=2&code=[redacted]&b=hello+world"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"url": "https://example.com/?q=a b&secret=s",
|
|
28
|
+
"redacted": "https://example.com/?q=a+b&secret=[redacted]"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"url": "https://example.com/?q=a+b&secret=s",
|
|
32
|
+
"redacted": "https://example.com/?q=a+b&secret=[redacted]"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"url": "https://example.com/?q=a%2Bb&session=1&x=~!*()'",
|
|
36
|
+
"redacted": "https://example.com/?q=a%2Bb&session=[redacted]&x=%7E%21*%28%29%27"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"url": "https://example.com/cb#access_token=t&state=s",
|
|
40
|
+
"redacted": "https://example.com/cb#access_token=[redacted]&state=s"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"url": "https://example.com/cb#plain-fragment",
|
|
44
|
+
"redacted": "https://example.com/cb#plain-fragment"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"url": "https://example.com/p a th/ü?x=ü&password=p",
|
|
48
|
+
"redacted": "https://example.com/p%20a%20th/%C3%BC?x=%C3%BC&password=[redacted]"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"url": "https://example.com/?keep=1",
|
|
52
|
+
"redacted": "https://example.com/?keep=1"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"url": "https://example.com/?token",
|
|
56
|
+
"redacted": "https://example.com/?token=[redacted]"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"url": "https://example.com/?=token&token=",
|
|
60
|
+
"redacted": "https://example.com/?=token&token=[redacted]"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"url": "http://example.com:8080/?api_key=1",
|
|
64
|
+
"redacted": "http://example.com:8080/?api_key=[redacted]"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"url": "about:blank",
|
|
68
|
+
"redacted": "about:blank"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"url": "data:text/html,<p>hi</p>",
|
|
72
|
+
"redacted": "data:text/html,<p>hi</p>"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"url": "/relative/path?token=abc",
|
|
76
|
+
"redacted": "/relative/path?token=abc"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"url": "not a url",
|
|
80
|
+
"redacted": "not a url"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"url": "",
|
|
84
|
+
"redacted": ""
|
|
85
|
+
}
|
|
86
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
(trace: (format: browser-commander-trace) (links: 1) (schema: 2) (bundle: bundle) (mode: continuous) (engine: playwright) (started: '2026-01-01T00:00:00.000Z') (commander: 0.0.0-conformance))
|
|
2
|
+
(timeline: (sequence: 1) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: trace.start) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-1) (actor: recorder) (outcome: recorded) (mode: continuous) (engine: playwright) (dom: '{"html":true,"liveControlState":true,"liveState":true,"mutations":true,"openShadowRoots":true}') (events: '["navigation","interaction","console","pageerror","dialog","requestfailed","download"]') (initialCheckpoint: true))
|
|
3
|
+
(timeline: (sequence: 2) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: checkpoint) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-1) (actor: recorder) (target: 'https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1') (outcome: recorded) (index: 1) (name: initial) (reason: initial) (url: 'https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1') (truncated: false))
|
|
4
|
+
(checkpoint: (index: 1) (sequence: 2) (at: '2026-01-01T00:00:00.000Z') (name: initial) (actor: recorder) (reason: initial) (page: page-1) (navigation: nav-1) (url: 'https://[redacted]:[redacted]@example.com/start?ticket=[redacted]&lang=en#access_token=[redacted]&view=1') (outcome: recorded) (html: checkpoints/0001.html) (state: checkpoints/0001.state.json) (screenshot: checkpoints/0001.png))
|
|
5
|
+
(timeline: (sequence: 3) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: navigation) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (action: framenavigated) (target: 'https://example.com/next?token=[redacted]&q=a+b') (outcome: recorded) (phase: framenavigated) (mainFrame: true) (url: 'https://example.com/next?token=[redacted]&q=a+b'))
|
|
6
|
+
(timeline: (sequence: 4) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: navigation) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (action: framenavigated) (target: 'https://ads.example.com/frame') (outcome: recorded) (phase: framenavigated) (mainFrame: false) (url: 'https://ads.example.com/frame'))
|
|
7
|
+
(timeline: (sequence: 5) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: console) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (outcome: recorded) (level: log) (text: "key [redacted] loaded\n\ttab \u0022and\u0022 'quotes'"))
|
|
8
|
+
(timeline: (sequence: 6) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: pageerror) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (outcome: recorded) (message: 'boom: (unbalanced') (stack: 'Error: boom\n at main.js:1:2'))
|
|
9
|
+
(timeline: (sequence: 7) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: requestfailed) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (target: 'https://api.example.com/v1?api_key=[redacted]&page=2') (outcome: failed) (url: 'https://api.example.com/v1?api_key=[redacted]&page=2') (method: POST) (failure: 'net::ERR_FAILED'))
|
|
10
|
+
(timeline: (sequence: 8) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: dialog) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (outcome: recorded) (type: confirm) (message: 'Continue? #yes'))
|
|
11
|
+
(timeline: (sequence: 9) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: download) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: browser) (action: completed) (target: 'https://files.example.com/r.csv?signature=[redacted]') (outcome: recorded) (phase: completed) (id: download-1) (suggestedFilename: 'report final.csv') (path: '/downloads/report final.csv') (checksum: 'sha256:00ff') (bytes: 42) (url: 'https://files.example.com/r.csv?signature=[redacted]'))
|
|
12
|
+
(timeline: (sequence: 10) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: interaction) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: automation) (action: goto) (target: 'https://example.com/next?code=abc') (outcome: ok) (actionId: trace-1-action-1) (durationMs: 0) (ok: true))
|
|
13
|
+
(timeline: (sequence: 11) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: interaction) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: automation) (action: clickButton) (target: '#go') (outcome: failed) (actionId: trace-1-action-2) (durationMs: 0) (ok: false) (error: 'button #go is not visible'))
|
|
14
|
+
(timeline: (sequence: 12) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: dropped) (actor: recorder) (target: mutations) (outcome: dropped) (reason: size-limit) (member: mutations) (detail: '2 records over the in-page queue limit'))
|
|
15
|
+
(timeline: (sequence: 13) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: mutations) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: recorder) (target: mutations/0001.ndjson) (outcome: recorded) (member: mutations/0001.ndjson) (batches: 2) (checkpoint: 1) (frames: 1))
|
|
16
|
+
(timeline: (sequence: 14) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: checkpoint) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: automation) (target: 'https://example.com/next?token=[redacted]') (outcome: partial) (index: 2) (name: 'after click') (reason: checkpoint) (url: 'https://example.com/next?token=[redacted]') (truncated: true))
|
|
17
|
+
(checkpoint: (index: 2) (sequence: 14) (at: '2026-01-01T00:00:00.000Z') (name: 'after click') (actor: automation) (reason: checkpoint) (page: page-1) (navigation: nav-2) (url: 'https://example.com/next?token=[redacted]') (outcome: partial) (html: checkpoints/0002.html) (state: checkpoints/0002.state.json) (screenshot: checkpoints/0002.png))
|
|
18
|
+
(timeline: (sequence: 15) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: interaction) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: caller) (action: note) (outcome: recorded) (authorization: [redacted]) (detail: 'uses [redacted]') (pageUrl: 'https://example.com/?session=[redacted]') (count: 3) (ratio: 0.25) (whole: 2) (nested: '{"list":[1,"two",null,true]}'))
|
|
19
|
+
(timeline: (sequence: 16) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: mutations) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: recorder) (target: mutations/0002.ndjson) (outcome: recorded) (member: mutations/0002.ndjson) (batches: 1) (checkpoint: 2) (frames: 1))
|
|
20
|
+
(timeline: (sequence: 17) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: checkpoint) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: caller) (target: 'https://example.com/next?token=[redacted]') (outcome: partial) (index: 3) (name: final) (reason: failure) (url: 'https://example.com/next?token=[redacted]') (truncated: true))
|
|
21
|
+
(checkpoint: (index: 3) (sequence: 17) (at: '2026-01-01T00:00:00.000Z') (name: final) (actor: caller) (reason: failure) (page: page-1) (navigation: nav-2) (url: 'https://example.com/next?token=[redacted]') (outcome: partial) (html: checkpoints/0003.html) (state: checkpoints/0003.state.json) (screenshot: checkpoints/0003.png))
|
|
22
|
+
(timeline: (sequence: 18) (at: '2026-01-01T00:00:00.000Z') (monotonicMs: 1235) (kind: trace.stop) (trace: trace-1) (context: context-1) (page: page-1) (navigation: nav-2) (actor: recorder) (outcome: recorded) (discarded: false))
|
|
23
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > input:nth-of-type(1)') (change: changed) (before: alice) (after: bob) (actor: automation))
|
|
24
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > input:nth-of-type(2)') (change: changed) (before: false) (after: true) (actor: automation))
|
|
25
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > textarea') (change: added) (after: '[redacted] note') (actor: automation))
|
|
26
|
+
(control-diff: (checkpoint: 2) (previous: 1) (path: 'form > select') (change: removed) (before: a) (actor: automation))
|
|
27
|
+
(result: (outcome: partial) (stopped: '2026-01-01T00:00:00.000Z') (events: 18) (checkpoints: 3) (mutationBatches: 3) (dropped: 1) (truncated: true) (replay: checkpoints mutations childListPositions liveState identifiers))
|