claw-browser-automation 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.
Files changed (135) hide show
  1. package/README.md +288 -0
  2. package/dist/actions/action.d.ts +50 -0
  3. package/dist/actions/action.d.ts.map +1 -0
  4. package/dist/actions/action.js +189 -0
  5. package/dist/actions/action.js.map +1 -0
  6. package/dist/actions/assertions.d.ts +15 -0
  7. package/dist/actions/assertions.d.ts.map +1 -0
  8. package/dist/actions/assertions.js +75 -0
  9. package/dist/actions/assertions.js.map +1 -0
  10. package/dist/actions/extract-structured.d.ts +26 -0
  11. package/dist/actions/extract-structured.d.ts.map +1 -0
  12. package/dist/actions/extract-structured.js +112 -0
  13. package/dist/actions/extract-structured.js.map +1 -0
  14. package/dist/actions/extract.d.ts +13 -0
  15. package/dist/actions/extract.d.ts.map +1 -0
  16. package/dist/actions/extract.js +119 -0
  17. package/dist/actions/extract.js.map +1 -0
  18. package/dist/actions/interact.d.ts +32 -0
  19. package/dist/actions/interact.d.ts.map +1 -0
  20. package/dist/actions/interact.js +263 -0
  21. package/dist/actions/interact.js.map +1 -0
  22. package/dist/actions/navigate.d.ts +13 -0
  23. package/dist/actions/navigate.d.ts.map +1 -0
  24. package/dist/actions/navigate.js +91 -0
  25. package/dist/actions/navigate.js.map +1 -0
  26. package/dist/actions/page.d.ts +21 -0
  27. package/dist/actions/page.d.ts.map +1 -0
  28. package/dist/actions/page.js +63 -0
  29. package/dist/actions/page.js.map +1 -0
  30. package/dist/actions/resilience.d.ts +21 -0
  31. package/dist/actions/resilience.d.ts.map +1 -0
  32. package/dist/actions/resilience.js +112 -0
  33. package/dist/actions/resilience.js.map +1 -0
  34. package/dist/actions/semantic.d.ts +58 -0
  35. package/dist/actions/semantic.d.ts.map +1 -0
  36. package/dist/actions/semantic.js +181 -0
  37. package/dist/actions/semantic.js.map +1 -0
  38. package/dist/actions/wait.d.ts +10 -0
  39. package/dist/actions/wait.d.ts.map +1 -0
  40. package/dist/actions/wait.js +69 -0
  41. package/dist/actions/wait.js.map +1 -0
  42. package/dist/errors.d.ts +30 -0
  43. package/dist/errors.d.ts.map +1 -0
  44. package/dist/errors.js +54 -0
  45. package/dist/errors.js.map +1 -0
  46. package/dist/index.d.ts +34 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +88 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/observe/logger.d.ts +4 -0
  51. package/dist/observe/logger.d.ts.map +1 -0
  52. package/dist/observe/logger.js +52 -0
  53. package/dist/observe/logger.js.map +1 -0
  54. package/dist/observe/trace.d.ts +46 -0
  55. package/dist/observe/trace.d.ts.map +1 -0
  56. package/dist/observe/trace.js +102 -0
  57. package/dist/observe/trace.js.map +1 -0
  58. package/dist/plugin.d.ts +9 -0
  59. package/dist/plugin.d.ts.map +1 -0
  60. package/dist/plugin.js +58 -0
  61. package/dist/plugin.js.map +1 -0
  62. package/dist/pool/browser-pool.d.ts +48 -0
  63. package/dist/pool/browser-pool.d.ts.map +1 -0
  64. package/dist/pool/browser-pool.js +216 -0
  65. package/dist/pool/browser-pool.js.map +1 -0
  66. package/dist/pool/health.d.ts +28 -0
  67. package/dist/pool/health.d.ts.map +1 -0
  68. package/dist/pool/health.js +96 -0
  69. package/dist/pool/health.js.map +1 -0
  70. package/dist/selectors/strategy.d.ts +37 -0
  71. package/dist/selectors/strategy.d.ts.map +1 -0
  72. package/dist/selectors/strategy.js +111 -0
  73. package/dist/selectors/strategy.js.map +1 -0
  74. package/dist/session/handle-registry.d.ts +59 -0
  75. package/dist/session/handle-registry.d.ts.map +1 -0
  76. package/dist/session/handle-registry.js +92 -0
  77. package/dist/session/handle-registry.js.map +1 -0
  78. package/dist/session/profiles.d.ts +11 -0
  79. package/dist/session/profiles.d.ts.map +1 -0
  80. package/dist/session/profiles.js +76 -0
  81. package/dist/session/profiles.js.map +1 -0
  82. package/dist/session/session.d.ts +34 -0
  83. package/dist/session/session.d.ts.map +1 -0
  84. package/dist/session/session.js +155 -0
  85. package/dist/session/session.js.map +1 -0
  86. package/dist/session/snapshot.d.ts +18 -0
  87. package/dist/session/snapshot.d.ts.map +1 -0
  88. package/dist/session/snapshot.js +2 -0
  89. package/dist/session/snapshot.js.map +1 -0
  90. package/dist/store/action-log.d.ts +31 -0
  91. package/dist/store/action-log.d.ts.map +1 -0
  92. package/dist/store/action-log.js +52 -0
  93. package/dist/store/action-log.js.map +1 -0
  94. package/dist/store/artifacts.d.ts +22 -0
  95. package/dist/store/artifacts.d.ts.map +1 -0
  96. package/dist/store/artifacts.js +101 -0
  97. package/dist/store/artifacts.js.map +1 -0
  98. package/dist/store/db.d.ts +16 -0
  99. package/dist/store/db.d.ts.map +1 -0
  100. package/dist/store/db.js +91 -0
  101. package/dist/store/db.js.map +1 -0
  102. package/dist/store/sessions.d.ts +25 -0
  103. package/dist/store/sessions.d.ts.map +1 -0
  104. package/dist/store/sessions.js +63 -0
  105. package/dist/store/sessions.js.map +1 -0
  106. package/dist/tools/action-tools.d.ts +4 -0
  107. package/dist/tools/action-tools.d.ts.map +1 -0
  108. package/dist/tools/action-tools.js +356 -0
  109. package/dist/tools/action-tools.js.map +1 -0
  110. package/dist/tools/approval-tools.d.ts +4 -0
  111. package/dist/tools/approval-tools.d.ts.map +1 -0
  112. package/dist/tools/approval-tools.js +28 -0
  113. package/dist/tools/approval-tools.js.map +1 -0
  114. package/dist/tools/context.d.ts +31 -0
  115. package/dist/tools/context.d.ts.map +1 -0
  116. package/dist/tools/context.js +32 -0
  117. package/dist/tools/context.js.map +1 -0
  118. package/dist/tools/handle-tools.d.ts +4 -0
  119. package/dist/tools/handle-tools.d.ts.map +1 -0
  120. package/dist/tools/handle-tools.js +103 -0
  121. package/dist/tools/handle-tools.js.map +1 -0
  122. package/dist/tools/page-tools.d.ts +4 -0
  123. package/dist/tools/page-tools.d.ts.map +1 -0
  124. package/dist/tools/page-tools.js +109 -0
  125. package/dist/tools/page-tools.js.map +1 -0
  126. package/dist/tools/semantic-tools.d.ts +4 -0
  127. package/dist/tools/semantic-tools.d.ts.map +1 -0
  128. package/dist/tools/semantic-tools.js +173 -0
  129. package/dist/tools/semantic-tools.js.map +1 -0
  130. package/dist/tools/session-tools.d.ts +18 -0
  131. package/dist/tools/session-tools.d.ts.map +1 -0
  132. package/dist/tools/session-tools.js +118 -0
  133. package/dist/tools/session-tools.js.map +1 -0
  134. package/openclaw.plugin.json +15 -0
  135. package/package.json +68 -0
package/README.md ADDED
@@ -0,0 +1,288 @@
1
+ # claw-browser-automation
2
+
3
+ Reliable browser automation layer for [OpenClaw](https://github.com/openclaw/openclaw). Replaces the flaky extension relay with managed Playwright sessions that the AI agent drives directly.
4
+
5
+ The agent (clawbot) is the workflow engine — it decides *what* to do. This layer provides reliable *how*: atomic browser actions with postcondition verification, automatic retries, session persistence, and full observability.
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────┐
9
+ │ OpenClaw Agent (clawbot) │
10
+ │ - Receives user intent via any channel │
11
+ │ - Decides what browser actions to take │
12
+ │ - Calls browser tools exposed by this skill │
13
+ └──────────────┬──────────────────────────────────┘
14
+ │ tool calls
15
+ ┌──────────────▼──────────────────────────────────┐
16
+ │ claw-browser-automation (this project) │
17
+ │ │
18
+ │ Tool Layer → Action Engine → Browser Pool │
19
+ │ │ │
20
+ │ State & Artifacts (SQLite) │
21
+ └──────────────┬──────────────────────────────────┘
22
+
23
+ ┌──────────▼──────────┐
24
+ │ Chromium (managed) │
25
+ └─────────────────────┘
26
+ ```
27
+
28
+ ## Prerequisites
29
+
30
+ - **Node.js** >= 22.12.0
31
+ - **Bun** (package manager)
32
+ - **OpenClaw** >= 2026.2.9 installed globally
33
+ - **Playwright browsers** installed (`npx playwright install chromium`)
34
+
35
+ ## Quick start
36
+
37
+ ### 1. Install as an OpenClaw plugin
38
+
39
+ ```bash
40
+ openclaw plugins install claw-browser-automation
41
+ ```
42
+
43
+ ### 2. Install Playwright browsers (first time only)
44
+
45
+ ```bash
46
+ npx playwright install chromium
47
+ ```
48
+
49
+ ### 3. Configure (optional)
50
+
51
+ All configuration has sensible defaults. To customize, edit `~/.openclaw/openclaw.json`:
52
+
53
+ ```jsonc
54
+ {
55
+ "skills": {
56
+ "entries": {
57
+ "browser-automation": {
58
+ "enabled": true,
59
+ "config": {
60
+ "maxContexts": 4,
61
+ "headless": true
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### 4. Start OpenClaw
70
+
71
+ ```bash
72
+ openclaw start
73
+ ```
74
+
75
+ The browser automation plugin loads automatically. The agent now has access to 19 browser tools and can perform any browser task you ask for via Telegram, the CLI, or any other configured channel.
76
+
77
+ ### Alternative: install from source
78
+
79
+ If you prefer to work from a local checkout instead of the published package:
80
+
81
+ ```bash
82
+ git clone https://github.com/ametel01/claw-browser-automation
83
+ cd claw-browser-automation
84
+ bun install
85
+ bun run build
86
+ ```
87
+
88
+ Then point OpenClaw at the source tree in `~/.openclaw/openclaw.json`:
89
+
90
+ ```jsonc
91
+ {
92
+ "skills": {
93
+ "load": {
94
+ "extraDirs": ["/path/to/claw-browser-automation"]
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Programmatic usage
101
+
102
+ You can also use the library directly without OpenClaw:
103
+
104
+ ```typescript
105
+ import { createSkill } from "claw-browser-automation";
106
+
107
+ const skill = await createSkill({
108
+ maxContexts: 4,
109
+ headless: true,
110
+ });
111
+
112
+ // skill.tools — array of 19 ToolDefinition objects
113
+ // skill.context — internal state (pool, store, trace, etc.)
114
+ // skill.shutdown() — graceful shutdown
115
+ ```
116
+
117
+ ## How it works with OpenClaw
118
+
119
+ When you send a message like "go to example.com and extract the main heading", the flow is:
120
+
121
+ 1. **You** send a message via Telegram / CLI / API
122
+ 2. **OpenClaw agent** receives the intent and plans a tool sequence
123
+ 3. **Agent calls** `browser_open` → `browser_navigate` → `browser_extract_text` → `browser_close`
124
+ 4. **This skill** executes each tool against a managed Playwright browser
125
+ 5. **Agent returns** the extracted data to you
126
+
127
+ The agent composes tools into arbitrary workflows. This layer doesn't hardcode any specific task — it provides primitives the agent chains together.
128
+
129
+ ## Available tools
130
+
131
+ The skill exposes 19 tools to the agent, grouped by function:
132
+
133
+ ### Session management
134
+
135
+ | Tool | Description |
136
+ |------|-------------|
137
+ | `browser_open` | Open a new browser session, optionally with a URL or named profile |
138
+ | `browser_close` | Close session and save a snapshot for later restore |
139
+ | `browser_list` | List all active sessions with URLs and health status |
140
+ | `browser_restore` | Restore a previously suspended session from its snapshot |
141
+ | `browser_state` | Get current page state (URL, title, loading status) |
142
+
143
+ ### Page actions
144
+
145
+ | Tool | Description |
146
+ |------|-------------|
147
+ | `browser_navigate` | Navigate to a URL and wait for load |
148
+ | `browser_click` | Click an element by CSS selector |
149
+ | `browser_type` | Type text into an input field |
150
+ | `browser_select` | Select a dropdown option |
151
+ | `browser_fill_form` | Fill multiple form fields at once |
152
+ | `browser_wait` | Wait for an element state or a JS condition |
153
+
154
+ ### Data extraction
155
+
156
+ | Tool | Description |
157
+ |------|-------------|
158
+ | `browser_extract_text` | Extract text content from a single element |
159
+ | `browser_extract_all` | Extract data from all matching elements (lists, tables) |
160
+ | `browser_get_content` | Get cleaned page text (scripts/styles removed) |
161
+
162
+ ### Page utilities
163
+
164
+ | Tool | Description |
165
+ |------|-------------|
166
+ | `browser_screenshot` | Capture a screenshot and save as artifact |
167
+ | `browser_evaluate` | Execute arbitrary JavaScript in the page |
168
+ | `browser_scroll` | Scroll the page in a direction |
169
+ | `browser_session_trace` | Get the full action trace for a session |
170
+
171
+ ### Safety
172
+
173
+ | Tool | Description |
174
+ |------|-------------|
175
+ | `browser_request_approval` | Pause and ask the human for confirmation before proceeding |
176
+
177
+ ## Configuration
178
+
179
+ All configuration is optional. Defaults work out of the box.
180
+
181
+ | Option | Default | Description |
182
+ |--------|---------|-------------|
183
+ | `maxContexts` | `4` | Maximum concurrent browser sessions |
184
+ | `headless` | `true` | Run browsers without a visible window |
185
+ | `dbPath` | `~/.openclaw/browser-automation/store.db` | SQLite database for session persistence |
186
+ | `artifactsDir` | `~/.openclaw/workspace/browser-automation/artifacts` | Screenshot and DOM snapshot storage |
187
+ | `logLevel` | `info` | Log level (`debug`, `info`, `warn`, `error`) |
188
+
189
+ Pass these via the `config` key in your `openclaw.json` skill entry, or programmatically via `createSkill()` (see [Quick start](#quick-start)).
190
+
191
+ ## Reliability features
192
+
193
+ These are the mechanisms that make this layer production-grade compared to an extension relay:
194
+
195
+ - **3-tier timeouts** — short (5s), medium (15s), long (45s) per action type
196
+ - **Exponential backoff retries** with jitter on every action
197
+ - **DOM stability checks** before reads and clicks (MutationObserver-based)
198
+ - **Automatic popup/cookie banner dismissal** — recognizes 13 common patterns
199
+ - **Health probes with circuit breaker** — detects browser crashes, auto-restarts
200
+ - **Session snapshots** — URL, cookies, localStorage checkpointed to SQLite
201
+ - **Pre/postcondition verification** per action
202
+ - **Layered selector resolution** — CSS, ARIA, text, label, test ID, XPath with fallback chains
203
+
204
+ ## Data persistence
205
+
206
+ All session state survives process restarts:
207
+
208
+ ```
209
+ ~/.openclaw/browser-automation/
210
+ ├── store.db # SQLite: sessions, action log, schema
211
+ └── ...
212
+
213
+ ~/.openclaw/workspace/browser-automation/
214
+ ├── artifacts/
215
+ │ └── {sessionId}/ # Screenshots and DOM snapshots per session
216
+ └── logs/
217
+ └── browser-automation-YYYY-MM-DD.log
218
+ ```
219
+
220
+ The agent can suspend a session, shut down, restart hours later, and resume exactly where it left off — same URL, same cookies, same localStorage.
221
+
222
+ ## Development
223
+
224
+ ### Scripts
225
+
226
+ ```bash
227
+ bun run build # Compile TypeScript to dist/
228
+ bun run test # Run all tests (183 tests across 12 files)
229
+ bun run test:watch # Watch mode
230
+ bun run check # Biome lint + format check
231
+ bun run check:fix # Auto-fix lint/format issues
232
+ bun run typecheck # TypeScript strict mode check
233
+ ```
234
+
235
+ ### Running integration tests
236
+
237
+ The integration tests prove end-to-end reliability across 9 scenarios: pool lifecycle, DOM extraction, crash recovery, popup dismissal, form filling, concurrent sessions, action retry, full tool chain, and session suspend/restore.
238
+
239
+ ```bash
240
+ # Single run
241
+ bun run test -- tests/integration/integration.test.ts
242
+
243
+ # Reliability check (10 consecutive passes required)
244
+ for i in {1..10}; do bun run test -- tests/integration/integration.test.ts || exit 1; done
245
+ ```
246
+
247
+ ### Project structure
248
+
249
+ ```
250
+ src/
251
+ ├── index.ts # Library entry point — createSkill()
252
+ ├── plugin.ts # OpenClaw plugin adapter — register(api)
253
+ ├── pool/
254
+ │ ├── browser-pool.ts # Session lifecycle, max-context enforcement
255
+ │ └── health.ts # Health probes, circuit breaker recovery
256
+ ├── session/
257
+ │ ├── session.ts # BrowserSession with snapshot/restore
258
+ │ ├── snapshot.ts # SessionSnapshot type (URL, cookies, localStorage)
259
+ │ └── profiles.ts # Named profile persistence
260
+ ├── actions/
261
+ │ ├── action.ts # executeAction framework (retries, timeouts, tracing)
262
+ │ ├── interact.ts # click, type, fill, select, check, hover, drag
263
+ │ ├── extract.ts # getText, getAll, getPageContent
264
+ │ ├── navigate.ts # navigate, reload, goBack, goForward
265
+ │ ├── wait.ts # waitForSelector, waitForCondition, waitForNetworkIdle
266
+ │ ├── page.ts # screenshot, evaluate, scroll, getPageState
267
+ │ └── resilience.ts # PopupDismisser, waitForDomStability
268
+ ├── selectors/
269
+ │ └── strategy.ts # Layered selector resolution (CSS/ARIA/text/label/xpath)
270
+ ├── store/
271
+ │ ├── db.ts # SQLite with auto-migrations
272
+ │ ├── sessions.ts # SessionStore (CRUD + suspend/restore)
273
+ │ ├── action-log.ts # Every action logged with timing and result
274
+ │ └── artifacts.ts # Screenshot/snapshot storage with retention
275
+ ├── observe/
276
+ │ ├── logger.ts # Pino structured logging (stdout + file)
277
+ │ └── trace.ts # Per-session action traces with p50/p95 stats
278
+ └── tools/
279
+ ├── context.ts # SkillContext type, helper functions
280
+ ├── session-tools.ts # browser_open, close, list, restore, state
281
+ ├── action-tools.ts # browser_navigate, click, type, fill, extract, wait
282
+ ├── page-tools.ts # browser_screenshot, evaluate, scroll, trace
283
+ └── approval-tools.ts # browser_request_approval
284
+ ```
285
+
286
+ ## License
287
+
288
+ MIT
@@ -0,0 +1,50 @@
1
+ import type { Page } from "playwright-core";
2
+ import type { Logger } from "../observe/logger.js";
3
+ import type { ActionTrace, SelectorResolutionTrace } from "../observe/trace.js";
4
+ export interface StructuredError {
5
+ code: string;
6
+ message: string;
7
+ recoveryHint: string;
8
+ }
9
+ export interface ActionResult<T = unknown> {
10
+ ok: boolean;
11
+ data?: T;
12
+ error?: string;
13
+ structuredError?: StructuredError;
14
+ retries: number;
15
+ durationMs: number;
16
+ screenshot?: string;
17
+ }
18
+ export declare function toStructuredError(err: unknown): string | StructuredError;
19
+ export type TimeoutTier = "short" | "medium" | "long";
20
+ export declare function resolveTimeout(timeout: TimeoutTier | number | undefined): number;
21
+ export interface TraceMetadata {
22
+ selectorResolved?: SelectorResolutionTrace;
23
+ eventsDispatched?: string[];
24
+ waitsPerformed?: string[];
25
+ assertionsChecked?: string[];
26
+ }
27
+ export interface ActionContext {
28
+ page: Page;
29
+ logger: Logger;
30
+ screenshotDir?: string;
31
+ sessionId?: string;
32
+ trace?: ActionTrace;
33
+ _traceMeta?: TraceMetadata;
34
+ _retryState?: RetryState;
35
+ }
36
+ export interface RetryState {
37
+ lastClickSelector?: string;
38
+ lastClickTime?: number;
39
+ }
40
+ export interface ActionOptions {
41
+ timeout?: TimeoutTier | number;
42
+ retries?: number;
43
+ screenshotOnFailure?: boolean;
44
+ precondition?: (ctx: ActionContext) => Promise<boolean>;
45
+ postcondition?: (ctx: ActionContext) => Promise<boolean>;
46
+ /** Internal: mutable array of selector strategies for rotation on retry. */
47
+ _selectorStrategies?: unknown[];
48
+ }
49
+ export declare function executeAction<T>(ctx: ActionContext, name: string, opts: ActionOptions, fn: (ctx: ActionContext, timeoutMs: number) => Promise<T>): Promise<ActionResult<T>>;
50
+ //# sourceMappingURL=action.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../src/actions/action.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAM5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAGhF,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACxC,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAQxE;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAQtD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAQhF;AAED,MAAM,WAAW,aAAa;IAC7B,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,WAAW,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,4EAA4E;IAC5E,mBAAmB,CAAC,EAAE,OAAO,EAAE,CAAC;CAChC;AAgHD,wBAAsB,aAAa,CAAC,CAAC,EACpC,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,EACnB,EAAE,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACvD,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CA8C1B"}
@@ -0,0 +1,189 @@
1
+ import { BrowserAutomationError, NavigationInterruptedError, TargetNotFoundError, } from "../errors.js";
2
+ import { PopupDismisser } from "./resilience.js";
3
+ export function toStructuredError(err) {
4
+ if (err instanceof BrowserAutomationError) {
5
+ return { code: err.code, message: err.message, recoveryHint: err.recoveryHint };
6
+ }
7
+ if (err instanceof Error) {
8
+ return err.message;
9
+ }
10
+ return String(err);
11
+ }
12
+ const TIMEOUT_VALUES = {
13
+ short: 5_000,
14
+ medium: 15_000,
15
+ long: 45_000,
16
+ };
17
+ export function resolveTimeout(timeout) {
18
+ if (timeout === undefined) {
19
+ return TIMEOUT_VALUES.medium;
20
+ }
21
+ if (typeof timeout === "number") {
22
+ return timeout;
23
+ }
24
+ return TIMEOUT_VALUES[timeout];
25
+ }
26
+ const DEFAULT_RETRIES = 3;
27
+ async function runAttempt(ctx, opts, fn, timeoutMs) {
28
+ if (opts.precondition && !(await opts.precondition(ctx))) {
29
+ return { tag: "retry", error: "precondition failed" };
30
+ }
31
+ const data = await fn(ctx, timeoutMs);
32
+ if (opts.postcondition && !(await opts.postcondition(ctx))) {
33
+ return { tag: "retry", error: "postcondition failed" };
34
+ }
35
+ return { tag: "success", data };
36
+ }
37
+ async function retryLoop(ctx, name, opts, fn, timeoutMs, maxRetries, startUrl, popupDismisser) {
38
+ let lastError = "";
39
+ let lastCaughtError;
40
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
41
+ lastCaughtError = undefined;
42
+ const navCheck = checkNavigationGuard(attempt, ctx, startUrl);
43
+ if (navCheck) {
44
+ return { tag: "exhausted", lastError: navCheck.message, lastCaughtError: navCheck };
45
+ }
46
+ try {
47
+ await popupDismisser.dismissOnce();
48
+ const outcome = await runAttempt(ctx, opts, fn, timeoutMs);
49
+ if (outcome.tag === "success") {
50
+ return { tag: "success", data: outcome.data, attempt };
51
+ }
52
+ lastError = outcome.error;
53
+ ctx.logger.warn({ action: name, attempt }, lastError);
54
+ }
55
+ catch (err) {
56
+ lastCaughtError = err;
57
+ lastError = err instanceof Error ? err.message : String(err);
58
+ ctx.logger.warn({ action: name, attempt, error: lastError }, "action attempt failed");
59
+ handleRetryError(err, opts, ctx, name);
60
+ }
61
+ if (attempt < maxRetries) {
62
+ await backoff(attempt);
63
+ }
64
+ }
65
+ return { tag: "exhausted", lastError, lastCaughtError };
66
+ }
67
+ function checkNavigationGuard(attempt, ctx, startUrl) {
68
+ if (attempt === 0) {
69
+ return undefined;
70
+ }
71
+ const currentUrl = ctx.page.url();
72
+ if (currentUrl !== startUrl) {
73
+ return new NavigationInterruptedError(`page navigated from ${startUrl} to ${currentUrl} during retry`);
74
+ }
75
+ return undefined;
76
+ }
77
+ function handleRetryError(err, opts, ctx, name) {
78
+ if (err instanceof TargetNotFoundError && opts._selectorStrategies) {
79
+ rotateStrategies(opts._selectorStrategies);
80
+ ctx.logger.debug({ action: name }, "rotated selector strategies for retry");
81
+ }
82
+ }
83
+ function rotateStrategies(strategies) {
84
+ if (strategies.length <= 1) {
85
+ return;
86
+ }
87
+ const first = strategies.shift();
88
+ if (first !== undefined) {
89
+ strategies.push(first);
90
+ }
91
+ }
92
+ export async function executeAction(ctx, name, opts, fn) {
93
+ const maxRetries = opts.retries ?? DEFAULT_RETRIES;
94
+ const timeoutMs = resolveTimeout(opts.timeout);
95
+ const startedAt = performance.now();
96
+ const popupDismisser = new PopupDismisser(ctx.page, ctx.logger);
97
+ popupDismisser.start();
98
+ ctx._traceMeta = {};
99
+ ctx._retryState = {};
100
+ const startUrl = ctx.page.url();
101
+ try {
102
+ const loopResult = await retryLoop(ctx, name, opts, fn, timeoutMs, maxRetries, startUrl, popupDismisser);
103
+ if (loopResult.tag === "success") {
104
+ const durationMs = Math.round(performance.now() - startedAt);
105
+ recordTraceEntry(ctx, name, {
106
+ timestamp: Date.now(),
107
+ durationMs,
108
+ ok: true,
109
+ retries: loopResult.attempt,
110
+ });
111
+ return { ok: true, data: loopResult.data, retries: loopResult.attempt, durationMs };
112
+ }
113
+ return buildFailureResult(ctx, name, loopResult.lastError, loopResult.lastCaughtError, maxRetries, startedAt, opts);
114
+ }
115
+ finally {
116
+ popupDismisser.stop();
117
+ }
118
+ }
119
+ async function buildFailureResult(ctx, name, lastError, lastCaughtError, maxRetries, startedAt, opts) {
120
+ const structured = toStructuredError(lastCaughtError ?? lastError);
121
+ const result = {
122
+ ok: false,
123
+ error: lastError,
124
+ retries: maxRetries,
125
+ durationMs: Math.round(performance.now() - startedAt),
126
+ };
127
+ if (typeof structured === "object") {
128
+ result.structuredError = structured;
129
+ }
130
+ if (opts.screenshotOnFailure !== false) {
131
+ try {
132
+ const screenshotPath = await captureFailureScreenshot(ctx, name);
133
+ if (screenshotPath) {
134
+ result.screenshot = screenshotPath;
135
+ }
136
+ }
137
+ catch {
138
+ ctx.logger.debug("failed to capture failure screenshot");
139
+ }
140
+ }
141
+ ctx.logger.error({ action: name, error: lastError, retries: maxRetries }, "action failed");
142
+ recordTraceEntry(ctx, name, {
143
+ timestamp: Date.now(),
144
+ durationMs: result.durationMs,
145
+ ok: false,
146
+ ...(result.error ? { error: result.error } : {}),
147
+ retries: result.retries,
148
+ });
149
+ return result;
150
+ }
151
+ function recordTraceEntry(ctx, action, entry) {
152
+ if (!(ctx.trace && ctx.sessionId)) {
153
+ return;
154
+ }
155
+ const meta = ctx._traceMeta;
156
+ ctx.trace.record(ctx.sessionId, {
157
+ action,
158
+ timestamp: entry.timestamp,
159
+ durationMs: entry.durationMs,
160
+ ok: entry.ok,
161
+ ...(entry.error ? { error: entry.error } : {}),
162
+ retries: entry.retries,
163
+ ...(meta?.selectorResolved ? { selectorResolved: meta.selectorResolved } : {}),
164
+ ...(meta?.eventsDispatched?.length ? { eventsDispatched: meta.eventsDispatched } : {}),
165
+ ...(meta?.waitsPerformed?.length ? { waitsPerformed: meta.waitsPerformed } : {}),
166
+ ...(meta?.assertionsChecked?.length ? { assertionsChecked: meta.assertionsChecked } : {}),
167
+ });
168
+ }
169
+ async function backoff(attempt) {
170
+ const base = Math.min(100 * 2 ** attempt, 2000);
171
+ const jitter = Math.floor(Math.random() * 500);
172
+ await new Promise((resolve) => {
173
+ setTimeout(resolve, base + jitter);
174
+ });
175
+ }
176
+ async function captureFailureScreenshot(ctx, actionName) {
177
+ if (!ctx.screenshotDir) {
178
+ return undefined;
179
+ }
180
+ const { mkdirSync, writeFileSync } = await import("node:fs");
181
+ const { join } = await import("node:path");
182
+ mkdirSync(ctx.screenshotDir, { recursive: true });
183
+ const filename = `${Date.now()}-${actionName}-failure.png`;
184
+ const filepath = join(ctx.screenshotDir, filename);
185
+ const buffer = await ctx.page.screenshot();
186
+ writeFileSync(filepath, buffer);
187
+ return filepath;
188
+ }
189
+ //# sourceMappingURL=action.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action.js","sourceRoot":"","sources":["../../src/actions/action.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Declarative postcondition assertion factories.
3
+ *
4
+ * Each function returns a `(ctx: ActionContext) => Promise<boolean>` compatible
5
+ * with ActionOptions.postcondition. Compose with `allOf()` for multiple checks.
6
+ */
7
+ import type { Selector } from "../selectors/strategy.js";
8
+ import type { ActionContext } from "./action.js";
9
+ export type AssertionCheck = (ctx: ActionContext) => Promise<boolean>;
10
+ export declare function assertUrlContains(substring: string): AssertionCheck;
11
+ export declare function assertElementVisible(selector: Selector): AssertionCheck;
12
+ export declare function assertElementText(selector: Selector, expected: string | RegExp): AssertionCheck;
13
+ export declare function assertElementGone(selector: Selector): AssertionCheck;
14
+ export declare function allOf(...checks: AssertionCheck[]): AssertionCheck;
15
+ //# sourceMappingURL=assertions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../../src/actions/assertions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAYtE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAKnE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,cAAc,CAUvE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CAiB/F;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,cAAc,CAYpE;AAED,wBAAgB,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,GAAG,cAAc,CASjE"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Declarative postcondition assertion factories.
3
+ *
4
+ * Each function returns a `(ctx: ActionContext) => Promise<boolean>` compatible
5
+ * with ActionOptions.postcondition. Compose with `allOf()` for multiple checks.
6
+ */
7
+ import { resolveSelector } from "../selectors/strategy.js";
8
+ function recordAssertion(ctx, label) {
9
+ if (!ctx._traceMeta) {
10
+ ctx._traceMeta = {};
11
+ }
12
+ if (!ctx._traceMeta.assertionsChecked) {
13
+ ctx._traceMeta.assertionsChecked = [];
14
+ }
15
+ ctx._traceMeta.assertionsChecked.push(label);
16
+ }
17
+ export function assertUrlContains(substring) {
18
+ return async (ctx) => {
19
+ recordAssertion(ctx, `urlContains:${substring}`);
20
+ return ctx.page.url().includes(substring);
21
+ };
22
+ }
23
+ export function assertElementVisible(selector) {
24
+ return async (ctx) => {
25
+ recordAssertion(ctx, "elementVisible");
26
+ try {
27
+ const locator = resolveSelector(ctx.page, selector).first();
28
+ return await locator.isVisible({ timeout: 2000 });
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ };
34
+ }
35
+ export function assertElementText(selector, expected) {
36
+ return async (ctx) => {
37
+ recordAssertion(ctx, typeof expected === "string" ? `elementText:${expected}` : `elementText:${expected.source}`);
38
+ try {
39
+ const locator = resolveSelector(ctx.page, selector).first();
40
+ const text = await locator.innerText({ timeout: 2000 });
41
+ if (typeof expected === "string") {
42
+ return text.trim() === expected;
43
+ }
44
+ return expected.test(text.trim());
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ };
50
+ }
51
+ export function assertElementGone(selector) {
52
+ return async (ctx) => {
53
+ recordAssertion(ctx, "elementGone");
54
+ try {
55
+ const locator = resolveSelector(ctx.page, selector).first();
56
+ const visible = await locator.isVisible({ timeout: 2000 });
57
+ return !visible;
58
+ }
59
+ catch {
60
+ // Element not found at all → gone
61
+ return true;
62
+ }
63
+ };
64
+ }
65
+ export function allOf(...checks) {
66
+ return async (ctx) => {
67
+ for (const check of checks) {
68
+ if (!(await check(ctx))) {
69
+ return false;
70
+ }
71
+ }
72
+ return true;
73
+ };
74
+ }
75
+ //# sourceMappingURL=assertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertions.js","sourceRoot":"","sources":["../../src/actions/assertions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Schema-based structured extraction with provenance tracking.
3
+ *
4
+ * Uses TypeBox schemas to define the expected shape. Each extracted item
5
+ * includes provenance metadata showing which DOM node produced it.
6
+ */
7
+ import type { TObject, TProperties } from "@sinclair/typebox";
8
+ import type { Selector } from "../selectors/strategy.js";
9
+ import type { ActionContext, ActionOptions, ActionResult } from "./action.js";
10
+ export interface ItemProvenance {
11
+ index: number;
12
+ tagName: string;
13
+ id: string;
14
+ className: string;
15
+ strategy: string;
16
+ }
17
+ export interface ExtractionResult<T> {
18
+ data: T[];
19
+ provenance: ItemProvenance[];
20
+ }
21
+ export interface ExtractStructuredOptions extends ActionOptions {
22
+ /** Maximum number of items to extract. Default: unlimited. */
23
+ limit?: number;
24
+ }
25
+ export declare function extractStructured<P extends TProperties>(ctx: ActionContext, selector: Selector, schema: TObject<P>, opts?: ExtractStructuredOptions): Promise<ActionResult<ExtractionResult<Record<string, unknown>>>>;
26
+ //# sourceMappingURL=extract-structured.d.ts.map