@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,865 @@
|
|
|
1
|
+
import { AppBrain } from "@taodigital/app-brain";
|
|
2
|
+
const ERROR_SELECTORS = [
|
|
3
|
+
'[role="alert"]',
|
|
4
|
+
'[aria-live="assertive"]',
|
|
5
|
+
'[aria-live="polite"]',
|
|
6
|
+
".invalid-feedback",
|
|
7
|
+
".field-error",
|
|
8
|
+
".error-message",
|
|
9
|
+
".form-error",
|
|
10
|
+
".validation-error",
|
|
11
|
+
'[class*="error" i]',
|
|
12
|
+
'[class*="invalid" i]',
|
|
13
|
+
'[class*="Error"]',
|
|
14
|
+
"p.text-red-500",
|
|
15
|
+
"p.text-red-600",
|
|
16
|
+
"p.text-destructive",
|
|
17
|
+
"p.text-danger",
|
|
18
|
+
"[data-error]",
|
|
19
|
+
"[data-invalid]",
|
|
20
|
+
".ant-form-item-explain-error",
|
|
21
|
+
// Ant Design
|
|
22
|
+
".el-form-item__error",
|
|
23
|
+
// Element UI
|
|
24
|
+
".chakra-form__error-message",
|
|
25
|
+
// Chakra UI
|
|
26
|
+
".mantine-InputWrapper-error"
|
|
27
|
+
// Mantine
|
|
28
|
+
].join(", ");
|
|
29
|
+
const TOAST_SELECTORS = [
|
|
30
|
+
'[role="status"]',
|
|
31
|
+
"[data-sonner-toast]",
|
|
32
|
+
"[data-toast]",
|
|
33
|
+
"[data-hot-toast]",
|
|
34
|
+
".Toastify__toast",
|
|
35
|
+
".toast-message",
|
|
36
|
+
".notification-body",
|
|
37
|
+
".notistack-snackbar",
|
|
38
|
+
'[class*="toast" i]',
|
|
39
|
+
'[class*="Toast"]',
|
|
40
|
+
'[class*="snackbar" i]',
|
|
41
|
+
'[class*="notification" i]'
|
|
42
|
+
].join(", ");
|
|
43
|
+
async function waitForAny(page, selector, maxMs = 2500) {
|
|
44
|
+
try {
|
|
45
|
+
await page.waitForSelector(selector, { timeout: maxMs, state: "visible" });
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return page.$$eval(
|
|
50
|
+
selector,
|
|
51
|
+
(els) => els.map((el) => el.innerText?.replace(/\s+/g, " ").trim()).filter(Boolean)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async function collectErrors(page) {
|
|
55
|
+
try {
|
|
56
|
+
return page.$$eval(
|
|
57
|
+
ERROR_SELECTORS,
|
|
58
|
+
(els) => els.filter((el) => {
|
|
59
|
+
const style = window.getComputedStyle(el);
|
|
60
|
+
return style.display !== "none" && style.visibility !== "hidden" && el.innerText?.trim().length > 0;
|
|
61
|
+
}).map((el) => el.innerText?.replace(/\s+/g, " ").trim()).filter(Boolean)
|
|
62
|
+
);
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function collectToasts(page) {
|
|
68
|
+
try {
|
|
69
|
+
return page.$$eval(
|
|
70
|
+
TOAST_SELECTORS,
|
|
71
|
+
(els) => els.filter((el) => {
|
|
72
|
+
const style = window.getComputedStyle(el);
|
|
73
|
+
return style.display !== "none" && el.innerText?.trim().length > 0;
|
|
74
|
+
}).map((el) => el.innerText?.replace(/\s+/g, " ").trim()).filter(Boolean)
|
|
75
|
+
);
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function findSubmitButton(page) {
|
|
81
|
+
return page.evaluate(() => {
|
|
82
|
+
const SUBMIT_KW = /submit|send|save|create|confirm|continue|proceed|register|sign.?in|log.?in|apply|verify|search|next|finish|complete|order|buy|checkout/i;
|
|
83
|
+
const candidates = Array.from(document.querySelectorAll('button, [role="button"], input[type="submit"]'));
|
|
84
|
+
const primary = candidates.find((el) => {
|
|
85
|
+
const style = window.getComputedStyle(el);
|
|
86
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
87
|
+
const text = el.innerText || el.value || el.getAttribute("aria-label") || "";
|
|
88
|
+
return SUBMIT_KW.test(text);
|
|
89
|
+
});
|
|
90
|
+
if (!primary) return null;
|
|
91
|
+
return primary.getAttribute("data-testid") || primary.getAttribute("aria-label") || primary.innerText?.trim().slice(0, 60);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function findModalTriggers(page) {
|
|
95
|
+
return page.evaluate(() => {
|
|
96
|
+
const SKIP = /submit|send|save|create|log.?in|sign.?in|register|continue|checkout|buy|order|search|close|cancel|dismiss/i;
|
|
97
|
+
const NAV_PARENTS = /* @__PURE__ */ new Set(["NAV", "HEADER", "FOOTER"]);
|
|
98
|
+
function isVisible(el) {
|
|
99
|
+
const s = window.getComputedStyle(el);
|
|
100
|
+
return s.display !== "none" && s.visibility !== "hidden" && s.opacity !== "0";
|
|
101
|
+
}
|
|
102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
103
|
+
const results = [];
|
|
104
|
+
for (const el of Array.from(document.querySelectorAll('button, [role="button"]'))) {
|
|
105
|
+
if (!isVisible(el)) continue;
|
|
106
|
+
const label = (el.innerText?.trim() || el.getAttribute("aria-label") || "").slice(0, 60);
|
|
107
|
+
if (!label || SKIP.test(label) || seen.has(label)) continue;
|
|
108
|
+
let p = el.parentElement;
|
|
109
|
+
let inNav = false;
|
|
110
|
+
while (p) {
|
|
111
|
+
if (NAV_PARENTS.has(p.tagName)) {
|
|
112
|
+
inNav = true;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
p = p.parentElement;
|
|
116
|
+
}
|
|
117
|
+
if (inNav) continue;
|
|
118
|
+
seen.add(label);
|
|
119
|
+
results.push({ selector: `button:has-text("${label.slice(0, 40)}")`, label });
|
|
120
|
+
if (results.length >= 15) break;
|
|
121
|
+
}
|
|
122
|
+
const rowCandidates = Array.from(document.querySelectorAll(
|
|
123
|
+
'[role="row"][tabindex="0"], tr[tabindex="0"], [role="listitem"][tabindex="0"], [class*="list-item"][tabindex="0"], [class*="table-row"][tabindex="0"]'
|
|
124
|
+
)).filter(isVisible).slice(0, 3);
|
|
125
|
+
for (const row of rowCandidates) {
|
|
126
|
+
const ariaLabel = row.getAttribute("aria-label");
|
|
127
|
+
const textLabel = row.innerText?.trim().split("\n")[0].trim().slice(0, 60);
|
|
128
|
+
const label = (ariaLabel || textLabel || "").slice(0, 60);
|
|
129
|
+
if (!label || seen.has(label)) continue;
|
|
130
|
+
seen.add(label);
|
|
131
|
+
const selector = ariaLabel ? `[aria-label="${ariaLabel}"]` : `[role="row"]:has-text("${label.slice(0, 40)}")`;
|
|
132
|
+
results.push({ selector, label });
|
|
133
|
+
}
|
|
134
|
+
return results.slice(0, 20);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async function probeConditionals(page) {
|
|
138
|
+
const results = [];
|
|
139
|
+
try {
|
|
140
|
+
const selects = await page.$$("select");
|
|
141
|
+
for (const sel of selects.slice(0, 5)) {
|
|
142
|
+
const label = await sel.getAttribute("aria-label") || await sel.getAttribute("name") || "select";
|
|
143
|
+
const options = await sel.$$eval("option", (opts) => opts.slice(1, 3).map((o) => ({ value: o.value, text: o.textContent?.trim() })));
|
|
144
|
+
for (const opt of options) {
|
|
145
|
+
if (!opt.value) continue;
|
|
146
|
+
const domBefore = await page.evaluate(() => document.body.innerText.length);
|
|
147
|
+
await sel.selectOption(opt.value);
|
|
148
|
+
await page.waitForTimeout(500);
|
|
149
|
+
const domAfter = await page.evaluate(() => document.body.innerText.length);
|
|
150
|
+
if (Math.abs(domAfter - domBefore) > 50) {
|
|
151
|
+
const revealed = await page.evaluate(() => {
|
|
152
|
+
return Array.from(document.querySelectorAll('[data-conditional],[data-show],[class*="conditional"]')).filter((el) => window.getComputedStyle(el).display !== "none").map((el) => el.innerText?.trim()).filter((t) => t && t.length > 10).slice(0, 3).join(" | ");
|
|
153
|
+
});
|
|
154
|
+
if (revealed) {
|
|
155
|
+
const domSnapshot = await page.content();
|
|
156
|
+
const stateId = await page.evaluate(() => {
|
|
157
|
+
let hash = 0;
|
|
158
|
+
const str = document.body.innerText;
|
|
159
|
+
for (let i = 0; i < str.length; i++) {
|
|
160
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
161
|
+
hash = hash & hash;
|
|
162
|
+
}
|
|
163
|
+
return Math.abs(hash).toString(16);
|
|
164
|
+
});
|
|
165
|
+
results.push({ triggerElement: label, triggerValue: opt.text || opt.value, revealedText: revealed.slice(0, 200), stateId, domSnapshot });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
async function probeScreen(page, screenId, url, elements) {
|
|
175
|
+
const result = {
|
|
176
|
+
screenId,
|
|
177
|
+
url,
|
|
178
|
+
validationErrors: [],
|
|
179
|
+
successMessages: [],
|
|
180
|
+
modals: [],
|
|
181
|
+
conditionals: [],
|
|
182
|
+
loadingPatterns: []
|
|
183
|
+
};
|
|
184
|
+
const formInputs = elements.filter(
|
|
185
|
+
(e) => /input|text|email|password|tel|search|textarea|select/i.test(e.role)
|
|
186
|
+
);
|
|
187
|
+
const hasForm = formInputs.length > 0;
|
|
188
|
+
if (hasForm) {
|
|
189
|
+
try {
|
|
190
|
+
const submitLabel = await findSubmitButton(page);
|
|
191
|
+
if (submitLabel) {
|
|
192
|
+
const submitBtn = page.locator(`button:has-text("${submitLabel}")`).first();
|
|
193
|
+
await submitBtn.click({ timeout: 2e3 });
|
|
194
|
+
await page.waitForTimeout(1500);
|
|
195
|
+
const errors = await collectErrors(page);
|
|
196
|
+
const ariaFieldErrors = await page.evaluate(
|
|
197
|
+
() => Array.from(document.querySelectorAll('[aria-invalid="true"]')).map((el) => {
|
|
198
|
+
const describedBy = el.getAttribute("aria-describedby");
|
|
199
|
+
const errorEl = describedBy ? document.getElementById(describedBy) : null;
|
|
200
|
+
const label = el.getAttribute("aria-label") || el.getAttribute("placeholder") || el.name || "";
|
|
201
|
+
return { label, errorText: errorEl?.textContent?.trim() || "" };
|
|
202
|
+
}).filter((f) => f.errorText)
|
|
203
|
+
);
|
|
204
|
+
const ariaMatched = /* @__PURE__ */ new Set();
|
|
205
|
+
for (const af of ariaFieldErrors) {
|
|
206
|
+
ariaMatched.add(af.errorText);
|
|
207
|
+
result.validationErrors.push({ field: af.label || "unknown", trigger: "empty_submit", errorText: af.errorText.slice(0, 150) });
|
|
208
|
+
}
|
|
209
|
+
for (const errorText of errors.filter((e) => !ariaMatched.has(e))) {
|
|
210
|
+
const matchedField = formInputs.find(
|
|
211
|
+
(el) => errorText.toLowerCase().includes(el.meaning.toLowerCase().split(" ")[0]) || el.meaning.toLowerCase().includes(errorText.toLowerCase().split(" ")[0])
|
|
212
|
+
);
|
|
213
|
+
result.validationErrors.push({
|
|
214
|
+
field: matchedField?.meaning ?? "unknown",
|
|
215
|
+
trigger: "empty_submit",
|
|
216
|
+
errorText: errorText.slice(0, 150)
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const loadingText = await page.evaluate(() => {
|
|
220
|
+
const loading = Array.from(document.querySelectorAll('button, [aria-busy="true"], [class*="loading" i], [class*="spinner" i]')).map((el) => el.innerText?.trim()).filter((t) => t && /loading|saving|submitting|sending|processing/i.test(t));
|
|
221
|
+
return loading.slice(0, 3);
|
|
222
|
+
});
|
|
223
|
+
result.loadingPatterns.push(...loadingText);
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (hasForm) {
|
|
229
|
+
try {
|
|
230
|
+
const emailField = formInputs.find((e) => e.expectedData === "valid_email" || /email/i.test(e.meaning));
|
|
231
|
+
const passwordField = formInputs.find((e) => e.expectedData === "password" || /password/i.test(e.meaning));
|
|
232
|
+
if (emailField) {
|
|
233
|
+
const emailLocator = page.locator('input[type="email"], input[name*="email"], input[placeholder*="email" i]').first();
|
|
234
|
+
try {
|
|
235
|
+
await emailLocator.fill("notanemail", { timeout: 1e3 });
|
|
236
|
+
await emailLocator.blur();
|
|
237
|
+
await page.waitForTimeout(600);
|
|
238
|
+
const errors = await collectErrors(page);
|
|
239
|
+
for (const errorText of errors) {
|
|
240
|
+
result.validationErrors.push({ field: emailField.meaning, trigger: "invalid_format", errorText: errorText.slice(0, 150) });
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (passwordField) {
|
|
246
|
+
const pwLocator = page.locator('input[type="password"]').first();
|
|
247
|
+
try {
|
|
248
|
+
await pwLocator.fill("123", { timeout: 1e3 });
|
|
249
|
+
await pwLocator.blur();
|
|
250
|
+
await page.waitForTimeout(600);
|
|
251
|
+
const errors = await collectErrors(page);
|
|
252
|
+
for (const errorText of errors) {
|
|
253
|
+
if (!result.validationErrors.some((e) => e.errorText === errorText)) {
|
|
254
|
+
result.validationErrors.push({ field: passwordField.meaning, trigger: "invalid_format", errorText: errorText.slice(0, 150) });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const triggers = await findModalTriggers(page);
|
|
265
|
+
for (const trigger of triggers) {
|
|
266
|
+
try {
|
|
267
|
+
const btn = page.locator(trigger.selector).first();
|
|
268
|
+
const domBefore = await page.evaluate(() => document.body.innerText.length);
|
|
269
|
+
await btn.click({ timeout: 1500 });
|
|
270
|
+
try {
|
|
271
|
+
await page.waitForSelector(
|
|
272
|
+
'[role="dialog"],[role="alertdialog"],[aria-modal="true"],[data-state="open"],[class*="modal" i]:not([class*="backdrop" i]),[class*="Dialog"]:not([class*="Overlay"])',
|
|
273
|
+
{ timeout: 8e3, state: "visible" }
|
|
274
|
+
);
|
|
275
|
+
} catch {
|
|
276
|
+
await page.waitForTimeout(300);
|
|
277
|
+
}
|
|
278
|
+
const dialogText = await page.evaluate(() => {
|
|
279
|
+
const DIALOG_SEL = '[role="dialog"], [role="alertdialog"], .modal, .Modal, [class*="modal" i], [class*="dialog" i], [class*="Dialog"]';
|
|
280
|
+
const dialogs = Array.from(document.querySelectorAll(DIALOG_SEL)).filter((el) => window.getComputedStyle(el).display !== "none");
|
|
281
|
+
if (dialogs.length === 0) return null;
|
|
282
|
+
const d = dialogs[0];
|
|
283
|
+
const title = d.querySelector('[role="heading"], h1, h2, h3')?.textContent?.trim() || "";
|
|
284
|
+
const content = d.innerText?.replace(/\s+/g, " ").trim().slice(0, 400);
|
|
285
|
+
const fields = Array.from(d.querySelectorAll("input, select, textarea, button")).map((f) => f.getAttribute("aria-label") || f.getAttribute("placeholder") || f.value || "").filter((t) => t.trim().length > 0);
|
|
286
|
+
return { title, content, fields };
|
|
287
|
+
});
|
|
288
|
+
const domSnapshot = await page.content();
|
|
289
|
+
const stateId = await page.evaluate(() => {
|
|
290
|
+
let hash = 0;
|
|
291
|
+
const str = document.body.innerText;
|
|
292
|
+
for (let i = 0; i < str.length; i++) {
|
|
293
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
294
|
+
hash = hash & hash;
|
|
295
|
+
}
|
|
296
|
+
return Math.abs(hash).toString(16);
|
|
297
|
+
});
|
|
298
|
+
if (dialogText && dialogText.content.length > 20) {
|
|
299
|
+
result.modals.push({
|
|
300
|
+
triggerElement: trigger.label,
|
|
301
|
+
title: dialogText.title,
|
|
302
|
+
content: dialogText.content,
|
|
303
|
+
fields: dialogText.fields.slice(0, 10),
|
|
304
|
+
stateId,
|
|
305
|
+
domSnapshot
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
const accordionText = await page.evaluate(() => {
|
|
309
|
+
const panels = Array.from(document.querySelectorAll(
|
|
310
|
+
'[aria-expanded="true"] + *, details[open] > :not(summary), [data-state="open"]:not([role="dialog"]):not([class*="modal" i]), [data-headlessui-state="open"] > *'
|
|
311
|
+
)).filter((el) => {
|
|
312
|
+
const s = window.getComputedStyle(el);
|
|
313
|
+
return s.display !== "none" && s.visibility !== "hidden";
|
|
314
|
+
});
|
|
315
|
+
if (panels.length === 0) return null;
|
|
316
|
+
const panel = panels[0];
|
|
317
|
+
const heading = panel.querySelector("h2,h3,h4")?.textContent?.replace(/\s+/g, " ").trim() || "";
|
|
318
|
+
const content = panel.innerText?.replace(/\s+/g, " ").trim().slice(0, 400) || "";
|
|
319
|
+
return content.length > 15 ? { heading, content } : null;
|
|
320
|
+
});
|
|
321
|
+
if (accordionText) {
|
|
322
|
+
result.modals.push({
|
|
323
|
+
triggerElement: trigger.label,
|
|
324
|
+
title: accordionText.heading || `${trigger.label} (revealed)`,
|
|
325
|
+
content: accordionText.content,
|
|
326
|
+
fields: [],
|
|
327
|
+
stateId,
|
|
328
|
+
domSnapshot
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
await page.keyboard.press("Escape");
|
|
333
|
+
await page.waitForTimeout(400);
|
|
334
|
+
const toasts = await collectToasts(page);
|
|
335
|
+
for (const t of toasts) {
|
|
336
|
+
if (t.length > 5) result.successMessages.push({ type: "toast", content: t.slice(0, 200) });
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const conditionals = await probeConditionals(page);
|
|
345
|
+
result.conditionals.push(...conditionals);
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
result.tabPanels = await probeTabPanels(page);
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
result.tooltips = await probeTooltips(page);
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
result.carouselSlides = await probeCarousels(page);
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
result.wizardSteps = await probeWizardSteps(page);
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
result.focusTraps = await detectFocusTraps(page);
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
result.skipNavIssues = await probeSkipNavigation(page);
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
result.contextMenus = await probeContextMenus(page);
|
|
374
|
+
} catch {
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
function probeResultToObservations(result) {
|
|
379
|
+
const obs = [];
|
|
380
|
+
for (const ve of result.validationErrors) {
|
|
381
|
+
obs.push({ elementMeaning: ve.field, observationType: "VALIDATION_ERROR", observedText: ve.errorText, triggeredBy: ve.trigger });
|
|
382
|
+
}
|
|
383
|
+
for (const sm of result.successMessages) {
|
|
384
|
+
obs.push({ elementMeaning: "(page)", observationType: "SUCCESS_TOAST", observedText: sm.content, triggeredBy: sm.type });
|
|
385
|
+
}
|
|
386
|
+
for (const m of result.modals) {
|
|
387
|
+
obs.push({ elementMeaning: m.triggerElement, observationType: "MODAL", observedText: [m.title, m.content].filter(Boolean).join(" \u2014 ").slice(0, 500), triggeredBy: `button_click:${m.triggerElement}` });
|
|
388
|
+
}
|
|
389
|
+
for (const c of result.conditionals) {
|
|
390
|
+
obs.push({ elementMeaning: c.triggerElement, observationType: "CONDITIONAL_REVEAL", observedText: c.revealedText, triggeredBy: `select_change:${c.triggerElement}:${c.triggerValue}` });
|
|
391
|
+
}
|
|
392
|
+
for (const lp of result.loadingPatterns) {
|
|
393
|
+
obs.push({ elementMeaning: "(submit button)", observationType: "LOADING_STATE", observedText: lp, triggeredBy: "form_submit" });
|
|
394
|
+
}
|
|
395
|
+
for (const tp of result.tabPanels ?? []) {
|
|
396
|
+
obs.push({ elementMeaning: tp.tabLabel, observationType: "TAB_PANEL", observedText: tp.panelContent, triggeredBy: `tab_click:${tp.tabLabel}` });
|
|
397
|
+
}
|
|
398
|
+
for (const tt of result.tooltips ?? []) {
|
|
399
|
+
obs.push({ elementMeaning: tt.triggerLabel, observationType: "TOOLTIP", observedText: tt.tooltipText, triggeredBy: "hover_or_title" });
|
|
400
|
+
}
|
|
401
|
+
for (const cs of result.carouselSlides ?? []) {
|
|
402
|
+
obs.push({ elementMeaning: `Slide ${cs.slideIndex + 1}`, observationType: "CAROUSEL_SLIDE", observedText: cs.content, triggeredBy: `carousel_next:${cs.slideIndex}` });
|
|
403
|
+
}
|
|
404
|
+
for (const ws of result.wizardSteps ?? []) {
|
|
405
|
+
obs.push({ elementMeaning: ws.stepLabel ?? `Step ${ws.stepIndex + 1}`, observationType: "WIZARD_STEP", observedText: ws.content, triggeredBy: `wizard_next:${ws.stepIndex}` });
|
|
406
|
+
}
|
|
407
|
+
for (const ft of result.focusTraps ?? []) {
|
|
408
|
+
obs.push({ elementMeaning: ft.modalTrigger, observationType: "FOCUS_TRAP", observedText: ft.focusTrapped ? "Focus correctly trapped inside dialog" : "FAIL: focus escaped dialog \u2014 WCAG 2.1.2 violation", triggeredBy: "tab_key_sequence" });
|
|
409
|
+
}
|
|
410
|
+
for (const issue of result.skipNavIssues ?? []) {
|
|
411
|
+
obs.push({ elementMeaning: "(page)", observationType: "SKIP_NAV_ISSUE", observedText: issue, triggeredBy: "skip_nav_audit" });
|
|
412
|
+
}
|
|
413
|
+
for (const cm of result.contextMenus ?? []) {
|
|
414
|
+
obs.push({ elementMeaning: cm.trigger, observationType: "CONTEXT_MENU", observedText: cm.items.join(" | "), triggeredBy: "right_click" });
|
|
415
|
+
}
|
|
416
|
+
return obs;
|
|
417
|
+
}
|
|
418
|
+
function probeResultToMarkdown(result) {
|
|
419
|
+
const lines = [`## Probe Observations \u2014 ${result.url}
|
|
420
|
+
`];
|
|
421
|
+
if (result.validationErrors.length > 0) {
|
|
422
|
+
lines.push("### Validation Errors (observed)");
|
|
423
|
+
for (const ve of result.validationErrors) {
|
|
424
|
+
lines.push(`- **${ve.field}** (${ve.trigger}): "${ve.errorText}"`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (result.successMessages.length > 0) {
|
|
428
|
+
lines.push("\n### Success / Feedback Messages (observed)");
|
|
429
|
+
for (const sm of result.successMessages) {
|
|
430
|
+
lines.push(`- ${sm.type}: "${sm.content}"`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (result.modals.length > 0) {
|
|
434
|
+
lines.push("\n### Modal / Dialog Contents (observed)");
|
|
435
|
+
for (const m of result.modals) {
|
|
436
|
+
lines.push(`- **Trigger:** ${m.triggerElement}`);
|
|
437
|
+
if (m.title) lines.push(` - Title: ${m.title}`);
|
|
438
|
+
lines.push(` - Content: ${m.content.slice(0, 200)}`);
|
|
439
|
+
if (m.fields.length > 0) lines.push(` - Fields: ${m.fields.join(", ")}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if ((result.tabPanels ?? []).length > 0) {
|
|
443
|
+
lines.push("\n### Tab Panel Contents (observed)");
|
|
444
|
+
for (const tp of result.tabPanels) {
|
|
445
|
+
lines.push(`- **Tab: ${tp.tabLabel}**${tp.panelHeading ? ` \u2014 ${tp.panelHeading}` : ""}: ${tp.panelContent.slice(0, 200)}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if ((result.tooltips ?? []).length > 0) {
|
|
449
|
+
lines.push("\n### Tooltips (observed)");
|
|
450
|
+
for (const tt of result.tooltips) {
|
|
451
|
+
lines.push(`- **${tt.triggerLabel}**: ${tt.tooltipText}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if ((result.carouselSlides ?? []).length > 0) {
|
|
455
|
+
lines.push("\n### Carousel Slides (observed)");
|
|
456
|
+
for (const cs of result.carouselSlides) {
|
|
457
|
+
lines.push(`- **Slide ${cs.slideIndex + 1}**${cs.heading ? ` \u2014 ${cs.heading}` : ""}: ${cs.content.slice(0, 200)}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if ((result.wizardSteps ?? []).length > 0) {
|
|
461
|
+
lines.push("\n### Wizard / Multi-Step Form (observed)");
|
|
462
|
+
for (const ws of result.wizardSteps) {
|
|
463
|
+
const label = ws.stepLabel ? ` \u2014 ${ws.stepLabel}` : "";
|
|
464
|
+
lines.push(`- **Step ${ws.stepIndex + 1}${label}**: ${ws.content.slice(0, 200)}`);
|
|
465
|
+
if (ws.fields.length > 0) lines.push(` - Fields: ${ws.fields.join(", ")}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if ((result.focusTraps ?? []).length > 0) {
|
|
469
|
+
lines.push("\n### Focus-Trap Audit (observed)");
|
|
470
|
+
for (const ft of result.focusTraps) {
|
|
471
|
+
const verdict = ft.focusTrapped ? "\u2705 PASS" : "\u274C FAIL (WCAG 2.1.2)";
|
|
472
|
+
lines.push(`- **${ft.modalTrigger}**: ${verdict} \u2014 focused: [${ft.sampledFocused.slice(0, 4).join(", ")}]`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (result.conditionals.length > 0) {
|
|
476
|
+
lines.push("\n### Conditional UI (observed)");
|
|
477
|
+
for (const c of result.conditionals) {
|
|
478
|
+
lines.push(`- **${c.triggerElement}** = "${c.triggerValue}" reveals: ${c.revealedText}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (result.loadingPatterns.length > 0) {
|
|
482
|
+
lines.push("\n### Loading State Text (observed)");
|
|
483
|
+
for (const lp of result.loadingPatterns) lines.push(`- "${lp}"`);
|
|
484
|
+
}
|
|
485
|
+
const full = lines.join("\n");
|
|
486
|
+
const errorSection = result.validationErrors.length > 0 ? `## Real Validation Errors \u2014 ${result.url}
|
|
487
|
+
` + result.validationErrors.map((e) => `- **${e.field}**: "${e.errorText}"`).join("\n") : "";
|
|
488
|
+
const interactionSection = [
|
|
489
|
+
result.successMessages.length > 0 ? `## Success Messages \u2014 ${result.url}
|
|
490
|
+
` + result.successMessages.map((s) => `- ${s.type}: "${s.content}"`).join("\n") : "",
|
|
491
|
+
result.loadingPatterns.length > 0 ? `## Loading Patterns \u2014 ${result.url}
|
|
492
|
+
` + result.loadingPatterns.map((lp) => `- "${lp}"`).join("\n") : "",
|
|
493
|
+
result.modals.length > 0 ? `## Modal Contents \u2014 ${result.url}
|
|
494
|
+
` + result.modals.map((m) => `### ${m.triggerElement}
|
|
495
|
+
${m.content}`).join("\n") : ""
|
|
496
|
+
].filter(Boolean).join("\n\n");
|
|
497
|
+
return {
|
|
498
|
+
errorCatalogueChunk: errorSection,
|
|
499
|
+
interactionPatternsChunk: interactionSection
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
async function probeAllScreens(page, tenantId, projectId, discovered, onProgress, jobId) {
|
|
503
|
+
const probeable = discovered.filter((d) => d.screenId && d.url && !d.url.includes("#"));
|
|
504
|
+
if (probeable.length === 0) return;
|
|
505
|
+
const errorCatalogueChunks = [];
|
|
506
|
+
const interactionChunks = [];
|
|
507
|
+
for (let i = 0; i < probeable.length; i++) {
|
|
508
|
+
const screen = probeable[i];
|
|
509
|
+
const pct = 88 + Math.round(i / probeable.length * 10);
|
|
510
|
+
await onProgress?.(pct, `Probing screen ${i + 1}/${probeable.length}: ${screen.url}`);
|
|
511
|
+
try {
|
|
512
|
+
if (page.url() !== screen.url) {
|
|
513
|
+
await page.goto(screen.url, { waitUntil: "domcontentloaded" });
|
|
514
|
+
await page.waitForTimeout(1500);
|
|
515
|
+
}
|
|
516
|
+
const result = await probeScreen(page, screen.screenId, screen.url, screen.elements ?? []);
|
|
517
|
+
const observations = probeResultToObservations(result);
|
|
518
|
+
if (observations.length > 0) {
|
|
519
|
+
await AppBrain.saveProbeObservations(tenantId, projectId, screen.screenId, observations).catch(console.warn);
|
|
520
|
+
}
|
|
521
|
+
const md = probeResultToMarkdown(result);
|
|
522
|
+
if (md.errorCatalogueChunk) errorCatalogueChunks.push(md.errorCatalogueChunk);
|
|
523
|
+
if (md.interactionPatternsChunk) interactionChunks.push(md.interactionPatternsChunk);
|
|
524
|
+
const probeMarkdown = [md.errorCatalogueChunk, md.interactionPatternsChunk].filter(Boolean).join("\n\n");
|
|
525
|
+
if (probeMarkdown.length > 50) {
|
|
526
|
+
await AppBrain.saveKnowledgeChunk(tenantId, projectId, "probe", screen.screenId, probeMarkdown, `Probe \u2014 ${screen.url}`).catch(console.warn);
|
|
527
|
+
}
|
|
528
|
+
const virtualScreensToSave = [];
|
|
529
|
+
for (const m of result.modals) {
|
|
530
|
+
if (m.stateId) {
|
|
531
|
+
virtualScreensToSave.push({
|
|
532
|
+
stateId: m.stateId,
|
|
533
|
+
triggerAction: `click ${m.triggerElement}`,
|
|
534
|
+
overlayType: "modal"
|
|
535
|
+
});
|
|
536
|
+
const modalUrl = `${screen.url}#probe:${m.stateId}`;
|
|
537
|
+
const modalTitle = m.title || `${m.triggerElement} Modal`;
|
|
538
|
+
AppBrain.saveScreen(tenantId, projectId, modalTitle, modalUrl, jobId, "INTERACTIVE_PROBE").then((modalScreen) => {
|
|
539
|
+
if (modalScreen?.id && screen.screenId) {
|
|
540
|
+
AppBrain.saveDiscoveredLinks(tenantId, projectId, screen.screenId, [{
|
|
541
|
+
href: modalUrl,
|
|
542
|
+
text: m.triggerElement,
|
|
543
|
+
linkType: "BUTTON_LINK",
|
|
544
|
+
source: "INTERACTIVE",
|
|
545
|
+
toScreenId: modalScreen.id,
|
|
546
|
+
jobId
|
|
547
|
+
}]).catch(() => {
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}).catch(() => {
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
for (const c of result.conditionals) {
|
|
555
|
+
if (c.stateId) {
|
|
556
|
+
virtualScreensToSave.push({
|
|
557
|
+
stateId: c.stateId,
|
|
558
|
+
triggerAction: `change ${c.triggerElement} to ${c.triggerValue}`,
|
|
559
|
+
overlayType: "panel"
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (virtualScreensToSave.length > 0) {
|
|
564
|
+
await AppBrain.saveVirtualScreens(tenantId, projectId, screen.screenId, virtualScreensToSave).catch(console.warn);
|
|
565
|
+
}
|
|
566
|
+
if (result.validationErrors.length > 0) {
|
|
567
|
+
console.log(`[probe] ${screen.url}: ${result.validationErrors.length} validation errors, ${result.modals.length} modals`);
|
|
568
|
+
}
|
|
569
|
+
} catch (err) {
|
|
570
|
+
console.warn(`[probe] \u2717 ${screen.url}:`, err instanceof Error ? err.message : err);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (errorCatalogueChunks.length > 0) {
|
|
574
|
+
const combined = `# Error Catalogue (Real Observed \u2014 Interactive Probe)
|
|
575
|
+
|
|
576
|
+
${errorCatalogueChunks.join("\n\n")}`;
|
|
577
|
+
await AppBrain.upsertSkillFile(tenantId, projectId, "ERROR_CATALOGUE", combined, {
|
|
578
|
+
summary: `Real validation error messages observed via interactive probe across ${errorCatalogueChunks.length} screens`,
|
|
579
|
+
scope: "PROJECT"
|
|
580
|
+
}).catch(console.warn);
|
|
581
|
+
}
|
|
582
|
+
if (interactionChunks.length > 0) {
|
|
583
|
+
const combined = `# Interaction Patterns (Real Observed \u2014 Interactive Probe)
|
|
584
|
+
|
|
585
|
+
${interactionChunks.join("\n\n")}`;
|
|
586
|
+
await AppBrain.upsertSkillFile(tenantId, projectId, "INTERACTION_PATTERNS", combined, {
|
|
587
|
+
summary: `Real loading states, success messages, and modal contents observed via interactive probe`,
|
|
588
|
+
scope: "PROJECT"
|
|
589
|
+
}).catch(console.warn);
|
|
590
|
+
}
|
|
591
|
+
await onProgress?.(98, "Interactive probe complete");
|
|
592
|
+
console.log(`[probe] Probed ${probeable.length} screens. Errors: ${errorCatalogueChunks.length}, Interactions: ${interactionChunks.length}`);
|
|
593
|
+
}
|
|
594
|
+
async function probeSkipNavigation(page) {
|
|
595
|
+
const issues = [];
|
|
596
|
+
try {
|
|
597
|
+
const skipLinks = await page.evaluate(
|
|
598
|
+
() => Array.from(document.querySelectorAll('a[href^="#"]')).filter((el) => /skip|jump to|main content|content|nav/i.test(el.innerText?.toLowerCase().trim() || "")).map((el) => ({
|
|
599
|
+
text: el.innerText?.trim() || "",
|
|
600
|
+
href: el.getAttribute("href") || "",
|
|
601
|
+
hasTarget: !!document.querySelector(el.getAttribute("href") || "#__invalid__")
|
|
602
|
+
}))
|
|
603
|
+
);
|
|
604
|
+
if (skipLinks.length === 0) {
|
|
605
|
+
issues.push("No skip navigation links found (WCAG 2.4.1 \u2014 keyboard users cannot bypass nav blocks)");
|
|
606
|
+
}
|
|
607
|
+
for (const link of skipLinks) {
|
|
608
|
+
if (!link.hasTarget) issues.push(`Skip link "${link.text}" \u2192 "${link.href}" target not found in DOM`);
|
|
609
|
+
}
|
|
610
|
+
} catch {
|
|
611
|
+
}
|
|
612
|
+
return issues;
|
|
613
|
+
}
|
|
614
|
+
async function probeContextMenus(page) {
|
|
615
|
+
const results = [];
|
|
616
|
+
try {
|
|
617
|
+
const targets = await page.$$('canvas, [data-contextmenu], [oncontextmenu], [class*="chart" i]:not(script)');
|
|
618
|
+
for (const t of targets.slice(0, 4)) {
|
|
619
|
+
try {
|
|
620
|
+
await t.click({ button: "right", timeout: 1e3 });
|
|
621
|
+
await page.waitForTimeout(350);
|
|
622
|
+
const items = await page.evaluate(
|
|
623
|
+
() => Array.from(document.querySelectorAll('[role="menu"] [role="menuitem"], .context-menu-item, [class*="contextmenu" i] li')).map((el) => el.innerText?.trim()).filter(Boolean).slice(0, 12)
|
|
624
|
+
);
|
|
625
|
+
if (items.length > 0) {
|
|
626
|
+
const trigger = await t.evaluate((el) => el.getAttribute("aria-label") || el.className.slice(0, 40) || el.tagName).catch(() => "");
|
|
627
|
+
results.push({ trigger, items });
|
|
628
|
+
}
|
|
629
|
+
await page.keyboard.press("Escape").catch(() => {
|
|
630
|
+
});
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
return results;
|
|
637
|
+
}
|
|
638
|
+
async function probeCarousels(page) {
|
|
639
|
+
const results = [];
|
|
640
|
+
try {
|
|
641
|
+
const CAROUSEL_SEL = '[class*="carousel" i], [class*="slider" i], [class*="swiper" i], [role="region"][aria-roledescription*="carousel" i]';
|
|
642
|
+
const hasCarousel = await page.evaluate((sel) => !!document.querySelector(sel), CAROUSEL_SEL);
|
|
643
|
+
if (!hasCarousel) return results;
|
|
644
|
+
const NEXT_SEL = '[aria-label*="next" i], [aria-label*="Next"], [class*="next" i]:not([class*="footnext"]):not(a), button[class*="arrow-right" i], button[class*="chevron-right" i]';
|
|
645
|
+
const slide0 = await page.evaluate((carSel) => {
|
|
646
|
+
const car = document.querySelector(carSel);
|
|
647
|
+
if (!car) return null;
|
|
648
|
+
const active = car.querySelector('[aria-hidden="false"], .active, [class*="active" i], [class*="current" i]');
|
|
649
|
+
const target = active ?? car;
|
|
650
|
+
return { heading: target.querySelector("h2,h3,h4")?.textContent?.trim() ?? "", content: target.innerText?.replace(/\s+/g, " ").trim().slice(0, 300) ?? "" };
|
|
651
|
+
}, CAROUSEL_SEL);
|
|
652
|
+
if (slide0?.content && slide0.content.length > 15) results.push({ slideIndex: 0, heading: slide0.heading || void 0, content: slide0.content });
|
|
653
|
+
const seen = new Set(slide0?.content ? [slide0.content] : []);
|
|
654
|
+
for (let i = 1; i < 8; i++) {
|
|
655
|
+
try {
|
|
656
|
+
const nextBtn = page.locator(NEXT_SEL).first();
|
|
657
|
+
const visible = await nextBtn.isVisible().catch(() => false);
|
|
658
|
+
if (!visible) break;
|
|
659
|
+
await nextBtn.click({ timeout: 1e3 });
|
|
660
|
+
await page.waitForTimeout(600);
|
|
661
|
+
const slide = await page.evaluate((carSel) => {
|
|
662
|
+
const car = document.querySelector(carSel);
|
|
663
|
+
if (!car) return null;
|
|
664
|
+
const active = car.querySelector('[aria-hidden="false"], .active, [class*="active" i], [class*="current" i]');
|
|
665
|
+
const target = active ?? car;
|
|
666
|
+
return { heading: target.querySelector("h2,h3,h4")?.textContent?.trim() ?? "", content: target.innerText?.replace(/\s+/g, " ").trim().slice(0, 300) ?? "" };
|
|
667
|
+
}, CAROUSEL_SEL);
|
|
668
|
+
if (!slide?.content || slide.content.length < 15 || seen.has(slide.content)) break;
|
|
669
|
+
seen.add(slide.content);
|
|
670
|
+
results.push({ slideIndex: i, heading: slide.heading || void 0, content: slide.content });
|
|
671
|
+
} catch {
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
return results;
|
|
678
|
+
}
|
|
679
|
+
async function probeWizardSteps(page) {
|
|
680
|
+
const results = [];
|
|
681
|
+
try {
|
|
682
|
+
const NEXT_LABELS = /^(next|continue|proceed|step \d|›|→|forward)/i;
|
|
683
|
+
const BACK_LABELS = /^(back|previous|prev|‹|←)/i;
|
|
684
|
+
const WIZARD_INDICATORS = '[role="progressbar"], [class*="stepper" i], [class*="wizard" i], [class*="progress-step" i], [aria-label*="step" i]';
|
|
685
|
+
const isWizard = await page.evaluate((sel) => !!document.querySelector(sel), WIZARD_INDICATORS);
|
|
686
|
+
if (!isWizard) return results;
|
|
687
|
+
const captureStep = async (stepIndex) => {
|
|
688
|
+
const data = await page.evaluate(() => {
|
|
689
|
+
const content = document.querySelector('main, [role="main"], form')?.innerText?.replace(/\s+/g, " ").trim().slice(0, 400) ?? "";
|
|
690
|
+
const stepLabel = document.querySelector('[aria-current="step"], [class*="step-label" i], [class*="stepper-label" i]')?.textContent?.trim() ?? "";
|
|
691
|
+
const fields = Array.from(document.querySelectorAll('input:not([type="hidden"]), select, textarea')).map((el) => el.getAttribute("aria-label") || el.placeholder || el.name || "").filter(Boolean).slice(0, 10);
|
|
692
|
+
return { content, stepLabel, fields };
|
|
693
|
+
});
|
|
694
|
+
return { stepIndex, stepLabel: data.stepLabel || void 0, content: data.content, fields: data.fields };
|
|
695
|
+
};
|
|
696
|
+
results.push(await captureStep(0));
|
|
697
|
+
for (let i = 1; i < 6; i++) {
|
|
698
|
+
try {
|
|
699
|
+
const nextLabel = await page.evaluate((nextRe) => {
|
|
700
|
+
const btns = Array.from(document.querySelectorAll('button, [role="button"]'));
|
|
701
|
+
const btn2 = btns.find((b) => new RegExp(nextRe).test(b.innerText?.trim() || ""));
|
|
702
|
+
return btn2?.innerText?.trim() || null;
|
|
703
|
+
}, NEXT_LABELS.source);
|
|
704
|
+
if (!nextLabel) break;
|
|
705
|
+
const btn = page.locator(`button:has-text("${nextLabel}")`).first();
|
|
706
|
+
await btn.click({ timeout: 1500 });
|
|
707
|
+
await page.waitForTimeout(800);
|
|
708
|
+
results.push(await captureStep(i));
|
|
709
|
+
} catch {
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
}
|
|
715
|
+
return results;
|
|
716
|
+
}
|
|
717
|
+
async function detectFocusTraps(page) {
|
|
718
|
+
const results = [];
|
|
719
|
+
try {
|
|
720
|
+
const openDialogs = await page.evaluate(() => {
|
|
721
|
+
return Array.from(document.querySelectorAll('[role="dialog"], [role="alertdialog"]')).filter((el) => window.getComputedStyle(el).display !== "none").map((el) => el.getAttribute("aria-label") || el.querySelector('[role="heading"]')?.textContent?.trim() || "dialog");
|
|
722
|
+
});
|
|
723
|
+
for (const dialogLabel of openDialogs.slice(0, 3)) {
|
|
724
|
+
const sampled = [];
|
|
725
|
+
let allInside = true;
|
|
726
|
+
for (let i = 0; i < 8; i++) {
|
|
727
|
+
await page.keyboard.press("Tab");
|
|
728
|
+
await page.waitForTimeout(80);
|
|
729
|
+
const focused = await page.evaluate(() => {
|
|
730
|
+
const el = document.activeElement;
|
|
731
|
+
const inDialog = !!el?.closest('[role="dialog"], [role="alertdialog"]');
|
|
732
|
+
return { label: el?.getAttribute("aria-label") || el?.textContent?.trim().slice(0, 40) || el?.tagName || "", inDialog };
|
|
733
|
+
});
|
|
734
|
+
sampled.push(focused.label);
|
|
735
|
+
if (!focused.inDialog) {
|
|
736
|
+
allInside = false;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
results.push({ modalTrigger: dialogLabel, focusTrapped: allInside, sampledFocused: sampled });
|
|
741
|
+
}
|
|
742
|
+
} catch {
|
|
743
|
+
}
|
|
744
|
+
return results;
|
|
745
|
+
}
|
|
746
|
+
async function probeTabPanels(page) {
|
|
747
|
+
const results = [];
|
|
748
|
+
try {
|
|
749
|
+
const tabs = await page.$$('[role="tab"]:not([aria-disabled="true"])');
|
|
750
|
+
for (const tab of tabs.slice(0, 10)) {
|
|
751
|
+
try {
|
|
752
|
+
const tabLabel = await tab.evaluate((el) => el.innerText?.trim() || el.getAttribute("aria-label") || "");
|
|
753
|
+
if (!tabLabel) continue;
|
|
754
|
+
await tab.click({ timeout: 1e3 });
|
|
755
|
+
await page.waitForTimeout(400);
|
|
756
|
+
const panelContent = await page.evaluate(() => {
|
|
757
|
+
const panel = document.querySelector('[role="tabpanel"]:not([hidden])');
|
|
758
|
+
if (!panel) return null;
|
|
759
|
+
const style = window.getComputedStyle(panel);
|
|
760
|
+
if (style.display === "none" || style.visibility === "hidden") return null;
|
|
761
|
+
return {
|
|
762
|
+
heading: panel.querySelector("h2,h3,h4")?.textContent?.trim() ?? "",
|
|
763
|
+
content: panel.innerText?.replace(/\s+/g, " ").trim().slice(0, 400) ?? ""
|
|
764
|
+
};
|
|
765
|
+
});
|
|
766
|
+
if (panelContent?.content && panelContent.content.length > 15) {
|
|
767
|
+
results.push({ tabLabel: tabLabel.slice(0, 60), panelContent: panelContent.content, panelHeading: panelContent.heading || void 0 });
|
|
768
|
+
}
|
|
769
|
+
} catch {
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
return results;
|
|
775
|
+
}
|
|
776
|
+
async function probeTooltips(page) {
|
|
777
|
+
const results = [];
|
|
778
|
+
try {
|
|
779
|
+
const candidates = await page.evaluate(() => {
|
|
780
|
+
return Array.from(document.querySelectorAll('[title], [aria-describedby], [data-tooltip], [data-tip], [class*="tooltip" i]')).filter((el) => {
|
|
781
|
+
const style = window.getComputedStyle(el);
|
|
782
|
+
return style.display !== "none" && style.visibility !== "hidden";
|
|
783
|
+
}).slice(0, 8).map((el) => ({
|
|
784
|
+
label: el.innerText?.trim().slice(0, 60) || el.getAttribute("aria-label") || "",
|
|
785
|
+
title: el.getAttribute("title") || "",
|
|
786
|
+
describedBy: el.getAttribute("aria-describedby") || "",
|
|
787
|
+
selector: el.tagName.toLowerCase() + (el.id ? `#${el.id}` : "")
|
|
788
|
+
}));
|
|
789
|
+
});
|
|
790
|
+
for (const c of candidates) {
|
|
791
|
+
try {
|
|
792
|
+
if (c.title && c.title.length > 5) {
|
|
793
|
+
results.push({ triggerLabel: c.label || c.selector, tooltipText: c.title.slice(0, 200) });
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (c.describedBy) {
|
|
797
|
+
const tooltipText = await page.evaluate((id) => document.getElementById(id)?.textContent?.trim() ?? "", c.describedBy);
|
|
798
|
+
if (tooltipText && tooltipText.length > 5) {
|
|
799
|
+
results.push({ triggerLabel: c.label || c.selector, tooltipText: tooltipText.slice(0, 200) });
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} catch {
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
} catch {
|
|
806
|
+
}
|
|
807
|
+
return results;
|
|
808
|
+
}
|
|
809
|
+
async function probeKeyboardNav(page) {
|
|
810
|
+
const sequence = [];
|
|
811
|
+
const issues = [];
|
|
812
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
813
|
+
let firstKey = null;
|
|
814
|
+
for (let i = 0; i < 50; i++) {
|
|
815
|
+
await page.keyboard.press("Tab");
|
|
816
|
+
const el = await page.evaluate(() => {
|
|
817
|
+
const active = document.activeElement;
|
|
818
|
+
if (!active || active === document.body) return null;
|
|
819
|
+
const outline = window.getComputedStyle(active).outline;
|
|
820
|
+
return {
|
|
821
|
+
tag: active.tagName.toLowerCase(),
|
|
822
|
+
role: active.getAttribute("role"),
|
|
823
|
+
ariaLabel: active.getAttribute("aria-label"),
|
|
824
|
+
text: (active.innerText ?? "").trim().slice(0, 60) || null,
|
|
825
|
+
outline,
|
|
826
|
+
tabIndex: active.tabIndex ?? null
|
|
827
|
+
};
|
|
828
|
+
});
|
|
829
|
+
if (!el) break;
|
|
830
|
+
const hasFocusIndicator = el.outline !== "none" && el.outline !== "" && !el.outline.startsWith("0px") && !el.outline.startsWith("rgba(0, 0, 0, 0)") && !el.outline.startsWith("rgba(0,0,0,0)");
|
|
831
|
+
const focusable = {
|
|
832
|
+
tag: el.tag,
|
|
833
|
+
role: el.role,
|
|
834
|
+
ariaLabel: el.ariaLabel,
|
|
835
|
+
text: el.text,
|
|
836
|
+
hasFocusIndicator,
|
|
837
|
+
tabIndex: el.tabIndex
|
|
838
|
+
};
|
|
839
|
+
const key = `${el.tag}|${el.text ?? ""}|${el.role ?? ""}`;
|
|
840
|
+
if (i === 0) {
|
|
841
|
+
firstKey = key;
|
|
842
|
+
} else if (key === firstKey || seenKeys.has(key)) {
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
seenKeys.add(key);
|
|
846
|
+
sequence.push(focusable);
|
|
847
|
+
if (!hasFocusIndicator) {
|
|
848
|
+
issues.push(`Element #${i + 1} (${el.tag}) has no visible focus indicator`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
focusableCount: sequence.length,
|
|
853
|
+
elementsWithNoFocusIndicator: issues.length,
|
|
854
|
+
sequence,
|
|
855
|
+
issues
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
export {
|
|
859
|
+
probeAllScreens,
|
|
860
|
+
probeKeyboardNav,
|
|
861
|
+
probeResultToMarkdown,
|
|
862
|
+
probeResultToObservations,
|
|
863
|
+
probeScreen
|
|
864
|
+
};
|
|
865
|
+
//# sourceMappingURL=interactive-probe.js.map
|