@taodigital/crawler 0.0.0-stage → 1.2.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 (494) hide show
  1. package/Dockerfile +82 -0
  2. package/README.md +10 -2
  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/accessibility-tree-crawler.d.ts +44 -0
  7. package/dist/accessibility-tree-crawler.js +156 -0
  8. package/dist/accessibility-tree-crawler.js.map +7 -0
  9. package/dist/advanced-ui-probes.d.ts +47 -0
  10. package/dist/advanced-ui-probes.js +310 -0
  11. package/dist/advanced-ui-probes.js.map +7 -0
  12. package/dist/ai-login.d.ts +32 -0
  13. package/dist/ai-login.js +250 -0
  14. package/dist/ai-login.js.map +7 -0
  15. package/dist/auth-token-rotator.d.ts +47 -0
  16. package/dist/auth-token-rotator.js +58 -0
  17. package/dist/auth-token-rotator.js.map +7 -0
  18. package/dist/auth.d.ts +20 -0
  19. package/dist/auth.js +348 -0
  20. package/dist/auth.js.map +7 -0
  21. package/dist/browser-extension.d.ts +37 -0
  22. package/dist/browser-extension.js +113 -0
  23. package/dist/browser-extension.js.map +7 -0
  24. package/dist/captcha-solver.d.ts +16 -0
  25. package/dist/captcha-solver.js +105 -0
  26. package/dist/captcha-solver.js.map +7 -0
  27. package/dist/color-contrast.d.ts +30 -0
  28. package/dist/color-contrast.js +78 -0
  29. package/dist/color-contrast.js.map +7 -0
  30. package/dist/content-chunker.d.ts +18 -0
  31. package/dist/content-chunker.js +152 -0
  32. package/dist/content-chunker.js.map +7 -0
  33. package/dist/content-dedup.d.ts +23 -0
  34. package/dist/content-dedup.js +60 -0
  35. package/dist/content-dedup.js.map +7 -0
  36. package/dist/cookie-consent.d.ts +25 -0
  37. package/dist/cookie-consent.js +78 -0
  38. package/dist/cookie-consent.js.map +7 -0
  39. package/dist/cookie-jar.d.ts +20 -0
  40. package/dist/cookie-jar.js +61 -0
  41. package/dist/cookie-jar.js.map +7 -0
  42. package/dist/crawl-session-manager.d.ts +44 -0
  43. package/dist/crawl-session-manager.js +145 -0
  44. package/dist/crawl-session-manager.js.map +7 -0
  45. package/dist/crawler.d.ts +233 -0
  46. package/dist/crawler.js +6239 -0
  47. package/dist/crawler.js.map +7 -0
  48. package/dist/crud-interaction.d.ts +31 -0
  49. package/dist/crud-interaction.js +264 -0
  50. package/dist/crud-interaction.js.map +7 -0
  51. package/dist/custom-js-injector.d.ts +42 -0
  52. package/dist/custom-js-injector.js +50 -0
  53. package/dist/custom-js-injector.js.map +7 -0
  54. package/dist/dark-mode-capture.d.ts +23 -0
  55. package/dist/dark-mode-capture.js +75 -0
  56. package/dist/dark-mode-capture.js.map +7 -0
  57. package/dist/dead-link-detector.d.ts +37 -0
  58. package/dist/dead-link-detector.js +50 -0
  59. package/dist/dead-link-detector.js.map +7 -0
  60. package/dist/dropdown-enumerator.d.ts +20 -0
  61. package/dist/dropdown-enumerator.js +230 -0
  62. package/dist/dropdown-enumerator.js.map +7 -0
  63. package/dist/element-hash.d.ts +9 -0
  64. package/dist/element-hash.js +9 -0
  65. package/dist/element-hash.js.map +7 -0
  66. package/dist/email-otp.d.ts +42 -0
  67. package/dist/email-otp.js +177 -0
  68. package/dist/email-otp.js.map +7 -0
  69. package/dist/engine-router.d.ts +47 -0
  70. package/dist/engine-router.js +94 -0
  71. package/dist/engine-router.js.map +7 -0
  72. package/dist/export/bulk-export.d.ts +26 -0
  73. package/dist/export/bulk-export.js +76 -0
  74. package/dist/export/bulk-export.js.map +7 -0
  75. package/dist/export/vector-export.d.ts +76 -0
  76. package/dist/export/vector-export.js +85 -0
  77. package/dist/export/vector-export.js.map +7 -0
  78. package/dist/extractors/advanced.d.ts +64 -0
  79. package/dist/extractors/advanced.js +265 -0
  80. package/dist/extractors/advanced.js.map +7 -0
  81. package/dist/extractors/artifact-envelope.d.ts +63 -0
  82. package/dist/extractors/artifact-envelope.js +63 -0
  83. package/dist/extractors/artifact-envelope.js.map +7 -0
  84. package/dist/extractors/branding-extractor.d.ts +46 -0
  85. package/dist/extractors/branding-extractor.js +104 -0
  86. package/dist/extractors/branding-extractor.js.map +7 -0
  87. package/dist/extractors/summary-extractor.d.ts +10 -0
  88. package/dist/extractors/summary-extractor.js +36 -0
  89. package/dist/extractors/summary-extractor.js.map +7 -0
  90. package/dist/failure-analysis-agent.d.ts +16 -0
  91. package/dist/failure-analysis-agent.js +42 -0
  92. package/dist/failure-analysis-agent.js.map +7 -0
  93. package/dist/feature-flag-detector.d.ts +30 -0
  94. package/dist/feature-flag-detector.js +55 -0
  95. package/dist/feature-flag-detector.js.map +7 -0
  96. package/dist/filter-pagination-crawler.d.ts +52 -0
  97. package/dist/filter-pagination-crawler.js +299 -0
  98. package/dist/filter-pagination-crawler.js.map +7 -0
  99. package/dist/form-detector.d.ts +34 -0
  100. package/dist/form-detector.js +58 -0
  101. package/dist/form-detector.js.map +7 -0
  102. package/dist/frontier.d.ts +40 -0
  103. package/dist/frontier.js +103 -0
  104. package/dist/frontier.js.map +7 -0
  105. package/dist/gap-pass.d.ts +36 -0
  106. package/dist/gap-pass.js +115 -0
  107. package/dist/gap-pass.js.map +7 -0
  108. package/dist/graphql-detector.d.ts +39 -0
  109. package/dist/graphql-detector.js +86 -0
  110. package/dist/graphql-detector.js.map +7 -0
  111. package/dist/http-engine.d.ts +80 -0
  112. package/dist/http-engine.js +318 -0
  113. package/dist/http-engine.js.map +7 -0
  114. package/dist/incremental-diff.d.ts +116 -0
  115. package/dist/incremental-diff.js +137 -0
  116. package/dist/incremental-diff.js.map +7 -0
  117. package/dist/index.d.ts +52 -0
  118. package/dist/index.js +83 -0
  119. package/dist/index.js.map +7 -0
  120. package/dist/input-fuzzer.d.ts +30 -0
  121. package/dist/input-fuzzer.js +105 -0
  122. package/dist/input-fuzzer.js.map +7 -0
  123. package/dist/interactive-probe.d.ts +144 -0
  124. package/dist/interactive-probe.js +865 -0
  125. package/dist/interactive-probe.js.map +7 -0
  126. package/dist/job-queue.d.ts +297 -0
  127. package/dist/job-queue.js +3812 -0
  128. package/dist/job-queue.js.map +7 -0
  129. package/dist/js-coverage.d.ts +29 -0
  130. package/dist/js-coverage.js +56 -0
  131. package/dist/js-coverage.js.map +7 -0
  132. package/dist/js-router-detector.d.ts +17 -0
  133. package/dist/js-router-detector.js +90 -0
  134. package/dist/js-router-detector.js.map +7 -0
  135. package/dist/language-detector.d.ts +22 -0
  136. package/dist/language-detector.js +57 -0
  137. package/dist/language-detector.js.map +7 -0
  138. package/dist/link-classifier.d.ts +23 -0
  139. package/dist/link-classifier.js +40 -0
  140. package/dist/link-classifier.js.map +7 -0
  141. package/dist/live-view.d.ts +36 -0
  142. package/dist/live-view.js +105 -0
  143. package/dist/live-view.js.map +7 -0
  144. package/dist/locator-extractor.d.ts +46 -0
  145. package/dist/locator-extractor.js +220 -0
  146. package/dist/locator-extractor.js.map +7 -0
  147. package/dist/logger.d.ts +2 -0
  148. package/dist/logger.js +10 -0
  149. package/dist/logger.js.map +7 -0
  150. package/dist/markdown-extract.d.ts +8 -0
  151. package/dist/markdown-extract.js +54 -0
  152. package/dist/markdown-extract.js.map +7 -0
  153. package/dist/multi-context-handler.d.ts +55 -0
  154. package/dist/multi-context-handler.js +182 -0
  155. package/dist/multi-context-handler.js.map +7 -0
  156. package/dist/native-crawler.d.ts +69 -0
  157. package/dist/native-crawler.js +839 -0
  158. package/dist/native-crawler.js.map +7 -0
  159. package/dist/nested-window-crawler.d.ts +41 -0
  160. package/dist/nested-window-crawler.js +172 -0
  161. package/dist/nested-window-crawler.js.map +7 -0
  162. package/dist/network-action-correlator.d.ts +47 -0
  163. package/dist/network-action-correlator.js +209 -0
  164. package/dist/network-action-correlator.js.map +7 -0
  165. package/dist/network-observer.d.ts +49 -0
  166. package/dist/network-observer.js +149 -0
  167. package/dist/network-observer.js.map +7 -0
  168. package/dist/network-throttle.d.ts +25 -0
  169. package/dist/network-throttle.js +34 -0
  170. package/dist/network-throttle.js.map +7 -0
  171. package/dist/openapi-generator.d.ts +49 -0
  172. package/dist/openapi-generator.js +89 -0
  173. package/dist/openapi-generator.js.map +7 -0
  174. package/dist/pdf-extractor.d.ts +28 -0
  175. package/dist/pdf-extractor.js +81 -0
  176. package/dist/pdf-extractor.js.map +7 -0
  177. package/dist/performance-metrics-collector.d.ts +50 -0
  178. package/dist/performance-metrics-collector.js +155 -0
  179. package/dist/performance-metrics-collector.js.map +7 -0
  180. package/dist/performance-metrics.d.ts +52 -0
  181. package/dist/performance-metrics.js +83 -0
  182. package/dist/performance-metrics.js.map +7 -0
  183. package/dist/phash.d.ts +50 -0
  184. package/dist/phash.js +136 -0
  185. package/dist/phash.js.map +7 -0
  186. package/dist/pii-scrubber.d.ts +21 -0
  187. package/dist/pii-scrubber.js +75 -0
  188. package/dist/pii-scrubber.js.map +7 -0
  189. package/dist/playwright-recorder.d.ts +39 -0
  190. package/dist/playwright-recorder.js +123 -0
  191. package/dist/playwright-recorder.js.map +7 -0
  192. package/dist/policies/crawl-safety.d.ts +9 -0
  193. package/dist/policies/crawl-safety.js +41 -0
  194. package/dist/policies/crawl-safety.js.map +7 -0
  195. package/dist/profiles/domain-profile.d.ts +19 -0
  196. package/dist/profiles/domain-profile.js +160 -0
  197. package/dist/profiles/domain-profile.js.map +7 -0
  198. package/dist/proxy.d.ts +50 -0
  199. package/dist/proxy.js +165 -0
  200. package/dist/proxy.js.map +7 -0
  201. package/dist/rate-limiter.d.ts +31 -0
  202. package/dist/rate-limiter.js +64 -0
  203. package/dist/rate-limiter.js.map +7 -0
  204. package/dist/redirect-tracker.d.ts +37 -0
  205. package/dist/redirect-tracker.js +65 -0
  206. package/dist/redirect-tracker.js.map +7 -0
  207. package/dist/redis-frontier.d.ts +48 -0
  208. package/dist/redis-frontier.js +126 -0
  209. package/dist/redis-frontier.js.map +7 -0
  210. package/dist/redis-queue.d.ts +48 -0
  211. package/dist/redis-queue.js +165 -0
  212. package/dist/redis-queue.js.map +7 -0
  213. package/dist/response-cache.d.ts +38 -0
  214. package/dist/response-cache.js +140 -0
  215. package/dist/response-cache.js.map +7 -0
  216. package/dist/responsive-detector.d.ts +29 -0
  217. package/dist/responsive-detector.js +46 -0
  218. package/dist/responsive-detector.js.map +7 -0
  219. package/dist/retry-policy.d.ts +20 -0
  220. package/dist/retry-policy.js +54 -0
  221. package/dist/retry-policy.js.map +7 -0
  222. package/dist/robots-txt.d.ts +9 -0
  223. package/dist/robots-txt.js +77 -0
  224. package/dist/robots-txt.js.map +7 -0
  225. package/dist/screen-reader.d.ts +15 -0
  226. package/dist/screen-reader.js +48 -0
  227. package/dist/screen-reader.js.map +7 -0
  228. package/dist/screenshot-masker.d.ts +28 -0
  229. package/dist/screenshot-masker.js +62 -0
  230. package/dist/screenshot-masker.js.map +7 -0
  231. package/dist/seo-checker.d.ts +33 -0
  232. package/dist/seo-checker.js +91 -0
  233. package/dist/seo-checker.js.map +7 -0
  234. package/dist/service-worker-interceptor.d.ts +36 -0
  235. package/dist/service-worker-interceptor.js +136 -0
  236. package/dist/service-worker-interceptor.js.map +7 -0
  237. package/dist/session-capture.d.ts +39 -0
  238. package/dist/session-capture.js +274 -0
  239. package/dist/session-capture.js.map +7 -0
  240. package/dist/session-export.d.ts +49 -0
  241. package/dist/session-export.js +117 -0
  242. package/dist/session-export.js.map +7 -0
  243. package/dist/shadow-dom-crawler.d.ts +35 -0
  244. package/dist/shadow-dom-crawler.js +132 -0
  245. package/dist/shadow-dom-crawler.js.map +7 -0
  246. package/dist/sitemap-writer.d.ts +21 -0
  247. package/dist/sitemap-writer.js +47 -0
  248. package/dist/sitemap-writer.js.map +7 -0
  249. package/dist/sitemap.d.ts +51 -0
  250. package/dist/sitemap.js +160 -0
  251. package/dist/sitemap.js.map +7 -0
  252. package/dist/skyvern.d.ts +56 -0
  253. package/dist/skyvern.js +129 -0
  254. package/dist/skyvern.js.map +7 -0
  255. package/dist/sqs-queue.d.ts +13 -0
  256. package/dist/sqs-queue.js +115 -0
  257. package/dist/sqs-queue.js.map +7 -0
  258. package/dist/sso-token.d.ts +29 -0
  259. package/dist/sso-token.js +76 -0
  260. package/dist/sso-token.js.map +7 -0
  261. package/dist/state-graph.d.ts +59 -0
  262. package/dist/state-graph.js +193 -0
  263. package/dist/state-graph.js.map +7 -0
  264. package/dist/stealth/human-behavior.d.ts +20 -0
  265. package/dist/stealth/human-behavior.js +202 -0
  266. package/dist/stealth/human-behavior.js.map +7 -0
  267. package/dist/telemetry.d.ts +34 -0
  268. package/dist/telemetry.js +77 -0
  269. package/dist/telemetry.js.map +7 -0
  270. package/dist/test-executor.d.ts +81 -0
  271. package/dist/test-executor.js +443 -0
  272. package/dist/test-executor.js.map +7 -0
  273. package/dist/tls-fingerprint.d.ts +50 -0
  274. package/dist/tls-fingerprint.js +81 -0
  275. package/dist/tls-fingerprint.js.map +7 -0
  276. package/dist/url-pattern-completer.d.ts +28 -0
  277. package/dist/url-pattern-completer.js +122 -0
  278. package/dist/url-pattern-completer.js.map +7 -0
  279. package/dist/visual-diff-verdict.d.ts +40 -0
  280. package/dist/visual-diff-verdict.js +27 -0
  281. package/dist/visual-diff-verdict.js.map +7 -0
  282. package/dist/webhook-notifier.d.ts +30 -0
  283. package/dist/webhook-notifier.js +76 -0
  284. package/dist/webhook-notifier.js.map +7 -0
  285. package/dist/websocket-detector.d.ts +24 -0
  286. package/dist/websocket-detector.js +60 -0
  287. package/dist/websocket-detector.js.map +7 -0
  288. package/dist/worker.d.ts +1 -0
  289. package/dist/worker.js +33 -0
  290. package/dist/worker.js.map +7 -0
  291. package/package.json +49 -4
  292. package/src/accessibility-auditor.ts +102 -0
  293. package/src/accessibility-tree-crawler.ts +246 -0
  294. package/src/advanced-ui-probes.ts +470 -0
  295. package/src/ai-login.ts +298 -0
  296. package/src/auth-token-rotator.ts +89 -0
  297. package/src/auth.ts +418 -0
  298. package/src/browser-extension.ts +186 -0
  299. package/src/captcha-solver.ts +143 -0
  300. package/src/color-contrast.ts +116 -0
  301. package/src/content-chunker.ts +170 -0
  302. package/src/content-dedup.ts +99 -0
  303. package/src/cookie-consent.ts +106 -0
  304. package/src/cookie-jar.ts +91 -0
  305. package/src/crawl-session-manager.ts +231 -0
  306. package/src/crawler.ts +7123 -0
  307. package/src/crawler.ts.rej +12 -0
  308. package/src/crud-interaction.ts +386 -0
  309. package/src/custom-js-injector.ts +93 -0
  310. package/src/dark-mode-capture.ts +110 -0
  311. package/src/dead-link-detector.ts +76 -0
  312. package/src/dropdown-enumerator.ts +312 -0
  313. package/src/element-hash.ts +16 -0
  314. package/src/email-otp.ts +252 -0
  315. package/src/engine-router.ts +141 -0
  316. package/src/export/bulk-export.ts +85 -0
  317. package/src/export/vector-export.ts +160 -0
  318. package/src/extractors/advanced.ts +309 -0
  319. package/src/extractors/artifact-envelope.ts +111 -0
  320. package/src/extractors/branding-extractor.ts +159 -0
  321. package/src/extractors/summary-extractor.ts +49 -0
  322. package/src/failure-analysis-agent.ts +51 -0
  323. package/src/feature-flag-detector.ts +97 -0
  324. package/src/filter-pagination-crawler.ts +500 -0
  325. package/src/form-detector.ts +103 -0
  326. package/src/frontier.test.ts +64 -0
  327. package/src/frontier.ts +144 -0
  328. package/src/gap-pass.ts +165 -0
  329. package/src/graphql-detector.ts +129 -0
  330. package/src/http-engine.ts +453 -0
  331. package/src/incremental-diff.ts +259 -0
  332. package/src/index.ts +52 -0
  333. package/src/input-fuzzer.ts +129 -0
  334. package/src/interactive-probe.ts +1136 -0
  335. package/src/job-queue.test.ts +187 -0
  336. package/src/job-queue.ts +4504 -0
  337. package/src/js-coverage.ts +87 -0
  338. package/src/js-router-detector.ts +112 -0
  339. package/src/language-detector.ts +82 -0
  340. package/src/link-classifier.ts +65 -0
  341. package/src/live-view.ts +146 -0
  342. package/src/locator-extractor.ts +309 -0
  343. package/src/logger.ts +9 -0
  344. package/src/markdown-extract.test.ts +73 -0
  345. package/src/markdown-extract.ts +76 -0
  346. package/src/multi-context-handler.ts +296 -0
  347. package/src/native-crawler.ts +1108 -0
  348. package/src/nested-window-crawler.ts +310 -0
  349. package/src/network-action-correlator.ts +320 -0
  350. package/src/network-observer.ts +204 -0
  351. package/src/network-throttle.ts +69 -0
  352. package/src/openapi-generator.ts +135 -0
  353. package/src/pdf-extractor.ts +123 -0
  354. package/src/performance-metrics-collector.ts +243 -0
  355. package/src/performance-metrics.ts +134 -0
  356. package/src/phash.ts +184 -0
  357. package/src/pii-scrubber.ts +99 -0
  358. package/src/playwright-recorder.ts +170 -0
  359. package/src/policies/crawl-safety.ts +52 -0
  360. package/src/profiles/domain-profile.ts +190 -0
  361. package/src/proxy.ts +238 -0
  362. package/src/rate-limiter.ts +87 -0
  363. package/src/redirect-tracker.ts +97 -0
  364. package/src/redis-frontier.ts +143 -0
  365. package/src/redis-queue.ts +208 -0
  366. package/src/response-cache.ts +150 -0
  367. package/src/responsive-detector.ts +92 -0
  368. package/src/retry-policy.ts +87 -0
  369. package/src/robots-txt.ts +96 -0
  370. package/src/screen-reader.ts +98 -0
  371. package/src/screenshot-masker.ts +98 -0
  372. package/src/seo-checker.ts +134 -0
  373. package/src/service-worker-interceptor.ts +185 -0
  374. package/src/session-capture.ts +335 -0
  375. package/src/session-export.test.ts +75 -0
  376. package/src/session-export.ts +172 -0
  377. package/src/shadow-dom-crawler.ts +198 -0
  378. package/src/sitemap-writer.ts +65 -0
  379. package/src/sitemap.test.ts +127 -0
  380. package/src/sitemap.ts +233 -0
  381. package/src/skyvern.ts +199 -0
  382. package/src/sqs-queue.ts +129 -0
  383. package/src/sso-token.ts +120 -0
  384. package/src/state-graph.ts +280 -0
  385. package/src/stealth/human-behavior.ts +227 -0
  386. package/src/telemetry.ts +122 -0
  387. package/src/test-executor.test.ts +72 -0
  388. package/src/test-executor.ts +578 -0
  389. package/src/tls-fingerprint.ts +111 -0
  390. package/src/url-pattern-completer.ts +161 -0
  391. package/src/vendor.d.ts +20 -0
  392. package/src/visual-diff-verdict.test.ts +80 -0
  393. package/src/visual-diff-verdict.ts +59 -0
  394. package/src/webhook-notifier.ts +109 -0
  395. package/src/websocket-detector.ts +83 -0
  396. package/src/worker.ts +50 -0
  397. package/tsconfig.json +13 -0
  398. package/types/accessibility-auditor.d.ts +29 -0
  399. package/types/accessibility-tree-crawler.d.ts +44 -0
  400. package/types/advanced-ui-probes.d.ts +47 -0
  401. package/types/ai-login.d.ts +32 -0
  402. package/types/auth-token-rotator.d.ts +47 -0
  403. package/types/auth.d.ts +20 -0
  404. package/types/browser-extension.d.ts +37 -0
  405. package/types/bull-worker.d.ts +6 -0
  406. package/types/captcha-solver.d.ts +16 -0
  407. package/types/color-contrast.d.ts +30 -0
  408. package/types/content-chunker.d.ts +18 -0
  409. package/types/content-dedup.d.ts +23 -0
  410. package/types/cookie-consent.d.ts +25 -0
  411. package/types/cookie-jar.d.ts +20 -0
  412. package/types/crawl-session-manager.d.ts +44 -0
  413. package/types/crawler.d.ts +233 -0
  414. package/types/crud-interaction.d.ts +31 -0
  415. package/types/custom-js-injector.d.ts +42 -0
  416. package/types/dark-mode-capture.d.ts +23 -0
  417. package/types/dead-link-detector.d.ts +37 -0
  418. package/types/dropdown-enumerator.d.ts +20 -0
  419. package/types/element-hash.d.ts +9 -0
  420. package/types/email-otp.d.ts +42 -0
  421. package/types/engine-router.d.ts +47 -0
  422. package/types/export/bulk-export.d.ts +26 -0
  423. package/types/export/vector-export.d.ts +76 -0
  424. package/types/extractors/advanced.d.ts +64 -0
  425. package/types/extractors/artifact-envelope.d.ts +63 -0
  426. package/types/extractors/branding-extractor.d.ts +46 -0
  427. package/types/extractors/summary-extractor.d.ts +10 -0
  428. package/types/failure-analysis-agent.d.ts +16 -0
  429. package/types/feature-flag-detector.d.ts +30 -0
  430. package/types/filter-pagination-crawler.d.ts +52 -0
  431. package/types/form-detector.d.ts +34 -0
  432. package/types/frontier.d.ts +40 -0
  433. package/types/gap-pass.d.ts +36 -0
  434. package/types/graphql-detector.d.ts +39 -0
  435. package/types/http-engine.d.ts +80 -0
  436. package/types/incremental-diff.d.ts +116 -0
  437. package/types/index.d.ts +52 -0
  438. package/types/input-fuzzer.d.ts +30 -0
  439. package/types/interactive-probe.d.ts +144 -0
  440. package/types/job-queue.d.ts +308 -0
  441. package/types/js-coverage.d.ts +29 -0
  442. package/types/js-router-detector.d.ts +17 -0
  443. package/types/language-detector.d.ts +22 -0
  444. package/types/link-classifier.d.ts +23 -0
  445. package/types/live-view.d.ts +36 -0
  446. package/types/locator-extractor.d.ts +46 -0
  447. package/types/logger.d.ts +2 -0
  448. package/types/markdown-extract.d.ts +8 -0
  449. package/types/multi-context-handler.d.ts +55 -0
  450. package/types/native-crawler.d.ts +69 -0
  451. package/types/nested-window-crawler.d.ts +41 -0
  452. package/types/network-action-correlator.d.ts +47 -0
  453. package/types/network-observer.d.ts +49 -0
  454. package/types/network-throttle.d.ts +25 -0
  455. package/types/openapi-generator.d.ts +49 -0
  456. package/types/pdf-extractor.d.ts +28 -0
  457. package/types/performance-metrics-collector.d.ts +50 -0
  458. package/types/performance-metrics.d.ts +52 -0
  459. package/types/phash.d.ts +50 -0
  460. package/types/pii-scrubber.d.ts +21 -0
  461. package/types/playwright-recorder.d.ts +39 -0
  462. package/types/policies/crawl-safety.d.ts +9 -0
  463. package/types/profiles/domain-profile.d.ts +19 -0
  464. package/types/proxy.d.ts +50 -0
  465. package/types/rate-limiter.d.ts +31 -0
  466. package/types/redirect-tracker.d.ts +37 -0
  467. package/types/redis-frontier.d.ts +48 -0
  468. package/types/redis-queue.d.ts +48 -0
  469. package/types/response-cache.d.ts +38 -0
  470. package/types/responsive-detector.d.ts +29 -0
  471. package/types/retry-policy.d.ts +20 -0
  472. package/types/robots-txt.d.ts +9 -0
  473. package/types/screen-reader.d.ts +15 -0
  474. package/types/screenshot-masker.d.ts +28 -0
  475. package/types/seo-checker.d.ts +33 -0
  476. package/types/service-worker-interceptor.d.ts +36 -0
  477. package/types/session-capture.d.ts +39 -0
  478. package/types/session-export.d.ts +49 -0
  479. package/types/shadow-dom-crawler.d.ts +35 -0
  480. package/types/sitemap-writer.d.ts +21 -0
  481. package/types/sitemap.d.ts +51 -0
  482. package/types/skyvern.d.ts +56 -0
  483. package/types/sqs-queue.d.ts +13 -0
  484. package/types/sso-token.d.ts +29 -0
  485. package/types/state-graph.d.ts +59 -0
  486. package/types/stealth/human-behavior.d.ts +20 -0
  487. package/types/telemetry.d.ts +34 -0
  488. package/types/test-executor.d.ts +84 -0
  489. package/types/tls-fingerprint.d.ts +50 -0
  490. package/types/url-pattern-completer.d.ts +28 -0
  491. package/types/visual-diff-verdict.d.ts +40 -0
  492. package/types/webhook-notifier.d.ts +30 -0
  493. package/types/websocket-detector.d.ts +24 -0
  494. package/types/worker.d.ts +1 -0
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Filter state crawling and pagination capture.
3
+ *
4
+ * Discovers filter controls (select, checkbox-group, radio-group, search),
5
+ * applies each meaningful option and records result counts and first-row
6
+ * values, then detects and walks pagination (numbered, load-more, infinite
7
+ * scroll). Also captures sort column behaviour.
8
+ *
9
+ * All per-filter / per-page errors are caught and skipped so one bad control
10
+ * never aborts the full crawl.
11
+ */
12
+
13
+ import type { Page } from 'playwright';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Public interfaces
17
+ // ---------------------------------------------------------------------------
18
+
19
+ export interface FilterOption {
20
+ value: string;
21
+ label: string;
22
+ }
23
+
24
+ export interface FilterDef {
25
+ kind: 'select' | 'checkbox-group' | 'radio-group' | 'search';
26
+ label: string;
27
+ locatorHint: string;
28
+ options: FilterOption[];
29
+ }
30
+
31
+ export interface FilterState {
32
+ filterLabel: string;
33
+ filterValue: string;
34
+ resultCount: number | null;
35
+ firstRowValues: string[];
36
+ }
37
+
38
+ export interface PaginationCapture {
39
+ kind: 'numbered' | 'load-more' | 'infinite-scroll' | 'none';
40
+ totalPages?: number;
41
+ totalItems?: number;
42
+ pageSizes?: number[];
43
+ pages: Array<{ pageNum: number; rowValues: string[] }>;
44
+ }
45
+
46
+ export interface FilterPaginationResult {
47
+ filters: FilterDef[];
48
+ filterStates: FilterState[];
49
+ pagination: PaginationCapture;
50
+ sortColumns: Array<{ header: string; ariaSortAfterClick: string | null }>;
51
+ searchCapture?: { noResultsText: string | null; sampleSuggestions: string[] };
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Internal helpers
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /** Pause for up to `ms` ms or until network is idle, whichever comes first. */
59
+ async function waitForSettle(page: Page, ms = 1500): Promise<void> {
60
+ await Promise.race([
61
+ page.waitForLoadState('networkidle').catch(() => undefined),
62
+ new Promise<void>(r => setTimeout(r, ms)),
63
+ ]);
64
+ }
65
+
66
+ /** Extract visible cell text from the first tbody row, if any. */
67
+ async function captureFirstRow(page: Page): Promise<string[]> {
68
+ try {
69
+ return await page.evaluate(() => {
70
+ const row = document.querySelector('tbody tr');
71
+ if (!row) return [];
72
+ return Array.from(row.querySelectorAll('td')).map(td => td.innerText.trim());
73
+ });
74
+ } catch {
75
+ return [];
76
+ }
77
+ }
78
+
79
+ /** Read a result-count hint from visible page text using common patterns. */
80
+ async function readResultCount(page: Page): Promise<number | null> {
81
+ try {
82
+ const text = await page.evaluate(() => document.body.innerText);
83
+ const match = text.match(/(\d[\d,]*)\s*(result|item|entr|record)/i);
84
+ if (!match) return null;
85
+ return parseInt(match[1].replace(/,/g, ''), 10);
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // discoverFilters
93
+ // ---------------------------------------------------------------------------
94
+
95
+ async function discoverFilters(page: Page): Promise<FilterDef[]> {
96
+ const filters: FilterDef[] = [];
97
+
98
+ // --- native <select> elements outside dialogs ---------------------------------
99
+ try {
100
+ const selectHandles = await page.locator('select:not([role="dialog"] select)').all();
101
+ for (const handle of selectHandles.slice(0, 10)) {
102
+ try {
103
+ const id = await handle.getAttribute('id').catch(() => null);
104
+ const name = await handle.getAttribute('name').catch(() => null);
105
+ const locatorHint = id ? `#${id}` : name ? `select[name="${name}"]` : 'select';
106
+
107
+ // Find associated label
108
+ let label = '';
109
+ if (id) {
110
+ label = await page.locator(`label[for="${id}"]`).innerText().catch(() => '');
111
+ }
112
+ if (!label) {
113
+ label = await handle.evaluate((el: HTMLSelectElement) => {
114
+ const lbl = el.closest('label') ?? el.previousElementSibling;
115
+ return (lbl as HTMLElement | null)?.innerText?.trim() ?? '';
116
+ }).catch(() => '');
117
+ }
118
+ if (!label) label = name ?? id ?? 'filter';
119
+
120
+ // Collect options, skipping default/placeholder entries
121
+ const rawOptions: FilterOption[] = await handle.evaluate((el: HTMLSelectElement) =>
122
+ Array.from(el.options).map(o => ({ value: o.value, label: o.text.trim() })),
123
+ ).catch(() => []);
124
+
125
+ const options = rawOptions.filter(
126
+ o => !(/^all$|^any$|^select|^--/i.test(o.label) || o.value === ''),
127
+ );
128
+
129
+ if (options.length === 0) continue;
130
+
131
+ filters.push({ kind: 'select', label, locatorHint, options });
132
+ if (filters.length >= 10) break;
133
+ } catch {
134
+ // skip this select
135
+ }
136
+ }
137
+ } catch {
138
+ // page.locator failed
139
+ }
140
+
141
+ // --- search inputs ------------------------------------------------------------
142
+ if (filters.length < 10) {
143
+ try {
144
+ const searchHandles = await page
145
+ .locator(
146
+ 'input[type="search"]:not([role="dialog"] input), ' +
147
+ 'input[placeholder*="search" i]:not([role="dialog"] input)',
148
+ )
149
+ .all();
150
+
151
+ for (const handle of searchHandles.slice(0, 10 - filters.length)) {
152
+ try {
153
+ const id = await handle.getAttribute('id').catch(() => null);
154
+ const placeholder = await handle.getAttribute('placeholder').catch(() => null);
155
+ const locatorHint = id ? `#${id}` : placeholder ? `input[placeholder="${placeholder}"]` : 'input[type="search"]';
156
+
157
+ let label = '';
158
+ if (id) {
159
+ label = await page.locator(`label[for="${id}"]`).innerText().catch(() => '');
160
+ }
161
+ if (!label) label = placeholder ?? 'search';
162
+
163
+ filters.push({ kind: 'search', label, locatorHint, options: [] });
164
+ if (filters.length >= 10) break;
165
+ } catch {
166
+ // skip
167
+ }
168
+ }
169
+ } catch {
170
+ // locator failed
171
+ }
172
+ }
173
+
174
+ return filters;
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // applyFilterAndCapture
179
+ // ---------------------------------------------------------------------------
180
+
181
+ async function applyFilterAndCapture(
182
+ page: Page,
183
+ filter: FilterDef,
184
+ option: FilterOption,
185
+ ): Promise<FilterState> {
186
+ const state: FilterState = {
187
+ filterLabel: filter.label,
188
+ filterValue: option.value,
189
+ resultCount: null,
190
+ firstRowValues: [],
191
+ };
192
+
193
+ try {
194
+ if (filter.kind === 'select') {
195
+ await page.locator(filter.locatorHint).selectOption(option.value);
196
+ } else if (filter.kind === 'search') {
197
+ await page.locator(filter.locatorHint).fill(option.value);
198
+ await page.locator(filter.locatorHint).press('Enter');
199
+ }
200
+
201
+ await waitForSettle(page, 1500);
202
+
203
+ state.resultCount = await readResultCount(page);
204
+ state.firstRowValues = await captureFirstRow(page);
205
+ } catch {
206
+ // non-fatal: return partial state
207
+ }
208
+
209
+ // Reset filter
210
+ try {
211
+ if (filter.kind === 'select') {
212
+ const firstOption = await page.locator(filter.locatorHint).evaluate(
213
+ (el: HTMLSelectElement) => el.options[0]?.value ?? '',
214
+ );
215
+ await page.locator(filter.locatorHint).selectOption(firstOption);
216
+ } else if (filter.kind === 'search') {
217
+ await page.locator(filter.locatorHint).fill('');
218
+ await page.locator(filter.locatorHint).press('Enter');
219
+ }
220
+ await waitForSettle(page, 800);
221
+ } catch {
222
+ // ignore reset errors
223
+ }
224
+
225
+ return state;
226
+ }
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // crawlFilterStates
230
+ // ---------------------------------------------------------------------------
231
+
232
+ async function crawlFilterStates(page: Page, filters: FilterDef[]): Promise<FilterState[]> {
233
+ const states: FilterState[] = [];
234
+ let totalStates = 0;
235
+
236
+ for (const filter of filters) {
237
+ if (totalStates >= 15) break;
238
+
239
+ const { options } = filter;
240
+ if (options.length === 0) continue;
241
+
242
+ // Pick first, middle, last — deduplicated, max 3
243
+ const indices = new Set<number>([
244
+ 0,
245
+ Math.floor((options.length - 1) / 2),
246
+ options.length - 1,
247
+ ]);
248
+ const picked = Array.from(indices).slice(0, 3);
249
+
250
+ for (const idx of picked) {
251
+ if (totalStates >= 15) break;
252
+ const option = options[idx];
253
+ try {
254
+ const s = await applyFilterAndCapture(page, filter, option);
255
+ states.push(s);
256
+ } catch {
257
+ // skip
258
+ }
259
+ totalStates++;
260
+ }
261
+ }
262
+
263
+ return states;
264
+ }
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // detectPaginationKind
268
+ // ---------------------------------------------------------------------------
269
+
270
+ async function detectPaginationKind(
271
+ page: Page,
272
+ ): Promise<'numbered' | 'load-more' | 'infinite-scroll' | 'none'> {
273
+ try {
274
+ // Numbered pagination: ARIA page buttons or .pagination
275
+ const numberedCount = await page
276
+ .locator('[aria-label*="page" i], .pagination button, [role="navigation"] button')
277
+ .count()
278
+ .catch(() => 0);
279
+ if (numberedCount > 1) return 'numbered';
280
+
281
+ // Load-more button
282
+ const loadMore = await page
283
+ .locator('button:has-text("Load more"), button:has-text("Show more"), a:has-text("Load more")')
284
+ .count()
285
+ .catch(() => 0);
286
+ if (loadMore > 0) return 'load-more';
287
+
288
+ // Infinite scroll: scrollable content significantly taller than viewport
289
+ const isInfinite = await page.evaluate(() => {
290
+ return document.documentElement.scrollHeight > window.innerHeight * 2;
291
+ }).catch(() => false);
292
+ if (isInfinite) return 'infinite-scroll';
293
+ } catch {
294
+ // fall through
295
+ }
296
+
297
+ return 'none';
298
+ }
299
+
300
+ // ---------------------------------------------------------------------------
301
+ // crawlNumberedPagination
302
+ // ---------------------------------------------------------------------------
303
+
304
+ async function crawlNumberedPagination(page: Page): Promise<PaginationCapture> {
305
+ const pages: Array<{ pageNum: number; rowValues: string[] }> = [];
306
+ const pageSizes: number[] = [];
307
+
308
+ // Read page size selector if present
309
+ try {
310
+ const sizeSelect = page.locator('select[name*="size" i], select[name*="per" i], select[aria-label*="per page" i]');
311
+ const sizeCount = await sizeSelect.count();
312
+ if (sizeCount > 0) {
313
+ const rawSizes = await sizeSelect.first().evaluate((el: HTMLSelectElement) =>
314
+ Array.from(el.options).map(o => parseInt(o.value, 10)).filter(n => !isNaN(n)),
315
+ );
316
+ pageSizes.push(...rawSizes);
317
+ }
318
+ } catch {
319
+ // ignore
320
+ }
321
+
322
+ // Determine total pages from pagination widget
323
+ let totalPages: number | undefined;
324
+ try {
325
+ totalPages = await page.evaluate(() => {
326
+ const buttons = Array.from(
327
+ document.querySelectorAll('[aria-label*="page" i], .pagination button'),
328
+ );
329
+ const nums = buttons
330
+ .map(b => parseInt((b as HTMLElement).innerText.trim(), 10))
331
+ .filter(n => !isNaN(n));
332
+ return nums.length > 0 ? Math.max(...nums) : undefined;
333
+ });
334
+ } catch {
335
+ // leave undefined
336
+ }
337
+
338
+ // Capture first page immediately
339
+ pages.push({ pageNum: 1, rowValues: await captureFirstRow(page) });
340
+
341
+ // Walk pages by clicking Next
342
+ for (let pageNum = 2; pageNum <= 20; pageNum++) {
343
+ try {
344
+ const nextBtn = page.locator(
345
+ 'button[aria-label*="next" i], a[aria-label*="next" i], [role="navigation"] button:last-child',
346
+ ).first();
347
+
348
+ const isDisabled = await nextBtn.isDisabled().catch(() => true);
349
+ if (isDisabled) break;
350
+
351
+ await nextBtn.click();
352
+ await waitForSettle(page, 2000);
353
+
354
+ const rowValues = await captureFirstRow(page);
355
+ pages.push({ pageNum, rowValues });
356
+ } catch {
357
+ break;
358
+ }
359
+ }
360
+
361
+ return {
362
+ kind: 'numbered',
363
+ totalPages,
364
+ pageSizes: pageSizes.length > 0 ? pageSizes : undefined,
365
+ pages,
366
+ };
367
+ }
368
+
369
+ // ---------------------------------------------------------------------------
370
+ // captureInfiniteScroll
371
+ // ---------------------------------------------------------------------------
372
+
373
+ async function captureInfiniteScroll(page: Page): Promise<PaginationCapture> {
374
+ const pages: Array<{ pageNum: number; rowValues: string[] }> = [];
375
+
376
+ let prevRowCount = -1;
377
+ let iteration = 0;
378
+
379
+ while (iteration < 10) {
380
+ try {
381
+ const rowCount = await page.evaluate(() =>
382
+ document.querySelectorAll('tbody tr').length,
383
+ ).catch(() => 0);
384
+
385
+ if (rowCount === prevRowCount) break; // stable
386
+ prevRowCount = rowCount;
387
+
388
+ // Record current first-row snapshot per scroll step
389
+ pages.push({ pageNum: iteration + 1, rowValues: await captureFirstRow(page) });
390
+
391
+ await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
392
+ await new Promise<void>(r => setTimeout(r, 800));
393
+ } catch {
394
+ break;
395
+ }
396
+ iteration++;
397
+ }
398
+
399
+ const totalItems = await page.evaluate(() =>
400
+ document.querySelectorAll('tbody tr').length,
401
+ ).catch(() => undefined);
402
+
403
+ return {
404
+ kind: 'infinite-scroll',
405
+ totalItems: typeof totalItems === 'number' ? totalItems : undefined,
406
+ pages,
407
+ };
408
+ }
409
+
410
+ // ---------------------------------------------------------------------------
411
+ // captureSortColumns
412
+ // ---------------------------------------------------------------------------
413
+
414
+ async function captureSortColumns(
415
+ page: Page,
416
+ ): Promise<Array<{ header: string; ariaSortAfterClick: string | null }>> {
417
+ const results: Array<{ header: string; ariaSortAfterClick: string | null }> = [];
418
+
419
+ try {
420
+ const sortable = await page.locator('[aria-sort]').all();
421
+ for (const th of sortable.slice(0, 5)) {
422
+ try {
423
+ const header = await th.innerText().catch(() => '');
424
+ await th.click();
425
+ await waitForSettle(page, 800);
426
+ const ariaSortAfterClick = await th.getAttribute('aria-sort').catch(() => null);
427
+ results.push({ header: header.trim(), ariaSortAfterClick });
428
+
429
+ // Reset by clicking again (most tables toggle back)
430
+ await th.click().catch(() => undefined);
431
+ await waitForSettle(page, 600);
432
+ } catch {
433
+ // skip this column
434
+ }
435
+ }
436
+ } catch {
437
+ // no sortable columns found
438
+ }
439
+
440
+ return results;
441
+ }
442
+
443
+ // ---------------------------------------------------------------------------
444
+ // crawlFiltersAndPagination — public orchestrator
445
+ // ---------------------------------------------------------------------------
446
+
447
+ export async function crawlFiltersAndPagination(page: Page): Promise<FilterPaginationResult> {
448
+ // 1. Discover filters
449
+ let filters: FilterDef[] = [];
450
+ try {
451
+ filters = await discoverFilters(page);
452
+ } catch {
453
+ // proceed with empty filters
454
+ }
455
+
456
+ // 2. Apply filters and capture states
457
+ let filterStates: FilterState[] = [];
458
+ try {
459
+ filterStates = await crawlFilterStates(page, filters);
460
+ } catch {
461
+ // proceed with empty states
462
+ }
463
+
464
+ // 3. Detect and crawl pagination
465
+ let pagination: PaginationCapture = { kind: 'none', pages: [] };
466
+ try {
467
+ const kind = await detectPaginationKind(page);
468
+
469
+ if (kind === 'numbered') {
470
+ pagination = await crawlNumberedPagination(page);
471
+ } else if (kind === 'load-more') {
472
+ // Treat load-more similarly to infinite scroll capture
473
+ pagination = await captureInfiniteScroll(page);
474
+ pagination = { ...pagination, kind: 'load-more' };
475
+ } else if (kind === 'infinite-scroll') {
476
+ pagination = await captureInfiniteScroll(page);
477
+ } else {
478
+ // Capture single-page rows
479
+ const rowValues = await captureFirstRow(page);
480
+ pagination = { kind: 'none', pages: rowValues.length > 0 ? [{ pageNum: 1, rowValues }] : [] };
481
+ }
482
+ } catch {
483
+ // leave pagination as none
484
+ }
485
+
486
+ // 4. Sort columns
487
+ let sortColumns: Array<{ header: string; ariaSortAfterClick: string | null }> = [];
488
+ try {
489
+ sortColumns = await captureSortColumns(page);
490
+ } catch {
491
+ // proceed with empty sort
492
+ }
493
+
494
+ return {
495
+ filters,
496
+ filterStates,
497
+ pagination,
498
+ sortColumns,
499
+ };
500
+ }
@@ -0,0 +1,103 @@
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
+
11
+ import type { Page } from 'playwright';
12
+
13
+ export interface FormField {
14
+ name: string;
15
+ type: string; // text, email, password, number, select, textarea, checkbox, radio, file, date
16
+ label?: string;
17
+ required: boolean;
18
+ placeholder?: string;
19
+ minLength?: number;
20
+ maxLength?: number;
21
+ min?: string;
22
+ max?: string;
23
+ pattern?: string;
24
+ options?: string[]; // for select/radio
25
+ autocomplete?: string;
26
+ }
27
+
28
+ export interface DetectedForm {
29
+ index: number;
30
+ action?: string;
31
+ method: string;
32
+ fields: FormField[];
33
+ submitText?: string;
34
+ hasFileUpload: boolean;
35
+ purpose: 'login' | 'search' | 'registration' | 'contact' | 'checkout' | 'settings' | 'unknown';
36
+ }
37
+
38
+ function guessPurpose(fields: FormField[], action?: string): DetectedForm['purpose'] {
39
+ const names = fields.map(f => f.name?.toLowerCase() + (f.label ?? '').toLowerCase()).join(' ');
40
+ if (/password/.test(names) && /email|username|user/.test(names) && fields.length <= 4) return 'login';
41
+ if (/password/.test(names) && /confirm|repeat/.test(names)) return 'registration';
42
+ if (/search|query|q\b/.test(names) && fields.length <= 3) return 'search';
43
+ if (/card|payment|cvv|expir/.test(names)) return 'checkout';
44
+ if (/message|subject|contact/.test(names)) return 'contact';
45
+ if (/setting|preference|profile/.test(names)) return 'settings';
46
+ return 'unknown';
47
+ }
48
+
49
+ export async function detectForms(page: Page): Promise<DetectedForm[]> {
50
+ try {
51
+ return await page.$$eval('form', (forms) => forms.map((form: any, index: number) => {
52
+ const inputs = Array.from(form.querySelectorAll('input,select,textarea,button[type="submit"]'));
53
+ const fields: any[] = [];
54
+
55
+ for (const input of inputs as any[]) {
56
+ const tag = input.tagName.toLowerCase();
57
+ const type = tag === 'button' ? 'submit' : (input.type || tag);
58
+ if (['submit', 'reset', 'button', 'hidden'].includes(type)) continue;
59
+
60
+ // Find associated label
61
+ let label = '';
62
+ if (input.id) {
63
+ const lbl = form.querySelector(`label[for="${input.id}"]`);
64
+ if (lbl) label = (lbl.textContent ?? '').trim();
65
+ }
66
+ if (!label) {
67
+ const parent = input.closest('label');
68
+ if (parent) label = (parent.textContent ?? '').replace(input.value ?? '', '').trim().slice(0, 80);
69
+ }
70
+
71
+ const options = tag === 'select'
72
+ ? Array.from(input.options ?? []).map((o: any) => o.text?.trim()).filter(Boolean).slice(0, 20)
73
+ : tag === 'input' && type === 'radio'
74
+ ? [input.value]
75
+ : undefined;
76
+
77
+ fields.push({
78
+ name: input.name || input.id || '',
79
+ type,
80
+ label: label || undefined,
81
+ required: !!input.required,
82
+ placeholder: input.placeholder || undefined,
83
+ minLength: input.minLength > 0 ? input.minLength : undefined,
84
+ maxLength: input.maxLength > 0 && input.maxLength < 524288 ? input.maxLength : undefined,
85
+ min: input.min || undefined,
86
+ max: input.max || undefined,
87
+ pattern: input.pattern || undefined,
88
+ options,
89
+ autocomplete: input.autocomplete || undefined,
90
+ });
91
+ }
92
+
93
+ const submitBtn = form.querySelector('button[type="submit"],input[type="submit"]') as any;
94
+ const hasFileUpload = !!form.querySelector('input[type="file"]');
95
+ const action = form.action || undefined;
96
+ const method = (form.method || 'get').toUpperCase();
97
+
98
+ return { index, action, method, fields, submitText: submitBtn?.textContent?.trim() || submitBtn?.value || undefined, hasFileUpload };
99
+ })).then(forms => forms.map((f: any) => ({ ...f, purpose: guessPurpose(f.fields, f.action) })));
100
+ } catch {
101
+ return [];
102
+ }
103
+ }
@@ -0,0 +1,64 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { CrawlFrontier } from './frontier.js';
4
+
5
+ test('CrawlFrontier: BFS dequeues in FIFO order, identical to a plain array shift()', () => {
6
+ const f = new CrawlFrontier('BFS', ['https://a.com/1', 'https://a.com/2']);
7
+ f.push('https://a.com/3');
8
+ assert.equal(f.shift(), 'https://a.com/1');
9
+ assert.equal(f.shift(), 'https://a.com/2');
10
+ assert.equal(f.shift(), 'https://a.com/3');
11
+ assert.equal(f.shift(), undefined);
12
+ });
13
+
14
+ test('CrawlFrontier: DFS dequeues in LIFO order', () => {
15
+ const f = new CrawlFrontier('DFS', ['https://a.com/1', 'https://a.com/2']);
16
+ f.push('https://a.com/3');
17
+ assert.equal(f.shift(), 'https://a.com/3');
18
+ assert.equal(f.shift(), 'https://a.com/2');
19
+ assert.equal(f.shift(), 'https://a.com/1');
20
+ });
21
+
22
+ test('CrawlFrontier: BEST_FIRST prefers shallower paths over deeper ones', () => {
23
+ const f = new CrawlFrontier('BEST_FIRST', [
24
+ 'https://a.com/products/electronics/phones/model-x',
25
+ 'https://a.com/about',
26
+ ]);
27
+ assert.equal(f.shift(), 'https://a.com/about');
28
+ assert.equal(f.shift(), 'https://a.com/products/electronics/phones/model-x');
29
+ });
30
+
31
+ test('CrawlFrontier: BEST_FIRST boosts URLs matching keywords ahead of shallower non-matching ones', () => {
32
+ const f = new CrawlFrontier('BEST_FIRST', ['https://a.com/'], ['pricing']);
33
+ f.push('https://a.com/deep/nested/pricing/plans');
34
+ // "/" has depth 0 (score 0); the pricing URL has depth 4 (score -4) but +10 for the
35
+ // keyword match, netting +6 — the keyword-matching page should win despite depth.
36
+ assert.equal(f.shift(), 'https://a.com/deep/nested/pricing/plans');
37
+ assert.equal(f.shift(), 'https://a.com/');
38
+ });
39
+
40
+ test('CrawlFrontier: length reflects push/shift correctly across all strategies', () => {
41
+ for (const strategy of ['BFS', 'DFS', 'BEST_FIRST'] as const) {
42
+ const f = new CrawlFrontier(strategy, ['https://a.com/1']);
43
+ assert.equal(f.length, 1);
44
+ f.push('https://a.com/2');
45
+ assert.equal(f.length, 2);
46
+ f.shift();
47
+ assert.equal(f.length, 1);
48
+ }
49
+ });
50
+
51
+ test('CrawlFrontier: filter returns URLs matching the predicate without mutating the frontier', () => {
52
+ const f = new CrawlFrontier('BFS', ['https://a.com/1', 'https://a.com/2', 'https://a.com/3']);
53
+ const matched = f.filter((u) => u.endsWith('/2'));
54
+ assert.deepEqual(matched, ['https://a.com/2']);
55
+ assert.equal(f.length, 3); // unchanged
56
+ });
57
+
58
+ test('CrawlFrontier: malformed URLs never throw when scored under BEST_FIRST', () => {
59
+ assert.doesNotThrow(() => {
60
+ const f = new CrawlFrontier('BEST_FIRST', ['not-a-valid-url', 'https://a.com/ok']);
61
+ f.shift();
62
+ f.shift();
63
+ });
64
+ });