@detiq/crawler 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +49 -0
- package/README.md +11 -0
- 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/ai-login.d.ts +32 -0
- package/dist/ai-login.js +177 -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 +329 -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/crawler.d.ts +179 -0
- package/dist/crawler.js +3672 -0
- package/dist/crawler.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/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 +54 -0
- package/dist/extractors/advanced.js +245 -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/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +40 -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/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 +32 -0
- package/dist/frontier.js +83 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +35 -0
- package/dist/gap-pass.js +81 -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 +304 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +59 -0
- package/dist/incremental-diff.js +61 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +61 -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 +104 -0
- package/dist/interactive-probe.js +442 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +171 -0
- package/dist/job-queue.js +2256 -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 +100 -0
- package/dist/live-view.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/native-crawler.d.ts +69 -0
- package/dist/native-crawler.js +839 -0
- package/dist/native-crawler.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.d.ts +53 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +38 -0
- package/dist/phash.js +115 -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 +37 -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 +140 -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 +16 -0
- package/dist/screen-reader.js +46 -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/session-capture.d.ts +35 -0
- package/dist/session-capture.js +267 -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/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 +152 -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 +2 -0
- package/dist/sqs-queue.js +47 -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/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +168 -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 +67 -0
- package/dist/test-executor.js +311 -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 +87 -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 +25 -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 +45 -0
- package/src/accessibility-auditor.ts +102 -0
- package/src/ai-login.ts +218 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +396 -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/crawler.ts +4305 -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/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 +283 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/failure-analysis-agent.ts +49 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +112 -0
- package/src/gap-pass.ts +122 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +433 -0
- package/src/incremental-diff.ts +121 -0
- package/src/index.ts +35 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +623 -0
- package/src/job-queue.test.ts +120 -0
- package/src/job-queue.ts +2701 -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 +141 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/native-crawler.ts +1106 -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.ts +134 -0
- package/src/phash.ts +151 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +46 -0
- package/src/profiles/domain-profile.ts +167 -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 +89 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/session-capture.ts +325 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +131 -0
- package/src/sitemap.ts +224 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +49 -0
- package/src/sso-token.ts +120 -0
- package/src/stealth/human-behavior.ts +191 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +424 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +114 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +57 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +48 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -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/crawler.d.ts +179 -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/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 +54 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +32 -0
- package/types/gap-pass.d.ts +35 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +59 -0
- package/types/index.d.ts +35 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +104 -0
- package/types/job-queue.d.ts +171 -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/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/native-crawler.d.ts +69 -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.d.ts +53 -0
- package/types/phash.d.ts +38 -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 +16 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/session-capture.d.ts +38 -0
- package/types/session-export.d.ts +49 -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 +2 -0
- package/types/sso-token.d.ts +29 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +67 -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,168 @@
|
|
|
1
|
+
function gaussian(mean, sigma) {
|
|
2
|
+
const u1 = Math.random();
|
|
3
|
+
const u2 = Math.random();
|
|
4
|
+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
5
|
+
return mean + z * sigma;
|
|
6
|
+
}
|
|
7
|
+
async function humanDelay(min = 80, max = 450) {
|
|
8
|
+
const mean = (min + max) / 2;
|
|
9
|
+
const sigma = (max - min) / 6;
|
|
10
|
+
const delay = Math.max(min, Math.min(max, gaussian(mean, sigma)));
|
|
11
|
+
await new Promise((r) => setTimeout(r, Math.round(delay)));
|
|
12
|
+
}
|
|
13
|
+
async function simulateMouseMovement(page, targetX, targetY) {
|
|
14
|
+
try {
|
|
15
|
+
const mouse = page.mouse;
|
|
16
|
+
const steps = 8 + Math.floor(Math.random() * 8);
|
|
17
|
+
const startX = targetX - 100 + Math.random() * 200;
|
|
18
|
+
const startY = targetY - 100 + Math.random() * 200;
|
|
19
|
+
const cpX = (startX + targetX) / 2 + (Math.random() - 0.5) * 80;
|
|
20
|
+
const cpY = (startY + targetY) / 2 + (Math.random() - 0.5) * 80;
|
|
21
|
+
for (let i = 0; i <= steps; i++) {
|
|
22
|
+
const t = i / steps;
|
|
23
|
+
const x = (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * cpX + t * t * targetX;
|
|
24
|
+
const y = (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * cpY + t * t * targetY;
|
|
25
|
+
await mouse.move(x + (Math.random() - 0.5) * 2, y + (Math.random() - 0.5) * 2);
|
|
26
|
+
if (i < steps) await humanDelay(8, 25);
|
|
27
|
+
}
|
|
28
|
+
await mouse.move(targetX + (Math.random() - 0.5) * 4, targetY + (Math.random() - 0.5) * 4);
|
|
29
|
+
await humanDelay(30, 80);
|
|
30
|
+
await mouse.move(targetX, targetY);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function simulateMouseHover(page, selector) {
|
|
35
|
+
try {
|
|
36
|
+
const el = await page.$(selector);
|
|
37
|
+
if (!el) return;
|
|
38
|
+
const box = await el.boundingBox();
|
|
39
|
+
if (!box) return;
|
|
40
|
+
const cx = box.x + box.width / 2;
|
|
41
|
+
const cy = box.y + box.height / 2;
|
|
42
|
+
await simulateMouseMovement(page, cx, cy);
|
|
43
|
+
await humanDelay(100, 300);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function simulateClick(page, selector) {
|
|
48
|
+
try {
|
|
49
|
+
await simulateMouseHover(page, selector);
|
|
50
|
+
await humanDelay(50, 150);
|
|
51
|
+
await page.click(selector, { delay: Math.round(gaussian(80, 30)) });
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function injectFingerprintOverrides(page) {
|
|
56
|
+
await page.addInitScript(() => {
|
|
57
|
+
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
58
|
+
HTMLCanvasElement.prototype.toDataURL = function(...args) {
|
|
59
|
+
const ctx = this.getContext("2d");
|
|
60
|
+
if (ctx) {
|
|
61
|
+
const imageData = ctx.getImageData(0, 0, this.width, this.height);
|
|
62
|
+
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
63
|
+
imageData.data[i] = Math.max(0, Math.min(255, imageData.data[i] + (Math.random() < 0.01 ? Math.random() > 0.5 ? 1 : -1 : 0)));
|
|
64
|
+
}
|
|
65
|
+
ctx.putImageData(imageData, 0, 0);
|
|
66
|
+
}
|
|
67
|
+
return origToDataURL.apply(this, args);
|
|
68
|
+
};
|
|
69
|
+
const origGetParam = WebGLRenderingContext.prototype.getParameter;
|
|
70
|
+
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
71
|
+
const UNMASKED_VENDOR_WEBGL = 37445;
|
|
72
|
+
const UNMASKED_RENDERER_WEBGL = 37446;
|
|
73
|
+
if (parameter === UNMASKED_VENDOR_WEBGL) return "Intel Inc.";
|
|
74
|
+
if (parameter === UNMASKED_RENDERER_WEBGL) return "Intel Iris OpenGL Engine";
|
|
75
|
+
return origGetParam.call(this, parameter);
|
|
76
|
+
};
|
|
77
|
+
const cpuCounts = [4, 6, 8, 8];
|
|
78
|
+
Object.defineProperty(navigator, "hardwareConcurrency", {
|
|
79
|
+
get: () => cpuCounts[Math.floor(Math.random() * cpuCounts.length)],
|
|
80
|
+
configurable: true
|
|
81
|
+
});
|
|
82
|
+
Object.defineProperty(navigator, "deviceMemory", {
|
|
83
|
+
get: () => [4, 8][Math.floor(Math.random() * 2)],
|
|
84
|
+
configurable: true
|
|
85
|
+
});
|
|
86
|
+
}).catch(() => {
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function infiniteScrollToBottom(page, opts = {}) {
|
|
90
|
+
const { maxScrolls = 50, scrollDelay = 400, stabilizeMs = 600 } = opts;
|
|
91
|
+
let scrollCount = 0;
|
|
92
|
+
let sameHeightCount = 0;
|
|
93
|
+
let lastHeight = 0;
|
|
94
|
+
try {
|
|
95
|
+
while (scrollCount < maxScrolls) {
|
|
96
|
+
const currentHeight = await page.evaluate(() => document.body.scrollHeight).catch(() => 0);
|
|
97
|
+
if (currentHeight === lastHeight) {
|
|
98
|
+
sameHeightCount++;
|
|
99
|
+
if (sameHeightCount >= 3) break;
|
|
100
|
+
} else {
|
|
101
|
+
sameHeightCount = 0;
|
|
102
|
+
lastHeight = currentHeight;
|
|
103
|
+
}
|
|
104
|
+
const scrollBy = 500 + Math.floor(Math.random() * 300);
|
|
105
|
+
await page.evaluate((by) => window.scrollBy(0, by), scrollBy).catch(() => {
|
|
106
|
+
});
|
|
107
|
+
await humanDelay(scrollDelay - 100, scrollDelay + 200);
|
|
108
|
+
await page.waitForTimeout(stabilizeMs).catch(() => {
|
|
109
|
+
});
|
|
110
|
+
scrollCount++;
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
return { scrollCount, heightReached: lastHeight };
|
|
115
|
+
}
|
|
116
|
+
async function extractIframeContent(page) {
|
|
117
|
+
const results = [];
|
|
118
|
+
try {
|
|
119
|
+
const iframeSrcs = await page.evaluate(
|
|
120
|
+
() => Array.from(document.querySelectorAll("iframe")).map((f) => f.getAttribute("src") ?? f.getAttribute("data-src") ?? "")
|
|
121
|
+
).catch(() => []);
|
|
122
|
+
for (const frame of page.frames()) {
|
|
123
|
+
const src = frame.url();
|
|
124
|
+
if (!src || src === "about:blank" || src === page.url()) continue;
|
|
125
|
+
try {
|
|
126
|
+
const content = await frame.content().catch(() => "");
|
|
127
|
+
if (!content || content.length < 50) continue;
|
|
128
|
+
const text = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
129
|
+
results.push({ src, content: content.slice(0, 1e4), markdown: text.slice(0, 5e3) });
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
return results;
|
|
136
|
+
}
|
|
137
|
+
async function pierceShadowDom(page) {
|
|
138
|
+
try {
|
|
139
|
+
return await page.evaluate(() => {
|
|
140
|
+
const parts = [];
|
|
141
|
+
function walkShadow(root) {
|
|
142
|
+
const shadow = root.shadowRoot;
|
|
143
|
+
if (shadow) {
|
|
144
|
+
parts.push(shadow.innerHTML ?? "");
|
|
145
|
+
for (const child of [...shadow.querySelectorAll("*")]) walkShadow(child);
|
|
146
|
+
}
|
|
147
|
+
for (const child of Array.from(root.querySelectorAll(":not(script):not(style)"))) {
|
|
148
|
+
if (child.shadowRoot) walkShadow(child);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
walkShadow(document.body);
|
|
152
|
+
return parts.join("\n").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 1e4);
|
|
153
|
+
}).catch(() => "");
|
|
154
|
+
} catch {
|
|
155
|
+
return "";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export {
|
|
159
|
+
extractIframeContent,
|
|
160
|
+
humanDelay,
|
|
161
|
+
infiniteScrollToBottom,
|
|
162
|
+
injectFingerprintOverrides,
|
|
163
|
+
pierceShadowDom,
|
|
164
|
+
simulateClick,
|
|
165
|
+
simulateMouseHover,
|
|
166
|
+
simulateMouseMovement
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=human-behavior.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/stealth/human-behavior.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Page } from 'playwright';\n\n// Gaussian random using Box-Muller transform\nfunction gaussian(mean: number, sigma: number): number {\n const u1 = Math.random();\n const u2 = Math.random();\n const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n return mean + z * sigma;\n}\n\nexport async function humanDelay(min = 80, max = 450): Promise<void> {\n const mean = (min + max) / 2;\n const sigma = (max - min) / 6;\n const delay = Math.max(min, Math.min(max, gaussian(mean, sigma)));\n await new Promise(r => setTimeout(r, Math.round(delay)));\n}\n\nexport async function simulateMouseMovement(page: Page, targetX: number, targetY: number): Promise<void> {\n try {\n const mouse = page.mouse;\n const steps = 8 + Math.floor(Math.random() * 8);\n // Generate Bezier control points with slight perpendicular offset\n const startX = targetX - 100 + Math.random() * 200;\n const startY = targetY - 100 + Math.random() * 200;\n const cpX = (startX + targetX) / 2 + (Math.random() - 0.5) * 80;\n const cpY = (startY + targetY) / 2 + (Math.random() - 0.5) * 80;\n\n for (let i = 0; i <= steps; i++) {\n const t = i / steps;\n // Quadratic Bezier: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2\n const x = (1 - t) * (1 - t) * startX + 2 * (1 - t) * t * cpX + t * t * targetX;\n const y = (1 - t) * (1 - t) * startY + 2 * (1 - t) * t * cpY + t * t * targetY;\n await mouse.move(x + (Math.random() - 0.5) * 2, y + (Math.random() - 0.5) * 2);\n if (i < steps) await humanDelay(8, 25);\n }\n // Small overshoot correction\n await mouse.move(targetX + (Math.random() - 0.5) * 4, targetY + (Math.random() - 0.5) * 4);\n await humanDelay(30, 80);\n await mouse.move(targetX, targetY);\n } catch { /* non-fatal */ }\n}\n\nexport async function simulateMouseHover(page: Page, selector: string): Promise<void> {\n try {\n const el = await page.$(selector);\n if (!el) return;\n const box = await el.boundingBox();\n if (!box) return;\n const cx = box.x + box.width / 2;\n const cy = box.y + box.height / 2;\n await simulateMouseMovement(page, cx, cy);\n await humanDelay(100, 300);\n } catch { /* non-fatal */ }\n}\n\nexport async function simulateClick(page: Page, selector: string): Promise<void> {\n try {\n await simulateMouseHover(page, selector);\n await humanDelay(50, 150);\n await page.click(selector, { delay: Math.round(gaussian(80, 30)) });\n } catch { /* non-fatal */ }\n}\n\nexport async function injectFingerprintOverrides(page: Page): Promise<void> {\n await page.addInitScript(() => {\n // Canvas noise \u2014 subtle per-pixel perturbation\n const origToDataURL = HTMLCanvasElement.prototype.toDataURL;\n HTMLCanvasElement.prototype.toDataURL = function(this: HTMLCanvasElement, ...args: any[]) {\n const ctx = this.getContext('2d');\n if (ctx) {\n const imageData = ctx.getImageData(0, 0, this.width, this.height);\n for (let i = 0; i < imageData.data.length; i += 4) {\n imageData.data[i] = Math.max(0, Math.min(255, imageData.data[i] + (Math.random() < 0.01 ? (Math.random() > 0.5 ? 1 : -1) : 0)));\n }\n ctx.putImageData(imageData, 0, 0);\n }\n return origToDataURL.apply(this, args as [type?: string, quality?: number]);\n };\n\n // WebGL parameter spoofing\n const origGetParam = WebGLRenderingContext.prototype.getParameter;\n WebGLRenderingContext.prototype.getParameter = function(this: WebGLRenderingContext, parameter: number) {\n const UNMASKED_VENDOR_WEBGL = 0x9245;\n const UNMASKED_RENDERER_WEBGL = 0x9246;\n if (parameter === UNMASKED_VENDOR_WEBGL) return 'Intel Inc.';\n if (parameter === UNMASKED_RENDERER_WEBGL) return 'Intel Iris OpenGL Engine';\n return origGetParam.call(this, parameter);\n };\n\n // Hardware concurrency \u2014 pick from common values\n const cpuCounts = [4, 6, 8, 8];\n Object.defineProperty(navigator, 'hardwareConcurrency', {\n get: () => cpuCounts[Math.floor(Math.random() * cpuCounts.length)],\n configurable: true,\n });\n\n // Device memory\n Object.defineProperty(navigator, 'deviceMemory', {\n get: () => [4, 8][Math.floor(Math.random() * 2)],\n configurable: true,\n });\n }).catch(() => {});\n}\n\nexport async function infiniteScrollToBottom(\n page: Page,\n opts: { maxScrolls?: number; scrollDelay?: number; stabilizeMs?: number } = {}\n): Promise<{ scrollCount: number; heightReached: number }> {\n const { maxScrolls = 50, scrollDelay = 400, stabilizeMs = 600 } = opts;\n let scrollCount = 0;\n let sameHeightCount = 0;\n let lastHeight = 0;\n\n try {\n while (scrollCount < maxScrolls) {\n const currentHeight: number = await page.evaluate(() => document.body.scrollHeight).catch(() => 0);\n if (currentHeight === lastHeight) {\n sameHeightCount++;\n if (sameHeightCount >= 3) break; // no new content\n } else {\n sameHeightCount = 0;\n lastHeight = currentHeight;\n }\n\n const scrollBy = 500 + Math.floor(Math.random() * 300);\n await page.evaluate((by: number) => window.scrollBy(0, by), scrollBy).catch(() => {});\n await humanDelay(scrollDelay - 100, scrollDelay + 200);\n\n // Wait for potential lazy-loaded content\n await page.waitForTimeout(stabilizeMs).catch(() => {});\n scrollCount++;\n }\n } catch { /* non-fatal */ }\n\n return { scrollCount, heightReached: lastHeight };\n}\n\nexport async function extractIframeContent(page: Page): Promise<Array<{ src: string; content: string; markdown: string }>> {\n const results: Array<{ src: string; content: string; markdown: string }> = [];\n try {\n const iframeSrcs: string[] = await page.evaluate(() =>\n Array.from(document.querySelectorAll('iframe')).map(f => f.getAttribute('src') ?? f.getAttribute('data-src') ?? '')\n ).catch(() => []);\n\n for (const frame of page.frames()) {\n const src = frame.url();\n if (!src || src === 'about:blank' || src === page.url()) continue;\n try {\n const content = await frame.content().catch(() => '');\n if (!content || content.length < 50) continue;\n // Simple text extraction from iframe HTML\n const text = content.replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<[^>]+>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n results.push({ src, content: content.slice(0, 10_000), markdown: text.slice(0, 5_000) });\n } catch { /* skip this frame */ }\n }\n } catch { /* non-fatal */ }\n return results;\n}\n\nexport async function pierceShadowDom(page: Page): Promise<string> {\n try {\n return await page.evaluate(() => {\n const parts: string[] = [];\n function walkShadow(root: Element | ShadowRoot): void {\n const shadow = (root as any).shadowRoot;\n if (shadow) {\n parts.push(shadow.innerHTML ?? '');\n for (const child of [...shadow.querySelectorAll('*')]) walkShadow(child);\n }\n for (const child of Array.from(root.querySelectorAll(':not(script):not(style)'))) {\n if ((child as any).shadowRoot) walkShadow(child);\n }\n }\n walkShadow(document.body);\n return parts\n .join('\\n')\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<[^>]+>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 10_000);\n }).catch(() => '');\n } catch {\n return '';\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight OTel-compatible tracing for Zeta crawler.
|
|
3
|
+
*
|
|
4
|
+
* Emits spans as structured JSON logs compatible with OpenTelemetry format.
|
|
5
|
+
* Drop-in upgrade path: replace this module with @opentelemetry/sdk-node when needed.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const span = startSpan('crawl.url', { url, engine });
|
|
9
|
+
* // ... do work ...
|
|
10
|
+
* endSpan(span, { screensFound: 3 });
|
|
11
|
+
*/
|
|
12
|
+
export interface Span {
|
|
13
|
+
traceId: string;
|
|
14
|
+
spanId: string;
|
|
15
|
+
parentSpanId?: string;
|
|
16
|
+
name: string;
|
|
17
|
+
startTimeMs: number;
|
|
18
|
+
attributes: Record<string, string | number | boolean>;
|
|
19
|
+
}
|
|
20
|
+
export interface FinishedSpan extends Span {
|
|
21
|
+
endTimeMs: number;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
status: 'ok' | 'error';
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Start a new span. If a trace is already active, the new span is a child. */
|
|
27
|
+
export declare function startSpan(name: string, attributes?: Record<string, string | number | boolean>): Span;
|
|
28
|
+
/** End a span and emit it as a structured log line. */
|
|
29
|
+
export declare function endSpan(span: Span, extraAttributes?: Record<string, string | number | boolean>, error?: Error): FinishedSpan;
|
|
30
|
+
/** Convenience: wrap an async function in a span. */
|
|
31
|
+
export declare function withSpan<T>(name: string, attributes: Record<string, string | number | boolean>, fn: (span: Span) => Promise<T>): Promise<T>;
|
|
32
|
+
export declare function increment(metric: string, value?: number, tags?: Record<string, string>): void;
|
|
33
|
+
/** Flush counters as a structured log — call periodically (e.g. at job end). */
|
|
34
|
+
export declare function flushMetrics(): Record<string, number>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
let currentTraceId = null;
|
|
3
|
+
let currentSpanId = null;
|
|
4
|
+
function genId(bytes) {
|
|
5
|
+
return randomBytes(bytes).toString("hex");
|
|
6
|
+
}
|
|
7
|
+
function startSpan(name, attributes = {}) {
|
|
8
|
+
const traceId = currentTraceId ?? genId(16);
|
|
9
|
+
const spanId = genId(8);
|
|
10
|
+
const parentSpanId = currentSpanId ?? void 0;
|
|
11
|
+
if (!currentTraceId) currentTraceId = traceId;
|
|
12
|
+
currentSpanId = spanId;
|
|
13
|
+
return { traceId, spanId, parentSpanId, name, startTimeMs: Date.now(), attributes };
|
|
14
|
+
}
|
|
15
|
+
function endSpan(span, extraAttributes = {}, error) {
|
|
16
|
+
const endTimeMs = Date.now();
|
|
17
|
+
const durationMs = endTimeMs - span.startTimeMs;
|
|
18
|
+
const finished = {
|
|
19
|
+
...span,
|
|
20
|
+
attributes: { ...span.attributes, ...extraAttributes },
|
|
21
|
+
endTimeMs,
|
|
22
|
+
durationMs,
|
|
23
|
+
status: error ? "error" : "ok",
|
|
24
|
+
error: error?.message
|
|
25
|
+
};
|
|
26
|
+
if (!span.parentSpanId) {
|
|
27
|
+
currentTraceId = null;
|
|
28
|
+
currentSpanId = span.parentSpanId ?? null;
|
|
29
|
+
} else {
|
|
30
|
+
currentSpanId = span.parentSpanId ?? null;
|
|
31
|
+
}
|
|
32
|
+
console.log(JSON.stringify({
|
|
33
|
+
ts: new Date(endTimeMs).toISOString(),
|
|
34
|
+
level: error ? "error" : "info",
|
|
35
|
+
msg: `span:${finished.name}`,
|
|
36
|
+
trace_id: finished.traceId,
|
|
37
|
+
span_id: finished.spanId,
|
|
38
|
+
parent_span_id: finished.parentSpanId,
|
|
39
|
+
duration_ms: durationMs,
|
|
40
|
+
status: finished.status,
|
|
41
|
+
error: finished.error,
|
|
42
|
+
...finished.attributes
|
|
43
|
+
}));
|
|
44
|
+
return finished;
|
|
45
|
+
}
|
|
46
|
+
async function withSpan(name, attributes, fn) {
|
|
47
|
+
const span = startSpan(name, attributes);
|
|
48
|
+
try {
|
|
49
|
+
const result = await fn(span);
|
|
50
|
+
endSpan(span);
|
|
51
|
+
return result;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
endSpan(span, {}, err);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const counters = /* @__PURE__ */ new Map();
|
|
58
|
+
function increment(metric, value = 1, tags = {}) {
|
|
59
|
+
const key = metric + (Object.keys(tags).length ? ":" + Object.entries(tags).map(([k, v]) => `${k}=${v}`).join(",") : "");
|
|
60
|
+
counters.set(key, (counters.get(key) ?? 0) + value);
|
|
61
|
+
}
|
|
62
|
+
function flushMetrics() {
|
|
63
|
+
const snapshot = Object.fromEntries(counters);
|
|
64
|
+
if (Object.keys(snapshot).length > 0) {
|
|
65
|
+
console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg: "metrics:flush", ...snapshot }));
|
|
66
|
+
counters.clear();
|
|
67
|
+
}
|
|
68
|
+
return snapshot;
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
endSpan,
|
|
72
|
+
flushMetrics,
|
|
73
|
+
increment,
|
|
74
|
+
startSpan,
|
|
75
|
+
withSpan
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/telemetry.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Lightweight OTel-compatible tracing for Zeta crawler.\n *\n * Emits spans as structured JSON logs compatible with OpenTelemetry format.\n * Drop-in upgrade path: replace this module with @opentelemetry/sdk-node when needed.\n *\n * Usage:\n * const span = startSpan('crawl.url', { url, engine });\n * // ... do work ...\n * endSpan(span, { screensFound: 3 });\n */\n\nimport { randomBytes } from 'node:crypto';\n\nexport interface Span {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n name: string;\n startTimeMs: number;\n attributes: Record<string, string | number | boolean>;\n}\n\nexport interface FinishedSpan extends Span {\n endTimeMs: number;\n durationMs: number;\n status: 'ok' | 'error';\n error?: string;\n}\n\n// Active trace context \u2014 per async execution context (simplified: per-process global)\nlet currentTraceId: string | null = null;\nlet currentSpanId: string | null = null;\n\nfunction genId(bytes: number): string {\n return randomBytes(bytes).toString('hex');\n}\n\n/** Start a new span. If a trace is already active, the new span is a child. */\nexport function startSpan(name: string, attributes: Record<string, string | number | boolean> = {}): Span {\n const traceId = currentTraceId ?? genId(16);\n const spanId = genId(8);\n const parentSpanId = currentSpanId ?? undefined;\n\n if (!currentTraceId) currentTraceId = traceId;\n currentSpanId = spanId;\n\n return { traceId, spanId, parentSpanId, name, startTimeMs: Date.now(), attributes };\n}\n\n/** End a span and emit it as a structured log line. */\nexport function endSpan(span: Span, extraAttributes: Record<string, string | number | boolean> = {}, error?: Error): FinishedSpan {\n const endTimeMs = Date.now();\n const durationMs = endTimeMs - span.startTimeMs;\n const finished: FinishedSpan = {\n ...span,\n attributes: { ...span.attributes, ...extraAttributes },\n endTimeMs,\n durationMs,\n status: error ? 'error' : 'ok',\n error: error?.message,\n };\n\n // Reset context when root span ends\n if (!span.parentSpanId) {\n currentTraceId = null;\n currentSpanId = span.parentSpanId ?? null;\n } else {\n currentSpanId = span.parentSpanId ?? null;\n }\n\n // Emit as structured JSON \u2014 compatible with Datadog/Grafana log parsing\n console.log(JSON.stringify({\n ts: new Date(endTimeMs).toISOString(),\n level: error ? 'error' : 'info',\n msg: `span:${finished.name}`,\n trace_id: finished.traceId,\n span_id: finished.spanId,\n parent_span_id: finished.parentSpanId,\n duration_ms: durationMs,\n status: finished.status,\n error: finished.error,\n ...finished.attributes,\n }));\n\n return finished;\n}\n\n/** Convenience: wrap an async function in a span. */\nexport async function withSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span: Span) => Promise<T>,\n): Promise<T> {\n const span = startSpan(name, attributes);\n try {\n const result = await fn(span);\n endSpan(span);\n return result;\n } catch (err: any) {\n endSpan(span, {}, err);\n throw err;\n }\n}\n\n/** Counter metrics \u2014 increment and log on flush. */\nconst counters: Map<string, number> = new Map();\n\nexport function increment(metric: string, value = 1, tags: Record<string, string> = {}): void {\n const key = metric + (Object.keys(tags).length ? ':' + Object.entries(tags).map(([k, v]) => `${k}=${v}`).join(',') : '');\n counters.set(key, (counters.get(key) ?? 0) + value);\n}\n\n/** Flush counters as a structured log \u2014 call periodically (e.g. at job end). */\nexport function flushMetrics(): Record<string, number> {\n const snapshot = Object.fromEntries(counters);\n if (Object.keys(snapshot).length > 0) {\n console.log(JSON.stringify({ ts: new Date().toISOString(), level: 'info', msg: 'metrics:flush', ...snapshot }));\n counters.clear();\n }\n return snapshot;\n}\n"],
|
|
5
|
+
"mappings": "AAYA,SAAS,mBAAmB;AAmB5B,IAAI,iBAAgC;AACpC,IAAI,gBAA+B;AAEnC,SAAS,MAAM,OAAuB;AACpC,SAAO,YAAY,KAAK,EAAE,SAAS,KAAK;AAC1C;AAGO,SAAS,UAAU,MAAc,aAAwD,CAAC,GAAS;AACxG,QAAM,UAAU,kBAAkB,MAAM,EAAE;AAC1C,QAAM,SAAS,MAAM,CAAC;AACtB,QAAM,eAAe,iBAAiB;AAEtC,MAAI,CAAC,eAAgB,kBAAiB;AACtC,kBAAgB;AAEhB,SAAO,EAAE,SAAS,QAAQ,cAAc,MAAM,aAAa,KAAK,IAAI,GAAG,WAAW;AACpF;AAGO,SAAS,QAAQ,MAAY,kBAA6D,CAAC,GAAG,OAA6B;AAChI,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,aAAa,YAAY,KAAK;AACpC,QAAM,WAAyB;AAAA,IAC7B,GAAG;AAAA,IACH,YAAY,EAAE,GAAG,KAAK,YAAY,GAAG,gBAAgB;AAAA,IACrD;AAAA,IACA;AAAA,IACA,QAAQ,QAAQ,UAAU;AAAA,IAC1B,OAAO,OAAO;AAAA,EAChB;AAGA,MAAI,CAAC,KAAK,cAAc;AACtB,qBAAiB;AACjB,oBAAgB,KAAK,gBAAgB;AAAA,EACvC,OAAO;AACL,oBAAgB,KAAK,gBAAgB;AAAA,EACvC;AAGA,UAAQ,IAAI,KAAK,UAAU;AAAA,IACzB,IAAI,IAAI,KAAK,SAAS,EAAE,YAAY;AAAA,IACpC,OAAO,QAAQ,UAAU;AAAA,IACzB,KAAK,QAAQ,SAAS,IAAI;AAAA,IAC1B,UAAU,SAAS;AAAA,IACnB,SAAS,SAAS;AAAA,IAClB,gBAAgB,SAAS;AAAA,IACzB,aAAa;AAAA,IACb,QAAQ,SAAS;AAAA,IACjB,OAAO,SAAS;AAAA,IAChB,GAAG,SAAS;AAAA,EACd,CAAC,CAAC;AAEF,SAAO;AACT;AAGA,eAAsB,SACpB,MACA,YACA,IACY;AACZ,QAAM,OAAO,UAAU,MAAM,UAAU;AACvC,MAAI;AACF,UAAM,SAAS,MAAM,GAAG,IAAI;AAC5B,YAAQ,IAAI;AACZ,WAAO;AAAA,EACT,SAAS,KAAU;AACjB,YAAQ,MAAM,CAAC,GAAG,GAAG;AACrB,UAAM;AAAA,EACR;AACF;AAGA,MAAM,WAAgC,oBAAI,IAAI;AAEvC,SAAS,UAAU,QAAgB,QAAQ,GAAG,OAA+B,CAAC,GAAS;AAC5F,QAAM,MAAM,UAAU,OAAO,KAAK,IAAI,EAAE,SAAS,MAAM,OAAO,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI;AACrH,WAAS,IAAI,MAAM,SAAS,IAAI,GAAG,KAAK,KAAK,KAAK;AACpD;AAGO,SAAS,eAAuC;AACrD,QAAM,WAAW,OAAO,YAAY,QAAQ;AAC5C,MAAI,OAAO,KAAK,QAAQ,EAAE,SAAS,GAAG;AACpC,YAAQ,IAAI,KAAK,UAAU,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,OAAO,QAAQ,KAAK,iBAAiB,GAAG,SAAS,CAAC,CAAC;AAC9G,aAAS,MAAM;AAAA,EACjB;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ExecutionStep } from '@qip/agents';
|
|
2
|
+
export interface StepResult {
|
|
3
|
+
stepNumber: number;
|
|
4
|
+
description: string;
|
|
5
|
+
status: 'PASSED' | 'FAILED' | 'SKIPPED';
|
|
6
|
+
duration: number;
|
|
7
|
+
error?: string;
|
|
8
|
+
screenshotPath?: string;
|
|
9
|
+
healed?: boolean;
|
|
10
|
+
/** 'mechanical' = same selector text, different Playwright locator strategy.
|
|
11
|
+
* 'ai' = AI Self-Healing Agent matched a semantically different element
|
|
12
|
+
* (the app's selector text itself changed, e.g. "Sign In" -> "Log In"). */
|
|
13
|
+
healedVia?: 'mechanical' | 'ai';
|
|
14
|
+
healReasoning?: string;
|
|
15
|
+
healConfidence?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ExecutionResult {
|
|
18
|
+
stepResults: StepResult[];
|
|
19
|
+
status: 'PASSED' | 'FAILED' | 'ERROR' | 'FLAKY';
|
|
20
|
+
errorStep?: number;
|
|
21
|
+
totalDuration: number;
|
|
22
|
+
finalScreenshotPath?: string;
|
|
23
|
+
retryCount?: number;
|
|
24
|
+
consoleLogs?: Array<{
|
|
25
|
+
type: string;
|
|
26
|
+
text: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}>;
|
|
29
|
+
networkErrors?: Array<{
|
|
30
|
+
url: string;
|
|
31
|
+
status: number;
|
|
32
|
+
method: string;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
}>;
|
|
35
|
+
rootCauseSummary?: string;
|
|
36
|
+
networkTimings?: Array<{
|
|
37
|
+
url: string;
|
|
38
|
+
method: string;
|
|
39
|
+
status: number;
|
|
40
|
+
duration: number;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
export declare function executeTestPlan(plan: ExecutionStep[], options: {
|
|
44
|
+
screenshotDir: string;
|
|
45
|
+
testRunId: string;
|
|
46
|
+
viewport?: {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
};
|
|
50
|
+
onStepComplete?: (result: StepResult) => void;
|
|
51
|
+
/** Run the test up to this many times to detect flakiness (default 1). */
|
|
52
|
+
maxRuns?: number;
|
|
53
|
+
browser?: 'chromium' | 'firefox' | 'webkit';
|
|
54
|
+
/** Data row for parameterized execution: substitutes {{varname}} in step fields. */
|
|
55
|
+
dataRow?: Record<string, string>;
|
|
56
|
+
/** Enables the AI Self-Healing Agent tier when a step's selector can't be resolved
|
|
57
|
+
* even after trying alternate locator strategies. Omit to keep mechanical-only
|
|
58
|
+
* healing (no LLM calls during execution). */
|
|
59
|
+
healCtx?: {
|
|
60
|
+
tenantId: string;
|
|
61
|
+
projectId: string;
|
|
62
|
+
};
|
|
63
|
+
/** Decrypted Playwright storageState JSON — restores a logged-in session so
|
|
64
|
+
* steps don't have to re-authenticate. Without this, any test case behind a
|
|
65
|
+
* login wall fails at the first authenticated step. */
|
|
66
|
+
storageState?: string;
|
|
67
|
+
}): Promise<ExecutionResult>;
|