@descryy/runtime-browser 0.4.8 → 0.5.0
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/dist/browser-recording-collector.d.ts +120 -0
- package/dist/browser-recording-collector.d.ts.map +1 -0
- package/dist/browser-recording-collector.js +372 -0
- package/dist/browser-recording-collector.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recording: turns a HUMAN's own interactions with an already-open page into
|
|
3
|
+
* an ordered, timestamped stream a host can hand straight to a scenario
|
|
4
|
+
* builder -- the descry-core follow-up this package owes
|
|
5
|
+
* (`documents/decisions-inbox/DEC-NEXT-browser-recording-primitive.md`,
|
|
6
|
+
* `descry-core`'s `packages/mcp/src/browser/driver.ts`:
|
|
7
|
+
* `BrowserPageSession.startRecording`/`stopRecording`). Nothing here writes
|
|
8
|
+
* anything or drives the page itself -- the opposite direction from
|
|
9
|
+
* `BrowserActionCollector`, which *commands* the page and reports what it
|
|
10
|
+
* did. This collector only listens.
|
|
11
|
+
*
|
|
12
|
+
* Four kinds of interaction, matching the port's own `BrowserActionKind`
|
|
13
|
+
* vocabulary so the host can forward a captured event as a
|
|
14
|
+
* `PerformedTarget`/`PerformedStep` with no translation of the action name
|
|
15
|
+
* itself:
|
|
16
|
+
* - `click`, on the same interactive-element set `playwright-driver.ts`'s
|
|
17
|
+
* own `INTERACTIVE` constant already uses for a snapshot
|
|
18
|
+
* (`a, button, input, select, textarea, [role], [onclick], summary`) --
|
|
19
|
+
* clicking a checkbox or radio is captured here, not by the `change`
|
|
20
|
+
* listener below (see that listener's own comment on why).
|
|
21
|
+
* - `fill`, for a text field's value once it is COMMITTED -- the native
|
|
22
|
+
* `change` event, which a text `<input>`/`<textarea>` fires on blur only
|
|
23
|
+
* when its value actually changed since focus. Not `input`: capturing
|
|
24
|
+
* every keystroke would record a value no scenario replay wants (a
|
|
25
|
+
* partially-typed string), and `fill`, not `type`, because a recording
|
|
26
|
+
* only ever knows a field's final value, never the keystroke sequence
|
|
27
|
+
* that produced it -- the same distinction `browser-action-collector.ts`
|
|
28
|
+
* draws between its two text verbs, resolved here the only way it can be.
|
|
29
|
+
* - `select`, for a `<select>`'s `change` event -- its own branch, not the
|
|
30
|
+
* text-field one, because "a chooser's value changed" and "a field's text
|
|
31
|
+
* was committed" are different facts (mirrors `BrowserActionCollector
|
|
32
|
+
* .select()`).
|
|
33
|
+
* - `navigate`, for a real top-level (main-frame) navigation, observed
|
|
34
|
+
* directly off Playwright's own `framenavigated` rather than through the
|
|
35
|
+
* page-side script -- the frame that navigated *is* the fact, and asking
|
|
36
|
+
* the page to report its own navigation would miss the first instant of
|
|
37
|
+
* the new document.
|
|
38
|
+
*
|
|
39
|
+
* **Passwords never leave the page.** A field this collector's page-side
|
|
40
|
+
* script recognises as a password field (`type="password"`, or
|
|
41
|
+
* `autocomplete="current-password"`/`"new-password"`) reports `secret: true`
|
|
42
|
+
* and never reads `.value` into the payload it sends across the binding at
|
|
43
|
+
* all -- not captured-then-redacted, never captured. `text` on the resulting
|
|
44
|
+
* `RecordedInteraction` is `null` whenever `secret` is `true`; assembling the
|
|
45
|
+
* actual `${credential:NAME}` placeholder is the host's job
|
|
46
|
+
* (`recordedSecretCredentialName`/`recordedSecretPlaceholder`,
|
|
47
|
+
* `descry-core`'s `driver.ts`) because only the host knows this session's
|
|
48
|
+
* running count of secrets recorded so far -- the ordinal that keeps two
|
|
49
|
+
* distinct password fields from colliding onto the same credential name.
|
|
50
|
+
*
|
|
51
|
+
* **Selector preference** reuses the identical tiered rule
|
|
52
|
+
* `playwright-driver.ts`'s own `computeSelector` already applies for a
|
|
53
|
+
* snapshot element: a `data-testid`/`data-test` hook first, then a
|
|
54
|
+
* non-generated `id`, then a named form control, then an
|
|
55
|
+
* aria-labelled-and-roled element, then a bounded structural path anchored at
|
|
56
|
+
* the nearest ancestor carrying one of the above -- each tier accepted only
|
|
57
|
+
* when it is unique in the element's own root (`document`, or the open
|
|
58
|
+
* shadow root it lives in). It is restated here, not imported, for the same
|
|
59
|
+
* reason `driver.ts` restates `PerformedTarget` rather than importing a
|
|
60
|
+
* scenario type: the string below is serialised into the page verbatim
|
|
61
|
+
* (`page.addInitScript`/`page.evaluate`), and a page-side script cannot
|
|
62
|
+
* import a Node module.
|
|
63
|
+
*
|
|
64
|
+
* **Listener lifecycle.** `page.exposeFunction` and `page.addInitScript` are
|
|
65
|
+
* each installed at most once per collector instance (lazily, on the first
|
|
66
|
+
* `start()`) -- Playwright refuses a second `exposeFunction` under the same
|
|
67
|
+
* name, and a second `addInitScript` would double-register the in-page
|
|
68
|
+
* listeners on every navigation after that. Because `addInitScript` "doesn't
|
|
69
|
+
* reach an already-loaded document" (the same contract every other probe in
|
|
70
|
+
* this package documents -- `frontend-initiator-capture.ts`,
|
|
71
|
+
* `identity-grounding`'s probes), `start()` ALSO runs the identical
|
|
72
|
+
* installer directly via `page.evaluate` against whatever document is
|
|
73
|
+
* current right now, idempotently (a `window` marker skips a second
|
|
74
|
+
* install), so a recording that starts mid-session on an already-open page
|
|
75
|
+
* is not silently missing its first document's events.
|
|
76
|
+
*
|
|
77
|
+
* A capturing session can therefore be started and stopped more than once
|
|
78
|
+
* without re-installing anything: `start()`/`stop()` do not attach or detach
|
|
79
|
+
* the page-side listeners themselves (Playwright exposes no way to
|
|
80
|
+
* un-expose a function or un-register an init script), they gate a
|
|
81
|
+
* `capturing` flag the Node-side binding handler checks before pushing to
|
|
82
|
+
* the buffer -- the identical "an unattached listener produces nothing"
|
|
83
|
+
* discipline `descry-core`'s fake driver already documents for its own
|
|
84
|
+
* `recordHuman*` methods. The one exception is the `framenavigated`
|
|
85
|
+
* listener, which genuinely is `page.on`/`page.off` in `start()`/`stop()`,
|
|
86
|
+
* matching `BrowserConsoleCollector`'s own precedent, because Playwright
|
|
87
|
+
* offers a real way to detach it.
|
|
88
|
+
*/
|
|
89
|
+
import type { BrowserSession } from "./browser-session.ts";
|
|
90
|
+
/** The element actually acted on -- role, accessible name, a selector when one is addressable. Mirrors the port's own `PerformedTarget` field-for-field so a host can forward this unmodified. */
|
|
91
|
+
export interface RecordedTarget {
|
|
92
|
+
readonly role: string;
|
|
93
|
+
readonly name: string;
|
|
94
|
+
/** Null when no candidate was unique in this element's own root -- the same "no guessed selector" rule every other selector computation in this repo follows. */
|
|
95
|
+
readonly selector: string | null;
|
|
96
|
+
}
|
|
97
|
+
/** One human-driven interaction, in the order it was observed. */
|
|
98
|
+
export interface RecordedInteraction {
|
|
99
|
+
readonly action: "click" | "fill" | "select" | "navigate";
|
|
100
|
+
/** The element acted on. Null for `navigate`, which acts on the page, not an element. */
|
|
101
|
+
readonly target: RecordedTarget | null;
|
|
102
|
+
/** Where a `navigate` went. Null for every other action. */
|
|
103
|
+
readonly url: string | null;
|
|
104
|
+
/** The committed value, for `fill`/`select`. Null for `click`/`navigate`, and ALWAYS null when `secret` is true -- see this module's header. */
|
|
105
|
+
readonly text: string | null;
|
|
106
|
+
/** True only for a `fill` whose source field this collector's page-side script recognised as a password field. When true, `text` is null and was never populated from the field's real value in the first place. */
|
|
107
|
+
readonly secret: boolean;
|
|
108
|
+
readonly at: string;
|
|
109
|
+
}
|
|
110
|
+
export interface BrowserRecordingCollectorOptions {
|
|
111
|
+
readonly session: BrowserSession;
|
|
112
|
+
}
|
|
113
|
+
export interface BrowserRecordingCollector {
|
|
114
|
+
/** Starts (or resumes) capturing. Throws if the session's page has already crashed or closed, or if this collector is already capturing -- call `stop()` first. */
|
|
115
|
+
start(): Promise<void>;
|
|
116
|
+
/** Stops capturing and returns every interaction captured since `start()`, in order. Throws if this collector is not currently capturing. */
|
|
117
|
+
stop(): Promise<readonly RecordedInteraction[]>;
|
|
118
|
+
}
|
|
119
|
+
export declare function createBrowserRecordingCollector(options: BrowserRecordingCollectorOptions): BrowserRecordingCollector;
|
|
120
|
+
//# sourceMappingURL=browser-recording-collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-recording-collector.d.ts","sourceRoot":"","sources":["../src/browser-recording-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,kMAAkM;AAClM,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iKAAiK;IACjK,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,kEAAkE;AAClE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC1D,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gJAAgJ;IAChJ,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oNAAoN;IACpN,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,mKAAmK;IACnK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,6IAA6I;IAC7I,IAAI,IAAI,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAAC;CACjD;AAqOD,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,gCAAgC,GAAG,yBAAyB,CAuEpH"}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recording: turns a HUMAN's own interactions with an already-open page into
|
|
3
|
+
* an ordered, timestamped stream a host can hand straight to a scenario
|
|
4
|
+
* builder -- the descry-core follow-up this package owes
|
|
5
|
+
* (`documents/decisions-inbox/DEC-NEXT-browser-recording-primitive.md`,
|
|
6
|
+
* `descry-core`'s `packages/mcp/src/browser/driver.ts`:
|
|
7
|
+
* `BrowserPageSession.startRecording`/`stopRecording`). Nothing here writes
|
|
8
|
+
* anything or drives the page itself -- the opposite direction from
|
|
9
|
+
* `BrowserActionCollector`, which *commands* the page and reports what it
|
|
10
|
+
* did. This collector only listens.
|
|
11
|
+
*
|
|
12
|
+
* Four kinds of interaction, matching the port's own `BrowserActionKind`
|
|
13
|
+
* vocabulary so the host can forward a captured event as a
|
|
14
|
+
* `PerformedTarget`/`PerformedStep` with no translation of the action name
|
|
15
|
+
* itself:
|
|
16
|
+
* - `click`, on the same interactive-element set `playwright-driver.ts`'s
|
|
17
|
+
* own `INTERACTIVE` constant already uses for a snapshot
|
|
18
|
+
* (`a, button, input, select, textarea, [role], [onclick], summary`) --
|
|
19
|
+
* clicking a checkbox or radio is captured here, not by the `change`
|
|
20
|
+
* listener below (see that listener's own comment on why).
|
|
21
|
+
* - `fill`, for a text field's value once it is COMMITTED -- the native
|
|
22
|
+
* `change` event, which a text `<input>`/`<textarea>` fires on blur only
|
|
23
|
+
* when its value actually changed since focus. Not `input`: capturing
|
|
24
|
+
* every keystroke would record a value no scenario replay wants (a
|
|
25
|
+
* partially-typed string), and `fill`, not `type`, because a recording
|
|
26
|
+
* only ever knows a field's final value, never the keystroke sequence
|
|
27
|
+
* that produced it -- the same distinction `browser-action-collector.ts`
|
|
28
|
+
* draws between its two text verbs, resolved here the only way it can be.
|
|
29
|
+
* - `select`, for a `<select>`'s `change` event -- its own branch, not the
|
|
30
|
+
* text-field one, because "a chooser's value changed" and "a field's text
|
|
31
|
+
* was committed" are different facts (mirrors `BrowserActionCollector
|
|
32
|
+
* .select()`).
|
|
33
|
+
* - `navigate`, for a real top-level (main-frame) navigation, observed
|
|
34
|
+
* directly off Playwright's own `framenavigated` rather than through the
|
|
35
|
+
* page-side script -- the frame that navigated *is* the fact, and asking
|
|
36
|
+
* the page to report its own navigation would miss the first instant of
|
|
37
|
+
* the new document.
|
|
38
|
+
*
|
|
39
|
+
* **Passwords never leave the page.** A field this collector's page-side
|
|
40
|
+
* script recognises as a password field (`type="password"`, or
|
|
41
|
+
* `autocomplete="current-password"`/`"new-password"`) reports `secret: true`
|
|
42
|
+
* and never reads `.value` into the payload it sends across the binding at
|
|
43
|
+
* all -- not captured-then-redacted, never captured. `text` on the resulting
|
|
44
|
+
* `RecordedInteraction` is `null` whenever `secret` is `true`; assembling the
|
|
45
|
+
* actual `${credential:NAME}` placeholder is the host's job
|
|
46
|
+
* (`recordedSecretCredentialName`/`recordedSecretPlaceholder`,
|
|
47
|
+
* `descry-core`'s `driver.ts`) because only the host knows this session's
|
|
48
|
+
* running count of secrets recorded so far -- the ordinal that keeps two
|
|
49
|
+
* distinct password fields from colliding onto the same credential name.
|
|
50
|
+
*
|
|
51
|
+
* **Selector preference** reuses the identical tiered rule
|
|
52
|
+
* `playwright-driver.ts`'s own `computeSelector` already applies for a
|
|
53
|
+
* snapshot element: a `data-testid`/`data-test` hook first, then a
|
|
54
|
+
* non-generated `id`, then a named form control, then an
|
|
55
|
+
* aria-labelled-and-roled element, then a bounded structural path anchored at
|
|
56
|
+
* the nearest ancestor carrying one of the above -- each tier accepted only
|
|
57
|
+
* when it is unique in the element's own root (`document`, or the open
|
|
58
|
+
* shadow root it lives in). It is restated here, not imported, for the same
|
|
59
|
+
* reason `driver.ts` restates `PerformedTarget` rather than importing a
|
|
60
|
+
* scenario type: the string below is serialised into the page verbatim
|
|
61
|
+
* (`page.addInitScript`/`page.evaluate`), and a page-side script cannot
|
|
62
|
+
* import a Node module.
|
|
63
|
+
*
|
|
64
|
+
* **Listener lifecycle.** `page.exposeFunction` and `page.addInitScript` are
|
|
65
|
+
* each installed at most once per collector instance (lazily, on the first
|
|
66
|
+
* `start()`) -- Playwright refuses a second `exposeFunction` under the same
|
|
67
|
+
* name, and a second `addInitScript` would double-register the in-page
|
|
68
|
+
* listeners on every navigation after that. Because `addInitScript` "doesn't
|
|
69
|
+
* reach an already-loaded document" (the same contract every other probe in
|
|
70
|
+
* this package documents -- `frontend-initiator-capture.ts`,
|
|
71
|
+
* `identity-grounding`'s probes), `start()` ALSO runs the identical
|
|
72
|
+
* installer directly via `page.evaluate` against whatever document is
|
|
73
|
+
* current right now, idempotently (a `window` marker skips a second
|
|
74
|
+
* install), so a recording that starts mid-session on an already-open page
|
|
75
|
+
* is not silently missing its first document's events.
|
|
76
|
+
*
|
|
77
|
+
* A capturing session can therefore be started and stopped more than once
|
|
78
|
+
* without re-installing anything: `start()`/`stop()` do not attach or detach
|
|
79
|
+
* the page-side listeners themselves (Playwright exposes no way to
|
|
80
|
+
* un-expose a function or un-register an init script), they gate a
|
|
81
|
+
* `capturing` flag the Node-side binding handler checks before pushing to
|
|
82
|
+
* the buffer -- the identical "an unattached listener produces nothing"
|
|
83
|
+
* discipline `descry-core`'s fake driver already documents for its own
|
|
84
|
+
* `recordHuman*` methods. The one exception is the `framenavigated`
|
|
85
|
+
* listener, which genuinely is `page.on`/`page.off` in `start()`/`stop()`,
|
|
86
|
+
* matching `BrowserConsoleCollector`'s own precedent, because Playwright
|
|
87
|
+
* offers a real way to detach it.
|
|
88
|
+
*/
|
|
89
|
+
const RECORDING_BINDING_NAME = "__descryRecordInteraction";
|
|
90
|
+
/** The same interactive-element set `playwright-driver.ts`'s `INTERACTIVE` constant already scopes a snapshot to -- kept identical so a click this collector captures is always one a snapshot-based replay could also have resolved. */
|
|
91
|
+
const INTERACTIVE_SELECTOR = "a, button, input, select, textarea, [role], [onclick], summary";
|
|
92
|
+
/**
|
|
93
|
+
* Serialised and run inside the page (`page.addInitScript`/`page.evaluate`)
|
|
94
|
+
* -- closes over nothing in this module. Idempotent: re-running it against a
|
|
95
|
+
* document that already has the listeners installed (the `window` marker)
|
|
96
|
+
* is a deliberate no-op, so calling it once via `addInitScript`'s
|
|
97
|
+
* registration and once more directly via `page.evaluate` at `start()` time
|
|
98
|
+
* (for the document already open when recording starts) never double-fires
|
|
99
|
+
* an event.
|
|
100
|
+
*/
|
|
101
|
+
function installerScriptSource() {
|
|
102
|
+
return `(() => {
|
|
103
|
+
const MARKER = "__descryRecordingInstalled";
|
|
104
|
+
if (window[MARKER] === true) return;
|
|
105
|
+
const bind = window[${JSON.stringify(RECORDING_BINDING_NAME)}];
|
|
106
|
+
if (typeof bind !== "function") return;
|
|
107
|
+
window[MARKER] = true;
|
|
108
|
+
|
|
109
|
+
const GENERATED_ID = /[0-9a-f]{8}|:r[0-9a-z]+:|^\\s*$/i;
|
|
110
|
+
const FORM_CONTROLS = ["input", "select", "textarea", "button"];
|
|
111
|
+
const TEXT_INPUT_TYPES = ["", "text", "email", "tel", "url", "search", "number", "password"];
|
|
112
|
+
|
|
113
|
+
function escapeAttrValue(value) {
|
|
114
|
+
return value.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function rootOf(el) {
|
|
118
|
+
return typeof el.getRootNode === "function" ? el.getRootNode() : el.ownerDocument;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isUnique(root, candidate) {
|
|
122
|
+
try {
|
|
123
|
+
return root.querySelectorAll(candidate).length === 1;
|
|
124
|
+
} catch (e) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function stableHookSelector(node) {
|
|
130
|
+
const testId = node.getAttribute("data-testid");
|
|
131
|
+
if (testId && testId.trim() !== "") return '[data-testid="' + escapeAttrValue(testId) + '"]';
|
|
132
|
+
const testAttr = node.getAttribute("data-test");
|
|
133
|
+
if (testAttr && testAttr.trim() !== "") return '[data-test="' + escapeAttrValue(testAttr) + '"]';
|
|
134
|
+
const id = node.getAttribute("id");
|
|
135
|
+
if (id && !GENERATED_ID.test(id)) return '[id="' + escapeAttrValue(id) + '"]';
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function nthOfTypeSegment(node, parent) {
|
|
140
|
+
const tag = node.tagName.toLowerCase();
|
|
141
|
+
const siblings = parent.querySelectorAll(":scope > " + tag);
|
|
142
|
+
if (siblings.length <= 1) return tag;
|
|
143
|
+
for (let i = 0; i < siblings.length; i += 1) {
|
|
144
|
+
if (siblings[i] === node) return tag + ":nth-of-type(" + (i + 1) + ")";
|
|
145
|
+
}
|
|
146
|
+
return tag;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function structuralPath(target, root) {
|
|
150
|
+
const segments = [];
|
|
151
|
+
let node = target;
|
|
152
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
153
|
+
const parent = node.parentElement;
|
|
154
|
+
if (!parent) return null;
|
|
155
|
+
segments.unshift(nthOfTypeSegment(node, parent));
|
|
156
|
+
const hook = stableHookSelector(parent);
|
|
157
|
+
if (hook) {
|
|
158
|
+
const candidate = hook + " > " + segments.join(" > ");
|
|
159
|
+
return isUnique(root, candidate) ? candidate : null;
|
|
160
|
+
}
|
|
161
|
+
node = parent;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function computeSelector(target) {
|
|
167
|
+
const root = rootOf(target);
|
|
168
|
+
const testId = target.getAttribute("data-testid");
|
|
169
|
+
if (testId && testId.trim() !== "") {
|
|
170
|
+
const candidate = '[data-testid="' + escapeAttrValue(testId) + '"]';
|
|
171
|
+
if (isUnique(root, candidate)) return candidate;
|
|
172
|
+
}
|
|
173
|
+
const testAttr = target.getAttribute("data-test");
|
|
174
|
+
if (testAttr && testAttr.trim() !== "") {
|
|
175
|
+
const candidate = '[data-test="' + escapeAttrValue(testAttr) + '"]';
|
|
176
|
+
if (isUnique(root, candidate)) return candidate;
|
|
177
|
+
}
|
|
178
|
+
const id = target.getAttribute("id");
|
|
179
|
+
if (id && !GENERATED_ID.test(id)) {
|
|
180
|
+
const candidate = '[id="' + escapeAttrValue(id) + '"]';
|
|
181
|
+
if (isUnique(root, candidate)) return candidate;
|
|
182
|
+
}
|
|
183
|
+
const tag = target.tagName.toLowerCase();
|
|
184
|
+
const name = target.getAttribute("name");
|
|
185
|
+
if (name && name.trim() !== "" && FORM_CONTROLS.indexOf(tag) !== -1) {
|
|
186
|
+
const candidate = tag + '[name="' + escapeAttrValue(name) + '"]';
|
|
187
|
+
if (isUnique(root, candidate)) return candidate;
|
|
188
|
+
}
|
|
189
|
+
const explicitRole = target.getAttribute("role");
|
|
190
|
+
const ariaLabel = target.getAttribute("aria-label");
|
|
191
|
+
if (explicitRole && ariaLabel && ariaLabel.trim() !== "") {
|
|
192
|
+
const candidate = '[role="' + escapeAttrValue(explicitRole) + '"][aria-label="' + escapeAttrValue(ariaLabel) + '"]';
|
|
193
|
+
if (isUnique(root, candidate)) return candidate;
|
|
194
|
+
}
|
|
195
|
+
return structuralPath(target, root);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function derivedRole(el) {
|
|
199
|
+
const tag = el.tagName.toLowerCase();
|
|
200
|
+
const explicit = el.getAttribute("role");
|
|
201
|
+
if (explicit) return explicit;
|
|
202
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
203
|
+
if (tag === "a") return "link";
|
|
204
|
+
if (tag === "button") return "button";
|
|
205
|
+
if (tag === "textarea") return "textbox";
|
|
206
|
+
if (tag === "summary") return "button";
|
|
207
|
+
if (tag === "select") {
|
|
208
|
+
const multiple = el.getAttribute("multiple") !== null;
|
|
209
|
+
const size = Number(el.getAttribute("size") || "1");
|
|
210
|
+
return multiple || size > 1 ? "listbox" : "combobox";
|
|
211
|
+
}
|
|
212
|
+
if (tag !== "input") return tag;
|
|
213
|
+
switch (type) {
|
|
214
|
+
case "button":
|
|
215
|
+
case "submit":
|
|
216
|
+
case "reset":
|
|
217
|
+
case "image":
|
|
218
|
+
return "button";
|
|
219
|
+
case "checkbox":
|
|
220
|
+
return "checkbox";
|
|
221
|
+
case "radio":
|
|
222
|
+
return "radio";
|
|
223
|
+
case "range":
|
|
224
|
+
return "slider";
|
|
225
|
+
case "number":
|
|
226
|
+
return "spinbutton";
|
|
227
|
+
case "search":
|
|
228
|
+
return "searchbox";
|
|
229
|
+
default:
|
|
230
|
+
return "textbox";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function derivedName(el) {
|
|
235
|
+
const tag = el.tagName.toLowerCase();
|
|
236
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
237
|
+
const aria = el.getAttribute("aria-label");
|
|
238
|
+
if (aria && aria.trim() !== "") return aria;
|
|
239
|
+
if (tag !== "select") {
|
|
240
|
+
const inner = (el.innerText || "").trim();
|
|
241
|
+
if (inner !== "") return inner;
|
|
242
|
+
const text = (el.textContent || "").trim();
|
|
243
|
+
if (text !== "") return text;
|
|
244
|
+
}
|
|
245
|
+
if (tag === "input" && (type === "button" || type === "submit" || type === "reset")) {
|
|
246
|
+
const value = el.getAttribute("value");
|
|
247
|
+
if (value && value.trim() !== "") return value.trim();
|
|
248
|
+
}
|
|
249
|
+
return el.getAttribute("placeholder") || el.getAttribute("title") || el.getAttribute("name") || "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isPasswordField(el) {
|
|
253
|
+
if (el.tagName.toLowerCase() !== "input") return false;
|
|
254
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
255
|
+
if (type === "password") return true;
|
|
256
|
+
const autocomplete = (el.getAttribute("autocomplete") || "").toLowerCase();
|
|
257
|
+
return autocomplete === "current-password" || autocomplete === "new-password";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function describeTarget(el) {
|
|
261
|
+
return { role: derivedRole(el), name: derivedName(el).slice(0, 200), selector: computeSelector(el) };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
document.addEventListener(
|
|
265
|
+
"click",
|
|
266
|
+
function (ev) {
|
|
267
|
+
const el = ev.target && typeof ev.target.closest === "function" ? ev.target.closest(${JSON.stringify(INTERACTIVE_SELECTOR)}) : null;
|
|
268
|
+
if (!el) return;
|
|
269
|
+
const described = describeTarget(el);
|
|
270
|
+
bind({ action: "click", role: described.role, name: described.name, selector: described.selector, value: null, secret: false }).catch(function () {});
|
|
271
|
+
},
|
|
272
|
+
true,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
document.addEventListener(
|
|
276
|
+
"change",
|
|
277
|
+
function (ev) {
|
|
278
|
+
const el = ev.target;
|
|
279
|
+
if (!el || !el.tagName) return;
|
|
280
|
+
const tag = el.tagName.toLowerCase();
|
|
281
|
+
if (tag === "select") {
|
|
282
|
+
const described = describeTarget(el);
|
|
283
|
+
const value = el.multiple ? Array.prototype.map.call(el.selectedOptions, function (o) { return o.value; }).join(",") : el.value;
|
|
284
|
+
bind({ action: "select", role: described.role, name: described.name, selector: described.selector, value: value, secret: false }).catch(function () {});
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (tag === "input" || tag === "textarea") {
|
|
288
|
+
const type = (el.getAttribute("type") || "").toLowerCase();
|
|
289
|
+
// Only a text-shaped control's committed value is a "fill" -- a checkbox/radio's
|
|
290
|
+
// own change is a state toggle, not a value entered, and is already reported by
|
|
291
|
+
// the click listener above (the click that toggled it matches INTERACTIVE too).
|
|
292
|
+
if (tag === "input" && TEXT_INPUT_TYPES.indexOf(type) === -1) return;
|
|
293
|
+
const secret = isPasswordField(el);
|
|
294
|
+
const described = describeTarget(el);
|
|
295
|
+
// The whole point: a password field's real value is never read into this payload at all.
|
|
296
|
+
bind({ action: "fill", role: described.role, name: described.name, selector: described.selector, value: secret ? null : el.value, secret: secret }).catch(function () {});
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
true,
|
|
300
|
+
);
|
|
301
|
+
})();`;
|
|
302
|
+
}
|
|
303
|
+
export function createBrowserRecordingCollector(options) {
|
|
304
|
+
const { session } = options;
|
|
305
|
+
const page = session.page;
|
|
306
|
+
let capturing = false;
|
|
307
|
+
let buffer = [];
|
|
308
|
+
let bindingInstalled = false;
|
|
309
|
+
function onRecordedInteraction(payload) {
|
|
310
|
+
if (!capturing)
|
|
311
|
+
return; // an unattached listener producing nothing -- see this module's header.
|
|
312
|
+
buffer.push({
|
|
313
|
+
action: payload.action,
|
|
314
|
+
target: { role: payload.role, name: payload.name, selector: payload.selector },
|
|
315
|
+
url: null,
|
|
316
|
+
text: payload.secret ? null : payload.value,
|
|
317
|
+
secret: payload.secret,
|
|
318
|
+
at: new Date().toISOString(),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function onFrameNavigated(frame) {
|
|
322
|
+
if (!capturing)
|
|
323
|
+
return;
|
|
324
|
+
if (frame !== page.mainFrame())
|
|
325
|
+
return; // "top-level" -- an iframe's own navigation is not this session's page moving.
|
|
326
|
+
buffer.push({ action: "navigate", target: null, url: frame.url(), text: null, secret: false, at: new Date().toISOString() });
|
|
327
|
+
}
|
|
328
|
+
async function ensureInstalled() {
|
|
329
|
+
if (!bindingInstalled) {
|
|
330
|
+
// Playwright refuses a second `exposeFunction` under the same name and would
|
|
331
|
+
// double-register in-page listeners on a second `addInitScript` -- both installed
|
|
332
|
+
// at most once per collector instance, ever.
|
|
333
|
+
await page.exposeFunction(RECORDING_BINDING_NAME, onRecordedInteraction);
|
|
334
|
+
await page.addInitScript({ content: installerScriptSource() });
|
|
335
|
+
bindingInstalled = true;
|
|
336
|
+
}
|
|
337
|
+
// `addInitScript` only reaches documents navigated to AFTER it is registered -- this
|
|
338
|
+
// session may already be sitting on a page when recording starts, so the identical,
|
|
339
|
+
// idempotent installer also runs directly against whatever document is current now.
|
|
340
|
+
await page.evaluate(installerScriptSource()).catch(() => {
|
|
341
|
+
// No accessible document to evaluate against right now (e.g. mid-navigation). Not
|
|
342
|
+
// fatal: `addInitScript` still covers every document from here forward, so a
|
|
343
|
+
// human's first observable interaction is only missed if it happens in the exact
|
|
344
|
+
// instant no document is present to click or type into.
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
async start() {
|
|
349
|
+
if (!session.isUsable()) {
|
|
350
|
+
throw new Error(`browser session ${session.sessionId}: the page has already crashed or closed.`);
|
|
351
|
+
}
|
|
352
|
+
if (capturing) {
|
|
353
|
+
throw new Error(`browser session ${session.sessionId}: already recording. Call stop() before starting again.`);
|
|
354
|
+
}
|
|
355
|
+
await ensureInstalled();
|
|
356
|
+
buffer = [];
|
|
357
|
+
capturing = true;
|
|
358
|
+
page.on("framenavigated", onFrameNavigated);
|
|
359
|
+
},
|
|
360
|
+
async stop() {
|
|
361
|
+
if (!capturing) {
|
|
362
|
+
throw new Error(`browser session ${session.sessionId}: not currently recording. Call start() first.`);
|
|
363
|
+
}
|
|
364
|
+
page.off("framenavigated", onFrameNavigated);
|
|
365
|
+
capturing = false;
|
|
366
|
+
const captured = buffer;
|
|
367
|
+
buffer = [];
|
|
368
|
+
return captured;
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=browser-recording-collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-recording-collector.js","sourceRoot":"","sources":["../src/browser-recording-collector.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type { DomElementSnapshot } from "./dom-evidence.ts";
|
|
|
4
4
|
export { captureElementSnapshot } from "./dom-evidence.ts";
|
|
5
5
|
export type { BrowserActionCollectorOptions, BrowserActionCollector, WaitForOptions } from "./browser-action-collector.ts";
|
|
6
6
|
export { createBrowserActionCollector } from "./browser-action-collector.ts";
|
|
7
|
+
export type { BrowserRecordingCollectorOptions, BrowserRecordingCollector, RecordedTarget, RecordedInteraction, } from "./browser-recording-collector.ts";
|
|
8
|
+
export { createBrowserRecordingCollector } from "./browser-recording-collector.ts";
|
|
7
9
|
export type { BrowserConsoleCollectorOptions } from "./browser-console-collector.ts";
|
|
8
10
|
export { createBrowserConsoleCollector } from "./browser-console-collector.ts";
|
|
9
11
|
export type { BrowserNetworkCollectorOptions, CapturedResponseBody } from "./browser-network-collector.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,2BAA2B,EAC3B,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EAIvB,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,YAAY,EAAE,6BAA6B,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC3H,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAE7E,YAAY,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AACrF,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAE/E,YAAY,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,6BAA6B,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE7H,YAAY,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAEvE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,2BAA2B,EAC3B,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,EAC1B,2BAA2B,EAC3B,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EAIvB,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,YAAY,EAAE,6BAA6B,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC3H,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAE7E,YAAY,EACV,gCAAgC,EAChC,yBAAyB,EACzB,cAAc,EACd,mBAAmB,GACpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,+BAA+B,EAAE,MAAM,kCAAkC,CAAC;AAEnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AACrF,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAE/E,YAAY,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,6BAA6B,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAE7H,YAAY,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAEvE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { launchBrowserSession, launchBrowserSessionOrRefuse, resolveChromiumExec
|
|
|
5
5
|
createSharedBrowserManager, SHARED_BROWSER_IDLE_TIMEOUT_MS, } from "./browser-session.js";
|
|
6
6
|
export { captureElementSnapshot } from "./dom-evidence.js";
|
|
7
7
|
export { createBrowserActionCollector } from "./browser-action-collector.js";
|
|
8
|
+
export { createBrowserRecordingCollector } from "./browser-recording-collector.js";
|
|
8
9
|
export { createBrowserConsoleCollector } from "./browser-console-collector.js";
|
|
9
10
|
export { createBrowserNetworkCollector, captureResponseBody, MAX_RESPONSE_BODY_BYTES } from "./browser-network-collector.js";
|
|
10
11
|
export { extractHeaderCorrelationIds } from "./network-correlation.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB;AACvB,8EAA8E;AAC9E,qFAAqF;AACrF,mFAAmF;AACnF,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB;AACvB,8EAA8E;AAC9E,qFAAqF;AACrF,mFAAmF;AACnF,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAQ7E,OAAO,EAAE,+BAA+B,EAAE,MAAM,kCAAkC,CAAC;AAGnF,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,EAAE,6BAA6B,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAG7H,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAGvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descryy/runtime-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Browser runtime: launch, isolated context, navigation, click/type/wait, DOM inspection, screenshots, console/page-error/network capture, all through the Collector contract (plan §7/§8). See documents/decisions-inbox/PROPOSAL-B-playwright-for-browser-runtime.md for the Playwright-vs-CDP call. BrowserSession.injectStorageState (0.4.5) is the mid-session counterpart to the launch-time auth seed -- lands cookies/localStorage into a session that is already open, for a scenario's sign-in step.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -25,17 +25,17 @@
|
|
|
25
25
|
"registry": "https://registry.npmjs.org",
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "tsc -b"
|
|
30
|
-
},
|
|
31
28
|
"dependencies": {
|
|
32
29
|
"@descryy/runtime-contracts": "0.3.3",
|
|
33
30
|
"@descryy/runtime-backend-observation": "0.3.6",
|
|
34
31
|
"playwright-core": "^1.62.1",
|
|
35
|
-
"@descryy/runtime-adapter-typescript": "0.3.
|
|
32
|
+
"@descryy/runtime-adapter-typescript": "0.3.8"
|
|
36
33
|
},
|
|
37
34
|
"devDependencies": {
|
|
38
|
-
"@descryy/runtime-controller": "0.3.
|
|
35
|
+
"@descryy/runtime-controller": "0.3.9",
|
|
39
36
|
"@descryy/runtime-evidence-store": "0.4.2"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -b"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|