@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.
- package/Dockerfile +82 -0
- package/README.md +10 -2
- package/dist/accessibility-auditor.d.ts +29 -0
- package/dist/accessibility-auditor.js +57 -0
- package/dist/accessibility-auditor.js.map +7 -0
- package/dist/accessibility-tree-crawler.d.ts +44 -0
- package/dist/accessibility-tree-crawler.js +156 -0
- package/dist/accessibility-tree-crawler.js.map +7 -0
- package/dist/advanced-ui-probes.d.ts +47 -0
- package/dist/advanced-ui-probes.js +310 -0
- package/dist/advanced-ui-probes.js.map +7 -0
- package/dist/ai-login.d.ts +32 -0
- package/dist/ai-login.js +250 -0
- package/dist/ai-login.js.map +7 -0
- package/dist/auth-token-rotator.d.ts +47 -0
- package/dist/auth-token-rotator.js +58 -0
- package/dist/auth-token-rotator.js.map +7 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +348 -0
- package/dist/auth.js.map +7 -0
- package/dist/browser-extension.d.ts +37 -0
- package/dist/browser-extension.js +113 -0
- package/dist/browser-extension.js.map +7 -0
- package/dist/captcha-solver.d.ts +16 -0
- package/dist/captcha-solver.js +105 -0
- package/dist/captcha-solver.js.map +7 -0
- package/dist/color-contrast.d.ts +30 -0
- package/dist/color-contrast.js +78 -0
- package/dist/color-contrast.js.map +7 -0
- package/dist/content-chunker.d.ts +18 -0
- package/dist/content-chunker.js +152 -0
- package/dist/content-chunker.js.map +7 -0
- package/dist/content-dedup.d.ts +23 -0
- package/dist/content-dedup.js +60 -0
- package/dist/content-dedup.js.map +7 -0
- package/dist/cookie-consent.d.ts +25 -0
- package/dist/cookie-consent.js +78 -0
- package/dist/cookie-consent.js.map +7 -0
- package/dist/cookie-jar.d.ts +20 -0
- package/dist/cookie-jar.js +61 -0
- package/dist/cookie-jar.js.map +7 -0
- package/dist/crawl-session-manager.d.ts +44 -0
- package/dist/crawl-session-manager.js +145 -0
- package/dist/crawl-session-manager.js.map +7 -0
- package/dist/crawler.d.ts +233 -0
- package/dist/crawler.js +6239 -0
- package/dist/crawler.js.map +7 -0
- package/dist/crud-interaction.d.ts +31 -0
- package/dist/crud-interaction.js +264 -0
- package/dist/crud-interaction.js.map +7 -0
- package/dist/custom-js-injector.d.ts +42 -0
- package/dist/custom-js-injector.js +50 -0
- package/dist/custom-js-injector.js.map +7 -0
- package/dist/dark-mode-capture.d.ts +23 -0
- package/dist/dark-mode-capture.js +75 -0
- package/dist/dark-mode-capture.js.map +7 -0
- package/dist/dead-link-detector.d.ts +37 -0
- package/dist/dead-link-detector.js +50 -0
- package/dist/dead-link-detector.js.map +7 -0
- package/dist/dropdown-enumerator.d.ts +20 -0
- package/dist/dropdown-enumerator.js +230 -0
- package/dist/dropdown-enumerator.js.map +7 -0
- package/dist/element-hash.d.ts +9 -0
- package/dist/element-hash.js +9 -0
- package/dist/element-hash.js.map +7 -0
- package/dist/email-otp.d.ts +42 -0
- package/dist/email-otp.js +177 -0
- package/dist/email-otp.js.map +7 -0
- package/dist/engine-router.d.ts +47 -0
- package/dist/engine-router.js +94 -0
- package/dist/engine-router.js.map +7 -0
- package/dist/export/bulk-export.d.ts +26 -0
- package/dist/export/bulk-export.js +76 -0
- package/dist/export/bulk-export.js.map +7 -0
- package/dist/export/vector-export.d.ts +76 -0
- package/dist/export/vector-export.js +85 -0
- package/dist/export/vector-export.js.map +7 -0
- package/dist/extractors/advanced.d.ts +64 -0
- package/dist/extractors/advanced.js +265 -0
- package/dist/extractors/advanced.js.map +7 -0
- package/dist/extractors/artifact-envelope.d.ts +63 -0
- package/dist/extractors/artifact-envelope.js +63 -0
- package/dist/extractors/artifact-envelope.js.map +7 -0
- package/dist/extractors/branding-extractor.d.ts +46 -0
- package/dist/extractors/branding-extractor.js +104 -0
- package/dist/extractors/branding-extractor.js.map +7 -0
- package/dist/extractors/summary-extractor.d.ts +10 -0
- package/dist/extractors/summary-extractor.js +36 -0
- package/dist/extractors/summary-extractor.js.map +7 -0
- package/dist/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +42 -0
- package/dist/failure-analysis-agent.js.map +7 -0
- package/dist/feature-flag-detector.d.ts +30 -0
- package/dist/feature-flag-detector.js +55 -0
- package/dist/feature-flag-detector.js.map +7 -0
- package/dist/filter-pagination-crawler.d.ts +52 -0
- package/dist/filter-pagination-crawler.js +299 -0
- package/dist/filter-pagination-crawler.js.map +7 -0
- package/dist/form-detector.d.ts +34 -0
- package/dist/form-detector.js +58 -0
- package/dist/form-detector.js.map +7 -0
- package/dist/frontier.d.ts +40 -0
- package/dist/frontier.js +103 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +36 -0
- package/dist/gap-pass.js +115 -0
- package/dist/gap-pass.js.map +7 -0
- package/dist/graphql-detector.d.ts +39 -0
- package/dist/graphql-detector.js +86 -0
- package/dist/graphql-detector.js.map +7 -0
- package/dist/http-engine.d.ts +80 -0
- package/dist/http-engine.js +318 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +116 -0
- package/dist/incremental-diff.js +137 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +7 -0
- package/dist/input-fuzzer.d.ts +30 -0
- package/dist/input-fuzzer.js +105 -0
- package/dist/input-fuzzer.js.map +7 -0
- package/dist/interactive-probe.d.ts +144 -0
- package/dist/interactive-probe.js +865 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +297 -0
- package/dist/job-queue.js +3812 -0
- package/dist/job-queue.js.map +7 -0
- package/dist/js-coverage.d.ts +29 -0
- package/dist/js-coverage.js +56 -0
- package/dist/js-coverage.js.map +7 -0
- package/dist/js-router-detector.d.ts +17 -0
- package/dist/js-router-detector.js +90 -0
- package/dist/js-router-detector.js.map +7 -0
- package/dist/language-detector.d.ts +22 -0
- package/dist/language-detector.js +57 -0
- package/dist/language-detector.js.map +7 -0
- package/dist/link-classifier.d.ts +23 -0
- package/dist/link-classifier.js +40 -0
- package/dist/link-classifier.js.map +7 -0
- package/dist/live-view.d.ts +36 -0
- package/dist/live-view.js +105 -0
- package/dist/live-view.js.map +7 -0
- package/dist/locator-extractor.d.ts +46 -0
- package/dist/locator-extractor.js +220 -0
- package/dist/locator-extractor.js.map +7 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +7 -0
- package/dist/markdown-extract.d.ts +8 -0
- package/dist/markdown-extract.js +54 -0
- package/dist/markdown-extract.js.map +7 -0
- package/dist/multi-context-handler.d.ts +55 -0
- package/dist/multi-context-handler.js +182 -0
- package/dist/multi-context-handler.js.map +7 -0
- package/dist/native-crawler.d.ts +69 -0
- package/dist/native-crawler.js +839 -0
- package/dist/native-crawler.js.map +7 -0
- package/dist/nested-window-crawler.d.ts +41 -0
- package/dist/nested-window-crawler.js +172 -0
- package/dist/nested-window-crawler.js.map +7 -0
- package/dist/network-action-correlator.d.ts +47 -0
- package/dist/network-action-correlator.js +209 -0
- package/dist/network-action-correlator.js.map +7 -0
- package/dist/network-observer.d.ts +49 -0
- package/dist/network-observer.js +149 -0
- package/dist/network-observer.js.map +7 -0
- package/dist/network-throttle.d.ts +25 -0
- package/dist/network-throttle.js +34 -0
- package/dist/network-throttle.js.map +7 -0
- package/dist/openapi-generator.d.ts +49 -0
- package/dist/openapi-generator.js +89 -0
- package/dist/openapi-generator.js.map +7 -0
- package/dist/pdf-extractor.d.ts +28 -0
- package/dist/pdf-extractor.js +81 -0
- package/dist/pdf-extractor.js.map +7 -0
- package/dist/performance-metrics-collector.d.ts +50 -0
- package/dist/performance-metrics-collector.js +155 -0
- package/dist/performance-metrics-collector.js.map +7 -0
- package/dist/performance-metrics.d.ts +52 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +50 -0
- package/dist/phash.js +136 -0
- package/dist/phash.js.map +7 -0
- package/dist/pii-scrubber.d.ts +21 -0
- package/dist/pii-scrubber.js +75 -0
- package/dist/pii-scrubber.js.map +7 -0
- package/dist/playwright-recorder.d.ts +39 -0
- package/dist/playwright-recorder.js +123 -0
- package/dist/playwright-recorder.js.map +7 -0
- package/dist/policies/crawl-safety.d.ts +9 -0
- package/dist/policies/crawl-safety.js +41 -0
- package/dist/policies/crawl-safety.js.map +7 -0
- package/dist/profiles/domain-profile.d.ts +19 -0
- package/dist/profiles/domain-profile.js +160 -0
- package/dist/profiles/domain-profile.js.map +7 -0
- package/dist/proxy.d.ts +50 -0
- package/dist/proxy.js +165 -0
- package/dist/proxy.js.map +7 -0
- package/dist/rate-limiter.d.ts +31 -0
- package/dist/rate-limiter.js +64 -0
- package/dist/rate-limiter.js.map +7 -0
- package/dist/redirect-tracker.d.ts +37 -0
- package/dist/redirect-tracker.js +65 -0
- package/dist/redirect-tracker.js.map +7 -0
- package/dist/redis-frontier.d.ts +48 -0
- package/dist/redis-frontier.js +126 -0
- package/dist/redis-frontier.js.map +7 -0
- package/dist/redis-queue.d.ts +48 -0
- package/dist/redis-queue.js +165 -0
- package/dist/redis-queue.js.map +7 -0
- package/dist/response-cache.d.ts +38 -0
- package/dist/response-cache.js +140 -0
- package/dist/response-cache.js.map +7 -0
- package/dist/responsive-detector.d.ts +29 -0
- package/dist/responsive-detector.js +46 -0
- package/dist/responsive-detector.js.map +7 -0
- package/dist/retry-policy.d.ts +20 -0
- package/dist/retry-policy.js +54 -0
- package/dist/retry-policy.js.map +7 -0
- package/dist/robots-txt.d.ts +9 -0
- package/dist/robots-txt.js +77 -0
- package/dist/robots-txt.js.map +7 -0
- package/dist/screen-reader.d.ts +15 -0
- package/dist/screen-reader.js +48 -0
- package/dist/screen-reader.js.map +7 -0
- package/dist/screenshot-masker.d.ts +28 -0
- package/dist/screenshot-masker.js +62 -0
- package/dist/screenshot-masker.js.map +7 -0
- package/dist/seo-checker.d.ts +33 -0
- package/dist/seo-checker.js +91 -0
- package/dist/seo-checker.js.map +7 -0
- package/dist/service-worker-interceptor.d.ts +36 -0
- package/dist/service-worker-interceptor.js +136 -0
- package/dist/service-worker-interceptor.js.map +7 -0
- package/dist/session-capture.d.ts +39 -0
- package/dist/session-capture.js +274 -0
- package/dist/session-capture.js.map +7 -0
- package/dist/session-export.d.ts +49 -0
- package/dist/session-export.js +117 -0
- package/dist/session-export.js.map +7 -0
- package/dist/shadow-dom-crawler.d.ts +35 -0
- package/dist/shadow-dom-crawler.js +132 -0
- package/dist/shadow-dom-crawler.js.map +7 -0
- package/dist/sitemap-writer.d.ts +21 -0
- package/dist/sitemap-writer.js +47 -0
- package/dist/sitemap-writer.js.map +7 -0
- package/dist/sitemap.d.ts +51 -0
- package/dist/sitemap.js +160 -0
- package/dist/sitemap.js.map +7 -0
- package/dist/skyvern.d.ts +56 -0
- package/dist/skyvern.js +129 -0
- package/dist/skyvern.js.map +7 -0
- package/dist/sqs-queue.d.ts +13 -0
- package/dist/sqs-queue.js +115 -0
- package/dist/sqs-queue.js.map +7 -0
- package/dist/sso-token.d.ts +29 -0
- package/dist/sso-token.js +76 -0
- package/dist/sso-token.js.map +7 -0
- package/dist/state-graph.d.ts +59 -0
- package/dist/state-graph.js +193 -0
- package/dist/state-graph.js.map +7 -0
- package/dist/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +202 -0
- package/dist/stealth/human-behavior.js.map +7 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +77 -0
- package/dist/telemetry.js.map +7 -0
- package/dist/test-executor.d.ts +81 -0
- package/dist/test-executor.js +443 -0
- package/dist/test-executor.js.map +7 -0
- package/dist/tls-fingerprint.d.ts +50 -0
- package/dist/tls-fingerprint.js +81 -0
- package/dist/tls-fingerprint.js.map +7 -0
- package/dist/url-pattern-completer.d.ts +28 -0
- package/dist/url-pattern-completer.js +122 -0
- package/dist/url-pattern-completer.js.map +7 -0
- package/dist/visual-diff-verdict.d.ts +40 -0
- package/dist/visual-diff-verdict.js +27 -0
- package/dist/visual-diff-verdict.js.map +7 -0
- package/dist/webhook-notifier.d.ts +30 -0
- package/dist/webhook-notifier.js +76 -0
- package/dist/webhook-notifier.js.map +7 -0
- package/dist/websocket-detector.d.ts +24 -0
- package/dist/websocket-detector.js +60 -0
- package/dist/websocket-detector.js.map +7 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +33 -0
- package/dist/worker.js.map +7 -0
- package/package.json +49 -4
- package/src/accessibility-auditor.ts +102 -0
- package/src/accessibility-tree-crawler.ts +246 -0
- package/src/advanced-ui-probes.ts +470 -0
- package/src/ai-login.ts +298 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +418 -0
- package/src/browser-extension.ts +186 -0
- package/src/captcha-solver.ts +143 -0
- package/src/color-contrast.ts +116 -0
- package/src/content-chunker.ts +170 -0
- package/src/content-dedup.ts +99 -0
- package/src/cookie-consent.ts +106 -0
- package/src/cookie-jar.ts +91 -0
- package/src/crawl-session-manager.ts +231 -0
- package/src/crawler.ts +7123 -0
- package/src/crawler.ts.rej +12 -0
- package/src/crud-interaction.ts +386 -0
- package/src/custom-js-injector.ts +93 -0
- package/src/dark-mode-capture.ts +110 -0
- package/src/dead-link-detector.ts +76 -0
- package/src/dropdown-enumerator.ts +312 -0
- package/src/element-hash.ts +16 -0
- package/src/email-otp.ts +252 -0
- package/src/engine-router.ts +141 -0
- package/src/export/bulk-export.ts +85 -0
- package/src/export/vector-export.ts +160 -0
- package/src/extractors/advanced.ts +309 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/extractors/branding-extractor.ts +159 -0
- package/src/extractors/summary-extractor.ts +49 -0
- package/src/failure-analysis-agent.ts +51 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/filter-pagination-crawler.ts +500 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +144 -0
- package/src/gap-pass.ts +165 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +453 -0
- package/src/incremental-diff.ts +259 -0
- package/src/index.ts +52 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +1136 -0
- package/src/job-queue.test.ts +187 -0
- package/src/job-queue.ts +4504 -0
- package/src/js-coverage.ts +87 -0
- package/src/js-router-detector.ts +112 -0
- package/src/language-detector.ts +82 -0
- package/src/link-classifier.ts +65 -0
- package/src/live-view.ts +146 -0
- package/src/locator-extractor.ts +309 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/multi-context-handler.ts +296 -0
- package/src/native-crawler.ts +1108 -0
- package/src/nested-window-crawler.ts +310 -0
- package/src/network-action-correlator.ts +320 -0
- package/src/network-observer.ts +204 -0
- package/src/network-throttle.ts +69 -0
- package/src/openapi-generator.ts +135 -0
- package/src/pdf-extractor.ts +123 -0
- package/src/performance-metrics-collector.ts +243 -0
- package/src/performance-metrics.ts +134 -0
- package/src/phash.ts +184 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +52 -0
- package/src/profiles/domain-profile.ts +190 -0
- package/src/proxy.ts +238 -0
- package/src/rate-limiter.ts +87 -0
- package/src/redirect-tracker.ts +97 -0
- package/src/redis-frontier.ts +143 -0
- package/src/redis-queue.ts +208 -0
- package/src/response-cache.ts +150 -0
- package/src/responsive-detector.ts +92 -0
- package/src/retry-policy.ts +87 -0
- package/src/robots-txt.ts +96 -0
- package/src/screen-reader.ts +98 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/service-worker-interceptor.ts +185 -0
- package/src/session-capture.ts +335 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/shadow-dom-crawler.ts +198 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +127 -0
- package/src/sitemap.ts +233 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +129 -0
- package/src/sso-token.ts +120 -0
- package/src/state-graph.ts +280 -0
- package/src/stealth/human-behavior.ts +227 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +578 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +161 -0
- package/src/vendor.d.ts +20 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +59 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +50 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -0
- package/types/accessibility-tree-crawler.d.ts +44 -0
- package/types/advanced-ui-probes.d.ts +47 -0
- package/types/ai-login.d.ts +32 -0
- package/types/auth-token-rotator.d.ts +47 -0
- package/types/auth.d.ts +20 -0
- package/types/browser-extension.d.ts +37 -0
- package/types/bull-worker.d.ts +6 -0
- package/types/captcha-solver.d.ts +16 -0
- package/types/color-contrast.d.ts +30 -0
- package/types/content-chunker.d.ts +18 -0
- package/types/content-dedup.d.ts +23 -0
- package/types/cookie-consent.d.ts +25 -0
- package/types/cookie-jar.d.ts +20 -0
- package/types/crawl-session-manager.d.ts +44 -0
- package/types/crawler.d.ts +233 -0
- package/types/crud-interaction.d.ts +31 -0
- package/types/custom-js-injector.d.ts +42 -0
- package/types/dark-mode-capture.d.ts +23 -0
- package/types/dead-link-detector.d.ts +37 -0
- package/types/dropdown-enumerator.d.ts +20 -0
- package/types/element-hash.d.ts +9 -0
- package/types/email-otp.d.ts +42 -0
- package/types/engine-router.d.ts +47 -0
- package/types/export/bulk-export.d.ts +26 -0
- package/types/export/vector-export.d.ts +76 -0
- package/types/extractors/advanced.d.ts +64 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/extractors/branding-extractor.d.ts +46 -0
- package/types/extractors/summary-extractor.d.ts +10 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/filter-pagination-crawler.d.ts +52 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +40 -0
- package/types/gap-pass.d.ts +36 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +116 -0
- package/types/index.d.ts +52 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +144 -0
- package/types/job-queue.d.ts +308 -0
- package/types/js-coverage.d.ts +29 -0
- package/types/js-router-detector.d.ts +17 -0
- package/types/language-detector.d.ts +22 -0
- package/types/link-classifier.d.ts +23 -0
- package/types/live-view.d.ts +36 -0
- package/types/locator-extractor.d.ts +46 -0
- package/types/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/multi-context-handler.d.ts +55 -0
- package/types/native-crawler.d.ts +69 -0
- package/types/nested-window-crawler.d.ts +41 -0
- package/types/network-action-correlator.d.ts +47 -0
- package/types/network-observer.d.ts +49 -0
- package/types/network-throttle.d.ts +25 -0
- package/types/openapi-generator.d.ts +49 -0
- package/types/pdf-extractor.d.ts +28 -0
- package/types/performance-metrics-collector.d.ts +50 -0
- package/types/performance-metrics.d.ts +52 -0
- package/types/phash.d.ts +50 -0
- package/types/pii-scrubber.d.ts +21 -0
- package/types/playwright-recorder.d.ts +39 -0
- package/types/policies/crawl-safety.d.ts +9 -0
- package/types/profiles/domain-profile.d.ts +19 -0
- package/types/proxy.d.ts +50 -0
- package/types/rate-limiter.d.ts +31 -0
- package/types/redirect-tracker.d.ts +37 -0
- package/types/redis-frontier.d.ts +48 -0
- package/types/redis-queue.d.ts +48 -0
- package/types/response-cache.d.ts +38 -0
- package/types/responsive-detector.d.ts +29 -0
- package/types/retry-policy.d.ts +20 -0
- package/types/robots-txt.d.ts +9 -0
- package/types/screen-reader.d.ts +15 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/service-worker-interceptor.d.ts +36 -0
- package/types/session-capture.d.ts +39 -0
- package/types/session-export.d.ts +49 -0
- package/types/shadow-dom-crawler.d.ts +35 -0
- package/types/sitemap-writer.d.ts +21 -0
- package/types/sitemap.d.ts +51 -0
- package/types/skyvern.d.ts +56 -0
- package/types/sqs-queue.d.ts +13 -0
- package/types/sso-token.d.ts +29 -0
- package/types/state-graph.d.ts +59 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +84 -0
- package/types/tls-fingerprint.d.ts +50 -0
- package/types/url-pattern-completer.d.ts +28 -0
- package/types/visual-diff-verdict.d.ts +40 -0
- package/types/webhook-notifier.d.ts +30 -0
- package/types/websocket-detector.d.ts +24 -0
- package/types/worker.d.ts +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
function parseNotes(notes) {
|
|
2
|
+
if (!notes) return {};
|
|
3
|
+
try {
|
|
4
|
+
const parsed = typeof notes === "string" ? JSON.parse(notes) : notes;
|
|
5
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
6
|
+
} catch {
|
|
7
|
+
}
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
function mapRole(role) {
|
|
11
|
+
if (!role) return null;
|
|
12
|
+
const r = role.toLowerCase().trim();
|
|
13
|
+
if (r === "button" || r === "submit" || r === "reset") return "button";
|
|
14
|
+
if (r === "link" || r === "a" || r === "anchor") return "link";
|
|
15
|
+
if (r === "textbox" || r === "input" || r === "text" || r === "email" || r === "password" || r === "tel" || r === "number" || r === "search") return "textbox";
|
|
16
|
+
if (r === "combobox" || r === "select" || r === "dropdown") return "combobox";
|
|
17
|
+
if (r === "checkbox") return "checkbox";
|
|
18
|
+
if (r === "radio") return "radio";
|
|
19
|
+
if (r === "heading" || r === "h1" || r === "h2" || r === "h3" || r === "h4" || r === "h5" || r === "h6") return "heading";
|
|
20
|
+
if (r === "img" || r === "image") return "img";
|
|
21
|
+
if (r === "nav" || r === "navigation") return "navigation";
|
|
22
|
+
return r;
|
|
23
|
+
}
|
|
24
|
+
function toKey(text) {
|
|
25
|
+
return text.replace(/[^a-zA-Z0-9\s]/g, " ").replace(/\s+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toLowerCase()).replace(/\s/g, "").slice(0, 40) || "element";
|
|
26
|
+
}
|
|
27
|
+
function elementToPlaywrightLocator(element) {
|
|
28
|
+
const attrs = parseNotes(element.notes);
|
|
29
|
+
const testId = attrs["data-testid"];
|
|
30
|
+
const ariaLabel = attrs["aria-label"] || element.meaning;
|
|
31
|
+
const id = attrs["id"];
|
|
32
|
+
const placeholder = attrs["placeholder"];
|
|
33
|
+
const name = attrs["name"];
|
|
34
|
+
const text = (element.text ?? "").trim();
|
|
35
|
+
const meaning = (element.meaning ?? "").trim();
|
|
36
|
+
const role = mapRole(element.role);
|
|
37
|
+
const ariaState = {};
|
|
38
|
+
if (element.ariaState && typeof element.ariaState === "object") {
|
|
39
|
+
for (const [k, v] of Object.entries(element.ariaState)) {
|
|
40
|
+
if (typeof v === "string") ariaState[k] = v;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const label = meaning || text || testId || ariaLabel || placeholder || id || name || "element";
|
|
44
|
+
const key = toKey(label);
|
|
45
|
+
if (testId) {
|
|
46
|
+
return {
|
|
47
|
+
key,
|
|
48
|
+
expression: `page.getByTestId(${JSON.stringify(testId)})`,
|
|
49
|
+
elementType: role ?? "element",
|
|
50
|
+
confidence: "high",
|
|
51
|
+
source: "testid",
|
|
52
|
+
description: meaning || testId
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (role === "textbox" || role === "combobox") {
|
|
56
|
+
if (ariaLabel) {
|
|
57
|
+
return {
|
|
58
|
+
key,
|
|
59
|
+
expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
|
|
60
|
+
elementType: "input",
|
|
61
|
+
confidence: "high",
|
|
62
|
+
source: "label",
|
|
63
|
+
description: ariaLabel
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (placeholder) {
|
|
67
|
+
return {
|
|
68
|
+
key,
|
|
69
|
+
expression: `page.getByPlaceholder(${JSON.stringify(placeholder)})`,
|
|
70
|
+
elementType: "input",
|
|
71
|
+
confidence: "high",
|
|
72
|
+
source: "placeholder",
|
|
73
|
+
description: placeholder
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if ((role === "checkbox" || role === "radio") && meaning) {
|
|
78
|
+
const isChecked = ariaState["checked"];
|
|
79
|
+
const stateOpt = isChecked === "true" ? `, { name: ${JSON.stringify(meaning)}, checked: true }` : isChecked === "false" ? `, { name: ${JSON.stringify(meaning)}, checked: false }` : `, { name: ${JSON.stringify(meaning)} }`;
|
|
80
|
+
return {
|
|
81
|
+
key,
|
|
82
|
+
expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,
|
|
83
|
+
elementType: role,
|
|
84
|
+
confidence: "high",
|
|
85
|
+
source: "role-name",
|
|
86
|
+
description: meaning
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if ((role === "checkbox" || role === "radio") && ariaLabel) {
|
|
90
|
+
return {
|
|
91
|
+
key,
|
|
92
|
+
expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
|
|
93
|
+
elementType: role,
|
|
94
|
+
confidence: "high",
|
|
95
|
+
source: "label",
|
|
96
|
+
description: ariaLabel
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const tabLikeRoles = ["tab", "option", "treeitem", "menuitem", "menuitemcheckbox", "menuitemradio", "gridcell"];
|
|
100
|
+
if (tabLikeRoles.includes(role ?? "") && meaning) {
|
|
101
|
+
const isSelected = ariaState["selected"];
|
|
102
|
+
const isCurrent = ariaState["current"];
|
|
103
|
+
const stateOpt = isSelected === "true" ? `, { name: ${JSON.stringify(meaning)}, selected: true }` : isCurrent ? `, { name: ${JSON.stringify(meaning)} }` : `, { name: ${JSON.stringify(meaning)} }`;
|
|
104
|
+
return {
|
|
105
|
+
key,
|
|
106
|
+
expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,
|
|
107
|
+
elementType: role ?? "element",
|
|
108
|
+
confidence: "high",
|
|
109
|
+
source: "role-name",
|
|
110
|
+
description: meaning
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if ((role === "button" || role === "link") && meaning) {
|
|
114
|
+
const isPressed = ariaState["pressed"];
|
|
115
|
+
const stateOpt = isPressed === "true" ? `, { name: ${JSON.stringify(meaning)}, pressed: true }` : isPressed === "false" ? `, { name: ${JSON.stringify(meaning)}, pressed: false }` : `, { name: ${JSON.stringify(meaning)} }`;
|
|
116
|
+
return {
|
|
117
|
+
key,
|
|
118
|
+
expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,
|
|
119
|
+
elementType: role,
|
|
120
|
+
confidence: "high",
|
|
121
|
+
source: "role-name",
|
|
122
|
+
description: meaning
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (role === "heading" && (meaning || text)) {
|
|
126
|
+
const name2 = meaning || text;
|
|
127
|
+
return {
|
|
128
|
+
key,
|
|
129
|
+
expression: `page.getByRole('heading', { name: ${JSON.stringify(name2)} })`,
|
|
130
|
+
elementType: "heading",
|
|
131
|
+
confidence: "medium",
|
|
132
|
+
source: "heading",
|
|
133
|
+
description: name2
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (element.selector && element.selector.length > 0) {
|
|
137
|
+
return {
|
|
138
|
+
key,
|
|
139
|
+
expression: `page.locator(${JSON.stringify(element.selector)})`,
|
|
140
|
+
elementType: role ?? "element",
|
|
141
|
+
confidence: "medium",
|
|
142
|
+
source: "id",
|
|
143
|
+
description: meaning || text || element.selector
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (id) {
|
|
147
|
+
return {
|
|
148
|
+
key,
|
|
149
|
+
expression: `page.locator(${JSON.stringify("#" + id)})`,
|
|
150
|
+
elementType: role ?? "element",
|
|
151
|
+
confidence: "medium",
|
|
152
|
+
source: "id",
|
|
153
|
+
description: meaning || id
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (meaning || text) {
|
|
157
|
+
const t = meaning || text;
|
|
158
|
+
return {
|
|
159
|
+
key,
|
|
160
|
+
expression: `page.getByText(${JSON.stringify(t)}, { exact: false })`,
|
|
161
|
+
elementType: role ?? "text",
|
|
162
|
+
confidence: "low",
|
|
163
|
+
source: "text",
|
|
164
|
+
description: t
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const META_ANNOTATION_RE = /^(css animations|scroll effects detected|design tokens?\s*:|font imports?:|contrast ratio|aria-label audit|landmark|wcag|color contrast|background.*foreground.*card|--[\w-]+,\s*--)/i;
|
|
170
|
+
const AUTH_WALL_TEXT_RE = /sign\s*in|log\s*in|platform access required|please sign in|access required|authentication required|session expired|unauthorized/i;
|
|
171
|
+
function isMetaAnnotation(el) {
|
|
172
|
+
const t = (el.meaning ?? el.text ?? "").trim();
|
|
173
|
+
if (!t) return false;
|
|
174
|
+
if (META_ANNOTATION_RE.test(t)) return true;
|
|
175
|
+
const interactiveRoles = /* @__PURE__ */ new Set(["button", "link", "textbox", "combobox", "checkbox", "radio", "tab", "option", "menuitem"]);
|
|
176
|
+
if (t.length > 120 && !interactiveRoles.has((el.role ?? "").toLowerCase())) return true;
|
|
177
|
+
if ((t.match(/--[\w-]+/g) ?? []).length > 3) return true;
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
function elementsToPlaywrightLocators(elements) {
|
|
181
|
+
const CONFIDENCE_RANK = { high: 0, medium: 1, low: 2 };
|
|
182
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
183
|
+
for (const el of elements) {
|
|
184
|
+
if (isMetaAnnotation(el)) continue;
|
|
185
|
+
const loc = elementToPlaywrightLocator(el);
|
|
186
|
+
if (!loc) continue;
|
|
187
|
+
const existing = byKey.get(loc.key);
|
|
188
|
+
if (!existing || CONFIDENCE_RANK[loc.confidence] < CONFIDENCE_RANK[existing.confidence]) {
|
|
189
|
+
byKey.set(loc.key, loc);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return Array.from(byKey.values());
|
|
193
|
+
}
|
|
194
|
+
function detectAuthWallLocators(locators) {
|
|
195
|
+
if (locators.length === 0) return null;
|
|
196
|
+
const lowCount = locators.filter((l) => l.confidence === "low").length;
|
|
197
|
+
const hasAuthText = locators.some((l) => AUTH_WALL_TEXT_RE.test(l.description));
|
|
198
|
+
const highCount = locators.filter((l) => l.confidence === "high").length;
|
|
199
|
+
if (hasAuthText && highCount === 0 && lowCount / locators.length > 0.5) {
|
|
200
|
+
return "These locators appear to be from a login/access wall, not the target page. The page requires authentication. Add credentials in Project Settings and re-crawl to get accurate locators for the protected content.";
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
function formatLocatorsForPrompt(locators) {
|
|
205
|
+
if (locators.length === 0) return "";
|
|
206
|
+
const lines = locators.map(
|
|
207
|
+
(l) => ` ${l.key}: ${l.expression} // ${l.description} [${l.confidence}]`
|
|
208
|
+
);
|
|
209
|
+
return [
|
|
210
|
+
"EXTRACTED PAGE LOCATORS (use these EXACTLY \u2014 do not invent other selectors; these were verified against the live page):",
|
|
211
|
+
...lines
|
|
212
|
+
].join("\n");
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
detectAuthWallLocators,
|
|
216
|
+
elementToPlaywrightLocator,
|
|
217
|
+
elementsToPlaywrightLocators,
|
|
218
|
+
formatLocatorsForPrompt
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=locator-extractor.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/locator-extractor.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Converts raw DB Element records (role, meaning, notes JSON) into Playwright-idiomatic locator strings.\n * Priority: data-testid \u2192 aria-label/placeholder (inputs) \u2192 role+name (buttons/links) \u2192 heading \u2192 text \u2192 id\n */\n\nexport interface PlaywrightLocator {\n key: string; // camelCase property name for page object\n expression: string; // e.g. \"page.getByRole('button', { name: 'Submit' })\"\n elementType: string; // button | input | link | heading | text | element\n confidence: 'high' | 'medium' | 'low';\n source: 'testid' | 'label' | 'placeholder' | 'role-name' | 'heading' | 'text' | 'id';\n description: string; // human-readable label\n}\n\n/** Parse the notes JSON string stored on Element records. */\nfunction parseNotes(notes: unknown): Record<string, string> {\n if (!notes) return {};\n try {\n const parsed = typeof notes === 'string' ? JSON.parse(notes) : notes;\n if (parsed && typeof parsed === 'object') return parsed as Record<string, string>;\n } catch {}\n return {};\n}\n\n/** Map crawler role strings to Playwright role values. */\nfunction mapRole(role?: string | null): string | null {\n if (!role) return null;\n const r = role.toLowerCase().trim();\n if (r === 'button' || r === 'submit' || r === 'reset') return 'button';\n if (r === 'link' || r === 'a' || r === 'anchor') return 'link';\n if (r === 'textbox' || r === 'input' || r === 'text' || r === 'email' || r === 'password' || r === 'tel' || r === 'number' || r === 'search') return 'textbox';\n if (r === 'combobox' || r === 'select' || r === 'dropdown') return 'combobox';\n if (r === 'checkbox') return 'checkbox';\n if (r === 'radio') return 'radio';\n if (r === 'heading' || r === 'h1' || r === 'h2' || r === 'h3' || r === 'h4' || r === 'h5' || r === 'h6') return 'heading';\n if (r === 'img' || r === 'image') return 'img';\n if (r === 'nav' || r === 'navigation') return 'navigation';\n return r;\n}\n\n/** Convert \"Submit Form Button\" \u2192 \"submitFormButton\" */\nfunction toKey(text: string): string {\n return text\n .replace(/[^a-zA-Z0-9\\s]/g, ' ')\n .replace(/\\s+(.)/g, (_, c) => c.toUpperCase())\n .replace(/^(.)/, (_, c) => c.toLowerCase())\n .replace(/\\s/g, '')\n .slice(0, 40)\n || 'element';\n}\n\n/** Convert a single DB Element record to a Playwright locator. Returns null if insufficient data. */\nexport function elementToPlaywrightLocator(element: {\n role?: string | null;\n meaning?: string | null;\n text?: string | null;\n notes?: unknown;\n selector?: string | null;\n ariaState?: Record<string, unknown> | null;\n}): PlaywrightLocator | null {\n const attrs = parseNotes(element.notes);\n const testId = attrs['data-testid'];\n const ariaLabel = attrs['aria-label'] || element.meaning;\n const id = attrs['id'];\n const placeholder = attrs['placeholder'];\n const name = attrs['name'];\n const text = (element.text ?? '').trim();\n const meaning = (element.meaning ?? '').trim();\n const role = mapRole(element.role);\n const ariaState: Record<string, string> = {};\n if (element.ariaState && typeof element.ariaState === 'object') {\n for (const [k, v] of Object.entries(element.ariaState)) {\n if (typeof v === 'string') ariaState[k] = v;\n }\n }\n\n const label = meaning || text || testId || ariaLabel || placeholder || id || name || 'element';\n const key = toKey(label);\n\n // Priority 1: data-testid\n if (testId) {\n return {\n key,\n expression: `page.getByTestId(${JSON.stringify(testId)})`,\n elementType: role ?? 'element',\n confidence: 'high',\n source: 'testid',\n description: meaning || testId,\n };\n }\n\n // Priority 2: input/combobox/textarea with label \u2192 getByLabel; with placeholder \u2192 getByPlaceholder\n if (role === 'textbox' || role === 'combobox') {\n if (ariaLabel) {\n return {\n key,\n expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,\n elementType: 'input',\n confidence: 'high',\n source: 'label',\n description: ariaLabel,\n };\n }\n if (placeholder) {\n return {\n key,\n expression: `page.getByPlaceholder(${JSON.stringify(placeholder)})`,\n elementType: 'input',\n confidence: 'high',\n source: 'placeholder',\n description: placeholder,\n };\n }\n }\n\n // Priority 3: checkbox/radio \u2192 getByRole with optional checked state from ariaState\n if ((role === 'checkbox' || role === 'radio') && meaning) {\n const isChecked = ariaState['checked'];\n const stateOpt = isChecked === 'true' ? `, { name: ${JSON.stringify(meaning)}, checked: true }` : isChecked === 'false' ? `, { name: ${JSON.stringify(meaning)}, checked: false }` : `, { name: ${JSON.stringify(meaning)} }`;\n return {\n key,\n expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,\n elementType: role,\n confidence: 'high',\n source: 'role-name',\n description: meaning,\n };\n }\n if ((role === 'checkbox' || role === 'radio') && ariaLabel) {\n return {\n key,\n expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,\n elementType: role,\n confidence: 'high',\n source: 'label',\n description: ariaLabel,\n };\n }\n\n // Priority 3b: tab/option/treeitem \u2192 getByRole with optional selected state\n const tabLikeRoles = ['tab', 'option', 'treeitem', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'gridcell'];\n if (tabLikeRoles.includes(role ?? '') && meaning) {\n const isSelected = ariaState['selected'];\n const isCurrent = ariaState['current'];\n const stateOpt = isSelected === 'true' ? `, { name: ${JSON.stringify(meaning)}, selected: true }` : isCurrent ? `, { name: ${JSON.stringify(meaning)} }` : `, { name: ${JSON.stringify(meaning)} }`;\n return {\n key,\n expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,\n elementType: role ?? 'element',\n confidence: 'high',\n source: 'role-name',\n description: meaning,\n };\n }\n\n // Priority 4: button/link with meaningful name \u2192 getByRole; include pressed state for toggles\n if ((role === 'button' || role === 'link') && meaning) {\n const isPressed = ariaState['pressed'];\n const stateOpt = isPressed === 'true' ? `, { name: ${JSON.stringify(meaning)}, pressed: true }` : isPressed === 'false' ? `, { name: ${JSON.stringify(meaning)}, pressed: false }` : `, { name: ${JSON.stringify(meaning)} }`;\n return {\n key,\n expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,\n elementType: role,\n confidence: 'high',\n source: 'role-name',\n description: meaning,\n };\n }\n\n // Priority 5: heading \u2192 getByRole heading\n if (role === 'heading' && (meaning || text)) {\n const name2 = meaning || text;\n return {\n key,\n expression: `page.getByRole('heading', { name: ${JSON.stringify(name2)} })`,\n elementType: 'heading',\n confidence: 'medium',\n source: 'heading',\n description: name2,\n };\n }\n\n // Priority 6: existing CSS selector from crawler\n if (element.selector && element.selector.length > 0) {\n return {\n key,\n expression: `page.locator(${JSON.stringify(element.selector)})`,\n elementType: role ?? 'element',\n confidence: 'medium',\n source: 'id',\n description: meaning || text || element.selector,\n };\n }\n\n // Priority 7: id from attrs\n if (id) {\n return {\n key,\n expression: `page.locator(${JSON.stringify('#' + id)})`,\n elementType: role ?? 'element',\n confidence: 'medium',\n source: 'id',\n description: meaning || id,\n };\n }\n\n // Priority 8: fallback to text content\n if (meaning || text) {\n const t = meaning || text;\n return {\n key,\n expression: `page.getByText(${JSON.stringify(t)}, { exact: false })`,\n elementType: role ?? 'text',\n confidence: 'low',\n source: 'text',\n description: t,\n };\n }\n\n return null;\n}\n\n// Patterns that indicate crawl-analysis meta-text stored as element records (not real UI elements).\n// These come from accessibility-auditor, screen-reader analysis, or CSS inspector annotations.\nconst META_ANNOTATION_RE = /^(css animations|scroll effects detected|design tokens?\\s*:|font imports?:|contrast ratio|aria-label audit|landmark|wcag|color contrast|background.*foreground.*card|--[\\w-]+,\\s*--)/i;\n\n// Auth-wall element patterns \u2014 if >50% of locators match these, the page was an auth wall.\nconst AUTH_WALL_TEXT_RE = /sign\\s*in|log\\s*in|platform access required|please sign in|access required|authentication required|session expired|unauthorized/i;\n\n/**\n * Returns true if an element's text content is crawl-analysis meta-text, not a real UI element.\n * These are annotations stored by the accessibility auditor, not actual DOM elements.\n */\nfunction isMetaAnnotation(el: { role?: string | null; meaning?: string | null; text?: string | null }): boolean {\n const t = (el.meaning ?? el.text ?? '').trim();\n if (!t) return false;\n // Filter crawl-analysis annotations\n if (META_ANNOTATION_RE.test(t)) return true;\n // Filter very long text strings with no interactive role \u2014 these are paragraph/body copy,\n // not useful UI locators. Threshold: 120 chars with no testId/label/role\n const interactiveRoles = new Set(['button', 'link', 'textbox', 'combobox', 'checkbox', 'radio', 'tab', 'option', 'menuitem']);\n if (t.length > 120 && !interactiveRoles.has((el.role ?? '').toLowerCase())) return true;\n // Filter CSS variable dumps (contain multiple --var-name patterns)\n if ((t.match(/--[\\w-]+/g) ?? []).length > 3) return true;\n return false;\n}\n\n/**\n * Convert an array of DB Element records to deduplicated Playwright locators.\n * Deduplicates by key \u2014 higher confidence wins.\n * Filters out crawl-analysis meta-annotations and produces an auth-wall warning when applicable.\n */\nexport function elementsToPlaywrightLocators(elements: Array<{\n role?: string | null;\n meaning?: string | null;\n text?: string | null;\n notes?: unknown;\n selector?: string | null;\n isInteractive?: boolean | null;\n ariaState?: Record<string, unknown> | null;\n}>): PlaywrightLocator[] {\n const CONFIDENCE_RANK = { high: 0, medium: 1, low: 2 };\n const byKey = new Map<string, PlaywrightLocator>();\n\n for (const el of elements) {\n // Skip crawl-analysis meta-annotations \u2014 these are not real page elements\n if (isMetaAnnotation(el)) continue;\n\n const loc = elementToPlaywrightLocator(el);\n if (!loc) continue;\n const existing = byKey.get(loc.key);\n if (!existing || CONFIDENCE_RANK[loc.confidence] < CONFIDENCE_RANK[existing.confidence]) {\n byKey.set(loc.key, loc);\n }\n }\n\n return Array.from(byKey.values());\n}\n\n/**\n * Detect whether a set of locators was extracted from an auth-wall page.\n * Returns a warning message if auth-wall detected, null otherwise.\n * Heuristic: >50% of locators are low-confidence text matches AND at least one matches auth-wall pattern.\n */\nexport function detectAuthWallLocators(locators: PlaywrightLocator[]): string | null {\n if (locators.length === 0) return null;\n const lowCount = locators.filter(l => l.confidence === 'low').length;\n const hasAuthText = locators.some(l => AUTH_WALL_TEXT_RE.test(l.description));\n const highCount = locators.filter(l => l.confidence === 'high').length;\n if (hasAuthText && highCount === 0 && lowCount / locators.length > 0.5) {\n return 'These locators appear to be from a login/access wall, not the target page. The page requires authentication. Add credentials in Project Settings and re-crawl to get accurate locators for the protected content.';\n }\n return null;\n}\n\n/**\n * Format locators as a block for injection into LLM script generation prompts.\n * Returns empty string if no locators.\n */\nexport function formatLocatorsForPrompt(locators: PlaywrightLocator[]): string {\n if (locators.length === 0) return '';\n const lines = locators.map(l =>\n ` ${l.key}: ${l.expression} // ${l.description} [${l.confidence}]`\n );\n return [\n 'EXTRACTED PAGE LOCATORS (use these EXACTLY \u2014 do not invent other selectors; these were verified against the live page):',\n ...lines,\n ].join('\\n');\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
const logger = pino({
|
|
3
|
+
level: process.env.LOG_LEVEL ?? "info",
|
|
4
|
+
...process.env.NODE_ENV === "development" ? { transport: { target: "pino-pretty", options: { colorize: true } } } : {},
|
|
5
|
+
base: { service: "crawler" }
|
|
6
|
+
});
|
|
7
|
+
export {
|
|
8
|
+
logger
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/logger.ts"],
|
|
4
|
+
"sourcesContent": ["import pino from 'pino';\n\nexport const logger = pino({\n level: process.env.LOG_LEVEL ?? 'info',\n ...(process.env.NODE_ENV === 'development'\n ? { transport: { target: 'pino-pretty', options: { colorize: true } } }\n : {}),\n base: { service: 'crawler' },\n});\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,UAAU;AAEV,MAAM,SAAS,KAAK;AAAA,EACzB,OAAO,QAAQ,IAAI,aAAa;AAAA,EAChC,GAAI,QAAQ,IAAI,aAAa,gBACzB,EAAE,WAAW,EAAE,QAAQ,eAAe,SAAS,EAAE,UAAU,KAAK,EAAE,EAAE,IACpE,CAAC;AAAA,EACL,MAAM,EAAE,SAAS,UAAU;AAC7B,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ExtractedMarkdown {
|
|
2
|
+
markdown: string;
|
|
3
|
+
method: 'readability' | 'full-page';
|
|
4
|
+
title: string | null;
|
|
5
|
+
wordCount: number;
|
|
6
|
+
}
|
|
7
|
+
/** Best-effort HTML→Markdown for one crawled page. Never throws. */
|
|
8
|
+
export declare function extractMarkdown(html: string, url: string): ExtractedMarkdown | null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { parseHTML } from "linkedom";
|
|
2
|
+
import { Readability } from "@mozilla/readability";
|
|
3
|
+
import TurndownService from "turndown";
|
|
4
|
+
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
|
5
|
+
turndown.remove(["script", "style", "noscript", "svg", "iframe"]);
|
|
6
|
+
function stripNoiseElements(document) {
|
|
7
|
+
const noisy = document.querySelectorAll('script, style, noscript, svg, iframe, [aria-hidden="true"]');
|
|
8
|
+
noisy.forEach((el) => el.remove());
|
|
9
|
+
}
|
|
10
|
+
function extractMarkdown(html, url) {
|
|
11
|
+
try {
|
|
12
|
+
const { document } = parseHTML(html);
|
|
13
|
+
stripNoiseElements(document);
|
|
14
|
+
let readabilityResult = null;
|
|
15
|
+
try {
|
|
16
|
+
const { document: readerDoc } = parseHTML(html);
|
|
17
|
+
stripNoiseElements(readerDoc);
|
|
18
|
+
readabilityResult = new Readability(readerDoc, { charThreshold: 50 }).parse();
|
|
19
|
+
} catch {
|
|
20
|
+
readabilityResult = null;
|
|
21
|
+
}
|
|
22
|
+
if (readabilityResult?.content && readabilityResult.textContent.trim().length >= 50) {
|
|
23
|
+
const markdown2 = turndown.turndown(readabilityResult.content);
|
|
24
|
+
return {
|
|
25
|
+
markdown: `# ${readabilityResult.title || document.title || url}
|
|
26
|
+
|
|
27
|
+
${markdown2}`,
|
|
28
|
+
method: "readability",
|
|
29
|
+
title: readabilityResult.title || document.title || null,
|
|
30
|
+
wordCount: readabilityResult.textContent.trim().split(/\s+/).length
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const body = document.body;
|
|
34
|
+
if (!body) return null;
|
|
35
|
+
const markdown = turndown.turndown(body.innerHTML || "");
|
|
36
|
+
const text = markdown.trim();
|
|
37
|
+
if (!text) return null;
|
|
38
|
+
const title = document.title || null;
|
|
39
|
+
return {
|
|
40
|
+
markdown: title ? `# ${title}
|
|
41
|
+
|
|
42
|
+
${markdown}` : markdown,
|
|
43
|
+
method: "full-page",
|
|
44
|
+
title,
|
|
45
|
+
wordCount: text.split(/\s+/).length
|
|
46
|
+
};
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
extractMarkdown
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=markdown-extract.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/markdown-extract.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * HTML \u2192 Markdown extraction for crawled pages \u2014 matches crawl4AI's markdown\n * output capability. Two-tier, mirroring crawl4AI's \"markdown\" vs \"fit markdown\":\n *\n * - \"fit\" markdown: Readability's best guess at the primary content (title +\n * article body), stripped of nav/ads/chrome. Ideal for content/marketing\n * pages and RAG chunking. null when Readability finds no article (most\n * app UI screens \u2014 dashboards, forms \u2014 legitimately have none).\n * - \"full\" markdown: the whole visible page converted straight to Markdown,\n * always present. The fallback for app screens where there's no \"article\".\n *\n * Runs entirely on an already-captured `page.content()` string \u2014 no extra\n * network fetch, no extra page navigation.\n */\nimport { parseHTML } from 'linkedom';\nimport { Readability } from '@mozilla/readability';\nimport TurndownService from 'turndown';\n\nconst turndown = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });\nturndown.remove(['script', 'style', 'noscript', 'svg', 'iframe'] as unknown as (keyof HTMLElementTagNameMap)[]);\n\nexport interface ExtractedMarkdown {\n markdown: string;\n method: 'readability' | 'full-page';\n title: string | null;\n wordCount: number;\n}\n\nfunction stripNoiseElements(document: Document): void {\n const noisy = document.querySelectorAll('script, style, noscript, svg, iframe, [aria-hidden=\"true\"]');\n noisy.forEach((el: any) => el.remove());\n}\n\n/** Best-effort HTML\u2192Markdown for one crawled page. Never throws. */\nexport function extractMarkdown(html: string, url: string): ExtractedMarkdown | null {\n try {\n const { document } = parseHTML(html);\n stripNoiseElements(document);\n\n let readabilityResult: { title: string; content: string; textContent: string } | null = null;\n try {\n // Readability mutates the DOM it's given \u2014 clone via re-parsing so the\n // full-page fallback below still has an intact document.\n const { document: readerDoc } = parseHTML(html);\n stripNoiseElements(readerDoc);\n readabilityResult = new Readability(readerDoc as any, { charThreshold: 50 }).parse() as any;\n } catch {\n readabilityResult = null;\n }\n\n if (readabilityResult?.content && readabilityResult.textContent.trim().length >= 50) {\n const markdown = turndown.turndown(readabilityResult.content);\n return {\n markdown: `# ${readabilityResult.title || document.title || url}\\n\\n${markdown}`,\n method: 'readability',\n title: readabilityResult.title || document.title || null,\n wordCount: readabilityResult.textContent.trim().split(/\\s+/).length,\n };\n }\n\n const body = document.body;\n if (!body) return null;\n const markdown = turndown.turndown(body.innerHTML || '');\n const text = markdown.trim();\n if (!text) return null;\n const title = document.title || null;\n return {\n markdown: title ? `# ${title}\\n\\n${markdown}` : markdown,\n method: 'full-page',\n title,\n wordCount: text.split(/\\s+/).length,\n };\n } catch {\n return null;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAcA,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,OAAO,qBAAqB;AAE5B,MAAM,WAAW,IAAI,gBAAgB,EAAE,cAAc,OAAO,gBAAgB,SAAS,CAAC;AACtF,SAAS,OAAO,CAAC,UAAU,SAAS,YAAY,OAAO,QAAQ,CAA+C;AAS9G,SAAS,mBAAmB,UAA0B;AACpD,QAAM,QAAQ,SAAS,iBAAiB,4DAA4D;AACpG,QAAM,QAAQ,CAAC,OAAY,GAAG,OAAO,CAAC;AACxC;AAGO,SAAS,gBAAgB,MAAc,KAAuC;AACnF,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,UAAU,IAAI;AACnC,uBAAmB,QAAQ;AAE3B,QAAI,oBAAoF;AACxF,QAAI;AAGF,YAAM,EAAE,UAAU,UAAU,IAAI,UAAU,IAAI;AAC9C,yBAAmB,SAAS;AAC5B,0BAAoB,IAAI,YAAY,WAAkB,EAAE,eAAe,GAAG,CAAC,EAAE,MAAM;AAAA,IACrF,QAAQ;AACN,0BAAoB;AAAA,IACtB;AAEA,QAAI,mBAAmB,WAAW,kBAAkB,YAAY,KAAK,EAAE,UAAU,IAAI;AACnF,YAAMA,YAAW,SAAS,SAAS,kBAAkB,OAAO;AAC5D,aAAO;AAAA,QACL,UAAU,KAAK,kBAAkB,SAAS,SAAS,SAAS,GAAG;AAAA;AAAA,EAAOA,SAAQ;AAAA,QAC9E,QAAQ;AAAA,QACR,OAAO,kBAAkB,SAAS,SAAS,SAAS;AAAA,QACpD,WAAW,kBAAkB,YAAY,KAAK,EAAE,MAAM,KAAK,EAAE;AAAA,MAC/D;AAAA,IACF;AAEA,UAAM,OAAO,SAAS;AACtB,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,WAAW,SAAS,SAAS,KAAK,aAAa,EAAE;AACvD,UAAM,OAAO,SAAS,KAAK;AAC3B,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,QAAQ,SAAS,SAAS;AAChC,WAAO;AAAA,MACL,UAAU,QAAQ,KAAK,KAAK;AAAA;AAAA,EAAO,QAAQ,KAAK;AAAA,MAChD,QAAQ;AAAA,MACR;AAAA,MACA,WAAW,KAAK,MAAM,KAAK,EAAE;AAAA,IAC/B;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": ["markdown"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-context browser handling — detects and records new tabs, iframes,
|
|
3
|
+
* popups, and file downloads triggered during a page crawl.
|
|
4
|
+
* This lets ZeTa discover cross-origin flows, OAuth redirects, embedded
|
|
5
|
+
* third-party content, and downloadable resources that single-page HTTP
|
|
6
|
+
* crawlers would miss entirely.
|
|
7
|
+
*/
|
|
8
|
+
import type { Page, BrowserContext } from 'playwright';
|
|
9
|
+
export interface NewTabRecord {
|
|
10
|
+
url: string;
|
|
11
|
+
linkText: string;
|
|
12
|
+
isInternal: boolean;
|
|
13
|
+
httpStatus?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface IframeRecord {
|
|
16
|
+
src: string;
|
|
17
|
+
crossOrigin: boolean;
|
|
18
|
+
sandboxed: boolean;
|
|
19
|
+
sandboxValue: string | null;
|
|
20
|
+
accessible: boolean;
|
|
21
|
+
contentSample: string;
|
|
22
|
+
hasForm: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface PopupRecord {
|
|
25
|
+
url: string;
|
|
26
|
+
triggerLabel: string;
|
|
27
|
+
isOAuthLike: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface DownloadRecord {
|
|
30
|
+
filename: string;
|
|
31
|
+
downloadUrl: string;
|
|
32
|
+
triggerLabel: string;
|
|
33
|
+
}
|
|
34
|
+
export interface MultiContextResult {
|
|
35
|
+
newTabs: NewTabRecord[];
|
|
36
|
+
iframes: IframeRecord[];
|
|
37
|
+
popups: PopupRecord[];
|
|
38
|
+
downloads: DownloadRecord[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Attaches a listener to the BrowserContext that records every new tab
|
|
42
|
+
* opened while the listener is active. Returns an unsubscribe function.
|
|
43
|
+
*
|
|
44
|
+
* Call this before navigating / clicking so that tabs opened during the
|
|
45
|
+
* crawl are captured, then call the returned function to stop listening.
|
|
46
|
+
*/
|
|
47
|
+
export declare function attachNewTabListener(context: BrowserContext, seedUrl: string, result: MultiContextResult): () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Probes the current page for multi-context browser events: iframes,
|
|
50
|
+
* popup windows, and file downloads.
|
|
51
|
+
*
|
|
52
|
+
* New-tab tracking is intentionally excluded here — it must be set up at
|
|
53
|
+
* the crawler level (before navigation) using `attachNewTabListener`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function probeMultiContext(page: Page, context: BrowserContext, seedUrl: string): Promise<MultiContextResult>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
async function enumerateIframes(page, seedUrl) {
|
|
2
|
+
const records = [];
|
|
3
|
+
try {
|
|
4
|
+
const seedHost = new URL(seedUrl).hostname;
|
|
5
|
+
const iframeLocators = await page.locator("iframe").all();
|
|
6
|
+
const limited = iframeLocators.slice(0, 20);
|
|
7
|
+
for (let idx = 0; idx < limited.length; idx++) {
|
|
8
|
+
try {
|
|
9
|
+
const loc = limited[idx];
|
|
10
|
+
const src = await loc.getAttribute("src") ?? "";
|
|
11
|
+
const sandboxValue = await loc.getAttribute("sandbox");
|
|
12
|
+
const sandboxed = sandboxValue !== null;
|
|
13
|
+
let crossOrigin = false;
|
|
14
|
+
if (src) {
|
|
15
|
+
try {
|
|
16
|
+
const iframeHost = new URL(src, seedUrl).hostname;
|
|
17
|
+
crossOrigin = iframeHost !== seedHost;
|
|
18
|
+
} catch {
|
|
19
|
+
crossOrigin = false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let accessible = false;
|
|
23
|
+
let contentSample = "";
|
|
24
|
+
let hasForm = false;
|
|
25
|
+
try {
|
|
26
|
+
const fl = page.frameLocator("iframe").nth(idx);
|
|
27
|
+
const body = await fl.locator("body").textContent({ timeout: 2e3 });
|
|
28
|
+
if (body !== null) {
|
|
29
|
+
accessible = true;
|
|
30
|
+
contentSample = body.trim().slice(0, 300);
|
|
31
|
+
}
|
|
32
|
+
const formCount = await fl.locator("form, input, textarea, select").count();
|
|
33
|
+
hasForm = formCount > 0;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
records.push({
|
|
37
|
+
src,
|
|
38
|
+
crossOrigin,
|
|
39
|
+
sandboxed,
|
|
40
|
+
sandboxValue,
|
|
41
|
+
accessible,
|
|
42
|
+
contentSample,
|
|
43
|
+
hasForm
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
return records;
|
|
51
|
+
}
|
|
52
|
+
async function detectPopups(page, result) {
|
|
53
|
+
try {
|
|
54
|
+
const candidates = [
|
|
55
|
+
...await page.locator('[onclick*="window.open"]').all(),
|
|
56
|
+
...await page.locator('button:has-text("Open in")').all()
|
|
57
|
+
];
|
|
58
|
+
const limited = candidates.slice(0, 3);
|
|
59
|
+
for (const trigger of limited) {
|
|
60
|
+
try {
|
|
61
|
+
const triggerLabel = (await trigger.textContent())?.trim() ?? "";
|
|
62
|
+
const outcome = await Promise.all([
|
|
63
|
+
page.waitForEvent("popup", { timeout: 2e3 }),
|
|
64
|
+
trigger.click()
|
|
65
|
+
]).catch(() => null);
|
|
66
|
+
if (!outcome) continue;
|
|
67
|
+
const popup = outcome[0];
|
|
68
|
+
if (!popup) continue;
|
|
69
|
+
const url = popup.url();
|
|
70
|
+
let isOAuthLike = false;
|
|
71
|
+
try {
|
|
72
|
+
const host = new URL(url).hostname;
|
|
73
|
+
isOAuthLike = /google|github|microsoft|okta|auth0/.test(host);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
result.popups.push({ url, triggerLabel, isOAuthLike });
|
|
77
|
+
await popup.close().catch(() => {
|
|
78
|
+
});
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function detectDownloads(page, result) {
|
|
86
|
+
try {
|
|
87
|
+
const candidates = [
|
|
88
|
+
...await page.locator("a[download]").all(),
|
|
89
|
+
...await page.locator('button:has-text("Export")').all(),
|
|
90
|
+
...await page.locator('button:has-text("Download")').all()
|
|
91
|
+
];
|
|
92
|
+
const limited = candidates.slice(0, 3);
|
|
93
|
+
for (const trigger of limited) {
|
|
94
|
+
try {
|
|
95
|
+
const triggerLabel = (await trigger.textContent())?.trim() ?? "";
|
|
96
|
+
const outcome = await Promise.all([
|
|
97
|
+
page.waitForEvent("download", { timeout: 2e3 }),
|
|
98
|
+
trigger.click()
|
|
99
|
+
]).catch(() => null);
|
|
100
|
+
if (!outcome) continue;
|
|
101
|
+
const download = outcome[0];
|
|
102
|
+
if (!download) continue;
|
|
103
|
+
const filename = download.suggestedFilename();
|
|
104
|
+
const downloadUrl = download.url();
|
|
105
|
+
await download.cancel().catch(() => {
|
|
106
|
+
});
|
|
107
|
+
result.downloads.push({ filename, downloadUrl, triggerLabel });
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function attachNewTabListener(context, seedUrl, result) {
|
|
115
|
+
let seedHost = "";
|
|
116
|
+
try {
|
|
117
|
+
seedHost = new URL(seedUrl).hostname;
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
const handler = async (newPage) => {
|
|
121
|
+
try {
|
|
122
|
+
await newPage.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {
|
|
123
|
+
});
|
|
124
|
+
const url = newPage.url();
|
|
125
|
+
if (!url || url === "about:blank") {
|
|
126
|
+
await newPage.close().catch(() => {
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
let isInternal = false;
|
|
131
|
+
let httpStatus;
|
|
132
|
+
try {
|
|
133
|
+
const tabHost = new URL(url).hostname;
|
|
134
|
+
isInternal = tabHost === seedHost;
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
if (!isInternal) {
|
|
138
|
+
try {
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
const timer = setTimeout(() => controller.abort(), 3e3);
|
|
141
|
+
const resp = await fetch(url, { method: "HEAD", signal: controller.signal });
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
httpStatus = resp.status;
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
result.newTabs.push({ url, linkText: "", isInternal, httpStatus });
|
|
148
|
+
await newPage.close().catch(() => {
|
|
149
|
+
});
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
context.on("page", handler);
|
|
154
|
+
return () => context.off("page", handler);
|
|
155
|
+
}
|
|
156
|
+
async function probeMultiContext(page, context, seedUrl) {
|
|
157
|
+
void context;
|
|
158
|
+
const result = {
|
|
159
|
+
newTabs: [],
|
|
160
|
+
iframes: [],
|
|
161
|
+
popups: [],
|
|
162
|
+
downloads: []
|
|
163
|
+
};
|
|
164
|
+
try {
|
|
165
|
+
result.iframes = await enumerateIframes(page, seedUrl);
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
await detectPopups(page, result);
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
await detectDownloads(page, result);
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
attachNewTabListener,
|
|
180
|
+
probeMultiContext
|
|
181
|
+
};
|
|
182
|
+
//# sourceMappingURL=multi-context-handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/multi-context-handler.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Multi-context browser handling \u2014 detects and records new tabs, iframes,\n * popups, and file downloads triggered during a page crawl.\n * This lets ZeTa discover cross-origin flows, OAuth redirects, embedded\n * third-party content, and downloadable resources that single-page HTTP\n * crawlers would miss entirely.\n */\n\nimport type { Page, BrowserContext } from 'playwright';\n\nexport interface NewTabRecord {\n url: string;\n linkText: string;\n isInternal: boolean;\n httpStatus?: number;\n}\n\nexport interface IframeRecord {\n src: string;\n crossOrigin: boolean;\n sandboxed: boolean;\n sandboxValue: string | null;\n accessible: boolean;\n contentSample: string;\n hasForm: boolean;\n}\n\nexport interface PopupRecord {\n url: string;\n triggerLabel: string;\n isOAuthLike: boolean;\n}\n\nexport interface DownloadRecord {\n filename: string;\n downloadUrl: string;\n triggerLabel: string;\n}\n\nexport interface MultiContextResult {\n newTabs: NewTabRecord[];\n iframes: IframeRecord[];\n popups: PopupRecord[];\n downloads: DownloadRecord[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nasync function enumerateIframes(\n page: Page,\n seedUrl: string,\n): Promise<IframeRecord[]> {\n const records: IframeRecord[] = [];\n try {\n const seedHost = new URL(seedUrl).hostname;\n const iframeLocators = await page.locator('iframe').all();\n const limited = iframeLocators.slice(0, 20);\n\n for (let idx = 0; idx < limited.length; idx++) {\n try {\n const loc = limited[idx];\n const src = (await loc.getAttribute('src')) ?? '';\n const sandboxValue = await loc.getAttribute('sandbox');\n const sandboxed = sandboxValue !== null;\n\n let crossOrigin = false;\n if (src) {\n try {\n const iframeHost = new URL(src, seedUrl).hostname;\n crossOrigin = iframeHost !== seedHost;\n } catch {\n crossOrigin = false;\n }\n }\n\n let accessible = false;\n let contentSample = '';\n let hasForm = false;\n\n try {\n const fl = page.frameLocator('iframe').nth(idx);\n const body = await fl.locator('body').textContent({ timeout: 2000 });\n if (body !== null) {\n accessible = true;\n contentSample = body.trim().slice(0, 300);\n }\n const formCount = await fl.locator('form, input, textarea, select').count();\n hasForm = formCount > 0;\n } catch {\n // cross-origin or sandboxed \u2014 not accessible from parent frame\n }\n\n records.push({\n src,\n crossOrigin,\n sandboxed,\n sandboxValue,\n accessible,\n contentSample,\n hasForm,\n });\n } catch {\n // skip individual iframe errors\n }\n }\n } catch {\n // skip entirely if locator fails\n }\n return records;\n}\n\nasync function detectPopups(page: Page, result: MultiContextResult): Promise<void> {\n try {\n const candidates = [\n ...await page.locator('[onclick*=\"window.open\"]').all(),\n ...await page.locator('button:has-text(\"Open in\")').all(),\n ];\n const limited = candidates.slice(0, 3);\n\n for (const trigger of limited) {\n try {\n const triggerLabel = (await trigger.textContent())?.trim() ?? '';\n const outcome = await Promise.all([\n page.waitForEvent('popup', { timeout: 2000 }),\n trigger.click(),\n ]).catch(() => null);\n\n if (!outcome) continue;\n const popup = outcome[0] as Page;\n if (!popup) continue;\n\n const url = popup.url();\n let isOAuthLike = false;\n try {\n const host = new URL(url).hostname;\n isOAuthLike = /google|github|microsoft|okta|auth0/.test(host);\n } catch {\n // not a valid URL\n }\n\n result.popups.push({ url, triggerLabel, isOAuthLike });\n await popup.close().catch(() => {});\n } catch {\n // skip this trigger\n }\n }\n } catch {\n // skip popup detection entirely\n }\n}\n\nasync function detectDownloads(page: Page, result: MultiContextResult): Promise<void> {\n try {\n const candidates = [\n ...await page.locator('a[download]').all(),\n ...await page.locator('button:has-text(\"Export\")').all(),\n ...await page.locator('button:has-text(\"Download\")').all(),\n ];\n const limited = candidates.slice(0, 3);\n\n for (const trigger of limited) {\n try {\n const triggerLabel = (await trigger.textContent())?.trim() ?? '';\n const outcome = await Promise.all([\n page.waitForEvent('download', { timeout: 2000 }),\n trigger.click(),\n ]).catch(() => null);\n\n if (!outcome) continue;\n const download = outcome[0] as unknown as import('playwright').Download;\n if (!download) continue;\n\n const filename = download.suggestedFilename();\n const downloadUrl = download.url();\n await download.cancel().catch(() => {});\n\n result.downloads.push({ filename, downloadUrl, triggerLabel });\n } catch {\n // skip this trigger\n }\n }\n } catch {\n // skip download detection entirely\n }\n}\n\n// ---------------------------------------------------------------------------\n// Exported functions\n// ---------------------------------------------------------------------------\n\n/**\n * Attaches a listener to the BrowserContext that records every new tab\n * opened while the listener is active. Returns an unsubscribe function.\n *\n * Call this before navigating / clicking so that tabs opened during the\n * crawl are captured, then call the returned function to stop listening.\n */\nexport function attachNewTabListener(\n context: BrowserContext,\n seedUrl: string,\n result: MultiContextResult,\n): () => void {\n let seedHost = '';\n try {\n seedHost = new URL(seedUrl).hostname;\n } catch {\n // leave empty \u2014 all tabs will be treated as external\n }\n\n const handler = async (newPage: Page) => {\n try {\n await newPage.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});\n const url = newPage.url();\n\n if (!url || url === 'about:blank') {\n await newPage.close().catch(() => {});\n return;\n }\n\n let isInternal = false;\n let httpStatus: number | undefined;\n\n try {\n const tabHost = new URL(url).hostname;\n isInternal = tabHost === seedHost;\n } catch {\n // non-parseable URL \u2014 treat as external\n }\n\n if (!isInternal) {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 3000);\n const resp = await fetch(url, { method: 'HEAD', signal: controller.signal });\n clearTimeout(timer);\n httpStatus = resp.status;\n } catch {\n // network unreachable or CORS \u2014 leave httpStatus undefined\n }\n }\n\n // linkText is not available at context level; caller fills it if needed\n result.newTabs.push({ url, linkText: '', isInternal, httpStatus });\n await newPage.close().catch(() => {});\n } catch {\n // swallow handler errors so the listener stays alive\n }\n };\n\n context.on('page', handler);\n return () => context.off('page', handler);\n}\n\n/**\n * Probes the current page for multi-context browser events: iframes,\n * popup windows, and file downloads.\n *\n * New-tab tracking is intentionally excluded here \u2014 it must be set up at\n * the crawler level (before navigation) using `attachNewTabListener`.\n */\nexport async function probeMultiContext(\n page: Page,\n context: BrowserContext,\n seedUrl: string,\n): Promise<MultiContextResult> {\n void context; // reserved for future use (e.g. routing overrides)\n\n const result: MultiContextResult = {\n newTabs: [],\n iframes: [],\n popups: [],\n downloads: [],\n };\n\n try {\n result.iframes = await enumerateIframes(page, seedUrl);\n } catch {\n // keep empty\n }\n\n try {\n await detectPopups(page, result);\n } catch {\n // keep empty\n }\n\n try {\n await detectDownloads(page, result);\n } catch {\n // keep empty\n }\n\n return result;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|