@taodigital/crawler 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts raw DB Element records (role, meaning, notes JSON) into Playwright-idiomatic locator strings.
|
|
3
|
+
* Priority: data-testid → aria-label/placeholder (inputs) → role+name (buttons/links) → heading → text → id
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface PlaywrightLocator {
|
|
7
|
+
key: string; // camelCase property name for page object
|
|
8
|
+
expression: string; // e.g. "page.getByRole('button', { name: 'Submit' })"
|
|
9
|
+
elementType: string; // button | input | link | heading | text | element
|
|
10
|
+
confidence: 'high' | 'medium' | 'low';
|
|
11
|
+
source: 'testid' | 'label' | 'placeholder' | 'role-name' | 'heading' | 'text' | 'id';
|
|
12
|
+
description: string; // human-readable label
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Parse the notes JSON string stored on Element records. */
|
|
16
|
+
function parseNotes(notes: unknown): Record<string, string> {
|
|
17
|
+
if (!notes) return {};
|
|
18
|
+
try {
|
|
19
|
+
const parsed = typeof notes === 'string' ? JSON.parse(notes) : notes;
|
|
20
|
+
if (parsed && typeof parsed === 'object') return parsed as Record<string, string>;
|
|
21
|
+
} catch {}
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Map crawler role strings to Playwright role values. */
|
|
26
|
+
function mapRole(role?: string | null): string | null {
|
|
27
|
+
if (!role) return null;
|
|
28
|
+
const r = role.toLowerCase().trim();
|
|
29
|
+
if (r === 'button' || r === 'submit' || r === 'reset') return 'button';
|
|
30
|
+
if (r === 'link' || r === 'a' || r === 'anchor') return 'link';
|
|
31
|
+
if (r === 'textbox' || r === 'input' || r === 'text' || r === 'email' || r === 'password' || r === 'tel' || r === 'number' || r === 'search') return 'textbox';
|
|
32
|
+
if (r === 'combobox' || r === 'select' || r === 'dropdown') return 'combobox';
|
|
33
|
+
if (r === 'checkbox') return 'checkbox';
|
|
34
|
+
if (r === 'radio') return 'radio';
|
|
35
|
+
if (r === 'heading' || r === 'h1' || r === 'h2' || r === 'h3' || r === 'h4' || r === 'h5' || r === 'h6') return 'heading';
|
|
36
|
+
if (r === 'img' || r === 'image') return 'img';
|
|
37
|
+
if (r === 'nav' || r === 'navigation') return 'navigation';
|
|
38
|
+
return r;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Convert "Submit Form Button" → "submitFormButton" */
|
|
42
|
+
function toKey(text: string): string {
|
|
43
|
+
return text
|
|
44
|
+
.replace(/[^a-zA-Z0-9\s]/g, ' ')
|
|
45
|
+
.replace(/\s+(.)/g, (_, c) => c.toUpperCase())
|
|
46
|
+
.replace(/^(.)/, (_, c) => c.toLowerCase())
|
|
47
|
+
.replace(/\s/g, '')
|
|
48
|
+
.slice(0, 40)
|
|
49
|
+
|| 'element';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Convert a single DB Element record to a Playwright locator. Returns null if insufficient data. */
|
|
53
|
+
export function elementToPlaywrightLocator(element: {
|
|
54
|
+
role?: string | null;
|
|
55
|
+
meaning?: string | null;
|
|
56
|
+
text?: string | null;
|
|
57
|
+
notes?: unknown;
|
|
58
|
+
selector?: string | null;
|
|
59
|
+
ariaState?: Record<string, unknown> | null;
|
|
60
|
+
}): PlaywrightLocator | null {
|
|
61
|
+
const attrs = parseNotes(element.notes);
|
|
62
|
+
const testId = attrs['data-testid'];
|
|
63
|
+
const ariaLabel = attrs['aria-label'] || element.meaning;
|
|
64
|
+
const id = attrs['id'];
|
|
65
|
+
const placeholder = attrs['placeholder'];
|
|
66
|
+
const name = attrs['name'];
|
|
67
|
+
const text = (element.text ?? '').trim();
|
|
68
|
+
const meaning = (element.meaning ?? '').trim();
|
|
69
|
+
const role = mapRole(element.role);
|
|
70
|
+
const ariaState: Record<string, string> = {};
|
|
71
|
+
if (element.ariaState && typeof element.ariaState === 'object') {
|
|
72
|
+
for (const [k, v] of Object.entries(element.ariaState)) {
|
|
73
|
+
if (typeof v === 'string') ariaState[k] = v;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const label = meaning || text || testId || ariaLabel || placeholder || id || name || 'element';
|
|
78
|
+
const key = toKey(label);
|
|
79
|
+
|
|
80
|
+
// Priority 1: data-testid
|
|
81
|
+
if (testId) {
|
|
82
|
+
return {
|
|
83
|
+
key,
|
|
84
|
+
expression: `page.getByTestId(${JSON.stringify(testId)})`,
|
|
85
|
+
elementType: role ?? 'element',
|
|
86
|
+
confidence: 'high',
|
|
87
|
+
source: 'testid',
|
|
88
|
+
description: meaning || testId,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Priority 2: input/combobox/textarea with label → getByLabel; with placeholder → getByPlaceholder
|
|
93
|
+
if (role === 'textbox' || role === 'combobox') {
|
|
94
|
+
if (ariaLabel) {
|
|
95
|
+
return {
|
|
96
|
+
key,
|
|
97
|
+
expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
|
|
98
|
+
elementType: 'input',
|
|
99
|
+
confidence: 'high',
|
|
100
|
+
source: 'label',
|
|
101
|
+
description: ariaLabel,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (placeholder) {
|
|
105
|
+
return {
|
|
106
|
+
key,
|
|
107
|
+
expression: `page.getByPlaceholder(${JSON.stringify(placeholder)})`,
|
|
108
|
+
elementType: 'input',
|
|
109
|
+
confidence: 'high',
|
|
110
|
+
source: 'placeholder',
|
|
111
|
+
description: placeholder,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Priority 3: checkbox/radio → getByRole with optional checked state from ariaState
|
|
117
|
+
if ((role === 'checkbox' || role === 'radio') && meaning) {
|
|
118
|
+
const isChecked = ariaState['checked'];
|
|
119
|
+
const stateOpt = isChecked === 'true' ? `, { name: ${JSON.stringify(meaning)}, checked: true }` : isChecked === 'false' ? `, { name: ${JSON.stringify(meaning)}, checked: false }` : `, { name: ${JSON.stringify(meaning)} }`;
|
|
120
|
+
return {
|
|
121
|
+
key,
|
|
122
|
+
expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,
|
|
123
|
+
elementType: role,
|
|
124
|
+
confidence: 'high',
|
|
125
|
+
source: 'role-name',
|
|
126
|
+
description: meaning,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if ((role === 'checkbox' || role === 'radio') && ariaLabel) {
|
|
130
|
+
return {
|
|
131
|
+
key,
|
|
132
|
+
expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
|
|
133
|
+
elementType: role,
|
|
134
|
+
confidence: 'high',
|
|
135
|
+
source: 'label',
|
|
136
|
+
description: ariaLabel,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Priority 3b: tab/option/treeitem → getByRole with optional selected state
|
|
141
|
+
const tabLikeRoles = ['tab', 'option', 'treeitem', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'gridcell'];
|
|
142
|
+
if (tabLikeRoles.includes(role ?? '') && meaning) {
|
|
143
|
+
const isSelected = ariaState['selected'];
|
|
144
|
+
const isCurrent = ariaState['current'];
|
|
145
|
+
const stateOpt = isSelected === 'true' ? `, { name: ${JSON.stringify(meaning)}, selected: true }` : isCurrent ? `, { name: ${JSON.stringify(meaning)} }` : `, { name: ${JSON.stringify(meaning)} }`;
|
|
146
|
+
return {
|
|
147
|
+
key,
|
|
148
|
+
expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,
|
|
149
|
+
elementType: role ?? 'element',
|
|
150
|
+
confidence: 'high',
|
|
151
|
+
source: 'role-name',
|
|
152
|
+
description: meaning,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Priority 4: button/link with meaningful name → getByRole; include pressed state for toggles
|
|
157
|
+
if ((role === 'button' || role === 'link') && meaning) {
|
|
158
|
+
const isPressed = ariaState['pressed'];
|
|
159
|
+
const stateOpt = isPressed === 'true' ? `, { name: ${JSON.stringify(meaning)}, pressed: true }` : isPressed === 'false' ? `, { name: ${JSON.stringify(meaning)}, pressed: false }` : `, { name: ${JSON.stringify(meaning)} }`;
|
|
160
|
+
return {
|
|
161
|
+
key,
|
|
162
|
+
expression: `page.getByRole(${JSON.stringify(role)}${stateOpt})`,
|
|
163
|
+
elementType: role,
|
|
164
|
+
confidence: 'high',
|
|
165
|
+
source: 'role-name',
|
|
166
|
+
description: meaning,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Priority 5: heading → getByRole heading
|
|
171
|
+
if (role === 'heading' && (meaning || text)) {
|
|
172
|
+
const name2 = meaning || text;
|
|
173
|
+
return {
|
|
174
|
+
key,
|
|
175
|
+
expression: `page.getByRole('heading', { name: ${JSON.stringify(name2)} })`,
|
|
176
|
+
elementType: 'heading',
|
|
177
|
+
confidence: 'medium',
|
|
178
|
+
source: 'heading',
|
|
179
|
+
description: name2,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Priority 6: existing CSS selector from crawler
|
|
184
|
+
if (element.selector && element.selector.length > 0) {
|
|
185
|
+
return {
|
|
186
|
+
key,
|
|
187
|
+
expression: `page.locator(${JSON.stringify(element.selector)})`,
|
|
188
|
+
elementType: role ?? 'element',
|
|
189
|
+
confidence: 'medium',
|
|
190
|
+
source: 'id',
|
|
191
|
+
description: meaning || text || element.selector,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Priority 7: id from attrs
|
|
196
|
+
if (id) {
|
|
197
|
+
return {
|
|
198
|
+
key,
|
|
199
|
+
expression: `page.locator(${JSON.stringify('#' + id)})`,
|
|
200
|
+
elementType: role ?? 'element',
|
|
201
|
+
confidence: 'medium',
|
|
202
|
+
source: 'id',
|
|
203
|
+
description: meaning || id,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Priority 8: fallback to text content
|
|
208
|
+
if (meaning || text) {
|
|
209
|
+
const t = meaning || text;
|
|
210
|
+
return {
|
|
211
|
+
key,
|
|
212
|
+
expression: `page.getByText(${JSON.stringify(t)}, { exact: false })`,
|
|
213
|
+
elementType: role ?? 'text',
|
|
214
|
+
confidence: 'low',
|
|
215
|
+
source: 'text',
|
|
216
|
+
description: t,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Patterns that indicate crawl-analysis meta-text stored as element records (not real UI elements).
|
|
224
|
+
// These come from accessibility-auditor, screen-reader analysis, or CSS inspector annotations.
|
|
225
|
+
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;
|
|
226
|
+
|
|
227
|
+
// Auth-wall element patterns — if >50% of locators match these, the page was an auth wall.
|
|
228
|
+
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;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Returns true if an element's text content is crawl-analysis meta-text, not a real UI element.
|
|
232
|
+
* These are annotations stored by the accessibility auditor, not actual DOM elements.
|
|
233
|
+
*/
|
|
234
|
+
function isMetaAnnotation(el: { role?: string | null; meaning?: string | null; text?: string | null }): boolean {
|
|
235
|
+
const t = (el.meaning ?? el.text ?? '').trim();
|
|
236
|
+
if (!t) return false;
|
|
237
|
+
// Filter crawl-analysis annotations
|
|
238
|
+
if (META_ANNOTATION_RE.test(t)) return true;
|
|
239
|
+
// Filter very long text strings with no interactive role — these are paragraph/body copy,
|
|
240
|
+
// not useful UI locators. Threshold: 120 chars with no testId/label/role
|
|
241
|
+
const interactiveRoles = new Set(['button', 'link', 'textbox', 'combobox', 'checkbox', 'radio', 'tab', 'option', 'menuitem']);
|
|
242
|
+
if (t.length > 120 && !interactiveRoles.has((el.role ?? '').toLowerCase())) return true;
|
|
243
|
+
// Filter CSS variable dumps (contain multiple --var-name patterns)
|
|
244
|
+
if ((t.match(/--[\w-]+/g) ?? []).length > 3) return true;
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Convert an array of DB Element records to deduplicated Playwright locators.
|
|
250
|
+
* Deduplicates by key — higher confidence wins.
|
|
251
|
+
* Filters out crawl-analysis meta-annotations and produces an auth-wall warning when applicable.
|
|
252
|
+
*/
|
|
253
|
+
export function elementsToPlaywrightLocators(elements: Array<{
|
|
254
|
+
role?: string | null;
|
|
255
|
+
meaning?: string | null;
|
|
256
|
+
text?: string | null;
|
|
257
|
+
notes?: unknown;
|
|
258
|
+
selector?: string | null;
|
|
259
|
+
isInteractive?: boolean | null;
|
|
260
|
+
ariaState?: Record<string, unknown> | null;
|
|
261
|
+
}>): PlaywrightLocator[] {
|
|
262
|
+
const CONFIDENCE_RANK = { high: 0, medium: 1, low: 2 };
|
|
263
|
+
const byKey = new Map<string, PlaywrightLocator>();
|
|
264
|
+
|
|
265
|
+
for (const el of elements) {
|
|
266
|
+
// Skip crawl-analysis meta-annotations — these are not real page elements
|
|
267
|
+
if (isMetaAnnotation(el)) continue;
|
|
268
|
+
|
|
269
|
+
const loc = elementToPlaywrightLocator(el);
|
|
270
|
+
if (!loc) continue;
|
|
271
|
+
const existing = byKey.get(loc.key);
|
|
272
|
+
if (!existing || CONFIDENCE_RANK[loc.confidence] < CONFIDENCE_RANK[existing.confidence]) {
|
|
273
|
+
byKey.set(loc.key, loc);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return Array.from(byKey.values());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Detect whether a set of locators was extracted from an auth-wall page.
|
|
282
|
+
* Returns a warning message if auth-wall detected, null otherwise.
|
|
283
|
+
* Heuristic: >50% of locators are low-confidence text matches AND at least one matches auth-wall pattern.
|
|
284
|
+
*/
|
|
285
|
+
export function detectAuthWallLocators(locators: PlaywrightLocator[]): string | null {
|
|
286
|
+
if (locators.length === 0) return null;
|
|
287
|
+
const lowCount = locators.filter(l => l.confidence === 'low').length;
|
|
288
|
+
const hasAuthText = locators.some(l => AUTH_WALL_TEXT_RE.test(l.description));
|
|
289
|
+
const highCount = locators.filter(l => l.confidence === 'high').length;
|
|
290
|
+
if (hasAuthText && highCount === 0 && lowCount / locators.length > 0.5) {
|
|
291
|
+
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.';
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Format locators as a block for injection into LLM script generation prompts.
|
|
298
|
+
* Returns empty string if no locators.
|
|
299
|
+
*/
|
|
300
|
+
export function formatLocatorsForPrompt(locators: PlaywrightLocator[]): string {
|
|
301
|
+
if (locators.length === 0) return '';
|
|
302
|
+
const lines = locators.map(l =>
|
|
303
|
+
` ${l.key}: ${l.expression} // ${l.description} [${l.confidence}]`
|
|
304
|
+
);
|
|
305
|
+
return [
|
|
306
|
+
'EXTRACTED PAGE LOCATORS (use these EXACTLY — do not invent other selectors; these were verified against the live page):',
|
|
307
|
+
...lines,
|
|
308
|
+
].join('\n');
|
|
309
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
|
|
3
|
+
export const logger = pino({
|
|
4
|
+
level: process.env.LOG_LEVEL ?? 'info',
|
|
5
|
+
...(process.env.NODE_ENV === 'development'
|
|
6
|
+
? { transport: { target: 'pino-pretty', options: { colorize: true } } }
|
|
7
|
+
: {}),
|
|
8
|
+
base: { service: 'crawler' },
|
|
9
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { extractMarkdown } from './markdown-extract.js';
|
|
4
|
+
|
|
5
|
+
const ARTICLE_HTML = `<!doctype html><html><head><title>How Widgets Work</title></head>
|
|
6
|
+
<body>
|
|
7
|
+
<nav><a href="/">Home</a><a href="/about">About</a></nav>
|
|
8
|
+
<header><div class="logo">WidgetCo</div></header>
|
|
9
|
+
<article>
|
|
10
|
+
<h1>How Widgets Work</h1>
|
|
11
|
+
<p>${'Widgets are small mechanical devices used in countless everyday applications. '.repeat(10)}</p>
|
|
12
|
+
<p>${'This second paragraph explains the history of widget manufacturing in great detail. '.repeat(10)}</p>
|
|
13
|
+
<h2>Manufacturing Process</h2>
|
|
14
|
+
<p>${'The manufacturing process involves several precise steps performed by skilled technicians. '.repeat(10)}</p>
|
|
15
|
+
</article>
|
|
16
|
+
<footer><p>Copyright 2026</p></footer>
|
|
17
|
+
<script>console.log('tracking pixel');</script>
|
|
18
|
+
</body></html>`;
|
|
19
|
+
|
|
20
|
+
const APP_DASHBOARD_HTML = `<!doctype html><html><head><title>Dashboard</title></head>
|
|
21
|
+
<body>
|
|
22
|
+
<div id="app">
|
|
23
|
+
<nav class="sidebar"><a href="/home">Home</a><a href="/settings">Settings</a></nav>
|
|
24
|
+
<main>
|
|
25
|
+
<h1>Dashboard</h1>
|
|
26
|
+
<div class="stat-card"><span>Revenue</span><strong>$12,400</strong></div>
|
|
27
|
+
<div class="stat-card"><span>Users</span><strong>842</strong></div>
|
|
28
|
+
<button>Export CSV</button>
|
|
29
|
+
</main>
|
|
30
|
+
</div>
|
|
31
|
+
<script>window.__STATE__ = {};</script>
|
|
32
|
+
</body></html>`;
|
|
33
|
+
|
|
34
|
+
const EMPTY_HTML = `<!doctype html><html><head><title></title></head><body></body></html>`;
|
|
35
|
+
|
|
36
|
+
test('extractMarkdown: article page uses Readability and strips nav/footer chrome', () => {
|
|
37
|
+
const result = extractMarkdown(ARTICLE_HTML, 'https://example.com/blog/widgets');
|
|
38
|
+
assert.ok(result);
|
|
39
|
+
assert.equal(result!.method, 'readability');
|
|
40
|
+
assert.match(result!.markdown, /How Widgets Work/);
|
|
41
|
+
assert.match(result!.markdown, /Manufacturing Process/);
|
|
42
|
+
assert.doesNotMatch(result!.markdown, /Copyright 2026/);
|
|
43
|
+
assert.doesNotMatch(result!.markdown, /tracking pixel/);
|
|
44
|
+
assert.ok(result!.wordCount > 50);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('extractMarkdown: app dashboard (no article) falls back to full-page conversion', () => {
|
|
48
|
+
const result = extractMarkdown(APP_DASHBOARD_HTML, 'https://app.example.com/dashboard');
|
|
49
|
+
assert.ok(result);
|
|
50
|
+
assert.equal(result!.method, 'full-page');
|
|
51
|
+
assert.match(result!.markdown, /Dashboard/);
|
|
52
|
+
assert.match(result!.markdown, /Revenue/);
|
|
53
|
+
assert.match(result!.markdown, /Export CSV/);
|
|
54
|
+
assert.doesNotMatch(result!.markdown, /__STATE__/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('extractMarkdown: empty page returns null instead of an empty markdown string', () => {
|
|
58
|
+
const result = extractMarkdown(EMPTY_HTML, 'https://example.com/empty');
|
|
59
|
+
assert.equal(result, null);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('extractMarkdown: malformed HTML never throws', () => {
|
|
63
|
+
assert.doesNotThrow(() => extractMarkdown('<html><body><div>unclosed', 'https://example.com/x'));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('extractMarkdown: script/style content is never leaked into markdown output', () => {
|
|
67
|
+
const html = `<!doctype html><html><head><title>Page</title><style>.x{color:red}</style></head>
|
|
68
|
+
<body><script>alert('xss-in-markdown')</script><h1>Real Content Heading For Testing</h1><p>${'Some real paragraph text that is long enough to be meaningful content here. '.repeat(6)}</p></body></html>`;
|
|
69
|
+
const result = extractMarkdown(html, 'https://example.com/page');
|
|
70
|
+
assert.ok(result);
|
|
71
|
+
assert.doesNotMatch(result!.markdown, /alert\(/);
|
|
72
|
+
assert.doesNotMatch(result!.markdown, /color:red/);
|
|
73
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML → Markdown extraction for crawled pages — matches crawl4AI's markdown
|
|
3
|
+
* output capability. Two-tier, mirroring crawl4AI's "markdown" vs "fit markdown":
|
|
4
|
+
*
|
|
5
|
+
* - "fit" markdown: Readability's best guess at the primary content (title +
|
|
6
|
+
* article body), stripped of nav/ads/chrome. Ideal for content/marketing
|
|
7
|
+
* pages and RAG chunking. null when Readability finds no article (most
|
|
8
|
+
* app UI screens — dashboards, forms — legitimately have none).
|
|
9
|
+
* - "full" markdown: the whole visible page converted straight to Markdown,
|
|
10
|
+
* always present. The fallback for app screens where there's no "article".
|
|
11
|
+
*
|
|
12
|
+
* Runs entirely on an already-captured `page.content()` string — no extra
|
|
13
|
+
* network fetch, no extra page navigation.
|
|
14
|
+
*/
|
|
15
|
+
import { parseHTML } from 'linkedom';
|
|
16
|
+
import { Readability } from '@mozilla/readability';
|
|
17
|
+
import TurndownService from 'turndown';
|
|
18
|
+
|
|
19
|
+
const turndown = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
|
|
20
|
+
turndown.remove(['script', 'style', 'noscript', 'svg', 'iframe'] as unknown as (keyof HTMLElementTagNameMap)[]);
|
|
21
|
+
|
|
22
|
+
export interface ExtractedMarkdown {
|
|
23
|
+
markdown: string;
|
|
24
|
+
method: 'readability' | 'full-page';
|
|
25
|
+
title: string | null;
|
|
26
|
+
wordCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stripNoiseElements(document: Document): void {
|
|
30
|
+
const noisy = document.querySelectorAll('script, style, noscript, svg, iframe, [aria-hidden="true"]');
|
|
31
|
+
noisy.forEach((el: any) => el.remove());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Best-effort HTML→Markdown for one crawled page. Never throws. */
|
|
35
|
+
export function extractMarkdown(html: string, url: string): ExtractedMarkdown | null {
|
|
36
|
+
try {
|
|
37
|
+
const { document } = parseHTML(html);
|
|
38
|
+
stripNoiseElements(document);
|
|
39
|
+
|
|
40
|
+
let readabilityResult: { title: string; content: string; textContent: string } | null = null;
|
|
41
|
+
try {
|
|
42
|
+
// Readability mutates the DOM it's given — clone via re-parsing so the
|
|
43
|
+
// full-page fallback below still has an intact document.
|
|
44
|
+
const { document: readerDoc } = parseHTML(html);
|
|
45
|
+
stripNoiseElements(readerDoc);
|
|
46
|
+
readabilityResult = new Readability(readerDoc as any, { charThreshold: 50 }).parse() as any;
|
|
47
|
+
} catch {
|
|
48
|
+
readabilityResult = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (readabilityResult?.content && readabilityResult.textContent.trim().length >= 50) {
|
|
52
|
+
const markdown = turndown.turndown(readabilityResult.content);
|
|
53
|
+
return {
|
|
54
|
+
markdown: `# ${readabilityResult.title || document.title || url}\n\n${markdown}`,
|
|
55
|
+
method: 'readability',
|
|
56
|
+
title: readabilityResult.title || document.title || null,
|
|
57
|
+
wordCount: readabilityResult.textContent.trim().split(/\s+/).length,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const body = document.body;
|
|
62
|
+
if (!body) return null;
|
|
63
|
+
const markdown = turndown.turndown(body.innerHTML || '');
|
|
64
|
+
const text = markdown.trim();
|
|
65
|
+
if (!text) return null;
|
|
66
|
+
const title = document.title || null;
|
|
67
|
+
return {
|
|
68
|
+
markdown: title ? `# ${title}\n\n${markdown}` : markdown,
|
|
69
|
+
method: 'full-page',
|
|
70
|
+
title,
|
|
71
|
+
wordCount: text.split(/\s+/).length,
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|