autokap 1.0.6 → 1.0.8

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 (347) hide show
  1. package/assets/chrome/ios-statusbar-comparison-reference.jpg +0 -0
  2. package/assets/chrome/ios-statusbar-dark-reference.jpg +0 -0
  3. package/assets/chrome/ios-statusbar-light-reference.jpg +0 -0
  4. package/assets/cursors/macos.svg +4 -0
  5. package/assets/cursors/windows.svg +15 -0
  6. package/assets/devices/ipad-pro-11-m4.json +52 -0
  7. package/assets/devices/iphone-16-pro.json +53 -0
  8. package/assets/devices/macbook-air-13.json +45 -0
  9. package/assets/frames/MacBook Air 13.svg +242 -0
  10. package/assets/frames/Status bar - iPhone.png +0 -0
  11. Menu bar- iPad.png +0 -0
  12. package/assets/frames/iPad Pro M4 11_.png +0 -0
  13. package/assets/frames/iPhone 16 Pro.png +0 -0
  14. package/assets/icons/Cellular Connection.svg +3 -0
  15. package/assets/icons/Union.svg +6 -0
  16. package/assets/icons/Wifi.svg +3 -0
  17. package/assets/icons/battery.svg +5 -0
  18. package/assets/icons/battery_charging.svg +8 -0
  19. package/assets/skill/OPCODE-REFERENCE.md +607 -0
  20. package/assets/skill/README.md +39 -0
  21. package/assets/skill/SKILL.md +453 -468
  22. package/assets/skill/STUDIO-SKILL.md +476 -0
  23. package/assets/skill/references/examples.md +104 -0
  24. package/assets/skill/references/interactive-demo.md +225 -0
  25. package/assets/skill/references/mock-data.md +178 -0
  26. package/dist/abort.d.ts +5 -0
  27. package/dist/abort.js +44 -0
  28. package/dist/action-verifier.d.ts +29 -0
  29. package/dist/action-verifier.js +133 -0
  30. package/dist/agent-action-recovery.d.ts +45 -0
  31. package/dist/agent-action-recovery.js +370 -0
  32. package/dist/agent-message-utils.d.ts +21 -0
  33. package/dist/agent-message-utils.js +77 -0
  34. package/dist/agent-url-utils.d.ts +30 -0
  35. package/dist/agent-url-utils.js +138 -0
  36. package/dist/agent.d.ts +226 -0
  37. package/dist/agent.js +6666 -0
  38. package/dist/ak-tree.d.ts +39 -0
  39. package/dist/ak-tree.js +368 -0
  40. package/dist/alt-text.d.ts +26 -0
  41. package/dist/alt-text.js +55 -0
  42. package/dist/auth-capture.d.ts +17 -0
  43. package/dist/auth-capture.js +164 -0
  44. package/dist/benchmark.d.ts +59 -0
  45. package/dist/benchmark.js +135 -0
  46. package/dist/billing-operation-logging.d.ts +38 -0
  47. package/dist/billing-operation-logging.js +248 -0
  48. package/dist/browser-bar.d.ts +48 -0
  49. package/dist/browser-bar.js +284 -0
  50. package/dist/browser-pool.d.ts +7 -0
  51. package/dist/browser-pool.js +15 -5
  52. package/dist/browser-utils.d.ts +31 -0
  53. package/dist/browser-utils.js +97 -0
  54. package/dist/browser.d.ts +76 -1
  55. package/dist/browser.js +1657 -39
  56. package/dist/capture-alt-text.d.ts +12 -0
  57. package/dist/capture-alt-text.js +52 -0
  58. package/dist/capture-encryption.d.ts +10 -0
  59. package/dist/capture-encryption.js +41 -0
  60. package/dist/capture-language-preflight.d.ts +41 -0
  61. package/dist/capture-language-preflight.js +300 -0
  62. package/dist/capture-llm-page-identity.d.ts +15 -0
  63. package/dist/capture-llm-page-identity.js +128 -0
  64. package/dist/capture-model-resolution.d.ts +9 -0
  65. package/dist/capture-model-resolution.js +21 -0
  66. package/dist/capture-page-identity.d.ts +7 -0
  67. package/dist/capture-page-identity.js +352 -0
  68. package/dist/capture-preset-credentials.d.ts +62 -0
  69. package/dist/capture-preset-credentials.js +184 -0
  70. package/dist/capture-request-plan.d.ts +58 -0
  71. package/dist/capture-request-plan.js +264 -0
  72. package/dist/capture-run-optimizer.d.ts +139 -0
  73. package/dist/capture-run-optimizer.js +863 -0
  74. package/dist/capture-selector-memory.d.ts +31 -0
  75. package/dist/capture-selector-memory.js +345 -0
  76. package/dist/capture-session-profile-encryption.d.ts +2 -0
  77. package/dist/capture-session-profile-encryption.js +22 -0
  78. package/dist/capture-step-timeout.d.ts +10 -0
  79. package/dist/capture-step-timeout.js +30 -0
  80. package/dist/capture-strategy.d.ts +36 -0
  81. package/dist/capture-strategy.js +95 -0
  82. package/dist/capture-studio-sync.d.ts +23 -0
  83. package/dist/capture-studio-sync.js +172 -0
  84. package/dist/capture-surface-contract.d.ts +36 -0
  85. package/dist/capture-surface-contract.js +299 -0
  86. package/dist/capture-transition-engine.d.ts +28 -0
  87. package/dist/capture-transition-engine.js +292 -0
  88. package/dist/capture-variant-state.d.ts +56 -0
  89. package/dist/capture-variant-state.js +182 -0
  90. package/dist/capture-verification.d.ts +35 -0
  91. package/dist/capture-verification.js +95 -0
  92. package/dist/capture-viewport-lock.d.ts +48 -0
  93. package/dist/capture-viewport-lock.js +74 -0
  94. package/dist/circuit-breaker.d.ts +42 -0
  95. package/dist/circuit-breaker.js +119 -0
  96. package/dist/cli-config.d.ts +8 -1
  97. package/dist/cli-config.js +62 -6
  98. package/dist/cli-contract.d.ts +15 -0
  99. package/dist/cli-contract.js +167 -0
  100. package/dist/cli-runner-local.d.ts +12 -0
  101. package/dist/cli-runner-local.js +102 -0
  102. package/dist/cli-runner.d.ts +34 -0
  103. package/dist/cli-runner.js +433 -0
  104. package/dist/cli-utils.d.ts +0 -1
  105. package/dist/cli-utils.js +2 -5
  106. package/dist/cli.js +1005 -252
  107. package/dist/clip-orchestrator.d.ts +148 -0
  108. package/dist/clip-orchestrator.js +957 -0
  109. package/dist/clip-postprocess.d.ts +42 -0
  110. package/dist/clip-postprocess.js +201 -0
  111. package/dist/cookie-dismiss.d.ts +2 -0
  112. package/dist/cookie-dismiss.js +48 -13
  113. package/dist/cost-logging.d.ts +35 -0
  114. package/dist/cost-logging.js +242 -0
  115. package/dist/cost-resolution-monitor.d.ts +16 -0
  116. package/dist/cost-resolution-monitor.js +34 -0
  117. package/dist/credential-templates.d.ts +5 -0
  118. package/dist/credential-templates.js +60 -0
  119. package/dist/cursor-overlay-script.d.ts +6 -0
  120. package/dist/cursor-overlay-script.js +169 -0
  121. package/dist/dom-css-purger.d.ts +65 -0
  122. package/dist/dom-css-purger.js +333 -0
  123. package/dist/dom-font-inliner.d.ts +45 -0
  124. package/dist/dom-font-inliner.js +148 -0
  125. package/dist/dom-patch-resolver.d.ts +52 -0
  126. package/dist/dom-patch-resolver.js +242 -0
  127. package/dist/dom-serializer.d.ts +82 -0
  128. package/dist/dom-serializer.js +378 -0
  129. package/dist/element-capture.d.ts +13 -0
  130. package/dist/element-capture.js +522 -0
  131. package/dist/env-validation.d.ts +5 -0
  132. package/dist/env-validation.js +29 -0
  133. package/dist/execution-schema.d.ts +4423 -0
  134. package/dist/execution-schema.js +507 -0
  135. package/dist/execution-types.d.ts +886 -0
  136. package/dist/execution-types.js +65 -0
  137. package/dist/fonts-loader.d.ts +14 -0
  138. package/dist/fonts-loader.js +55 -0
  139. package/dist/hybrid-navigator.d.ts +138 -0
  140. package/dist/hybrid-navigator.js +468 -0
  141. package/dist/index.d.ts +18 -0
  142. package/dist/index.js +17 -0
  143. package/dist/legacy/agent-action-recovery.d.ts +45 -0
  144. package/dist/legacy/agent-action-recovery.js +370 -0
  145. package/dist/legacy/agent-message-utils.d.ts +21 -0
  146. package/dist/legacy/agent-message-utils.js +77 -0
  147. package/dist/legacy/agent-url-utils.d.ts +30 -0
  148. package/dist/legacy/agent-url-utils.js +138 -0
  149. package/dist/legacy/agent.d.ts +226 -0
  150. package/dist/legacy/agent.js +6666 -0
  151. package/dist/legacy/clip-orchestrator.d.ts +148 -0
  152. package/dist/legacy/clip-orchestrator.js +957 -0
  153. package/dist/legacy/credential-templates.d.ts +5 -0
  154. package/dist/legacy/credential-templates.js +60 -0
  155. package/dist/legacy/hybrid-navigator.d.ts +138 -0
  156. package/dist/legacy/hybrid-navigator.js +468 -0
  157. package/dist/legacy/llm-usage.d.ts +17 -0
  158. package/dist/legacy/llm-usage.js +45 -0
  159. package/dist/legacy/prompt-cache.d.ts +10 -0
  160. package/dist/legacy/prompt-cache.js +24 -0
  161. package/dist/legacy/prompts.d.ts +175 -0
  162. package/dist/legacy/prompts.js +1038 -0
  163. package/dist/legacy/tools.d.ts +4 -0
  164. package/dist/legacy/tools.js +216 -0
  165. package/dist/legacy/video-agent.d.ts +143 -0
  166. package/dist/legacy/video-agent.js +4788 -0
  167. package/dist/legacy/video-observation.d.ts +36 -0
  168. package/dist/legacy/video-observation.js +192 -0
  169. package/dist/legacy/video-planner.d.ts +12 -0
  170. package/dist/legacy/video-planner.js +501 -0
  171. package/dist/legacy/video-prompts.d.ts +37 -0
  172. package/dist/legacy/video-prompts.js +569 -0
  173. package/dist/legacy/video-tools.d.ts +3 -0
  174. package/dist/legacy/video-tools.js +59 -0
  175. package/dist/legacy/video-variant-state.d.ts +29 -0
  176. package/dist/legacy/video-variant-state.js +80 -0
  177. package/dist/legacy/vision-model.d.ts +17 -0
  178. package/dist/legacy/vision-model.js +74 -0
  179. package/dist/llm-healer.d.ts +63 -0
  180. package/dist/llm-healer.js +166 -0
  181. package/dist/llm-provider.d.ts +29 -0
  182. package/dist/llm-provider.js +80 -0
  183. package/dist/llm-usage.d.ts +17 -0
  184. package/dist/llm-usage.js +45 -0
  185. package/dist/logger.d.ts +6 -2
  186. package/dist/logger.js +15 -1
  187. package/dist/mockup-html.d.ts +119 -0
  188. package/dist/mockup-html.js +263 -0
  189. package/dist/mockup.d.ts +187 -0
  190. package/dist/mockup.js +869 -0
  191. package/dist/mouse-animation.d.ts +46 -0
  192. package/dist/mouse-animation.js +114 -0
  193. package/dist/opcode-actions.d.ts +42 -0
  194. package/dist/opcode-actions.js +511 -0
  195. package/dist/opcode-runner.d.ts +51 -0
  196. package/dist/opcode-runner.js +770 -0
  197. package/dist/openrouter-client.d.ts +40 -0
  198. package/dist/openrouter-client.js +16 -0
  199. package/dist/overlay-engine.d.ts +24 -0
  200. package/dist/overlay-engine.js +176 -0
  201. package/dist/overlay-utils.d.ts +14 -0
  202. package/dist/overlay-utils.js +13 -0
  203. package/dist/postcondition.d.ts +16 -0
  204. package/dist/postcondition.js +269 -0
  205. package/dist/posthog.d.ts +4 -0
  206. package/dist/posthog.js +26 -0
  207. package/dist/program-patcher.d.ts +25 -0
  208. package/dist/program-patcher.js +44 -0
  209. package/dist/prompt-cache.d.ts +10 -0
  210. package/dist/prompt-cache.js +24 -0
  211. package/dist/prompts.d.ts +175 -0
  212. package/dist/prompts.js +1038 -0
  213. package/dist/provider-config.d.ts +12 -0
  214. package/dist/provider-config.js +15 -0
  215. package/dist/recovery-chain.d.ts +37 -0
  216. package/dist/recovery-chain.js +350 -0
  217. package/dist/remote-browser.d.ts +215 -0
  218. package/dist/remote-browser.js +360 -0
  219. package/dist/safari-browser-bar.d.ts +15 -0
  220. package/dist/safari-browser-bar.js +95 -0
  221. package/dist/safari-toolbar-asset.d.ts +15 -0
  222. package/dist/safari-toolbar-asset.js +12 -0
  223. package/dist/security.d.ts +21 -0
  224. package/dist/security.js +608 -0
  225. package/dist/selector-resolver.d.ts +34 -0
  226. package/dist/selector-resolver.js +181 -0
  227. package/dist/semantic-resolver.d.ts +35 -0
  228. package/dist/semantic-resolver.js +161 -0
  229. package/dist/server-capture-runtime.d.ts +125 -0
  230. package/dist/server-capture-runtime.js +585 -0
  231. package/dist/server-credit-usage.d.ts +12 -0
  232. package/dist/server-credit-usage.js +41 -0
  233. package/dist/server-posthog.d.ts +2 -0
  234. package/dist/server-posthog.js +16 -0
  235. package/dist/server-project-webhooks.d.ts +59 -0
  236. package/dist/server-project-webhooks.js +123 -0
  237. package/dist/server-screenshot-watermark.d.ts +7 -0
  238. package/dist/server-screenshot-watermark.js +60 -0
  239. package/dist/session-profile.d.ts +86 -0
  240. package/dist/session-profile.js +1536 -0
  241. package/dist/sf-pro-fonts.d.ts +4 -0
  242. package/dist/sf-pro-fonts.js +7 -0
  243. package/dist/sf-pro-symbols.d.ts +1 -0
  244. package/dist/sf-pro-symbols.js +55 -0
  245. package/dist/skill-packaging.d.ts +28 -0
  246. package/dist/skill-packaging.js +169 -0
  247. package/dist/smart-wait.d.ts +27 -0
  248. package/dist/smart-wait.js +81 -0
  249. package/dist/status-bar-l10n.d.ts +14 -0
  250. package/dist/status-bar-l10n.js +177 -0
  251. package/dist/status-bar-render.d.ts +20 -0
  252. package/dist/status-bar-render.js +410 -0
  253. package/dist/status-bar.d.ts +53 -0
  254. package/dist/status-bar.js +620 -0
  255. package/dist/svg-browser-bar.d.ts +33 -0
  256. package/dist/svg-browser-bar.js +206 -0
  257. package/dist/svg-status-bar.d.ts +36 -0
  258. package/dist/svg-status-bar.js +597 -0
  259. package/dist/svg-text.d.ts +61 -0
  260. package/dist/svg-text.js +118 -0
  261. package/dist/tools.d.ts +4 -0
  262. package/dist/tools.js +216 -0
  263. package/dist/types.d.ts +240 -5
  264. package/dist/types.js +23 -1
  265. package/dist/v2/action-verifier.d.ts +29 -0
  266. package/dist/v2/action-verifier.js +133 -0
  267. package/dist/v2/alt-text.d.ts +26 -0
  268. package/dist/v2/alt-text.js +55 -0
  269. package/dist/v2/benchmark.d.ts +59 -0
  270. package/dist/v2/benchmark.js +135 -0
  271. package/dist/v2/capture-strategy.d.ts +30 -0
  272. package/dist/v2/capture-strategy.js +67 -0
  273. package/dist/v2/capture-verification.d.ts +35 -0
  274. package/dist/v2/capture-verification.js +95 -0
  275. package/dist/v2/circuit-breaker.d.ts +42 -0
  276. package/dist/v2/circuit-breaker.js +119 -0
  277. package/dist/v2/cli-runner-local.d.ts +11 -0
  278. package/dist/v2/cli-runner-local.js +91 -0
  279. package/dist/v2/cli-runner.d.ts +34 -0
  280. package/dist/v2/cli-runner.js +300 -0
  281. package/dist/v2/compiler-prompts.d.ts +27 -0
  282. package/dist/v2/compiler-prompts.js +123 -0
  283. package/dist/v2/compiler.d.ts +37 -0
  284. package/dist/v2/compiler.js +147 -0
  285. package/dist/v2/explorer.d.ts +41 -0
  286. package/dist/v2/explorer.js +56 -0
  287. package/dist/v2/index.d.ts +37 -0
  288. package/dist/v2/index.js +31 -0
  289. package/dist/v2/llm-healer.d.ts +62 -0
  290. package/dist/v2/llm-healer.js +166 -0
  291. package/dist/v2/llm-provider.d.ts +29 -0
  292. package/dist/v2/llm-provider.js +80 -0
  293. package/dist/v2/opcode-runner.d.ts +47 -0
  294. package/dist/v2/opcode-runner.js +634 -0
  295. package/dist/v2/overlay-engine.d.ts +24 -0
  296. package/dist/v2/overlay-engine.js +150 -0
  297. package/dist/v2/postcondition.d.ts +16 -0
  298. package/dist/v2/postcondition.js +249 -0
  299. package/dist/v2/program-patcher.d.ts +25 -0
  300. package/dist/v2/program-patcher.js +44 -0
  301. package/dist/v2/recovery-chain.d.ts +30 -0
  302. package/dist/v2/recovery-chain.js +368 -0
  303. package/dist/v2/schema.d.ts +2580 -0
  304. package/dist/v2/schema.js +295 -0
  305. package/dist/v2/selector-resolver.d.ts +34 -0
  306. package/dist/v2/selector-resolver.js +181 -0
  307. package/dist/v2/semantic-resolver.d.ts +35 -0
  308. package/dist/v2/semantic-resolver.js +161 -0
  309. package/dist/v2/smart-wait.d.ts +27 -0
  310. package/dist/v2/smart-wait.js +81 -0
  311. package/dist/v2/types.d.ts +444 -0
  312. package/dist/v2/types.js +19 -0
  313. package/dist/v2/web-playwright-local.d.ts +69 -0
  314. package/dist/v2/web-playwright-local.js +392 -0
  315. package/dist/version.d.ts +1 -0
  316. package/dist/version.js +5 -0
  317. package/dist/video-agent.d.ts +143 -0
  318. package/dist/video-agent.js +4788 -0
  319. package/dist/video-observation.d.ts +36 -0
  320. package/dist/video-observation.js +192 -0
  321. package/dist/video-planner.d.ts +12 -0
  322. package/dist/video-planner.js +501 -0
  323. package/dist/video-prompts.d.ts +37 -0
  324. package/dist/video-prompts.js +554 -0
  325. package/dist/video-tools.d.ts +3 -0
  326. package/dist/video-tools.js +59 -0
  327. package/dist/video-variant-state.d.ts +29 -0
  328. package/dist/video-variant-state.js +80 -0
  329. package/dist/vision-model.d.ts +17 -0
  330. package/dist/vision-model.js +74 -0
  331. package/dist/web-playwright-local.d.ts +126 -0
  332. package/dist/web-playwright-local.js +819 -0
  333. package/dist/ws-auth.d.ts +20 -0
  334. package/dist/ws-auth.js +70 -0
  335. package/dist/ws-broadcast.d.ts +34 -0
  336. package/dist/ws-broadcast.js +85 -0
  337. package/dist/ws-connection-limits.d.ts +12 -0
  338. package/dist/ws-connection-limits.js +44 -0
  339. package/dist/ws-handler-utils.d.ts +32 -0
  340. package/dist/ws-handler-utils.js +139 -0
  341. package/dist/ws-handler.d.ts +10 -0
  342. package/dist/ws-handler.js +1793 -0
  343. package/dist/ws-metrics-server.d.ts +9 -0
  344. package/dist/ws-metrics-server.js +31 -0
  345. package/dist/ws-server.d.ts +9 -0
  346. package/dist/ws-server.js +92 -0
  347. package/package.json +142 -71
@@ -0,0 +1,819 @@
1
+ /**
2
+ * Capture Agent — WebPlaywrightLocal RuntimeAdapter
3
+ *
4
+ * Thin adapter delegating to the existing Browser class from src/browser.ts.
5
+ * This is the first (and for now only) RuntimeAdapter implementation.
6
+ */
7
+ import fs from 'node:fs/promises';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+ import { humanType, moveMouse } from './mouse-animation.js';
11
+ import { resolveTarget } from './semantic-resolver.js';
12
+ import { logger } from './logger.js';
13
+ export class WebPlaywrightLocal {
14
+ browser;
15
+ recordingDir;
16
+ sessionStartedAt = Date.now();
17
+ recording = null;
18
+ clipCursor = null;
19
+ constructor(browser, recordingDir) {
20
+ this.browser = browser;
21
+ this.recordingDir = recordingDir;
22
+ }
23
+ async navigate(url) {
24
+ await this.browser.navigateTo(url);
25
+ }
26
+ async getCurrentUrl() {
27
+ const page = await this.browser.currentPage;
28
+ return page.url();
29
+ }
30
+ async getAKTree() {
31
+ return this.browser.getAKTree();
32
+ }
33
+ async getPageSignals() {
34
+ return this.browser.capturePageSignals();
35
+ }
36
+ async click(selector, options) {
37
+ const page = await this.browser.currentPage;
38
+ const t0 = Date.now();
39
+ logger.debug(`[click] start selector="${selector}"${options?.useKeyboard ? ' mode=keyboard' : ''}${options?.useJsDispatch ? ' mode=js_dispatch' : ''}${options?.coordinates ? ` mode=coords(${options.coordinates.x},${options.coordinates.y})` : ''}`);
40
+ try {
41
+ if (options?.coordinates) {
42
+ await this.moveClipCursorToPoint(options.coordinates);
43
+ await this.browser.clickByCoordinates(options.coordinates.x, options.coordinates.y);
44
+ logger.debug(`[click] done coords took ${Date.now() - t0}ms`);
45
+ return;
46
+ }
47
+ const locator = page.locator(selector).first();
48
+ await this.moveClipCursorToLocator(locator);
49
+ if (options?.useKeyboard) {
50
+ await locator.focus();
51
+ await page.keyboard.press('Enter');
52
+ logger.debug(`[click] done keyboard took ${Date.now() - t0}ms`);
53
+ return;
54
+ }
55
+ if (options?.useJsDispatch) {
56
+ await locator.dispatchEvent('click');
57
+ logger.debug(`[click] done js_dispatch took ${Date.now() - t0}ms`);
58
+ return;
59
+ }
60
+ if (options?.button && options.button !== 'left') {
61
+ await locator.click({ button: options.button, timeout: 5000, force: options?.force });
62
+ logger.debug(`[click] done button=${options.button} took ${Date.now() - t0}ms`);
63
+ return;
64
+ }
65
+ await this.browser.clickBySelector(selector, { force: options?.force });
66
+ await this.emitClipClickPulse();
67
+ logger.debug(`[click] done normal took ${Date.now() - t0}ms`);
68
+ }
69
+ catch (err) {
70
+ logger.debug(`[click] FAILED after ${Date.now() - t0}ms — ${err instanceof Error ? err.message : String(err)}`);
71
+ throw err;
72
+ }
73
+ }
74
+ /**
75
+ * Click an element using semantic target resolution.
76
+ * Tries CSS selector first, falls back to Playwright semantic locators.
77
+ */
78
+ async clickByTarget(opts) {
79
+ const page = await this.browser.currentPage;
80
+ const resolved = await resolveTarget(page, opts);
81
+ if (!resolved) {
82
+ throw new Error(`cannot find target: ${describeResolveOptions(opts)}`);
83
+ }
84
+ await this.moveClipCursorToLocator(resolved.locator);
85
+ await resolved.locator.click({ timeout: 5000 });
86
+ }
87
+ /**
88
+ * Type into an element using semantic target resolution.
89
+ */
90
+ async typeByTarget(opts, text, clearFirst = true) {
91
+ const page = await this.browser.currentPage;
92
+ const resolved = await resolveTarget(page, opts);
93
+ if (!resolved) {
94
+ throw new Error(`cannot find target for typing: ${describeResolveOptions(opts)}`);
95
+ }
96
+ if (this.clipCursor) {
97
+ await this.typeIntoLocator(resolved.locator, text, clearFirst);
98
+ return;
99
+ }
100
+ if (clearFirst) {
101
+ await resolved.locator.fill(text);
102
+ return;
103
+ }
104
+ await resolved.locator.click();
105
+ await resolved.locator.pressSequentially(text);
106
+ }
107
+ /**
108
+ * Wait for an element using semantic target resolution.
109
+ */
110
+ async waitForTarget(opts, timeoutMs = 10000) {
111
+ const page = await this.browser.currentPage;
112
+ const resolved = await resolveTarget(page, { ...opts, timeoutMs });
113
+ return resolved !== null;
114
+ }
115
+ /**
116
+ * Scroll an element into view using semantic target resolution.
117
+ */
118
+ async scrollIntoViewByTarget(opts) {
119
+ const page = await this.browser.currentPage;
120
+ const resolved = await resolveTarget(page, opts);
121
+ if (!resolved) {
122
+ throw new Error(`cannot find target to scroll into view: ${describeResolveOptions(opts)}`);
123
+ }
124
+ if (this.clipCursor) {
125
+ await this.moveClipCursorToViewportCenter();
126
+ await resolved.locator.evaluate((node) => {
127
+ node.scrollIntoView({ block: 'center', behavior: 'smooth' });
128
+ });
129
+ await page.waitForTimeout(350);
130
+ return;
131
+ }
132
+ await resolved.locator.scrollIntoViewIfNeeded({ timeout: 5000 });
133
+ }
134
+ async type(selector, text, clearFirst = true) {
135
+ if (this.clipCursor) {
136
+ const page = await this.browser.currentPage;
137
+ await this.typeIntoLocator(page.locator(selector).first(), text, clearFirst);
138
+ return;
139
+ }
140
+ await this.browser.typeText(text, { selector, clearFirst });
141
+ }
142
+ async pressKey(key) {
143
+ const page = await this.browser.currentPage;
144
+ if (this.clipCursor) {
145
+ await page.waitForTimeout(90);
146
+ await page.keyboard.press(key);
147
+ return;
148
+ }
149
+ await this.browser.pressKey(key);
150
+ }
151
+ async scroll(direction, amount) {
152
+ if (this.clipCursor) {
153
+ const page = await this.browser.currentPage;
154
+ await this.moveClipCursorToViewportCenter();
155
+ const dx = direction === 'right' ? (amount ?? 500) : direction === 'left' ? -(amount ?? 500) : 0;
156
+ const dy = direction === 'down' ? (amount ?? 500) : direction === 'up' ? -(amount ?? 500) : 0;
157
+ await page.evaluate(({ deltaX, deltaY }) => {
158
+ window.scrollBy({ left: deltaX, top: deltaY, behavior: 'smooth' });
159
+ }, { deltaX: dx, deltaY: dy });
160
+ await page.waitForTimeout(420);
161
+ return;
162
+ }
163
+ await this.browser.scroll(direction, amount);
164
+ }
165
+ async scrollIntoView(selector) {
166
+ const page = await this.browser.currentPage;
167
+ if (this.clipCursor) {
168
+ await this.moveClipCursorToViewportCenter();
169
+ await page.locator(selector).first().evaluate((node) => {
170
+ node.scrollIntoView({ block: 'center', behavior: 'smooth' });
171
+ });
172
+ await page.waitForTimeout(350);
173
+ return;
174
+ }
175
+ await page.locator(selector).first().scrollIntoViewIfNeeded({ timeout: 5000 });
176
+ }
177
+ async waitFor(condition) {
178
+ try {
179
+ const page = await this.browser.currentPage;
180
+ const stateMap = { visible: 'visible', attached: 'attached' };
181
+ await page.locator(condition.selector).waitFor({
182
+ state: stateMap[condition.state],
183
+ timeout: condition.timeoutMs,
184
+ });
185
+ return true;
186
+ }
187
+ catch {
188
+ return false;
189
+ }
190
+ }
191
+ async dismissOverlays() {
192
+ // Pass 1: Built-in cookie/widget dismissal (cookie-dismiss.ts)
193
+ const result = await this.browser.dismissOverlays();
194
+ if (result.dismissed)
195
+ return result;
196
+ // Pass 2: Playwright-level sweep for common accept/close buttons
197
+ // in cookie-like containers that the built-in module might miss
198
+ const page = await this.browser.currentPage;
199
+ const acceptPatterns = [
200
+ 'button:has-text("Accept")',
201
+ 'button:has-text("Accept all")',
202
+ 'button:has-text("Accepter")',
203
+ 'button:has-text("Tout accepter")',
204
+ 'button:has-text("Got it")',
205
+ 'button:has-text("I agree")',
206
+ 'button:has-text("OK")',
207
+ '[role="button"]:has-text("Accept")',
208
+ '[role="button"]:has-text("Accept all")',
209
+ ];
210
+ for (const pattern of acceptPatterns) {
211
+ try {
212
+ const btn = page.locator(pattern).first();
213
+ if (await btn.isVisible({ timeout: 500 })) {
214
+ // Check if the button is near the bottom of the viewport (likely a banner)
215
+ const box = await btn.boundingBox();
216
+ const viewport = page.viewportSize();
217
+ if (box && viewport && box.y > viewport.height * 0.6) {
218
+ await btn.click({ timeout: 2000 });
219
+ await page.waitForTimeout(500);
220
+ return { dismissed: true, method: `overlay-sweep:${pattern}` };
221
+ }
222
+ }
223
+ }
224
+ catch {
225
+ // Try next pattern
226
+ }
227
+ }
228
+ // Pass 3: Try Escape key for modal overlays
229
+ try {
230
+ await page.keyboard.press('Escape');
231
+ await page.waitForTimeout(300);
232
+ }
233
+ catch {
234
+ // Non-fatal
235
+ }
236
+ return result;
237
+ }
238
+ async takeScreenshot() {
239
+ return this.browser.takeScreenshot();
240
+ }
241
+ async takeElementScreenshot(selector) {
242
+ const { buffer } = await this.browser.screenshotBySelector(selector);
243
+ return buffer;
244
+ }
245
+ async takeCleanScreenshot() {
246
+ return this.browser.takeScreenshot();
247
+ }
248
+ async beginRecording(options) {
249
+ const page = await this.browser.currentPage;
250
+ if (!page.video()) {
251
+ throw new Error(`recording is not enabled for ${options.mediaMode} mode`);
252
+ }
253
+ const recordingDir = this.recordingDir
254
+ ?? await fs.mkdtemp(path.join(os.tmpdir(), 'autokap-recording-'));
255
+ this.recording = {
256
+ mediaMode: options.mediaMode,
257
+ startedAt: Date.now(),
258
+ trimStartMs: Math.max(0, Date.now() - this.sessionStartedAt),
259
+ outputPath: path.join(recordingDir, `${options.mediaMode}.webm`),
260
+ finalized: false,
261
+ };
262
+ this.clipCursor = { currentPosition: null };
263
+ await this.seedClipCursor();
264
+ }
265
+ async endRecording() {
266
+ if (!this.recording) {
267
+ throw new Error('recording was not started');
268
+ }
269
+ if (this.recording.finalized) {
270
+ const buffer = await fs.readFile(this.recording.outputPath);
271
+ return {
272
+ buffer,
273
+ durationMs: Date.now() - this.recording.startedAt,
274
+ mimeType: 'video/webm',
275
+ trimStartMs: this.recording.trimStartMs,
276
+ };
277
+ }
278
+ const videoRef = this.browser.currentPage.video();
279
+ if (!videoRef) {
280
+ throw new Error('recording finalization failed: no Playwright video handle found');
281
+ }
282
+ await this.browser.closeContext();
283
+ await videoRef.saveAs(this.recording.outputPath);
284
+ this.recording.finalized = true;
285
+ this.clipCursor = null;
286
+ const buffer = await fs.readFile(this.recording.outputPath);
287
+ return {
288
+ buffer,
289
+ durationMs: Date.now() - this.recording.startedAt,
290
+ mimeType: 'video/webm',
291
+ trimStartMs: this.recording.trimStartMs,
292
+ };
293
+ }
294
+ async setLocale(locale) {
295
+ await this.browser.setLanguage(locale);
296
+ }
297
+ async setColorScheme(scheme) {
298
+ await this.browser.setColorScheme(scheme);
299
+ }
300
+ async reloadPage() {
301
+ await this.browser.reloadCurrentPage();
302
+ }
303
+ async writeStorageHint(params) {
304
+ if (params.storage === 'cookie') {
305
+ const page = await this.browser.currentPage;
306
+ const url = new URL(page.url());
307
+ await this.browser.addCookies([{
308
+ name: params.key,
309
+ value: params.value,
310
+ domain: url.hostname,
311
+ path: '/',
312
+ httpOnly: false,
313
+ secure: url.protocol === 'https:',
314
+ sameSite: 'Lax',
315
+ }]);
316
+ return true;
317
+ }
318
+ return this.browser.writeStorageHintCandidate({
319
+ storageName: params.storage,
320
+ key: params.key,
321
+ candidate: params.value,
322
+ kind: params.kind,
323
+ });
324
+ }
325
+ async hover(selector) {
326
+ const page = await this.browser.currentPage;
327
+ const locator = page.locator(selector).first();
328
+ await this.moveClipCursorToLocator(locator);
329
+ await this.browser.hoverBySelector(selector);
330
+ }
331
+ async hoverByTarget(opts) {
332
+ const page = await this.browser.currentPage;
333
+ const resolved = await resolveTarget(page, opts);
334
+ if (!resolved) {
335
+ throw new Error(`cannot find target for hover: ${describeResolveOptions(opts)}`);
336
+ }
337
+ await this.moveClipCursorToLocator(resolved.locator);
338
+ await resolved.locator.hover({ timeout: 5000 });
339
+ }
340
+ async selectOption(selector, option) {
341
+ await this.browser.selectOption({
342
+ selector,
343
+ optionLabel: option.label,
344
+ optionValue: option.value,
345
+ optionIndex: option.index,
346
+ });
347
+ }
348
+ async check(selector, checked) {
349
+ const page = await this.browser.currentPage;
350
+ const locator = page.locator(selector).first();
351
+ await this.moveClipCursorToLocator(locator);
352
+ if (checked) {
353
+ await locator.check({ timeout: 5000 });
354
+ }
355
+ else {
356
+ await locator.uncheck({ timeout: 5000 });
357
+ }
358
+ }
359
+ async doubleClick(selector) {
360
+ const page = await this.browser.currentPage;
361
+ const locator = page.locator(selector).first();
362
+ await this.moveClipCursorToLocator(locator);
363
+ await locator.dblclick({ timeout: 5000 });
364
+ }
365
+ // ── Mock data DOM mutations (soft / non-blocking) ──
366
+ async cloneElement(opts) {
367
+ const page = await this.browser.currentPage;
368
+ const clonedCount = await page.evaluate(({ sourceSelector, containerSelector, count, removeSource }) => {
369
+ const source = document.querySelector(sourceSelector);
370
+ const container = document.querySelector(containerSelector);
371
+ if (!source) {
372
+ throw new Error(`cloneElement: source "${sourceSelector}" not found`);
373
+ }
374
+ if (!container) {
375
+ throw new Error(`cloneElement: container "${containerSelector}" not found`);
376
+ }
377
+ let cloned = 0;
378
+ for (let i = 0; i < count; i++) {
379
+ const clone = source.cloneNode(true);
380
+ // Tag the clone so the mock-data row addressing can find it
381
+ // independently of nth-child math (which breaks when the
382
+ // container has non-template siblings like placeholders).
383
+ clone.setAttribute('data-ak-mock-clone', String(i));
384
+ container.appendChild(clone);
385
+ cloned++;
386
+ }
387
+ if (removeSource && source.parentElement === container) {
388
+ source.remove();
389
+ }
390
+ return cloned;
391
+ }, opts);
392
+ return { clonedCount };
393
+ }
394
+ async setAttribute(opts) {
395
+ const page = await this.browser.currentPage;
396
+ await page.evaluate(({ selector, attribute, value }) => {
397
+ const el = document.querySelector(selector);
398
+ if (!el) {
399
+ throw new Error(`setAttribute: element "${selector}" not found`);
400
+ }
401
+ el.setAttribute(attribute, value);
402
+ }, opts);
403
+ }
404
+ async setTextContent(opts) {
405
+ const page = await this.browser.currentPage;
406
+ await page.evaluate(({ selector, text }) => {
407
+ const el = document.querySelector(selector);
408
+ if (!el) {
409
+ throw new Error(`setTextContent: element "${selector}" not found`);
410
+ }
411
+ el.textContent = text;
412
+ }, opts);
413
+ }
414
+ async removeElement(opts) {
415
+ const page = await this.browser.currentPage;
416
+ const removedCount = await page.evaluate(({ selector }) => {
417
+ const nodes = document.querySelectorAll(selector);
418
+ if (nodes.length === 0) {
419
+ throw new Error(`removeElement: no elements match "${selector}"`);
420
+ }
421
+ nodes.forEach((n) => n.remove());
422
+ return nodes.length;
423
+ }, opts);
424
+ return { removedCount };
425
+ }
426
+ async setInputValue(opts) {
427
+ const page = await this.browser.currentPage;
428
+ await page.evaluate(({ selector, value }) => {
429
+ const el = document.querySelector(selector);
430
+ if (!el) {
431
+ throw new Error(`setInputValue: element "${selector}" not found`);
432
+ }
433
+ // Use the native value setter so React (and other controlled-component
434
+ // libraries) notice the change. Setting `el.value` directly bypasses
435
+ // React's internal value tracker and is silently ignored on next render.
436
+ const proto = el instanceof HTMLTextAreaElement
437
+ ? window.HTMLTextAreaElement.prototype
438
+ : el instanceof HTMLSelectElement
439
+ ? window.HTMLSelectElement.prototype
440
+ : window.HTMLInputElement.prototype;
441
+ const desc = Object.getOwnPropertyDescriptor(proto, 'value');
442
+ if (desc?.set) {
443
+ desc.set.call(el, value);
444
+ }
445
+ else {
446
+ el.value = value;
447
+ }
448
+ el.dispatchEvent(new Event('input', { bubbles: true }));
449
+ el.dispatchEvent(new Event('change', { bubbles: true }));
450
+ }, opts);
451
+ }
452
+ async clickHidden(opts) {
453
+ const page = await this.browser.currentPage;
454
+ await page.evaluate(({ selector }) => {
455
+ const el = document.querySelector(selector);
456
+ if (!el) {
457
+ throw new Error(`clickHidden: element "${selector}" not found`);
458
+ }
459
+ // Use the native click() method so React onClick handlers fire.
460
+ el.click();
461
+ }, opts);
462
+ }
463
+ async serializeDom(selector) {
464
+ const page = await this.browser.currentPage;
465
+ const baseUrl = page.url();
466
+ const fallbackViewport = page.viewportSize() ?? { width: 1440, height: 900 };
467
+ const rawCapture = await page.evaluate((targetSelector) => {
468
+ function serializeDoctype() {
469
+ return document.doctype
470
+ ? `<!DOCTYPE ${document.doctype.name}` +
471
+ (document.doctype.publicId ? ` PUBLIC "${document.doctype.publicId}"` : '') +
472
+ (document.doctype.systemId && !document.doctype.publicId ? ' SYSTEM' : '') +
473
+ (document.doctype.systemId ? ` "${document.doctype.systemId}"` : '') +
474
+ '>'
475
+ : '';
476
+ }
477
+ function copyAttributes(source, target) {
478
+ for (const attr of Array.from(source.attributes)) {
479
+ target.setAttribute(attr.name, attr.value);
480
+ }
481
+ }
482
+ function buildFocusedBranch(element) {
483
+ let branch = element.cloneNode(true);
484
+ let currentSource = element.parentElement;
485
+ while (currentSource && currentSource !== document.body) {
486
+ const shell = currentSource.cloneNode(false);
487
+ shell.appendChild(branch);
488
+ branch = shell;
489
+ currentSource = currentSource.parentElement;
490
+ }
491
+ return branch;
492
+ }
493
+ /**
494
+ * Inline same-origin <link rel="stylesheet"> tags as <style> blocks.
495
+ * This makes the captured HTML self-contained — CSS chunk names are
496
+ * build-specific and will 404 when served from a different build.
497
+ * Cross-origin stylesheets (e.g. Google Fonts CSS) are left as <link>
498
+ * tags since their URLs are stable public CDN paths.
499
+ */
500
+ function inlineStylesheets(doc) {
501
+ const links = Array.from(doc.querySelectorAll('link[rel="stylesheet"]'));
502
+ for (const link of links) {
503
+ const href = link.getAttribute('href');
504
+ if (!href)
505
+ continue;
506
+ // Skip cross-origin stylesheets — their cssRules aren't accessible
507
+ // and their URLs are stable (e.g. fonts.googleapis.com).
508
+ try {
509
+ const url = new URL(href, location.origin);
510
+ if (url.origin !== location.origin)
511
+ continue;
512
+ }
513
+ catch {
514
+ continue;
515
+ }
516
+ // Find the matching CSSStyleSheet and read its rules.
517
+ const sheet = Array.from(doc.styleSheets).find((s) => s.ownerNode === link);
518
+ if (!sheet)
519
+ continue;
520
+ let cssText = '';
521
+ try {
522
+ const rules = sheet.cssRules;
523
+ for (let i = 0; i < rules.length; i++) {
524
+ cssText += rules[i].cssText + '\n';
525
+ }
526
+ }
527
+ catch {
528
+ continue;
529
+ } // SecurityError for cross-origin sheets
530
+ if (!cssText)
531
+ continue;
532
+ const style = doc.createElement('style');
533
+ style.textContent = cssText;
534
+ link.replaceWith(style);
535
+ }
536
+ }
537
+ const doctype = serializeDoctype();
538
+ const root = document.documentElement;
539
+ if (!targetSelector) {
540
+ inlineStylesheets(document);
541
+ return {
542
+ html: doctype + (root ? root.outerHTML : ''),
543
+ viewport: {
544
+ width: window.innerWidth || document.documentElement.clientWidth || 1440,
545
+ height: window.innerHeight || document.documentElement.clientHeight || 900,
546
+ },
547
+ };
548
+ }
549
+ const element = document.querySelector(targetSelector);
550
+ if (!element) {
551
+ throw new Error(`serializeDom: no element matched selector "${targetSelector}"`);
552
+ }
553
+ if (element === document.documentElement) {
554
+ inlineStylesheets(document);
555
+ return {
556
+ html: doctype + root.outerHTML,
557
+ viewport: {
558
+ width: window.innerWidth || document.documentElement.clientWidth || 1440,
559
+ height: window.innerHeight || document.documentElement.clientHeight || 900,
560
+ },
561
+ };
562
+ }
563
+ const nextDoc = document.implementation.createHTMLDocument(document.title || '');
564
+ copyAttributes(document.documentElement, nextDoc.documentElement);
565
+ nextDoc.head.innerHTML = document.head ? document.head.innerHTML : '';
566
+ copyAttributes(document.body, nextDoc.body);
567
+ if (element === document.body) {
568
+ nextDoc.body.innerHTML = document.body.innerHTML;
569
+ }
570
+ else {
571
+ const branch = buildFocusedBranch(element);
572
+ nextDoc.body.replaceChildren(nextDoc.importNode(branch, true));
573
+ }
574
+ // Inline stylesheets in the cloned document. The <link> elements were
575
+ // copied from the live document's <head>, so their ownerNode points to
576
+ // the cloned <link>. The CSSStyleSheet objects on the *live* document
577
+ // still hold the parsed rules — iterate those and match by href.
578
+ const liveSheets = Array.from(document.styleSheets);
579
+ const clonedLinks = Array.from(nextDoc.querySelectorAll('link[rel="stylesheet"]'));
580
+ for (const link of clonedLinks) {
581
+ const href = link.getAttribute('href');
582
+ if (!href)
583
+ continue;
584
+ try {
585
+ const url = new URL(href, location.origin);
586
+ if (url.origin !== location.origin)
587
+ continue;
588
+ }
589
+ catch {
590
+ continue;
591
+ }
592
+ // Match by href against the live document's stylesheets.
593
+ const liveSheet = liveSheets.find((s) => {
594
+ try {
595
+ return s.href === new URL(href, location.origin).href;
596
+ }
597
+ catch {
598
+ return false;
599
+ }
600
+ });
601
+ if (!liveSheet)
602
+ continue;
603
+ let cssText = '';
604
+ try {
605
+ const rules = liveSheet.cssRules;
606
+ for (let i = 0; i < rules.length; i++) {
607
+ cssText += rules[i].cssText + '\n';
608
+ }
609
+ }
610
+ catch {
611
+ continue;
612
+ }
613
+ if (!cssText)
614
+ continue;
615
+ const style = nextDoc.createElement('style');
616
+ style.textContent = cssText;
617
+ link.replaceWith(style);
618
+ }
619
+ const rect = element.getBoundingClientRect();
620
+ return {
621
+ html: doctype + nextDoc.documentElement.outerHTML,
622
+ viewport: {
623
+ width: Math.max(1, Math.ceil(rect.width || window.innerWidth || document.documentElement.clientWidth || 1440)),
624
+ height: Math.max(1, Math.ceil(rect.height || window.innerHeight || document.documentElement.clientHeight || 900)),
625
+ },
626
+ };
627
+ }, selector ?? null);
628
+ // Lazy import to avoid loading parse5 unless DOM capture is actually used.
629
+ const { sanitizeDom } = await import('./dom-serializer.js');
630
+ const viewport = rawCapture?.viewport ?? fallbackViewport;
631
+ const result = sanitizeDom({ html: rawCapture.html, baseUrl, viewport });
632
+ return {
633
+ html: result.html,
634
+ assetUrls: result.assetUrls,
635
+ viewport,
636
+ capturedAt: new Date().toISOString(),
637
+ };
638
+ }
639
+ async serializeFragment(selector) {
640
+ const page = await this.browser.currentPage;
641
+ const baseUrl = page.url();
642
+ const rawHtml = await page.evaluate((sel) => {
643
+ const el = document.querySelector(sel);
644
+ return el ? el.outerHTML : null;
645
+ }, selector);
646
+ if (!rawHtml) {
647
+ throw new Error(`serializeFragment: no element matched selector "${selector}"`);
648
+ }
649
+ const { sanitizeFragment } = await import('./dom-serializer.js');
650
+ const result = sanitizeFragment({ html: rawHtml, baseUrl });
651
+ return {
652
+ html: result.html,
653
+ assetUrls: result.assetUrls,
654
+ capturedAt: new Date().toISOString(),
655
+ };
656
+ }
657
+ async extractFavicon() {
658
+ try {
659
+ const page = await this.browser.currentPage;
660
+ const faviconUrl = await page.evaluate(() => {
661
+ const icons = Array.from(document.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'));
662
+ if (icons.length > 0) {
663
+ // Prefer apple-touch-icon > icon > shortcut icon, largest size
664
+ icons.sort((a, b) => {
665
+ const priority = (el) => el.rel === 'apple-touch-icon' ? 3 : el.rel === 'icon' ? 2 : 1;
666
+ const size = (el) => {
667
+ const s = el.getAttribute('sizes')?.split('x')[0];
668
+ return s ? parseInt(s, 10) : 0;
669
+ };
670
+ return priority(b) - priority(a) || size(b) - size(a);
671
+ });
672
+ return icons[0].href;
673
+ }
674
+ return new URL('/favicon.ico', window.location.origin).href;
675
+ });
676
+ if (!faviconUrl)
677
+ return null;
678
+ const response = await page.context().request.get(faviconUrl, { timeout: 5000 });
679
+ if (!response.ok())
680
+ return null;
681
+ const buffer = Buffer.from(await response.body());
682
+ if (buffer.length === 0 || buffer.length > 512 * 1024)
683
+ return null;
684
+ const contentType = response.headers()['content-type'] ?? 'image/png';
685
+ const mimeType = contentType.split(';')[0].trim();
686
+ return { buffer, mimeType };
687
+ }
688
+ catch {
689
+ return null;
690
+ }
691
+ }
692
+ async close() {
693
+ await this.browser.close();
694
+ }
695
+ async typeIntoLocator(locator, text, clearFirst) {
696
+ const page = await this.browser.currentPage;
697
+ await locator.waitFor({ state: 'visible', timeout: 5000 });
698
+ await locator.scrollIntoViewIfNeeded({ timeout: 5000 }).catch(() => undefined);
699
+ await this.moveClipCursorToLocator(locator);
700
+ await locator.click({ timeout: 5000 });
701
+ if (clearFirst) {
702
+ await page.keyboard.press('Control+A');
703
+ }
704
+ await page.waitForTimeout(70);
705
+ await humanType(page, text);
706
+ }
707
+ async seedClipCursor() {
708
+ if (!this.clipCursor)
709
+ return;
710
+ const page = await this.browser.currentPage;
711
+ const viewport = page.viewportSize();
712
+ if (!viewport)
713
+ return;
714
+ const startX = Math.round(viewport.width * (0.3 + Math.random() * 0.4));
715
+ const startY = Math.round(viewport.height * (0.3 + Math.random() * 0.4));
716
+ await page.mouse.move(startX, startY);
717
+ await page.evaluate(({ x, y }) => {
718
+ if (typeof window.__akMoveCursor === 'function')
719
+ window.__akMoveCursor(x, y);
720
+ }, { x: startX, y: startY }).catch(() => { });
721
+ this.clipCursor.currentPosition = { x: startX, y: startY };
722
+ await page.waitForTimeout(60);
723
+ }
724
+ async moveClipCursorToViewportCenter() {
725
+ if (!this.clipCursor)
726
+ return;
727
+ const page = await this.browser.currentPage;
728
+ const viewport = page.viewportSize();
729
+ if (!viewport)
730
+ return;
731
+ await this.moveClipCursorToPoint({
732
+ x: Math.round(viewport.width / 2),
733
+ y: Math.round(viewport.height / 2),
734
+ });
735
+ }
736
+ async moveClipCursorToLocator(locator) {
737
+ if (!this.clipCursor)
738
+ return;
739
+ await locator.waitFor({ state: 'visible', timeout: 5000 });
740
+ await locator.scrollIntoViewIfNeeded({ timeout: 5000 }).catch(() => undefined);
741
+ const page = await this.browser.currentPage;
742
+ const viewport = page.viewportSize();
743
+ const box = await locator.boundingBox();
744
+ if (!box || !viewport)
745
+ return;
746
+ // Skip the move if the cursor is already inside the element — avoids
747
+ // erratic micro-movements when HOVER is immediately followed by CLICK
748
+ // on the same target.
749
+ const cur = this.clipCursor.currentPosition;
750
+ if (cur
751
+ && cur.x >= box.x && cur.x <= box.x + box.width
752
+ && cur.y >= box.y && cur.y <= box.y + box.height) {
753
+ return;
754
+ }
755
+ await this.moveClipCursorToPoint(getHumanPointInBox(box, viewport));
756
+ }
757
+ async moveClipCursorToPoint(point, options) {
758
+ if (!this.clipCursor)
759
+ return;
760
+ const page = await this.browser.currentPage;
761
+ const from = this.clipCursor.currentPosition;
762
+ if (from) {
763
+ await moveMouse(page, from, point, options);
764
+ }
765
+ else {
766
+ await page.mouse.move(point.x, point.y);
767
+ await page.evaluate(({ x, y }) => {
768
+ if (typeof window.__akMoveCursor === 'function')
769
+ window.__akMoveCursor(x, y);
770
+ }, { x: Math.round(point.x), y: Math.round(point.y) }).catch(() => { });
771
+ }
772
+ this.clipCursor.currentPosition = point;
773
+ }
774
+ async emitClipClickPulse() {
775
+ if (!this.clipCursor?.currentPosition)
776
+ return;
777
+ const page = await this.browser.currentPage;
778
+ const { x, y } = this.clipCursor.currentPosition;
779
+ await page.evaluate(({ px, py }) => {
780
+ if (typeof window.__akClickPulse === 'function')
781
+ window.__akClickPulse(px, py);
782
+ }, { px: Math.round(x), py: Math.round(y) }).catch(() => { });
783
+ }
784
+ }
785
+ function describeResolveOptions(opts) {
786
+ const parts = [];
787
+ if (opts.selector)
788
+ parts.push(`selector="${opts.selector}"`);
789
+ if (opts.target?.text)
790
+ parts.push(`text="${opts.target.text}"`);
791
+ if (opts.target?.role)
792
+ parts.push(`role="${opts.target.role}"`);
793
+ if (opts.target?.label)
794
+ parts.push(`label="${opts.target.label}"`);
795
+ if (opts.target?.placeholder)
796
+ parts.push(`placeholder="${opts.target.placeholder}"`);
797
+ return parts.join(', ') || 'no target specified';
798
+ }
799
+ function getHumanPointInBox(box, viewport) {
800
+ const insetX = Math.min(Math.max(box.width * 0.2, 6), Math.max(6, box.width / 2));
801
+ const insetY = Math.min(Math.max(box.height * 0.2, 6), Math.max(6, box.height / 2));
802
+ const minX = box.x + insetX;
803
+ const maxX = box.x + Math.max(insetX, box.width - insetX);
804
+ const minY = box.y + insetY;
805
+ const maxY = box.y + Math.max(insetY, box.height - insetY);
806
+ const targetX = maxX <= minX ? box.x + box.width / 2 : randomBetween(minX, maxX);
807
+ const targetY = maxY <= minY ? box.y + box.height / 2 : randomBetween(minY, maxY);
808
+ return {
809
+ x: clampPoint(targetX, 4, viewport.width - 4),
810
+ y: clampPoint(targetY, 4, viewport.height - 4),
811
+ };
812
+ }
813
+ function randomBetween(min, max) {
814
+ return min + Math.random() * Math.max(0, max - min);
815
+ }
816
+ function clampPoint(value, min, max) {
817
+ return Math.round(Math.max(min, Math.min(max, value)));
818
+ }
819
+ //# sourceMappingURL=web-playwright-local.js.map