@epsilon-asi/actors 0.0.1

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 (381) hide show
  1. package/.ai/generators/_template.ts +37 -0
  2. package/.ai/generators/abstract.ts +24 -0
  3. package/.ai/generators/actor-task-form-filler.ts +140 -0
  4. package/.ai/generators/actor-task.ts +122 -0
  5. package/.ai/generators/auth-core.ts +126 -0
  6. package/.ai/generators/browser-runtime.ts +114 -0
  7. package/.ai/generators/cli-command.ts +96 -0
  8. package/.ai/generators/core-framework.ts +80 -0
  9. package/.ai/generators/docs.ts +92 -0
  10. package/.ai/generators/error-logging.ts +102 -0
  11. package/.ai/generators/extraction-helper.ts +96 -0
  12. package/.ai/generators/interaction-behavior.ts +129 -0
  13. package/.ai/generators/site-actor.ts +125 -0
  14. package/.ai/generators/site-login-flow.ts +117 -0
  15. package/.ai/generators/unit-test.ts +109 -0
  16. package/.ai/workflows/_template.ts +20 -0
  17. package/.ai/workflows/starter.ts +20 -0
  18. package/README.md +435 -0
  19. package/ai-gen.config.ts +67 -0
  20. package/dist/auth/AuthStateDetector.d.ts +6 -0
  21. package/dist/auth/AuthStateDetector.d.ts.map +1 -0
  22. package/dist/auth/AuthStateDetector.js +14 -0
  23. package/dist/auth/AuthStateDetector.js.map +1 -0
  24. package/dist/auth/CredentialsProvider.d.ts +22 -0
  25. package/dist/auth/CredentialsProvider.d.ts.map +1 -0
  26. package/dist/auth/CredentialsProvider.js +30 -0
  27. package/dist/auth/CredentialsProvider.js.map +1 -0
  28. package/dist/auth/LoginFlow.d.ts +27 -0
  29. package/dist/auth/LoginFlow.d.ts.map +1 -0
  30. package/dist/auth/LoginFlow.js +233 -0
  31. package/dist/auth/LoginFlow.js.map +1 -0
  32. package/dist/auth/LoginFlow.types.d.ts +123 -0
  33. package/dist/auth/LoginFlow.types.d.ts.map +1 -0
  34. package/dist/auth/LoginFlow.types.js +7 -0
  35. package/dist/auth/LoginFlow.types.js.map +1 -0
  36. package/dist/auth/SessionStore.d.ts +16 -0
  37. package/dist/auth/SessionStore.d.ts.map +1 -0
  38. package/dist/auth/SessionStore.js +8 -0
  39. package/dist/auth/SessionStore.js.map +1 -0
  40. package/dist/auth/index.d.ts +6 -0
  41. package/dist/auth/index.d.ts.map +1 -0
  42. package/dist/auth/index.js +6 -0
  43. package/dist/auth/index.js.map +1 -0
  44. package/dist/browser/BrowserFactory.d.ts +16 -0
  45. package/dist/browser/BrowserFactory.d.ts.map +1 -0
  46. package/dist/browser/BrowserFactory.js +209 -0
  47. package/dist/browser/BrowserFactory.js.map +1 -0
  48. package/dist/browser/BrowserSession.d.ts +15 -0
  49. package/dist/browser/BrowserSession.d.ts.map +1 -0
  50. package/dist/browser/BrowserSession.js +42 -0
  51. package/dist/browser/BrowserSession.js.map +1 -0
  52. package/dist/browser/PuppeteerLike.d.ts +45 -0
  53. package/dist/browser/PuppeteerLike.d.ts.map +1 -0
  54. package/dist/browser/PuppeteerLike.js +10 -0
  55. package/dist/browser/PuppeteerLike.js.map +1 -0
  56. package/dist/browser/RuntimeConfig.d.ts +82 -0
  57. package/dist/browser/RuntimeConfig.d.ts.map +1 -0
  58. package/dist/browser/RuntimeConfig.js +64 -0
  59. package/dist/browser/RuntimeConfig.js.map +1 -0
  60. package/dist/browser/index.d.ts +6 -0
  61. package/dist/browser/index.d.ts.map +1 -0
  62. package/dist/browser/index.js +6 -0
  63. package/dist/browser/index.js.map +1 -0
  64. package/dist/browser/profileValidation.d.ts +6 -0
  65. package/dist/browser/profileValidation.d.ts.map +1 -0
  66. package/dist/browser/profileValidation.js +59 -0
  67. package/dist/browser/profileValidation.js.map +1 -0
  68. package/dist/cli/run.d.ts +3 -0
  69. package/dist/cli/run.d.ts.map +1 -0
  70. package/dist/cli/run.js +103 -0
  71. package/dist/cli/run.js.map +1 -0
  72. package/dist/core/Actor.d.ts +54 -0
  73. package/dist/core/Actor.d.ts.map +1 -0
  74. package/dist/core/Actor.js +68 -0
  75. package/dist/core/Actor.js.map +1 -0
  76. package/dist/core/ActorContext.d.ts +32 -0
  77. package/dist/core/ActorContext.d.ts.map +1 -0
  78. package/dist/core/ActorContext.js +2 -0
  79. package/dist/core/ActorContext.js.map +1 -0
  80. package/dist/core/ActorRegistry.d.ts +8 -0
  81. package/dist/core/ActorRegistry.d.ts.map +1 -0
  82. package/dist/core/ActorRegistry.js +22 -0
  83. package/dist/core/ActorRegistry.js.map +1 -0
  84. package/dist/core/ActorRunner.d.ts +57 -0
  85. package/dist/core/ActorRunner.d.ts.map +1 -0
  86. package/dist/core/ActorRunner.js +157 -0
  87. package/dist/core/ActorRunner.js.map +1 -0
  88. package/dist/core/defineActor.d.ts +3 -0
  89. package/dist/core/defineActor.d.ts.map +1 -0
  90. package/dist/core/defineActor.js +4 -0
  91. package/dist/core/defineActor.js.map +1 -0
  92. package/dist/core/index.d.ts +6 -0
  93. package/dist/core/index.d.ts.map +1 -0
  94. package/dist/core/index.js +6 -0
  95. package/dist/core/index.js.map +1 -0
  96. package/dist/errors/AuthError.d.ts +5 -0
  97. package/dist/errors/AuthError.d.ts.map +1 -0
  98. package/dist/errors/AuthError.js +7 -0
  99. package/dist/errors/AuthError.js.map +1 -0
  100. package/dist/errors/AutomationError.d.ts +17 -0
  101. package/dist/errors/AutomationError.d.ts.map +1 -0
  102. package/dist/errors/AutomationError.js +17 -0
  103. package/dist/errors/AutomationError.js.map +1 -0
  104. package/dist/errors/ConfigError.d.ts +5 -0
  105. package/dist/errors/ConfigError.d.ts.map +1 -0
  106. package/dist/errors/ConfigError.js +7 -0
  107. package/dist/errors/ConfigError.js.map +1 -0
  108. package/dist/errors/ExtractionError.d.ts +5 -0
  109. package/dist/errors/ExtractionError.d.ts.map +1 -0
  110. package/dist/errors/ExtractionError.js +7 -0
  111. package/dist/errors/ExtractionError.js.map +1 -0
  112. package/dist/errors/NavigationError.d.ts +5 -0
  113. package/dist/errors/NavigationError.d.ts.map +1 -0
  114. package/dist/errors/NavigationError.js +7 -0
  115. package/dist/errors/NavigationError.js.map +1 -0
  116. package/dist/errors/SelectorError.d.ts +6 -0
  117. package/dist/errors/SelectorError.d.ts.map +1 -0
  118. package/dist/errors/SelectorError.js +9 -0
  119. package/dist/errors/SelectorError.js.map +1 -0
  120. package/dist/errors/index.d.ts +7 -0
  121. package/dist/errors/index.d.ts.map +1 -0
  122. package/dist/errors/index.js +7 -0
  123. package/dist/errors/index.js.map +1 -0
  124. package/dist/extraction/Extractor.d.ts +16 -0
  125. package/dist/extraction/Extractor.d.ts.map +1 -0
  126. package/dist/extraction/Extractor.js +54 -0
  127. package/dist/extraction/Extractor.js.map +1 -0
  128. package/dist/extraction/Pagination.d.ts +16 -0
  129. package/dist/extraction/Pagination.d.ts.map +1 -0
  130. package/dist/extraction/Pagination.js +36 -0
  131. package/dist/extraction/Pagination.js.map +1 -0
  132. package/dist/extraction/index.d.ts +3 -0
  133. package/dist/extraction/index.d.ts.map +1 -0
  134. package/dist/extraction/index.js +3 -0
  135. package/dist/extraction/index.js.map +1 -0
  136. package/dist/index.d.ts +10 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +10 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/interaction/FieldClearer.d.ts +7 -0
  141. package/dist/interaction/FieldClearer.d.ts.map +1 -0
  142. package/dist/interaction/FieldClearer.js +53 -0
  143. package/dist/interaction/FieldClearer.js.map +1 -0
  144. package/dist/interaction/Forms.d.ts +13 -0
  145. package/dist/interaction/Forms.d.ts.map +1 -0
  146. package/dist/interaction/Forms.js +22 -0
  147. package/dist/interaction/Forms.js.map +1 -0
  148. package/dist/interaction/GhostCursorAdapter.d.ts +27 -0
  149. package/dist/interaction/GhostCursorAdapter.d.ts.map +1 -0
  150. package/dist/interaction/GhostCursorAdapter.js +51 -0
  151. package/dist/interaction/GhostCursorAdapter.js.map +1 -0
  152. package/dist/interaction/HumanInteractor.d.ts +29 -0
  153. package/dist/interaction/HumanInteractor.d.ts.map +1 -0
  154. package/dist/interaction/HumanInteractor.js +2 -0
  155. package/dist/interaction/HumanInteractor.js.map +1 -0
  156. package/dist/interaction/HumanTyping.d.ts +43 -0
  157. package/dist/interaction/HumanTyping.d.ts.map +1 -0
  158. package/dist/interaction/HumanTyping.js +85 -0
  159. package/dist/interaction/HumanTyping.js.map +1 -0
  160. package/dist/interaction/NativePuppeteerInteractor.d.ts +20 -0
  161. package/dist/interaction/NativePuppeteerInteractor.d.ts.map +1 -0
  162. package/dist/interaction/NativePuppeteerInteractor.js +47 -0
  163. package/dist/interaction/NativePuppeteerInteractor.js.map +1 -0
  164. package/dist/interaction/Navigation.d.ts +17 -0
  165. package/dist/interaction/Navigation.d.ts.map +1 -0
  166. package/dist/interaction/Navigation.js +28 -0
  167. package/dist/interaction/Navigation.js.map +1 -0
  168. package/dist/interaction/PageAdapter.d.ts +32 -0
  169. package/dist/interaction/PageAdapter.d.ts.map +1 -0
  170. package/dist/interaction/PageAdapter.js +62 -0
  171. package/dist/interaction/PageAdapter.js.map +1 -0
  172. package/dist/interaction/Waits.d.ts +3 -0
  173. package/dist/interaction/Waits.d.ts.map +1 -0
  174. package/dist/interaction/Waits.js +4 -0
  175. package/dist/interaction/Waits.js.map +1 -0
  176. package/dist/interaction/index.d.ts +10 -0
  177. package/dist/interaction/index.d.ts.map +1 -0
  178. package/dist/interaction/index.js +10 -0
  179. package/dist/interaction/index.js.map +1 -0
  180. package/dist/interaction/textToKeystrokeEvents.d.ts +13 -0
  181. package/dist/interaction/textToKeystrokeEvents.d.ts.map +1 -0
  182. package/dist/interaction/textToKeystrokeEvents.js +15 -0
  183. package/dist/interaction/textToKeystrokeEvents.js.map +1 -0
  184. package/dist/logging/ConsoleLogger.d.ts +12 -0
  185. package/dist/logging/ConsoleLogger.d.ts.map +1 -0
  186. package/dist/logging/ConsoleLogger.js +42 -0
  187. package/dist/logging/ConsoleLogger.js.map +1 -0
  188. package/dist/logging/Logger.d.ts +14 -0
  189. package/dist/logging/Logger.d.ts.map +1 -0
  190. package/dist/logging/Logger.js +2 -0
  191. package/dist/logging/Logger.js.map +1 -0
  192. package/dist/logging/MemoryLogger.d.ts +10 -0
  193. package/dist/logging/MemoryLogger.d.ts.map +1 -0
  194. package/dist/logging/MemoryLogger.js +28 -0
  195. package/dist/logging/MemoryLogger.js.map +1 -0
  196. package/dist/logging/NullLogger.d.ts +8 -0
  197. package/dist/logging/NullLogger.d.ts.map +1 -0
  198. package/dist/logging/NullLogger.js +7 -0
  199. package/dist/logging/NullLogger.js.map +1 -0
  200. package/dist/logging/index.d.ts +5 -0
  201. package/dist/logging/index.d.ts.map +1 -0
  202. package/dist/logging/index.js +5 -0
  203. package/dist/logging/index.js.map +1 -0
  204. package/dist/sites/example/example.actor.d.ts +6 -0
  205. package/dist/sites/example/example.actor.d.ts.map +1 -0
  206. package/dist/sites/example/example.actor.js +47 -0
  207. package/dist/sites/example/example.actor.js.map +1 -0
  208. package/dist/sites/example/example.selectors.d.ts +18 -0
  209. package/dist/sites/example/example.selectors.d.ts.map +1 -0
  210. package/dist/sites/example/example.selectors.js +18 -0
  211. package/dist/sites/example/example.selectors.js.map +1 -0
  212. package/dist/sites/example/example.types.d.ts +16 -0
  213. package/dist/sites/example/example.types.d.ts.map +1 -0
  214. package/dist/sites/example/example.types.js +2 -0
  215. package/dist/sites/example/example.types.js.map +1 -0
  216. package/dist/sites/example/index.d.ts +4 -0
  217. package/dist/sites/example/index.d.ts.map +1 -0
  218. package/dist/sites/example/index.js +4 -0
  219. package/dist/sites/example/index.js.map +1 -0
  220. package/dist/sites/index.d.ts +4 -0
  221. package/dist/sites/index.d.ts.map +1 -0
  222. package/dist/sites/index.js +4 -0
  223. package/dist/sites/index.js.map +1 -0
  224. package/dist/sites/myvistage-com/index.d.ts +4 -0
  225. package/dist/sites/myvistage-com/index.d.ts.map +1 -0
  226. package/dist/sites/myvistage-com/index.js +4 -0
  227. package/dist/sites/myvistage-com/index.js.map +1 -0
  228. package/dist/sites/myvistage-com/myvistage-com.actor.d.ts +6 -0
  229. package/dist/sites/myvistage-com/myvistage-com.actor.d.ts.map +1 -0
  230. package/dist/sites/myvistage-com/myvistage-com.actor.js +44 -0
  231. package/dist/sites/myvistage-com/myvistage-com.actor.js.map +1 -0
  232. package/dist/sites/myvistage-com/myvistage-com.selectors.d.ts +15 -0
  233. package/dist/sites/myvistage-com/myvistage-com.selectors.d.ts.map +1 -0
  234. package/dist/sites/myvistage-com/myvistage-com.selectors.js +15 -0
  235. package/dist/sites/myvistage-com/myvistage-com.selectors.js.map +1 -0
  236. package/dist/sites/myvistage-com/myvistage-com.types.d.ts +16 -0
  237. package/dist/sites/myvistage-com/myvistage-com.types.d.ts.map +1 -0
  238. package/dist/sites/myvistage-com/myvistage-com.types.js +2 -0
  239. package/dist/sites/myvistage-com/myvistage-com.types.js.map +1 -0
  240. package/dist/sites/upwork-com/index.d.ts +6 -0
  241. package/dist/sites/upwork-com/index.d.ts.map +1 -0
  242. package/dist/sites/upwork-com/index.js +6 -0
  243. package/dist/sites/upwork-com/index.js.map +1 -0
  244. package/dist/sites/upwork-com/upwork-com.actor.d.ts +6 -0
  245. package/dist/sites/upwork-com/upwork-com.actor.d.ts.map +1 -0
  246. package/dist/sites/upwork-com/upwork-com.actor.js +82 -0
  247. package/dist/sites/upwork-com/upwork-com.actor.js.map +1 -0
  248. package/dist/sites/upwork-com/upwork-com.runner.d.ts +2 -0
  249. package/dist/sites/upwork-com/upwork-com.runner.d.ts.map +1 -0
  250. package/dist/sites/upwork-com/upwork-com.runner.js +14 -0
  251. package/dist/sites/upwork-com/upwork-com.runner.js.map +1 -0
  252. package/dist/sites/upwork-com/upwork-com.selectors.d.ts +11 -0
  253. package/dist/sites/upwork-com/upwork-com.selectors.d.ts.map +1 -0
  254. package/dist/sites/upwork-com/upwork-com.selectors.js +11 -0
  255. package/dist/sites/upwork-com/upwork-com.selectors.js.map +1 -0
  256. package/dist/sites/upwork-com/upwork-com.types.d.ts +88 -0
  257. package/dist/sites/upwork-com/upwork-com.types.d.ts.map +1 -0
  258. package/dist/sites/upwork-com/upwork-com.types.js +63 -0
  259. package/dist/sites/upwork-com/upwork-com.types.js.map +1 -0
  260. package/dist/sites/upwork-com/upwork-com.util.d.ts +4 -0
  261. package/dist/sites/upwork-com/upwork-com.util.d.ts.map +1 -0
  262. package/dist/sites/upwork-com/upwork-com.util.js +43 -0
  263. package/dist/sites/upwork-com/upwork-com.util.js.map +1 -0
  264. package/dist/utils/delay.d.ts +2 -0
  265. package/dist/utils/delay.d.ts.map +1 -0
  266. package/dist/utils/delay.js +6 -0
  267. package/dist/utils/delay.js.map +1 -0
  268. package/dist/utils/index.d.ts +6 -0
  269. package/dist/utils/index.d.ts.map +1 -0
  270. package/dist/utils/index.js +6 -0
  271. package/dist/utils/index.js.map +1 -0
  272. package/dist/utils/invariant.d.ts +2 -0
  273. package/dist/utils/invariant.d.ts.map +1 -0
  274. package/dist/utils/invariant.js +7 -0
  275. package/dist/utils/invariant.js.map +1 -0
  276. package/dist/utils/redact.d.ts +6 -0
  277. package/dist/utils/redact.d.ts.map +1 -0
  278. package/dist/utils/redact.js +40 -0
  279. package/dist/utils/redact.js.map +1 -0
  280. package/dist/utils/retry.d.ts +8 -0
  281. package/dist/utils/retry.d.ts.map +1 -0
  282. package/dist/utils/retry.js +23 -0
  283. package/dist/utils/retry.js.map +1 -0
  284. package/dist/utils/url.d.ts +2 -0
  285. package/dist/utils/url.d.ts.map +1 -0
  286. package/dist/utils/url.js +9 -0
  287. package/dist/utils/url.js.map +1 -0
  288. package/package.json +39 -0
  289. package/src/auth/AuthStateDetector.ts +18 -0
  290. package/src/auth/CredentialsProvider.ts +48 -0
  291. package/src/auth/LoginFlow.ts +332 -0
  292. package/src/auth/LoginFlow.types.ts +141 -0
  293. package/src/auth/SessionStore.ts +21 -0
  294. package/src/auth/index.ts +5 -0
  295. package/src/browser/BrowserFactory.ts +253 -0
  296. package/src/browser/BrowserSession.ts +50 -0
  297. package/src/browser/PuppeteerLike.ts +65 -0
  298. package/src/browser/RuntimeConfig.ts +152 -0
  299. package/src/browser/index.ts +5 -0
  300. package/src/browser/profileValidation.ts +73 -0
  301. package/src/cli/run.ts +112 -0
  302. package/src/core/Actor.ts +167 -0
  303. package/src/core/ActorContext.ts +34 -0
  304. package/src/core/ActorRegistry.ts +26 -0
  305. package/src/core/ActorRunner.ts +240 -0
  306. package/src/core/defineActor.ts +5 -0
  307. package/src/core/index.ts +5 -0
  308. package/src/errors/AuthError.ts +7 -0
  309. package/src/errors/AutomationError.ts +26 -0
  310. package/src/errors/ConfigError.ts +7 -0
  311. package/src/errors/ExtractionError.ts +7 -0
  312. package/src/errors/NavigationError.ts +7 -0
  313. package/src/errors/SelectorError.ts +10 -0
  314. package/src/errors/index.ts +6 -0
  315. package/src/extraction/Extractor.ts +65 -0
  316. package/src/extraction/Pagination.ts +47 -0
  317. package/src/extraction/index.ts +2 -0
  318. package/src/index.ts +9 -0
  319. package/src/interaction/FieldClearer.ts +73 -0
  320. package/src/interaction/Forms.ts +27 -0
  321. package/src/interaction/GhostCursorAdapter.ts +79 -0
  322. package/src/interaction/HumanInteractor.ts +32 -0
  323. package/src/interaction/HumanTyping.ts +157 -0
  324. package/src/interaction/NativePuppeteerInteractor.ts +68 -0
  325. package/src/interaction/Navigation.ts +37 -0
  326. package/src/interaction/PageAdapter.ts +86 -0
  327. package/src/interaction/Waits.ts +5 -0
  328. package/src/interaction/index.ts +9 -0
  329. package/src/logging/ConsoleLogger.ts +44 -0
  330. package/src/logging/Logger.ts +15 -0
  331. package/src/logging/MemoryLogger.ts +34 -0
  332. package/src/logging/NullLogger.ts +8 -0
  333. package/src/logging/index.ts +4 -0
  334. package/src/sites/example/example.actor.ts +53 -0
  335. package/src/sites/example/example.selectors.ts +17 -0
  336. package/src/sites/example/example.types.ts +18 -0
  337. package/src/sites/example/index.ts +3 -0
  338. package/src/sites/index.ts +3 -0
  339. package/src/sites/myvistage-com/index.ts +3 -0
  340. package/src/sites/myvistage-com/login-action-list.json +349 -0
  341. package/src/sites/myvistage-com/myvistage-com.actor.ts +50 -0
  342. package/src/sites/myvistage-com/myvistage-com.selectors.ts +14 -0
  343. package/src/sites/myvistage-com/myvistage-com.types.ts +18 -0
  344. package/src/sites/myvistage-com/post-comment-action.json +81 -0
  345. package/src/sites/upwork-com/index.ts +6 -0
  346. package/src/sites/upwork-com/upwork-com.actor.ts +97 -0
  347. package/src/sites/upwork-com/upwork-com.runner.ts +17 -0
  348. package/src/sites/upwork-com/upwork-com.selectors.ts +10 -0
  349. package/src/sites/upwork-com/upwork-com.types.ts +102 -0
  350. package/src/sites/upwork-com/upwork-com.util.ts +41 -0
  351. package/src/utils/delay.ts +4 -0
  352. package/src/utils/index.ts +5 -0
  353. package/src/utils/invariant.ts +7 -0
  354. package/src/utils/redact.ts +53 -0
  355. package/src/utils/retry.ts +31 -0
  356. package/src/utils/url.ts +7 -0
  357. package/tests/fixtures/FakeCredentialsProvider.ts +12 -0
  358. package/tests/fixtures/FakeCursor.ts +48 -0
  359. package/tests/fixtures/FakePage.ts +266 -0
  360. package/tests/fixtures/makeContext.ts +76 -0
  361. package/tests/unit/auth/AuthStateDetector.test.ts +80 -0
  362. package/tests/unit/auth/LoginFlow.test.ts +296 -0
  363. package/tests/unit/browser/BrowserFactory.test.ts +370 -0
  364. package/tests/unit/core/ActorRunner.test.ts +370 -0
  365. package/tests/unit/core/defineActor.test.ts +112 -0
  366. package/tests/unit/extraction/Extractor.test.ts +48 -0
  367. package/tests/unit/extraction/Pagination.test.ts +54 -0
  368. package/tests/unit/interaction/FieldClearer.test.ts +29 -0
  369. package/tests/unit/interaction/Forms.test.ts +35 -0
  370. package/tests/unit/interaction/GhostCursorAdapter.test.ts +68 -0
  371. package/tests/unit/interaction/HumanTyping.test.ts +54 -0
  372. package/tests/unit/interaction/NativePuppeteerInteractor.test.ts +22 -0
  373. package/tests/unit/interaction/PageAdapter.test.ts +25 -0
  374. package/tests/unit/logging/redact.test.ts +36 -0
  375. package/tests/unit/sites/myvistage-com.actor.test.ts +19 -0
  376. package/tests/unit/sites/myvistage-com.login.test.ts +22 -0
  377. package/tests/unit/sites/myvistage-com.postComment.test.ts +70 -0
  378. package/tests/unit/sites/upwork-com.login.test.ts +52 -0
  379. package/tsconfig.build.json +9 -0
  380. package/tsconfig.json +22 -0
  381. package/vitest.config.ts +12 -0
@@ -0,0 +1,253 @@
1
+ import puppeteer from 'puppeteer-core';
2
+ import { BrowserSession } from './BrowserSession.js';
3
+ import { ConfigError } from '../errors/ConfigError.js';
4
+ import type { BrowserRuntimeConfig, ExistingChromeProfileConfig, RemoteDebuggingChromeConfig, RuntimeConfig } from './RuntimeConfig.js';
5
+ import { isExistingChromeProfileConfig, isRemoteDebuggingChromeConfig, normalizeRuntimeConfig } from './RuntimeConfig.js';
6
+ import type { BrowserContextLike, BrowserLike, PageLike, PuppeteerConnectOptions, PuppeteerLauncher, PuppeteerLaunchOptions } from './PuppeteerLike.js';
7
+ import { validateExistingProfileConfig, validateRemoteDebuggingConfig } from './profileValidation.js';
8
+
9
+ export class BrowserFactory {
10
+ constructor(private readonly launcher: PuppeteerLauncher = puppeteer as unknown as PuppeteerLauncher) {}
11
+
12
+ buildLaunchOptions(profile: ExistingChromeProfileConfig): PuppeteerLaunchOptions {
13
+ const args = [
14
+ ...(profile.profileDirectory !== undefined ? [`--profile-directory=${profile.profileDirectory}`] : []),
15
+ ...(profile.args ?? [])
16
+ ];
17
+
18
+ const options: PuppeteerLaunchOptions = {
19
+ browser: 'chrome',
20
+ channel: profile.channel ?? 'chrome',
21
+ headless: false,
22
+ userDataDir: profile.userDataDir,
23
+ args
24
+ };
25
+
26
+ if (profile.executablePath !== undefined) options.executablePath = profile.executablePath;
27
+ if (profile.defaultViewport !== undefined) options.defaultViewport = profile.defaultViewport;
28
+ if (profile.dumpio !== undefined) options.dumpio = profile.dumpio;
29
+
30
+ return options;
31
+ }
32
+
33
+ buildConnectOptions(config: RemoteDebuggingChromeConfig): PuppeteerConnectOptions {
34
+ if (config.browserURL !== undefined && config.browserWSEndpoint !== undefined) {
35
+ throw new ConfigError('Use either browser.browserURL or browser.browserWSEndpoint, not both.');
36
+ }
37
+
38
+ const options: Record<string, unknown> = {};
39
+
40
+ if (config.browserWSEndpoint !== undefined) {
41
+ options.browserWSEndpoint = config.browserWSEndpoint;
42
+ } else {
43
+ options.browserURL = config.browserURL ?? `http://${config.remoteDebuggingHost ?? '127.0.0.1'}:${config.remoteDebuggingPort ?? 9222}`;
44
+ }
45
+
46
+ if (config.defaultViewport !== undefined) options.defaultViewport = config.defaultViewport;
47
+ if (config.protocolTimeout !== undefined) options.protocolTimeout = config.protocolTimeout;
48
+ if (config.slowMo !== undefined) options.slowMo = config.slowMo;
49
+
50
+ return options as PuppeteerConnectOptions;
51
+ }
52
+
53
+ async launch(config: RuntimeConfig): Promise<BrowserSession> {
54
+ const normalized = normalizeRuntimeConfig(config);
55
+
56
+ if (isExistingChromeProfileConfig(normalized.browser)) {
57
+ return this.launchExistingProfile(normalized as RuntimeConfig & { browser: ExistingChromeProfileConfig });
58
+ }
59
+
60
+ if (isRemoteDebuggingChromeConfig(normalized.browser)) {
61
+ return this.connectToRemoteDebuggingBrowser(normalized as RuntimeConfig & { browser: RemoteDebuggingChromeConfig });
62
+ }
63
+
64
+ throw new ConfigError('Unsupported browser mode.');
65
+ }
66
+
67
+ private async launchExistingProfile(config: RuntimeConfig & { browser: ExistingChromeProfileConfig }): Promise<BrowserSession> {
68
+ validateExistingProfileConfig(config.browser);
69
+
70
+ let browser: BrowserLike;
71
+ try {
72
+ browser = await this.launcher.launch(this.buildLaunchOptions(config.browser));
73
+ } catch (error) {
74
+ if ((config.browser.runningInstance?.enabled ?? true) && isLikelyProfileInUseError(error)) {
75
+ return this.connectToAlreadyRunningProfile(config, error);
76
+ }
77
+
78
+ throw explainLaunchFailure(error, config.browser);
79
+ }
80
+
81
+ const { context, page } = await this.preparePage(browser, config);
82
+ return new BrowserSession(browser, context, page, config.browser, 'launched');
83
+ }
84
+
85
+ private async connectToAlreadyRunningProfile(
86
+ config: RuntimeConfig & { browser: ExistingChromeProfileConfig },
87
+ launchError: unknown
88
+ ): Promise<BrowserSession> {
89
+ const remoteBrowser = this.buildProfileLockedConnectConfig(config.browser);
90
+ validateRemoteDebuggingConfig(remoteBrowser);
91
+
92
+ const connect = this.launcher.connect;
93
+ if (connect === undefined) {
94
+ throw new ConfigError('Chrome profile is already running, but the configured Puppeteer launcher does not support connect().', {
95
+ cause: launchError,
96
+ details: profileLockConnectDetails(config.browser, remoteBrowser, launchError)
97
+ });
98
+ }
99
+
100
+ let browser: BrowserLike;
101
+ try {
102
+ browser = await connect.call(this.launcher, this.buildConnectOptions(remoteBrowser));
103
+ } catch (connectError) {
104
+ throw explainProfileLockedConnectFailure(connectError, config.browser, remoteBrowser, launchError);
105
+ }
106
+
107
+ const connectedRuntimeConfig: RuntimeConfig & { browser: RemoteDebuggingChromeConfig } = {
108
+ ...config,
109
+ browser: remoteBrowser
110
+ };
111
+ const { context, page } = await this.preparePage(browser, connectedRuntimeConfig);
112
+ return new BrowserSession(browser, context, page, remoteBrowser, 'connected');
113
+ }
114
+
115
+ private buildProfileLockedConnectConfig(profile: ExistingChromeProfileConfig): RemoteDebuggingChromeConfig {
116
+ const running = profile.runningInstance ?? {};
117
+ const remoteBrowser: RemoteDebuggingChromeConfig = {
118
+ mode: 'remote-debugging',
119
+ remoteDebuggingHost: running.remoteDebuggingHost ?? '127.0.0.1',
120
+ remoteDebuggingPort: running.remoteDebuggingPort ?? 9222,
121
+ reuseExistingPage: running.reuseExistingPage ?? false,
122
+ closeBrowserOnFinish: false,
123
+ disconnectOnFinish: running.disconnectOnFinish ?? true
124
+ };
125
+
126
+ if (running.browserURL !== undefined) remoteBrowser.browserURL = running.browserURL;
127
+ if (running.browserWSEndpoint !== undefined) remoteBrowser.browserWSEndpoint = running.browserWSEndpoint;
128
+ if (running.protocolTimeout !== undefined) remoteBrowser.protocolTimeout = running.protocolTimeout;
129
+ if (running.slowMo !== undefined) remoteBrowser.slowMo = running.slowMo;
130
+ if (profile.defaultViewport !== undefined) remoteBrowser.defaultViewport = profile.defaultViewport;
131
+ if (profile.startUrl !== undefined) remoteBrowser.startUrl = profile.startUrl;
132
+
133
+ return remoteBrowser;
134
+ }
135
+
136
+ private async connectToRemoteDebuggingBrowser(config: RuntimeConfig & { browser: RemoteDebuggingChromeConfig }): Promise<BrowserSession> {
137
+ validateRemoteDebuggingConfig(config.browser);
138
+
139
+ const connect = this.launcher.connect;
140
+ if (connect === undefined) {
141
+ throw new ConfigError('The configured Puppeteer launcher does not support connect().');
142
+ }
143
+
144
+ let browser: BrowserLike;
145
+ try {
146
+ browser = await connect.call(this.launcher, this.buildConnectOptions(config.browser));
147
+ } catch (error) {
148
+ throw explainConnectFailure(error, config.browser);
149
+ }
150
+
151
+ const { context, page } = await this.preparePage(browser, config);
152
+ return new BrowserSession(browser, context, page, config.browser, 'connected');
153
+ }
154
+
155
+ private async preparePage(browser: BrowserLike, config: RuntimeConfig & { browser: BrowserRuntimeConfig }): Promise<{ context: BrowserContextLike; page: PageLike }> {
156
+ const context = browser.defaultBrowserContext();
157
+ const pages = await browser.pages();
158
+ const page = config.browser.reuseExistingPage !== false && pages[0] !== undefined
159
+ ? pages[0]
160
+ : await browser.newPage();
161
+
162
+ page.setDefaultTimeout?.(config.defaultTimeoutMs ?? 15_000);
163
+ page.setDefaultNavigationTimeout?.(config.navigationTimeoutMs ?? 30_000);
164
+
165
+ if (config.browser.startUrl !== undefined) {
166
+ await page.goto(config.browser.startUrl, { waitUntil: 'domcontentloaded' });
167
+ }
168
+
169
+ return { context, page };
170
+ }
171
+ }
172
+
173
+ function isLikelyProfileInUseError(error: unknown): boolean {
174
+ const message = error instanceof Error ? error.message : String(error);
175
+ return /DevToolsActivePort|SingletonLock|already\s+(?:in\s+use|running)|browser is already running|userDataDir|user data directory|profile.*(?:lock|in use|running)/i.test(message);
176
+ }
177
+
178
+ function explainLaunchFailure(error: unknown, profile: ExistingChromeProfileConfig): ConfigError {
179
+ const message = error instanceof Error ? error.message : String(error);
180
+ const likelyProfileIssue = isLikelyProfileInUseError(error) || /profile/i.test(message);
181
+
182
+ if (likelyProfileIssue) {
183
+ return new ConfigError(
184
+ 'Could not launch Chrome with the configured existing profile because it appears to already be running. Close other Chrome windows using this profile, or start Chrome once with a local remote debugging endpoint and let existing-profile mode connect to it.',
185
+ {
186
+ cause: error,
187
+ details: {
188
+ userDataDir: profile.userDataDir,
189
+ profileDirectory: profile.profileDirectory ?? '(default)',
190
+ launchError: message
191
+ }
192
+ }
193
+ );
194
+ }
195
+
196
+ return new ConfigError('Could not launch Chrome with the configured existing profile.', {
197
+ cause: error,
198
+ details: {
199
+ userDataDir: profile.userDataDir,
200
+ profileDirectory: profile.profileDirectory ?? '(default)',
201
+ launchError: message
202
+ }
203
+ });
204
+ }
205
+
206
+ function explainConnectFailure(error: unknown, config: RemoteDebuggingChromeConfig): ConfigError {
207
+ const message = error instanceof Error ? error.message : String(error);
208
+ return new ConfigError(
209
+ 'Could not connect to the existing Chrome debugging endpoint. Make sure Chrome was started with --remote-debugging-port and that the browserURL/browserWSEndpoint is correct.',
210
+ {
211
+ cause: error,
212
+ details: {
213
+ browserURL: config.browserURL ?? `http://${config.remoteDebuggingHost ?? '127.0.0.1'}:${config.remoteDebuggingPort ?? 9222}`,
214
+ browserWSEndpoint: config.browserWSEndpoint ?? '(not configured)',
215
+ connectError: message
216
+ }
217
+ }
218
+ );
219
+ }
220
+
221
+ function explainProfileLockedConnectFailure(
222
+ connectError: unknown,
223
+ profile: ExistingChromeProfileConfig,
224
+ remoteBrowser: RemoteDebuggingChromeConfig,
225
+ launchError: unknown
226
+ ): ConfigError {
227
+ const connectMessage = connectError instanceof Error ? connectError.message : String(connectError);
228
+ return new ConfigError(
229
+ 'Chrome profile is already running, but the configured local DevTools endpoint could not be reached. Ensure Chrome was started with --remote-debugging-port and that the endpoint is bound only to localhost.',
230
+ {
231
+ cause: connectError,
232
+ details: profileLockConnectDetails(profile, remoteBrowser, launchError, connectMessage)
233
+ }
234
+ );
235
+ }
236
+
237
+ function profileLockConnectDetails(
238
+ profile: ExistingChromeProfileConfig,
239
+ remoteBrowser: RemoteDebuggingChromeConfig,
240
+ launchError: unknown,
241
+ connectError?: string
242
+ ): Record<string, unknown> {
243
+ const launchMessage = launchError instanceof Error ? launchError.message : String(launchError);
244
+ return {
245
+ userDataDir: profile.userDataDir,
246
+ profileDirectory: profile.profileDirectory ?? '(default)',
247
+ endpoint: remoteBrowser.browserWSEndpoint ?? remoteBrowser.browserURL ?? `http://${remoteBrowser.remoteDebuggingHost ?? '127.0.0.1'}:${remoteBrowser.remoteDebuggingPort ?? 9222}`,
248
+ browserURL: remoteBrowser.browserURL ?? `http://${remoteBrowser.remoteDebuggingHost ?? '127.0.0.1'}:${remoteBrowser.remoteDebuggingPort ?? 9222}`,
249
+ browserWSEndpoint: remoteBrowser.browserWSEndpoint ?? '(not configured)',
250
+ launchError: launchMessage,
251
+ ...(connectError !== undefined ? { connectError } : {})
252
+ };
253
+ }
@@ -0,0 +1,50 @@
1
+ import type { BrowserRuntimeConfig } from './RuntimeConfig.js';
2
+ import type { BrowserContextLike, BrowserLike, PageLike } from './PuppeteerLike.js';
3
+
4
+ export type BrowserSessionKind = 'launched' | 'connected';
5
+
6
+ export class BrowserSession {
7
+ readonly connectionMode: BrowserSessionKind;
8
+
9
+ constructor(
10
+ readonly browser: BrowserLike,
11
+ readonly context: BrowserContextLike,
12
+ readonly page: PageLike,
13
+ readonly profile: BrowserRuntimeConfig,
14
+ readonly kind: BrowserSessionKind = profile.mode === 'remote-debugging' ? 'connected' : 'launched'
15
+ ) {
16
+ this.connectionMode = kind;
17
+ }
18
+
19
+ shouldCloseOnFinish(): boolean {
20
+ if (this.kind === 'connected' && this.profile.mode === 'remote-debugging') {
21
+ return this.profile.closeBrowserOnFinish === true || this.profile.disconnectOnFinish !== false;
22
+ }
23
+
24
+ // In launched mode, closeBrowserOnFinish=false means "detach but keep Chrome running".
25
+ return true;
26
+ }
27
+
28
+ async close(): Promise<void> {
29
+ if (this.kind === 'connected') {
30
+ if (this.profile.mode === 'remote-debugging' && this.profile.closeBrowserOnFinish === true && this.profile.disconnectOnFinish === false) {
31
+ await this.browser.close();
32
+ return;
33
+ }
34
+
35
+ if (this.profile.mode === 'remote-debugging' && this.profile.disconnectOnFinish === false) {
36
+ return;
37
+ }
38
+
39
+ await this.browser.disconnect?.();
40
+ return;
41
+ }
42
+
43
+ if (this.profile.closeBrowserOnFinish === false) {
44
+ await this.browser.disconnect?.();
45
+ return;
46
+ }
47
+
48
+ await this.browser.close();
49
+ }
50
+ }
@@ -0,0 +1,65 @@
1
+ import type {
2
+ Browser,
3
+ BrowserContext,
4
+ ConnectOptions,
5
+ HTTPResponse,
6
+ LaunchOptions,
7
+ Page,
8
+ WaitForOptions,
9
+ WaitForSelectorOptions
10
+ } from 'puppeteer-core';
11
+
12
+ export type PuppeteerLaunchOptions = LaunchOptions;
13
+ export type PuppeteerConnectOptions = ConnectOptions;
14
+
15
+ export interface PuppeteerLauncher {
16
+ launch(options: PuppeteerLaunchOptions): Promise<BrowserLike>;
17
+ connect?(options: PuppeteerConnectOptions): Promise<BrowserLike>;
18
+ }
19
+
20
+ export interface BrowserLike {
21
+ defaultBrowserContext(): BrowserContextLike;
22
+ pages(): Promise<PageLike[]>;
23
+ newPage(): Promise<PageLike>;
24
+ close(): Promise<void>;
25
+ disconnect?(): Promise<void> | void;
26
+ }
27
+
28
+ export interface BrowserContextLike {
29
+ // Intentionally minimal; actors normally interact with PageLike.
30
+ }
31
+
32
+ export interface KeyboardLike {
33
+ type(text: string, options?: { delay?: number }): Promise<void>;
34
+ press(key: string, options?: { delay?: number }): Promise<void>;
35
+ }
36
+
37
+ export interface PageLike {
38
+ keyboard: KeyboardLike;
39
+ goto(url: string, options?: WaitForOptions): Promise<HTTPResponse | null>;
40
+ waitForSelector(selector: string, options?: WaitForSelectorOptions): Promise<unknown>;
41
+ waitForNavigation(options?: WaitForOptions): Promise<HTTPResponse | null>;
42
+ click(selector: string, options?: Record<string, unknown>): Promise<void>;
43
+ focus(selector: string): Promise<void>;
44
+ $eval<T>(selector: string, pageFunction: (element: Element, ...args: unknown[]) => T, ...args: unknown[]): Promise<T>;
45
+ $$eval<T>(selector: string, pageFunction: (elements: Element[], ...args: unknown[]) => T, ...args: unknown[]): Promise<T>;
46
+ evaluate<T>(pageFunction: (...args: unknown[]) => T, ...args: unknown[]): Promise<T>;
47
+ url(): string;
48
+ title(): Promise<string>;
49
+ content(): Promise<string>;
50
+ screenshot(options?: Record<string, unknown>): Promise<Uint8Array | string>;
51
+ setDefaultTimeout?(timeout: number): void;
52
+ setDefaultNavigationTimeout?(timeout: number): void;
53
+ }
54
+
55
+ export function asBrowserLike(browser: Browser): BrowserLike {
56
+ return browser as unknown as BrowserLike;
57
+ }
58
+
59
+ export function asBrowserContextLike(context: BrowserContext): BrowserContextLike {
60
+ return context as unknown as BrowserContextLike;
61
+ }
62
+
63
+ export function asPageLike(page: Page): PageLike {
64
+ return page as unknown as PageLike;
65
+ }
@@ -0,0 +1,152 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import type { HumanTypingOptions } from '../interaction/HumanTyping.js';
4
+
5
+ export type ChromeReleaseChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary';
6
+
7
+ export interface BrowserSessionBehavior {
8
+ defaultViewport?: { width: number; height: number } | null;
9
+ startUrl?: string;
10
+ reuseExistingPage?: boolean;
11
+ /**
12
+ * Existing-profile mode defaults to true because the framework owns the browser it launched.
13
+ * Remote-debugging mode defaults to false because Chrome is usually a long-lived host process.
14
+ */
15
+ closeBrowserOnFinish?: boolean;
16
+ }
17
+
18
+ export interface RemoteDebuggingEndpointConfig {
19
+ /**
20
+ * HTTP endpoint for an already-running Chrome instance, e.g. http://127.0.0.1:9222.
21
+ * Use either browserURL or browserWSEndpoint, not both.
22
+ */
23
+ browserURL?: string;
24
+ /** WebSocket endpoint from /json/version, e.g. ws://127.0.0.1:9222/devtools/browser/<id>. */
25
+ browserWSEndpoint?: string;
26
+ /** Used only when browserURL and browserWSEndpoint are omitted. */
27
+ remoteDebuggingHost?: string;
28
+ /** Used only when browserURL and browserWSEndpoint are omitted. */
29
+ remoteDebuggingPort?: number;
30
+ protocolTimeout?: number;
31
+ slowMo?: number;
32
+ }
33
+
34
+ export interface RunningChromeInstanceConfig extends RemoteDebuggingEndpointConfig {
35
+ /**
36
+ * If false, disables automatic attach when an existing-profile launch fails
37
+ * because the profile is already running. Defaults to true.
38
+ */
39
+ enabled?: boolean;
40
+ /** Connected runs should usually open a fresh tab. Defaults to false. */
41
+ reuseExistingPage?: boolean;
42
+ /** A running Chrome host should not be closed by default. Defaults to false. */
43
+ closeBrowserOnFinish?: boolean;
44
+ /** Disconnect the Puppeteer client on finish. Defaults to true. */
45
+ disconnectOnFinish?: boolean;
46
+ }
47
+
48
+ export interface ExistingChromeProfileConfig extends BrowserSessionBehavior {
49
+ mode: 'existing-profile';
50
+ /** Parent user data directory, not the nested profile folder. */
51
+ userDataDir: string;
52
+ /** Profile folder within userDataDir, e.g. Default or Profile 1. */
53
+ profileDirectory?: string;
54
+ channel?: ChromeReleaseChannel;
55
+ executablePath?: string;
56
+ headless?: false;
57
+ args?: string[];
58
+ allowCreatingUserDataDir?: boolean;
59
+ allowCreatingProfileDirectory?: boolean;
60
+ dumpio?: boolean;
61
+
62
+ /**
63
+ * Endpoint to try when this profile is already locked/running. If omitted,
64
+ * the framework still tries http://127.0.0.1:9222 by default. Set
65
+ * { enabled: false } to disable automatic attach.
66
+ */
67
+ runningInstance?: RunningChromeInstanceConfig;
68
+ }
69
+
70
+ export interface RemoteDebuggingChromeConfig extends BrowserSessionBehavior, RemoteDebuggingEndpointConfig {
71
+ mode: 'remote-debugging';
72
+ /** Disconnect the Puppeteer client on finish. Defaults to true. */
73
+ disconnectOnFinish?: boolean;
74
+ }
75
+
76
+ export type BrowserRuntimeConfig = ExistingChromeProfileConfig | RemoteDebuggingChromeConfig;
77
+
78
+ export interface RuntimeConfig {
79
+ browser: BrowserRuntimeConfig;
80
+ defaultTimeoutMs?: number;
81
+ navigationTimeoutMs?: number;
82
+ interaction?: {
83
+ typing?: HumanTypingOptions;
84
+ };
85
+ }
86
+
87
+ export function expandHome(input: string): string {
88
+ if (input === '~') return os.homedir();
89
+ if (input.startsWith(`~${path.sep}`)) return path.join(os.homedir(), input.slice(2));
90
+ return input;
91
+ }
92
+
93
+ export function isExistingChromeProfileConfig(config: BrowserRuntimeConfig): config is ExistingChromeProfileConfig {
94
+ return config.mode === 'existing-profile';
95
+ }
96
+
97
+ export function isRemoteDebuggingChromeConfig(config: BrowserRuntimeConfig): config is RemoteDebuggingChromeConfig {
98
+ return config.mode === 'remote-debugging';
99
+ }
100
+
101
+ function normalizeRunningChromeInstanceConfig(config: RunningChromeInstanceConfig | undefined): RunningChromeInstanceConfig | undefined {
102
+ if (config === undefined) return undefined;
103
+
104
+ return {
105
+ ...config,
106
+ enabled: config.enabled ?? true,
107
+ remoteDebuggingHost: config.remoteDebuggingHost ?? '127.0.0.1',
108
+ remoteDebuggingPort: config.remoteDebuggingPort ?? 9222,
109
+ reuseExistingPage: config.reuseExistingPage ?? false,
110
+ closeBrowserOnFinish: config.closeBrowserOnFinish ?? false,
111
+ disconnectOnFinish: config.disconnectOnFinish ?? true
112
+ };
113
+ }
114
+
115
+ function normalizeExistingProfileConfig(browser: ExistingChromeProfileConfig): ExistingChromeProfileConfig {
116
+ const runningInstance = normalizeRunningChromeInstanceConfig(browser.runningInstance);
117
+
118
+ return {
119
+ ...browser,
120
+ userDataDir: path.resolve(expandHome(browser.userDataDir)),
121
+ channel: browser.channel ?? 'chrome',
122
+ headless: false,
123
+ reuseExistingPage: browser.reuseExistingPage ?? true,
124
+ closeBrowserOnFinish: browser.closeBrowserOnFinish ?? true,
125
+ allowCreatingUserDataDir: browser.allowCreatingUserDataDir ?? false,
126
+ allowCreatingProfileDirectory: browser.allowCreatingProfileDirectory ?? false,
127
+ ...(runningInstance !== undefined ? { runningInstance } : {})
128
+ };
129
+ }
130
+
131
+ function normalizeRemoteDebuggingConfig(browser: RemoteDebuggingChromeConfig): RemoteDebuggingChromeConfig {
132
+ return {
133
+ ...browser,
134
+ remoteDebuggingHost: browser.remoteDebuggingHost ?? '127.0.0.1',
135
+ remoteDebuggingPort: browser.remoteDebuggingPort ?? 9222,
136
+ reuseExistingPage: browser.reuseExistingPage ?? false,
137
+ closeBrowserOnFinish: browser.closeBrowserOnFinish ?? false,
138
+ disconnectOnFinish: browser.disconnectOnFinish ?? true
139
+ };
140
+ }
141
+
142
+ export function normalizeRuntimeConfig(config: RuntimeConfig): RuntimeConfig {
143
+ return {
144
+ ...config,
145
+ browser: config.browser.mode === 'existing-profile'
146
+ ? normalizeExistingProfileConfig(config.browser)
147
+ : normalizeRemoteDebuggingConfig(config.browser),
148
+ defaultTimeoutMs: config.defaultTimeoutMs ?? 15_000,
149
+ navigationTimeoutMs: config.navigationTimeoutMs ?? 30_000,
150
+ ...(config.interaction !== undefined ? { interaction: config.interaction } : {})
151
+ };
152
+ }
@@ -0,0 +1,5 @@
1
+ export * from './BrowserFactory.js';
2
+ export * from './BrowserSession.js';
3
+ export * from './PuppeteerLike.js';
4
+ export * from './RuntimeConfig.js';
5
+ export * from './profileValidation.js';
@@ -0,0 +1,73 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { ConfigError } from '../errors/ConfigError.js';
4
+ import type { ExistingChromeProfileConfig, RemoteDebuggingChromeConfig, RemoteDebuggingEndpointConfig, RunningChromeInstanceConfig } from './RuntimeConfig.js';
5
+
6
+ export function getProfileDirectoryPath(config: ExistingChromeProfileConfig): string | undefined {
7
+ return config.profileDirectory === undefined
8
+ ? undefined
9
+ : path.join(config.userDataDir, config.profileDirectory);
10
+ }
11
+
12
+ export function validateExistingProfileConfig(config: ExistingChromeProfileConfig): void {
13
+ if (config.mode !== 'existing-profile') {
14
+ throw new ConfigError('validateExistingProfileConfig only accepts existing-profile mode.');
15
+ }
16
+
17
+ if (!config.userDataDir.trim()) {
18
+ throw new ConfigError('browser.userDataDir is required for existing-profile mode.');
19
+ }
20
+
21
+ if (!config.allowCreatingUserDataDir && !fs.existsSync(config.userDataDir)) {
22
+ throw new ConfigError('Configured Chrome userDataDir does not exist. Refusing to create a new profile directory.', {
23
+ details: { userDataDir: config.userDataDir }
24
+ });
25
+ }
26
+
27
+ if (config.profileDirectory !== undefined) {
28
+ if (path.isAbsolute(config.profileDirectory) || config.profileDirectory.includes('..')) {
29
+ throw new ConfigError('browser.profileDirectory must be a Chrome profile folder name such as Default or Profile 1, not a path.');
30
+ }
31
+
32
+ const profilePath = getProfileDirectoryPath(config);
33
+ if (profilePath !== undefined && !config.allowCreatingProfileDirectory && !fs.existsSync(profilePath)) {
34
+ throw new ConfigError('Configured Chrome profileDirectory does not exist inside userDataDir. Refusing to create a new Chrome profile.', {
35
+ details: { profileDirectory: config.profileDirectory, profilePath }
36
+ });
37
+ }
38
+ }
39
+
40
+ if (config.runningInstance !== undefined) {
41
+ validateRunningChromeInstanceConfig(config.runningInstance);
42
+ }
43
+ }
44
+
45
+ export function validateRemoteDebuggingConfig(config: RemoteDebuggingChromeConfig): void {
46
+ if (config.mode !== 'remote-debugging') {
47
+ throw new ConfigError('validateRemoteDebuggingConfig only accepts remote-debugging mode.');
48
+ }
49
+
50
+ validateRemoteDebuggingEndpointConfig(config);
51
+ }
52
+
53
+ export function validateRunningChromeInstanceConfig(config: RunningChromeInstanceConfig): void {
54
+ validateRemoteDebuggingEndpointConfig(config);
55
+ }
56
+
57
+ function validateRemoteDebuggingEndpointConfig(config: RemoteDebuggingEndpointConfig): void {
58
+ if (config.browserURL !== undefined && config.browserWSEndpoint !== undefined) {
59
+ throw new ConfigError('Use either browser.browserURL or browser.browserWSEndpoint, not both.');
60
+ }
61
+
62
+ if (config.browserURL !== undefined && config.browserURL.trim().length === 0) {
63
+ throw new ConfigError('browser.browserURL cannot be empty.');
64
+ }
65
+
66
+ if (config.browserWSEndpoint !== undefined && config.browserWSEndpoint.trim().length === 0) {
67
+ throw new ConfigError('browser.browserWSEndpoint cannot be empty.');
68
+ }
69
+
70
+ if (config.remoteDebuggingPort !== undefined && (!Number.isInteger(config.remoteDebuggingPort) || config.remoteDebuggingPort < 1 || config.remoteDebuggingPort > 65_535)) {
71
+ throw new ConfigError('browser.remoteDebuggingPort must be an integer between 1 and 65535.');
72
+ }
73
+ }