tabwright 1.0.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 (599) hide show
  1. package/LICENSE +21 -0
  2. package/bin.js +4 -0
  3. package/dist/a11y-client.js +204 -0
  4. package/dist/agent-skills/tabwright/SKILL.md +119 -0
  5. package/dist/aria-snapshot.d.ts +262 -0
  6. package/dist/aria-snapshot.d.ts.map +1 -0
  7. package/dist/aria-snapshot.js +1256 -0
  8. package/dist/aria-snapshot.js.map +1 -0
  9. package/dist/aria-snapshot.test.d.ts +2 -0
  10. package/dist/aria-snapshot.test.d.ts.map +1 -0
  11. package/dist/aria-snapshot.test.js +270 -0
  12. package/dist/aria-snapshot.test.js.map +1 -0
  13. package/dist/aria-snapshot.unit.test.d.ts +2 -0
  14. package/dist/aria-snapshot.unit.test.d.ts.map +1 -0
  15. package/dist/aria-snapshot.unit.test.js +542 -0
  16. package/dist/aria-snapshot.unit.test.js.map +1 -0
  17. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
  18. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
  19. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
  20. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
  21. package/dist/bippy.js +894 -0
  22. package/dist/browser-config.d.ts +13 -0
  23. package/dist/browser-config.d.ts.map +1 -0
  24. package/dist/browser-config.js +126 -0
  25. package/dist/browser-config.js.map +1 -0
  26. package/dist/browser-install.d.ts +16 -0
  27. package/dist/browser-install.d.ts.map +1 -0
  28. package/dist/browser-install.js +238 -0
  29. package/dist/browser-install.js.map +1 -0
  30. package/dist/browser-launch.d.ts +17 -0
  31. package/dist/browser-launch.d.ts.map +1 -0
  32. package/dist/browser-launch.js +44 -0
  33. package/dist/browser-launch.js.map +1 -0
  34. package/dist/builtin-capabilities.d.ts +28 -0
  35. package/dist/builtin-capabilities.d.ts.map +1 -0
  36. package/dist/builtin-capabilities.js +2673 -0
  37. package/dist/builtin-capabilities.js.map +1 -0
  38. package/dist/capability-agent-skill.d.ts +60 -0
  39. package/dist/capability-agent-skill.d.ts.map +1 -0
  40. package/dist/capability-agent-skill.js +225 -0
  41. package/dist/capability-agent-skill.js.map +1 -0
  42. package/dist/capability-auth-state.d.ts +34 -0
  43. package/dist/capability-auth-state.d.ts.map +1 -0
  44. package/dist/capability-auth-state.js +157 -0
  45. package/dist/capability-auth-state.js.map +1 -0
  46. package/dist/capability-auth.d.ts +36 -0
  47. package/dist/capability-auth.d.ts.map +1 -0
  48. package/dist/capability-auth.js +198 -0
  49. package/dist/capability-auth.js.map +1 -0
  50. package/dist/capability-cli-confirmation.test.d.ts +2 -0
  51. package/dist/capability-cli-confirmation.test.d.ts.map +1 -0
  52. package/dist/capability-cli-confirmation.test.js +79 -0
  53. package/dist/capability-cli-confirmation.test.js.map +1 -0
  54. package/dist/capability-options.d.ts +20 -0
  55. package/dist/capability-options.d.ts.map +1 -0
  56. package/dist/capability-options.js +31 -0
  57. package/dist/capability-options.js.map +1 -0
  58. package/dist/capability-options.test.d.ts +2 -0
  59. package/dist/capability-options.test.d.ts.map +1 -0
  60. package/dist/capability-options.test.js +81 -0
  61. package/dist/capability-options.test.js.map +1 -0
  62. package/dist/capability-package.d.ts +27 -0
  63. package/dist/capability-package.d.ts.map +1 -0
  64. package/dist/capability-package.js +444 -0
  65. package/dist/capability-package.js.map +1 -0
  66. package/dist/capability-package.test.d.ts +2 -0
  67. package/dist/capability-package.test.d.ts.map +1 -0
  68. package/dist/capability-package.test.js +179 -0
  69. package/dist/capability-package.test.js.map +1 -0
  70. package/dist/capability-registry.d.ts +262 -0
  71. package/dist/capability-registry.d.ts.map +1 -0
  72. package/dist/capability-registry.js +931 -0
  73. package/dist/capability-registry.js.map +1 -0
  74. package/dist/capability-registry.test.d.ts +2 -0
  75. package/dist/capability-registry.test.d.ts.map +1 -0
  76. package/dist/capability-registry.test.js +1683 -0
  77. package/dist/capability-registry.test.js.map +1 -0
  78. package/dist/capability-runner.d.ts +99 -0
  79. package/dist/capability-runner.d.ts.map +1 -0
  80. package/dist/capability-runner.js +608 -0
  81. package/dist/capability-runner.js.map +1 -0
  82. package/dist/capability-search-relevance.test.d.ts +2 -0
  83. package/dist/capability-search-relevance.test.d.ts.map +1 -0
  84. package/dist/capability-search-relevance.test.js +67 -0
  85. package/dist/capability-search-relevance.test.js.map +1 -0
  86. package/dist/capability-studio.d.ts +11 -0
  87. package/dist/capability-studio.d.ts.map +1 -0
  88. package/dist/capability-studio.js +593 -0
  89. package/dist/capability-studio.js.map +1 -0
  90. package/dist/capability-studio.test.d.ts +2 -0
  91. package/dist/capability-studio.test.d.ts.map +1 -0
  92. package/dist/capability-studio.test.js +41 -0
  93. package/dist/capability-studio.test.js.map +1 -0
  94. package/dist/cdp-log.d.ts +19 -0
  95. package/dist/cdp-log.d.ts.map +1 -0
  96. package/dist/cdp-log.js +83 -0
  97. package/dist/cdp-log.js.map +1 -0
  98. package/dist/cdp-log.test.d.ts +2 -0
  99. package/dist/cdp-log.test.d.ts.map +1 -0
  100. package/dist/cdp-log.test.js +109 -0
  101. package/dist/cdp-log.test.js.map +1 -0
  102. package/dist/cdp-relay.d.ts +19 -0
  103. package/dist/cdp-relay.d.ts.map +1 -0
  104. package/dist/cdp-relay.js +2375 -0
  105. package/dist/cdp-relay.js.map +1 -0
  106. package/dist/cdp-session.d.ts +37 -0
  107. package/dist/cdp-session.d.ts.map +1 -0
  108. package/dist/cdp-session.js +36 -0
  109. package/dist/cdp-session.js.map +1 -0
  110. package/dist/cdp-types.d.ts +65 -0
  111. package/dist/cdp-types.d.ts.map +1 -0
  112. package/dist/cdp-types.js +91 -0
  113. package/dist/cdp-types.js.map +1 -0
  114. package/dist/channel-owner-inspect.test.d.ts +2 -0
  115. package/dist/channel-owner-inspect.test.d.ts.map +1 -0
  116. package/dist/channel-owner-inspect.test.js +75 -0
  117. package/dist/channel-owner-inspect.test.js.map +1 -0
  118. package/dist/chrome-discovery.d.ts +65 -0
  119. package/dist/chrome-discovery.d.ts.map +1 -0
  120. package/dist/chrome-discovery.js +173 -0
  121. package/dist/chrome-discovery.js.map +1 -0
  122. package/dist/chrome-discovery.test.d.ts +2 -0
  123. package/dist/chrome-discovery.test.d.ts.map +1 -0
  124. package/dist/chrome-discovery.test.js +41 -0
  125. package/dist/chrome-discovery.test.js.map +1 -0
  126. package/dist/clean-html.d.ts +11 -0
  127. package/dist/clean-html.d.ts.map +1 -0
  128. package/dist/clean-html.js +106 -0
  129. package/dist/clean-html.js.map +1 -0
  130. package/dist/cli-help.test.d.ts +2 -0
  131. package/dist/cli-help.test.d.ts.map +1 -0
  132. package/dist/cli-help.test.js +98 -0
  133. package/dist/cli-help.test.js.map +1 -0
  134. package/dist/cli.d.ts +3 -0
  135. package/dist/cli.d.ts.map +1 -0
  136. package/dist/cli.js +2709 -0
  137. package/dist/cli.js.map +1 -0
  138. package/dist/cloud-client.d.ts +56 -0
  139. package/dist/cloud-client.d.ts.map +1 -0
  140. package/dist/cloud-client.js +127 -0
  141. package/dist/cloud-client.js.map +1 -0
  142. package/dist/create-logger.d.ts +9 -0
  143. package/dist/create-logger.d.ts.map +1 -0
  144. package/dist/create-logger.js +27 -0
  145. package/dist/create-logger.js.map +1 -0
  146. package/dist/debugger-api.md +458 -0
  147. package/dist/debugger-examples-types.d.ts +24 -0
  148. package/dist/debugger-examples-types.d.ts.map +1 -0
  149. package/dist/debugger-examples-types.js +2 -0
  150. package/dist/debugger-examples-types.js.map +1 -0
  151. package/dist/debugger-examples.d.ts +6 -0
  152. package/dist/debugger-examples.d.ts.map +1 -0
  153. package/dist/debugger-examples.js +53 -0
  154. package/dist/debugger-examples.js.map +1 -0
  155. package/dist/debugger.d.ts +381 -0
  156. package/dist/debugger.d.ts.map +1 -0
  157. package/dist/debugger.js +630 -0
  158. package/dist/debugger.js.map +1 -0
  159. package/dist/diff-utils.d.ts +20 -0
  160. package/dist/diff-utils.d.ts.map +1 -0
  161. package/dist/diff-utils.js +50 -0
  162. package/dist/diff-utils.js.map +1 -0
  163. package/dist/diff-utils.test.d.ts +2 -0
  164. package/dist/diff-utils.test.d.ts.map +1 -0
  165. package/dist/diff-utils.test.js +137 -0
  166. package/dist/diff-utils.test.js.map +1 -0
  167. package/dist/doctor.d.ts +44 -0
  168. package/dist/doctor.d.ts.map +1 -0
  169. package/dist/doctor.js +246 -0
  170. package/dist/doctor.js.map +1 -0
  171. package/dist/doctor.test.d.ts +2 -0
  172. package/dist/doctor.test.d.ts.map +1 -0
  173. package/dist/doctor.test.js +208 -0
  174. package/dist/doctor.test.js.map +1 -0
  175. package/dist/editor-api.md +374 -0
  176. package/dist/editor-examples.d.ts +11 -0
  177. package/dist/editor-examples.d.ts.map +1 -0
  178. package/dist/editor-examples.js +124 -0
  179. package/dist/editor-examples.js.map +1 -0
  180. package/dist/editor.d.ts +203 -0
  181. package/dist/editor.d.ts.map +1 -0
  182. package/dist/editor.js +363 -0
  183. package/dist/editor.js.map +1 -0
  184. package/dist/env-compat.d.ts +2 -0
  185. package/dist/env-compat.d.ts.map +1 -0
  186. package/dist/env-compat.js +27 -0
  187. package/dist/env-compat.js.map +1 -0
  188. package/dist/executor.d.ts +229 -0
  189. package/dist/executor.d.ts.map +1 -0
  190. package/dist/executor.js +1465 -0
  191. package/dist/executor.js.map +1 -0
  192. package/dist/executor.unit.test.d.ts +2 -0
  193. package/dist/executor.unit.test.d.ts.map +1 -0
  194. package/dist/executor.unit.test.js +143 -0
  195. package/dist/executor.unit.test.js.map +1 -0
  196. package/dist/extension/_locales/en/messages.json +278 -0
  197. package/dist/extension/_locales/zh_CN/messages.json +256 -0
  198. package/dist/extension/assets/options-CYH96Lcx.css +1349 -0
  199. package/dist/extension/background.js +3994 -0
  200. package/dist/extension/icons/SolarCursorSquareBold.png +0 -0
  201. package/dist/extension/icons/icon-black-128.png +0 -0
  202. package/dist/extension/icons/icon-black-16.png +0 -0
  203. package/dist/extension/icons/icon-black-32.png +0 -0
  204. package/dist/extension/icons/icon-black-48.png +0 -0
  205. package/dist/extension/icons/icon-gray-128.png +0 -0
  206. package/dist/extension/icons/icon-gray-16.png +0 -0
  207. package/dist/extension/icons/icon-gray-32.png +0 -0
  208. package/dist/extension/icons/icon-gray-48.png +0 -0
  209. package/dist/extension/icons/icon-green-128.png +0 -0
  210. package/dist/extension/icons/icon-green-16.png +0 -0
  211. package/dist/extension/icons/icon-green-32.png +0 -0
  212. package/dist/extension/icons/icon-green-48.png +0 -0
  213. package/dist/extension/icons/tabwright-icon-black.png +0 -0
  214. package/dist/extension/icons/tabwright-icon-gray-disabled.png +0 -0
  215. package/dist/extension/icons/tabwright-icon-gray-disabled.svg +1 -0
  216. package/dist/extension/manifest.json +54 -0
  217. package/dist/extension/options.js +16145 -0
  218. package/dist/extension/rrweb-recorder.js +12952 -0
  219. package/dist/extension/src/options.html +165 -0
  220. package/dist/extension/src/prism-bash.min.js +1 -0
  221. package/dist/extension/src/prism.min.js +1 -0
  222. package/dist/extension/src/welcome.html +396 -0
  223. package/dist/extension-connection.test.d.ts +2 -0
  224. package/dist/extension-connection.test.d.ts.map +1 -0
  225. package/dist/extension-connection.test.js +708 -0
  226. package/dist/extension-connection.test.js.map +1 -0
  227. package/dist/extension-icon-warning.test.d.ts +2 -0
  228. package/dist/extension-icon-warning.test.d.ts.map +1 -0
  229. package/dist/extension-icon-warning.test.js +33 -0
  230. package/dist/extension-icon-warning.test.js.map +1 -0
  231. package/dist/ghost-browser.d.ts +62 -0
  232. package/dist/ghost-browser.d.ts.map +1 -0
  233. package/dist/ghost-browser.js +107 -0
  234. package/dist/ghost-browser.js.map +1 -0
  235. package/dist/ghost-cursor-client.js +374 -0
  236. package/dist/ghost-cursor-controller.d.ts +46 -0
  237. package/dist/ghost-cursor-controller.d.ts.map +1 -0
  238. package/dist/ghost-cursor-controller.js +98 -0
  239. package/dist/ghost-cursor-controller.js.map +1 -0
  240. package/dist/ghost-cursor.d.ts +27 -0
  241. package/dist/ghost-cursor.d.ts.map +1 -0
  242. package/dist/ghost-cursor.js +79 -0
  243. package/dist/ghost-cursor.js.map +1 -0
  244. package/dist/htmlrewrite.d.ts +8 -0
  245. package/dist/htmlrewrite.d.ts.map +1 -0
  246. package/dist/htmlrewrite.js +339 -0
  247. package/dist/htmlrewrite.js.map +1 -0
  248. package/dist/htmlrewrite.test.d.ts +2 -0
  249. package/dist/htmlrewrite.test.d.ts.map +1 -0
  250. package/dist/htmlrewrite.test.js +7975 -0
  251. package/dist/htmlrewrite.test.js.map +1 -0
  252. package/dist/index.d.ts +20 -0
  253. package/dist/index.d.ts.map +1 -0
  254. package/dist/index.js +11 -0
  255. package/dist/index.js.map +1 -0
  256. package/dist/kill-port.d.ts +29 -0
  257. package/dist/kill-port.d.ts.map +1 -0
  258. package/dist/kill-port.js +190 -0
  259. package/dist/kill-port.js.map +1 -0
  260. package/dist/kitty-graphics.d.ts +22 -0
  261. package/dist/kitty-graphics.d.ts.map +1 -0
  262. package/dist/kitty-graphics.js +71 -0
  263. package/dist/kitty-graphics.js.map +1 -0
  264. package/dist/kitty-graphics.test.d.ts +2 -0
  265. package/dist/kitty-graphics.test.d.ts.map +1 -0
  266. package/dist/kitty-graphics.test.js +125 -0
  267. package/dist/kitty-graphics.test.js.map +1 -0
  268. package/dist/locator-selector.test.d.ts +2 -0
  269. package/dist/locator-selector.test.d.ts.map +1 -0
  270. package/dist/locator-selector.test.js +98 -0
  271. package/dist/locator-selector.test.js.map +1 -0
  272. package/dist/mcp-client.d.ts +20 -0
  273. package/dist/mcp-client.d.ts.map +1 -0
  274. package/dist/mcp-client.js +56 -0
  275. package/dist/mcp-client.js.map +1 -0
  276. package/dist/mcp.d.ts +6 -0
  277. package/dist/mcp.d.ts.map +1 -0
  278. package/dist/mcp.js +511 -0
  279. package/dist/mcp.js.map +1 -0
  280. package/dist/on-mouse-action.test.d.ts +2 -0
  281. package/dist/on-mouse-action.test.d.ts.map +1 -0
  282. package/dist/on-mouse-action.test.js +180 -0
  283. package/dist/on-mouse-action.test.js.map +1 -0
  284. package/dist/options-page.test.d.ts +2 -0
  285. package/dist/options-page.test.d.ts.map +1 -0
  286. package/dist/options-page.test.js +773 -0
  287. package/dist/options-page.test.js.map +1 -0
  288. package/dist/package-paths.d.ts +4 -0
  289. package/dist/package-paths.d.ts.map +1 -0
  290. package/dist/package-paths.js +30 -0
  291. package/dist/package-paths.js.map +1 -0
  292. package/dist/page-markdown.d.ts +40 -0
  293. package/dist/page-markdown.d.ts.map +1 -0
  294. package/dist/page-markdown.js +181 -0
  295. package/dist/page-markdown.js.map +1 -0
  296. package/dist/performance-examples.d.ts +5 -0
  297. package/dist/performance-examples.d.ts.map +1 -0
  298. package/dist/performance-examples.js +112 -0
  299. package/dist/performance-examples.js.map +1 -0
  300. package/dist/performance-profiling.md +417 -0
  301. package/dist/playwright-import.d.ts +19 -0
  302. package/dist/playwright-import.d.ts.map +1 -0
  303. package/dist/playwright-import.js +40 -0
  304. package/dist/playwright-import.js.map +1 -0
  305. package/dist/popup-relocation.test.d.ts +7 -0
  306. package/dist/popup-relocation.test.d.ts.map +1 -0
  307. package/dist/popup-relocation.test.js +139 -0
  308. package/dist/popup-relocation.test.js.map +1 -0
  309. package/dist/product-paths.d.ts +9 -0
  310. package/dist/product-paths.d.ts.map +1 -0
  311. package/dist/product-paths.js +24 -0
  312. package/dist/product-paths.js.map +1 -0
  313. package/dist/product-paths.test.d.ts +2 -0
  314. package/dist/product-paths.test.d.ts.map +1 -0
  315. package/dist/product-paths.test.js +39 -0
  316. package/dist/product-paths.test.js.map +1 -0
  317. package/dist/prompt.md +1004 -0
  318. package/dist/protocol.d.ts +248 -0
  319. package/dist/protocol.d.ts.map +1 -0
  320. package/dist/protocol.js +59 -0
  321. package/dist/protocol.js.map +1 -0
  322. package/dist/protocol.test.d.ts +2 -0
  323. package/dist/protocol.test.d.ts.map +1 -0
  324. package/dist/protocol.test.js +31 -0
  325. package/dist/protocol.test.js.map +1 -0
  326. package/dist/react-source.d.ts +57 -0
  327. package/dist/react-source.d.ts.map +1 -0
  328. package/dist/react-source.js +254 -0
  329. package/dist/react-source.js.map +1 -0
  330. package/dist/readability.js +1674 -0
  331. package/dist/relay-client.d.ts +104 -0
  332. package/dist/relay-client.d.ts.map +1 -0
  333. package/dist/relay-client.js +426 -0
  334. package/dist/relay-client.js.map +1 -0
  335. package/dist/relay-client.test.d.ts +2 -0
  336. package/dist/relay-client.test.d.ts.map +1 -0
  337. package/dist/relay-client.test.js +305 -0
  338. package/dist/relay-client.test.js.map +1 -0
  339. package/dist/relay-core.test.d.ts +2 -0
  340. package/dist/relay-core.test.d.ts.map +1 -0
  341. package/dist/relay-core.test.js +1427 -0
  342. package/dist/relay-core.test.js.map +1 -0
  343. package/dist/relay-navigation.test.d.ts +2 -0
  344. package/dist/relay-navigation.test.d.ts.map +1 -0
  345. package/dist/relay-navigation.test.js +661 -0
  346. package/dist/relay-navigation.test.js.map +1 -0
  347. package/dist/relay-session.test.d.ts +2 -0
  348. package/dist/relay-session.test.d.ts.map +1 -0
  349. package/dist/relay-session.test.js +1121 -0
  350. package/dist/relay-session.test.js.map +1 -0
  351. package/dist/relay-state.d.ts +178 -0
  352. package/dist/relay-state.d.ts.map +1 -0
  353. package/dist/relay-state.js +346 -0
  354. package/dist/relay-state.js.map +1 -0
  355. package/dist/relay-state.test.d.ts +2 -0
  356. package/dist/relay-state.test.d.ts.map +1 -0
  357. package/dist/relay-state.test.js +594 -0
  358. package/dist/relay-state.test.js.map +1 -0
  359. package/dist/replay-ai-index.d.ts +99 -0
  360. package/dist/replay-ai-index.d.ts.map +1 -0
  361. package/dist/replay-ai-index.js +677 -0
  362. package/dist/replay-ai-index.js.map +1 -0
  363. package/dist/replay-ai-index.test.d.ts +2 -0
  364. package/dist/replay-ai-index.test.d.ts.map +1 -0
  365. package/dist/replay-ai-index.test.js +255 -0
  366. package/dist/replay-ai-index.test.js.map +1 -0
  367. package/dist/replay-cli.test.d.ts +2 -0
  368. package/dist/replay-cli.test.d.ts.map +1 -0
  369. package/dist/replay-cli.test.js +275 -0
  370. package/dist/replay-cli.test.js.map +1 -0
  371. package/dist/replay-eval.d.ts +44 -0
  372. package/dist/replay-eval.d.ts.map +1 -0
  373. package/dist/replay-eval.js +902 -0
  374. package/dist/replay-eval.js.map +1 -0
  375. package/dist/replay-handoff.d.ts +29 -0
  376. package/dist/replay-handoff.d.ts.map +1 -0
  377. package/dist/replay-handoff.js +83 -0
  378. package/dist/replay-handoff.js.map +1 -0
  379. package/dist/replay-handoff.test.d.ts +2 -0
  380. package/dist/replay-handoff.test.d.ts.map +1 -0
  381. package/dist/replay-handoff.test.js +123 -0
  382. package/dist/replay-handoff.test.js.map +1 -0
  383. package/dist/replay-workflow-compiler.d.ts +44 -0
  384. package/dist/replay-workflow-compiler.d.ts.map +1 -0
  385. package/dist/replay-workflow-compiler.js +601 -0
  386. package/dist/replay-workflow-compiler.js.map +1 -0
  387. package/dist/replay-workflow-compiler.test.d.ts +2 -0
  388. package/dist/replay-workflow-compiler.test.d.ts.map +1 -0
  389. package/dist/replay-workflow-compiler.test.js +223 -0
  390. package/dist/replay-workflow-compiler.test.js.map +1 -0
  391. package/dist/rrweb-recording-relay.d.ts +56 -0
  392. package/dist/rrweb-recording-relay.d.ts.map +1 -0
  393. package/dist/rrweb-recording-relay.js +299 -0
  394. package/dist/rrweb-recording-relay.js.map +1 -0
  395. package/dist/rrweb-recording-relay.test.d.ts +2 -0
  396. package/dist/rrweb-recording-relay.test.d.ts.map +1 -0
  397. package/dist/rrweb-recording-relay.test.js +85 -0
  398. package/dist/rrweb-recording-relay.test.js.map +1 -0
  399. package/dist/rrweb-recording.d.ts +104 -0
  400. package/dist/rrweb-recording.d.ts.map +1 -0
  401. package/dist/rrweb-recording.js +180 -0
  402. package/dist/rrweb-recording.js.map +1 -0
  403. package/dist/scoped-fs.d.ts +95 -0
  404. package/dist/scoped-fs.d.ts.map +1 -0
  405. package/dist/scoped-fs.js +358 -0
  406. package/dist/scoped-fs.js.map +1 -0
  407. package/dist/scoped-fs.test.d.ts +5 -0
  408. package/dist/scoped-fs.test.d.ts.map +1 -0
  409. package/dist/scoped-fs.test.js +73 -0
  410. package/dist/scoped-fs.test.js.map +1 -0
  411. package/dist/selector-generator.js +7378 -0
  412. package/dist/skill-stub.test.d.ts +2 -0
  413. package/dist/skill-stub.test.d.ts.map +1 -0
  414. package/dist/skill-stub.test.js +22 -0
  415. package/dist/skill-stub.test.js.map +1 -0
  416. package/dist/snapshot-tools.test.d.ts +2 -0
  417. package/dist/snapshot-tools.test.d.ts.map +1 -0
  418. package/dist/snapshot-tools.test.js +856 -0
  419. package/dist/snapshot-tools.test.js.map +1 -0
  420. package/dist/start-relay-server.d.ts +6 -0
  421. package/dist/start-relay-server.d.ts.map +1 -0
  422. package/dist/start-relay-server.js +57 -0
  423. package/dist/start-relay-server.js.map +1 -0
  424. package/dist/styles-api.md +124 -0
  425. package/dist/styles-examples.d.ts +8 -0
  426. package/dist/styles-examples.d.ts.map +1 -0
  427. package/dist/styles-examples.js +64 -0
  428. package/dist/styles-examples.js.map +1 -0
  429. package/dist/styles.d.ts +27 -0
  430. package/dist/styles.d.ts.map +1 -0
  431. package/dist/styles.js +231 -0
  432. package/dist/styles.js.map +1 -0
  433. package/dist/tabwright-agent-skill.d.ts +30 -0
  434. package/dist/tabwright-agent-skill.d.ts.map +1 -0
  435. package/dist/tabwright-agent-skill.js +74 -0
  436. package/dist/tabwright-agent-skill.js.map +1 -0
  437. package/dist/tabwright-agent-skill.test.d.ts +2 -0
  438. package/dist/tabwright-agent-skill.test.d.ts.map +1 -0
  439. package/dist/tabwright-agent-skill.test.js +51 -0
  440. package/dist/tabwright-agent-skill.test.js.map +1 -0
  441. package/dist/test-declarations.d.ts +13 -0
  442. package/dist/test-declarations.d.ts.map +1 -0
  443. package/dist/test-declarations.js +2 -0
  444. package/dist/test-declarations.js.map +1 -0
  445. package/dist/test-utils.d.ts +69 -0
  446. package/dist/test-utils.d.ts.map +1 -0
  447. package/dist/test-utils.js +448 -0
  448. package/dist/test-utils.js.map +1 -0
  449. package/dist/test-utils.test.d.ts +2 -0
  450. package/dist/test-utils.test.d.ts.map +1 -0
  451. package/dist/test-utils.test.js +55 -0
  452. package/dist/test-utils.test.js.map +1 -0
  453. package/dist/utils.d.ts +26 -0
  454. package/dist/utils.d.ts.map +1 -0
  455. package/dist/utils.js +60 -0
  456. package/dist/utils.js.map +1 -0
  457. package/dist/wait-for-page-load.d.ts +16 -0
  458. package/dist/wait-for-page-load.d.ts.map +1 -0
  459. package/dist/wait-for-page-load.js +146 -0
  460. package/dist/wait-for-page-load.js.map +1 -0
  461. package/dist/workflow-capability.d.ts +72 -0
  462. package/dist/workflow-capability.d.ts.map +1 -0
  463. package/dist/workflow-capability.js +316 -0
  464. package/dist/workflow-capability.js.map +1 -0
  465. package/dist/workflow-script-flow.test.d.ts +2 -0
  466. package/dist/workflow-script-flow.test.d.ts.map +1 -0
  467. package/dist/workflow-script-flow.test.js +285 -0
  468. package/dist/workflow-script-flow.test.js.map +1 -0
  469. package/package.json +80 -0
  470. package/src/__snapshots__/x.com.processed.html +1003 -0
  471. package/src/__snapshots__/x.com.processed.withStyles.html +2720 -0
  472. package/src/a11y-client.ts +241 -0
  473. package/src/agent-skills/conan-config/SKILL.md +141 -0
  474. package/src/agent-skills/conan-config/agents/openai.yaml +4 -0
  475. package/src/aria-snapshot.test.ts +311 -0
  476. package/src/aria-snapshot.ts +1681 -0
  477. package/src/aria-snapshot.unit.test.ts +615 -0
  478. package/src/aria-snapshots/github-interactive.txt +193 -0
  479. package/src/aria-snapshots/github-raw.txt +309 -0
  480. package/src/aria-snapshots/hackernews-interactive.txt +477 -0
  481. package/src/aria-snapshots/hackernews-raw.txt +428 -0
  482. package/src/aria-snapshots/prosemirror-interactive.txt +66 -0
  483. package/src/aria-snapshots/prosemirror-raw.txt +145 -0
  484. package/src/assets/aria-labels-hacker-news.png +0 -0
  485. package/src/assets/aria-labels-old-reddit.png +0 -0
  486. package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
  487. package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
  488. package/src/assets/x.com.html +32946 -0
  489. package/src/browser-config.ts +179 -0
  490. package/src/browser-install.ts +284 -0
  491. package/src/browser-launch.ts +75 -0
  492. package/src/builtin-capabilities.ts +2765 -0
  493. package/src/capability-agent-skill.ts +305 -0
  494. package/src/capability-auth-state.ts +197 -0
  495. package/src/capability-auth.ts +249 -0
  496. package/src/capability-cli-confirmation.test.ts +90 -0
  497. package/src/capability-options.test.ts +89 -0
  498. package/src/capability-options.ts +57 -0
  499. package/src/capability-package.test.ts +204 -0
  500. package/src/capability-package.ts +555 -0
  501. package/src/capability-registry.test.ts +1905 -0
  502. package/src/capability-registry.ts +1234 -0
  503. package/src/capability-runner.ts +801 -0
  504. package/src/capability-search-relevance.test.ts +73 -0
  505. package/src/capability-studio.test.ts +45 -0
  506. package/src/capability-studio.ts +641 -0
  507. package/src/cdp-log.test.ts +131 -0
  508. package/src/cdp-log.ts +112 -0
  509. package/src/cdp-relay.ts +2944 -0
  510. package/src/cdp-session.ts +77 -0
  511. package/src/cdp-types.ts +158 -0
  512. package/src/channel-owner-inspect.test.ts +94 -0
  513. package/src/chrome-discovery.test.ts +50 -0
  514. package/src/chrome-discovery.ts +220 -0
  515. package/src/clean-html.ts +141 -0
  516. package/src/cli-help.test.ts +123 -0
  517. package/src/cli.ts +3181 -0
  518. package/src/cloud-client.ts +179 -0
  519. package/src/create-logger.ts +38 -0
  520. package/src/debugger-examples-types.ts +16 -0
  521. package/src/debugger-examples.ts +66 -0
  522. package/src/debugger.ts +708 -0
  523. package/src/diff-utils.test.ts +152 -0
  524. package/src/diff-utils.ts +70 -0
  525. package/src/doctor.test.ts +244 -0
  526. package/src/doctor.ts +321 -0
  527. package/src/editor-examples.ts +158 -0
  528. package/src/editor.ts +426 -0
  529. package/src/env-compat.ts +28 -0
  530. package/src/executor.ts +1824 -0
  531. package/src/executor.unit.test.ts +168 -0
  532. package/src/extension-connection.test.ts +855 -0
  533. package/src/extension-icon-warning.test.ts +45 -0
  534. package/src/ghost-browser.ts +149 -0
  535. package/src/ghost-cursor-client.ts +494 -0
  536. package/src/ghost-cursor-controller.ts +129 -0
  537. package/src/ghost-cursor.ts +123 -0
  538. package/src/htmlrewrite.test.ts +8008 -0
  539. package/src/htmlrewrite.ts +390 -0
  540. package/src/index.ts +53 -0
  541. package/src/kill-port.ts +219 -0
  542. package/src/kitty-graphics.test.ts +139 -0
  543. package/src/kitty-graphics.ts +79 -0
  544. package/src/locator-selector.test.ts +119 -0
  545. package/src/mcp-client.ts +78 -0
  546. package/src/mcp.ts +629 -0
  547. package/src/on-mouse-action.test.ts +226 -0
  548. package/src/options-page.test.ts +839 -0
  549. package/src/package-paths.ts +38 -0
  550. package/src/page-markdown.ts +240 -0
  551. package/src/performance-examples.ts +186 -0
  552. package/src/playwright-import.ts +59 -0
  553. package/src/popup-relocation.test.ts +163 -0
  554. package/src/product-paths.test.ts +47 -0
  555. package/src/product-paths.ts +28 -0
  556. package/src/protocol.test.ts +52 -0
  557. package/src/protocol.ts +351 -0
  558. package/src/react-source.ts +379 -0
  559. package/src/relay-client.test.ts +375 -0
  560. package/src/relay-client.ts +572 -0
  561. package/src/relay-core.test.ts +1603 -0
  562. package/src/relay-navigation.test.ts +790 -0
  563. package/src/relay-session.test.ts +1375 -0
  564. package/src/relay-state.test.ts +729 -0
  565. package/src/relay-state.ts +561 -0
  566. package/src/replay-ai-index.test.ts +278 -0
  567. package/src/replay-ai-index.ts +872 -0
  568. package/src/replay-cli.test.ts +325 -0
  569. package/src/replay-eval.ts +1039 -0
  570. package/src/replay-handoff.test.ts +163 -0
  571. package/src/replay-handoff.ts +109 -0
  572. package/src/replay-workflow-compiler.test.ts +250 -0
  573. package/src/replay-workflow-compiler.ts +700 -0
  574. package/src/resource.md +409 -0
  575. package/src/rrweb-recording-relay.test.ts +99 -0
  576. package/src/rrweb-recording-relay.ts +400 -0
  577. package/src/rrweb-recording.ts +322 -0
  578. package/src/scoped-fs.test.ts +80 -0
  579. package/src/scoped-fs.ts +420 -0
  580. package/src/skill-stub.test.ts +24 -0
  581. package/src/skill.md +1413 -0
  582. package/src/snapshot-tools.test.ts +1017 -0
  583. package/src/snapshots/hacker-news-accessibility-full.md +81 -0
  584. package/src/snapshots/hacker-news-accessibility-interactive.md +43 -0
  585. package/src/snapshots/page-markdown-output.txt +16 -0
  586. package/src/snapshots/shadcn-ui-accessibility-full.md +201 -0
  587. package/src/snapshots/shadcn-ui-accessibility-interactive.md +109 -0
  588. package/src/start-relay-server.ts +69 -0
  589. package/src/styles-examples.ts +84 -0
  590. package/src/styles.ts +343 -0
  591. package/src/tabwright-agent-skill.test.ts +61 -0
  592. package/src/tabwright-agent-skill.ts +105 -0
  593. package/src/test-declarations.ts +13 -0
  594. package/src/test-utils.test.ts +70 -0
  595. package/src/test-utils.ts +566 -0
  596. package/src/utils.ts +78 -0
  597. package/src/wait-for-page-load.ts +198 -0
  598. package/src/workflow-capability.ts +417 -0
  599. package/src/workflow-script-flow.test.ts +388 -0
@@ -0,0 +1,1039 @@
1
+ import fs from 'node:fs'
2
+ import http from 'node:http'
3
+ import path from 'node:path'
4
+ import util from 'node:util'
5
+ import { chromium, type Browser, type Page } from '@xmorse/playwright-core'
6
+ import pc from 'picocolors'
7
+ import { prepareCapabilityRun, runCapabilityWithExecutor, type CapabilityExecutor } from './capability-runner.js'
8
+ import { createReplayAiIndexFromRecording } from './replay-ai-index.js'
9
+ import { compileReplayWorkflow } from './replay-workflow-compiler.js'
10
+ import type { ExecuteResult } from './executor.js'
11
+ import type { RrwebEvent } from './protocol.js'
12
+ import { getTabwrightUserDataDir } from './product-paths.js'
13
+
14
+ type ReplayEvalExpectedStatus = 'completed' | 'needs_ai' | 'compile_failed'
15
+
16
+ type ReplayEvalCase = {
17
+ id: string
18
+ title: string
19
+ description: string
20
+ editLabel: string
21
+ addLabel: string
22
+ submitLabel: string
23
+ okLabel: string
24
+ demonstratedValue: string
25
+ runValue: string
26
+ initialValues?: string[]
27
+ alreadyEditing?: boolean
28
+ draftAction?: 'restart' | 'continue'
29
+ brokenAdd?: boolean
30
+ unsupported?: boolean
31
+ addAnnotation?: boolean
32
+ deleteAnnotation?: boolean
33
+ expectedStatus: ReplayEvalExpectedStatus
34
+ expectedReasonIncludes?: string
35
+ expectedAnnotationCount?: number
36
+ }
37
+
38
+ export type ReplayEvalCaseResult = {
39
+ id: string
40
+ title: string
41
+ status: 'passed' | 'failed'
42
+ durationMs: number
43
+ replayId: string
44
+ phases: Array<'recording' | 'index' | 'compile' | 'run' | 'verify'>
45
+ index?: {
46
+ actionCount: number
47
+ fieldCount: number
48
+ annotationCount: number
49
+ }
50
+ compile?: {
51
+ actionKind: string
52
+ confidence: string
53
+ demonstratedValue?: string
54
+ }
55
+ run?: {
56
+ status?: string
57
+ value?: string
58
+ reason?: string
59
+ completed?: number
60
+ total?: number
61
+ }
62
+ error?: string
63
+ }
64
+
65
+ export type ReplayEvalReport = {
66
+ generatedAt: string
67
+ durationMs: number
68
+ passed: number
69
+ failed: number
70
+ total: number
71
+ results: ReplayEvalCaseResult[]
72
+ artifactsDir: string
73
+ }
74
+
75
+ export type RunReplayEvalOptions = {
76
+ caseId?: string
77
+ reportPath?: string
78
+ keepArtifacts?: boolean
79
+ headed?: boolean
80
+ }
81
+
82
+ type ExampleCaseState = {
83
+ values: string[]
84
+ }
85
+
86
+ type ExampleServer = {
87
+ baseUrl: string
88
+ close: () => Promise<void>
89
+ }
90
+
91
+ type AsyncFunctionConstructor = new (...args: string[]) => (...args: unknown[]) => Promise<unknown>
92
+
93
+ const REPLAY_EVAL_CASES: ReplayEvalCase[] = [
94
+ {
95
+ id: 'zh-list-append',
96
+ title: 'Chinese admin list append',
97
+ description: '编辑 -> Add entry -> 填值 -> 提交 -> OK',
98
+ editLabel: '编辑',
99
+ addLabel: 'Add entry',
100
+ submitLabel: '提交',
101
+ okLabel: 'OK',
102
+ demonstratedValue: 'test2',
103
+ runValue: 'test3',
104
+ expectedStatus: 'completed',
105
+ },
106
+ {
107
+ id: 'en-list-append',
108
+ title: 'English list append',
109
+ description: 'Edit -> Add entry -> fill -> Submit -> OK',
110
+ editLabel: 'Edit',
111
+ addLabel: 'Add entry',
112
+ submitLabel: 'Submit',
113
+ okLabel: 'OK',
114
+ demonstratedValue: 'alpha',
115
+ runValue: 'beta',
116
+ expectedStatus: 'completed',
117
+ },
118
+ {
119
+ id: 'cn-add-confirm',
120
+ title: 'Chinese add and confirm labels',
121
+ description: '新增/确定 button labels should still compile and run.',
122
+ editLabel: '编辑',
123
+ addLabel: '新增',
124
+ submitLabel: '提交',
125
+ okLabel: '确定',
126
+ demonstratedValue: '中文值1',
127
+ runValue: '中文值2',
128
+ expectedStatus: 'completed',
129
+ },
130
+ {
131
+ id: 'already-editing',
132
+ title: 'Already editing page',
133
+ description: 'Generated script should continue from an already-editing page.',
134
+ editLabel: '编辑',
135
+ addLabel: 'Add entry',
136
+ submitLabel: '提交',
137
+ okLabel: 'OK',
138
+ demonstratedValue: 'inline-demo',
139
+ runValue: 'inline-run',
140
+ alreadyEditing: true,
141
+ expectedStatus: 'completed',
142
+ },
143
+ {
144
+ id: 'draft-restart',
145
+ title: 'Draft dialog restart',
146
+ description: 'Replay clicked 重新编辑, so generated script should restart the draft.',
147
+ editLabel: '编辑',
148
+ addLabel: 'Add entry',
149
+ submitLabel: '提交',
150
+ okLabel: 'OK',
151
+ demonstratedValue: 'restart-demo',
152
+ runValue: 'restart-run',
153
+ draftAction: 'restart',
154
+ expectedStatus: 'completed',
155
+ },
156
+ {
157
+ id: 'draft-continue',
158
+ title: 'Draft dialog continue',
159
+ description: 'Replay clicked 继续编辑, so generated script should continue the draft.',
160
+ editLabel: '编辑',
161
+ addLabel: 'Add entry',
162
+ submitLabel: '提交',
163
+ okLabel: 'OK',
164
+ demonstratedValue: 'continue-demo',
165
+ runValue: 'continue-run',
166
+ draftAction: 'continue',
167
+ expectedStatus: 'completed',
168
+ },
169
+ {
170
+ id: 'duplicate-short-circuit',
171
+ title: 'Duplicate value short-circuit',
172
+ description: 'If the target value is already visible, script should return completed without editing.',
173
+ editLabel: '编辑',
174
+ addLabel: 'Add entry',
175
+ submitLabel: '提交',
176
+ okLabel: 'OK',
177
+ demonstratedValue: 'existing-demo',
178
+ runValue: 'existing-demo',
179
+ initialValues: ['existing-demo'],
180
+ expectedStatus: 'completed',
181
+ },
182
+ {
183
+ id: 'drift-add-fails',
184
+ title: 'Page drift handoff',
185
+ description: 'The add button no longer creates an input, so script should return needs_ai.',
186
+ editLabel: '编辑',
187
+ addLabel: 'Add entry',
188
+ submitLabel: '提交',
189
+ okLabel: 'OK',
190
+ demonstratedValue: 'drift-demo',
191
+ runValue: 'drift-run',
192
+ brokenAdd: true,
193
+ expectedStatus: 'needs_ai',
194
+ expectedReasonIncludes: 'Add entry did not create a new list input',
195
+ },
196
+ {
197
+ id: 'annotation-delete-index',
198
+ title: 'Deleted annotation index',
199
+ description: 'A deleted annotation should not reach AI index output.',
200
+ editLabel: '编辑',
201
+ addLabel: 'Add entry',
202
+ submitLabel: '提交',
203
+ okLabel: 'OK',
204
+ demonstratedValue: 'annotation-demo',
205
+ runValue: 'annotation-run',
206
+ addAnnotation: true,
207
+ deleteAnnotation: true,
208
+ expectedStatus: 'completed',
209
+ expectedAnnotationCount: 0,
210
+ },
211
+ {
212
+ id: 'unsupported-click-only',
213
+ title: 'Unsupported click-only replay',
214
+ description: 'Compiler should fail clearly instead of generating a fake workflow.',
215
+ editLabel: '编辑',
216
+ addLabel: 'Add entry',
217
+ submitLabel: '提交',
218
+ okLabel: 'OK',
219
+ demonstratedValue: '',
220
+ runValue: '',
221
+ unsupported: true,
222
+ expectedStatus: 'compile_failed',
223
+ },
224
+ ]
225
+
226
+ function ensureDir(dir: string): void {
227
+ fs.mkdirSync(dir, { recursive: true })
228
+ }
229
+
230
+ function createLocalTempDir(prefix: string): string {
231
+ const root = path.join(process.cwd(), 'tmp')
232
+ ensureDir(root)
233
+ return fs.mkdtempSync(path.join(root, prefix))
234
+ }
235
+
236
+ function writeJson(filePath: string, value: unknown): void {
237
+ ensureDir(path.dirname(filePath))
238
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`)
239
+ }
240
+
241
+ function normalizeError(error: unknown): string {
242
+ return error instanceof Error ? error.message : String(error)
243
+ }
244
+
245
+ function textNode(id: number, textContent: string): Record<string, unknown> {
246
+ return { id, type: 3, textContent }
247
+ }
248
+
249
+ function elementNode(options: {
250
+ id: number
251
+ tagName: string
252
+ attributes?: Record<string, string>
253
+ childNodes?: Array<Record<string, unknown>>
254
+ }): Record<string, unknown> {
255
+ return {
256
+ id: options.id,
257
+ type: 2,
258
+ tagName: options.tagName,
259
+ attributes: options.attributes || {},
260
+ childNodes: options.childNodes || [],
261
+ }
262
+ }
263
+
264
+ function clickEvent(options: { timestamp: number; id: number }): RrwebEvent {
265
+ return {
266
+ type: 3,
267
+ timestamp: options.timestamp,
268
+ data: {
269
+ source: 2,
270
+ type: 2,
271
+ id: options.id,
272
+ x: 10,
273
+ y: 10,
274
+ pointerType: 0,
275
+ },
276
+ }
277
+ }
278
+
279
+ function inputEvent(options: { timestamp: number; id: number; text: string }): RrwebEvent {
280
+ return {
281
+ type: 3,
282
+ timestamp: options.timestamp,
283
+ data: {
284
+ source: 5,
285
+ id: options.id,
286
+ text: options.text,
287
+ isChecked: false,
288
+ },
289
+ }
290
+ }
291
+
292
+ function annotationEvent(options: { timestamp: number; id: string; text: string }): RrwebEvent {
293
+ return {
294
+ type: 5,
295
+ timestamp: options.timestamp,
296
+ data: {
297
+ tag: 'playwriter.annotation',
298
+ payload: {
299
+ schemaVersion: 1,
300
+ id: options.id,
301
+ text: options.text,
302
+ timestamp: options.timestamp,
303
+ target: {
304
+ tagName: 'textarea',
305
+ label: 'List value',
306
+ selectorHints: ['textarea.designer-input'],
307
+ rect: { x: 10, y: 20, width: 240, height: 32 },
308
+ },
309
+ },
310
+ },
311
+ }
312
+ }
313
+
314
+ function annotationDeleteEvent(options: { timestamp: number; id: string }): RrwebEvent {
315
+ return {
316
+ type: 5,
317
+ timestamp: options.timestamp,
318
+ data: {
319
+ tag: 'playwriter.annotation.delete',
320
+ payload: {
321
+ schemaVersion: 1,
322
+ id: options.id,
323
+ timestamp: options.timestamp,
324
+ },
325
+ },
326
+ }
327
+ }
328
+
329
+ function createReplayEvents(testCase: ReplayEvalCase): RrwebEvent[] {
330
+ const nodes = [
331
+ elementNode({ id: 10, tagName: 'button', childNodes: [textNode(11, testCase.editLabel)] }),
332
+ elementNode({
333
+ id: 12,
334
+ tagName: 'button',
335
+ attributes: { class: 'designer-btn designer-btn-dangerous' },
336
+ childNodes: [textNode(13, testCase.draftAction === 'restart' ? '重新编辑' : 'Restart')],
337
+ }),
338
+ elementNode({
339
+ id: 14,
340
+ tagName: 'button',
341
+ attributes: { class: 'designer-btn designer-btn-primary' },
342
+ childNodes: [textNode(15, testCase.draftAction === 'continue' ? '继续编辑' : 'Continue')],
343
+ }),
344
+ elementNode({
345
+ id: 20,
346
+ tagName: 'button',
347
+ attributes: { class: 'designer-formily-array-base-addition' },
348
+ childNodes: [textNode(21, testCase.addLabel)],
349
+ }),
350
+ elementNode({ id: 30, tagName: 'textarea', attributes: { class: 'designer-input' } }),
351
+ elementNode({ id: 40, tagName: 'button', childNodes: [textNode(41, testCase.submitLabel)] }),
352
+ elementNode({
353
+ id: 50,
354
+ tagName: 'button',
355
+ attributes: { class: 'designer-btn designer-btn-primary' },
356
+ childNodes: [textNode(51, testCase.okLabel)],
357
+ }),
358
+ ]
359
+ const events: RrwebEvent[] = [
360
+ {
361
+ type: 2,
362
+ timestamp: 1000,
363
+ data: {
364
+ node: {
365
+ id: 1,
366
+ type: 0,
367
+ childNodes: [
368
+ {
369
+ id: 2,
370
+ type: 1,
371
+ childNodes: [
372
+ elementNode({
373
+ id: 3,
374
+ tagName: 'body',
375
+ childNodes: nodes,
376
+ }),
377
+ ],
378
+ },
379
+ ],
380
+ },
381
+ },
382
+ },
383
+ ]
384
+ if (testCase.unsupported) {
385
+ events.push(clickEvent({ timestamp: 1100, id: 10 }))
386
+ return events
387
+ }
388
+ if (!testCase.alreadyEditing) {
389
+ events.push(clickEvent({ timestamp: 1100, id: 10 }))
390
+ }
391
+ if (testCase.draftAction === 'restart') {
392
+ events.push(clickEvent({ timestamp: 1200, id: 12 }))
393
+ }
394
+ if (testCase.draftAction === 'continue') {
395
+ events.push(clickEvent({ timestamp: 1200, id: 14 }))
396
+ }
397
+ events.push(clickEvent({ timestamp: 1300, id: 20 }))
398
+ events.push(inputEvent({ timestamp: 1400, id: 30, text: testCase.demonstratedValue }))
399
+ if (testCase.addAnnotation) {
400
+ events.push(annotationEvent({ timestamp: 1450, id: `${testCase.id}-annotation`, text: '重点填写这个列表项' }))
401
+ }
402
+ if (testCase.deleteAnnotation) {
403
+ events.push(annotationDeleteEvent({ timestamp: 1460, id: `${testCase.id}-annotation` }))
404
+ }
405
+ events.push(clickEvent({ timestamp: 1500, id: 40 }))
406
+ events.push(clickEvent({ timestamp: 1600, id: 50 }))
407
+ return events
408
+ }
409
+
410
+ function htmlEscape(value: string): string {
411
+ return value
412
+ .replace(/&/g, '&amp;')
413
+ .replace(/</g, '&lt;')
414
+ .replace(/>/g, '&gt;')
415
+ .replace(/"/g, '&quot;')
416
+ .replace(/'/g, '&#39;')
417
+ }
418
+
419
+ function buildExampleHtml(testCase: ReplayEvalCase, state: ExampleCaseState): string {
420
+ const config = {
421
+ id: testCase.id,
422
+ labels: {
423
+ edit: testCase.editLabel,
424
+ add: testCase.addLabel,
425
+ submit: testCase.submitLabel,
426
+ ok: testCase.okLabel,
427
+ restart: testCase.draftAction === 'restart' ? '重新编辑' : 'Restart',
428
+ continue: testCase.draftAction === 'continue' ? '继续编辑' : 'Continue',
429
+ },
430
+ initialValues: state.values,
431
+ alreadyEditing: Boolean(testCase.alreadyEditing),
432
+ draftAction: testCase.draftAction || null,
433
+ brokenAdd: Boolean(testCase.brokenAdd),
434
+ }
435
+ return `<!doctype html>
436
+ <html>
437
+ <head>
438
+ <meta charset="utf-8" />
439
+ <title>${htmlEscape(testCase.title)}</title>
440
+ <style>
441
+ body { font-family: system-ui, sans-serif; padding: 24px; color: #172033; }
442
+ button { margin: 6px; padding: 7px 12px; border: 1px solid #cbd5e1; border-radius: 6px; background: white; cursor: pointer; }
443
+ textarea { display: block; width: 320px; min-height: 36px; margin: 8px 0; border: 1px solid #cbd5e1; border-radius: 6px; padding: 7px; }
444
+ .designer-modal { position: fixed; inset: auto 24px 24px auto; width: 360px; padding: 16px; border: 1px solid #cbd5e1; border-radius: 8px; background: white; box-shadow: 0 18px 48px rgba(15,23,42,.18); }
445
+ .readonly-value { padding: 4px 0; }
446
+ </style>
447
+ </head>
448
+ <body>
449
+ <h1>${htmlEscape(testCase.title)}</h1>
450
+ <div id="app"></div>
451
+ <script>
452
+ const config = ${JSON.stringify(config)};
453
+ const app = document.getElementById('app');
454
+ const state = {
455
+ values: [...config.initialValues],
456
+ editing: config.alreadyEditing,
457
+ draftShown: false,
458
+ };
459
+ function renderReadOnly() {
460
+ app.innerHTML = '<button id="edit">' + config.labels.edit + '</button><div id="values"></div>';
461
+ const values = document.getElementById('values');
462
+ state.values.forEach((value) => {
463
+ const item = document.createElement('div');
464
+ item.className = 'readonly-value';
465
+ item.textContent = value;
466
+ values.appendChild(item);
467
+ });
468
+ document.getElementById('edit').addEventListener('click', () => {
469
+ if (config.draftAction && !state.draftShown) {
470
+ state.draftShown = true;
471
+ renderDraftDialog();
472
+ return;
473
+ }
474
+ state.editing = true;
475
+ render();
476
+ });
477
+ }
478
+ function renderDraftDialog() {
479
+ renderReadOnly();
480
+ const modal = document.createElement('div');
481
+ modal.className = 'designer-modal';
482
+ modal.innerHTML = '<p>继续编辑上次修改 / Continue previous edit draft</p><button class="designer-btn designer-btn-dangerous" id="restart">' + config.labels.restart + '</button><button class="designer-btn designer-btn-primary" id="continue">' + config.labels.continue + '</button>';
483
+ document.body.appendChild(modal);
484
+ document.getElementById('restart').addEventListener('click', () => {
485
+ modal.remove();
486
+ state.values = [];
487
+ state.editing = true;
488
+ render();
489
+ });
490
+ document.getElementById('continue').addEventListener('click', () => {
491
+ modal.remove();
492
+ state.editing = true;
493
+ render();
494
+ });
495
+ }
496
+ function renderEditor() {
497
+ app.innerHTML = '<div id="editor"></div><button class="designer-formily-array-base-addition" id="add">' + config.labels.add + '</button><button id="submit">' + config.labels.submit + '</button>';
498
+ const editor = document.getElementById('editor');
499
+ state.values.forEach((value, index) => {
500
+ const input = document.createElement('textarea');
501
+ input.className = 'designer-input';
502
+ input.value = value;
503
+ input.addEventListener('input', () => {
504
+ state.values[index] = input.value;
505
+ });
506
+ editor.appendChild(input);
507
+ });
508
+ document.getElementById('add').addEventListener('click', () => {
509
+ if (!config.brokenAdd) {
510
+ state.values.push('');
511
+ }
512
+ render();
513
+ });
514
+ document.getElementById('submit').addEventListener('click', () => {
515
+ renderConfirm();
516
+ });
517
+ }
518
+ function renderConfirm() {
519
+ const modal = document.createElement('div');
520
+ modal.className = 'designer-modal';
521
+ modal.innerHTML = '<p>Confirm values: ' + state.values.join(', ') + '</p><button class="designer-btn designer-btn-primary" id="ok">' + config.labels.ok + '</button>';
522
+ document.body.appendChild(modal);
523
+ document.getElementById('ok').addEventListener('click', async () => {
524
+ await fetch('/enhancedConfigs/update', {
525
+ method: 'POST',
526
+ headers: { 'content-type': 'application/json' },
527
+ body: JSON.stringify({ caseId: config.id, values: state.values }),
528
+ });
529
+ await fetch('/enhancedConfigs/publish', { method: 'POST' });
530
+ await fetch('/enhancedConfigs/detail?id=' + encodeURIComponent(config.id));
531
+ modal.remove();
532
+ state.editing = false;
533
+ render();
534
+ });
535
+ }
536
+ function render() {
537
+ if (state.editing) {
538
+ renderEditor();
539
+ return;
540
+ }
541
+ renderReadOnly();
542
+ }
543
+ render();
544
+ </script>
545
+ </body>
546
+ </html>`
547
+ }
548
+
549
+ function requestBody(request: http.IncomingMessage): Promise<string> {
550
+ return new Promise((resolve, reject) => {
551
+ const chunks: Buffer[] = []
552
+ request.on('data', (chunk: Buffer) => {
553
+ chunks.push(chunk)
554
+ })
555
+ request.on('end', () => {
556
+ resolve(Buffer.concat(chunks).toString('utf-8'))
557
+ })
558
+ request.on('error', (error) => {
559
+ reject(error)
560
+ })
561
+ })
562
+ }
563
+
564
+ async function startExampleServer(cases: ReplayEvalCase[]): Promise<ExampleServer> {
565
+ const casesById = new Map(
566
+ cases.map((testCase) => {
567
+ return [testCase.id, testCase] as const
568
+ }),
569
+ )
570
+ const states: Map<string, ExampleCaseState> = new Map(
571
+ cases.map((testCase) => {
572
+ const state: ExampleCaseState = { values: [...(testCase.initialValues || [])] }
573
+ return [testCase.id, state]
574
+ }),
575
+ )
576
+ const server = http.createServer((request, response) => {
577
+ const host = request.headers.host || 'localhost'
578
+ const url = new URL(request.url || '/', `http://${host}`)
579
+ const sendJson = (statusCode: number, value: unknown): void => {
580
+ response.writeHead(statusCode, { 'content-type': 'application/json' })
581
+ response.end(JSON.stringify(value))
582
+ }
583
+ if (request.method === 'GET' && url.pathname.startsWith('/case/')) {
584
+ const caseId = decodeURIComponent(url.pathname.slice('/case/'.length))
585
+ const testCase = casesById.get(caseId)
586
+ const state = states.get(caseId)
587
+ if (!testCase || !state) {
588
+ response.writeHead(404)
589
+ response.end('Not found')
590
+ return
591
+ }
592
+ response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
593
+ response.end(buildExampleHtml(testCase, state))
594
+ return
595
+ }
596
+ if (request.method === 'POST' && url.pathname === '/enhancedConfigs/update') {
597
+ requestBody(request)
598
+ .then((body) => {
599
+ const parsed = JSON.parse(body || '{}') as { caseId?: unknown; values?: unknown }
600
+ const caseId = typeof parsed.caseId === 'string' ? parsed.caseId : ''
601
+ const state = states.get(caseId)
602
+ if (state && Array.isArray(parsed.values)) {
603
+ state.values = parsed.values.map((value) => {
604
+ return String(value)
605
+ })
606
+ }
607
+ sendJson(200, { code: 0, data: true })
608
+ })
609
+ .catch((error: unknown) => {
610
+ sendJson(500, { code: 1, message: normalizeError(error) })
611
+ })
612
+ return
613
+ }
614
+ if (request.method === 'POST' && url.pathname === '/enhancedConfigs/publish') {
615
+ sendJson(200, { code: 0, data: true })
616
+ return
617
+ }
618
+ if (request.method === 'GET' && url.pathname === '/enhancedConfigs/detail') {
619
+ const caseId = url.searchParams.get('id') || ''
620
+ const state = states.get(caseId)
621
+ sendJson(200, {
622
+ code: 0,
623
+ data: {
624
+ config: {
625
+ value: JSON.stringify(state?.values || []),
626
+ },
627
+ },
628
+ })
629
+ return
630
+ }
631
+ response.writeHead(404)
632
+ response.end('Not found')
633
+ })
634
+
635
+ await new Promise<void>((resolve, reject) => {
636
+ server.on('error', reject)
637
+ server.listen(0, '127.0.0.1', () => {
638
+ resolve()
639
+ })
640
+ })
641
+ const address = server.address()
642
+ if (!address || typeof address === 'string') {
643
+ throw new Error('Replay eval server did not expose a TCP port')
644
+ }
645
+ return {
646
+ baseUrl: `http://127.0.0.1:${address.port}`,
647
+ close: async () => {
648
+ await new Promise<void>((resolve, reject) => {
649
+ server.close((error) => {
650
+ if (error) {
651
+ reject(error)
652
+ return
653
+ }
654
+ resolve()
655
+ })
656
+ })
657
+ },
658
+ }
659
+ }
660
+
661
+ function getChromeExecutable(): string | undefined {
662
+ const candidates = [
663
+ process.env.TABWRIGHT_TEST_CHROME || process.env.PLAYWRITER_TEST_CHROME,
664
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
665
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
666
+ '/usr/bin/google-chrome',
667
+ '/usr/bin/google-chrome-stable',
668
+ '/usr/bin/chromium',
669
+ '/usr/bin/chromium-browser',
670
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
671
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
672
+ ].filter((candidate): candidate is string => {
673
+ return Boolean(candidate)
674
+ })
675
+ return candidates.find((candidate) => {
676
+ return fs.existsSync(candidate)
677
+ })
678
+ }
679
+
680
+ async function launchEvalBrowser(options: { headed?: boolean }): Promise<Browser> {
681
+ const executablePath = getChromeExecutable()
682
+ return await chromium.launch({
683
+ ...(executablePath ? { executablePath } : { channel: 'chromium' }),
684
+ headless: !options.headed,
685
+ })
686
+ }
687
+
688
+ function formatOutput(value: unknown): string {
689
+ return `[return value] ${util.inspect(value, {
690
+ depth: 5,
691
+ colors: false,
692
+ maxArrayLength: 80,
693
+ maxStringLength: 2000,
694
+ breakLength: 100,
695
+ })}`
696
+ }
697
+
698
+ class PageEvalExecutor implements CapabilityExecutor {
699
+ constructor(private page: Page) {}
700
+
701
+ async execute(code: string, timeout = 15000): Promise<ExecuteResult> {
702
+ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor as AsyncFunctionConstructor
703
+ const runner = new AsyncFunction(
704
+ 'page',
705
+ 'snapshot',
706
+ 'console',
707
+ 'setTimeout',
708
+ 'clearTimeout',
709
+ 'URL',
710
+ 'URLSearchParams',
711
+ 'fetch',
712
+ 'Buffer',
713
+ code,
714
+ )
715
+ const snapshot = async (): Promise<string> => {
716
+ return await this.page.locator('body').innerText({ timeout: 2000 }).catch((error: unknown) => {
717
+ return `snapshot failed: ${normalizeError(error)}`
718
+ })
719
+ }
720
+ try {
721
+ const structuredResult = await Promise.race([
722
+ runner(this.page, snapshot, console, setTimeout, clearTimeout, URL, URLSearchParams, fetch, Buffer),
723
+ new Promise((_, reject) => {
724
+ setTimeout(() => {
725
+ reject(new Error(`Replay eval execution timed out after ${timeout}ms`))
726
+ }, timeout)
727
+ }),
728
+ ])
729
+ return {
730
+ text: formatOutput(structuredResult),
731
+ images: [],
732
+ screenshots: [],
733
+ isError: false,
734
+ structuredResult: JSON.parse(JSON.stringify(structuredResult)),
735
+ }
736
+ } catch (error: unknown) {
737
+ return {
738
+ text: normalizeError(error),
739
+ images: [],
740
+ screenshots: [],
741
+ isError: true,
742
+ }
743
+ }
744
+ }
745
+ }
746
+
747
+ function writeRecording(options: {
748
+ home: string
749
+ replayId: string
750
+ url: string
751
+ events: RrwebEvent[]
752
+ }): void {
753
+ const recordingsDir = path.join(getTabwrightUserDataDir({ homeDir: options.home }), 'rrweb-recordings')
754
+ const recordingPath = path.join(recordingsDir, `${options.replayId}.json`)
755
+ writeJson(recordingPath, options.events)
756
+ writeJson(path.join(recordingsDir, 'index.json'), [
757
+ {
758
+ id: options.replayId,
759
+ path: recordingPath,
760
+ startedAt: 1000,
761
+ savedAt: 2000,
762
+ duration: 1000,
763
+ size: fs.statSync(recordingPath).size,
764
+ eventCount: options.events.length,
765
+ tabId: 1,
766
+ url: options.url,
767
+ },
768
+ ])
769
+ }
770
+
771
+ function getOutputStatus(output: unknown): string | undefined {
772
+ if (!output || typeof output !== 'object') {
773
+ return undefined
774
+ }
775
+ const candidate = output as { status?: unknown }
776
+ return typeof candidate.status === 'string' ? candidate.status : undefined
777
+ }
778
+
779
+ function outputField(output: unknown, key: string): string | undefined {
780
+ if (!output || typeof output !== 'object') {
781
+ return undefined
782
+ }
783
+ const value = (output as Record<string, unknown>)[key]
784
+ return typeof value === 'string' ? value : undefined
785
+ }
786
+
787
+ function outputNumberField(output: unknown, key: string): number | undefined {
788
+ if (!output || typeof output !== 'object') {
789
+ return undefined
790
+ }
791
+ const value = (output as Record<string, unknown>)[key]
792
+ return typeof value === 'number' ? value : undefined
793
+ }
794
+
795
+ function assertCaseResult(options: { testCase: ReplayEvalCase; output: unknown; annotationCount: number }): void {
796
+ if (options.testCase.expectedAnnotationCount !== undefined && options.annotationCount !== options.testCase.expectedAnnotationCount) {
797
+ throw new Error(`Expected ${options.testCase.expectedAnnotationCount} annotations, got ${options.annotationCount}`)
798
+ }
799
+ const status = getOutputStatus(options.output)
800
+ if (status !== options.testCase.expectedStatus) {
801
+ throw new Error(`Expected run status ${options.testCase.expectedStatus}, got ${status || 'undefined'}`)
802
+ }
803
+ if (options.testCase.expectedReasonIncludes) {
804
+ const reason = outputField(options.output, 'reason') || ''
805
+ const needsAi = options.output && typeof options.output === 'object' ? (options.output as Record<string, unknown>).needsAi : undefined
806
+ const nestedReason = outputField(needsAi, 'reason') || ''
807
+ if (!reason.includes(options.testCase.expectedReasonIncludes) && !nestedReason.includes(options.testCase.expectedReasonIncludes)) {
808
+ throw new Error(`Expected reason to include "${options.testCase.expectedReasonIncludes}", got "${reason || nestedReason}"`)
809
+ }
810
+ }
811
+ }
812
+
813
+ async function runOneEvalCase(options: {
814
+ testCase: ReplayEvalCase
815
+ server: ExampleServer
816
+ browser: Browser
817
+ artifactsDir: string
818
+ }): Promise<ReplayEvalCaseResult> {
819
+ const start = Date.now()
820
+ const phases: ReplayEvalCaseResult['phases'] = []
821
+ const replayId = `eval-${options.testCase.id}`
822
+ const home = path.join(options.artifactsDir, replayId, 'home')
823
+ const cwd = path.join(options.artifactsDir, replayId, 'cwd')
824
+ ensureDir(home)
825
+ ensureDir(cwd)
826
+ const previousHome = process.env.HOME
827
+ const url = `${options.server.baseUrl}/case/${encodeURIComponent(options.testCase.id)}?key=${encodeURIComponent(options.testCase.id)}`
828
+ try {
829
+ process.env.HOME = home
830
+ const events = createReplayEvents(options.testCase)
831
+ writeRecording({ home, replayId, url, events })
832
+ phases.push('recording')
833
+
834
+ const index = createReplayAiIndexFromRecording(replayId)
835
+ phases.push('index')
836
+
837
+ if (options.testCase.expectedStatus === 'compile_failed') {
838
+ try {
839
+ compileReplayWorkflow({ replayId, id: `${options.testCase.id}-capability`, cwd, overwrite: true })
840
+ } catch (error: unknown) {
841
+ phases.push('compile')
842
+ return {
843
+ id: options.testCase.id,
844
+ title: options.testCase.title,
845
+ status: 'passed',
846
+ durationMs: Date.now() - start,
847
+ replayId,
848
+ phases,
849
+ index: {
850
+ actionCount: index.actions.length,
851
+ fieldCount: index.fields.length,
852
+ annotationCount: index.annotations.length,
853
+ },
854
+ error: normalizeError(error),
855
+ }
856
+ }
857
+ throw new Error('Expected replay compiler to fail, but it produced a capability')
858
+ }
859
+
860
+ const compiled = compileReplayWorkflow({ replayId, id: `${options.testCase.id}-capability`, cwd, overwrite: true })
861
+ phases.push('compile')
862
+
863
+ const page = await options.browser.newPage()
864
+ try {
865
+ await page.goto(url)
866
+ const executor = new PageEvalExecutor(page)
867
+ const runResult = await runCapabilityWithExecutor({
868
+ executor,
869
+ id: `${options.testCase.id}-capability`,
870
+ input: { value: options.testCase.runValue },
871
+ cwd,
872
+ force: true,
873
+ confirmation: `${options.testCase.id}-capability`,
874
+ timeout: 20000,
875
+ })
876
+ phases.push('run')
877
+ assertCaseResult({
878
+ testCase: options.testCase,
879
+ output: runResult.output,
880
+ annotationCount: index.annotations.length,
881
+ })
882
+ phases.push('verify')
883
+ return {
884
+ id: options.testCase.id,
885
+ title: options.testCase.title,
886
+ status: 'passed',
887
+ durationMs: Date.now() - start,
888
+ replayId,
889
+ phases,
890
+ index: {
891
+ actionCount: index.actions.length,
892
+ fieldCount: index.fields.length,
893
+ annotationCount: index.annotations.length,
894
+ },
895
+ compile: {
896
+ actionKind: compiled.analysis.actionKind,
897
+ confidence: compiled.analysis.confidence,
898
+ demonstratedValue: compiled.analysis.demonstratedValue,
899
+ },
900
+ run: {
901
+ status: getOutputStatus(runResult.output),
902
+ value: outputField(runResult.output, 'value'),
903
+ reason: outputField(runResult.output, 'reason'),
904
+ completed: outputNumberField(runResult.output, 'completed'),
905
+ total: outputNumberField(runResult.output, 'total'),
906
+ },
907
+ }
908
+ } finally {
909
+ await page.close().catch(() => {})
910
+ }
911
+ } catch (error: unknown) {
912
+ return {
913
+ id: options.testCase.id,
914
+ title: options.testCase.title,
915
+ status: 'failed',
916
+ durationMs: Date.now() - start,
917
+ replayId,
918
+ phases,
919
+ error: normalizeError(error),
920
+ }
921
+ } finally {
922
+ if (previousHome === undefined) {
923
+ delete process.env.HOME
924
+ } else {
925
+ process.env.HOME = previousHome
926
+ }
927
+ }
928
+ }
929
+
930
+ function renderReportHtml(report: ReplayEvalReport): string {
931
+ const rows = report.results
932
+ .map((result) => {
933
+ const statusColor = result.status === 'passed' ? '#15803d' : '#b91c1c'
934
+ return `<tr>
935
+ <td>${htmlEscape(result.id)}</td>
936
+ <td>${htmlEscape(result.title)}</td>
937
+ <td style="color:${statusColor};font-weight:700">${htmlEscape(result.status)}</td>
938
+ <td>${result.durationMs}ms</td>
939
+ <td>${htmlEscape(result.phases.join(' -> '))}</td>
940
+ <td>${htmlEscape(result.run?.status || result.error || '-')}</td>
941
+ </tr>`
942
+ })
943
+ .join('\n')
944
+ return `<!doctype html>
945
+ <html>
946
+ <head>
947
+ <meta charset="utf-8" />
948
+ <title>Tabwright Replay Eval</title>
949
+ <style>
950
+ body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 32px; color: #172033; }
951
+ table { border-collapse: collapse; width: 100%; }
952
+ th, td { border-bottom: 1px solid #e2e8f0; padding: 8px; text-align: left; vertical-align: top; }
953
+ th { background: #f8fafc; font-size: 12px; text-transform: uppercase; color: #64748b; }
954
+ .summary { margin-bottom: 18px; padding: 14px; border: 1px solid #e2e8f0; border-radius: 8px; background: #f8fafc; }
955
+ </style>
956
+ </head>
957
+ <body>
958
+ <h1>Tabwright Replay Eval</h1>
959
+ <div class="summary">
960
+ <strong>${report.passed}/${report.total} passed</strong>
961
+ <span>generated ${htmlEscape(report.generatedAt)}</span>
962
+ <span>duration ${report.durationMs}ms</span>
963
+ <span>artifacts ${htmlEscape(report.artifactsDir)}</span>
964
+ </div>
965
+ <table>
966
+ <thead>
967
+ <tr><th>Case</th><th>Title</th><th>Status</th><th>Duration</th><th>Phases</th><th>Run/Error</th></tr>
968
+ </thead>
969
+ <tbody>${rows}</tbody>
970
+ </table>
971
+ </body>
972
+ </html>`
973
+ }
974
+
975
+ export async function runReplayEval(options: RunReplayEvalOptions = {}): Promise<ReplayEvalReport> {
976
+ const selectedCases = options.caseId
977
+ ? REPLAY_EVAL_CASES.filter((testCase) => {
978
+ return testCase.id === options.caseId
979
+ })
980
+ : REPLAY_EVAL_CASES
981
+ if (selectedCases.length === 0) {
982
+ throw new Error(`Replay eval case not found: ${options.caseId}`)
983
+ }
984
+ const start = Date.now()
985
+ const artifactsDir = createLocalTempDir('replay-eval-')
986
+ const server = await startExampleServer(selectedCases)
987
+ const browser = await launchEvalBrowser({ headed: options.headed })
988
+ try {
989
+ const results: ReplayEvalCaseResult[] = []
990
+ for (const testCase of selectedCases) {
991
+ results.push(
992
+ await runOneEvalCase({
993
+ testCase,
994
+ server,
995
+ browser,
996
+ artifactsDir,
997
+ }),
998
+ )
999
+ }
1000
+ const passed = results.filter((result) => {
1001
+ return result.status === 'passed'
1002
+ }).length
1003
+ const report: ReplayEvalReport = {
1004
+ generatedAt: new Date().toISOString(),
1005
+ durationMs: Date.now() - start,
1006
+ passed,
1007
+ failed: results.length - passed,
1008
+ total: results.length,
1009
+ results,
1010
+ artifactsDir,
1011
+ }
1012
+ if (options.reportPath) {
1013
+ ensureDir(path.dirname(options.reportPath))
1014
+ fs.writeFileSync(options.reportPath, renderReportHtml(report))
1015
+ }
1016
+ if (!options.keepArtifacts) {
1017
+ fs.rmSync(artifactsDir, { recursive: true, force: true })
1018
+ return { ...report, artifactsDir: `${artifactsDir} (removed)` }
1019
+ }
1020
+ return report
1021
+ } finally {
1022
+ await browser.close().catch(() => {})
1023
+ await server.close().catch(() => {})
1024
+ }
1025
+ }
1026
+
1027
+ export function formatReplayEvalReport(report: ReplayEvalReport): string {
1028
+ const lines = [
1029
+ `Replay eval: ${report.passed}/${report.total} passed in ${report.durationMs}ms`,
1030
+ `Artifacts: ${report.artifactsDir}`,
1031
+ '',
1032
+ ...report.results.map((result) => {
1033
+ const status = result.status === 'passed' ? pc.green('PASS') : pc.red('FAIL')
1034
+ const detail = result.error || result.run?.status || ''
1035
+ return `${status} ${result.id} (${result.durationMs}ms) ${detail}`
1036
+ }),
1037
+ ]
1038
+ return lines.join('\n')
1039
+ }