agent-browser 0.1.3 → 0.3.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 (224) hide show
  1. package/AGENTS.md +26 -0
  2. package/README.md +68 -11
  3. package/benchmark/benchmark.ts +521 -0
  4. package/benchmark/run.ts +322 -0
  5. package/bin/agent-browser +0 -0
  6. package/cli/Cargo.lock +114 -0
  7. package/cli/Cargo.toml +17 -0
  8. package/cli/src/main.rs +332 -0
  9. package/cli/target/.rustc_info.json +1 -0
  10. package/cli/target/CACHEDIR.TAG +3 -0
  11. package/cli/target/release/.cargo-lock +0 -0
  12. package/cli/target/release/.fingerprint/agent-browser-5894536b887e2ce7/bin-agent-browser +1 -0
  13. package/cli/target/release/.fingerprint/agent-browser-5894536b887e2ce7/bin-agent-browser.json +1 -0
  14. package/cli/target/release/.fingerprint/agent-browser-5894536b887e2ce7/dep-bin-agent-browser +0 -0
  15. package/cli/target/release/.fingerprint/agent-browser-5894536b887e2ce7/invoked.timestamp +1 -0
  16. package/cli/target/release/.fingerprint/agent-browser-5894536b887e2ce7/output-bin-agent-browser +2 -0
  17. package/cli/target/release/.fingerprint/itoa-653b9192107a1caa/dep-lib-itoa +0 -0
  18. package/cli/target/release/.fingerprint/itoa-653b9192107a1caa/invoked.timestamp +1 -0
  19. package/cli/target/release/.fingerprint/itoa-653b9192107a1caa/lib-itoa +1 -0
  20. package/cli/target/release/.fingerprint/itoa-653b9192107a1caa/lib-itoa.json +1 -0
  21. package/cli/target/release/.fingerprint/libc-0303d277881093f4/build-script-build-script-build +1 -0
  22. package/cli/target/release/.fingerprint/libc-0303d277881093f4/build-script-build-script-build.json +1 -0
  23. package/cli/target/release/.fingerprint/libc-0303d277881093f4/dep-build-script-build-script-build +0 -0
  24. package/cli/target/release/.fingerprint/libc-0303d277881093f4/invoked.timestamp +1 -0
  25. package/cli/target/release/.fingerprint/libc-b8c0d8e35a1980d3/run-build-script-build-script-build +1 -0
  26. package/cli/target/release/.fingerprint/libc-b8c0d8e35a1980d3/run-build-script-build-script-build.json +1 -0
  27. package/cli/target/release/.fingerprint/libc-d843359d3dd4757b/dep-lib-libc +0 -0
  28. package/cli/target/release/.fingerprint/libc-d843359d3dd4757b/invoked.timestamp +1 -0
  29. package/cli/target/release/.fingerprint/libc-d843359d3dd4757b/lib-libc +1 -0
  30. package/cli/target/release/.fingerprint/libc-d843359d3dd4757b/lib-libc.json +1 -0
  31. package/cli/target/release/.fingerprint/memchr-dcaf8011940d18dd/dep-lib-memchr +0 -0
  32. package/cli/target/release/.fingerprint/memchr-dcaf8011940d18dd/invoked.timestamp +1 -0
  33. package/cli/target/release/.fingerprint/memchr-dcaf8011940d18dd/lib-memchr +1 -0
  34. package/cli/target/release/.fingerprint/memchr-dcaf8011940d18dd/lib-memchr.json +1 -0
  35. package/cli/target/release/.fingerprint/proc-macro2-291b57751730d5b3/build-script-build-script-build +1 -0
  36. package/cli/target/release/.fingerprint/proc-macro2-291b57751730d5b3/build-script-build-script-build.json +1 -0
  37. package/cli/target/release/.fingerprint/proc-macro2-291b57751730d5b3/dep-build-script-build-script-build +0 -0
  38. package/cli/target/release/.fingerprint/proc-macro2-291b57751730d5b3/invoked.timestamp +1 -0
  39. package/cli/target/release/.fingerprint/proc-macro2-a753b344a6b4aa98/dep-lib-proc_macro2 +0 -0
  40. package/cli/target/release/.fingerprint/proc-macro2-a753b344a6b4aa98/invoked.timestamp +1 -0
  41. package/cli/target/release/.fingerprint/proc-macro2-a753b344a6b4aa98/lib-proc_macro2 +1 -0
  42. package/cli/target/release/.fingerprint/proc-macro2-a753b344a6b4aa98/lib-proc_macro2.json +1 -0
  43. package/cli/target/release/.fingerprint/proc-macro2-fc2999f6676f03db/run-build-script-build-script-build +1 -0
  44. package/cli/target/release/.fingerprint/proc-macro2-fc2999f6676f03db/run-build-script-build-script-build.json +1 -0
  45. package/cli/target/release/.fingerprint/quote-352ae41707d371c9/run-build-script-build-script-build +1 -0
  46. package/cli/target/release/.fingerprint/quote-352ae41707d371c9/run-build-script-build-script-build.json +1 -0
  47. package/cli/target/release/.fingerprint/quote-7d13be3cbe4f9de4/build-script-build-script-build +1 -0
  48. package/cli/target/release/.fingerprint/quote-7d13be3cbe4f9de4/build-script-build-script-build.json +1 -0
  49. package/cli/target/release/.fingerprint/quote-7d13be3cbe4f9de4/dep-build-script-build-script-build +0 -0
  50. package/cli/target/release/.fingerprint/quote-7d13be3cbe4f9de4/invoked.timestamp +1 -0
  51. package/cli/target/release/.fingerprint/quote-833e6725e0f7d298/dep-lib-quote +0 -0
  52. package/cli/target/release/.fingerprint/quote-833e6725e0f7d298/invoked.timestamp +1 -0
  53. package/cli/target/release/.fingerprint/quote-833e6725e0f7d298/lib-quote +1 -0
  54. package/cli/target/release/.fingerprint/quote-833e6725e0f7d298/lib-quote.json +1 -0
  55. package/cli/target/release/.fingerprint/serde-b8c046c16de48f41/run-build-script-build-script-build +1 -0
  56. package/cli/target/release/.fingerprint/serde-b8c046c16de48f41/run-build-script-build-script-build.json +1 -0
  57. package/cli/target/release/.fingerprint/serde-d35d32ab52b82a81/build-script-build-script-build +1 -0
  58. package/cli/target/release/.fingerprint/serde-d35d32ab52b82a81/build-script-build-script-build.json +1 -0
  59. package/cli/target/release/.fingerprint/serde-d35d32ab52b82a81/dep-build-script-build-script-build +0 -0
  60. package/cli/target/release/.fingerprint/serde-d35d32ab52b82a81/invoked.timestamp +1 -0
  61. package/cli/target/release/.fingerprint/serde-d6fb44202dad3efd/dep-lib-serde +0 -0
  62. package/cli/target/release/.fingerprint/serde-d6fb44202dad3efd/invoked.timestamp +1 -0
  63. package/cli/target/release/.fingerprint/serde-d6fb44202dad3efd/lib-serde +1 -0
  64. package/cli/target/release/.fingerprint/serde-d6fb44202dad3efd/lib-serde.json +1 -0
  65. package/cli/target/release/.fingerprint/serde_core-0f7ba2581c8c0423/dep-lib-serde_core +0 -0
  66. package/cli/target/release/.fingerprint/serde_core-0f7ba2581c8c0423/invoked.timestamp +1 -0
  67. package/cli/target/release/.fingerprint/serde_core-0f7ba2581c8c0423/lib-serde_core +1 -0
  68. package/cli/target/release/.fingerprint/serde_core-0f7ba2581c8c0423/lib-serde_core.json +1 -0
  69. package/cli/target/release/.fingerprint/serde_core-74db491143173930/run-build-script-build-script-build +1 -0
  70. package/cli/target/release/.fingerprint/serde_core-74db491143173930/run-build-script-build-script-build.json +1 -0
  71. package/cli/target/release/.fingerprint/serde_core-f043ae3f4b601577/build-script-build-script-build +1 -0
  72. package/cli/target/release/.fingerprint/serde_core-f043ae3f4b601577/build-script-build-script-build.json +1 -0
  73. package/cli/target/release/.fingerprint/serde_core-f043ae3f4b601577/dep-build-script-build-script-build +0 -0
  74. package/cli/target/release/.fingerprint/serde_core-f043ae3f4b601577/invoked.timestamp +1 -0
  75. package/cli/target/release/.fingerprint/serde_derive-a5d13e0e658ceae3/dep-lib-serde_derive +0 -0
  76. package/cli/target/release/.fingerprint/serde_derive-a5d13e0e658ceae3/invoked.timestamp +1 -0
  77. package/cli/target/release/.fingerprint/serde_derive-a5d13e0e658ceae3/lib-serde_derive +1 -0
  78. package/cli/target/release/.fingerprint/serde_derive-a5d13e0e658ceae3/lib-serde_derive.json +1 -0
  79. package/cli/target/release/.fingerprint/serde_json-a8467019a959068f/run-build-script-build-script-build +1 -0
  80. package/cli/target/release/.fingerprint/serde_json-a8467019a959068f/run-build-script-build-script-build.json +1 -0
  81. package/cli/target/release/.fingerprint/serde_json-bfa3f43b57842d41/build-script-build-script-build +1 -0
  82. package/cli/target/release/.fingerprint/serde_json-bfa3f43b57842d41/build-script-build-script-build.json +1 -0
  83. package/cli/target/release/.fingerprint/serde_json-bfa3f43b57842d41/dep-build-script-build-script-build +0 -0
  84. package/cli/target/release/.fingerprint/serde_json-bfa3f43b57842d41/invoked.timestamp +1 -0
  85. package/cli/target/release/.fingerprint/serde_json-f61651a65bf0eb31/dep-lib-serde_json +0 -0
  86. package/cli/target/release/.fingerprint/serde_json-f61651a65bf0eb31/invoked.timestamp +1 -0
  87. package/cli/target/release/.fingerprint/serde_json-f61651a65bf0eb31/lib-serde_json +1 -0
  88. package/cli/target/release/.fingerprint/serde_json-f61651a65bf0eb31/lib-serde_json.json +1 -0
  89. package/cli/target/release/.fingerprint/syn-6f9a22f8c7f909b0/dep-lib-syn +0 -0
  90. package/cli/target/release/.fingerprint/syn-6f9a22f8c7f909b0/invoked.timestamp +1 -0
  91. package/cli/target/release/.fingerprint/syn-6f9a22f8c7f909b0/lib-syn +1 -0
  92. package/cli/target/release/.fingerprint/syn-6f9a22f8c7f909b0/lib-syn.json +1 -0
  93. package/cli/target/release/.fingerprint/unicode-ident-60c57228d30a23d0/dep-lib-unicode_ident +0 -0
  94. package/cli/target/release/.fingerprint/unicode-ident-60c57228d30a23d0/invoked.timestamp +1 -0
  95. package/cli/target/release/.fingerprint/unicode-ident-60c57228d30a23d0/lib-unicode_ident +1 -0
  96. package/cli/target/release/.fingerprint/unicode-ident-60c57228d30a23d0/lib-unicode_ident.json +1 -0
  97. package/cli/target/release/.fingerprint/zmij-60b0e0e9d7c08f71/run-build-script-build-script-build +1 -0
  98. package/cli/target/release/.fingerprint/zmij-60b0e0e9d7c08f71/run-build-script-build-script-build.json +1 -0
  99. package/cli/target/release/.fingerprint/zmij-9501bcbd6d8b933c/dep-lib-zmij +0 -0
  100. package/cli/target/release/.fingerprint/zmij-9501bcbd6d8b933c/invoked.timestamp +1 -0
  101. package/cli/target/release/.fingerprint/zmij-9501bcbd6d8b933c/lib-zmij +1 -0
  102. package/cli/target/release/.fingerprint/zmij-9501bcbd6d8b933c/lib-zmij.json +1 -0
  103. package/cli/target/release/.fingerprint/zmij-aa602f885104061e/build-script-build-script-build +1 -0
  104. package/cli/target/release/.fingerprint/zmij-aa602f885104061e/build-script-build-script-build.json +1 -0
  105. package/cli/target/release/.fingerprint/zmij-aa602f885104061e/dep-build-script-build-script-build +0 -0
  106. package/cli/target/release/.fingerprint/zmij-aa602f885104061e/invoked.timestamp +1 -0
  107. package/cli/target/release/agent-browser +0 -0
  108. package/cli/target/release/agent-browser.d +1 -0
  109. package/cli/target/release/build/libc-0303d277881093f4/build-script-build +0 -0
  110. package/cli/target/release/build/libc-0303d277881093f4/build_script_build-0303d277881093f4 +0 -0
  111. package/cli/target/release/build/libc-0303d277881093f4/build_script_build-0303d277881093f4.d +5 -0
  112. package/cli/target/release/build/libc-b8c0d8e35a1980d3/invoked.timestamp +1 -0
  113. package/cli/target/release/build/libc-b8c0d8e35a1980d3/output +25 -0
  114. package/cli/target/release/build/libc-b8c0d8e35a1980d3/root-output +1 -0
  115. package/cli/target/release/build/libc-b8c0d8e35a1980d3/stderr +0 -0
  116. package/cli/target/release/build/proc-macro2-291b57751730d5b3/build-script-build +0 -0
  117. package/cli/target/release/build/proc-macro2-291b57751730d5b3/build_script_build-291b57751730d5b3 +0 -0
  118. package/cli/target/release/build/proc-macro2-291b57751730d5b3/build_script_build-291b57751730d5b3.d +5 -0
  119. package/cli/target/release/build/proc-macro2-fc2999f6676f03db/invoked.timestamp +1 -0
  120. package/cli/target/release/build/proc-macro2-fc2999f6676f03db/output +23 -0
  121. package/cli/target/release/build/proc-macro2-fc2999f6676f03db/root-output +1 -0
  122. package/cli/target/release/build/proc-macro2-fc2999f6676f03db/stderr +0 -0
  123. package/cli/target/release/build/quote-352ae41707d371c9/invoked.timestamp +1 -0
  124. package/cli/target/release/build/quote-352ae41707d371c9/output +2 -0
  125. package/cli/target/release/build/quote-352ae41707d371c9/root-output +1 -0
  126. package/cli/target/release/build/quote-352ae41707d371c9/stderr +0 -0
  127. package/cli/target/release/build/quote-7d13be3cbe4f9de4/build-script-build +0 -0
  128. package/cli/target/release/build/quote-7d13be3cbe4f9de4/build_script_build-7d13be3cbe4f9de4 +0 -0
  129. package/cli/target/release/build/quote-7d13be3cbe4f9de4/build_script_build-7d13be3cbe4f9de4.d +5 -0
  130. package/cli/target/release/build/serde-b8c046c16de48f41/invoked.timestamp +1 -0
  131. package/cli/target/release/build/serde-b8c046c16de48f41/out/private.rs +6 -0
  132. package/cli/target/release/build/serde-b8c046c16de48f41/output +13 -0
  133. package/cli/target/release/build/serde-b8c046c16de48f41/root-output +1 -0
  134. package/cli/target/release/build/serde-b8c046c16de48f41/stderr +0 -0
  135. package/cli/target/release/build/serde-d35d32ab52b82a81/build-script-build +0 -0
  136. package/cli/target/release/build/serde-d35d32ab52b82a81/build_script_build-d35d32ab52b82a81 +0 -0
  137. package/cli/target/release/build/serde-d35d32ab52b82a81/build_script_build-d35d32ab52b82a81.d +5 -0
  138. package/cli/target/release/build/serde_core-74db491143173930/invoked.timestamp +1 -0
  139. package/cli/target/release/build/serde_core-74db491143173930/out/private.rs +5 -0
  140. package/cli/target/release/build/serde_core-74db491143173930/output +11 -0
  141. package/cli/target/release/build/serde_core-74db491143173930/root-output +1 -0
  142. package/cli/target/release/build/serde_core-74db491143173930/stderr +0 -0
  143. package/cli/target/release/build/serde_core-f043ae3f4b601577/build-script-build +0 -0
  144. package/cli/target/release/build/serde_core-f043ae3f4b601577/build_script_build-f043ae3f4b601577 +0 -0
  145. package/cli/target/release/build/serde_core-f043ae3f4b601577/build_script_build-f043ae3f4b601577.d +5 -0
  146. package/cli/target/release/build/serde_json-a8467019a959068f/invoked.timestamp +1 -0
  147. package/cli/target/release/build/serde_json-a8467019a959068f/output +3 -0
  148. package/cli/target/release/build/serde_json-a8467019a959068f/root-output +1 -0
  149. package/cli/target/release/build/serde_json-a8467019a959068f/stderr +0 -0
  150. package/cli/target/release/build/serde_json-bfa3f43b57842d41/build-script-build +0 -0
  151. package/cli/target/release/build/serde_json-bfa3f43b57842d41/build_script_build-bfa3f43b57842d41 +0 -0
  152. package/cli/target/release/build/serde_json-bfa3f43b57842d41/build_script_build-bfa3f43b57842d41.d +5 -0
  153. package/cli/target/release/build/zmij-60b0e0e9d7c08f71/invoked.timestamp +1 -0
  154. package/cli/target/release/build/zmij-60b0e0e9d7c08f71/output +3 -0
  155. package/cli/target/release/build/zmij-60b0e0e9d7c08f71/root-output +1 -0
  156. package/cli/target/release/build/zmij-60b0e0e9d7c08f71/stderr +0 -0
  157. package/cli/target/release/build/zmij-aa602f885104061e/build-script-build +0 -0
  158. package/cli/target/release/build/zmij-aa602f885104061e/build_script_build-aa602f885104061e +0 -0
  159. package/cli/target/release/build/zmij-aa602f885104061e/build_script_build-aa602f885104061e.d +5 -0
  160. package/cli/target/release/deps/agent_browser-5894536b887e2ce7 +0 -0
  161. package/cli/target/release/deps/agent_browser-5894536b887e2ce7.d +5 -0
  162. package/cli/target/release/deps/itoa-653b9192107a1caa.d +8 -0
  163. package/cli/target/release/deps/libc-d843359d3dd4757b.d +45 -0
  164. package/cli/target/release/deps/libitoa-653b9192107a1caa.rlib +0 -0
  165. package/cli/target/release/deps/libitoa-653b9192107a1caa.rmeta +0 -0
  166. package/cli/target/release/deps/liblibc-d843359d3dd4757b.rlib +0 -0
  167. package/cli/target/release/deps/liblibc-d843359d3dd4757b.rmeta +0 -0
  168. package/cli/target/release/deps/libmemchr-dcaf8011940d18dd.rlib +0 -0
  169. package/cli/target/release/deps/libmemchr-dcaf8011940d18dd.rmeta +0 -0
  170. package/cli/target/release/deps/libproc_macro2-a753b344a6b4aa98.rlib +0 -0
  171. package/cli/target/release/deps/libproc_macro2-a753b344a6b4aa98.rmeta +0 -0
  172. package/cli/target/release/deps/libquote-833e6725e0f7d298.rlib +0 -0
  173. package/cli/target/release/deps/libquote-833e6725e0f7d298.rmeta +0 -0
  174. package/cli/target/release/deps/libserde-d6fb44202dad3efd.rlib +0 -0
  175. package/cli/target/release/deps/libserde-d6fb44202dad3efd.rmeta +0 -0
  176. package/cli/target/release/deps/libserde_core-0f7ba2581c8c0423.rlib +0 -0
  177. package/cli/target/release/deps/libserde_core-0f7ba2581c8c0423.rmeta +0 -0
  178. package/cli/target/release/deps/libserde_derive-a5d13e0e658ceae3.dylib +0 -0
  179. package/cli/target/release/deps/libserde_json-f61651a65bf0eb31.rlib +0 -0
  180. package/cli/target/release/deps/libserde_json-f61651a65bf0eb31.rmeta +0 -0
  181. package/cli/target/release/deps/libsyn-6f9a22f8c7f909b0.rlib +0 -0
  182. package/cli/target/release/deps/libsyn-6f9a22f8c7f909b0.rmeta +0 -0
  183. package/cli/target/release/deps/libunicode_ident-60c57228d30a23d0.rlib +0 -0
  184. package/cli/target/release/deps/libunicode_ident-60c57228d30a23d0.rmeta +0 -0
  185. package/cli/target/release/deps/libzmij-9501bcbd6d8b933c.rlib +0 -0
  186. package/cli/target/release/deps/libzmij-9501bcbd6d8b933c.rmeta +0 -0
  187. package/cli/target/release/deps/memchr-dcaf8011940d18dd.d +30 -0
  188. package/cli/target/release/deps/proc_macro2-a753b344a6b4aa98.d +17 -0
  189. package/cli/target/release/deps/quote-833e6725e0f7d298.d +13 -0
  190. package/cli/target/release/deps/serde-d6fb44202dad3efd.d +14 -0
  191. package/cli/target/release/deps/serde_core-0f7ba2581c8c0423.d +27 -0
  192. package/cli/target/release/deps/serde_derive-a5d13e0e658ceae3.d +34 -0
  193. package/cli/target/release/deps/serde_json-f61651a65bf0eb31.d +22 -0
  194. package/cli/target/release/deps/syn-6f9a22f8c7f909b0.d +49 -0
  195. package/cli/target/release/deps/unicode_ident-60c57228d30a23d0.d +8 -0
  196. package/cli/target/release/deps/zmij-9501bcbd6d8b933c.d +8 -0
  197. package/dist/actions.d.ts.map +1 -1
  198. package/dist/actions.js +46 -35
  199. package/dist/actions.js.map +1 -1
  200. package/dist/browser.d.ts +30 -1
  201. package/dist/browser.d.ts.map +1 -1
  202. package/dist/browser.js +59 -0
  203. package/dist/browser.js.map +1 -1
  204. package/dist/cli-light.d.ts +11 -0
  205. package/dist/cli-light.d.ts.map +1 -0
  206. package/dist/cli-light.js +409 -0
  207. package/dist/cli-light.js.map +1 -0
  208. package/dist/index.js +13 -8
  209. package/dist/index.js.map +1 -1
  210. package/dist/protocol.d.ts.map +1 -1
  211. package/dist/protocol.js +4 -0
  212. package/dist/protocol.js.map +1 -1
  213. package/dist/snapshot.d.ts +63 -0
  214. package/dist/snapshot.d.ts.map +1 -0
  215. package/dist/snapshot.js +298 -0
  216. package/dist/snapshot.js.map +1 -0
  217. package/package.json +6 -4
  218. package/src/actions.ts +50 -36
  219. package/src/browser.ts +70 -0
  220. package/src/cli-light.ts +457 -0
  221. package/src/index.ts +13 -8
  222. package/src/protocol.ts +4 -0
  223. package/src/snapshot.ts +380 -0
  224. package/tsconfig.json +12 -3
package/AGENTS.md ADDED
@@ -0,0 +1,26 @@
1
+ # AGENTS.md
2
+
3
+ Instructions for AI coding agents working with this codebase.
4
+
5
+ <!-- opensrc:start -->
6
+
7
+ ## Source Code Reference
8
+
9
+ Source code for dependencies is available in `opensrc/` for deeper understanding of implementation details.
10
+
11
+ See `opensrc/sources.json` for the list of available packages and their versions.
12
+
13
+ Use this source code when you need to understand how a package works internally, not just its types/interface.
14
+
15
+ ### Fetching Additional Source Code
16
+
17
+ To fetch source code for a package or repository you need to understand, run:
18
+
19
+ ```bash
20
+ npx opensrc <package> # npm package (e.g., npx opensrc zod)
21
+ npx opensrc pypi:<package> # Python package (e.g., npx opensrc pypi:requests)
22
+ npx opensrc crates:<package> # Rust crate (e.g., npx opensrc crates:serde)
23
+ npx opensrc <owner>/<repo> # GitHub repo (e.g., npx opensrc vercel/ai)
24
+ ```
25
+
26
+ <!-- opensrc:end -->
package/README.md CHANGED
@@ -14,13 +14,22 @@ pnpm build
14
14
 
15
15
  ```bash
16
16
  agent-browser open example.com
17
- agent-browser click "#submit"
18
- agent-browser fill "#email" "test@example.com"
19
- agent-browser get text "h1"
17
+ agent-browser snapshot # Get accessibility tree with refs
18
+ agent-browser click @e2 # Click by ref from snapshot
19
+ agent-browser fill @e3 "test@example.com" # Fill by ref
20
+ agent-browser get text @e1 # Get text by ref
20
21
  agent-browser screenshot page.png
21
22
  agent-browser close
22
23
  ```
23
24
 
25
+ ### Traditional Selectors (also supported)
26
+
27
+ ```bash
28
+ agent-browser click "#submit"
29
+ agent-browser fill "#email" "test@example.com"
30
+ agent-browser find role button click --name "Submit"
31
+ ```
32
+
24
33
  ## Commands
25
34
 
26
35
  ### Core Commands
@@ -48,7 +57,7 @@ agent-browser upload <sel> <files> # Upload files
48
57
  agent-browser download [path] # Wait for download
49
58
  agent-browser screenshot [path] # Take screenshot (--full for full page)
50
59
  agent-browser pdf <path> # Save as PDF
51
- agent-browser snapshot # Accessibility tree (best for AI)
60
+ agent-browser snapshot # Accessibility tree with refs (best for AI)
52
61
  agent-browser eval <js> # Run JavaScript
53
62
  agent-browser close # Close browser
54
63
  ```
@@ -244,19 +253,49 @@ agent-browser session list
244
253
 
245
254
  ## Selectors
246
255
 
256
+ ### Refs (Recommended for AI)
257
+
258
+ Refs provide deterministic element selection from snapshots:
259
+
260
+ ```bash
261
+ # 1. Get snapshot with refs
262
+ agent-browser snapshot
263
+ # Output:
264
+ # - heading "Example Domain" [ref=e1] [level=1]
265
+ # - button "Submit" [ref=e2]
266
+ # - textbox "Email" [ref=e3]
267
+ # - link "Learn more" [ref=e4]
268
+
269
+ # 2. Use refs to interact
270
+ agent-browser click @e2 # Click the button
271
+ agent-browser fill @e3 "test@example.com" # Fill the textbox
272
+ agent-browser get text @e1 # Get heading text
273
+ agent-browser hover @e4 # Hover the link
274
+ ```
275
+
276
+ **Why use refs?**
277
+ - **Deterministic**: Ref points to exact element from snapshot
278
+ - **Fast**: No DOM re-query needed
279
+ - **AI-friendly**: Snapshot + ref workflow is optimal for LLMs
280
+
281
+ ### CSS Selectors
282
+
247
283
  ```bash
248
- # CSS
249
284
  agent-browser click "#id"
250
285
  agent-browser click ".class"
251
286
  agent-browser click "div > button"
287
+ ```
252
288
 
253
- # Text
254
- agent-browser click "text=Submit"
289
+ ### Text & XPath
255
290
 
256
- # XPath
291
+ ```bash
292
+ agent-browser click "text=Submit"
257
293
  agent-browser click "xpath=//button"
294
+ ```
295
+
296
+ ### Semantic Locators
258
297
 
259
- # Semantic (recommended)
298
+ ```bash
260
299
  agent-browser find role button click --name "Submit"
261
300
  agent-browser find label "Email" fill "test@test.com"
262
301
  ```
@@ -267,8 +306,26 @@ Use `--json` for machine-readable output:
267
306
 
268
307
  ```bash
269
308
  agent-browser snapshot --json
270
- agent-browser get text "h1" --json
271
- agent-browser is visible ".modal" --json
309
+ # Returns: {"success":true,"data":{"snapshot":"...","refs":{"e1":{"role":"heading","name":"Title"},...}}}
310
+
311
+ agent-browser get text @e1 --json
312
+ agent-browser is visible @e2 --json
313
+ ```
314
+
315
+ ### Optimal AI Workflow
316
+
317
+ ```bash
318
+ # 1. Navigate and get snapshot
319
+ agent-browser open example.com
320
+ agent-browser snapshot --json # AI parses tree and refs
321
+
322
+ # 2. AI identifies target refs from snapshot
323
+ # 3. Execute actions using refs
324
+ agent-browser click @e2
325
+ agent-browser fill @e3 "input text"
326
+
327
+ # 4. Get new snapshot if page changed
328
+ agent-browser snapshot --json
272
329
  ```
273
330
 
274
331
  ## License
@@ -0,0 +1,521 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Benchmark: agent-browser vs playwright-mcp
4
+ *
5
+ * Measures:
6
+ * - Speed: cold start, navigation, click, snapshot operations
7
+ * - Context usage: output/response size in bytes and estimated tokens
8
+ */
9
+
10
+ import { spawn, execSync, ChildProcess } from 'child_process';
11
+ import * as readline from 'readline';
12
+
13
+ // ============================================================================
14
+ // Configuration
15
+ // ============================================================================
16
+
17
+ const TEST_URL = 'https://example.com';
18
+ const ITERATIONS = 3;
19
+
20
+ interface BenchmarkResult {
21
+ operation: string;
22
+ tool: string;
23
+ timeMs: number;
24
+ outputBytes: number;
25
+ estimatedTokens: number;
26
+ }
27
+
28
+ const results: BenchmarkResult[] = [];
29
+
30
+ // Estimate tokens (~4 chars per token for English text)
31
+ function estimateTokens(text: string): number {
32
+ return Math.ceil(text.length / 4);
33
+ }
34
+
35
+ function formatBytes(bytes: number): string {
36
+ if (bytes < 1024) return `${bytes} B`;
37
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
38
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
39
+ }
40
+
41
+ // ============================================================================
42
+ // Agent-Browser Benchmarks
43
+ // ============================================================================
44
+
45
+ async function runAgentBrowser(args: string[]): Promise<{ timeMs: number; output: string }> {
46
+ const start = performance.now();
47
+
48
+ return new Promise((resolve, reject) => {
49
+ const proc = spawn('node', ['./dist/index.js', '--session', 'benchmark', '--json', ...args], {
50
+ cwd: process.cwd(),
51
+ env: { ...process.env },
52
+ });
53
+
54
+ let output = '';
55
+ let stderr = '';
56
+
57
+ proc.stdout.on('data', (data) => { output += data.toString(); });
58
+ proc.stderr.on('data', (data) => { stderr += data.toString(); });
59
+
60
+ proc.on('close', (code) => {
61
+ const timeMs = performance.now() - start;
62
+ if (code !== 0 && !output.includes('"success"')) {
63
+ reject(new Error(`agent-browser failed: ${stderr || output}`));
64
+ } else {
65
+ resolve({ timeMs, output: output.trim() });
66
+ }
67
+ });
68
+
69
+ proc.on('error', reject);
70
+ });
71
+ }
72
+
73
+ async function benchmarkAgentBrowser(): Promise<void> {
74
+ console.log('\n📦 Benchmarking agent-browser...\n');
75
+
76
+ // Clean up any existing session
77
+ try {
78
+ await runAgentBrowser(['close']);
79
+ } catch {
80
+ // Ignore - session might not exist
81
+ }
82
+
83
+ // Wait a bit for cleanup
84
+ await new Promise(r => setTimeout(r, 500));
85
+
86
+ // Cold start (includes daemon startup + browser launch + navigation)
87
+ console.log(' ⏱️ Cold start (navigate)...');
88
+ const coldStart = await runAgentBrowser(['open', TEST_URL]);
89
+ results.push({
90
+ operation: 'cold_start_navigate',
91
+ tool: 'agent-browser',
92
+ timeMs: coldStart.timeMs,
93
+ outputBytes: coldStart.output.length,
94
+ estimatedTokens: estimateTokens(coldStart.output),
95
+ });
96
+ console.log(` ${coldStart.timeMs.toFixed(0)}ms, ${formatBytes(coldStart.output.length)}`);
97
+
98
+ // Warm operations
99
+ for (let i = 0; i < ITERATIONS; i++) {
100
+ // Navigate (warm)
101
+ console.log(` ⏱️ Navigate (warm, iter ${i + 1})...`);
102
+ const nav = await runAgentBrowser(['open', TEST_URL]);
103
+ results.push({
104
+ operation: 'navigate_warm',
105
+ tool: 'agent-browser',
106
+ timeMs: nav.timeMs,
107
+ outputBytes: nav.output.length,
108
+ estimatedTokens: estimateTokens(nav.output),
109
+ });
110
+ console.log(` ${nav.timeMs.toFixed(0)}ms, ${formatBytes(nav.output.length)}`);
111
+
112
+ // Snapshot
113
+ console.log(` ⏱️ Snapshot (iter ${i + 1})...`);
114
+ const snapshot = await runAgentBrowser(['snapshot']);
115
+ results.push({
116
+ operation: 'snapshot',
117
+ tool: 'agent-browser',
118
+ timeMs: snapshot.timeMs,
119
+ outputBytes: snapshot.output.length,
120
+ estimatedTokens: estimateTokens(snapshot.output),
121
+ });
122
+ console.log(` ${snapshot.timeMs.toFixed(0)}ms, ${formatBytes(snapshot.output.length)}`);
123
+
124
+ // Get title
125
+ console.log(` ⏱️ Get title (iter ${i + 1})...`);
126
+ const title = await runAgentBrowser(['get', 'title']);
127
+ results.push({
128
+ operation: 'get_title',
129
+ tool: 'agent-browser',
130
+ timeMs: title.timeMs,
131
+ outputBytes: title.output.length,
132
+ estimatedTokens: estimateTokens(title.output),
133
+ });
134
+ console.log(` ${title.timeMs.toFixed(0)}ms, ${formatBytes(title.output.length)}`);
135
+
136
+ // Get URL
137
+ console.log(` ⏱️ Get URL (iter ${i + 1})...`);
138
+ const url = await runAgentBrowser(['get', 'url']);
139
+ results.push({
140
+ operation: 'get_url',
141
+ tool: 'agent-browser',
142
+ timeMs: url.timeMs,
143
+ outputBytes: url.output.length,
144
+ estimatedTokens: estimateTokens(url.output),
145
+ });
146
+ console.log(` ${url.timeMs.toFixed(0)}ms, ${formatBytes(url.output.length)}`);
147
+
148
+ // Click (on a link that exists on example.com)
149
+ console.log(` ⏱️ Click link (iter ${i + 1})...`);
150
+ const click = await runAgentBrowser(['click', 'a']);
151
+ results.push({
152
+ operation: 'click',
153
+ tool: 'agent-browser',
154
+ timeMs: click.timeMs,
155
+ outputBytes: click.output.length,
156
+ estimatedTokens: estimateTokens(click.output),
157
+ });
158
+ console.log(` ${click.timeMs.toFixed(0)}ms, ${formatBytes(click.output.length)}`);
159
+
160
+ // Navigate back for next iteration
161
+ await runAgentBrowser(['open', TEST_URL]);
162
+ }
163
+
164
+ // Screenshot
165
+ console.log(' ⏱️ Screenshot...');
166
+ const screenshot = await runAgentBrowser(['screenshot']);
167
+ results.push({
168
+ operation: 'screenshot',
169
+ tool: 'agent-browser',
170
+ timeMs: screenshot.timeMs,
171
+ outputBytes: screenshot.output.length,
172
+ estimatedTokens: estimateTokens(screenshot.output),
173
+ });
174
+ console.log(` ${screenshot.timeMs.toFixed(0)}ms, ${formatBytes(screenshot.output.length)}`);
175
+
176
+ // Close
177
+ console.log(' ⏱️ Close...');
178
+ const close = await runAgentBrowser(['close']);
179
+ results.push({
180
+ operation: 'close',
181
+ tool: 'agent-browser',
182
+ timeMs: close.timeMs,
183
+ outputBytes: close.output.length,
184
+ estimatedTokens: estimateTokens(close.output),
185
+ });
186
+ console.log(` ${close.timeMs.toFixed(0)}ms`);
187
+ }
188
+
189
+ // ============================================================================
190
+ // Playwright-MCP Benchmarks
191
+ // ============================================================================
192
+
193
+ class MCPClient {
194
+ private proc: ChildProcess;
195
+ private rl: readline.Interface;
196
+ private responseBuffer: Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }> = new Map();
197
+ private requestId = 0;
198
+ private ready = false;
199
+
200
+ constructor() {
201
+ this.proc = spawn('node', ['./opensrc/repos/github.com/microsoft/playwright-mcp/cli.js', '--headless'], {
202
+ cwd: process.cwd(),
203
+ stdio: ['pipe', 'pipe', 'pipe'],
204
+ });
205
+
206
+ this.rl = readline.createInterface({ input: this.proc.stdout! });
207
+
208
+ this.rl.on('line', (line) => {
209
+ try {
210
+ const msg = JSON.parse(line);
211
+ if (msg.id !== undefined && this.responseBuffer.has(msg.id)) {
212
+ const handler = this.responseBuffer.get(msg.id)!;
213
+ this.responseBuffer.delete(msg.id);
214
+ handler.resolve(msg);
215
+ }
216
+ } catch {
217
+ // Non-JSON output, ignore
218
+ }
219
+ });
220
+
221
+ this.proc.stderr?.on('data', (data) => {
222
+ // Debug output, ignore in benchmarks
223
+ });
224
+ }
225
+
226
+ async initialize(): Promise<{ timeMs: number; output: string }> {
227
+ const start = performance.now();
228
+
229
+ // Send initialize request
230
+ const initResult = await this.sendRequest('initialize', {
231
+ protocolVersion: '2024-11-05',
232
+ capabilities: {},
233
+ clientInfo: { name: 'benchmark', version: '1.0.0' },
234
+ });
235
+
236
+ // Send initialized notification
237
+ this.sendNotification('notifications/initialized', {});
238
+
239
+ const timeMs = performance.now() - start;
240
+ const output = JSON.stringify(initResult);
241
+
242
+ this.ready = true;
243
+ return { timeMs, output };
244
+ }
245
+
246
+ async callTool(name: string, args: Record<string, unknown>): Promise<{ timeMs: number; output: string }> {
247
+ const start = performance.now();
248
+ const result = await this.sendRequest('tools/call', { name, arguments: args });
249
+ const timeMs = performance.now() - start;
250
+ const output = JSON.stringify(result);
251
+ return { timeMs, output };
252
+ }
253
+
254
+ private sendRequest(method: string, params: Record<string, unknown>): Promise<any> {
255
+ const id = ++this.requestId;
256
+ const request = { jsonrpc: '2.0', id, method, params };
257
+
258
+ return new Promise((resolve, reject) => {
259
+ this.responseBuffer.set(id, { resolve, reject });
260
+ this.proc.stdin!.write(JSON.stringify(request) + '\n');
261
+
262
+ // Timeout after 30s
263
+ setTimeout(() => {
264
+ if (this.responseBuffer.has(id)) {
265
+ this.responseBuffer.delete(id);
266
+ reject(new Error(`Request timeout: ${method}`));
267
+ }
268
+ }, 30000);
269
+ });
270
+ }
271
+
272
+ private sendNotification(method: string, params: Record<string, unknown>): void {
273
+ const notification = { jsonrpc: '2.0', method, params };
274
+ this.proc.stdin!.write(JSON.stringify(notification) + '\n');
275
+ }
276
+
277
+ async close(): Promise<void> {
278
+ this.proc.kill();
279
+ this.rl.close();
280
+ }
281
+ }
282
+
283
+ async function benchmarkPlaywrightMCP(): Promise<void> {
284
+ console.log('\n📦 Benchmarking playwright-mcp...\n');
285
+
286
+ let client: MCPClient | null = null;
287
+
288
+ try {
289
+ // Cold start (includes server startup + initialization)
290
+ console.log(' ⏱️ Cold start (initialize + navigate)...');
291
+ const coldStartBegin = performance.now();
292
+
293
+ client = new MCPClient();
294
+ const init = await client.initialize();
295
+
296
+ // Navigate
297
+ const nav = await client.callTool('browser_navigate', { url: TEST_URL });
298
+
299
+ const coldStartTime = performance.now() - coldStartBegin;
300
+ const coldStartOutput = init.output + nav.output;
301
+
302
+ results.push({
303
+ operation: 'cold_start_navigate',
304
+ tool: 'playwright-mcp',
305
+ timeMs: coldStartTime,
306
+ outputBytes: coldStartOutput.length,
307
+ estimatedTokens: estimateTokens(coldStartOutput),
308
+ });
309
+ console.log(` ${coldStartTime.toFixed(0)}ms, ${formatBytes(coldStartOutput.length)}`);
310
+
311
+ // Warm operations
312
+ for (let i = 0; i < ITERATIONS; i++) {
313
+ // Navigate (warm)
314
+ console.log(` ⏱️ Navigate (warm, iter ${i + 1})...`);
315
+ const navWarm = await client.callTool('browser_navigate', { url: TEST_URL });
316
+ results.push({
317
+ operation: 'navigate_warm',
318
+ tool: 'playwright-mcp',
319
+ timeMs: navWarm.timeMs,
320
+ outputBytes: navWarm.output.length,
321
+ estimatedTokens: estimateTokens(navWarm.output),
322
+ });
323
+ console.log(` ${navWarm.timeMs.toFixed(0)}ms, ${formatBytes(navWarm.output.length)}`);
324
+
325
+ // Snapshot
326
+ console.log(` ⏱️ Snapshot (iter ${i + 1})...`);
327
+ const snapshot = await client.callTool('browser_snapshot', {});
328
+ results.push({
329
+ operation: 'snapshot',
330
+ tool: 'playwright-mcp',
331
+ timeMs: snapshot.timeMs,
332
+ outputBytes: snapshot.output.length,
333
+ estimatedTokens: estimateTokens(snapshot.output),
334
+ });
335
+ console.log(` ${snapshot.timeMs.toFixed(0)}ms, ${formatBytes(snapshot.output.length)}`);
336
+
337
+ // Note: playwright-mcp doesn't have separate get_title/get_url tools
338
+ // Title and URL are included in snapshot, so we'll skip those
339
+
340
+ // Click
341
+ console.log(` ⏱️ Click link (iter ${i + 1})...`);
342
+ // playwright-mcp uses ref from snapshot - we'll use a generic approach
343
+ const click = await client.callTool('browser_click', {
344
+ element: 'More information link',
345
+ ref: 'a' // This might not work exactly the same way
346
+ });
347
+ results.push({
348
+ operation: 'click',
349
+ tool: 'playwright-mcp',
350
+ timeMs: click.timeMs,
351
+ outputBytes: click.output.length,
352
+ estimatedTokens: estimateTokens(click.output),
353
+ });
354
+ console.log(` ${click.timeMs.toFixed(0)}ms, ${formatBytes(click.output.length)}`);
355
+
356
+ // Navigate back
357
+ await client.callTool('browser_navigate', { url: TEST_URL });
358
+ }
359
+
360
+ // Screenshot
361
+ console.log(' ⏱️ Screenshot...');
362
+ const screenshot = await client.callTool('browser_take_screenshot', {});
363
+ results.push({
364
+ operation: 'screenshot',
365
+ tool: 'playwright-mcp',
366
+ timeMs: screenshot.timeMs,
367
+ outputBytes: screenshot.output.length,
368
+ estimatedTokens: estimateTokens(screenshot.output),
369
+ });
370
+ console.log(` ${screenshot.timeMs.toFixed(0)}ms, ${formatBytes(screenshot.output.length)}`);
371
+
372
+ // Close
373
+ console.log(' ⏱️ Close...');
374
+ const closeStart = performance.now();
375
+ const closeResult = await client.callTool('browser_close', {});
376
+ results.push({
377
+ operation: 'close',
378
+ tool: 'playwright-mcp',
379
+ timeMs: closeResult.timeMs,
380
+ outputBytes: closeResult.output.length,
381
+ estimatedTokens: estimateTokens(closeResult.output),
382
+ });
383
+ console.log(` ${closeResult.timeMs.toFixed(0)}ms`);
384
+
385
+ } catch (error) {
386
+ console.error(' ❌ playwright-mcp benchmark failed:', error);
387
+ } finally {
388
+ if (client) {
389
+ await client.close();
390
+ }
391
+ }
392
+ }
393
+
394
+ // ============================================================================
395
+ // Results Summary
396
+ // ============================================================================
397
+
398
+ function printResults(): void {
399
+ console.log('\n' + '='.repeat(80));
400
+ console.log('📊 BENCHMARK RESULTS');
401
+ console.log('='.repeat(80));
402
+
403
+ // Group by operation
404
+ const operations = [...new Set(results.map(r => r.operation))];
405
+
406
+ console.log('\n📈 Speed Comparison (average across iterations):\n');
407
+ console.log('| Operation | agent-browser | playwright-mcp | Difference |');
408
+ console.log('|---------------------|---------------|----------------|------------|');
409
+
410
+ for (const op of operations) {
411
+ const agentResults = results.filter(r => r.operation === op && r.tool === 'agent-browser');
412
+ const mcpResults = results.filter(r => r.operation === op && r.tool === 'playwright-mcp');
413
+
414
+ const agentAvg = agentResults.length > 0
415
+ ? agentResults.reduce((sum, r) => sum + r.timeMs, 0) / agentResults.length
416
+ : null;
417
+ const mcpAvg = mcpResults.length > 0
418
+ ? mcpResults.reduce((sum, r) => sum + r.timeMs, 0) / mcpResults.length
419
+ : null;
420
+
421
+ const agentStr = agentAvg !== null ? `${agentAvg.toFixed(0)}ms`.padEnd(13) : 'N/A'.padEnd(13);
422
+ const mcpStr = mcpAvg !== null ? `${mcpAvg.toFixed(0)}ms`.padEnd(14) : 'N/A'.padEnd(14);
423
+
424
+ let diff = '';
425
+ if (agentAvg !== null && mcpAvg !== null) {
426
+ const ratio = agentAvg / mcpAvg;
427
+ if (ratio < 1) {
428
+ diff = `${((1 - ratio) * 100).toFixed(0)}% faster`;
429
+ } else if (ratio > 1) {
430
+ diff = `${((ratio - 1) * 100).toFixed(0)}% slower`;
431
+ } else {
432
+ diff = 'same';
433
+ }
434
+ }
435
+
436
+ console.log(`| ${op.padEnd(19)} | ${agentStr} | ${mcpStr} | ${diff.padEnd(10)} |`);
437
+ }
438
+
439
+ console.log('\n📦 Context Usage (output size for AI consumption):\n');
440
+ console.log('| Operation | agent-browser | playwright-mcp |');
441
+ console.log('|---------------------|--------------------|--------------------|');
442
+
443
+ for (const op of operations) {
444
+ const agentResults = results.filter(r => r.operation === op && r.tool === 'agent-browser');
445
+ const mcpResults = results.filter(r => r.operation === op && r.tool === 'playwright-mcp');
446
+
447
+ const agentAvgBytes = agentResults.length > 0
448
+ ? agentResults.reduce((sum, r) => sum + r.outputBytes, 0) / agentResults.length
449
+ : null;
450
+ const agentAvgTokens = agentResults.length > 0
451
+ ? agentResults.reduce((sum, r) => sum + r.estimatedTokens, 0) / agentResults.length
452
+ : null;
453
+
454
+ const mcpAvgBytes = mcpResults.length > 0
455
+ ? mcpResults.reduce((sum, r) => sum + r.outputBytes, 0) / mcpResults.length
456
+ : null;
457
+ const mcpAvgTokens = mcpResults.length > 0
458
+ ? mcpResults.reduce((sum, r) => sum + r.estimatedTokens, 0) / mcpResults.length
459
+ : null;
460
+
461
+ const agentStr = agentAvgBytes !== null
462
+ ? `${formatBytes(agentAvgBytes)} (~${Math.round(agentAvgTokens!)} tok)`.padEnd(18)
463
+ : 'N/A'.padEnd(18);
464
+ const mcpStr = mcpAvgBytes !== null
465
+ ? `${formatBytes(mcpAvgBytes)} (~${Math.round(mcpAvgTokens!)} tok)`.padEnd(18)
466
+ : 'N/A'.padEnd(18);
467
+
468
+ console.log(`| ${op.padEnd(19)} | ${agentStr} | ${mcpStr} |`);
469
+ }
470
+
471
+ // Total context usage
472
+ const agentTotal = results.filter(r => r.tool === 'agent-browser');
473
+ const mcpTotal = results.filter(r => r.tool === 'playwright-mcp');
474
+
475
+ const agentTotalBytes = agentTotal.reduce((sum, r) => sum + r.outputBytes, 0);
476
+ const agentTotalTokens = agentTotal.reduce((sum, r) => sum + r.estimatedTokens, 0);
477
+ const mcpTotalBytes = mcpTotal.reduce((sum, r) => sum + r.outputBytes, 0);
478
+ const mcpTotalTokens = mcpTotal.reduce((sum, r) => sum + r.estimatedTokens, 0);
479
+
480
+ console.log('\n📊 Total Context Usage (all operations combined):');
481
+ console.log(` agent-browser: ${formatBytes(agentTotalBytes)} (~${agentTotalTokens} tokens)`);
482
+ console.log(` playwright-mcp: ${formatBytes(mcpTotalBytes)} (~${mcpTotalTokens} tokens)`);
483
+
484
+ if (agentTotalBytes > 0 && mcpTotalBytes > 0) {
485
+ const ratio = agentTotalBytes / mcpTotalBytes;
486
+ if (ratio < 1) {
487
+ console.log(` → agent-browser uses ${((1 - ratio) * 100).toFixed(0)}% less context`);
488
+ } else {
489
+ console.log(` → playwright-mcp uses ${((1 - 1/ratio) * 100).toFixed(0)}% less context`);
490
+ }
491
+ }
492
+
493
+ console.log('\n' + '='.repeat(80));
494
+ }
495
+
496
+ // ============================================================================
497
+ // Main
498
+ // ============================================================================
499
+
500
+ async function main(): Promise<void> {
501
+ console.log('🚀 Browser Automation Benchmark');
502
+ console.log(` Testing against: ${TEST_URL}`);
503
+ console.log(` Iterations: ${ITERATIONS}`);
504
+ console.log('='.repeat(80));
505
+
506
+ try {
507
+ // Build first
508
+ console.log('\n🔨 Building agent-browser...');
509
+ execSync('pnpm build', { cwd: process.cwd(), stdio: 'inherit' });
510
+
511
+ await benchmarkAgentBrowser();
512
+ await benchmarkPlaywrightMCP();
513
+
514
+ printResults();
515
+ } catch (error) {
516
+ console.error('\n❌ Benchmark failed:', error);
517
+ process.exit(1);
518
+ }
519
+ }
520
+
521
+ main();