@detiq/crawler 0.1.3

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 (412) hide show
  1. package/Dockerfile +49 -0
  2. package/README.md +11 -0
  3. package/dist/accessibility-auditor.d.ts +29 -0
  4. package/dist/accessibility-auditor.js +57 -0
  5. package/dist/accessibility-auditor.js.map +7 -0
  6. package/dist/ai-login.d.ts +32 -0
  7. package/dist/ai-login.js +177 -0
  8. package/dist/ai-login.js.map +7 -0
  9. package/dist/auth-token-rotator.d.ts +47 -0
  10. package/dist/auth-token-rotator.js +58 -0
  11. package/dist/auth-token-rotator.js.map +7 -0
  12. package/dist/auth.d.ts +20 -0
  13. package/dist/auth.js +329 -0
  14. package/dist/auth.js.map +7 -0
  15. package/dist/browser-extension.d.ts +37 -0
  16. package/dist/browser-extension.js +113 -0
  17. package/dist/browser-extension.js.map +7 -0
  18. package/dist/captcha-solver.d.ts +16 -0
  19. package/dist/captcha-solver.js +105 -0
  20. package/dist/captcha-solver.js.map +7 -0
  21. package/dist/color-contrast.d.ts +30 -0
  22. package/dist/color-contrast.js +78 -0
  23. package/dist/color-contrast.js.map +7 -0
  24. package/dist/content-chunker.d.ts +18 -0
  25. package/dist/content-chunker.js +152 -0
  26. package/dist/content-chunker.js.map +7 -0
  27. package/dist/content-dedup.d.ts +23 -0
  28. package/dist/content-dedup.js +60 -0
  29. package/dist/content-dedup.js.map +7 -0
  30. package/dist/cookie-consent.d.ts +25 -0
  31. package/dist/cookie-consent.js +78 -0
  32. package/dist/cookie-consent.js.map +7 -0
  33. package/dist/cookie-jar.d.ts +20 -0
  34. package/dist/cookie-jar.js +61 -0
  35. package/dist/cookie-jar.js.map +7 -0
  36. package/dist/crawler.d.ts +179 -0
  37. package/dist/crawler.js +3672 -0
  38. package/dist/crawler.js.map +7 -0
  39. package/dist/custom-js-injector.d.ts +42 -0
  40. package/dist/custom-js-injector.js +50 -0
  41. package/dist/custom-js-injector.js.map +7 -0
  42. package/dist/dark-mode-capture.d.ts +23 -0
  43. package/dist/dark-mode-capture.js +75 -0
  44. package/dist/dark-mode-capture.js.map +7 -0
  45. package/dist/dead-link-detector.d.ts +37 -0
  46. package/dist/dead-link-detector.js +50 -0
  47. package/dist/dead-link-detector.js.map +7 -0
  48. package/dist/element-hash.d.ts +9 -0
  49. package/dist/element-hash.js +9 -0
  50. package/dist/element-hash.js.map +7 -0
  51. package/dist/email-otp.d.ts +42 -0
  52. package/dist/email-otp.js +177 -0
  53. package/dist/email-otp.js.map +7 -0
  54. package/dist/engine-router.d.ts +47 -0
  55. package/dist/engine-router.js +94 -0
  56. package/dist/engine-router.js.map +7 -0
  57. package/dist/export/bulk-export.d.ts +26 -0
  58. package/dist/export/bulk-export.js +76 -0
  59. package/dist/export/bulk-export.js.map +7 -0
  60. package/dist/export/vector-export.d.ts +76 -0
  61. package/dist/export/vector-export.js +85 -0
  62. package/dist/export/vector-export.js.map +7 -0
  63. package/dist/extractors/advanced.d.ts +54 -0
  64. package/dist/extractors/advanced.js +245 -0
  65. package/dist/extractors/advanced.js.map +7 -0
  66. package/dist/extractors/artifact-envelope.d.ts +63 -0
  67. package/dist/extractors/artifact-envelope.js +63 -0
  68. package/dist/extractors/artifact-envelope.js.map +7 -0
  69. package/dist/failure-analysis-agent.d.ts +16 -0
  70. package/dist/failure-analysis-agent.js +40 -0
  71. package/dist/failure-analysis-agent.js.map +7 -0
  72. package/dist/feature-flag-detector.d.ts +30 -0
  73. package/dist/feature-flag-detector.js +55 -0
  74. package/dist/feature-flag-detector.js.map +7 -0
  75. package/dist/form-detector.d.ts +34 -0
  76. package/dist/form-detector.js +58 -0
  77. package/dist/form-detector.js.map +7 -0
  78. package/dist/frontier.d.ts +32 -0
  79. package/dist/frontier.js +83 -0
  80. package/dist/frontier.js.map +7 -0
  81. package/dist/gap-pass.d.ts +35 -0
  82. package/dist/gap-pass.js +81 -0
  83. package/dist/gap-pass.js.map +7 -0
  84. package/dist/graphql-detector.d.ts +39 -0
  85. package/dist/graphql-detector.js +86 -0
  86. package/dist/graphql-detector.js.map +7 -0
  87. package/dist/http-engine.d.ts +80 -0
  88. package/dist/http-engine.js +304 -0
  89. package/dist/http-engine.js.map +7 -0
  90. package/dist/incremental-diff.d.ts +59 -0
  91. package/dist/incremental-diff.js +61 -0
  92. package/dist/incremental-diff.js.map +7 -0
  93. package/dist/index.d.ts +35 -0
  94. package/dist/index.js +61 -0
  95. package/dist/index.js.map +7 -0
  96. package/dist/input-fuzzer.d.ts +30 -0
  97. package/dist/input-fuzzer.js +105 -0
  98. package/dist/input-fuzzer.js.map +7 -0
  99. package/dist/interactive-probe.d.ts +104 -0
  100. package/dist/interactive-probe.js +442 -0
  101. package/dist/interactive-probe.js.map +7 -0
  102. package/dist/job-queue.d.ts +171 -0
  103. package/dist/job-queue.js +2256 -0
  104. package/dist/job-queue.js.map +7 -0
  105. package/dist/js-coverage.d.ts +29 -0
  106. package/dist/js-coverage.js +56 -0
  107. package/dist/js-coverage.js.map +7 -0
  108. package/dist/js-router-detector.d.ts +17 -0
  109. package/dist/js-router-detector.js +90 -0
  110. package/dist/js-router-detector.js.map +7 -0
  111. package/dist/language-detector.d.ts +22 -0
  112. package/dist/language-detector.js +57 -0
  113. package/dist/language-detector.js.map +7 -0
  114. package/dist/link-classifier.d.ts +23 -0
  115. package/dist/link-classifier.js +40 -0
  116. package/dist/link-classifier.js.map +7 -0
  117. package/dist/live-view.d.ts +36 -0
  118. package/dist/live-view.js +100 -0
  119. package/dist/live-view.js.map +7 -0
  120. package/dist/logger.d.ts +2 -0
  121. package/dist/logger.js +10 -0
  122. package/dist/logger.js.map +7 -0
  123. package/dist/markdown-extract.d.ts +8 -0
  124. package/dist/markdown-extract.js +54 -0
  125. package/dist/markdown-extract.js.map +7 -0
  126. package/dist/native-crawler.d.ts +69 -0
  127. package/dist/native-crawler.js +839 -0
  128. package/dist/native-crawler.js.map +7 -0
  129. package/dist/network-observer.d.ts +49 -0
  130. package/dist/network-observer.js +149 -0
  131. package/dist/network-observer.js.map +7 -0
  132. package/dist/network-throttle.d.ts +25 -0
  133. package/dist/network-throttle.js +34 -0
  134. package/dist/network-throttle.js.map +7 -0
  135. package/dist/openapi-generator.d.ts +49 -0
  136. package/dist/openapi-generator.js +89 -0
  137. package/dist/openapi-generator.js.map +7 -0
  138. package/dist/pdf-extractor.d.ts +28 -0
  139. package/dist/pdf-extractor.js +81 -0
  140. package/dist/pdf-extractor.js.map +7 -0
  141. package/dist/performance-metrics.d.ts +53 -0
  142. package/dist/performance-metrics.js +83 -0
  143. package/dist/performance-metrics.js.map +7 -0
  144. package/dist/phash.d.ts +38 -0
  145. package/dist/phash.js +115 -0
  146. package/dist/phash.js.map +7 -0
  147. package/dist/pii-scrubber.d.ts +21 -0
  148. package/dist/pii-scrubber.js +75 -0
  149. package/dist/pii-scrubber.js.map +7 -0
  150. package/dist/playwright-recorder.d.ts +39 -0
  151. package/dist/playwright-recorder.js +123 -0
  152. package/dist/playwright-recorder.js.map +7 -0
  153. package/dist/policies/crawl-safety.d.ts +9 -0
  154. package/dist/policies/crawl-safety.js +37 -0
  155. package/dist/policies/crawl-safety.js.map +7 -0
  156. package/dist/profiles/domain-profile.d.ts +19 -0
  157. package/dist/profiles/domain-profile.js +140 -0
  158. package/dist/profiles/domain-profile.js.map +7 -0
  159. package/dist/proxy.d.ts +50 -0
  160. package/dist/proxy.js +165 -0
  161. package/dist/proxy.js.map +7 -0
  162. package/dist/rate-limiter.d.ts +31 -0
  163. package/dist/rate-limiter.js +64 -0
  164. package/dist/rate-limiter.js.map +7 -0
  165. package/dist/redirect-tracker.d.ts +37 -0
  166. package/dist/redirect-tracker.js +65 -0
  167. package/dist/redirect-tracker.js.map +7 -0
  168. package/dist/redis-frontier.d.ts +48 -0
  169. package/dist/redis-frontier.js +126 -0
  170. package/dist/redis-frontier.js.map +7 -0
  171. package/dist/redis-queue.d.ts +48 -0
  172. package/dist/redis-queue.js +165 -0
  173. package/dist/redis-queue.js.map +7 -0
  174. package/dist/response-cache.d.ts +38 -0
  175. package/dist/response-cache.js +140 -0
  176. package/dist/response-cache.js.map +7 -0
  177. package/dist/responsive-detector.d.ts +29 -0
  178. package/dist/responsive-detector.js +46 -0
  179. package/dist/responsive-detector.js.map +7 -0
  180. package/dist/retry-policy.d.ts +20 -0
  181. package/dist/retry-policy.js +54 -0
  182. package/dist/retry-policy.js.map +7 -0
  183. package/dist/robots-txt.d.ts +9 -0
  184. package/dist/robots-txt.js +77 -0
  185. package/dist/robots-txt.js.map +7 -0
  186. package/dist/screen-reader.d.ts +16 -0
  187. package/dist/screen-reader.js +46 -0
  188. package/dist/screen-reader.js.map +7 -0
  189. package/dist/screenshot-masker.d.ts +28 -0
  190. package/dist/screenshot-masker.js +62 -0
  191. package/dist/screenshot-masker.js.map +7 -0
  192. package/dist/seo-checker.d.ts +33 -0
  193. package/dist/seo-checker.js +91 -0
  194. package/dist/seo-checker.js.map +7 -0
  195. package/dist/session-capture.d.ts +35 -0
  196. package/dist/session-capture.js +267 -0
  197. package/dist/session-capture.js.map +7 -0
  198. package/dist/session-export.d.ts +49 -0
  199. package/dist/session-export.js +117 -0
  200. package/dist/session-export.js.map +7 -0
  201. package/dist/sitemap-writer.d.ts +21 -0
  202. package/dist/sitemap-writer.js +47 -0
  203. package/dist/sitemap-writer.js.map +7 -0
  204. package/dist/sitemap.d.ts +51 -0
  205. package/dist/sitemap.js +152 -0
  206. package/dist/sitemap.js.map +7 -0
  207. package/dist/skyvern.d.ts +56 -0
  208. package/dist/skyvern.js +129 -0
  209. package/dist/skyvern.js.map +7 -0
  210. package/dist/sqs-queue.d.ts +2 -0
  211. package/dist/sqs-queue.js +47 -0
  212. package/dist/sqs-queue.js.map +7 -0
  213. package/dist/sso-token.d.ts +29 -0
  214. package/dist/sso-token.js +76 -0
  215. package/dist/sso-token.js.map +7 -0
  216. package/dist/stealth/human-behavior.d.ts +20 -0
  217. package/dist/stealth/human-behavior.js +168 -0
  218. package/dist/stealth/human-behavior.js.map +7 -0
  219. package/dist/telemetry.d.ts +34 -0
  220. package/dist/telemetry.js +77 -0
  221. package/dist/telemetry.js.map +7 -0
  222. package/dist/test-executor.d.ts +67 -0
  223. package/dist/test-executor.js +311 -0
  224. package/dist/test-executor.js.map +7 -0
  225. package/dist/tls-fingerprint.d.ts +50 -0
  226. package/dist/tls-fingerprint.js +81 -0
  227. package/dist/tls-fingerprint.js.map +7 -0
  228. package/dist/url-pattern-completer.d.ts +28 -0
  229. package/dist/url-pattern-completer.js +87 -0
  230. package/dist/url-pattern-completer.js.map +7 -0
  231. package/dist/visual-diff-verdict.d.ts +40 -0
  232. package/dist/visual-diff-verdict.js +25 -0
  233. package/dist/visual-diff-verdict.js.map +7 -0
  234. package/dist/webhook-notifier.d.ts +30 -0
  235. package/dist/webhook-notifier.js +76 -0
  236. package/dist/webhook-notifier.js.map +7 -0
  237. package/dist/websocket-detector.d.ts +24 -0
  238. package/dist/websocket-detector.js +60 -0
  239. package/dist/websocket-detector.js.map +7 -0
  240. package/dist/worker.d.ts +1 -0
  241. package/dist/worker.js +33 -0
  242. package/dist/worker.js.map +7 -0
  243. package/package.json +45 -0
  244. package/src/accessibility-auditor.ts +102 -0
  245. package/src/ai-login.ts +218 -0
  246. package/src/auth-token-rotator.ts +89 -0
  247. package/src/auth.ts +396 -0
  248. package/src/browser-extension.ts +186 -0
  249. package/src/captcha-solver.ts +143 -0
  250. package/src/color-contrast.ts +116 -0
  251. package/src/content-chunker.ts +170 -0
  252. package/src/content-dedup.ts +99 -0
  253. package/src/cookie-consent.ts +106 -0
  254. package/src/cookie-jar.ts +91 -0
  255. package/src/crawler.ts +4305 -0
  256. package/src/custom-js-injector.ts +93 -0
  257. package/src/dark-mode-capture.ts +110 -0
  258. package/src/dead-link-detector.ts +76 -0
  259. package/src/element-hash.ts +16 -0
  260. package/src/email-otp.ts +252 -0
  261. package/src/engine-router.ts +141 -0
  262. package/src/export/bulk-export.ts +85 -0
  263. package/src/export/vector-export.ts +160 -0
  264. package/src/extractors/advanced.ts +283 -0
  265. package/src/extractors/artifact-envelope.ts +111 -0
  266. package/src/failure-analysis-agent.ts +49 -0
  267. package/src/feature-flag-detector.ts +97 -0
  268. package/src/form-detector.ts +103 -0
  269. package/src/frontier.test.ts +64 -0
  270. package/src/frontier.ts +112 -0
  271. package/src/gap-pass.ts +122 -0
  272. package/src/graphql-detector.ts +129 -0
  273. package/src/http-engine.ts +433 -0
  274. package/src/incremental-diff.ts +121 -0
  275. package/src/index.ts +35 -0
  276. package/src/input-fuzzer.ts +129 -0
  277. package/src/interactive-probe.ts +623 -0
  278. package/src/job-queue.test.ts +120 -0
  279. package/src/job-queue.ts +2701 -0
  280. package/src/js-coverage.ts +87 -0
  281. package/src/js-router-detector.ts +112 -0
  282. package/src/language-detector.ts +82 -0
  283. package/src/link-classifier.ts +65 -0
  284. package/src/live-view.ts +141 -0
  285. package/src/logger.ts +9 -0
  286. package/src/markdown-extract.test.ts +73 -0
  287. package/src/markdown-extract.ts +76 -0
  288. package/src/native-crawler.ts +1106 -0
  289. package/src/network-observer.ts +204 -0
  290. package/src/network-throttle.ts +69 -0
  291. package/src/openapi-generator.ts +135 -0
  292. package/src/pdf-extractor.ts +123 -0
  293. package/src/performance-metrics.ts +134 -0
  294. package/src/phash.ts +151 -0
  295. package/src/pii-scrubber.ts +99 -0
  296. package/src/playwright-recorder.ts +170 -0
  297. package/src/policies/crawl-safety.ts +46 -0
  298. package/src/profiles/domain-profile.ts +167 -0
  299. package/src/proxy.ts +238 -0
  300. package/src/rate-limiter.ts +87 -0
  301. package/src/redirect-tracker.ts +97 -0
  302. package/src/redis-frontier.ts +143 -0
  303. package/src/redis-queue.ts +208 -0
  304. package/src/response-cache.ts +150 -0
  305. package/src/responsive-detector.ts +92 -0
  306. package/src/retry-policy.ts +87 -0
  307. package/src/robots-txt.ts +96 -0
  308. package/src/screen-reader.ts +89 -0
  309. package/src/screenshot-masker.ts +98 -0
  310. package/src/seo-checker.ts +134 -0
  311. package/src/session-capture.ts +325 -0
  312. package/src/session-export.test.ts +75 -0
  313. package/src/session-export.ts +172 -0
  314. package/src/sitemap-writer.ts +65 -0
  315. package/src/sitemap.test.ts +131 -0
  316. package/src/sitemap.ts +224 -0
  317. package/src/skyvern.ts +199 -0
  318. package/src/sqs-queue.ts +49 -0
  319. package/src/sso-token.ts +120 -0
  320. package/src/stealth/human-behavior.ts +191 -0
  321. package/src/telemetry.ts +122 -0
  322. package/src/test-executor.test.ts +72 -0
  323. package/src/test-executor.ts +424 -0
  324. package/src/tls-fingerprint.ts +111 -0
  325. package/src/url-pattern-completer.ts +114 -0
  326. package/src/visual-diff-verdict.test.ts +80 -0
  327. package/src/visual-diff-verdict.ts +57 -0
  328. package/src/webhook-notifier.ts +109 -0
  329. package/src/websocket-detector.ts +83 -0
  330. package/src/worker.ts +48 -0
  331. package/tsconfig.json +13 -0
  332. package/types/accessibility-auditor.d.ts +29 -0
  333. package/types/ai-login.d.ts +32 -0
  334. package/types/auth-token-rotator.d.ts +47 -0
  335. package/types/auth.d.ts +20 -0
  336. package/types/browser-extension.d.ts +37 -0
  337. package/types/bull-worker.d.ts +6 -0
  338. package/types/captcha-solver.d.ts +16 -0
  339. package/types/color-contrast.d.ts +30 -0
  340. package/types/content-chunker.d.ts +18 -0
  341. package/types/content-dedup.d.ts +23 -0
  342. package/types/cookie-consent.d.ts +25 -0
  343. package/types/cookie-jar.d.ts +20 -0
  344. package/types/crawler.d.ts +179 -0
  345. package/types/custom-js-injector.d.ts +42 -0
  346. package/types/dark-mode-capture.d.ts +23 -0
  347. package/types/dead-link-detector.d.ts +37 -0
  348. package/types/element-hash.d.ts +9 -0
  349. package/types/email-otp.d.ts +42 -0
  350. package/types/engine-router.d.ts +47 -0
  351. package/types/export/bulk-export.d.ts +26 -0
  352. package/types/export/vector-export.d.ts +76 -0
  353. package/types/extractors/advanced.d.ts +54 -0
  354. package/types/extractors/artifact-envelope.d.ts +63 -0
  355. package/types/failure-analysis-agent.d.ts +16 -0
  356. package/types/feature-flag-detector.d.ts +30 -0
  357. package/types/form-detector.d.ts +34 -0
  358. package/types/frontier.d.ts +32 -0
  359. package/types/gap-pass.d.ts +35 -0
  360. package/types/graphql-detector.d.ts +39 -0
  361. package/types/http-engine.d.ts +80 -0
  362. package/types/incremental-diff.d.ts +59 -0
  363. package/types/index.d.ts +35 -0
  364. package/types/input-fuzzer.d.ts +30 -0
  365. package/types/interactive-probe.d.ts +104 -0
  366. package/types/job-queue.d.ts +171 -0
  367. package/types/js-coverage.d.ts +29 -0
  368. package/types/js-router-detector.d.ts +17 -0
  369. package/types/language-detector.d.ts +22 -0
  370. package/types/link-classifier.d.ts +23 -0
  371. package/types/live-view.d.ts +36 -0
  372. package/types/logger.d.ts +2 -0
  373. package/types/markdown-extract.d.ts +8 -0
  374. package/types/native-crawler.d.ts +69 -0
  375. package/types/network-observer.d.ts +49 -0
  376. package/types/network-throttle.d.ts +25 -0
  377. package/types/openapi-generator.d.ts +49 -0
  378. package/types/pdf-extractor.d.ts +28 -0
  379. package/types/performance-metrics.d.ts +53 -0
  380. package/types/phash.d.ts +38 -0
  381. package/types/pii-scrubber.d.ts +21 -0
  382. package/types/playwright-recorder.d.ts +39 -0
  383. package/types/policies/crawl-safety.d.ts +9 -0
  384. package/types/profiles/domain-profile.d.ts +19 -0
  385. package/types/proxy.d.ts +50 -0
  386. package/types/rate-limiter.d.ts +31 -0
  387. package/types/redirect-tracker.d.ts +37 -0
  388. package/types/redis-frontier.d.ts +48 -0
  389. package/types/redis-queue.d.ts +48 -0
  390. package/types/response-cache.d.ts +38 -0
  391. package/types/responsive-detector.d.ts +29 -0
  392. package/types/retry-policy.d.ts +20 -0
  393. package/types/robots-txt.d.ts +9 -0
  394. package/types/screen-reader.d.ts +16 -0
  395. package/types/screenshot-masker.d.ts +28 -0
  396. package/types/seo-checker.d.ts +33 -0
  397. package/types/session-capture.d.ts +38 -0
  398. package/types/session-export.d.ts +49 -0
  399. package/types/sitemap-writer.d.ts +21 -0
  400. package/types/sitemap.d.ts +51 -0
  401. package/types/skyvern.d.ts +56 -0
  402. package/types/sqs-queue.d.ts +2 -0
  403. package/types/sso-token.d.ts +29 -0
  404. package/types/stealth/human-behavior.d.ts +20 -0
  405. package/types/telemetry.d.ts +34 -0
  406. package/types/test-executor.d.ts +67 -0
  407. package/types/tls-fingerprint.d.ts +50 -0
  408. package/types/url-pattern-completer.d.ts +28 -0
  409. package/types/visual-diff-verdict.d.ts +40 -0
  410. package/types/webhook-notifier.d.ts +30 -0
  411. package/types/websocket-detector.d.ts +24 -0
  412. package/types/worker.d.ts +1 -0
@@ -0,0 +1,245 @@
1
+ import { parseHTML } from "linkedom";
2
+ function extractByCssSelector(html, selector, attr) {
3
+ try {
4
+ const { document } = parseHTML(html);
5
+ const nodes = Array.from(document.querySelectorAll(selector));
6
+ const matches = nodes.map((n) => {
7
+ if (attr) return n.getAttribute(attr) ?? "";
8
+ return n.textContent?.trim() ?? "";
9
+ }).filter(Boolean);
10
+ return { matches, count: matches.length, method: "css-selector" };
11
+ } catch (e) {
12
+ return { matches: [], count: 0, method: "css-selector-error" };
13
+ }
14
+ }
15
+ function extractByXPath(html, xpath) {
16
+ try {
17
+ const { document, XPathResult } = parseHTML(html);
18
+ const result = document.evaluate(xpath, document, null, XPathResult?.ANY_TYPE ?? 0, null);
19
+ const matches = [];
20
+ let node = result.iterateNext?.();
21
+ while (node) {
22
+ const text = node.textContent?.trim() ?? node.nodeValue?.trim() ?? "";
23
+ if (text) matches.push(text);
24
+ node = result.iterateNext?.();
25
+ }
26
+ return { matches, count: matches.length, method: "xpath" };
27
+ } catch (e) {
28
+ return { matches: [], count: 0, method: "xpath-error" };
29
+ }
30
+ }
31
+ function extractByRegex(text, pattern, flags) {
32
+ try {
33
+ const re = new RegExp(pattern, (flags ?? "g").includes("g") ? flags : (flags ?? "") + "g");
34
+ const matches = [];
35
+ let m;
36
+ while ((m = re.exec(text)) !== null) {
37
+ matches.push(m[1] ?? m[0]);
38
+ if (!re.global) break;
39
+ }
40
+ return { matches, count: matches.length, method: "regex" };
41
+ } catch {
42
+ return { matches: [], count: 0, method: "regex-error" };
43
+ }
44
+ }
45
+ function extractTables(html) {
46
+ try {
47
+ const { document } = parseHTML(html);
48
+ const tables = Array.from(document.querySelectorAll("table"));
49
+ return tables.map((table) => {
50
+ const headers = [];
51
+ const rows = [];
52
+ const ths = [...table.querySelectorAll("thead th, thead td, tr:first-child th")];
53
+ if (ths.length > 0) {
54
+ ths.forEach((th, i) => headers.push(th.textContent?.trim() || `col${i + 1}`));
55
+ }
56
+ const trs = [...table.querySelectorAll("tbody tr, tr")];
57
+ for (const tr of trs) {
58
+ const cells = [...tr.querySelectorAll("td, th")];
59
+ if (cells.length === 0) continue;
60
+ if (headers.length === 0) {
61
+ cells.forEach((_, i) => headers.push(`col${i + 1}`));
62
+ }
63
+ const row = {};
64
+ cells.forEach((cell, i) => {
65
+ const key = headers[i] || `col${i + 1}`;
66
+ row[key] = cell.textContent?.trim() ?? "";
67
+ });
68
+ if (Object.values(row).some((v) => v)) rows.push(row);
69
+ }
70
+ return rows;
71
+ }).filter((t) => t.length > 0);
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
76
+ function extractMetadata(html, url) {
77
+ const meta = {
78
+ title: null,
79
+ description: null,
80
+ keywords: null,
81
+ ogTitle: null,
82
+ ogDescription: null,
83
+ ogImage: null,
84
+ ogUrl: null,
85
+ ogType: null,
86
+ twitterCard: null,
87
+ twitterTitle: null,
88
+ twitterDescription: null,
89
+ canonical: null,
90
+ robots: null,
91
+ jsonLd: [],
92
+ schemaTypes: [],
93
+ lang: null
94
+ };
95
+ try {
96
+ const { document } = parseHTML(html);
97
+ meta.title = document.title ?? document.querySelector("title")?.textContent?.trim() ?? null;
98
+ meta.lang = document.documentElement?.getAttribute("lang") ?? null;
99
+ const metas = [...document.querySelectorAll("meta")];
100
+ for (const m of metas) {
101
+ const name = m.getAttribute("name")?.toLowerCase() ?? "";
102
+ const prop = m.getAttribute("property")?.toLowerCase() ?? "";
103
+ const content = m.getAttribute("content") ?? "";
104
+ if (name === "description") meta.description = content;
105
+ if (name === "keywords") meta.keywords = content;
106
+ if (name === "robots") meta.robots = content;
107
+ if (name === "twitter:card") meta.twitterCard = content;
108
+ if (name === "twitter:title") meta.twitterTitle = content;
109
+ if (name === "twitter:description") meta.twitterDescription = content;
110
+ if (prop === "og:title") meta.ogTitle = content;
111
+ if (prop === "og:description") meta.ogDescription = content;
112
+ if (prop === "og:image") meta.ogImage = content;
113
+ if (prop === "og:url") meta.ogUrl = content;
114
+ if (prop === "og:type") meta.ogType = content;
115
+ }
116
+ const canonical = document.querySelector('link[rel="canonical"]');
117
+ if (canonical) meta.canonical = canonical.getAttribute("href") ?? null;
118
+ const jsonLdScripts = [...document.querySelectorAll('script[type="application/ld+json"]')];
119
+ for (const s of jsonLdScripts) {
120
+ try {
121
+ const parsed = JSON.parse(s.textContent ?? "{}");
122
+ meta.jsonLd.push(parsed);
123
+ const types = Array.isArray(parsed) ? parsed.map((p) => p["@type"]).flat() : [parsed["@type"]];
124
+ meta.schemaTypes.push(...types.filter(Boolean));
125
+ } catch {
126
+ }
127
+ }
128
+ } catch {
129
+ }
130
+ return meta;
131
+ }
132
+ function extractStructuredProduct(jsonLd) {
133
+ for (const item of jsonLd) {
134
+ try {
135
+ const type = item["@type"];
136
+ const isProduct = Array.isArray(type) ? type.some((t) => typeof t === "string" && t.toLowerCase() === "product") : typeof type === "string" && type.toLowerCase() === "product";
137
+ if (!isProduct) continue;
138
+ const i = item;
139
+ const offers = Array.isArray(i.offers) ? i.offers[0] : i.offers;
140
+ const rawPrice = offers?.price;
141
+ const price = rawPrice != null ? parseFloat(rawPrice) : NaN;
142
+ const rawRating = i.aggregateRating?.ratingValue;
143
+ const rating = rawRating != null ? parseFloat(rawRating) : NaN;
144
+ const rawReviewCount = i.aggregateRating?.reviewCount;
145
+ const reviewCount = rawReviewCount != null ? parseInt(rawReviewCount, 10) : NaN;
146
+ const rawAvailability = offers?.availability;
147
+ const availability = rawAvailability ? rawAvailability.replace(/^https?:\/\/schema\.org\//, "") : null;
148
+ const rawImage = i.image;
149
+ const image = Array.isArray(rawImage) ? rawImage[0] ?? null : rawImage ?? null;
150
+ const brand = i.brand?.name ?? i.brand ?? null;
151
+ return {
152
+ name: i.name ?? null,
153
+ description: i.description ?? null,
154
+ brand: typeof brand === "string" ? brand : null,
155
+ sku: i.sku ?? null,
156
+ price: isNaN(price) ? null : price,
157
+ priceCurrency: offers?.priceCurrency ?? null,
158
+ availability,
159
+ rating: isNaN(rating) ? null : rating,
160
+ reviewCount: isNaN(reviewCount) ? null : reviewCount,
161
+ image,
162
+ url: i.url ?? null
163
+ };
164
+ } catch {
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+ function extractImages(html, baseUrl) {
170
+ try {
171
+ const { document } = parseHTML(html);
172
+ const base = new URL(baseUrl);
173
+ return Array.from(document.querySelectorAll("img")).map((img) => {
174
+ const src = img.getAttribute("src") ?? img.getAttribute("data-src") ?? "";
175
+ if (!src) return null;
176
+ try {
177
+ return {
178
+ src: new URL(src, base).href,
179
+ alt: img.getAttribute("alt") ?? "",
180
+ width: img.getAttribute("width") ? parseInt(img.getAttribute("width")) : void 0,
181
+ height: img.getAttribute("height") ? parseInt(img.getAttribute("height")) : void 0
182
+ };
183
+ } catch {
184
+ return null;
185
+ }
186
+ }).filter((x) => x !== null);
187
+ } catch {
188
+ return [];
189
+ }
190
+ }
191
+ function extractMediaFiles(html, baseUrl) {
192
+ const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|ico|avif)(\?.*)?$/i;
193
+ const VIDEO_EXT = /\.(mp4|webm|ogv|mov|avi)(\?.*)?$/i;
194
+ const AUDIO_EXT = /\.(mp3|ogg|wav|flac|aac)(\?.*)?$/i;
195
+ const DOC_EXT = /\.(pdf|docx?|xlsx?|pptx?|csv|zip)(\?.*)?$/i;
196
+ function classify(url, tag) {
197
+ if (tag === "img" || IMAGE_EXT.test(url)) return "image";
198
+ if (tag === "video" || VIDEO_EXT.test(url)) return "video";
199
+ if (tag === "audio" || AUDIO_EXT.test(url)) return "audio";
200
+ if (DOC_EXT.test(url)) return "document";
201
+ return "document";
202
+ }
203
+ try {
204
+ const { document } = parseHTML(html);
205
+ const base = new URL(baseUrl);
206
+ const results = [];
207
+ const seen = /* @__PURE__ */ new Set();
208
+ const selectors = ["img[src],video[src],audio[src],source[src]", "src"];
209
+ const attrPairs = [
210
+ ["img", "src"],
211
+ ["video", "src"],
212
+ ["audio", "src"],
213
+ ["source", "src"],
214
+ ["a", "href"]
215
+ ];
216
+ for (const [sel, attr] of attrPairs) {
217
+ for (const el of [...document.querySelectorAll(sel)]) {
218
+ const raw = el.getAttribute(attr) ?? "";
219
+ if (!raw) continue;
220
+ try {
221
+ const resolved = new URL(raw, base).href;
222
+ if (seen.has(resolved)) continue;
223
+ if (sel === "a" && !DOC_EXT.test(resolved) && !IMAGE_EXT.test(resolved) && !VIDEO_EXT.test(resolved) && !AUDIO_EXT.test(resolved)) continue;
224
+ seen.add(resolved);
225
+ results.push({ url: resolved, type: classify(resolved, sel), tag: sel });
226
+ } catch {
227
+ }
228
+ }
229
+ }
230
+ return results;
231
+ } catch {
232
+ return [];
233
+ }
234
+ }
235
+ export {
236
+ extractByCssSelector,
237
+ extractByRegex,
238
+ extractByXPath,
239
+ extractImages,
240
+ extractMediaFiles,
241
+ extractMetadata,
242
+ extractStructuredProduct,
243
+ extractTables
244
+ };
245
+ //# sourceMappingURL=advanced.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/extractors/advanced.ts"],
4
+ "sourcesContent": ["import { parseHTML } from 'linkedom';\n\nexport interface ExtractionResult {\n matches: string[];\n count: number;\n method: string;\n}\n\nexport type TableRow = Record<string, string>;\n\nexport interface PageMetadata {\n title: string | null;\n description: string | null;\n keywords: string | null;\n ogTitle: string | null;\n ogDescription: string | null;\n ogImage: string | null;\n ogUrl: string | null;\n ogType: string | null;\n twitterCard: string | null;\n twitterTitle: string | null;\n twitterDescription: string | null;\n canonical: string | null;\n robots: string | null;\n jsonLd: object[];\n schemaTypes: string[];\n lang: string | null;\n}\n\nexport function extractByCssSelector(html: string, selector: string, attr?: string): ExtractionResult {\n try {\n const { document } = parseHTML(html);\n const nodes = Array.from(document.querySelectorAll(selector));\n const matches = nodes.map((n: any) => {\n if (attr) return (n as any).getAttribute(attr) ?? '';\n return (n as any).textContent?.trim() ?? '';\n }).filter(Boolean);\n return { matches, count: matches.length, method: 'css-selector' };\n } catch (e) {\n return { matches: [], count: 0, method: 'css-selector-error' };\n }\n}\n\nexport function extractByXPath(html: string, xpath: string): ExtractionResult {\n try {\n const { document, XPathResult } = parseHTML(html) as any;\n const result = document.evaluate(xpath, document, null, XPathResult?.ANY_TYPE ?? 0, null);\n const matches: string[] = [];\n let node = result.iterateNext?.();\n while (node) {\n const text = (node as any).textContent?.trim() ?? (node as any).nodeValue?.trim() ?? '';\n if (text) matches.push(text);\n node = result.iterateNext?.();\n }\n return { matches, count: matches.length, method: 'xpath' };\n } catch (e) {\n return { matches: [], count: 0, method: 'xpath-error' };\n }\n}\n\nexport function extractByRegex(text: string, pattern: string, flags?: string): ExtractionResult {\n try {\n const re = new RegExp(pattern, (flags ?? 'g').includes('g') ? flags : (flags ?? '') + 'g');\n const matches: string[] = [];\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n matches.push(m[1] ?? m[0]);\n if (!re.global) break;\n }\n return { matches, count: matches.length, method: 'regex' };\n } catch {\n return { matches: [], count: 0, method: 'regex-error' };\n }\n}\n\nexport function extractTables(html: string): TableRow[][] {\n try {\n const { document } = parseHTML(html);\n const tables = Array.from(document.querySelectorAll('table'));\n return tables.map((table: any) => {\n const headers: string[] = [];\n const rows: TableRow[] = [];\n const ths = [...table.querySelectorAll('thead th, thead td, tr:first-child th')];\n if (ths.length > 0) {\n ths.forEach((th: any, i: number) => headers.push(th.textContent?.trim() || `col${i + 1}`));\n }\n const trs = [...table.querySelectorAll('tbody tr, tr')];\n for (const tr of trs) {\n const cells = [...(tr as any).querySelectorAll('td, th')];\n if (cells.length === 0) continue;\n if (headers.length === 0) {\n cells.forEach((_: any, i: number) => headers.push(`col${i + 1}`));\n }\n const row: TableRow = {};\n cells.forEach((cell: any, i: number) => {\n const key = headers[i] || `col${i + 1}`;\n row[key] = cell.textContent?.trim() ?? '';\n });\n if (Object.values(row).some(v => v)) rows.push(row);\n }\n return rows;\n }).filter(t => t.length > 0);\n } catch {\n return [];\n }\n}\n\nexport function extractMetadata(html: string, url: string): PageMetadata {\n const meta: PageMetadata = {\n title: null, description: null, keywords: null,\n ogTitle: null, ogDescription: null, ogImage: null, ogUrl: null, ogType: null,\n twitterCard: null, twitterTitle: null, twitterDescription: null,\n canonical: null, robots: null, jsonLd: [], schemaTypes: [], lang: null,\n };\n try {\n const { document } = parseHTML(html);\n meta.title = (document as any).title ?? (document as any).querySelector('title')?.textContent?.trim() ?? null;\n meta.lang = (document as any).documentElement?.getAttribute('lang') ?? null;\n\n const metas = [...(document as any).querySelectorAll('meta')];\n for (const m of metas) {\n const name = (m as any).getAttribute('name')?.toLowerCase() ?? '';\n const prop = (m as any).getAttribute('property')?.toLowerCase() ?? '';\n const content = (m as any).getAttribute('content') ?? '';\n if (name === 'description') meta.description = content;\n if (name === 'keywords') meta.keywords = content;\n if (name === 'robots') meta.robots = content;\n if (name === 'twitter:card') meta.twitterCard = content;\n if (name === 'twitter:title') meta.twitterTitle = content;\n if (name === 'twitter:description') meta.twitterDescription = content;\n if (prop === 'og:title') meta.ogTitle = content;\n if (prop === 'og:description') meta.ogDescription = content;\n if (prop === 'og:image') meta.ogImage = content;\n if (prop === 'og:url') meta.ogUrl = content;\n if (prop === 'og:type') meta.ogType = content;\n }\n\n const canonical = (document as any).querySelector('link[rel=\"canonical\"]');\n if (canonical) meta.canonical = canonical.getAttribute('href') ?? null;\n\n const jsonLdScripts = [...(document as any).querySelectorAll('script[type=\"application/ld+json\"]')];\n for (const s of jsonLdScripts) {\n try {\n const parsed = JSON.parse((s as any).textContent ?? '{}');\n meta.jsonLd.push(parsed);\n const types = Array.isArray(parsed) ? parsed.map((p: any) => p['@type']).flat() : [parsed['@type']];\n meta.schemaTypes.push(...types.filter(Boolean));\n } catch { /* skip invalid JSON-LD */ }\n }\n } catch { /* non-fatal */ }\n return meta;\n}\n\nexport interface ProductData {\n name: string | null;\n description: string | null;\n brand: string | null;\n sku: string | null;\n price: number | null;\n priceCurrency: string | null;\n availability: string | null;\n rating: number | null;\n reviewCount: number | null;\n image: string | null;\n url: string | null;\n}\n\nexport function extractStructuredProduct(jsonLd: object[]): ProductData | null {\n for (const item of jsonLd) {\n try {\n const type = (item as any)['@type'];\n const isProduct = Array.isArray(type)\n ? type.some((t: any) => typeof t === 'string' && t.toLowerCase() === 'product')\n : typeof type === 'string' && type.toLowerCase() === 'product';\n if (!isProduct) continue;\n\n const i = item as any;\n const offers = Array.isArray(i.offers) ? i.offers[0] : i.offers;\n\n const rawPrice = offers?.price;\n const price = rawPrice != null ? parseFloat(rawPrice) : NaN;\n\n const rawRating = i.aggregateRating?.ratingValue;\n const rating = rawRating != null ? parseFloat(rawRating) : NaN;\n\n const rawReviewCount = i.aggregateRating?.reviewCount;\n const reviewCount = rawReviewCount != null ? parseInt(rawReviewCount, 10) : NaN;\n\n const rawAvailability: string | undefined = offers?.availability;\n const availability = rawAvailability\n ? rawAvailability.replace(/^https?:\\/\\/schema\\.org\\//, '')\n : null;\n\n const rawImage = i.image;\n const image = Array.isArray(rawImage) ? (rawImage[0] ?? null) : (rawImage ?? null);\n\n const brand = i.brand?.name ?? i.brand ?? null;\n\n return {\n name: i.name ?? null,\n description: i.description ?? null,\n brand: typeof brand === 'string' ? brand : null,\n sku: i.sku ?? null,\n price: isNaN(price) ? null : price,\n priceCurrency: offers?.priceCurrency ?? null,\n availability,\n rating: isNaN(rating) ? null : rating,\n reviewCount: isNaN(reviewCount) ? null : reviewCount,\n image,\n url: i.url ?? null,\n };\n } catch { /* skip malformed item */ }\n }\n return null;\n}\n\nexport function extractImages(html: string, baseUrl: string): Array<{ src: string; alt: string; width?: number; height?: number }> {\n try {\n const { document } = parseHTML(html);\n const base = new URL(baseUrl);\n return Array.from((document as any).querySelectorAll('img'))\n .map((img: any) => {\n const src = img.getAttribute('src') ?? img.getAttribute('data-src') ?? '';\n if (!src) return null;\n try {\n return {\n src: new URL(src, base).href,\n alt: (img.getAttribute('alt') ?? '') as string,\n width: img.getAttribute('width') ? parseInt(img.getAttribute('width')) : undefined,\n height: img.getAttribute('height') ? parseInt(img.getAttribute('height')) : undefined,\n } as { src: string; alt: string; width?: number; height?: number };\n } catch { return null; }\n })\n .filter((x): x is { src: string; alt: string; width?: number; height?: number } => x !== null);\n } catch {\n return [];\n }\n}\n\nexport function extractMediaFiles(html: string, baseUrl: string): Array<{ url: string; type: 'image' | 'video' | 'audio' | 'document'; tag: string }> {\n const IMAGE_EXT = /\\.(png|jpe?g|gif|webp|svg|ico|avif)(\\?.*)?$/i;\n const VIDEO_EXT = /\\.(mp4|webm|ogv|mov|avi)(\\?.*)?$/i;\n const AUDIO_EXT = /\\.(mp3|ogg|wav|flac|aac)(\\?.*)?$/i;\n const DOC_EXT = /\\.(pdf|docx?|xlsx?|pptx?|csv|zip)(\\?.*)?$/i;\n\n function classify(url: string, tag: string): 'image' | 'video' | 'audio' | 'document' {\n if (tag === 'img' || IMAGE_EXT.test(url)) return 'image';\n if (tag === 'video' || VIDEO_EXT.test(url)) return 'video';\n if (tag === 'audio' || AUDIO_EXT.test(url)) return 'audio';\n if (DOC_EXT.test(url)) return 'document';\n return 'document';\n }\n\n try {\n const { document } = parseHTML(html);\n const base = new URL(baseUrl);\n const results: Array<{ url: string; type: 'image' | 'video' | 'audio' | 'document'; tag: string }> = [];\n const seen = new Set<string>();\n\n const selectors: [string, string] = ['img[src],video[src],audio[src],source[src]', 'src'];\n const attrPairs: Array<[string, string]> = [\n ['img', 'src'], ['video', 'src'], ['audio', 'src'], ['source', 'src'],\n ['a', 'href'],\n ];\n\n for (const [sel, attr] of attrPairs) {\n for (const el of [...(document as any).querySelectorAll(sel)]) {\n const raw = (el as any).getAttribute(attr) ?? '';\n if (!raw) continue;\n try {\n const resolved = new URL(raw, base).href;\n if (seen.has(resolved)) continue;\n if (sel === 'a' && !DOC_EXT.test(resolved) && !IMAGE_EXT.test(resolved) && !VIDEO_EXT.test(resolved) && !AUDIO_EXT.test(resolved)) continue;\n seen.add(resolved);\n results.push({ url: resolved, type: classify(resolved, sel), tag: sel });\n } catch { /* skip */ }\n }\n }\n return results;\n } catch {\n return [];\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,63 @@
1
+ import type { RawElement } from '../screen-reader.js';
2
+ export interface CrawlEvidenceFact {
3
+ type: string;
4
+ payload: unknown;
5
+ }
6
+ export interface CrawlArtifactEnvelope {
7
+ tenantId: string;
8
+ projectId: string;
9
+ jobId?: string;
10
+ url: string;
11
+ canonicalUrl: string;
12
+ screenId?: string;
13
+ status: 'ok' | 'blocked' | 'auth_expired' | 'error' | 'skipped';
14
+ outputs: {
15
+ screenshot?: string;
16
+ html?: string;
17
+ markdown?: string;
18
+ fitMarkdown?: string;
19
+ accessibilityTree?: string;
20
+ structuredJson?: unknown;
21
+ networkTrace?: string;
22
+ consoleLog?: string;
23
+ };
24
+ fingerprints: {
25
+ textHash?: string;
26
+ domHash?: string;
27
+ accessibilityHash?: string;
28
+ urlCanonicalHash: string;
29
+ };
30
+ evidence: CrawlEvidenceFact[];
31
+ }
32
+ export declare function sha256(input: string): string;
33
+ export declare function buildFitMarkdown(markdown: string, keywords?: string[]): string;
34
+ export declare function summarizeStructuredElements(elements: RawElement[]): {
35
+ elementCount: number;
36
+ formFieldCount: number;
37
+ actionCount: number;
38
+ fields: {
39
+ label: string;
40
+ role: string;
41
+ expectedData: string | undefined;
42
+ }[];
43
+ actions: {
44
+ label: string;
45
+ role: string;
46
+ }[];
47
+ };
48
+ export declare function buildCrawlArtifactEnvelope(input: {
49
+ tenantId: string;
50
+ projectId: string;
51
+ jobId?: string;
52
+ url: string;
53
+ canonicalUrl: string;
54
+ screenId?: string;
55
+ screenshotPath?: string;
56
+ domPath?: string;
57
+ domContent?: string;
58
+ markdown?: string;
59
+ fitMarkdown?: string;
60
+ accessibilityElements?: RawElement[];
61
+ structuredJson?: unknown;
62
+ evidence?: CrawlEvidenceFact[];
63
+ }): CrawlArtifactEnvelope;
@@ -0,0 +1,63 @@
1
+ import crypto from "node:crypto";
2
+ function sha256(input) {
3
+ return crypto.createHash("sha256").update(input).digest("hex");
4
+ }
5
+ function buildFitMarkdown(markdown, keywords = []) {
6
+ const lines = markdown.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
7
+ const lowerKeywords = keywords.map((k) => k.toLowerCase()).filter(Boolean);
8
+ const filtered = lines.filter((line) => {
9
+ const plain = line.replace(/[#*_`>\-[\]()]/g, "").trim();
10
+ if (plain.length < 3) return false;
11
+ if (/^(cookie|privacy|terms|copyright|all rights reserved|subscribe|newsletter)$/i.test(plain)) return false;
12
+ if (lowerKeywords.length === 0) return true;
13
+ const lower = plain.toLowerCase();
14
+ return lowerKeywords.some((kw) => lower.includes(kw)) || /^#{1,4}\s/.test(line);
15
+ });
16
+ const candidate = filtered.join("\n");
17
+ return candidate.length >= 200 ? candidate : markdown;
18
+ }
19
+ function summarizeStructuredElements(elements) {
20
+ const forms = elements.filter((e) => ["email", "password", "text", "tel", "date", "number", "select", "textarea"].includes(e.role));
21
+ const actions = elements.filter((e) => e.role === "button" || e.role === "submit" || e.role === "link" || e.role === "a");
22
+ return {
23
+ elementCount: elements.length,
24
+ formFieldCount: forms.length,
25
+ actionCount: actions.length,
26
+ fields: forms.slice(0, 50).map((e) => ({ label: e.label, role: e.role, expectedData: e.expectedData })),
27
+ actions: actions.slice(0, 50).map((e) => ({ label: e.label, role: e.role }))
28
+ };
29
+ }
30
+ function buildCrawlArtifactEnvelope(input) {
31
+ const accessibilityTree = input.accessibilityElements ? JSON.stringify(input.accessibilityElements) : void 0;
32
+ return {
33
+ tenantId: input.tenantId,
34
+ projectId: input.projectId,
35
+ jobId: input.jobId,
36
+ url: input.url,
37
+ canonicalUrl: input.canonicalUrl,
38
+ screenId: input.screenId,
39
+ status: "ok",
40
+ outputs: {
41
+ screenshot: input.screenshotPath,
42
+ html: input.domPath,
43
+ markdown: input.markdown,
44
+ fitMarkdown: input.fitMarkdown,
45
+ accessibilityTree,
46
+ structuredJson: input.structuredJson
47
+ },
48
+ fingerprints: {
49
+ textHash: input.markdown ? sha256(input.markdown) : void 0,
50
+ domHash: input.domContent ? sha256(input.domContent) : void 0,
51
+ accessibilityHash: accessibilityTree ? sha256(accessibilityTree) : void 0,
52
+ urlCanonicalHash: sha256(input.canonicalUrl)
53
+ },
54
+ evidence: input.evidence ?? []
55
+ };
56
+ }
57
+ export {
58
+ buildCrawlArtifactEnvelope,
59
+ buildFitMarkdown,
60
+ sha256,
61
+ summarizeStructuredElements
62
+ };
63
+ //# sourceMappingURL=artifact-envelope.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/extractors/artifact-envelope.ts"],
4
+ "sourcesContent": ["import crypto from 'node:crypto';\nimport type { RawElement } from '../screen-reader.js';\n\nexport interface CrawlEvidenceFact {\n type: string;\n payload: unknown;\n}\n\nexport interface CrawlArtifactEnvelope {\n tenantId: string;\n projectId: string;\n jobId?: string;\n url: string;\n canonicalUrl: string;\n screenId?: string;\n status: 'ok' | 'blocked' | 'auth_expired' | 'error' | 'skipped';\n outputs: {\n screenshot?: string;\n html?: string;\n markdown?: string;\n fitMarkdown?: string;\n accessibilityTree?: string;\n structuredJson?: unknown;\n networkTrace?: string;\n consoleLog?: string;\n };\n fingerprints: {\n textHash?: string;\n domHash?: string;\n accessibilityHash?: string;\n urlCanonicalHash: string;\n };\n evidence: CrawlEvidenceFact[];\n}\n\nexport function sha256(input: string): string {\n return crypto.createHash('sha256').update(input).digest('hex');\n}\n\nexport function buildFitMarkdown(markdown: string, keywords: string[] = []): string {\n const lines = markdown\n .split('\\n')\n .map((line) => line.trimEnd())\n .filter((line) => line.trim().length > 0);\n const lowerKeywords = keywords.map((k) => k.toLowerCase()).filter(Boolean);\n const filtered = lines.filter((line) => {\n const plain = line.replace(/[#*_`>\\-[\\]()]/g, '').trim();\n if (plain.length < 3) return false;\n if (/^(cookie|privacy|terms|copyright|all rights reserved|subscribe|newsletter)$/i.test(plain)) return false;\n if (lowerKeywords.length === 0) return true;\n const lower = plain.toLowerCase();\n return lowerKeywords.some((kw) => lower.includes(kw)) || /^#{1,4}\\s/.test(line);\n });\n const candidate = filtered.join('\\n');\n return candidate.length >= 200 ? candidate : markdown;\n}\n\nexport function summarizeStructuredElements(elements: RawElement[]) {\n const forms = elements.filter((e) => ['email', 'password', 'text', 'tel', 'date', 'number', 'select', 'textarea'].includes(e.role));\n const actions = elements.filter((e) => e.role === 'button' || e.role === 'submit' || e.role === 'link' || e.role === 'a');\n return {\n elementCount: elements.length,\n formFieldCount: forms.length,\n actionCount: actions.length,\n fields: forms.slice(0, 50).map((e) => ({ label: e.label, role: e.role, expectedData: e.expectedData })),\n actions: actions.slice(0, 50).map((e) => ({ label: e.label, role: e.role })),\n };\n}\n\nexport function buildCrawlArtifactEnvelope(input: {\n tenantId: string;\n projectId: string;\n jobId?: string;\n url: string;\n canonicalUrl: string;\n screenId?: string;\n screenshotPath?: string;\n domPath?: string;\n domContent?: string;\n markdown?: string;\n fitMarkdown?: string;\n accessibilityElements?: RawElement[];\n structuredJson?: unknown;\n evidence?: CrawlEvidenceFact[];\n}): CrawlArtifactEnvelope {\n const accessibilityTree = input.accessibilityElements ? JSON.stringify(input.accessibilityElements) : undefined;\n return {\n tenantId: input.tenantId,\n projectId: input.projectId,\n jobId: input.jobId,\n url: input.url,\n canonicalUrl: input.canonicalUrl,\n screenId: input.screenId,\n status: 'ok',\n outputs: {\n screenshot: input.screenshotPath,\n html: input.domPath,\n markdown: input.markdown,\n fitMarkdown: input.fitMarkdown,\n accessibilityTree,\n structuredJson: input.structuredJson,\n },\n fingerprints: {\n textHash: input.markdown ? sha256(input.markdown) : undefined,\n domHash: input.domContent ? sha256(input.domContent) : undefined,\n accessibilityHash: accessibilityTree ? sha256(accessibilityTree) : undefined,\n urlCanonicalHash: sha256(input.canonicalUrl),\n },\n evidence: input.evidence ?? [],\n };\n}\n"],
5
+ "mappings": "AAAA,OAAO,YAAY;AAmCZ,SAAS,OAAO,OAAuB;AAC5C,SAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAC/D;AAEO,SAAS,iBAAiB,UAAkB,WAAqB,CAAC,GAAW;AAClF,QAAM,QAAQ,SACX,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,EAC5B,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,SAAS,CAAC;AAC1C,QAAM,gBAAgB,SAAS,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,OAAO;AACzE,QAAM,WAAW,MAAM,OAAO,CAAC,SAAS;AACtC,UAAM,QAAQ,KAAK,QAAQ,mBAAmB,EAAE,EAAE,KAAK;AACvD,QAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAI,+EAA+E,KAAK,KAAK,EAAG,QAAO;AACvG,QAAI,cAAc,WAAW,EAAG,QAAO;AACvC,UAAM,QAAQ,MAAM,YAAY;AAChC,WAAO,cAAc,KAAK,CAAC,OAAO,MAAM,SAAS,EAAE,CAAC,KAAK,YAAY,KAAK,IAAI;AAAA,EAChF,CAAC;AACD,QAAM,YAAY,SAAS,KAAK,IAAI;AACpC,SAAO,UAAU,UAAU,MAAM,YAAY;AAC/C;AAEO,SAAS,4BAA4B,UAAwB;AAClE,QAAM,QAAQ,SAAS,OAAO,CAAC,MAAM,CAAC,SAAS,YAAY,QAAQ,OAAO,QAAQ,UAAU,UAAU,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC;AAClI,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,SAAS,YAAY,EAAE,SAAS,UAAU,EAAE,SAAS,GAAG;AACxH,SAAO;AAAA,IACL,cAAc,SAAS;AAAA,IACvB,gBAAgB,MAAM;AAAA,IACtB,aAAa,QAAQ;AAAA,IACrB,QAAQ,MAAM,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,MAAM,EAAE,MAAM,cAAc,EAAE,aAAa,EAAE;AAAA,IACtG,SAAS,QAAQ,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,MAAM,EAAE,KAAK,EAAE;AAAA,EAC7E;AACF;AAEO,SAAS,2BAA2B,OAejB;AACxB,QAAM,oBAAoB,MAAM,wBAAwB,KAAK,UAAU,MAAM,qBAAqB,IAAI;AACtG,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,IACjB,OAAO,MAAM;AAAA,IACb,KAAK,MAAM;AAAA,IACX,cAAc,MAAM;AAAA,IACpB,UAAU,MAAM;AAAA,IAChB,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,YAAY,MAAM;AAAA,MAClB,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,aAAa,MAAM;AAAA,MACnB;AAAA,MACA,gBAAgB,MAAM;AAAA,IACxB;AAAA,IACA,cAAc;AAAA,MACZ,UAAU,MAAM,WAAW,OAAO,MAAM,QAAQ,IAAI;AAAA,MACpD,SAAS,MAAM,aAAa,OAAO,MAAM,UAAU,IAAI;AAAA,MACvD,mBAAmB,oBAAoB,OAAO,iBAAiB,IAAI;AAAA,MACnE,kBAAkB,OAAO,MAAM,YAAY;AAAA,IAC7C;AAAA,IACA,UAAU,MAAM,YAAY,CAAC;AAAA,EAC/B;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ export interface FailureContext {
2
+ tenantId?: string;
3
+ error: string;
4
+ stepDescription?: string;
5
+ consoleLogs?: Array<{
6
+ type: string;
7
+ text: string;
8
+ }>;
9
+ networkErrors?: Array<{
10
+ url: string;
11
+ status: number;
12
+ method: string;
13
+ }>;
14
+ screenshotPath?: string;
15
+ }
16
+ export declare function analyzeFailure(ctx: FailureContext): Promise<string | null>;
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { askTrackedWithImages, askTracked } from "@qip/agents";
3
+ import { AppBrain } from "@qip/app-brain";
4
+ async function analyzeFailure(ctx) {
5
+ try {
6
+ let llmCfg;
7
+ if (ctx.tenantId) {
8
+ llmCfg = await AppBrain.getTaskLLMConfig(ctx.tenantId, "testRunner").catch(() => void 0) ?? void 0;
9
+ }
10
+ if (!llmCfg) return null;
11
+ const consoleSection = ctx.consoleLogs?.length ? `
12
+
13
+ Browser console (last ${ctx.consoleLogs.length} entries):
14
+ ${ctx.consoleLogs.map((l) => `[${l.type}] ${l.text}`).join("\n")}` : "";
15
+ const networkSection = ctx.networkErrors?.length ? `
16
+
17
+ Failed network requests:
18
+ ${ctx.networkErrors.map((n) => `${n.method} ${n.url} \u2192 ${n.status}`).join("\n")}` : "";
19
+ const userText = `A Playwright automated test failed. Identify the root cause in 2-3 sentences. Be specific \u2014 name the failing element, API endpoint, or error type. Do not suggest fixes.
20
+
21
+ Test step: ${ctx.stepDescription ?? "unknown"}
22
+ Error: ${ctx.error}${consoleSection}${networkSection}`;
23
+ if (ctx.screenshotPath) {
24
+ try {
25
+ const img = readFileSync(ctx.screenshotPath);
26
+ const result2 = await askTrackedWithImages("", userText, [{ base64: img.toString("base64"), mediaType: "image/png" }], llmCfg);
27
+ return result2.text || null;
28
+ } catch {
29
+ }
30
+ }
31
+ const result = await askTracked("", userText, llmCfg, 256);
32
+ return result.text || null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+ export {
38
+ analyzeFailure
39
+ };
40
+ //# sourceMappingURL=failure-analysis-agent.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/failure-analysis-agent.ts"],
4
+ "sourcesContent": ["import { readFileSync } from 'node:fs';\nimport { askTrackedWithImages, askTracked } from '@qip/agents';\nimport type { LLMConfig } from '@qip/agents';\nimport { AppBrain } from '@qip/app-brain';\n\nexport interface FailureContext {\n tenantId?: string;\n error: string;\n stepDescription?: string;\n consoleLogs?: Array<{ type: string; text: string }>;\n networkErrors?: Array<{ url: string; status: number; method: string }>;\n screenshotPath?: string;\n}\n\nexport async function analyzeFailure(ctx: FailureContext): Promise<string | null> {\n try {\n let llmCfg: LLMConfig | undefined;\n if (ctx.tenantId) {\n llmCfg = await AppBrain.getTaskLLMConfig(ctx.tenantId, 'testRunner').catch(() => undefined) as LLMConfig | undefined ?? undefined;\n }\n if (!llmCfg) return null;\n\n const consoleSection = ctx.consoleLogs?.length\n ? `\\n\\nBrowser console (last ${ctx.consoleLogs.length} entries):\\n${ctx.consoleLogs.map(l => `[${l.type}] ${l.text}`).join('\\n')}`\n : '';\n\n const networkSection = ctx.networkErrors?.length\n ? `\\n\\nFailed network requests:\\n${ctx.networkErrors.map(n => `${n.method} ${n.url} \u2192 ${n.status}`).join('\\n')}`\n : '';\n\n const userText = `A Playwright automated test failed. Identify the root cause in 2-3 sentences. Be specific \u2014 name the failing element, API endpoint, or error type. Do not suggest fixes.\n\nTest step: ${ctx.stepDescription ?? 'unknown'}\nError: ${ctx.error}${consoleSection}${networkSection}`;\n\n if (ctx.screenshotPath) {\n try {\n const img = readFileSync(ctx.screenshotPath);\n const result = await askTrackedWithImages('', userText, [{ base64: img.toString('base64'), mediaType: 'image/png' }], llmCfg);\n return result.text || null;\n } catch { /* fall through to text-only */ }\n }\n\n const result = await askTracked('', userText, llmCfg, 256);\n return result.text || null;\n } catch {\n return null;\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB,kBAAkB;AAEjD,SAAS,gBAAgB;AAWzB,eAAsB,eAAe,KAA6C;AAChF,MAAI;AACF,QAAI;AACJ,QAAI,IAAI,UAAU;AAChB,eAAS,MAAM,SAAS,iBAAiB,IAAI,UAAU,YAAY,EAAE,MAAM,MAAM,MAAS,KAA8B;AAAA,IAC1H;AACA,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,iBAAiB,IAAI,aAAa,SACpC;AAAA;AAAA,wBAA6B,IAAI,YAAY,MAAM;AAAA,EAAe,IAAI,YAAY,IAAI,OAAK,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI,CAAC,KAC9H;AAEJ,UAAM,iBAAiB,IAAI,eAAe,SACtC;AAAA;AAAA;AAAA,EAAiC,IAAI,cAAc,IAAI,OAAK,GAAG,EAAE,MAAM,IAAI,EAAE,GAAG,WAAM,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC,KAC5G;AAEJ,UAAM,WAAW;AAAA;AAAA,aAER,IAAI,mBAAmB,SAAS;AAAA,SACpC,IAAI,KAAK,GAAG,cAAc,GAAG,cAAc;AAEhD,QAAI,IAAI,gBAAgB;AACtB,UAAI;AACF,cAAM,MAAM,aAAa,IAAI,cAAc;AAC3C,cAAMA,UAAS,MAAM,qBAAqB,IAAI,UAAU,CAAC,EAAE,QAAQ,IAAI,SAAS,QAAQ,GAAG,WAAW,YAAY,CAAC,GAAG,MAAM;AAC5H,eAAOA,QAAO,QAAQ;AAAA,MACxB,QAAQ;AAAA,MAAkC;AAAA,IAC5C;AAEA,UAAM,SAAS,MAAM,WAAW,IAAI,UAAU,QAAQ,GAAG;AACzD,WAAO,OAAO,QAAQ;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
+ "names": ["result"]
7
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Feature flag + A/B test detector.
3
+ *
4
+ * Detects popular feature flag / experiment SDKs loaded on the page:
5
+ * LaunchDarkly, Optimizely, Split.io, Unleash, GrowthBook, Statsig, Flagsmith, Amplitude Experiment.
6
+ *
7
+ * Also detects common A/B test patterns: data-testid attributes, gtm.js experiment layers,
8
+ * Optimizely variation IDs, VWO, etc.
9
+ *
10
+ * Why: ZeTa needs to know when a page shows different content to different users
11
+ * so test generation can account for variants and not generate flaky tests.
12
+ */
13
+ import type { Page } from 'playwright';
14
+ export interface FeatureFlagInfo {
15
+ sdk: string;
16
+ confidence: 'high' | 'medium' | 'low';
17
+ details?: string;
18
+ }
19
+ export interface AbTestInfo {
20
+ tool: string;
21
+ experiments?: string[];
22
+ }
23
+ export interface FeatureFlagDetectionResult {
24
+ url: string;
25
+ flagSdks: FeatureFlagInfo[];
26
+ abTools: AbTestInfo[];
27
+ hasAnyFlags: boolean;
28
+ detectedAt: string;
29
+ }
30
+ export declare function detectFeatureFlags(page: Page, url: string): Promise<FeatureFlagDetectionResult>;
@@ -0,0 +1,55 @@
1
+ const FLAG_SDK_CHECKS = [
2
+ { sdk: "LaunchDarkly", windowKeys: ["LDClient", "LaunchDarkly", "ldClient"], scriptPattern: /launchdarkly/i },
3
+ { sdk: "Optimizely", windowKeys: ["optimizely", "optimizelySdk"], scriptPattern: /optimizely/i },
4
+ { sdk: "Split.io", windowKeys: ["splitio", "SplitFactory"], scriptPattern: /split\.io/i },
5
+ { sdk: "GrowthBook", windowKeys: ["growthbook", "GrowthBook"], scriptPattern: /growthbook/i },
6
+ { sdk: "Statsig", windowKeys: ["statsig", "StatsigClient"], scriptPattern: /statsig/i },
7
+ { sdk: "Flagsmith", windowKeys: ["flagsmith"], scriptPattern: /flagsmith/i },
8
+ { sdk: "Unleash", windowKeys: ["unleash", "UnleashClient"], scriptPattern: /unleash/i },
9
+ { sdk: "VWO", windowKeys: ["vwo", "_vwo"], scriptPattern: /vwo\.com/i },
10
+ { sdk: "Google Optimize", windowKeys: ["gaData", "google_optimize"], scriptPattern: /googleoptimize/i },
11
+ { sdk: "AB Tasty", windowKeys: ["ABTasty"], scriptPattern: /abtasty/i }
12
+ ];
13
+ async function detectFeatureFlags(page, url) {
14
+ const detectedAt = (/* @__PURE__ */ new Date()).toISOString();
15
+ const result = await page.evaluate((checks) => {
16
+ const flagSdks = [];
17
+ const scripts = Array.from(document.querySelectorAll("script[src]")).map((s) => s.src ?? "");
18
+ for (const check of checks) {
19
+ let found = false;
20
+ let confidence = "low";
21
+ if (check.windowKeys?.some((k) => k in window)) {
22
+ found = true;
23
+ confidence = "high";
24
+ }
25
+ if (!found && check.scriptPattern) {
26
+ const re = new RegExp(check.scriptPattern.source, "i");
27
+ const matchingSrc = scripts.find((s) => re.test(s));
28
+ if (matchingSrc) {
29
+ found = true;
30
+ confidence = "medium";
31
+ }
32
+ }
33
+ if (found) flagSdks.push({ sdk: check.sdk, confidence, details: void 0 });
34
+ }
35
+ const abTools = [];
36
+ if ("_vwo_code" in window || "_vis_opt_queue" in window) abTools.push({ tool: "VWO", experiments: [] });
37
+ if ("dataLayer" in window) {
38
+ const dl = window.dataLayer ?? [];
39
+ const expEvents = dl.filter((e) => e?.event?.includes("experiment") || e?.experimentId);
40
+ if (expEvents.length > 0) abTools.push({ tool: "Google Tag Manager Experiments", experiments: expEvents.slice(0, 5).map((e) => e.experimentId ?? "") });
41
+ }
42
+ return { flagSdks, abTools };
43
+ }, FLAG_SDK_CHECKS).catch(() => ({ flagSdks: [], abTools: [] }));
44
+ return {
45
+ url,
46
+ flagSdks: result.flagSdks,
47
+ abTools: result.abTools,
48
+ hasAnyFlags: result.flagSdks.length > 0 || result.abTools.length > 0,
49
+ detectedAt
50
+ };
51
+ }
52
+ export {
53
+ detectFeatureFlags
54
+ };
55
+ //# sourceMappingURL=feature-flag-detector.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/feature-flag-detector.ts"],
4
+ "sourcesContent": ["/**\n * Feature flag + A/B test detector.\n *\n * Detects popular feature flag / experiment SDKs loaded on the page:\n * LaunchDarkly, Optimizely, Split.io, Unleash, GrowthBook, Statsig, Flagsmith, Amplitude Experiment.\n *\n * Also detects common A/B test patterns: data-testid attributes, gtm.js experiment layers,\n * Optimizely variation IDs, VWO, etc.\n *\n * Why: ZeTa needs to know when a page shows different content to different users\n * so test generation can account for variants and not generate flaky tests.\n */\n\nimport type { Page } from 'playwright';\n\nexport interface FeatureFlagInfo {\n sdk: string;\n confidence: 'high' | 'medium' | 'low';\n details?: string;\n}\n\nexport interface AbTestInfo {\n tool: string;\n experiments?: string[];\n}\n\nexport interface FeatureFlagDetectionResult {\n url: string;\n flagSdks: FeatureFlagInfo[];\n abTools: AbTestInfo[];\n hasAnyFlags: boolean;\n detectedAt: string;\n}\n\n// SDK detectors \u2014 checked against window object + script URLs\nconst FLAG_SDK_CHECKS: Array<{\n sdk: string;\n windowKeys?: string[];\n scriptPattern?: RegExp;\n}> = [\n { sdk: 'LaunchDarkly', windowKeys: ['LDClient', 'LaunchDarkly', 'ldClient'], scriptPattern: /launchdarkly/i },\n { sdk: 'Optimizely', windowKeys: ['optimizely', 'optimizelySdk'], scriptPattern: /optimizely/i },\n { sdk: 'Split.io', windowKeys: ['splitio', 'SplitFactory'], scriptPattern: /split\\.io/i },\n { sdk: 'GrowthBook', windowKeys: ['growthbook', 'GrowthBook'], scriptPattern: /growthbook/i },\n { sdk: 'Statsig', windowKeys: ['statsig', 'StatsigClient'], scriptPattern: /statsig/i },\n { sdk: 'Flagsmith', windowKeys: ['flagsmith'], scriptPattern: /flagsmith/i },\n { sdk: 'Unleash', windowKeys: ['unleash', 'UnleashClient'], scriptPattern: /unleash/i },\n { sdk: 'VWO', windowKeys: ['vwo', '_vwo'], scriptPattern: /vwo\\.com/i },\n { sdk: 'Google Optimize', windowKeys: ['gaData', 'google_optimize'], scriptPattern: /googleoptimize/i },\n { sdk: 'AB Tasty', windowKeys: ['ABTasty'], scriptPattern: /abtasty/i },\n];\n\nexport async function detectFeatureFlags(page: Page, url: string): Promise<FeatureFlagDetectionResult> {\n const detectedAt = new Date().toISOString();\n\n const result = await page.evaluate((checks) => {\n const flagSdks: any[] = [];\n const scripts = Array.from(document.querySelectorAll('script[src]')).map((s: any) => s.src ?? '');\n\n for (const check of checks) {\n let found = false;\n let confidence = 'low';\n\n // Check window keys\n if (check.windowKeys?.some((k: string) => k in (window as any))) {\n found = true;\n confidence = 'high';\n }\n // Check script URLs\n if (!found && check.scriptPattern) {\n const re = new RegExp(check.scriptPattern.source, 'i');\n const matchingSrc = scripts.find(s => re.test(s));\n if (matchingSrc) { found = true; confidence = 'medium'; }\n }\n if (found) flagSdks.push({ sdk: check.sdk, confidence, details: undefined });\n }\n\n // A/B tools\n const abTools: any[] = [];\n if ('_vwo_code' in window || '_vis_opt_queue' in (window as any)) abTools.push({ tool: 'VWO', experiments: [] });\n if ('dataLayer' in window) {\n const dl = (window as any).dataLayer ?? [];\n const expEvents = dl.filter((e: any) => e?.event?.includes('experiment') || e?.experimentId);\n if (expEvents.length > 0) abTools.push({ tool: 'Google Tag Manager Experiments', experiments: expEvents.slice(0, 5).map((e: any) => e.experimentId ?? '') });\n }\n\n return { flagSdks, abTools };\n }, FLAG_SDK_CHECKS as any).catch(() => ({ flagSdks: [], abTools: [] }));\n\n return {\n url,\n flagSdks: result.flagSdks,\n abTools: result.abTools,\n hasAnyFlags: result.flagSdks.length > 0 || result.abTools.length > 0,\n detectedAt,\n };\n}\n"],
5
+ "mappings": "AAmCA,MAAM,kBAID;AAAA,EACH,EAAE,KAAK,gBAAgB,YAAY,CAAC,YAAY,gBAAgB,UAAU,GAAG,eAAe,gBAAgB;AAAA,EAC5G,EAAE,KAAK,cAAc,YAAY,CAAC,cAAc,eAAe,GAAG,eAAe,cAAc;AAAA,EAC/F,EAAE,KAAK,YAAY,YAAY,CAAC,WAAW,cAAc,GAAG,eAAe,aAAa;AAAA,EACxF,EAAE,KAAK,cAAc,YAAY,CAAC,cAAc,YAAY,GAAG,eAAe,cAAc;AAAA,EAC5F,EAAE,KAAK,WAAW,YAAY,CAAC,WAAW,eAAe,GAAG,eAAe,WAAW;AAAA,EACtF,EAAE,KAAK,aAAa,YAAY,CAAC,WAAW,GAAG,eAAe,aAAa;AAAA,EAC3E,EAAE,KAAK,WAAW,YAAY,CAAC,WAAW,eAAe,GAAG,eAAe,WAAW;AAAA,EACtF,EAAE,KAAK,OAAO,YAAY,CAAC,OAAO,MAAM,GAAG,eAAe,YAAY;AAAA,EACtE,EAAE,KAAK,mBAAmB,YAAY,CAAC,UAAU,iBAAiB,GAAG,eAAe,kBAAkB;AAAA,EACtG,EAAE,KAAK,YAAY,YAAY,CAAC,SAAS,GAAG,eAAe,WAAW;AACxE;AAEA,eAAsB,mBAAmB,MAAY,KAAkD;AACrG,QAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAE1C,QAAM,SAAS,MAAM,KAAK,SAAS,CAAC,WAAW;AAC7C,UAAM,WAAkB,CAAC;AACzB,UAAM,UAAU,MAAM,KAAK,SAAS,iBAAiB,aAAa,CAAC,EAAE,IAAI,CAAC,MAAW,EAAE,OAAO,EAAE;AAEhG,eAAW,SAAS,QAAQ;AAC1B,UAAI,QAAQ;AACZ,UAAI,aAAa;AAGjB,UAAI,MAAM,YAAY,KAAK,CAAC,MAAc,KAAM,MAAc,GAAG;AAC/D,gBAAQ;AACR,qBAAa;AAAA,MACf;AAEA,UAAI,CAAC,SAAS,MAAM,eAAe;AACjC,cAAM,KAAK,IAAI,OAAO,MAAM,cAAc,QAAQ,GAAG;AACrD,cAAM,cAAc,QAAQ,KAAK,OAAK,GAAG,KAAK,CAAC,CAAC;AAChD,YAAI,aAAa;AAAE,kBAAQ;AAAM,uBAAa;AAAA,QAAU;AAAA,MAC1D;AACA,UAAI,MAAO,UAAS,KAAK,EAAE,KAAK,MAAM,KAAK,YAAY,SAAS,OAAU,CAAC;AAAA,IAC7E;AAGA,UAAM,UAAiB,CAAC;AACxB,QAAI,eAAe,UAAU,oBAAqB,OAAgB,SAAQ,KAAK,EAAE,MAAM,OAAO,aAAa,CAAC,EAAE,CAAC;AAC/G,QAAI,eAAe,QAAQ;AACzB,YAAM,KAAM,OAAe,aAAa,CAAC;AACzC,YAAM,YAAY,GAAG,OAAO,CAAC,MAAW,GAAG,OAAO,SAAS,YAAY,KAAK,GAAG,YAAY;AAC3F,UAAI,UAAU,SAAS,EAAG,SAAQ,KAAK,EAAE,MAAM,kCAAkC,aAAa,UAAU,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAW,EAAE,gBAAgB,EAAE,EAAE,CAAC;AAAA,IAC7J;AAEA,WAAO,EAAE,UAAU,QAAQ;AAAA,EAC7B,GAAG,eAAsB,EAAE,MAAM,OAAO,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC,EAAE,EAAE;AAEtE,SAAO;AAAA,IACL;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,SAAS,OAAO;AAAA,IAChB,aAAa,OAAO,SAAS,SAAS,KAAK,OAAO,QAAQ,SAAS;AAAA,IACnE;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Form detector — extract form metadata from crawled pages.
3
+ *
4
+ * For each form found on a page, captures: action URL, method, all input fields
5
+ * with their types/names/required/validation constraints, and submit button text.
6
+ *
7
+ * This data feeds ZeTa's test generator to create form validation test cases
8
+ * automatically (happy path + boundary/error cases per field type).
9
+ */
10
+ import type { Page } from 'playwright';
11
+ export interface FormField {
12
+ name: string;
13
+ type: string;
14
+ label?: string;
15
+ required: boolean;
16
+ placeholder?: string;
17
+ minLength?: number;
18
+ maxLength?: number;
19
+ min?: string;
20
+ max?: string;
21
+ pattern?: string;
22
+ options?: string[];
23
+ autocomplete?: string;
24
+ }
25
+ export interface DetectedForm {
26
+ index: number;
27
+ action?: string;
28
+ method: string;
29
+ fields: FormField[];
30
+ submitText?: string;
31
+ hasFileUpload: boolean;
32
+ purpose: 'login' | 'search' | 'registration' | 'contact' | 'checkout' | 'settings' | 'unknown';
33
+ }
34
+ export declare function detectForms(page: Page): Promise<DetectedForm[]>;