uxinspect 0.2.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -22
- package/dist/a11y-filter.d.ts +15 -0
- package/dist/a11y-filter.d.ts.map +1 -0
- package/dist/a11y-filter.js +107 -0
- package/dist/a11y-filter.js.map +1 -0
- package/dist/ab-compare.d.ts +23 -0
- package/dist/ab-compare.d.ts.map +1 -0
- package/dist/ab-compare.js +340 -0
- package/dist/ab-compare.js.map +1 -0
- package/dist/ai-codegen.d.ts +30 -0
- package/dist/ai-codegen.d.ts.map +1 -0
- package/dist/ai-codegen.js +296 -0
- package/dist/ai-codegen.js.map +1 -0
- package/dist/ai-triage.d.ts +26 -0
- package/dist/ai-triage.d.ts.map +1 -0
- package/dist/ai-triage.js +207 -0
- package/dist/ai-triage.js.map +1 -0
- package/dist/amp.d.ts +32 -0
- package/dist/amp.d.ts.map +1 -0
- package/dist/amp.js +179 -0
- package/dist/amp.js.map +1 -0
- package/dist/animation-audit.d.ts +25 -0
- package/dist/animation-audit.d.ts.map +1 -0
- package/dist/animation-audit.js +296 -0
- package/dist/animation-audit.js.map +1 -0
- package/dist/api.d.ts +3 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +85 -0
- package/dist/api.js.map +1 -0
- package/dist/aria-audit.d.ts +20 -0
- package/dist/aria-audit.d.ts.map +1 -0
- package/dist/aria-audit.js +445 -0
- package/dist/aria-audit.js.map +1 -0
- package/dist/assertions.d.ts +30 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +342 -0
- package/dist/assertions.js.map +1 -0
- package/dist/autofix.d.ts +40 -0
- package/dist/autofix.d.ts.map +1 -0
- package/dist/autofix.js +244 -0
- package/dist/autofix.js.map +1 -0
- package/dist/badge.d.ts +27 -0
- package/dist/badge.d.ts.map +1 -0
- package/dist/badge.js +183 -0
- package/dist/badge.js.map +1 -0
- package/dist/baseline-drift.d.ts +43 -0
- package/dist/baseline-drift.d.ts.map +1 -0
- package/dist/baseline-drift.js +208 -0
- package/dist/baseline-drift.js.map +1 -0
- package/dist/bdd.d.ts +31 -0
- package/dist/bdd.d.ts.map +1 -0
- package/dist/bdd.js +316 -0
- package/dist/bdd.js.map +1 -0
- package/dist/bisect.d.ts +32 -0
- package/dist/bisect.d.ts.map +1 -0
- package/dist/bisect.js +253 -0
- package/dist/bisect.js.map +1 -0
- package/dist/budget-diff.d.ts +37 -0
- package/dist/budget-diff.d.ts.map +1 -0
- package/dist/budget-diff.js +273 -0
- package/dist/budget-diff.js.map +1 -0
- package/dist/budget-file.d.ts +15 -0
- package/dist/budget-file.d.ts.map +1 -0
- package/dist/budget-file.js +185 -0
- package/dist/budget-file.js.map +1 -0
- package/dist/bundle-size.d.ts +36 -0
- package/dist/bundle-size.d.ts.map +1 -0
- package/dist/bundle-size.js +347 -0
- package/dist/bundle-size.js.map +1 -0
- package/dist/cache-headers.d.ts +33 -0
- package/dist/cache-headers.d.ts.map +1 -0
- package/dist/cache-headers.js +270 -0
- package/dist/cache-headers.js.map +1 -0
- package/dist/canonical-audit.d.ts +19 -0
- package/dist/canonical-audit.d.ts.map +1 -0
- package/dist/canonical-audit.js +196 -0
- package/dist/canonical-audit.js.map +1 -0
- package/dist/chaos.d.ts +38 -0
- package/dist/chaos.d.ts.map +1 -0
- package/dist/chaos.js +348 -0
- package/dist/chaos.js.map +1 -0
- package/dist/cli.js +201 -23
- package/dist/cli.js.map +1 -1
- package/dist/clickjacking-audit.d.ts +18 -0
- package/dist/clickjacking-audit.d.ts.map +1 -0
- package/dist/clickjacking-audit.js +231 -0
- package/dist/clickjacking-audit.js.map +1 -0
- package/dist/cls-culprit.d.ts +36 -0
- package/dist/cls-culprit.d.ts.map +1 -0
- package/dist/cls-culprit.js +203 -0
- package/dist/cls-culprit.js.map +1 -0
- package/dist/cls-timeline.d.ts +30 -0
- package/dist/cls-timeline.d.ts.map +1 -0
- package/dist/cls-timeline.js +61 -0
- package/dist/cls-timeline.js.map +1 -0
- package/dist/codegen-converter.d.ts +19 -0
- package/dist/codegen-converter.d.ts.map +1 -0
- package/dist/codegen-converter.js +464 -0
- package/dist/codegen-converter.js.map +1 -0
- package/dist/compression.d.ts +14 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +150 -0
- package/dist/compression.js.map +1 -0
- package/dist/console-errors.d.ts +24 -0
- package/dist/console-errors.d.ts.map +1 -0
- package/dist/console-errors.js +96 -0
- package/dist/console-errors.js.map +1 -0
- package/dist/content-quality.d.ts +34 -0
- package/dist/content-quality.d.ts.map +1 -0
- package/dist/content-quality.js +124 -0
- package/dist/content-quality.js.map +1 -0
- package/dist/contract-openapi.d.ts +74 -0
- package/dist/contract-openapi.d.ts.map +1 -0
- package/dist/contract-openapi.js +305 -0
- package/dist/contract-openapi.js.map +1 -0
- package/dist/cookie-banner.d.ts +27 -0
- package/dist/cookie-banner.d.ts.map +1 -0
- package/dist/cookie-banner.js +285 -0
- package/dist/cookie-banner.js.map +1 -0
- package/dist/cookie-flags-audit.d.ts +35 -0
- package/dist/cookie-flags-audit.d.ts.map +1 -0
- package/dist/cookie-flags-audit.js +167 -0
- package/dist/cookie-flags-audit.js.map +1 -0
- package/dist/cpu-throttle.d.ts +34 -0
- package/dist/cpu-throttle.d.ts.map +1 -0
- package/dist/cpu-throttle.js +149 -0
- package/dist/cpu-throttle.js.map +1 -0
- package/dist/crawl.d.ts +29 -0
- package/dist/crawl.d.ts.map +1 -0
- package/dist/crawl.js +153 -0
- package/dist/crawl.js.map +1 -0
- package/dist/critical-css.d.ts +25 -0
- package/dist/critical-css.d.ts.map +1 -0
- package/dist/critical-css.js +353 -0
- package/dist/critical-css.js.map +1 -0
- package/dist/cross-browser.d.ts +44 -0
- package/dist/cross-browser.d.ts.map +1 -0
- package/dist/cross-browser.js +300 -0
- package/dist/cross-browser.js.map +1 -0
- package/dist/csrf-audit.d.ts +33 -0
- package/dist/csrf-audit.d.ts.map +1 -0
- package/dist/csrf-audit.js +276 -0
- package/dist/csrf-audit.js.map +1 -0
- package/dist/css-coverage.d.ts +20 -0
- package/dist/css-coverage.d.ts.map +1 -0
- package/dist/css-coverage.js +91 -0
- package/dist/css-coverage.js.map +1 -0
- package/dist/csv-exporter.d.ts +34 -0
- package/dist/csv-exporter.d.ts.map +1 -0
- package/dist/csv-exporter.js +241 -0
- package/dist/csv-exporter.js.map +1 -0
- package/dist/dark-mode-audit.d.ts +31 -0
- package/dist/dark-mode-audit.d.ts.map +1 -0
- package/dist/dark-mode-audit.js +236 -0
- package/dist/dark-mode-audit.js.map +1 -0
- package/dist/dead-images.d.ts +18 -0
- package/dist/dead-images.d.ts.map +1 -0
- package/dist/dead-images.js +236 -0
- package/dist/dead-images.js.map +1 -0
- package/dist/deadclicks.d.ts +19 -0
- package/dist/deadclicks.d.ts.map +1 -0
- package/dist/deadclicks.js +109 -0
- package/dist/deadclicks.js.map +1 -0
- package/dist/discord-formatter.d.ts +39 -0
- package/dist/discord-formatter.d.ts.map +1 -0
- package/dist/discord-formatter.js +191 -0
- package/dist/discord-formatter.js.map +1 -0
- package/dist/dom-audit.d.ts +23 -0
- package/dist/dom-audit.d.ts.map +1 -0
- package/dist/dom-audit.js +111 -0
- package/dist/dom-audit.js.map +1 -0
- package/dist/driver.d.ts.map +1 -1
- package/dist/driver.js +10 -0
- package/dist/driver.js.map +1 -1
- package/dist/error-page-audit.d.ts +26 -0
- package/dist/error-page-audit.d.ts.map +1 -0
- package/dist/error-page-audit.js +219 -0
- package/dist/error-page-audit.js.map +1 -0
- package/dist/event-listener-audit.d.ts +22 -0
- package/dist/event-listener-audit.d.ts.map +1 -0
- package/dist/event-listener-audit.js +156 -0
- package/dist/event-listener-audit.js.map +1 -0
- package/dist/exposed-paths.d.ts +21 -0
- package/dist/exposed-paths.d.ts.map +1 -0
- package/dist/exposed-paths.js +116 -0
- package/dist/exposed-paths.js.map +1 -0
- package/dist/favicon-audit.d.ts +28 -0
- package/dist/favicon-audit.d.ts.map +1 -0
- package/dist/favicon-audit.js +358 -0
- package/dist/favicon-audit.js.map +1 -0
- package/dist/flaky-detector.d.ts +32 -0
- package/dist/flaky-detector.d.ts.map +1 -0
- package/dist/flaky-detector.js +254 -0
- package/dist/flaky-detector.js.map +1 -0
- package/dist/flaky.d.ts +28 -0
- package/dist/flaky.d.ts.map +1 -0
- package/dist/flaky.js +106 -0
- package/dist/flaky.js.map +1 -0
- package/dist/focus-trap-audit.d.ts +29 -0
- package/dist/focus-trap-audit.d.ts.map +1 -0
- package/dist/focus-trap-audit.js +285 -0
- package/dist/focus-trap-audit.js.map +1 -0
- package/dist/font-loading.d.ts +29 -0
- package/dist/font-loading.d.ts.map +1 -0
- package/dist/font-loading.js +216 -0
- package/dist/font-loading.js.map +1 -0
- package/dist/forms-audit.d.ts +23 -0
- package/dist/forms-audit.d.ts.map +1 -0
- package/dist/forms-audit.js +147 -0
- package/dist/forms-audit.js.map +1 -0
- package/dist/github-annotations.d.ts +17 -0
- package/dist/github-annotations.d.ts.map +1 -0
- package/dist/github-annotations.js +264 -0
- package/dist/github-annotations.js.map +1 -0
- package/dist/graphql.d.ts +60 -0
- package/dist/graphql.d.ts.map +1 -0
- package/dist/graphql.js +188 -0
- package/dist/graphql.js.map +1 -0
- package/dist/har-waterfall.d.ts +37 -0
- package/dist/har-waterfall.d.ts.map +1 -0
- package/dist/har-waterfall.js +376 -0
- package/dist/har-waterfall.js.map +1 -0
- package/dist/heading-hierarchy.d.ts +20 -0
- package/dist/heading-hierarchy.d.ts.map +1 -0
- package/dist/heading-hierarchy.js +112 -0
- package/dist/heading-hierarchy.js.map +1 -0
- package/dist/headless-detect.d.ts +22 -0
- package/dist/headless-detect.d.ts.map +1 -0
- package/dist/headless-detect.js +167 -0
- package/dist/headless-detect.js.map +1 -0
- package/dist/history-timeline.d.ts +13 -0
- package/dist/history-timeline.d.ts.map +1 -0
- package/dist/history-timeline.js +327 -0
- package/dist/history-timeline.js.map +1 -0
- package/dist/hreflang-audit.d.ts +26 -0
- package/dist/hreflang-audit.d.ts.map +1 -0
- package/dist/hreflang-audit.js +273 -0
- package/dist/hreflang-audit.js.map +1 -0
- package/dist/hydration-audit.d.ts +21 -0
- package/dist/hydration-audit.d.ts.map +1 -0
- package/dist/hydration-audit.js +277 -0
- package/dist/hydration-audit.js.map +1 -0
- package/dist/image-audit.d.ts +41 -0
- package/dist/image-audit.d.ts.map +1 -0
- package/dist/image-audit.js +229 -0
- package/dist/image-audit.js.map +1 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +708 -2
- package/dist/index.js.map +1 -1
- package/dist/init-wizard.d.ts +33 -0
- package/dist/init-wizard.d.ts.map +1 -0
- package/dist/init-wizard.js +289 -0
- package/dist/init-wizard.js.map +1 -0
- package/dist/inp-audit.d.ts +26 -0
- package/dist/inp-audit.d.ts.map +1 -0
- package/dist/inp-audit.js +202 -0
- package/dist/inp-audit.js.map +1 -0
- package/dist/js-coverage.d.ts +20 -0
- package/dist/js-coverage.d.ts.map +1 -0
- package/dist/js-coverage.js +81 -0
- package/dist/js-coverage.js.map +1 -0
- package/dist/json-schema.d.ts +27 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +284 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/keyboard.d.ts +21 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +119 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/lang-audit.d.ts +24 -0
- package/dist/lang-audit.d.ts.map +1 -0
- package/dist/lang-audit.js +141 -0
- package/dist/lang-audit.js.map +1 -0
- package/dist/lcp-element.d.ts +22 -0
- package/dist/lcp-element.d.ts.map +1 -0
- package/dist/lcp-element.js +240 -0
- package/dist/lcp-element.js.map +1 -0
- package/dist/longtasks.d.ts +38 -0
- package/dist/longtasks.d.ts.map +1 -0
- package/dist/longtasks.js +97 -0
- package/dist/longtasks.js.map +1 -0
- package/dist/mailbox.d.ts +35 -0
- package/dist/mailbox.d.ts.map +1 -0
- package/dist/mailbox.js +207 -0
- package/dist/mailbox.js.map +1 -0
- package/dist/media-audit.d.ts +20 -0
- package/dist/media-audit.d.ts.map +1 -0
- package/dist/media-audit.js +182 -0
- package/dist/media-audit.js.map +1 -0
- package/dist/metrics-exporter.d.ts +23 -0
- package/dist/metrics-exporter.d.ts.map +1 -0
- package/dist/metrics-exporter.js +297 -0
- package/dist/metrics-exporter.js.map +1 -0
- package/dist/mixed-content.d.ts +19 -0
- package/dist/mixed-content.d.ts.map +1 -0
- package/dist/mixed-content.js +86 -0
- package/dist/mixed-content.js.map +1 -0
- package/dist/motion-prefs.d.ts +21 -0
- package/dist/motion-prefs.d.ts.map +1 -0
- package/dist/motion-prefs.js +170 -0
- package/dist/motion-prefs.js.map +1 -0
- package/dist/open-graph.d.ts +40 -0
- package/dist/open-graph.d.ts.map +1 -0
- package/dist/open-graph.js +200 -0
- package/dist/open-graph.js.map +1 -0
- package/dist/orphan-assets.d.ts +17 -0
- package/dist/orphan-assets.d.ts.map +1 -0
- package/dist/orphan-assets.js +174 -0
- package/dist/orphan-assets.js.map +1 -0
- package/dist/page-object.d.ts +18 -0
- package/dist/page-object.d.ts.map +1 -0
- package/dist/page-object.js +346 -0
- package/dist/page-object.js.map +1 -0
- package/dist/pagination-audit.d.ts +24 -0
- package/dist/pagination-audit.d.ts.map +1 -0
- package/dist/pagination-audit.js +285 -0
- package/dist/pagination-audit.js.map +1 -0
- package/dist/passive-security.d.ts +19 -0
- package/dist/passive-security.d.ts.map +1 -0
- package/dist/passive-security.js +149 -0
- package/dist/passive-security.js.map +1 -0
- package/dist/pr-comment.d.ts +13 -0
- package/dist/pr-comment.d.ts.map +1 -0
- package/dist/pr-comment.js +316 -0
- package/dist/pr-comment.js.map +1 -0
- package/dist/precommit.d.ts +24 -0
- package/dist/precommit.d.ts.map +1 -0
- package/dist/precommit.js +239 -0
- package/dist/precommit.js.map +1 -0
- package/dist/prerender-audit.d.ts +22 -0
- package/dist/prerender-audit.d.ts.map +1 -0
- package/dist/prerender-audit.js +158 -0
- package/dist/prerender-audit.js.map +1 -0
- package/dist/print-audit.d.ts +21 -0
- package/dist/print-audit.d.ts.map +1 -0
- package/dist/print-audit.js +281 -0
- package/dist/print-audit.js.map +1 -0
- package/dist/protocol-audit.d.ts +17 -0
- package/dist/protocol-audit.d.ts.map +1 -0
- package/dist/protocol-audit.js +128 -0
- package/dist/protocol-audit.js.map +1 -0
- package/dist/reading-level.d.ts +37 -0
- package/dist/reading-level.d.ts.map +1 -0
- package/dist/reading-level.js +220 -0
- package/dist/reading-level.js.map +1 -0
- package/dist/redirects.d.ts +24 -0
- package/dist/redirects.d.ts.map +1 -0
- package/dist/redirects.js +119 -0
- package/dist/redirects.js.map +1 -0
- package/dist/report.d.ts +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +736 -1
- package/dist/report.js.map +1 -1
- package/dist/reporter-plugin.d.ts +32 -0
- package/dist/reporter-plugin.d.ts.map +1 -0
- package/dist/reporter-plugin.js +120 -0
- package/dist/reporter-plugin.js.map +1 -0
- package/dist/resource-hints.d.ts +23 -0
- package/dist/resource-hints.d.ts.map +1 -0
- package/dist/resource-hints.js +225 -0
- package/dist/resource-hints.js.map +1 -0
- package/dist/retire.d.ts +22 -0
- package/dist/retire.d.ts.map +1 -0
- package/dist/retire.js +140 -0
- package/dist/retire.js.map +1 -0
- package/dist/retry.d.ts +20 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +120 -0
- package/dist/retry.js.map +1 -0
- package/dist/robots-audit.d.ts +24 -0
- package/dist/robots-audit.d.ts.map +1 -0
- package/dist/robots-audit.js +206 -0
- package/dist/robots-audit.js.map +1 -0
- package/dist/rum.d.ts +35 -0
- package/dist/rum.d.ts.map +1 -0
- package/dist/rum.js +219 -0
- package/dist/rum.js.map +1 -0
- package/dist/schedule.d.ts +30 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +238 -0
- package/dist/schedule.js.map +1 -0
- package/dist/secret-scan.d.ts +24 -0
- package/dist/secret-scan.d.ts.map +1 -0
- package/dist/secret-scan.js +202 -0
- package/dist/secret-scan.js.map +1 -0
- package/dist/service-worker.d.ts +26 -0
- package/dist/service-worker.d.ts.map +1 -0
- package/dist/service-worker.js +179 -0
- package/dist/service-worker.js.map +1 -0
- package/dist/shard.d.ts +14 -0
- package/dist/shard.d.ts.map +1 -0
- package/dist/shard.js +72 -0
- package/dist/shard.js.map +1 -0
- package/dist/sitemap-flows.d.ts +13 -0
- package/dist/sitemap-flows.d.ts.map +1 -0
- package/dist/sitemap-flows.js +157 -0
- package/dist/sitemap-flows.js.map +1 -0
- package/dist/sitemap.d.ts +27 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +137 -0
- package/dist/sitemap.js.map +1 -0
- package/dist/slack-formatter.d.ts +35 -0
- package/dist/slack-formatter.d.ts.map +1 -0
- package/dist/slack-formatter.js +193 -0
- package/dist/slack-formatter.js.map +1 -0
- package/dist/sourcemap-scan.d.ts +24 -0
- package/dist/sourcemap-scan.d.ts.map +1 -0
- package/dist/sourcemap-scan.js +232 -0
- package/dist/sourcemap-scan.js.map +1 -0
- package/dist/sri-audit.d.ts +23 -0
- package/dist/sri-audit.d.ts.map +1 -0
- package/dist/sri-audit.js +180 -0
- package/dist/sri-audit.js.map +1 -0
- package/dist/storage-audit.d.ts +28 -0
- package/dist/storage-audit.d.ts.map +1 -0
- package/dist/storage-audit.js +263 -0
- package/dist/storage-audit.js.map +1 -0
- package/dist/storybook.d.ts +48 -0
- package/dist/storybook.d.ts.map +1 -0
- package/dist/storybook.js +191 -0
- package/dist/storybook.js.map +1 -0
- package/dist/structured-data.d.ts +25 -0
- package/dist/structured-data.d.ts.map +1 -0
- package/dist/structured-data.js +164 -0
- package/dist/structured-data.js.map +1 -0
- package/dist/svg-audit.d.ts +20 -0
- package/dist/svg-audit.d.ts.map +1 -0
- package/dist/svg-audit.js +213 -0
- package/dist/svg-audit.js.map +1 -0
- package/dist/table-audit.d.ts +18 -0
- package/dist/table-audit.d.ts.map +1 -0
- package/dist/table-audit.js +188 -0
- package/dist/table-audit.js.map +1 -0
- package/dist/teams-formatter.d.ts +66 -0
- package/dist/teams-formatter.d.ts.map +1 -0
- package/dist/teams-formatter.js +194 -0
- package/dist/teams-formatter.js.map +1 -0
- package/dist/third-party.d.ts +35 -0
- package/dist/third-party.d.ts.map +1 -0
- package/dist/third-party.js +175 -0
- package/dist/third-party.js.map +1 -0
- package/dist/tls.d.ts +33 -0
- package/dist/tls.d.ts.map +1 -0
- package/dist/tls.js +122 -0
- package/dist/tls.js.map +1 -0
- package/dist/touchtargets.d.ts +22 -0
- package/dist/touchtargets.d.ts.map +1 -0
- package/dist/touchtargets.js +80 -0
- package/dist/touchtargets.js.map +1 -0
- package/dist/tracker-sniff.d.ts +25 -0
- package/dist/tracker-sniff.d.ts.map +1 -0
- package/dist/tracker-sniff.js +355 -0
- package/dist/tracker-sniff.js.map +1 -0
- package/dist/types.d.ts +265 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/visual-mask.d.ts +33 -0
- package/dist/visual-mask.d.ts.map +1 -0
- package/dist/visual-mask.js +102 -0
- package/dist/visual-mask.js.map +1 -0
- package/dist/visual-ssim.d.ts +26 -0
- package/dist/visual-ssim.d.ts.map +1 -0
- package/dist/visual-ssim.js +153 -0
- package/dist/visual-ssim.js.map +1 -0
- package/dist/watch-mode.d.ts +10 -0
- package/dist/watch-mode.d.ts.map +1 -0
- package/dist/watch-mode.js +156 -0
- package/dist/watch-mode.js.map +1 -0
- package/dist/web-worker-audit.d.ts +27 -0
- package/dist/web-worker-audit.d.ts.map +1 -0
- package/dist/web-worker-audit.js +324 -0
- package/dist/web-worker-audit.js.map +1 -0
- package/dist/webfonts.d.ts +26 -0
- package/dist/webfonts.d.ts.map +1 -0
- package/dist/webfonts.js +244 -0
- package/dist/webfonts.js.map +1 -0
- package/dist/webhook-reporter.d.ts +20 -0
- package/dist/webhook-reporter.d.ts.map +1 -0
- package/dist/webhook-reporter.js +124 -0
- package/dist/webhook-reporter.js.map +1 -0
- package/dist/websocket.d.ts +39 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +233 -0
- package/dist/websocket.js.map +1 -0
- package/dist/worker-runtime.d.ts +129 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/dist/worker-runtime.js +414 -0
- package/dist/worker-runtime.js.map +1 -0
- package/dist/zindex-audit.d.ts +28 -0
- package/dist/zindex-audit.d.ts.map +1 -0
- package/dist/zindex-audit.js +291 -0
- package/dist/zindex-audit.js.map +1 -0
- package/package.json +10 -2
package/dist/report.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
3
4
|
const _pathRef = path;
|
|
4
5
|
export async function writeReport(result, outDir, reporters = ['html', 'json']) {
|
|
5
6
|
await fs.mkdir(outDir, { recursive: true });
|
|
@@ -16,8 +17,247 @@ export async function writeReport(result, outDir, reporters = ['html', 'json'])
|
|
|
16
17
|
if (reporters.includes('sarif')) {
|
|
17
18
|
await fs.writeFile(path.join(outDir, 'sarif.json'), JSON.stringify(renderSARIF(result), null, 2));
|
|
18
19
|
}
|
|
20
|
+
if (reporters.includes('allure')) {
|
|
21
|
+
await writeAllure(result, outDir);
|
|
22
|
+
}
|
|
23
|
+
if (reporters.includes('tap')) {
|
|
24
|
+
await fs.writeFile(path.join(outDir, 'report.tap'), renderTAP(result));
|
|
25
|
+
}
|
|
19
26
|
return htmlPath;
|
|
20
27
|
}
|
|
28
|
+
async function writeAllure(r, outDir) {
|
|
29
|
+
const allureDir = path.join(outDir, 'allure-results');
|
|
30
|
+
await fs.mkdir(allureDir, { recursive: true });
|
|
31
|
+
const startBase = new Date(r.startedAt).getTime();
|
|
32
|
+
const stopBase = new Date(r.finishedAt).getTime();
|
|
33
|
+
const safeUrl = r.url.replace(/[^a-zA-Z0-9]+/g, '_');
|
|
34
|
+
const hashId = (s) => crypto.createHash('md5').update(s).digest('hex');
|
|
35
|
+
const writeResult = async (obj) => {
|
|
36
|
+
const uuid = obj.uuid ?? crypto.randomUUID();
|
|
37
|
+
obj.uuid = uuid;
|
|
38
|
+
await fs.writeFile(path.join(allureDir, `${uuid}-result.json`), JSON.stringify(obj, null, 2));
|
|
39
|
+
};
|
|
40
|
+
for (const flow of r.flows) {
|
|
41
|
+
const flowStart = startBase;
|
|
42
|
+
let cursor = flowStart;
|
|
43
|
+
const steps = flow.steps.map((s) => {
|
|
44
|
+
const stepStart = cursor;
|
|
45
|
+
const stepStop = cursor + (s.durationMs || 0);
|
|
46
|
+
cursor = stepStop;
|
|
47
|
+
return {
|
|
48
|
+
name: describeStep(s.step),
|
|
49
|
+
status: s.passed ? 'passed' : 'failed',
|
|
50
|
+
stage: 'finished',
|
|
51
|
+
start: stepStart,
|
|
52
|
+
stop: stepStop,
|
|
53
|
+
...(s.error ? { statusDetails: { message: s.error } } : {}),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
const flowStop = cursor > flowStart ? cursor : stopBase;
|
|
57
|
+
const status = flow.passed ? 'passed' : flow.error ? 'broken' : 'failed';
|
|
58
|
+
await writeResult({
|
|
59
|
+
uuid: crypto.randomUUID(),
|
|
60
|
+
historyId: hashId(`flow:${flow.name}`),
|
|
61
|
+
name: flow.name,
|
|
62
|
+
fullName: `uxinspect.${safeUrl}.${flow.name}`,
|
|
63
|
+
status,
|
|
64
|
+
stage: 'finished',
|
|
65
|
+
start: flowStart,
|
|
66
|
+
stop: flowStop,
|
|
67
|
+
labels: [
|
|
68
|
+
{ name: 'suite', value: r.url },
|
|
69
|
+
{ name: 'severity', value: 'normal' },
|
|
70
|
+
{ name: 'feature', value: 'flow' },
|
|
71
|
+
],
|
|
72
|
+
steps,
|
|
73
|
+
...(flow.error ? { statusDetails: { message: flow.error } } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
for (const a of r.a11y ?? []) {
|
|
77
|
+
const critical = a.violations.filter((v) => v.impact === 'critical' || v.impact === 'serious');
|
|
78
|
+
const failed = critical.length > 0;
|
|
79
|
+
const name = `a11y: ${a.page}`;
|
|
80
|
+
await writeResult({
|
|
81
|
+
uuid: crypto.randomUUID(),
|
|
82
|
+
historyId: hashId(`a11y:${a.page}`),
|
|
83
|
+
name,
|
|
84
|
+
fullName: `uxinspect.${safeUrl}.a11y.${a.page}`,
|
|
85
|
+
status: failed ? 'failed' : 'passed',
|
|
86
|
+
stage: 'finished',
|
|
87
|
+
start: startBase,
|
|
88
|
+
stop: stopBase,
|
|
89
|
+
labels: [
|
|
90
|
+
{ name: 'suite', value: r.url },
|
|
91
|
+
{ name: 'severity', value: failed ? 'critical' : 'normal' },
|
|
92
|
+
{ name: 'feature', value: 'a11y' },
|
|
93
|
+
],
|
|
94
|
+
steps: a.violations.map((v) => ({
|
|
95
|
+
name: `${v.id}: ${v.help}`,
|
|
96
|
+
status: v.impact === 'critical' || v.impact === 'serious' ? 'failed' : 'passed',
|
|
97
|
+
stage: 'finished',
|
|
98
|
+
start: startBase,
|
|
99
|
+
stop: stopBase,
|
|
100
|
+
})),
|
|
101
|
+
...(failed
|
|
102
|
+
? { statusDetails: { message: `${critical.length} critical/serious a11y violations` } }
|
|
103
|
+
: {}),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function renderTAP(r) {
|
|
108
|
+
const points = [];
|
|
109
|
+
for (const f of r.flows) {
|
|
110
|
+
points.push({
|
|
111
|
+
ok: f.passed,
|
|
112
|
+
desc: `flow: ${f.name}`,
|
|
113
|
+
message: f.error,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
for (const a of r.a11y ?? []) {
|
|
117
|
+
const crit = a.violations.filter((v) => v.impact === 'critical' || v.impact === 'serious');
|
|
118
|
+
points.push({
|
|
119
|
+
ok: crit.length === 0,
|
|
120
|
+
desc: `a11y: ${a.page}`,
|
|
121
|
+
message: crit.length > 0 ? `${crit.length} critical/serious violations` : undefined,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
for (const v of r.visual ?? []) {
|
|
125
|
+
points.push({
|
|
126
|
+
ok: v.passed,
|
|
127
|
+
desc: `visual: ${v.page} [${v.viewport}]`,
|
|
128
|
+
message: v.passed ? undefined : `diff ${v.diffPixels}px (${(v.diffRatio * 100).toFixed(2)}%)`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
for (const p of r.perf ?? []) {
|
|
132
|
+
const score = p.scores.performance;
|
|
133
|
+
points.push({
|
|
134
|
+
ok: score >= 50,
|
|
135
|
+
desc: `perf: ${p.page} (score=${score})`,
|
|
136
|
+
message: score < 50 ? `performance score ${score} below threshold 50` : undefined,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
for (const s of r.seo ?? []) {
|
|
140
|
+
const issues = s.issues ?? [];
|
|
141
|
+
points.push({
|
|
142
|
+
ok: issues.length === 0,
|
|
143
|
+
desc: `seo: ${s.page ?? 'page'}`,
|
|
144
|
+
message: issues.length > 0 ? `${issues.length} seo issues` : undefined,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
for (const l of r.links ?? []) {
|
|
148
|
+
const broken = l.broken ?? [];
|
|
149
|
+
points.push({
|
|
150
|
+
ok: broken.length === 0,
|
|
151
|
+
desc: `links: ${l.page ?? 'page'}`,
|
|
152
|
+
message: broken.length > 0 ? `${broken.length} broken links` : undefined,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
for (const p of r.pwa ?? []) {
|
|
156
|
+
const passed = p.passed ?? true;
|
|
157
|
+
points.push({
|
|
158
|
+
ok: passed,
|
|
159
|
+
desc: `pwa: ${p.page ?? 'page'}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (r.security) {
|
|
163
|
+
const missing = r.security.missing ?? [];
|
|
164
|
+
points.push({
|
|
165
|
+
ok: missing.length === 0,
|
|
166
|
+
desc: 'security: headers',
|
|
167
|
+
message: missing.length > 0 ? `${missing.length} missing headers` : undefined,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
for (const v of r.retire ?? []) {
|
|
171
|
+
const vulns = v.vulnerabilities ?? [];
|
|
172
|
+
points.push({
|
|
173
|
+
ok: vulns.length === 0,
|
|
174
|
+
desc: `retire: ${v.url ?? 'script'}`,
|
|
175
|
+
message: vulns.length > 0 ? `${vulns.length} vulnerabilities` : undefined,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
for (const f of r.apiFlows ?? []) {
|
|
179
|
+
points.push({ ok: f.passed, desc: `api: ${f.name}`, message: f.error });
|
|
180
|
+
}
|
|
181
|
+
for (const b of r.budget ?? []) {
|
|
182
|
+
const msg = b.message
|
|
183
|
+
?? b.metric
|
|
184
|
+
?? 'budget violation';
|
|
185
|
+
points.push({ ok: false, desc: `budget: ${msg}`, message: msg });
|
|
186
|
+
}
|
|
187
|
+
for (const d of r.deadClicks ?? []) {
|
|
188
|
+
const found = d.deadClicks ?? [];
|
|
189
|
+
points.push({
|
|
190
|
+
ok: found.length === 0,
|
|
191
|
+
desc: `deadClicks: ${d.page ?? 'page'}`,
|
|
192
|
+
message: found.length > 0 ? `${found.length} dead clicks` : undefined,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
for (const t of r.touchTargets ?? []) {
|
|
196
|
+
const small = t.tooSmall ?? [];
|
|
197
|
+
points.push({
|
|
198
|
+
ok: small.length === 0,
|
|
199
|
+
desc: `touchTargets: ${t.page ?? 'page'}`,
|
|
200
|
+
message: small.length > 0 ? `${small.length} small targets` : undefined,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
for (const k of r.keyboard ?? []) {
|
|
204
|
+
const issues = k.issues ?? [];
|
|
205
|
+
points.push({
|
|
206
|
+
ok: issues.length === 0,
|
|
207
|
+
desc: `keyboard: ${k.page ?? 'page'}`,
|
|
208
|
+
message: issues.length > 0 ? `${issues.length} keyboard issues` : undefined,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
for (const ce of r.consoleErrors ?? []) {
|
|
212
|
+
const errs = ce.errors ?? [];
|
|
213
|
+
points.push({
|
|
214
|
+
ok: errs.length === 0,
|
|
215
|
+
desc: `consoleErrors: ${ce.page ?? 'page'}`,
|
|
216
|
+
message: errs.length > 0 ? `${errs.length} console errors` : undefined,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
for (const mc of r.mixedContent ?? []) {
|
|
220
|
+
const items = mc.items ?? [];
|
|
221
|
+
points.push({
|
|
222
|
+
ok: items.length === 0,
|
|
223
|
+
desc: `mixedContent: ${mc.page ?? 'page'}`,
|
|
224
|
+
message: items.length > 0 ? `${items.length} mixed content items` : undefined,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const total = points.length;
|
|
228
|
+
const lines = ['TAP version 14', `1..${total}`];
|
|
229
|
+
points.forEach((p, i) => {
|
|
230
|
+
const n = i + 1;
|
|
231
|
+
const prefix = p.ok ? 'ok' : 'not ok';
|
|
232
|
+
lines.push(`${prefix} ${n} - ${escapeTapDesc(p.desc)}`);
|
|
233
|
+
if (!p.ok && p.message) {
|
|
234
|
+
lines.push(' ---');
|
|
235
|
+
lines.push(` message: '${escapeTapMsg(p.message)}'`);
|
|
236
|
+
lines.push(' ---');
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
return lines.join('\n') + '\n';
|
|
240
|
+
}
|
|
241
|
+
function escapeTapDesc(s) {
|
|
242
|
+
return s.replace(/[\r\n]+/g, ' ').replace(/#/g, '\\#');
|
|
243
|
+
}
|
|
244
|
+
function escapeTapMsg(s) {
|
|
245
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/[\r\n]+/g, ' ');
|
|
246
|
+
}
|
|
247
|
+
function describeStep(step) {
|
|
248
|
+
if (!step || typeof step !== 'object')
|
|
249
|
+
return 'step';
|
|
250
|
+
const keys = Object.keys(step);
|
|
251
|
+
if (keys.length === 0)
|
|
252
|
+
return 'step';
|
|
253
|
+
const key = keys[0];
|
|
254
|
+
const val = step[key];
|
|
255
|
+
if (typeof val === 'string')
|
|
256
|
+
return `${key}: ${val}`;
|
|
257
|
+
if (typeof val === 'number' || typeof val === 'boolean')
|
|
258
|
+
return `${key}: ${String(val)}`;
|
|
259
|
+
return key;
|
|
260
|
+
}
|
|
21
261
|
function renderJUnit(r) {
|
|
22
262
|
const tests = r.flows.length;
|
|
23
263
|
const failures = r.flows.filter((f) => !f.passed).length;
|
|
@@ -122,6 +362,29 @@ function renderHTML(r) {
|
|
|
122
362
|
.visual-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin-top: 12px; }
|
|
123
363
|
ul { margin: 8px 0; padding-left: 20px; font-size: 13px; }
|
|
124
364
|
code { background: #F3F4F6; padding: 1px 6px; border-radius: 3px; font-size: 12px; }
|
|
365
|
+
details { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; margin-bottom: 12px; }
|
|
366
|
+
details[open] { padding-bottom: 16px; }
|
|
367
|
+
summary { cursor: pointer; font-weight: 600; display: flex; justify-content: space-between; align-items: center; gap: 12px; list-style: none; }
|
|
368
|
+
summary::-webkit-details-marker { display: none; }
|
|
369
|
+
summary::before { content: '\\25B8'; display: inline-block; transition: transform 0.15s ease; color: var(--muted); font-size: 10px; margin-right: 6px; }
|
|
370
|
+
details[open] > summary::before { transform: rotate(90deg); }
|
|
371
|
+
.summary-meta { display: flex; align-items: center; gap: 8px; font-weight: 400; color: var(--muted); font-size: 13px; }
|
|
372
|
+
table { width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 8px; }
|
|
373
|
+
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
374
|
+
th { color: var(--muted); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; background: #F9FAFB; }
|
|
375
|
+
tbody tr:last-child td { border-bottom: none; }
|
|
376
|
+
.pill-warn { background: #FFFBEB; color: #92400E; }
|
|
377
|
+
.pill-error { background: #FEF2F2; color: #991B1B; }
|
|
378
|
+
.pill-info { background: var(--blue-bg); color: var(--blue); }
|
|
379
|
+
.pill-high { background: #FEF2F2; color: #991B1B; }
|
|
380
|
+
.pill-medium { background: #FFFBEB; color: #92400E; }
|
|
381
|
+
.pill-low { background: var(--blue-bg); color: var(--blue); }
|
|
382
|
+
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: 13px; margin-top: 8px; }
|
|
383
|
+
.kv dt { color: var(--muted); }
|
|
384
|
+
.kv dd { margin: 0; color: var(--text); word-break: break-word; }
|
|
385
|
+
.empty { color: var(--muted); font-style: italic; font-size: 13px; }
|
|
386
|
+
.mono { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }
|
|
387
|
+
.trunc { max-width: 420px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle; }
|
|
125
388
|
</style>
|
|
126
389
|
</head>
|
|
127
390
|
<body>
|
|
@@ -145,10 +408,64 @@ function renderHTML(r) {
|
|
|
145
408
|
${r.links?.length ? `<h2>Broken links</h2>${r.links.map(renderLinks).join('')}` : ''}
|
|
146
409
|
${r.pwa?.length ? `<h2>PWA</h2>${r.pwa.map(renderPwa).join('')}` : ''}
|
|
147
410
|
${r.security ? `<h2>Security headers</h2>${renderSecurity(r.security)}` : ''}
|
|
411
|
+
${r.retire?.length ? `<h2>Vulnerable libraries</h2>${r.retire.map(renderRetire).join('')}` : ''}
|
|
412
|
+
${r.deadClicks?.length ? `<h2>Dead clicks</h2>${r.deadClicks.map(renderDeadClicks).join('')}` : ''}
|
|
413
|
+
${r.touchTargets?.length ? `<h2>Touch targets</h2>${r.touchTargets.map(renderTouchTargets).join('')}` : ''}
|
|
414
|
+
${r.keyboard?.length ? `<h2>Keyboard</h2>${r.keyboard.map(renderKeyboard).join('')}` : ''}
|
|
415
|
+
${r.longTasks?.length ? `<h2>Long tasks & INP</h2>${r.longTasks.map(renderLongTasks).join('')}` : ''}
|
|
416
|
+
${r.clsTimeline?.length ? `<h2>Layout shifts</h2>${r.clsTimeline.map(renderClsTimeline).join('')}` : ''}
|
|
417
|
+
${r.forms?.length ? `<h2>Forms</h2>${r.forms.map(renderForms).join('')}` : ''}
|
|
418
|
+
${r.structuredData?.length ? `<h2>Structured data</h2>${r.structuredData.map(renderStructuredData).join('')}` : ''}
|
|
419
|
+
${r.passiveSecurity?.length ? `<h2>Passive security</h2>${r.passiveSecurity.map(renderPassiveSecurity).join('')}` : ''}
|
|
420
|
+
${r.consoleErrors?.length ? `<h2>Console errors</h2>${r.consoleErrors.map(renderConsoleErrors).join('')}` : ''}
|
|
421
|
+
${r.sitemap ? `<h2>Sitemap</h2>${renderSitemap(r.sitemap)}` : ''}
|
|
422
|
+
${r.redirects ? `<h2>Redirects</h2>${renderRedirects(r.redirects)}` : ''}
|
|
423
|
+
${r.exposedPaths ? `<h2>Exposed paths</h2>${renderExposedPaths(r.exposedPaths)}` : ''}
|
|
424
|
+
${r.tls ? `<h2>TLS</h2>${renderTls(r.tls)}` : ''}
|
|
425
|
+
${r.crawl ? `<h2>Crawl</h2>${renderCrawl(r.crawl)}` : ''}
|
|
426
|
+
${r.contentQuality ? `<h2>Content quality</h2>${renderContentQuality(r.contentQuality)}` : ''}
|
|
427
|
+
${r.resourceHints?.length ? `<h2>Resource hints</h2>${r.resourceHints.map(renderResourceHints).join('')}` : ''}
|
|
428
|
+
${r.mixedContent?.length ? `<h2>Mixed content</h2>${r.mixedContent.map(renderMixedContent).join('')}` : ''}
|
|
429
|
+
${r.compression ? `<h2>Compression</h2>${renderCompression(r.compression)}` : ''}
|
|
430
|
+
${r.cacheHeaders?.length ? `<h2>Cache headers</h2>${r.cacheHeaders.map(renderCacheHeaders).join('')}` : ''}
|
|
431
|
+
${r.cookieBanner?.length ? `<h2>Cookie banner</h2>${r.cookieBanner.map(renderCookieBanner).join('')}` : ''}
|
|
432
|
+
${r.thirdParty?.length ? `<h2>Third-party resources</h2>${r.thirdParty.map(renderThirdParty).join('')}` : ''}
|
|
433
|
+
${r.bundleSize?.length ? `<h2>Bundle size</h2>${r.bundleSize.map(renderBundleSize).join('')}` : ''}
|
|
434
|
+
${r.openGraph?.length ? `<h2>Open Graph</h2>${r.openGraph.map(renderOpenGraph).join('')}` : ''}
|
|
435
|
+
${r.robotsAudit ? `<h2>robots.txt</h2>${renderRobotsAudit(r.robotsAudit)}` : ''}
|
|
436
|
+
${r.imageAudit?.length ? `<h2>Images</h2>${r.imageAudit.map(renderImageAudit).join('')}` : ''}
|
|
437
|
+
${r.webfonts?.length ? `<h2>Webfonts</h2>${r.webfonts.map(renderWebfonts).join('')}` : ''}
|
|
438
|
+
${r.motionPrefs?.length ? `<h2>Motion preferences</h2>${r.motionPrefs.map(renderMotionPrefs).join('')}` : ''}
|
|
148
439
|
${r.explore ? `<h2>Exploration</h2>${renderExplore(r.explore)}` : ''}
|
|
440
|
+
${renderUnknownSections(r)}
|
|
149
441
|
</body>
|
|
150
442
|
</html>`;
|
|
151
443
|
}
|
|
444
|
+
const KNOWN_RESULT_KEYS = new Set([
|
|
445
|
+
'url', 'startedAt', 'finishedAt', 'durationMs', 'flows', 'budget',
|
|
446
|
+
'a11y', 'perf', 'visual', 'seo', 'links', 'pwa', 'security', 'retire',
|
|
447
|
+
'deadClicks', 'touchTargets', 'keyboard', 'longTasks', 'clsTimeline', 'forms',
|
|
448
|
+
'structuredData', 'passiveSecurity', 'consoleErrors', 'sitemap', 'redirects',
|
|
449
|
+
'exposedPaths', 'tls', 'crawl', 'contentQuality', 'resourceHints', 'mixedContent',
|
|
450
|
+
'compression', 'cacheHeaders', 'cookieBanner', 'thirdParty', 'bundleSize',
|
|
451
|
+
'openGraph', 'robotsAudit', 'imageAudit', 'webfonts', 'motionPrefs', 'explore',
|
|
452
|
+
'apiFlows', 'passed',
|
|
453
|
+
]);
|
|
454
|
+
function renderUnknownSections(r) {
|
|
455
|
+
const out = [];
|
|
456
|
+
for (const key of Object.keys(r)) {
|
|
457
|
+
if (KNOWN_RESULT_KEYS.has(key))
|
|
458
|
+
continue;
|
|
459
|
+
const val = r[key];
|
|
460
|
+
if (val === undefined || val === null)
|
|
461
|
+
continue;
|
|
462
|
+
if (Array.isArray(val) && val.length === 0)
|
|
463
|
+
continue;
|
|
464
|
+
const title = escape(key.replace(/([A-Z])/g, ' $1').replace(/^./, (c) => c.toUpperCase()));
|
|
465
|
+
out.push(`<h2>${title}</h2><div class="section"><pre>${escape(JSON.stringify(val, null, 2))}</pre></div>`);
|
|
466
|
+
}
|
|
467
|
+
return out.join('');
|
|
468
|
+
}
|
|
152
469
|
function renderFlow(f) {
|
|
153
470
|
return `<div class="section">
|
|
154
471
|
<div class="row"><strong>${escape(f.name)}</strong> <span class="${f.passed ? 'pass' : 'fail'}">${f.passed ? 'PASS' : 'FAIL'}</span></div>
|
|
@@ -243,6 +560,424 @@ function renderExplore(e) {
|
|
|
243
560
|
</div>`;
|
|
244
561
|
}
|
|
245
562
|
function escape(s) {
|
|
246
|
-
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
563
|
+
return (s ?? '').toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
564
|
+
}
|
|
565
|
+
function statusBadge(passed, label) {
|
|
566
|
+
return `<span class="${passed ? 'pass' : 'fail'}">${label ?? (passed ? 'PASS' : 'FAIL')}</span>`;
|
|
567
|
+
}
|
|
568
|
+
function summaryRow(title, passed, meta) {
|
|
569
|
+
return `<summary><span>${escape(title)}</span><span class="summary-meta">${meta} ${statusBadge(passed)}</span></summary>`;
|
|
570
|
+
}
|
|
571
|
+
function plural(n, one, many) {
|
|
572
|
+
return `${n} ${n === 1 ? one : many ?? one + 's'}`;
|
|
573
|
+
}
|
|
574
|
+
function bytes(n) {
|
|
575
|
+
if (n === undefined || n === null || !Number.isFinite(n))
|
|
576
|
+
return '—';
|
|
577
|
+
if (n < 1024)
|
|
578
|
+
return `${n} B`;
|
|
579
|
+
if (n < 1024 * 1024)
|
|
580
|
+
return `${(n / 1024).toFixed(1)} KB`;
|
|
581
|
+
return `${(n / 1024 / 1024).toFixed(2)} MB`;
|
|
582
|
+
}
|
|
583
|
+
function renderRetire(r) {
|
|
584
|
+
const pageUrl = r.findings[0]?.url ?? '';
|
|
585
|
+
const vulnCount = r.findings.reduce((n, f) => n + f.vulnerabilities.length, 0);
|
|
586
|
+
const meta = `${plural(r.findings.length, 'library', 'libraries')} · ${plural(vulnCount, 'vulnerability', 'vulnerabilities')} · ${r.librariesScanned} scanned`;
|
|
587
|
+
const body = r.findings.length === 0
|
|
588
|
+
? `<div class="empty">No vulnerable libraries detected.</div>`
|
|
589
|
+
: `<table><thead><tr><th>Library</th><th>Version</th><th>Severity</th><th>Summary</th><th>CVE</th></tr></thead><tbody>${r.findings.map((f) => f.vulnerabilities.map((v) => `
|
|
590
|
+
<tr>
|
|
591
|
+
<td><strong>${escape(f.library)}</strong><div class="mono trunc">${escape(f.url)}</div></td>
|
|
592
|
+
<td><code>${escape(f.version)}</code></td>
|
|
593
|
+
<td><span class="pill pill-${v.severity === 'critical' ? 'critical' : v.severity === 'high' ? 'serious' : v.severity === 'medium' ? 'moderate' : 'minor'}">${escape(v.severity)}</span></td>
|
|
594
|
+
<td>${escape(v.summary)}</td>
|
|
595
|
+
<td class="mono">${(v.identifiers?.CVE ?? []).map((c) => escape(c)).join(', ') || '—'}</td>
|
|
596
|
+
</tr>`).join('')).join('')}</tbody></table>`;
|
|
597
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(pageUrl || 'Retire.js', r.passed, meta)}${body}</details>`;
|
|
598
|
+
}
|
|
599
|
+
function renderDeadClicks(r) {
|
|
600
|
+
const meta = `${plural(r.clicked, 'click')} · ${plural(r.findings.length, 'dead click')}`;
|
|
601
|
+
const body = r.findings.length === 0
|
|
602
|
+
? `<div class="empty">All interactive elements responded.</div>`
|
|
603
|
+
: `<table><thead><tr><th>Selector</th><th>Reason</th><th>Feedback</th><th>HTML</th></tr></thead><tbody>${r.findings.map((f) => `
|
|
604
|
+
<tr>
|
|
605
|
+
<td class="mono trunc">${escape(f.selector)}</td>
|
|
606
|
+
<td><span class="pill pill-warn">${escape(f.reason)}</span></td>
|
|
607
|
+
<td>${f.feedbackMs !== undefined ? `${f.feedbackMs}ms` : '—'}</td>
|
|
608
|
+
<td class="mono trunc">${escape(f.html)}</td>
|
|
609
|
+
</tr>`).join('')}</tbody></table>`;
|
|
610
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
611
|
+
}
|
|
612
|
+
function renderTouchTargets(r) {
|
|
613
|
+
const meta = `${r.scanned} scanned · ${plural(r.tooSmall.length, 'too small')} · ${plural(r.overlapping.length, 'overlap')}`;
|
|
614
|
+
const rows = (arr, kind) => arr.map((f) => `
|
|
615
|
+
<tr>
|
|
616
|
+
<td class="mono trunc">${escape(f.selector)}</td>
|
|
617
|
+
<td><span class="pill pill-warn">${kind}</span></td>
|
|
618
|
+
<td>${Math.round(f.width)}×${Math.round(f.height)}px</td>
|
|
619
|
+
<td class="mono trunc">${escape(f.overlapsWith ?? '')}</td>
|
|
620
|
+
</tr>`).join('');
|
|
621
|
+
const body = r.tooSmall.length === 0 && r.overlapping.length === 0
|
|
622
|
+
? `<div class="empty">All touch targets meet minimum size.</div>`
|
|
623
|
+
: `<table><thead><tr><th>Selector</th><th>Issue</th><th>Size</th><th>Overlaps with</th></tr></thead><tbody>${rows(r.tooSmall, 'too-small')}${rows(r.overlapping, 'overlapping')}</tbody></table>`;
|
|
624
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
625
|
+
}
|
|
626
|
+
function renderKeyboard(r) {
|
|
627
|
+
const meta = `${r.focusableCount} focusable · ${r.tabsTaken} tabs · ${plural(r.issues.length, 'issue')}`;
|
|
628
|
+
const body = r.issues.length === 0
|
|
629
|
+
? `<div class="empty">Keyboard navigation is healthy.</div>`
|
|
630
|
+
: `<table><thead><tr><th>Level</th><th>Type</th><th>Selector</th><th>Message</th></tr></thead><tbody>${r.issues.map((i) => `
|
|
631
|
+
<tr>
|
|
632
|
+
<td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
|
|
633
|
+
<td><code>${escape(i.type)}</code></td>
|
|
634
|
+
<td class="mono trunc">${escape(i.selector ?? '—')}</td>
|
|
635
|
+
<td>${escape(i.message)}</td>
|
|
636
|
+
</tr>`).join('')}</tbody></table>`;
|
|
637
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
638
|
+
}
|
|
639
|
+
function renderLongTasks(r) {
|
|
640
|
+
const meta = `TBT ${Math.round(r.totalBlockingMs)}ms · INP ${r.inpMs !== undefined ? `${Math.round(r.inpMs)}ms` : '—'} · ${plural(r.longTasks.length, 'long task')}`;
|
|
641
|
+
const body = `
|
|
642
|
+
<dl class="kv">
|
|
643
|
+
<dt>Total blocking</dt><dd>${Math.round(r.totalBlockingMs)}ms</dd>
|
|
644
|
+
<dt>INP</dt><dd>${r.inpMs !== undefined ? `${Math.round(r.inpMs)}ms${r.inpTarget ? ` on <code>${escape(r.inpTarget)}</code>` : ''}` : '—'}</dd>
|
|
645
|
+
<dt>Long tasks</dt><dd>${r.longTasks.length}</dd>
|
|
646
|
+
<dt>LoAF frames</dt><dd>${r.longAnimationFrames.length}</dd>
|
|
647
|
+
</dl>
|
|
648
|
+
${r.longTasks.length ? `<table><thead><tr><th>Start</th><th>Duration</th><th>Attribution</th></tr></thead><tbody>${r.longTasks.slice(0, 20).map((t) => `
|
|
649
|
+
<tr>
|
|
650
|
+
<td>${Math.round(t.startTime)}ms</td>
|
|
651
|
+
<td>${Math.round(t.duration)}ms</td>
|
|
652
|
+
<td class="mono trunc">${(t.attribution ?? []).map((a) => escape([a.containerType, a.containerName, a.containerSrc].filter(Boolean).join(' '))).join(', ') || '—'}</td>
|
|
653
|
+
</tr>`).join('')}</tbody></table>` : ''}`;
|
|
654
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
655
|
+
}
|
|
656
|
+
function renderClsTimeline(r) {
|
|
657
|
+
const meta = `CLS ${r.cls.toFixed(3)} · ${plural(r.timeline.length, 'shift')}`;
|
|
658
|
+
const body = r.worstElements.length === 0
|
|
659
|
+
? `<div class="empty">No layout shifts recorded.</div>`
|
|
660
|
+
: `<table><thead><tr><th>Selector</th><th>Total shift</th><th>Occurrences</th></tr></thead><tbody>${r.worstElements.map((e) => `
|
|
661
|
+
<tr>
|
|
662
|
+
<td class="mono trunc">${escape(e.selector)}</td>
|
|
663
|
+
<td>${e.totalShift.toFixed(4)}</td>
|
|
664
|
+
<td>${e.occurrences}</td>
|
|
665
|
+
</tr>`).join('')}</tbody></table>`;
|
|
666
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
667
|
+
}
|
|
668
|
+
function renderForms(r) {
|
|
669
|
+
const meta = `${plural(r.forms.length, 'form')} · ${plural(r.totalIssues, 'issue')}`;
|
|
670
|
+
const body = r.forms.length === 0
|
|
671
|
+
? `<div class="empty">No forms on this page.</div>`
|
|
672
|
+
: r.forms.map((f) => `
|
|
673
|
+
<div style="margin-top:12px">
|
|
674
|
+
<div><strong class="mono">${escape(f.selector)}</strong> <span class="label">${escape(f.method)} · ${f.fields} fields</span></div>
|
|
675
|
+
${f.issues.length ? `<table><thead><tr><th>Level</th><th>Type</th><th>Selector</th><th>Message</th></tr></thead><tbody>${f.issues.map((i) => `
|
|
676
|
+
<tr>
|
|
677
|
+
<td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
|
|
678
|
+
<td><code>${escape(i.type)}</code></td>
|
|
679
|
+
<td class="mono trunc">${escape(i.selector)}</td>
|
|
680
|
+
<td>${escape(i.message)}</td>
|
|
681
|
+
</tr>`).join('')}</tbody></table>` : '<div class="empty">No issues.</div>'}
|
|
682
|
+
</div>`).join('');
|
|
683
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
684
|
+
}
|
|
685
|
+
function renderStructuredData(r) {
|
|
686
|
+
const meta = `${plural(r.items.length, 'item')} · ${plural(r.hreflangTags.length, 'hreflang')} · ${plural(r.issues.length, 'issue')}`;
|
|
687
|
+
const items = r.items.length
|
|
688
|
+
? `<table><thead><tr><th>Format</th><th>Type</th></tr></thead><tbody>${r.items.map((i) => `<tr><td><code>${escape(i.format)}</code></td><td>${escape(i.type)}</td></tr>`).join('')}</tbody></table>` : '';
|
|
689
|
+
const issues = r.issues.length
|
|
690
|
+
? `<table><thead><tr><th>Level</th><th>Type</th><th>Message</th></tr></thead><tbody>${r.issues.map((i) => `
|
|
691
|
+
<tr>
|
|
692
|
+
<td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
|
|
693
|
+
<td><code>${escape(i.type)}</code></td>
|
|
694
|
+
<td>${escape(i.message)}${i.snippet ? `<div class="mono trunc">${escape(i.snippet)}</div>` : ''}</td>
|
|
695
|
+
</tr>`).join('')}</tbody></table>` : '';
|
|
696
|
+
const body = r.items.length === 0 && r.issues.length === 0
|
|
697
|
+
? `<div class="empty">No structured data found.</div>`
|
|
698
|
+
: items + issues;
|
|
699
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
700
|
+
}
|
|
701
|
+
function renderPassiveSecurity(r) {
|
|
702
|
+
const meta = `${plural(r.issues.length, 'issue')} · ${r.scannedScripts} scripts · ${r.scannedLinks} links · ${r.cookiesChecked} cookies`;
|
|
703
|
+
const body = r.issues.length === 0
|
|
704
|
+
? `<div class="empty">No passive security smells.</div>`
|
|
705
|
+
: `<table><thead><tr><th>Level</th><th>Type</th><th>Target</th><th>Message</th></tr></thead><tbody>${r.issues.map((i) => `
|
|
706
|
+
<tr>
|
|
707
|
+
<td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
|
|
708
|
+
<td><code>${escape(i.type)}</code></td>
|
|
709
|
+
<td class="mono trunc">${escape(i.selector ?? i.url ?? i.cookieName ?? '—')}</td>
|
|
710
|
+
<td>${escape(i.message)}</td>
|
|
711
|
+
</tr>`).join('')}</tbody></table>`;
|
|
712
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
713
|
+
}
|
|
714
|
+
function renderConsoleErrors(r) {
|
|
715
|
+
const meta = `${r.errorCount} errors · ${r.warningCount} warnings · ${plural(r.issues.length, 'unique issue')}`;
|
|
716
|
+
const body = r.issues.length === 0
|
|
717
|
+
? `<div class="empty">No console messages captured.</div>`
|
|
718
|
+
: `<table><thead><tr><th>Type</th><th>Count</th><th>Message</th><th>Source</th></tr></thead><tbody>${r.issues.map((i) => `
|
|
719
|
+
<tr>
|
|
720
|
+
<td><span class="pill pill-${i.type === 'warning' ? 'warn' : 'error'}">${escape(i.type)}</span></td>
|
|
721
|
+
<td>${i.occurrences}</td>
|
|
722
|
+
<td class="mono trunc">${escape(i.message)}</td>
|
|
723
|
+
<td class="mono trunc">${escape([i.url, i.lineNumber, i.columnNumber].filter((v) => v !== undefined).join(':'))}</td>
|
|
724
|
+
</tr>`).join('')}</tbody></table>`;
|
|
725
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
726
|
+
}
|
|
727
|
+
function renderSitemap(r) {
|
|
728
|
+
const meta = `${r.sitemapFound ? 'found' : 'missing'} · ${r.urlsInSitemap} urls · ${plural(r.brokenUrls.length, 'broken')} · ${plural(r.issues.length, 'issue')}`;
|
|
729
|
+
const body = `
|
|
730
|
+
<dl class="kv">
|
|
731
|
+
<dt>Sitemap URL</dt><dd>${r.sitemapUrl ? `<code>${escape(r.sitemapUrl)}</code>` : '—'}</dd>
|
|
732
|
+
<dt>robots.txt</dt><dd>${r.robotsTxtFound ? 'found' : 'missing'}</dd>
|
|
733
|
+
<dt>URLs checked</dt><dd>${r.urlsChecked}</dd>
|
|
734
|
+
<dt>Blocked critical</dt><dd>${r.robotsBlockedCritical.length ? r.robotsBlockedCritical.map((u) => `<code>${escape(u)}</code>`).join(' ') : '—'}</dd>
|
|
735
|
+
</dl>
|
|
736
|
+
${r.brokenUrls.length ? `<table><thead><tr><th>Status</th><th>URL</th></tr></thead><tbody>${r.brokenUrls.map((b) => `<tr><td><code>${b.status}</code></td><td class="mono trunc">${escape(b.url)}</td></tr>`).join('')}</tbody></table>` : ''}
|
|
737
|
+
${r.issues.length ? `<ul>${r.issues.map((i) => `<li><span class="pill pill-${i.level === 'error' ? 'error' : i.level === 'warn' ? 'warn' : 'info'}">${escape(i.level)}</span> ${escape(i.message)}${i.url ? ` — <code>${escape(i.url)}</code>` : ''}</li>`).join('')}</ul>` : ''}`;
|
|
738
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.baseUrl, r.passed, meta)}${body}</details>`;
|
|
739
|
+
}
|
|
740
|
+
function renderRedirects(r) {
|
|
741
|
+
const meta = `${r.hopCount} hops${r.loop ? ' · loop' : ''}${r.mixedScheme ? ' · mixed scheme' : ''}${r.metaRefresh ? ' · meta-refresh' : ''}`;
|
|
742
|
+
const body = `
|
|
743
|
+
<dl class="kv">
|
|
744
|
+
<dt>Start</dt><dd class="mono trunc">${escape(r.start)}</dd>
|
|
745
|
+
<dt>Final</dt><dd class="mono trunc">${escape(r.final)}</dd>
|
|
746
|
+
</dl>
|
|
747
|
+
${r.hops.length ? `<table><thead><tr><th>#</th><th>Status</th><th>Method</th><th>URL</th><th>Location</th><th>Time</th></tr></thead><tbody>${r.hops.map((h, idx) => `
|
|
748
|
+
<tr>
|
|
749
|
+
<td>${idx + 1}</td>
|
|
750
|
+
<td><code>${h.status}</code></td>
|
|
751
|
+
<td>${escape(h.method)}</td>
|
|
752
|
+
<td class="mono trunc">${escape(h.url)}</td>
|
|
753
|
+
<td class="mono trunc">${escape(h.location ?? '—')}</td>
|
|
754
|
+
<td>${Math.round(h.durationMs)}ms</td>
|
|
755
|
+
</tr>`).join('')}</tbody></table>` : ''}`;
|
|
756
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.start, r.passed, meta)}${body}</details>`;
|
|
757
|
+
}
|
|
758
|
+
function renderExposedPaths(r) {
|
|
759
|
+
const meta = `${r.scanned} scanned · ${plural(r.findings.length, 'finding')}${r.securityTxtPresent ? ' · security.txt' : ''}`;
|
|
760
|
+
const body = r.findings.length === 0
|
|
761
|
+
? `<div class="empty">No exposed paths detected.</div>`
|
|
762
|
+
: `<table><thead><tr><th>Severity</th><th>Path</th><th>Status</th><th>Snippet</th></tr></thead><tbody>${r.findings.map((f) => `
|
|
763
|
+
<tr>
|
|
764
|
+
<td><span class="pill pill-${f.severity}">${escape(f.severity)}</span></td>
|
|
765
|
+
<td class="mono trunc">${escape(f.path)}</td>
|
|
766
|
+
<td><code>${f.status}</code></td>
|
|
767
|
+
<td class="mono trunc">${escape(f.contentSnippet ?? '')}</td>
|
|
768
|
+
</tr>`).join('')}</tbody></table>`;
|
|
769
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.baseUrl, r.passed, meta)}${body}</details>`;
|
|
770
|
+
}
|
|
771
|
+
function renderTls(r) {
|
|
772
|
+
const meta = `${r.protocol ?? '—'}${r.cert ? ` · expires in ${r.cert.daysUntilExpiry}d` : ''} · ${plural(r.issues.length, 'issue')}`;
|
|
773
|
+
const body = `
|
|
774
|
+
<dl class="kv">
|
|
775
|
+
<dt>Host</dt><dd>${escape(r.host)}:${r.port}</dd>
|
|
776
|
+
<dt>Protocol</dt><dd>${escape(r.protocol ?? '—')}</dd>
|
|
777
|
+
<dt>Cipher</dt><dd>${r.cipher ? `${escape(r.cipher.name)} (${escape(r.cipher.version)})` : '—'}</dd>
|
|
778
|
+
${r.cert ? `
|
|
779
|
+
<dt>Subject</dt><dd class="mono trunc">${escape(r.cert.subject)}</dd>
|
|
780
|
+
<dt>Issuer</dt><dd class="mono trunc">${escape(r.cert.issuer)}</dd>
|
|
781
|
+
<dt>Valid</dt><dd>${escape(r.cert.validFrom)} → ${escape(r.cert.validTo)} (${r.cert.daysUntilExpiry}d)</dd>
|
|
782
|
+
<dt>Key length</dt><dd>${r.cert.keyLength ?? '—'}${r.cert.selfSigned ? ' · self-signed' : ''}</dd>
|
|
783
|
+
` : ''}
|
|
784
|
+
<dt>Chain</dt><dd>${r.chainComplete ? 'complete' : 'incomplete'}</dd>
|
|
785
|
+
<dt>HSTS</dt><dd class="mono trunc">${escape(r.hstsHeader ?? '—')}${r.hstsPreloadEligible ? ' · preload-eligible' : ''}</dd>
|
|
786
|
+
</dl>
|
|
787
|
+
${r.issues.length ? `<ul>${r.issues.map((i) => `<li><span class="pill pill-${i.level === 'error' ? 'error' : i.level === 'warn' ? 'warn' : 'info'}">${escape(i.level)}</span> ${escape(i.message)}</li>`).join('')}</ul>` : ''}`;
|
|
788
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.host, r.passed, meta)}${body}</details>`;
|
|
789
|
+
}
|
|
790
|
+
function renderCrawl(r) {
|
|
791
|
+
const errors = r.pages.filter((p) => p.error || (p.status >= 400 && p.status !== 0)).length;
|
|
792
|
+
const passed = errors === 0;
|
|
793
|
+
const meta = `${r.pagesVisited} pages · ${plural(errors, 'error')} · ${(r.durationMs / 1000).toFixed(1)}s`;
|
|
794
|
+
const body = `
|
|
795
|
+
<dl class="kv">
|
|
796
|
+
<dt>Seed</dt><dd class="mono trunc">${escape(r.seed)}</dd>
|
|
797
|
+
<dt>Pages visited</dt><dd>${r.pagesVisited}</dd>
|
|
798
|
+
<dt>Duration</dt><dd>${(r.durationMs / 1000).toFixed(1)}s</dd>
|
|
799
|
+
</dl>
|
|
800
|
+
<table><thead><tr><th>Depth</th><th>Status</th><th>URL</th><th>Title</th><th>Load</th></tr></thead><tbody>${r.pages.slice(0, 100).map((p) => `
|
|
801
|
+
<tr>
|
|
802
|
+
<td>${p.depth}</td>
|
|
803
|
+
<td><code class="${p.status >= 400 || p.error ? 'fail' : 'pass'}">${p.error ? 'ERR' : p.status}</code></td>
|
|
804
|
+
<td class="mono trunc">${escape(p.url)}</td>
|
|
805
|
+
<td class="trunc">${escape(p.title ?? '')}</td>
|
|
806
|
+
<td>${Math.round(p.loadTimeMs)}ms</td>
|
|
807
|
+
</tr>`).join('')}</tbody></table>`;
|
|
808
|
+
return `<details${passed ? '' : ' open'}>${summaryRow(r.seed, passed, meta)}${body}</details>`;
|
|
809
|
+
}
|
|
810
|
+
function renderContentQuality(r) {
|
|
811
|
+
const meta = `${r.pages.length} pages · ${plural(r.thinContent.length, 'thin')} · ${plural(r.duplicates.length, 'duplicate')} · ${plural(r.issues.length, 'issue')}`;
|
|
812
|
+
const body = `
|
|
813
|
+
<table><thead><tr><th>URL</th><th>Words</th><th>H1s</th><th>Flesch</th><th>Grade</th></tr></thead><tbody>${r.pages.slice(0, 50).map((p) => `
|
|
814
|
+
<tr>
|
|
815
|
+
<td class="mono trunc">${escape(p.url)}</td>
|
|
816
|
+
<td>${p.wordCount}</td>
|
|
817
|
+
<td class="${p.h1Count === 1 ? 'pass' : 'fail'}">${p.h1Count}</td>
|
|
818
|
+
<td>${p.fleschReadingEase}</td>
|
|
819
|
+
<td>${p.fleschKincaidGrade}</td>
|
|
820
|
+
</tr>`).join('')}</tbody></table>
|
|
821
|
+
${r.duplicates.length ? `<h3 style="font-size:13px;margin-top:12px">Duplicates</h3><ul>${r.duplicates.map((d) => `<li><code>${escape(d.kind)}</code> (${d.similarity.toFixed(2)}): ${d.urls.map((u) => `<span class="mono">${escape(u)}</span>`).join(', ')}</li>`).join('')}</ul>` : ''}
|
|
822
|
+
${r.issues.length ? `<ul>${r.issues.map((i) => `<li><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span> ${escape(i.message)}${i.url ? ` — <code>${escape(i.url)}</code>` : ''}</li>`).join('')}</ul>` : ''}`;
|
|
823
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow('Content quality', r.passed, meta)}${body}</details>`;
|
|
824
|
+
}
|
|
825
|
+
function renderIssueTable(issues, targetKey = 'target') {
|
|
826
|
+
if (!issues?.length)
|
|
827
|
+
return '<div class="empty">No issues.</div>';
|
|
828
|
+
return `<table><thead><tr><th>Type</th><th>Target</th><th>Detail</th></tr></thead><tbody>${issues.map((i) => `
|
|
829
|
+
<tr>
|
|
830
|
+
<td><code>${escape(i.type)}</code></td>
|
|
831
|
+
<td class="mono trunc">${escape(i[targetKey] ?? '')}</td>
|
|
832
|
+
<td>${escape(i.detail ?? i.message ?? '')}</td>
|
|
833
|
+
</tr>`).join('')}</tbody></table>`;
|
|
834
|
+
}
|
|
835
|
+
function renderResourceHints(r) {
|
|
836
|
+
const meta = `${plural(r.hints.length, 'hint')} · score ${r.score} · ${plural(r.issues.length, 'issue')}`;
|
|
837
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${renderIssueTable(r.issues)}</details>`;
|
|
838
|
+
}
|
|
839
|
+
function renderMixedContent(r) {
|
|
840
|
+
const meta = `${r.httpsPage ? 'HTTPS' : 'HTTP'} · ${plural(r.insecureResources.length, 'insecure resource')}`;
|
|
841
|
+
const body = `
|
|
842
|
+
<dl class="kv">
|
|
843
|
+
<dt>CSP present</dt><dd>${r.cspPresent ? 'yes' : 'no'}</dd>
|
|
844
|
+
<dt>upgrade-insecure-requests</dt><dd>${r.cspUpgradeInsecure ? 'yes' : 'no'}</dd>
|
|
845
|
+
<dt>block-all-mixed-content</dt><dd>${r.cspBlockAllMixed ? 'yes' : 'no'}</dd>
|
|
846
|
+
<dt>Referrer-Policy</dt><dd>${escape(r.referrerPolicy ?? '—')}</dd>
|
|
847
|
+
</dl>
|
|
848
|
+
${r.insecureResources.length ? `<table><thead><tr><th>Type</th><th>URL</th></tr></thead><tbody>${r.insecureResources.map((i) => `<tr><td>${escape(i.type ?? i.tag ?? '')}</td><td class="mono trunc">${escape(i.url)}</td></tr>`).join('')}</tbody></table>` : ''}`;
|
|
849
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
850
|
+
}
|
|
851
|
+
function renderCompression(r) {
|
|
852
|
+
const meta = `${escape(r.contentEncoding ?? 'identity')} · ${escape(r.httpVersion ?? '—')} · ${plural(r.issues.length, 'issue')}`;
|
|
853
|
+
const body = `
|
|
854
|
+
<dl class="kv">
|
|
855
|
+
<dt>HTTP version</dt><dd>${escape(r.httpVersion ?? '—')}</dd>
|
|
856
|
+
<dt>Content-Encoding</dt><dd>${escape(r.contentEncoding ?? '—')}</dd>
|
|
857
|
+
<dt>Content-Length</dt><dd>${bytes(r.contentLength)}</dd>
|
|
858
|
+
<dt>Transfer size</dt><dd>${bytes(r.transferLength)}</dd>
|
|
859
|
+
<dt>Ratio</dt><dd>${r.compressionRatio !== undefined ? r.compressionRatio.toFixed(2) : '—'}</dd>
|
|
860
|
+
<dt>Brotli supported</dt><dd>${r.supportsBrotli ? 'yes' : 'no'}</dd>
|
|
861
|
+
<dt>Alt-Svc has h3</dt><dd>${r.altSvcHasH3 ? 'yes' : 'no'}</dd>
|
|
862
|
+
</dl>
|
|
863
|
+
${r.issues.length ? `<ul>${r.issues.map((i) => `<li>${escape(i)}</li>`).join('')}</ul>` : ''}`;
|
|
864
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.url, r.passed, meta)}${body}</details>`;
|
|
865
|
+
}
|
|
866
|
+
function renderCacheHeaders(r) {
|
|
867
|
+
const meta = `${r.resources.length} resources · ${plural(r.issues.length, 'issue')}`;
|
|
868
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${renderIssueTable(r.issues)}</details>`;
|
|
869
|
+
}
|
|
870
|
+
function renderCookieBanner(r) {
|
|
871
|
+
const meta = `${r.bannerDetected ? 'banner detected' : 'no banner'} · ${plural(r.issues.length, 'issue')}`;
|
|
872
|
+
const body = `
|
|
873
|
+
<dl class="kv">
|
|
874
|
+
<dt>Accept button</dt><dd>${r.hasAcceptButton ? 'yes' : 'no'}</dd>
|
|
875
|
+
<dt>Reject button</dt><dd>${r.hasRejectButton ? 'yes' : 'no'}</dd>
|
|
876
|
+
<dt>Settings button</dt><dd>${r.hasSettingsButton ? 'yes' : 'no'}</dd>
|
|
877
|
+
<dt>Cookies before consent</dt><dd>${r.beforeConsentCookies.length}</dd>
|
|
878
|
+
<dt>Trackers before consent</dt><dd>${r.beforeConsentTrackers.length ? r.beforeConsentTrackers.map((t) => `<code>${escape(t)}</code>`).join(' ') : '—'}</dd>
|
|
879
|
+
</dl>
|
|
880
|
+
${r.issues.length ? `<ul>${r.issues.map((i) => `<li><code>${escape(i.type)}</code> — ${escape(i.detail)}</li>`).join('')}</ul>` : ''}`;
|
|
881
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
882
|
+
}
|
|
883
|
+
function renderThirdParty(r) {
|
|
884
|
+
const meta = `${r.thirdPartyResources}/${r.totalResources} resources · ${bytes(r.thirdPartyBytes)} · ${Math.round(r.thirdPartyBlockingMs)}ms blocking`;
|
|
885
|
+
const body = `
|
|
886
|
+
<dl class="kv">
|
|
887
|
+
<dt>First-party origin</dt><dd class="mono trunc">${escape(r.firstPartyOrigin)}</dd>
|
|
888
|
+
<dt>Third-party bytes</dt><dd>${bytes(r.thirdPartyBytes)}</dd>
|
|
889
|
+
<dt>Blocking time</dt><dd>${Math.round(r.thirdPartyBlockingMs)}ms</dd>
|
|
890
|
+
</dl>
|
|
891
|
+
${r.topEntities?.length ? `<table><thead><tr><th>Entity</th><th>Requests</th><th>Bytes</th></tr></thead><tbody>${r.topEntities.slice(0, 20).map((e) => `<tr><td>${escape(e.name ?? e.entity ?? '—')}</td><td>${e.requests ?? e.count ?? '—'}</td><td>${bytes(e.bytes)}</td></tr>`).join('')}</tbody></table>` : ''}
|
|
892
|
+
${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
|
|
893
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
894
|
+
}
|
|
895
|
+
function renderBundleSize(r) {
|
|
896
|
+
const meta = `JS ${bytes(r.totalJsBytes)} · CSS ${bytes(r.totalCssBytes)} · ${plural(r.bundles.length, 'bundle')}`;
|
|
897
|
+
const body = `
|
|
898
|
+
<dl class="kv">
|
|
899
|
+
<dt>JS</dt><dd>${bytes(r.totalJsBytes)} (transfer ${bytes(r.totalJsTransferBytes)})</dd>
|
|
900
|
+
<dt>CSS</dt><dd>${bytes(r.totalCssBytes)} (transfer ${bytes(r.totalCssTransferBytes)})</dd>
|
|
901
|
+
<dt>Duplicate packages</dt><dd>${r.duplicatePackages?.length ?? 0}</dd>
|
|
902
|
+
</dl>
|
|
903
|
+
${r.bundles?.length ? `<table><thead><tr><th>Type</th><th>URL</th><th>Size</th><th>Transfer</th><th>Framework</th></tr></thead><tbody>${r.bundles.slice(0, 30).map((b) => `
|
|
904
|
+
<tr>
|
|
905
|
+
<td><code>${escape(b.type)}</code></td>
|
|
906
|
+
<td class="mono trunc">${escape(b.url)}</td>
|
|
907
|
+
<td>${bytes(b.bytes)}</td>
|
|
908
|
+
<td>${bytes(b.transferBytes)}</td>
|
|
909
|
+
<td>${escape(b.framework ?? '—')}</td>
|
|
910
|
+
</tr>`).join('')}</tbody></table>` : ''}
|
|
911
|
+
${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
|
|
912
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
913
|
+
}
|
|
914
|
+
function renderOpenGraph(r) {
|
|
915
|
+
const og = r.openGraph ?? {};
|
|
916
|
+
const tw = r.twitter ?? {};
|
|
917
|
+
const meta = `og:${og.type ?? '—'} · twitter:${tw.card ?? '—'} · ${plural(r.issues.length, 'issue')}`;
|
|
918
|
+
const body = `
|
|
919
|
+
<dl class="kv">
|
|
920
|
+
<dt>og:title</dt><dd>${escape(og.title ?? '—')}</dd>
|
|
921
|
+
<dt>og:description</dt><dd>${escape(og.description ?? '—')}</dd>
|
|
922
|
+
<dt>og:image</dt><dd class="mono trunc">${escape(og.image ?? '—')}</dd>
|
|
923
|
+
<dt>og:url</dt><dd class="mono trunc">${escape(og.url ?? '—')}</dd>
|
|
924
|
+
<dt>image reachable</dt><dd>${r.imageReachable ? 'yes' : 'no'}</dd>
|
|
925
|
+
<dt>image size</dt><dd>${r.imageActualWidth ?? '—'}×${r.imageActualHeight ?? '—'}</dd>
|
|
926
|
+
<dt>twitter:card</dt><dd>${escape(tw.card ?? '—')}</dd>
|
|
927
|
+
<dt>twitter:site</dt><dd>${escape(tw.site ?? '—')}</dd>
|
|
928
|
+
</dl>
|
|
929
|
+
${r.issues?.length ? `<ul>${r.issues.map((i) => `<li><code>${escape(i.type)}</code>${i.detail ? ` — ${escape(i.detail)}` : ''}</li>`).join('')}</ul>` : ''}`;
|
|
930
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
931
|
+
}
|
|
932
|
+
function renderRobotsAudit(r) {
|
|
933
|
+
const meta = `${r.present ? 'present' : 'missing'}${r.status !== undefined ? ` · ${r.status}` : ''} · ${r.disallowRules?.length ?? 0} disallow · ${plural(r.issues.length, 'issue')}`;
|
|
934
|
+
const body = `
|
|
935
|
+
<dl class="kv">
|
|
936
|
+
<dt>URL</dt><dd class="mono trunc">${escape(r.url)}</dd>
|
|
937
|
+
<dt>Size</dt><dd>${bytes(r.size)}</dd>
|
|
938
|
+
<dt>User agents</dt><dd>${(r.userAgents ?? []).map((u) => `<code>${escape(u)}</code>`).join(' ') || '—'}</dd>
|
|
939
|
+
<dt>Sitemap URLs</dt><dd>${(r.sitemapUrls ?? []).map((u) => `<div class="mono trunc">${escape(u)}</div>`).join('') || '—'}</dd>
|
|
940
|
+
<dt>Crawl-delay</dt><dd>${r.crawlDelay ?? '—'}</dd>
|
|
941
|
+
<dt>Wildcard disallow</dt><dd>${r.hasWildcardDisallow ? 'yes' : 'no'}</dd>
|
|
942
|
+
</dl>
|
|
943
|
+
${r.issues?.length ? `<ul>${r.issues.map((i) => `<li><code>${escape(i.type)}</code> — ${escape(i.detail)}</li>`).join('')}</ul>` : ''}`;
|
|
944
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.url, r.passed, meta)}${body}</details>`;
|
|
945
|
+
}
|
|
946
|
+
function renderImageAudit(r) {
|
|
947
|
+
const meta = `${r.images?.length ?? 0} images · ${plural(r.issues.length, 'issue')}`;
|
|
948
|
+
const body = `
|
|
949
|
+
${r.stats ? `<dl class="kv">${Object.entries(r.stats).map(([k, v]) => `<dt>${escape(k)}</dt><dd>${typeof v === 'number' ? (k.toLowerCase().includes('byte') ? bytes(v) : v) : escape(String(v))}</dd>`).join('')}</dl>` : ''}
|
|
950
|
+
${r.issues?.length ? renderIssueTable(r.issues) : '<div class="empty">No issues.</div>'}`;
|
|
951
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
952
|
+
}
|
|
953
|
+
function renderWebfonts(r) {
|
|
954
|
+
const meta = `${r.totalFontsLoaded ?? r.fonts?.length ?? 0} fonts · ${bytes(r.totalFontBytes)} · ${plural(r.issues.length, 'issue')}`;
|
|
955
|
+
const body = `
|
|
956
|
+
${r.fonts?.length ? `<table><thead><tr><th>Family</th><th>Source</th><th>Format</th><th>Size</th><th>Display</th><th>Preloaded</th></tr></thead><tbody>${r.fonts.slice(0, 30).map((f) => `
|
|
957
|
+
<tr>
|
|
958
|
+
<td>${escape(f.family)}</td>
|
|
959
|
+
<td><code>${escape(f.source)}</code></td>
|
|
960
|
+
<td>${escape(f.format ?? '—')}</td>
|
|
961
|
+
<td>${bytes(f.size)}</td>
|
|
962
|
+
<td>${escape(f.fontDisplay ?? '—')}</td>
|
|
963
|
+
<td>${f.preloaded ? 'yes' : 'no'}</td>
|
|
964
|
+
</tr>`).join('')}</tbody></table>` : ''}
|
|
965
|
+
${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
|
|
966
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
967
|
+
}
|
|
968
|
+
function renderMotionPrefs(r) {
|
|
969
|
+
const meta = `${r.animationsCount ?? 0} animations · ${r.autoplayVideos ?? 0} autoplay · ${plural(r.issues.length, 'issue')}`;
|
|
970
|
+
const body = `
|
|
971
|
+
<dl class="kv">
|
|
972
|
+
<dt>Respects reduced motion</dt><dd>${r.respectsReducedMotion ? 'yes' : 'no'}</dd>
|
|
973
|
+
<dt>Respects dark mode</dt><dd>${r.respectsDarkMode ? 'yes' : 'no'}</dd>
|
|
974
|
+
<dt>Respects print</dt><dd>${r.respectsPrint ? 'yes' : 'no'}</dd>
|
|
975
|
+
<dt>Respects forced colors</dt><dd>${r.respectsForcedColors ? 'yes' : 'no'}</dd>
|
|
976
|
+
<dt>Animations</dt><dd>${r.animationsCount}</dd>
|
|
977
|
+
<dt>Autoplay videos</dt><dd>${r.autoplayVideos}</dd>
|
|
978
|
+
<dt>Infinite animations</dt><dd>${r.infiniteAnimations}</dd>
|
|
979
|
+
</dl>
|
|
980
|
+
${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
|
|
981
|
+
return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
|
|
247
982
|
}
|
|
248
983
|
//# sourceMappingURL=report.js.map
|