@undo76/agent-dom 0.1.0 → 0.2.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/README.md +19 -4
- package/dist/index.cjs +6 -2
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,17 +96,32 @@ A locator throws if it finds zero or multiple elements. This keeps agent actions
|
|
|
96
96
|
|
|
97
97
|
## Ref lifetime
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
A ref identifies one element in the last observation. By default (`stale: "connected"`) it stays valid until that **own** element leaves the document, so unrelated page churn — streamed chat, spinners, clocks, framework re-renders — does not stop you acting.
|
|
100
100
|
|
|
101
101
|
```ts
|
|
102
|
-
const
|
|
103
|
-
const
|
|
102
|
+
const obs = page.observe({ interactiveOnly: true });
|
|
103
|
+
const link = obs.findByRole("link", { name: "Home" }).ref;
|
|
104
|
+
|
|
105
|
+
chat.append(document.createTextNode("thinking…")); // sibling subtree mutates
|
|
106
|
+
page.click(link); // works
|
|
107
|
+
|
|
108
|
+
sidebar.querySelector("a")?.remove();
|
|
109
|
+
page.click(link); // StaleElementReferenceError
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Opt into strict snapshot semantics with `stale: "generation"`, where any mutation under the root invalidates every older ref:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const strict = createAgentPage(window, { stale: "generation" });
|
|
116
|
+
const ref = strict.observe().findByRole("button").ref;
|
|
104
117
|
|
|
105
118
|
document.body.append(document.createElement("div"));
|
|
106
119
|
|
|
107
|
-
|
|
120
|
+
strict.click(ref); // throws StaleElementReferenceError
|
|
108
121
|
```
|
|
109
122
|
|
|
123
|
+
The trade is freshness, not safety: under `"connected"` a ref can resolve to an element whose text changed since you observed it. Re-observe when the label is the contract (totals, quantities, confirm dialogs); `disabled` and removed elements are still caught.
|
|
124
|
+
|
|
110
125
|
## Browser boundaries
|
|
111
126
|
|
|
112
127
|
The library traverses the current document and open shadow roots. Normal page JavaScript cannot inspect closed shadow roots or cross-origin iframe documents. A browser extension can inject one `AgentPage` into each permitted frame and merge the results in a coordinator.
|
package/dist/index.cjs
CHANGED
|
@@ -462,12 +462,14 @@ var AgentPage = class {
|
|
|
462
462
|
root;
|
|
463
463
|
#documentGeneration = 0;
|
|
464
464
|
#snapshotGeneration = 0;
|
|
465
|
+
#stale;
|
|
465
466
|
#refState;
|
|
466
467
|
#observation;
|
|
467
468
|
#observer;
|
|
468
469
|
constructor(window, options = {}) {
|
|
469
470
|
this.window = window;
|
|
470
471
|
this.root = options.root ?? window.document;
|
|
472
|
+
this.#stale = options.stale ?? "connected";
|
|
471
473
|
const document = ownerDocument(this.root);
|
|
472
474
|
if (document.defaultView !== window) {
|
|
473
475
|
throw new TypeError("The root must belong to the supplied window.");
|
|
@@ -568,7 +570,9 @@ var AgentPage = class {
|
|
|
568
570
|
#freshObservation() {
|
|
569
571
|
this.#syncMutations();
|
|
570
572
|
if (!this.#observation || !this.#refState) return this.observe();
|
|
571
|
-
if (this.#refState.documentGeneration !== this.#documentGeneration)
|
|
573
|
+
if (this.#stale === "generation" && this.#refState.documentGeneration !== this.#documentGeneration) {
|
|
574
|
+
return this.observe();
|
|
575
|
+
}
|
|
572
576
|
return this.#observation;
|
|
573
577
|
}
|
|
574
578
|
#resolve(ref) {
|
|
@@ -576,7 +580,7 @@ var AgentPage = class {
|
|
|
576
580
|
const normalized = normalizeRef(ref);
|
|
577
581
|
const state = this.#refState;
|
|
578
582
|
if (!state) throw new ElementNotFoundError("Observe the page before using a ref.");
|
|
579
|
-
if (state.documentGeneration !== this.#documentGeneration) {
|
|
583
|
+
if (this.#stale === "generation" && state.documentGeneration !== this.#documentGeneration) {
|
|
580
584
|
throw new StaleElementReferenceError(normalized, state.snapshotGeneration, this.#snapshotGeneration + 1);
|
|
581
585
|
}
|
|
582
586
|
const element = state.refs.get(normalized);
|
package/dist/index.d.cts
CHANGED
|
@@ -74,7 +74,19 @@ type AgentAction = {
|
|
|
74
74
|
};
|
|
75
75
|
interface CreateAgentPageOptions {
|
|
76
76
|
root?: Document | Element | ShadowRoot;
|
|
77
|
+
/**
|
|
78
|
+
* When a ref stops being usable.
|
|
79
|
+
*
|
|
80
|
+
* - `"connected"` (default): a ref stays valid until *its own* element is removed.
|
|
81
|
+
* Real pages never stop mutating — streaming chat, spinners, clocks, toasts,
|
|
82
|
+
* virtualised lists, framework re-renders. A policy that reacted to all of it
|
|
83
|
+
* would mean no action ever lands on a live page.
|
|
84
|
+
* - `"generation"`: any mutation anywhere under the root invalidates every ref from
|
|
85
|
+
* the previous observation. Opt in for stepwise harnesses that must be current.
|
|
86
|
+
*/
|
|
87
|
+
stale?: StalePolicy;
|
|
77
88
|
}
|
|
89
|
+
type StalePolicy = "generation" | "connected";
|
|
78
90
|
|
|
79
91
|
declare class AgentPage {
|
|
80
92
|
#private;
|
|
@@ -125,4 +137,4 @@ declare class ActionError extends AgentDomError {
|
|
|
125
137
|
readonly name: string;
|
|
126
138
|
}
|
|
127
139
|
|
|
128
|
-
export { ActionError, type AgentAction, AgentDomError, AgentObservation, AgentPage, type BrowserWindow, type CreateAgentPageOptions, ElementNotFoundError, type ElementState, type Observation, type ObserveOptions, type ObservedElement, type RoleLocatorOptions, StaleElementReferenceError, type TextMatch, createAgentPage };
|
|
140
|
+
export { ActionError, type AgentAction, AgentDomError, AgentObservation, AgentPage, type BrowserWindow, type CreateAgentPageOptions, ElementNotFoundError, type ElementState, type Observation, type ObserveOptions, type ObservedElement, type RoleLocatorOptions, StaleElementReferenceError, type StalePolicy, type TextMatch, createAgentPage };
|
package/dist/index.d.ts
CHANGED
|
@@ -74,7 +74,19 @@ type AgentAction = {
|
|
|
74
74
|
};
|
|
75
75
|
interface CreateAgentPageOptions {
|
|
76
76
|
root?: Document | Element | ShadowRoot;
|
|
77
|
+
/**
|
|
78
|
+
* When a ref stops being usable.
|
|
79
|
+
*
|
|
80
|
+
* - `"connected"` (default): a ref stays valid until *its own* element is removed.
|
|
81
|
+
* Real pages never stop mutating — streaming chat, spinners, clocks, toasts,
|
|
82
|
+
* virtualised lists, framework re-renders. A policy that reacted to all of it
|
|
83
|
+
* would mean no action ever lands on a live page.
|
|
84
|
+
* - `"generation"`: any mutation anywhere under the root invalidates every ref from
|
|
85
|
+
* the previous observation. Opt in for stepwise harnesses that must be current.
|
|
86
|
+
*/
|
|
87
|
+
stale?: StalePolicy;
|
|
77
88
|
}
|
|
89
|
+
type StalePolicy = "generation" | "connected";
|
|
78
90
|
|
|
79
91
|
declare class AgentPage {
|
|
80
92
|
#private;
|
|
@@ -125,4 +137,4 @@ declare class ActionError extends AgentDomError {
|
|
|
125
137
|
readonly name: string;
|
|
126
138
|
}
|
|
127
139
|
|
|
128
|
-
export { ActionError, type AgentAction, AgentDomError, AgentObservation, AgentPage, type BrowserWindow, type CreateAgentPageOptions, ElementNotFoundError, type ElementState, type Observation, type ObserveOptions, type ObservedElement, type RoleLocatorOptions, StaleElementReferenceError, type TextMatch, createAgentPage };
|
|
140
|
+
export { ActionError, type AgentAction, AgentDomError, AgentObservation, AgentPage, type BrowserWindow, type CreateAgentPageOptions, ElementNotFoundError, type ElementState, type Observation, type ObserveOptions, type ObservedElement, type RoleLocatorOptions, StaleElementReferenceError, type StalePolicy, type TextMatch, createAgentPage };
|
package/dist/index.js
CHANGED
|
@@ -434,12 +434,14 @@ var AgentPage = class {
|
|
|
434
434
|
root;
|
|
435
435
|
#documentGeneration = 0;
|
|
436
436
|
#snapshotGeneration = 0;
|
|
437
|
+
#stale;
|
|
437
438
|
#refState;
|
|
438
439
|
#observation;
|
|
439
440
|
#observer;
|
|
440
441
|
constructor(window, options = {}) {
|
|
441
442
|
this.window = window;
|
|
442
443
|
this.root = options.root ?? window.document;
|
|
444
|
+
this.#stale = options.stale ?? "connected";
|
|
443
445
|
const document = ownerDocument(this.root);
|
|
444
446
|
if (document.defaultView !== window) {
|
|
445
447
|
throw new TypeError("The root must belong to the supplied window.");
|
|
@@ -540,7 +542,9 @@ var AgentPage = class {
|
|
|
540
542
|
#freshObservation() {
|
|
541
543
|
this.#syncMutations();
|
|
542
544
|
if (!this.#observation || !this.#refState) return this.observe();
|
|
543
|
-
if (this.#refState.documentGeneration !== this.#documentGeneration)
|
|
545
|
+
if (this.#stale === "generation" && this.#refState.documentGeneration !== this.#documentGeneration) {
|
|
546
|
+
return this.observe();
|
|
547
|
+
}
|
|
544
548
|
return this.#observation;
|
|
545
549
|
}
|
|
546
550
|
#resolve(ref) {
|
|
@@ -548,7 +552,7 @@ var AgentPage = class {
|
|
|
548
552
|
const normalized = normalizeRef(ref);
|
|
549
553
|
const state = this.#refState;
|
|
550
554
|
if (!state) throw new ElementNotFoundError("Observe the page before using a ref.");
|
|
551
|
-
if (state.documentGeneration !== this.#documentGeneration) {
|
|
555
|
+
if (this.#stale === "generation" && state.documentGeneration !== this.#documentGeneration) {
|
|
552
556
|
throw new StaleElementReferenceError(normalized, state.snapshotGeneration, this.#snapshotGeneration + 1);
|
|
553
557
|
}
|
|
554
558
|
const element = state.refs.get(normalized);
|