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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const DEFAULT_THRESHOLD = 0.5;
|
|
2
|
+
const TOP_N = 30;
|
|
3
|
+
function emptyResult(pageUrl, passed) {
|
|
4
|
+
return {
|
|
5
|
+
page: pageUrl,
|
|
6
|
+
totalBytes: 0,
|
|
7
|
+
usedBytes: 0,
|
|
8
|
+
unusedBytes: 0,
|
|
9
|
+
unusedRatio: 0,
|
|
10
|
+
byFile: [],
|
|
11
|
+
passed,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export async function auditCssCoverage(page, opts) {
|
|
15
|
+
const pageUrl = page.url();
|
|
16
|
+
const threshold = opts?.threshold ?? DEFAULT_THRESHOLD;
|
|
17
|
+
const pageWithCoverage = page;
|
|
18
|
+
const coverage = pageWithCoverage.coverage;
|
|
19
|
+
if (!coverage || typeof coverage.startCSSCoverage !== 'function' || typeof coverage.stopCSSCoverage !== 'function') {
|
|
20
|
+
return emptyResult(pageUrl, true);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
await coverage.startCSSCoverage({ resetOnNavigation: false });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return emptyResult(pageUrl, true);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
await page.reload({ waitUntil: 'networkidle' });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
try {
|
|
33
|
+
await coverage.stopCSSCoverage();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* ignore */
|
|
37
|
+
}
|
|
38
|
+
return emptyResult(pageUrl, true);
|
|
39
|
+
}
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = await coverage.stopCSSCoverage();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return emptyResult(pageUrl, true);
|
|
46
|
+
}
|
|
47
|
+
const merged = new Map();
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!entry.url || entry.url.startsWith('data:'))
|
|
50
|
+
continue;
|
|
51
|
+
const total = entry.text?.length ?? 0;
|
|
52
|
+
let used = 0;
|
|
53
|
+
for (const range of entry.ranges) {
|
|
54
|
+
const len = Math.max(0, range.end - range.start);
|
|
55
|
+
used += len;
|
|
56
|
+
}
|
|
57
|
+
if (total > 0 && used > total)
|
|
58
|
+
used = total;
|
|
59
|
+
const prev = merged.get(entry.url);
|
|
60
|
+
if (prev) {
|
|
61
|
+
merged.set(entry.url, { total: prev.total + total, used: prev.used + used });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
merged.set(entry.url, { total, used });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let totalBytes = 0;
|
|
68
|
+
let usedBytes = 0;
|
|
69
|
+
const files = [];
|
|
70
|
+
for (const [url, { total, used }] of merged) {
|
|
71
|
+
totalBytes += total;
|
|
72
|
+
usedBytes += used;
|
|
73
|
+
const unusedRatio = total > 0 ? Math.max(0, (total - used) / total) : 0;
|
|
74
|
+
files.push({ url, total, used, unusedRatio });
|
|
75
|
+
}
|
|
76
|
+
files.sort((a, b) => b.unusedRatio - a.unusedRatio);
|
|
77
|
+
const byFile = files.slice(0, TOP_N);
|
|
78
|
+
const unusedBytes = Math.max(0, totalBytes - usedBytes);
|
|
79
|
+
const unusedRatio = totalBytes > 0 ? unusedBytes / totalBytes : 0;
|
|
80
|
+
const passed = unusedRatio <= threshold;
|
|
81
|
+
return {
|
|
82
|
+
page: pageUrl,
|
|
83
|
+
totalBytes,
|
|
84
|
+
usedBytes,
|
|
85
|
+
unusedBytes,
|
|
86
|
+
unusedRatio,
|
|
87
|
+
byFile,
|
|
88
|
+
passed,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=css-coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css-coverage.js","sourceRoot":"","sources":["../src/css-coverage.ts"],"names":[],"mappings":"AAqCA,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,KAAK,GAAG,EAAE,CAAC;AAEjB,SAAS,WAAW,CAAC,OAAe,EAAE,MAAe;IACnD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,UAAU,EAAE,CAAC;QACb,SAAS,EAAE,CAAC;QACZ,WAAW,EAAE,CAAC;QACd,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,EAAE;QACV,MAAM;KACP,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAU,EACV,IAA6B;IAE7B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAG,IAAI,EAAE,SAAS,IAAI,iBAAiB,CAAC;IAEvD,MAAM,gBAAgB,GAAG,IAAmC,CAAC;IAC7D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC;IAC3C,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,CAAC,gBAAgB,KAAK,UAAU,IAAI,OAAO,QAAQ,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;QACnH,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,eAAe,EAAE,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;QACD,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,OAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,eAAe,EAAE,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2C,CAAC;IAClE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,SAAS;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;QACtC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YACjD,IAAI,IAAI,GAAG,CAAC;QACd,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK;YAAE,IAAI,GAAG,KAAK,CAAC;QAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,KAAK,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;QAC5C,UAAU,IAAI,KAAK,CAAC;QACpB,SAAS,IAAI,IAAI,CAAC;QAClB,MAAM,WAAW,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAErC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,WAAW,IAAI,SAAS,CAAC;IAExC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,UAAU;QACV,SAAS;QACT,WAAW;QACX,WAAW;QACX,MAAM;QACN,MAAM;KACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { InspectResult } from './types.js';
|
|
2
|
+
export interface CsvOptions {
|
|
3
|
+
delimiter?: string;
|
|
4
|
+
lineEnding?: '\n' | '\r\n';
|
|
5
|
+
header?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface ResolvedCsvOptions {
|
|
8
|
+
delimiter: string;
|
|
9
|
+
lineEnding: '\n' | '\r\n';
|
|
10
|
+
header: boolean;
|
|
11
|
+
}
|
|
12
|
+
type CsvCell = string | number | boolean | null | undefined;
|
|
13
|
+
declare function resolve(opts: CsvOptions | undefined): ResolvedCsvOptions;
|
|
14
|
+
declare function escapeCell(value: CsvCell, delimiter: string): string;
|
|
15
|
+
declare function toRow(cells: CsvCell[], delimiter: string): string;
|
|
16
|
+
declare function avg(values: number[]): string;
|
|
17
|
+
declare function stepLabel(step: unknown): string;
|
|
18
|
+
export declare function flowsToCsv(result: InspectResult, opts?: CsvOptions): string;
|
|
19
|
+
export declare function a11yToCsv(result: InspectResult, opts?: CsvOptions): string;
|
|
20
|
+
export declare function perfToCsv(result: InspectResult, opts?: CsvOptions): string;
|
|
21
|
+
export declare function visualToCsv(result: InspectResult, opts?: CsvOptions): string;
|
|
22
|
+
export declare function linksToCsv(result: InspectResult, opts?: CsvOptions): string;
|
|
23
|
+
export declare function consoleErrorsToCsv(result: InspectResult, opts?: CsvOptions): string;
|
|
24
|
+
export declare function summaryToCsv(result: InspectResult, opts?: CsvOptions): string;
|
|
25
|
+
export declare function writeAllCsvs(result: InspectResult, outDir: string, opts?: CsvOptions): Promise<string[]>;
|
|
26
|
+
export declare const __internal: {
|
|
27
|
+
escapeCell: typeof escapeCell;
|
|
28
|
+
toRow: typeof toRow;
|
|
29
|
+
avg: typeof avg;
|
|
30
|
+
stepLabel: typeof stepLabel;
|
|
31
|
+
resolve: typeof resolve;
|
|
32
|
+
};
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=csv-exporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv-exporter.d.ts","sourceRoot":"","sources":["../src/csv-exporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,KAAK,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAE5D,iBAAS,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,kBAAkB,CAMjE;AAED,iBAAS,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAe7D;AAED,iBAAS,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAaD,iBAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAKrC;AAED,iBAAS,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAMxC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAkB3E;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAU1E;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CA8B1E;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAQ5E;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAc3E;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,IAAI,CAAC,EAAE,UAAU,GAChB,MAAM,CAUR;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EACrB,IAAI,CAAC,EAAE,UAAU,GAChB,MAAM,CAkER;AAUD,wBAAsB,YAAY,CAChC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBnB;AAGD,eAAO,MAAM,UAAU;;;;;;CAAiD,CAAC"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
function resolve(opts) {
|
|
4
|
+
return {
|
|
5
|
+
delimiter: opts?.delimiter ?? ',',
|
|
6
|
+
lineEnding: opts?.lineEnding ?? '\n',
|
|
7
|
+
header: opts?.header !== false,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function escapeCell(value, delimiter) {
|
|
11
|
+
if (value === null || value === undefined)
|
|
12
|
+
return '';
|
|
13
|
+
if (typeof value === 'number') {
|
|
14
|
+
if (!Number.isFinite(value))
|
|
15
|
+
return '';
|
|
16
|
+
return String(value);
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === 'boolean')
|
|
19
|
+
return value ? 'true' : 'false';
|
|
20
|
+
const s = String(value);
|
|
21
|
+
const needsQuote = s.includes(delimiter) ||
|
|
22
|
+
s.includes('"') ||
|
|
23
|
+
s.includes('\n') ||
|
|
24
|
+
s.includes('\r');
|
|
25
|
+
if (!needsQuote)
|
|
26
|
+
return s;
|
|
27
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
28
|
+
}
|
|
29
|
+
function toRow(cells, delimiter) {
|
|
30
|
+
return cells.map((c) => escapeCell(c, delimiter)).join(delimiter);
|
|
31
|
+
}
|
|
32
|
+
function serialize(headerRow, rows, opts) {
|
|
33
|
+
const lines = [];
|
|
34
|
+
if (opts.header)
|
|
35
|
+
lines.push(toRow(headerRow, opts.delimiter));
|
|
36
|
+
for (const r of rows)
|
|
37
|
+
lines.push(toRow(r, opts.delimiter));
|
|
38
|
+
return lines.join(opts.lineEnding) + (lines.length > 0 ? opts.lineEnding : '');
|
|
39
|
+
}
|
|
40
|
+
function avg(values) {
|
|
41
|
+
const valid = values.filter((v) => typeof v === 'number' && Number.isFinite(v));
|
|
42
|
+
if (valid.length === 0)
|
|
43
|
+
return '';
|
|
44
|
+
const sum = valid.reduce((a, b) => a + b, 0);
|
|
45
|
+
return (sum / valid.length).toFixed(2);
|
|
46
|
+
}
|
|
47
|
+
function stepLabel(step) {
|
|
48
|
+
if (step && typeof step === 'object') {
|
|
49
|
+
const keys = Object.keys(step);
|
|
50
|
+
if (keys.length > 0)
|
|
51
|
+
return keys[0];
|
|
52
|
+
}
|
|
53
|
+
return 'step';
|
|
54
|
+
}
|
|
55
|
+
export function flowsToCsv(result, opts) {
|
|
56
|
+
const o = resolve(opts);
|
|
57
|
+
const header = ['name', 'passed', 'steps', 'durationMs', 'error'];
|
|
58
|
+
const rows = [];
|
|
59
|
+
for (const f of result.flows ?? []) {
|
|
60
|
+
const durationMs = (f.steps ?? []).reduce((sum, s) => sum + (typeof s.durationMs === 'number' ? s.durationMs : 0), 0);
|
|
61
|
+
rows.push([
|
|
62
|
+
f.name,
|
|
63
|
+
f.passed,
|
|
64
|
+
(f.steps ?? []).length,
|
|
65
|
+
durationMs,
|
|
66
|
+
f.error ?? '',
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
return serialize(header, rows, o);
|
|
70
|
+
}
|
|
71
|
+
export function a11yToCsv(result, opts) {
|
|
72
|
+
const o = resolve(opts);
|
|
73
|
+
const header = ['page', 'id', 'impact', 'description', 'helpUrl'];
|
|
74
|
+
const rows = [];
|
|
75
|
+
for (const r of result.a11y ?? []) {
|
|
76
|
+
for (const v of r.violations ?? []) {
|
|
77
|
+
rows.push([r.page, v.id, v.impact, v.description, v.helpUrl]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return serialize(header, rows, o);
|
|
81
|
+
}
|
|
82
|
+
export function perfToCsv(result, opts) {
|
|
83
|
+
const o = resolve(opts);
|
|
84
|
+
const header = [
|
|
85
|
+
'page',
|
|
86
|
+
'performance',
|
|
87
|
+
'accessibility',
|
|
88
|
+
'bestPractices',
|
|
89
|
+
'seo',
|
|
90
|
+
'lcp',
|
|
91
|
+
'fcp',
|
|
92
|
+
'cls',
|
|
93
|
+
'tbt',
|
|
94
|
+
'si',
|
|
95
|
+
];
|
|
96
|
+
const rows = [];
|
|
97
|
+
for (const p of result.perf ?? []) {
|
|
98
|
+
rows.push([
|
|
99
|
+
p.page,
|
|
100
|
+
p.scores?.performance,
|
|
101
|
+
p.scores?.accessibility,
|
|
102
|
+
p.scores?.bestPractices,
|
|
103
|
+
p.scores?.seo,
|
|
104
|
+
p.metrics?.lcp,
|
|
105
|
+
p.metrics?.fcp,
|
|
106
|
+
p.metrics?.cls,
|
|
107
|
+
p.metrics?.tbt,
|
|
108
|
+
p.metrics?.si,
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
return serialize(header, rows, o);
|
|
112
|
+
}
|
|
113
|
+
export function visualToCsv(result, opts) {
|
|
114
|
+
const o = resolve(opts);
|
|
115
|
+
const header = ['page', 'viewport', 'diffPixels', 'diffRatio', 'passed'];
|
|
116
|
+
const rows = [];
|
|
117
|
+
for (const v of result.visual ?? []) {
|
|
118
|
+
rows.push([v.page, v.viewport, v.diffPixels, v.diffRatio, v.passed]);
|
|
119
|
+
}
|
|
120
|
+
return serialize(header, rows, o);
|
|
121
|
+
}
|
|
122
|
+
export function linksToCsv(result, opts) {
|
|
123
|
+
const o = resolve(opts);
|
|
124
|
+
const header = ['page', 'url', 'status', 'ok'];
|
|
125
|
+
const rows = [];
|
|
126
|
+
for (const link of result.links ?? []) {
|
|
127
|
+
for (const broken of link.broken ?? []) {
|
|
128
|
+
const ok = typeof broken.status === 'number' &&
|
|
129
|
+
broken.status >= 200 &&
|
|
130
|
+
broken.status < 400;
|
|
131
|
+
rows.push([link.page, broken.url, broken.status, ok]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return serialize(header, rows, o);
|
|
135
|
+
}
|
|
136
|
+
export function consoleErrorsToCsv(result, opts) {
|
|
137
|
+
const o = resolve(opts);
|
|
138
|
+
const header = ['page', 'type', 'message'];
|
|
139
|
+
const rows = [];
|
|
140
|
+
for (const c of result.consoleErrors ?? []) {
|
|
141
|
+
for (const issue of c.issues ?? []) {
|
|
142
|
+
rows.push([c.page, issue.type, issue.message]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return serialize(header, rows, o);
|
|
146
|
+
}
|
|
147
|
+
export function summaryToCsv(result, opts) {
|
|
148
|
+
const o = resolve(opts);
|
|
149
|
+
const header = [
|
|
150
|
+
'url',
|
|
151
|
+
'startedAt',
|
|
152
|
+
'passed',
|
|
153
|
+
'durationMs',
|
|
154
|
+
'flowsTotal',
|
|
155
|
+
'flowsFailed',
|
|
156
|
+
'a11yCritical',
|
|
157
|
+
'a11ySerious',
|
|
158
|
+
'lcpAvg',
|
|
159
|
+
'clsAvg',
|
|
160
|
+
'visualDiffs',
|
|
161
|
+
'brokenLinks',
|
|
162
|
+
'consoleErrors',
|
|
163
|
+
];
|
|
164
|
+
const flows = result.flows ?? [];
|
|
165
|
+
const flowsTotal = flows.length;
|
|
166
|
+
const flowsFailed = flows.filter((f) => !f.passed).length;
|
|
167
|
+
let a11yCritical = 0;
|
|
168
|
+
let a11ySerious = 0;
|
|
169
|
+
for (const r of result.a11y ?? []) {
|
|
170
|
+
for (const v of r.violations ?? []) {
|
|
171
|
+
if (v.impact === 'critical')
|
|
172
|
+
a11yCritical += 1;
|
|
173
|
+
else if (v.impact === 'serious')
|
|
174
|
+
a11ySerious += 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const perf = result.perf ?? [];
|
|
178
|
+
const lcpAvg = avg(perf.map((p) => p.metrics?.lcp ?? NaN));
|
|
179
|
+
const clsAvg = avg(perf.map((p) => p.metrics?.cls ?? NaN));
|
|
180
|
+
const visualDiffs = (result.visual ?? []).filter((v) => !v.passed || v.diffPixels > 0).length;
|
|
181
|
+
let brokenLinks = 0;
|
|
182
|
+
for (const link of result.links ?? []) {
|
|
183
|
+
brokenLinks += (link.broken ?? []).length;
|
|
184
|
+
}
|
|
185
|
+
let consoleErrorCount = 0;
|
|
186
|
+
for (const c of result.consoleErrors ?? []) {
|
|
187
|
+
consoleErrorCount += c.errorCount ?? 0;
|
|
188
|
+
}
|
|
189
|
+
const row = [
|
|
190
|
+
result.url,
|
|
191
|
+
result.startedAt,
|
|
192
|
+
result.passed,
|
|
193
|
+
result.durationMs,
|
|
194
|
+
flowsTotal,
|
|
195
|
+
flowsFailed,
|
|
196
|
+
a11yCritical,
|
|
197
|
+
a11ySerious,
|
|
198
|
+
lcpAvg,
|
|
199
|
+
clsAvg,
|
|
200
|
+
visualDiffs,
|
|
201
|
+
brokenLinks,
|
|
202
|
+
consoleErrorCount,
|
|
203
|
+
];
|
|
204
|
+
return serialize(header, [row], o);
|
|
205
|
+
}
|
|
206
|
+
function hasBodyRows(csv, opts) {
|
|
207
|
+
if (!csv)
|
|
208
|
+
return false;
|
|
209
|
+
const parts = csv.split(opts.lineEnding).filter((l) => l.length > 0);
|
|
210
|
+
if (parts.length === 0)
|
|
211
|
+
return false;
|
|
212
|
+
if (opts.header)
|
|
213
|
+
return parts.length > 1;
|
|
214
|
+
return parts.length > 0;
|
|
215
|
+
}
|
|
216
|
+
export async function writeAllCsvs(result, outDir, opts) {
|
|
217
|
+
const o = resolve(opts);
|
|
218
|
+
await mkdir(outDir, { recursive: true });
|
|
219
|
+
const sections = [
|
|
220
|
+
{ name: 'flows.csv', csv: flowsToCsv(result, opts) },
|
|
221
|
+
{ name: 'a11y.csv', csv: a11yToCsv(result, opts) },
|
|
222
|
+
{ name: 'perf.csv', csv: perfToCsv(result, opts) },
|
|
223
|
+
{ name: 'visual.csv', csv: visualToCsv(result, opts) },
|
|
224
|
+
{ name: 'links.csv', csv: linksToCsv(result, opts) },
|
|
225
|
+
{ name: 'console-errors.csv', csv: consoleErrorsToCsv(result, opts) },
|
|
226
|
+
{ name: 'summary.csv', csv: summaryToCsv(result, opts), always: true },
|
|
227
|
+
];
|
|
228
|
+
const written = [];
|
|
229
|
+
for (const s of sections) {
|
|
230
|
+
const include = s.always === true || hasBodyRows(s.csv, o);
|
|
231
|
+
if (!include)
|
|
232
|
+
continue;
|
|
233
|
+
const path = join(outDir, s.name);
|
|
234
|
+
await writeFile(path, s.csv, 'utf8');
|
|
235
|
+
written.push(path);
|
|
236
|
+
}
|
|
237
|
+
return written;
|
|
238
|
+
}
|
|
239
|
+
// internal exports used by tests / adjacent tooling
|
|
240
|
+
export const __internal = { escapeCell, toRow, avg, stepLabel, resolve };
|
|
241
|
+
//# sourceMappingURL=csv-exporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv-exporter.js","sourceRoot":"","sources":["../src/csv-exporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Page, BrowserContext } from 'playwright';
|
|
2
|
+
export interface DarkModeIssue {
|
|
3
|
+
type: 'no-color-scheme-meta' | 'no-media-query-support' | 'visually-identical' | 'contrast-regression' | 'hardcoded-color' | 'missing-theme-color-dark';
|
|
4
|
+
severity: 'warn' | 'error';
|
|
5
|
+
selector?: string;
|
|
6
|
+
detail: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DarkModeResult {
|
|
9
|
+
page: string;
|
|
10
|
+
supportsColorScheme: boolean;
|
|
11
|
+
hasColorSchemeMeta: boolean;
|
|
12
|
+
hasThemeColorLight: boolean;
|
|
13
|
+
hasThemeColorDark: boolean;
|
|
14
|
+
lightScreenshot?: string;
|
|
15
|
+
darkScreenshot?: string;
|
|
16
|
+
pixelDiffRatio: number;
|
|
17
|
+
contrastRegressions: Array<{
|
|
18
|
+
selector: string;
|
|
19
|
+
lightRatio: number;
|
|
20
|
+
darkRatio: number;
|
|
21
|
+
}>;
|
|
22
|
+
issues: DarkModeIssue[];
|
|
23
|
+
passed: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface DarkModeAuditOptions {
|
|
26
|
+
screenshotDir?: string;
|
|
27
|
+
sampleSelectors?: string[];
|
|
28
|
+
}
|
|
29
|
+
export type DarkModeContext = BrowserContext;
|
|
30
|
+
export declare function auditDarkMode(page: Page, opts?: DarkModeAuditOptions): Promise<DarkModeResult>;
|
|
31
|
+
//# sourceMappingURL=dark-mode-audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dark-mode-audit.d.ts","sourceRoot":"","sources":["../src/dark-mode-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIvD,MAAM,WAAW,aAAa;IAC5B,IAAI,EACA,sBAAsB,GACtB,wBAAwB,GACxB,oBAAoB,GACpB,qBAAqB,GACrB,iBAAiB,GACjB,0BAA0B,CAAC;IAC/B,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB,EAAE,OAAO,CAAC;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxF,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAGD,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC;AA8J7C,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,cAAc,CAAC,CA2EzB"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const DEFAULT_SELECTORS = ['body', 'main', 'h1', 'p', 'a', 'button'];
|
|
4
|
+
const PIXEL_DIFF_THRESHOLD = 0.02;
|
|
5
|
+
const WCAG_AA_NORMAL = 4.5;
|
|
6
|
+
const HARDCODED_LIMIT = 20;
|
|
7
|
+
function parseColor(input) {
|
|
8
|
+
const s = input.trim().toLowerCase();
|
|
9
|
+
if (!s || s === 'transparent' || s === 'currentcolor')
|
|
10
|
+
return null;
|
|
11
|
+
const rgb = s.match(/^rgba?\s*\(\s*([\d.]+)[ ,]+([\d.]+)[ ,]+([\d.]+)(?:[ ,/]+([\d.%]+))?\s*\)$/);
|
|
12
|
+
if (rgb) {
|
|
13
|
+
const a = rgb[4] === undefined ? 1 : rgb[4].endsWith('%') ? Number(rgb[4].slice(0, -1)) / 100 : Number(rgb[4]);
|
|
14
|
+
return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3]), a];
|
|
15
|
+
}
|
|
16
|
+
const hex = s.match(/^#([0-9a-f]{3,8})$/);
|
|
17
|
+
if (!hex)
|
|
18
|
+
return null;
|
|
19
|
+
const h = hex[1];
|
|
20
|
+
const expand = (i, n) => parseInt(h.slice(i, i + n).padEnd(2, h.slice(i, i + n)), 16);
|
|
21
|
+
if (h.length === 3 || h.length === 4) {
|
|
22
|
+
const a = h.length === 4 ? parseInt(h[3] + h[3], 16) / 255 : 1;
|
|
23
|
+
return [expand(0, 1), expand(1, 1), expand(2, 1), a];
|
|
24
|
+
}
|
|
25
|
+
if (h.length === 6 || h.length === 8) {
|
|
26
|
+
const a = h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1;
|
|
27
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16), a];
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function relLum(c) {
|
|
32
|
+
const v = c / 255;
|
|
33
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
34
|
+
}
|
|
35
|
+
function luminance(rgb) {
|
|
36
|
+
return 0.2126 * relLum(rgb[0]) + 0.7152 * relLum(rgb[1]) + 0.0722 * relLum(rgb[2]);
|
|
37
|
+
}
|
|
38
|
+
function contrastRatio(fgStr, bgStr) {
|
|
39
|
+
const fg = parseColor(fgStr);
|
|
40
|
+
const bg = parseColor(bgStr);
|
|
41
|
+
if (!fg || !bg)
|
|
42
|
+
return null;
|
|
43
|
+
const a = fg[3];
|
|
44
|
+
const blended = a < 1
|
|
45
|
+
? [fg[0] * a + bg[0] * (1 - a), fg[1] * a + bg[1] * (1 - a), fg[2] * a + bg[2] * (1 - a)]
|
|
46
|
+
: [fg[0], fg[1], fg[2]];
|
|
47
|
+
const l1 = luminance(blended);
|
|
48
|
+
const l2 = luminance([bg[0], bg[1], bg[2]]);
|
|
49
|
+
const hi = Math.max(l1, l2);
|
|
50
|
+
const lo = Math.min(l1, l2);
|
|
51
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
52
|
+
}
|
|
53
|
+
function diffRatio(a, b) {
|
|
54
|
+
if (a.length === 0 || b.length === 0)
|
|
55
|
+
return 0;
|
|
56
|
+
const len = Math.min(a.length, b.length);
|
|
57
|
+
let sampled = 0;
|
|
58
|
+
let diff = 0;
|
|
59
|
+
for (let i = 0; i < len; i += 8) {
|
|
60
|
+
sampled += 1;
|
|
61
|
+
if (a[i] !== b[i])
|
|
62
|
+
diff += 1;
|
|
63
|
+
}
|
|
64
|
+
return sampled === 0 ? 0 : diff / sampled;
|
|
65
|
+
}
|
|
66
|
+
async function scanMeta(page) {
|
|
67
|
+
return await page.evaluate(() => {
|
|
68
|
+
const cs = document.querySelector('meta[name="color-scheme"]');
|
|
69
|
+
const themes = Array.from(document.querySelectorAll('meta[name="theme-color"]'));
|
|
70
|
+
let hasLight = false;
|
|
71
|
+
let hasDark = false;
|
|
72
|
+
for (const m of themes) {
|
|
73
|
+
const media = (m.getAttribute('media') ?? '').toLowerCase();
|
|
74
|
+
if (media.includes('prefers-color-scheme: dark'))
|
|
75
|
+
hasDark = true;
|
|
76
|
+
else
|
|
77
|
+
hasLight = true;
|
|
78
|
+
}
|
|
79
|
+
return { hasColorSchemeMeta: cs !== null, hasThemeColorLight: hasLight, hasThemeColorDark: hasDark };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function scanStylesheets(page) {
|
|
83
|
+
return await page.evaluate((limit) => {
|
|
84
|
+
let supportsColorScheme = false;
|
|
85
|
+
const hardcoded = [];
|
|
86
|
+
const commonRx = /(^|[\s,>+~])(html|body|main|a|p|h[1-6])(\s*[,{:.#\[]|$)/i;
|
|
87
|
+
const literalRx = /#[0-9a-f]{3,8}\b|\brgba?\s*\(/i;
|
|
88
|
+
const varRx = /\bvar\s*\(\s*--/i;
|
|
89
|
+
function inspect(rule) {
|
|
90
|
+
if (hardcoded.length >= limit)
|
|
91
|
+
return;
|
|
92
|
+
const sel = rule.selectorText || '';
|
|
93
|
+
if (!commonRx.test(sel))
|
|
94
|
+
return;
|
|
95
|
+
const style = rule.style;
|
|
96
|
+
const candidates = [
|
|
97
|
+
style.getPropertyValue('color').trim(),
|
|
98
|
+
style.getPropertyValue('background-color').trim(),
|
|
99
|
+
style.getPropertyValue('background').trim(),
|
|
100
|
+
].filter(v => v.length > 0);
|
|
101
|
+
for (const value of candidates) {
|
|
102
|
+
if (varRx.test(value))
|
|
103
|
+
continue;
|
|
104
|
+
if (literalRx.test(value)) {
|
|
105
|
+
hardcoded.push({ selector: sel, detail: `literal color in rule: ${value.slice(0, 80)}` });
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function walk(rules) {
|
|
111
|
+
if (!rules)
|
|
112
|
+
return;
|
|
113
|
+
for (let i = 0; i < rules.length; i += 1) {
|
|
114
|
+
const rule = rules[i];
|
|
115
|
+
if (rule instanceof CSSMediaRule) {
|
|
116
|
+
const text = (rule.conditionText || rule.media.mediaText || '').toLowerCase();
|
|
117
|
+
if (text.includes('prefers-color-scheme: dark'))
|
|
118
|
+
supportsColorScheme = true;
|
|
119
|
+
walk(rule.cssRules);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (rule instanceof CSSStyleRule) {
|
|
123
|
+
inspect(rule);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const nested = rule;
|
|
127
|
+
if (nested.cssRules)
|
|
128
|
+
walk(nested.cssRules);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
132
|
+
try {
|
|
133
|
+
walk(sheet.cssRules);
|
|
134
|
+
}
|
|
135
|
+
catch { /* cross-origin */ }
|
|
136
|
+
}
|
|
137
|
+
return { supportsColorScheme, hardcodedRules: hardcoded };
|
|
138
|
+
}, HARDCODED_LIMIT);
|
|
139
|
+
}
|
|
140
|
+
async function readSelectorColors(page, selectors) {
|
|
141
|
+
return await page.evaluate((sels) => {
|
|
142
|
+
function effectiveBg(el) {
|
|
143
|
+
let cur = el;
|
|
144
|
+
while (cur) {
|
|
145
|
+
const bg = window.getComputedStyle(cur).backgroundColor;
|
|
146
|
+
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent')
|
|
147
|
+
return bg;
|
|
148
|
+
cur = cur.parentElement;
|
|
149
|
+
}
|
|
150
|
+
return window.getComputedStyle(document.documentElement).backgroundColor || 'rgb(255, 255, 255)';
|
|
151
|
+
}
|
|
152
|
+
return sels.map(sel => {
|
|
153
|
+
try {
|
|
154
|
+
const el = document.querySelector(sel);
|
|
155
|
+
if (!el)
|
|
156
|
+
return { selector: sel, found: false, fg: '', bg: '' };
|
|
157
|
+
const cs = window.getComputedStyle(el);
|
|
158
|
+
return { selector: sel, found: true, fg: cs.color, bg: effectiveBg(el) };
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { selector: sel, found: false, fg: '', bg: '' };
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}, selectors);
|
|
165
|
+
}
|
|
166
|
+
export async function auditDarkMode(page, opts) {
|
|
167
|
+
const url = page.url();
|
|
168
|
+
const sampleSelectors = opts?.sampleSelectors ?? DEFAULT_SELECTORS;
|
|
169
|
+
const issues = [];
|
|
170
|
+
const meta = await scanMeta(page);
|
|
171
|
+
const styles = await scanStylesheets(page);
|
|
172
|
+
await page.emulateMedia({ colorScheme: 'light' });
|
|
173
|
+
const lightBytes = await page.screenshot({ type: 'png', fullPage: false });
|
|
174
|
+
const lightColors = await readSelectorColors(page, sampleSelectors);
|
|
175
|
+
await page.emulateMedia({ colorScheme: 'dark' });
|
|
176
|
+
const darkBytes = await page.screenshot({ type: 'png', fullPage: false });
|
|
177
|
+
const darkColors = await readSelectorColors(page, sampleSelectors);
|
|
178
|
+
let lightScreenshot;
|
|
179
|
+
let darkScreenshot;
|
|
180
|
+
if (opts?.screenshotDir) {
|
|
181
|
+
await fs.mkdir(opts.screenshotDir, { recursive: true });
|
|
182
|
+
lightScreenshot = path.join(opts.screenshotDir, 'dark-mode-light.png');
|
|
183
|
+
darkScreenshot = path.join(opts.screenshotDir, 'dark-mode-dark.png');
|
|
184
|
+
await fs.writeFile(lightScreenshot, lightBytes);
|
|
185
|
+
await fs.writeFile(darkScreenshot, darkBytes);
|
|
186
|
+
}
|
|
187
|
+
const pixelDiffRatio = diffRatio(lightBytes, darkBytes);
|
|
188
|
+
await page.emulateMedia({ colorScheme: null });
|
|
189
|
+
if (!meta.hasColorSchemeMeta) {
|
|
190
|
+
issues.push({ type: 'no-color-scheme-meta', severity: 'warn', detail: 'Missing <meta name="color-scheme">; browsers cannot pre-style scrollbars or form controls.' });
|
|
191
|
+
}
|
|
192
|
+
if (!styles.supportsColorScheme) {
|
|
193
|
+
issues.push({ type: 'no-media-query-support', severity: 'error', detail: 'No @media (prefers-color-scheme: dark) rules found in any same-origin stylesheet.' });
|
|
194
|
+
}
|
|
195
|
+
if (meta.hasThemeColorLight && !meta.hasThemeColorDark) {
|
|
196
|
+
issues.push({ type: 'missing-theme-color-dark', severity: 'warn', detail: 'theme-color defined for light only; add a media="(prefers-color-scheme: dark)" variant.' });
|
|
197
|
+
}
|
|
198
|
+
if (styles.supportsColorScheme && pixelDiffRatio < PIXEL_DIFF_THRESHOLD) {
|
|
199
|
+
issues.push({ type: 'visually-identical', severity: 'warn', detail: `Light vs dark differ by only ${(pixelDiffRatio * 100).toFixed(2)}%; dark mode rules may not actually apply.` });
|
|
200
|
+
}
|
|
201
|
+
for (const rule of styles.hardcodedRules) {
|
|
202
|
+
issues.push({ type: 'hardcoded-color', severity: 'warn', selector: rule.selector, detail: rule.detail });
|
|
203
|
+
}
|
|
204
|
+
const contrastRegressions = [];
|
|
205
|
+
for (let i = 0; i < sampleSelectors.length; i += 1) {
|
|
206
|
+
const sel = sampleSelectors[i];
|
|
207
|
+
const light = lightColors[i];
|
|
208
|
+
const dark = darkColors[i];
|
|
209
|
+
if (!light?.found || !dark?.found)
|
|
210
|
+
continue;
|
|
211
|
+
const lightRatio = contrastRatio(light.fg, light.bg);
|
|
212
|
+
const darkRatio = contrastRatio(dark.fg, dark.bg);
|
|
213
|
+
if (lightRatio === null || darkRatio === null)
|
|
214
|
+
continue;
|
|
215
|
+
if (darkRatio < lightRatio && darkRatio < WCAG_AA_NORMAL) {
|
|
216
|
+
const lr = Number(lightRatio.toFixed(2));
|
|
217
|
+
const dr = Number(darkRatio.toFixed(2));
|
|
218
|
+
contrastRegressions.push({ selector: sel, lightRatio: lr, darkRatio: dr });
|
|
219
|
+
issues.push({ type: 'contrast-regression', severity: 'error', selector: sel, detail: `Contrast drops from ${lr} (light) to ${dr} (dark); below WCAG AA 4.5:1.` });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
page: url,
|
|
224
|
+
supportsColorScheme: styles.supportsColorScheme,
|
|
225
|
+
hasColorSchemeMeta: meta.hasColorSchemeMeta,
|
|
226
|
+
hasThemeColorLight: meta.hasThemeColorLight,
|
|
227
|
+
hasThemeColorDark: meta.hasThemeColorDark,
|
|
228
|
+
lightScreenshot,
|
|
229
|
+
darkScreenshot,
|
|
230
|
+
pixelDiffRatio: Number(pixelDiffRatio.toFixed(4)),
|
|
231
|
+
contrastRegressions,
|
|
232
|
+
issues,
|
|
233
|
+
passed: issues.every(i => i.severity !== 'error'),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=dark-mode-audit.js.map
|