qa360 2.3.0 → 2.3.2
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 +155 -262
- package/{cli/dist → dist}/commands/ai.js +1 -1
- package/{cli/dist → dist}/commands/ask.js +49 -22
- package/{cli/dist → dist}/commands/coverage.js +17 -4
- package/{cli/dist → dist}/commands/crawl.js +2 -2
- package/{cli/dist → dist}/commands/doctor.js +2 -2
- package/{cli/dist → dist}/commands/explain.js +2 -2
- package/{cli/dist → dist}/commands/flakiness.js +1 -1
- package/{cli/dist → dist}/commands/generate.js +12 -5
- package/{cli/dist → dist}/commands/history.js +1 -1
- package/{cli/dist → dist}/commands/monitor.js +3 -3
- package/{cli/dist → dist}/commands/ollama.js +14 -6
- package/{cli/dist → dist}/commands/pack.js +2 -2
- package/{cli/dist → dist}/commands/regression.js +1 -1
- package/{cli/dist → dist}/commands/repair.js +1 -1
- package/{cli/dist → dist}/commands/retry.js +1 -1
- package/{cli/dist → dist}/commands/run.d.ts +5 -1
- package/{cli/dist → dist}/commands/run.js +87 -1
- package/{cli/dist → dist}/commands/secrets.js +1 -1
- package/{cli/dist → dist}/commands/serve.js +1 -1
- package/{cli/dist → dist}/commands/slo.js +1 -1
- package/{cli/dist → dist}/commands/verify.js +1 -1
- package/{cli/dist → dist}/core/adapters/playwright-native-api.d.ts +2 -0
- package/{cli/dist → dist}/core/adapters/playwright-native-api.js +20 -1
- package/{cli/dist → dist}/core/adapters/playwright-ui.d.ts +21 -0
- package/dist/core/adapters/playwright-ui.js +2050 -0
- package/{cli/dist → dist}/core/ai/ollama-provider.d.ts +4 -0
- package/{cli/dist → dist}/core/ai/ollama-provider.js +41 -8
- package/{cli/dist → dist}/core/artifacts/ui-artifacts.js +24 -4
- package/dist/core/auth/backup-codes-provider.d.ts +91 -0
- package/dist/core/auth/backup-codes-provider.js +215 -0
- package/{cli/dist → dist}/core/auth/basic-auth-provider.d.ts +6 -0
- package/{cli/dist → dist}/core/auth/basic-auth-provider.js +24 -6
- package/dist/core/auth/digest-auth-provider.d.ts +116 -0
- package/dist/core/auth/digest-auth-provider.js +244 -0
- package/dist/core/auth/hcaptcha-handler.d.ts +103 -0
- package/dist/core/auth/hcaptcha-handler.js +288 -0
- package/{cli/dist → dist}/core/auth/index.d.ts +81 -4
- package/{cli/dist → dist}/core/auth/index.js +15 -1
- package/dist/core/auth/oauth-handler.d.ts +408 -0
- package/dist/core/auth/oauth-handler.js +636 -0
- package/{cli/dist → dist}/core/auth/oauth2-provider.d.ts +9 -0
- package/dist/core/auth/oauth2-provider.js +227 -0
- package/dist/core/auth/otp-provider.d.ts +93 -0
- package/dist/core/auth/otp-provider.js +288 -0
- package/dist/core/auth/recaptcha-handler.d.ts +119 -0
- package/dist/core/auth/recaptcha-handler.js +301 -0
- package/dist/core/auth/remember-me-handler.d.ts +142 -0
- package/dist/core/auth/remember-me-handler.js +255 -0
- package/dist/core/auth/saml-handler.d.ts +173 -0
- package/dist/core/auth/saml-handler.js +364 -0
- package/dist/core/auth/webauthn-handler.d.ts +182 -0
- package/dist/core/auth/webauthn-handler.js +310 -0
- package/dist/core/crawler/advanced-interactions.d.ts +342 -0
- package/dist/core/crawler/advanced-interactions.js +1069 -0
- package/dist/core/crawler/blob-url-download-handler.d.ts +145 -0
- package/dist/core/crawler/blob-url-download-handler.js +392 -0
- package/dist/core/crawler/consent-handler.d.ts +49 -0
- package/dist/core/crawler/consent-handler.js +258 -0
- package/dist/core/crawler/cookie-manager.d.ts +166 -0
- package/dist/core/crawler/cookie-manager.js +353 -0
- package/dist/core/crawler/coop-coep-handler.d.ts +136 -0
- package/dist/core/crawler/coop-coep-handler.js +338 -0
- package/dist/core/crawler/csp-handler.d.ts +151 -0
- package/dist/core/crawler/csp-handler.js +415 -0
- package/dist/core/crawler/download-handler.d.ts +155 -0
- package/dist/core/crawler/download-handler.js +370 -0
- package/dist/core/crawler/email-testing-handler.d.ts +214 -0
- package/dist/core/crawler/email-testing-handler.js +398 -0
- package/dist/core/crawler/error-tracking-handler.d.ts +177 -0
- package/dist/core/crawler/error-tracking-handler.js +378 -0
- package/dist/core/crawler/form-handler.d.ts +100 -0
- package/dist/core/crawler/form-handler.js +465 -0
- package/dist/core/crawler/framework-wait-handler.d.ts +96 -0
- package/dist/core/crawler/framework-wait-handler.js +464 -0
- package/dist/core/crawler/geolocation-handler.d.ts +112 -0
- package/dist/core/crawler/geolocation-handler.js +276 -0
- package/dist/core/crawler/index.d.ts +78 -0
- package/{cli/dist → dist}/core/crawler/index.js +74 -1
- package/dist/core/crawler/intelligent-selector-generator.d.ts +164 -0
- package/dist/core/crawler/intelligent-selector-generator.js +612 -0
- package/{cli/dist → dist}/core/crawler/journey-generator.js +44 -1
- package/{cli/dist → dist}/core/crawler/page-analyzer.d.ts +16 -1
- package/{cli/dist → dist}/core/crawler/page-analyzer.js +469 -17
- package/dist/core/crawler/permissions-handler.d.ts +112 -0
- package/dist/core/crawler/permissions-handler.js +236 -0
- package/dist/core/crawler/permissions-policy-handler.d.ts +113 -0
- package/dist/core/crawler/permissions-policy-handler.js +402 -0
- package/dist/core/crawler/presets.d.ts +100 -0
- package/dist/core/crawler/presets.js +887 -0
- package/dist/core/crawler/repl-debug-handler.d.ts +105 -0
- package/dist/core/crawler/repl-debug-handler.js +552 -0
- package/dist/core/crawler/reporting-api-handler.d.ts +212 -0
- package/dist/core/crawler/reporting-api-handler.js +344 -0
- package/{cli/dist → dist}/core/crawler/selector-generator.d.ts +9 -0
- package/{cli/dist → dist}/core/crawler/selector-generator.js +99 -23
- package/dist/core/crawler/site-profiler.d.ts +89 -0
- package/dist/core/crawler/site-profiler.js +290 -0
- package/dist/core/crawler/sourcemaps-handler.d.ts +144 -0
- package/dist/core/crawler/sourcemaps-handler.js +420 -0
- package/dist/core/crawler/stacked-modals-handler.d.ts +118 -0
- package/dist/core/crawler/stacked-modals-handler.js +429 -0
- package/dist/core/crawler/trusted-types-handler.d.ts +149 -0
- package/dist/core/crawler/trusted-types-handler.js +413 -0
- package/{cli/dist → dist}/core/crawler/types.d.ts +68 -2
- package/dist/core/crawler/wait-strategies.d.ts +108 -0
- package/dist/core/crawler/wait-strategies.js +399 -0
- package/dist/core/fixtures/factories.d.ts +180 -0
- package/dist/core/fixtures/factories.js +279 -0
- package/dist/core/fixtures/index.d.ts +6 -0
- package/dist/core/fixtures/index.js +6 -0
- package/{cli/dist → dist}/core/generation/crawler-pack-generator.d.ts +13 -3
- package/dist/core/generation/crawler-pack-generator.js +232 -0
- package/{cli/dist → dist}/core/generation/index.d.ts +2 -0
- package/{cli/dist → dist}/core/generation/index.js +2 -0
- package/{cli/dist → dist}/core/index.d.ts +2 -0
- package/{cli/dist → dist}/core/index.js +4 -0
- package/dist/core/network/index.d.ts +7 -0
- package/dist/core/network/index.js +7 -0
- package/dist/core/network/network-manager.d.ts +237 -0
- package/dist/core/network/network-manager.js +343 -0
- package/dist/core/network/network-simulator.d.ts +158 -0
- package/dist/core/network/network-simulator.js +261 -0
- package/{cli/dist → dist}/core/pack/validator.js +2 -2
- package/{cli/dist → dist}/core/pack-v2/migrator.d.ts +5 -0
- package/{cli/dist → dist}/core/pack-v2/migrator.js +81 -6
- package/{cli/dist → dist}/core/pack-v2/validator.js +4 -3
- package/{cli/dist → dist}/core/pom/base-page.js +1 -1
- package/{cli/dist → dist}/core/pom/loader.js +1 -1
- package/dist/core/reporting/index.d.ts +9 -0
- package/dist/core/reporting/index.js +10 -0
- package/dist/core/reporting/junit-reporter.d.ts +114 -0
- package/dist/core/reporting/junit-reporter.js +306 -0
- package/{cli/dist → dist}/core/runner/e2e-helpers.d.ts +1 -1
- package/{cli/dist → dist}/core/runner/e2e-helpers.js +2 -2
- package/{cli/dist → dist}/core/runner/phase3-runner.d.ts +3 -0
- package/{cli/dist → dist}/core/runner/phase3-runner.js +45 -14
- package/dist/core/sharding/test-sharding.d.ts +137 -0
- package/dist/core/sharding/test-sharding.js +233 -0
- package/dist/core/storage/cookie-manager.d.ts +160 -0
- package/dist/core/storage/cookie-manager.js +268 -0
- package/dist/core/storage/index.d.ts +7 -0
- package/dist/core/storage/index.js +7 -0
- package/dist/core/storage/storage-helpers.d.ts +138 -0
- package/dist/core/storage/storage-helpers.js +315 -0
- package/dist/core/test-helpers/index.d.ts +6 -0
- package/dist/core/test-helpers/index.js +6 -0
- package/dist/core/test-helpers/state-reset.d.ts +119 -0
- package/dist/core/test-helpers/state-reset.js +234 -0
- package/{cli/dist → dist}/core/types/pack-v1.d.ts +15 -2
- package/{cli/dist → dist}/core/types/pack-v2.d.ts +1 -1
- package/dist/core/upload/chunked-uploader.d.ts +150 -0
- package/dist/core/upload/chunked-uploader.js +289 -0
- package/dist/core/upload/index.d.ts +11 -0
- package/dist/core/upload/index.js +8 -0
- package/dist/core/upload/mime-validator.d.ts +119 -0
- package/dist/core/upload/mime-validator.js +373 -0
- package/dist/core/upload/presigned-uploader.d.ts +118 -0
- package/dist/core/upload/presigned-uploader.js +274 -0
- package/dist/core/utils/device-emulation.d.ts +194 -0
- package/dist/core/utils/device-emulation.js +380 -0
- package/dist/core/utils/index.d.ts +8 -0
- package/dist/core/utils/index.js +8 -0
- package/dist/core/utils/retry.d.ts +145 -0
- package/dist/core/utils/retry.js +242 -0
- package/dist/core/utils/smart-wait.d.ts +133 -0
- package/dist/core/utils/smart-wait.js +417 -0
- package/dist/core/visual/index.d.ts +7 -0
- package/dist/core/visual/index.js +7 -0
- package/dist/core/visual/pixel-diff.d.ts +87 -0
- package/dist/core/visual/pixel-diff.js +213 -0
- package/dist/core/visual/screenshot-helper.d.ts +130 -0
- package/dist/core/visual/screenshot-helper.js +223 -0
- package/{cli/dist → dist}/index.js +2 -3
- package/{cli/dist → dist}/utils/config.d.ts +1 -1
- package/{cli/dist → dist}/utils/config.js +36 -3
- package/examples/README.md +160 -0
- package/examples/accessibility.yml +48 -0
- package/examples/api-basic.yml +27 -0
- package/examples/complete.yml +146 -0
- package/examples/crawler.yml +38 -0
- package/examples/fullstack.yml +78 -0
- package/examples/security.yml +58 -0
- package/examples/ui-advanced.yml +49 -0
- package/examples/ui-basic.yml +24 -0
- package/package.json +33 -67
- package/CHANGELOG.md +0 -330
- package/CONTRIBUTING.md +0 -273
- package/QUICK_START.md +0 -191
- package/cli/CHANGELOG.md +0 -84
- package/cli/LICENSE +0 -24
- package/cli/README.md +0 -222
- package/cli/dist/core/adapters/playwright-ui.js +0 -864
- package/cli/dist/core/auth/oauth2-provider.js +0 -114
- package/cli/dist/core/coverage/analyzer.d.ts +0 -101
- package/cli/dist/core/coverage/analyzer.js +0 -415
- package/cli/dist/core/coverage/collector.d.ts +0 -74
- package/cli/dist/core/coverage/collector.js +0 -459
- package/cli/dist/core/coverage/config.d.ts +0 -37
- package/cli/dist/core/coverage/config.js +0 -156
- package/cli/dist/core/coverage/index.d.ts +0 -11
- package/cli/dist/core/coverage/index.js +0 -15
- package/cli/dist/core/coverage/types.d.ts +0 -267
- package/cli/dist/core/coverage/types.js +0 -6
- package/cli/dist/core/coverage/vault.d.ts +0 -95
- package/cli/dist/core/coverage/vault.js +0 -405
- package/cli/dist/core/crawler/index.d.ts +0 -57
- package/cli/dist/core/fixtures/index.d.ts +0 -8
- package/cli/dist/core/fixtures/index.js +0 -8
- package/cli/dist/core/generation/crawler-pack-generator.js +0 -231
- package/cli/dist/core/reporting/index.d.ts +0 -6
- package/cli/dist/core/reporting/index.js +0 -6
- package/cli/dist/core/visual/index.d.ts +0 -6
- package/cli/dist/core/visual/index.js +0 -6
- package/cli/package.json +0 -76
- package/core/LICENSE +0 -24
- package/core/README.md +0 -105
- package/core/package.json +0 -90
- package/core/schemas/pack.schema.json +0 -236
- /package/{cli/bin → bin}/qa360.js +0 -0
- /package/{cli/dist → dist}/cli-minimal.d.ts +0 -0
- /package/{cli/dist → dist}/cli-minimal.js +0 -0
- /package/{cli/dist → dist}/commands/ai.d.ts +0 -0
- /package/{cli/dist → dist}/commands/ask.d.ts +0 -0
- /package/{cli/dist → dist}/commands/coverage.d.ts +0 -0
- /package/{cli/dist → dist}/commands/crawl.d.ts +0 -0
- /package/{cli/dist → dist}/commands/doctor.d.ts +0 -0
- /package/{cli/dist → dist}/commands/examples.d.ts +0 -0
- /package/{cli/dist → dist}/commands/examples.js +0 -0
- /package/{cli/dist → dist}/commands/explain.d.ts +0 -0
- /package/{cli/dist → dist}/commands/flakiness.d.ts +0 -0
- /package/{cli/dist → dist}/commands/generate.d.ts +0 -0
- /package/{cli/dist → dist}/commands/history.d.ts +0 -0
- /package/{cli/dist → dist}/commands/init.d.ts +0 -0
- /package/{cli/dist → dist}/commands/init.js +0 -0
- /package/{cli/dist → dist}/commands/monitor.d.ts +0 -0
- /package/{cli/dist → dist}/commands/ollama.d.ts +0 -0
- /package/{cli/dist → dist}/commands/pack.d.ts +0 -0
- /package/{cli/dist → dist}/commands/regression.d.ts +0 -0
- /package/{cli/dist → dist}/commands/repair.d.ts +0 -0
- /package/{cli/dist → dist}/commands/report.d.ts +0 -0
- /package/{cli/dist → dist}/commands/report.js +0 -0
- /package/{cli/dist → dist}/commands/retry.d.ts +0 -0
- /package/{cli/dist → dist}/commands/scan.d.ts +0 -0
- /package/{cli/dist → dist}/commands/scan.js +0 -0
- /package/{cli/dist → dist}/commands/secrets.d.ts +0 -0
- /package/{cli/dist → dist}/commands/serve.d.ts +0 -0
- /package/{cli/dist → dist}/commands/slo.d.ts +0 -0
- /package/{cli/dist → dist}/commands/verify.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/gitleaks-secrets.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/gitleaks-secrets.js +0 -0
- /package/{cli/dist → dist}/core/adapters/jest-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/jest-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/k6-perf.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/k6-perf.js +0 -0
- /package/{cli/dist → dist}/core/adapters/osv-deps.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/osv-deps.js +0 -0
- /package/{cli/dist → dist}/core/adapters/playwright-native-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/playwright-native-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/pytest-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/pytest-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/semgrep-sast.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/semgrep-sast.js +0 -0
- /package/{cli/dist → dist}/core/adapters/unit-test-types.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/unit-test-types.js +0 -0
- /package/{cli/dist → dist}/core/adapters/vitest-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/vitest-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/zap-dast.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/zap-dast.js +0 -0
- /package/{cli/dist → dist}/core/ai/anthropic-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/anthropic-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/deepseek-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/deepseek-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/index.js +0 -0
- /package/{cli/dist → dist}/core/ai/llm-client.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/llm-client.js +0 -0
- /package/{cli/dist → dist}/core/ai/mock-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/mock-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/openai-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/openai-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/provider-factory.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/provider-factory.js +0 -0
- /package/{cli/dist → dist}/core/artifacts/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/artifacts/index.js +0 -0
- /package/{cli/dist → dist}/core/artifacts/ui-artifacts.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/engine.js +0 -0
- /package/{cli/dist → dist}/core/assertions/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/index.js +0 -0
- /package/{cli/dist → dist}/core/assertions/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/types.js +0 -0
- /package/{cli/dist → dist}/core/auth/api-key-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/api-key-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/aws-iam-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/aws-iam-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/azure-ad-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/azure-ad-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/gcp-adc-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/gcp-adc-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/jwt-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/jwt-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/manager.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/manager.js +0 -0
- /package/{cli/dist → dist}/core/auth/totp-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/totp-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/ui-login-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/ui-login-provider.js +0 -0
- /package/{cli/dist → dist}/core/cache/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/cache/index.js +0 -0
- /package/{cli/dist → dist}/core/cache/lru-cache.d.ts +0 -0
- /package/{cli/dist → dist}/core/cache/lru-cache.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/analyzer.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/analyzer.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/collector.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/collector.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/config.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/config.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/index.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/index.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/types.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/types.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/vault.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/vault.js +0 -0
- /package/{cli/dist → dist}/core/crawler/journey-generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/crawler/types.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/assets.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/assets.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/index.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/server.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/server.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/types.js +0 -0
- /package/{cli/dist → dist}/core/discoverer/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/discoverer/index.js +0 -0
- /package/{cli/dist → dist}/core/fixtures/loader.d.ts +0 -0
- /package/{cli/dist → dist}/core/fixtures/loader.js +0 -0
- /package/{cli/dist → dist}/core/fixtures/resolver.d.ts +0 -0
- /package/{cli/dist → dist}/core/fixtures/resolver.js +0 -0
- /package/{cli/dist → dist}/core/fixtures/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/fixtures/types.js +0 -0
- /package/{cli/dist → dist}/core/flakiness/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/flakiness/index.js +0 -0
- /package/{cli/dist → dist}/core/generation/code-formatter.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/code-formatter.js +0 -0
- /package/{cli/dist → dist}/core/generation/code-generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/code-generator.js +0 -0
- /package/{cli/dist → dist}/core/generation/generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/generator.js +0 -0
- /package/{cli/dist → dist}/core/generation/pack-generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/pack-generator.js +0 -0
- /package/{cli/dist → dist}/core/generation/prompt-builder.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/prompt-builder.js +0 -0
- /package/{cli/dist → dist}/core/generation/source-analyzer.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/source-analyzer.js +0 -0
- /package/{cli/dist → dist}/core/generation/test-optimizer.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/test-optimizer.js +0 -0
- /package/{cli/dist → dist}/core/generation/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/types.js +0 -0
- /package/{cli/dist → dist}/core/hooks/compose.d.ts +0 -0
- /package/{cli/dist → dist}/core/hooks/compose.js +0 -0
- /package/{cli/dist → dist}/core/hooks/runner.d.ts +0 -0
- /package/{cli/dist → dist}/core/hooks/runner.js +0 -0
- /package/{cli/dist → dist}/core/pack/migrator.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack/migrator.js +0 -0
- /package/{cli/dist → dist}/core/pack/validator.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack-v2/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack-v2/index.js +0 -0
- /package/{cli/dist → dist}/core/pack-v2/loader.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack-v2/loader.js +0 -0
- /package/{cli/dist → dist}/core/pack-v2/validator.d.ts +0 -0
- /package/{cli/dist → dist}/core/parallel/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/parallel/index.js +0 -0
- /package/{cli/dist → dist}/core/parallel/parallel-runner.d.ts +0 -0
- /package/{cli/dist → dist}/core/parallel/parallel-runner.js +0 -0
- /package/{cli/dist → dist}/core/pom/base-page.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/index.js +0 -0
- /package/{cli/dist → dist}/core/pom/loader.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/types.js +0 -0
- /package/{cli/dist → dist}/core/proof/bundle.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/bundle.js +0 -0
- /package/{cli/dist → dist}/core/proof/canonicalize.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/canonicalize.js +0 -0
- /package/{cli/dist → dist}/core/proof/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/index.js +0 -0
- /package/{cli/dist → dist}/core/proof/schema.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/schema.js +0 -0
- /package/{cli/dist → dist}/core/proof/signer.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/signer.js +0 -0
- /package/{cli/dist → dist}/core/proof/verifier.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/verifier.js +0 -0
- /package/{cli/dist → dist}/core/regression/detector.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/detector.js +0 -0
- /package/{cli/dist → dist}/core/regression/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/index.js +0 -0
- /package/{cli/dist → dist}/core/regression/trend-analyzer.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/trend-analyzer.js +0 -0
- /package/{cli/dist → dist}/core/regression/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/types.js +0 -0
- /package/{cli/dist → dist}/core/regression/vault.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/vault.js +0 -0
- /package/{cli/dist → dist}/core/repair/engine/fixer.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/engine/fixer.js +0 -0
- /package/{cli/dist → dist}/core/repair/engine/suggestion-engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/engine/suggestion-engine.js +0 -0
- /package/{cli/dist → dist}/core/repair/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/index.js +0 -0
- /package/{cli/dist → dist}/core/repair/repairer.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/repairer.js +0 -0
- /package/{cli/dist → dist}/core/repair/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/types.js +0 -0
- /package/{cli/dist → dist}/core/repair/utils/error-analyzer.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/utils/error-analyzer.js +0 -0
- /package/{cli/dist → dist}/core/reporting/html-reporter.d.ts +0 -0
- /package/{cli/dist → dist}/core/reporting/html-reporter.js +0 -0
- /package/{cli/dist → dist}/core/retry/flakiness-integration.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/flakiness-integration.js +0 -0
- /package/{cli/dist → dist}/core/retry/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/index.js +0 -0
- /package/{cli/dist → dist}/core/retry/retry-engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/retry-engine.js +0 -0
- /package/{cli/dist → dist}/core/retry/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/types.js +0 -0
- /package/{cli/dist → dist}/core/retry/vault.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/vault.js +0 -0
- /package/{cli/dist → dist}/core/schemas/pack.schema.json +0 -0
- /package/{cli/dist → dist}/core/secrets/crypto.d.ts +0 -0
- /package/{cli/dist → dist}/core/secrets/crypto.js +0 -0
- /package/{cli/dist → dist}/core/secrets/manager.d.ts +0 -0
- /package/{cli/dist → dist}/core/secrets/manager.js +0 -0
- /package/{cli/dist → dist}/core/security/redaction-patterns-extended.d.ts +0 -0
- /package/{cli/dist → dist}/core/security/redaction-patterns-extended.js +0 -0
- /package/{cli/dist → dist}/core/security/redactor.d.ts +0 -0
- /package/{cli/dist → dist}/core/security/redactor.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/assertion-healer.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/assertion-healer.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/engine.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/index.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/selector-healer.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/selector-healer.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/types.js +0 -0
- /package/{cli/dist → dist}/core/serve/diagnostics-collector.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/diagnostics-collector.js +0 -0
- /package/{cli/dist → dist}/core/serve/health-checker.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/health-checker.js +0 -0
- /package/{cli/dist → dist}/core/serve/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/index.js +0 -0
- /package/{cli/dist → dist}/core/serve/metrics-collector.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/metrics-collector.js +0 -0
- /package/{cli/dist → dist}/core/serve/process-manager.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/process-manager.js +0 -0
- /package/{cli/dist → dist}/core/serve/server.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/server.js +0 -0
- /package/{cli/dist → dist}/core/slo/config.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/config.js +0 -0
- /package/{cli/dist → dist}/core/slo/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/index.js +0 -0
- /package/{cli/dist → dist}/core/slo/sli-calculator.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/sli-calculator.js +0 -0
- /package/{cli/dist → dist}/core/slo/slo-tracker.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/slo-tracker.js +0 -0
- /package/{cli/dist → dist}/core/slo/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/types.js +0 -0
- /package/{cli/dist → dist}/core/slo/vault.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/vault.js +0 -0
- /package/{cli/dist → dist}/core/tui/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/index.js +0 -0
- /package/{cli/dist → dist}/core/tui/monitor.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/monitor.js +0 -0
- /package/{cli/dist → dist}/core/tui/renderer.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/renderer.js +0 -0
- /package/{cli/dist → dist}/core/tui/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/types.js +0 -0
- /package/{cli/dist → dist}/core/types/pack-v1.js +0 -0
- /package/{cli/dist → dist}/core/types/pack-v2.js +0 -0
- /package/{cli/dist → dist}/core/types/trust-score.d.ts +0 -0
- /package/{cli/dist → dist}/core/types/trust-score.js +0 -0
- /package/{cli/dist → dist}/core/vault/cas.d.ts +0 -0
- /package/{cli/dist → dist}/core/vault/cas.js +0 -0
- /package/{cli/dist → dist}/core/vault/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/vault/index.js +0 -0
- /package/{cli/dist → dist}/core/visual/visual-regression.d.ts +0 -0
- /package/{cli/dist → dist}/core/visual/visual-regression.js +0 -0
- /package/{cli/dist → dist}/core/watch/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/watch/index.js +0 -0
- /package/{cli/dist → dist}/core/watch/watch-mode.d.ts +0 -0
- /package/{cli/dist → dist}/core/watch/watch-mode.js +0 -0
- /package/{cli/dist → dist}/generators/index.d.ts +0 -0
- /package/{cli/dist → dist}/generators/index.js +0 -0
- /package/{cli/dist → dist}/generators/json-reporter.d.ts +0 -0
- /package/{cli/dist → dist}/generators/json-reporter.js +0 -0
- /package/{cli/dist → dist}/generators/test-generator.d.ts +0 -0
- /package/{cli/dist → dist}/generators/test-generator.js +0 -0
- /package/{cli/dist → dist}/index.d.ts +0 -0
- /package/{cli/dist → dist}/scanners/dom-scanner.d.ts +0 -0
- /package/{cli/dist → dist}/scanners/dom-scanner.js +0 -0
- /package/{cli/dist → dist}/scanners/index.d.ts +0 -0
- /package/{cli/dist → dist}/scanners/index.js +0 -0
- /package/{cli/dist → dist}/schemas/pack.schema.json +0 -0
- /package/{cli/dist → dist}/types/scan.d.ts +0 -0
- /package/{cli/dist → dist}/types/scan.js +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Visual Comparison Module
|
|
3
|
+
*
|
|
4
|
+
* P0 Features: Pixel diff for visual regression testing
|
|
5
|
+
* - Pixel-by-pixel comparison
|
|
6
|
+
* - Perceptual diff with anti-aliasing tolerance
|
|
7
|
+
* - Configurable thresholds
|
|
8
|
+
* - Diff image generation
|
|
9
|
+
*/
|
|
10
|
+
import { mkdirSync, existsSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
/**
|
|
13
|
+
* Pixel Diff Comparator
|
|
14
|
+
*/
|
|
15
|
+
export class PixelDiff {
|
|
16
|
+
/**
|
|
17
|
+
* Compare two images pixel by pixel
|
|
18
|
+
*
|
|
19
|
+
* @param image1Path - Path to baseline image
|
|
20
|
+
* @param image2Path - Path to current image
|
|
21
|
+
* @param options - Comparison options
|
|
22
|
+
* @returns Comparison result
|
|
23
|
+
*/
|
|
24
|
+
static async compare(image1Path, image2Path, options = {}) {
|
|
25
|
+
const { threshold = 0.1, // 0.1% default threshold
|
|
26
|
+
pixelThreshold = 10, // Color difference of 10
|
|
27
|
+
generateDiffImage = true, outputDir = '.qa360/diffs', antialiasing = true, } = options;
|
|
28
|
+
// Load images
|
|
29
|
+
const [img1, img2] = await Promise.all([
|
|
30
|
+
this.loadImage(image1Path),
|
|
31
|
+
this.loadImage(image2Path),
|
|
32
|
+
]);
|
|
33
|
+
// Check dimensions match
|
|
34
|
+
if (img1.width !== img2.width || img1.height !== img2.height) {
|
|
35
|
+
throw new Error(`Image dimensions mismatch: ${img1.width}x${img1.height} vs ${img2.width}x${img2.height}`);
|
|
36
|
+
}
|
|
37
|
+
const totalPixels = img1.width * img1.height;
|
|
38
|
+
let diffPixels = 0;
|
|
39
|
+
const diffData = Buffer.alloc(img1.data.length);
|
|
40
|
+
// Compare pixels
|
|
41
|
+
for (let i = 0; i < img1.data.length; i += 4) {
|
|
42
|
+
const r1 = img1.data[i];
|
|
43
|
+
const g1 = img1.data[i + 1];
|
|
44
|
+
const b1 = img1.data[i + 2];
|
|
45
|
+
const a1 = img1.data[i + 3];
|
|
46
|
+
const r2 = img2.data[i];
|
|
47
|
+
const g2 = img2.data[i + 1];
|
|
48
|
+
const b2 = img2.data[i + 2];
|
|
49
|
+
const a2 = img2.data[i + 3];
|
|
50
|
+
// Calculate color difference
|
|
51
|
+
const diff = this.colorDifference({ r: r1, g: g1, b: b1 }, { r: r2, g: g2, b: b2 });
|
|
52
|
+
const alphaDiff = Math.abs(a1 - a2);
|
|
53
|
+
// Check if pixels are different
|
|
54
|
+
if (diff > pixelThreshold || alphaDiff > pixelThreshold) {
|
|
55
|
+
diffPixels++;
|
|
56
|
+
// Copy diff pixel (highlight in red)
|
|
57
|
+
diffData[i] = 255; // R
|
|
58
|
+
diffData[i + 1] = 0; // G
|
|
59
|
+
diffData[i + 2] = 0; // B
|
|
60
|
+
diffData[i + 3] = 255; // A
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Copy original pixel
|
|
64
|
+
diffData[i] = Math.floor((r1 + r2) / 2);
|
|
65
|
+
diffData[i + 1] = Math.floor((g1 + g2) / 2);
|
|
66
|
+
diffData[i + 2] = Math.floor((b1 + b2) / 2);
|
|
67
|
+
diffData[i + 3] = Math.max(a1, a2);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const diffPercentage = (diffPixels / totalPixels) * 100;
|
|
71
|
+
const identical = diffPercentage <= threshold;
|
|
72
|
+
const result = {
|
|
73
|
+
identical,
|
|
74
|
+
diffPixels,
|
|
75
|
+
totalPixels,
|
|
76
|
+
diffPercentage,
|
|
77
|
+
};
|
|
78
|
+
// Generate diff image if requested
|
|
79
|
+
if (generateDiffImage && !identical) {
|
|
80
|
+
const diffImagePath = await this.saveDiffImage(diffData, img1.width, img1.height, image2Path, outputDir);
|
|
81
|
+
result.diffImagePath = diffImagePath;
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Compare two image buffers directly
|
|
87
|
+
*
|
|
88
|
+
* @param buffer1 - First image buffer (PNG)
|
|
89
|
+
* @param buffer2 - Second image buffer (PNG)
|
|
90
|
+
* @param options - Comparison options
|
|
91
|
+
* @returns Comparison result
|
|
92
|
+
*/
|
|
93
|
+
static async compareBuffers(buffer1, buffer2, options = {}) {
|
|
94
|
+
const { threshold = 0.1, pixelThreshold = 10, antialiasing = true, } = options;
|
|
95
|
+
const [img1, img2] = await Promise.all([
|
|
96
|
+
this.decodeBuffer(buffer1),
|
|
97
|
+
this.decodeBuffer(buffer2),
|
|
98
|
+
]);
|
|
99
|
+
if (img1.width !== img2.width || img1.height !== img2.height) {
|
|
100
|
+
return {
|
|
101
|
+
identical: false,
|
|
102
|
+
diffPixels: img1.width * img1.height,
|
|
103
|
+
totalPixels: img1.width * img1.height,
|
|
104
|
+
diffPercentage: 100,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const totalPixels = img1.width * img1.height;
|
|
108
|
+
let diffPixels = 0;
|
|
109
|
+
for (let i = 0; i < img1.data.length; i += 4) {
|
|
110
|
+
const diff = this.colorDifference({ r: img1.data[i], g: img1.data[i + 1], b: img1.data[i + 2] }, { r: img2.data[i], g: img2.data[i + 1], b: img2.data[i + 2] });
|
|
111
|
+
if (diff > pixelThreshold) {
|
|
112
|
+
diffPixels++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const diffPercentage = (diffPixels / totalPixels) * 100;
|
|
116
|
+
return {
|
|
117
|
+
identical: diffPercentage <= threshold,
|
|
118
|
+
diffPixels,
|
|
119
|
+
totalPixels,
|
|
120
|
+
diffPercentage,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Calculate perceptual color difference
|
|
125
|
+
* Uses Euclidean distance in RGB space
|
|
126
|
+
*/
|
|
127
|
+
static colorDifference(c1, c2) {
|
|
128
|
+
return Math.sqrt(Math.pow(c1.r - c2.r, 2) +
|
|
129
|
+
Math.pow(c1.g - c2.g, 2) +
|
|
130
|
+
Math.pow(c1.b - c2.b, 2));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Load image from file path
|
|
134
|
+
*/
|
|
135
|
+
static async loadImage(path) {
|
|
136
|
+
const { readFile } = await import('fs/promises');
|
|
137
|
+
const buffer = await readFile(path);
|
|
138
|
+
return this.decodeBuffer(buffer);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Decode PNG buffer to image data
|
|
142
|
+
*/
|
|
143
|
+
static async decodeBuffer(buffer) {
|
|
144
|
+
// Try using sharp if available (fast, handles many formats)
|
|
145
|
+
try {
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
147
|
+
const sharp = require('sharp');
|
|
148
|
+
const metadata = await sharp(buffer).metadata();
|
|
149
|
+
const { data, info } = await sharp(buffer)
|
|
150
|
+
.ensureAlpha()
|
|
151
|
+
.raw()
|
|
152
|
+
.toBuffer({ resolveWithObject: true });
|
|
153
|
+
return {
|
|
154
|
+
width: info.width,
|
|
155
|
+
height: info.height,
|
|
156
|
+
data: data,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Fallback: use PNG.js
|
|
161
|
+
try {
|
|
162
|
+
const { PNG } = await import('pngjs');
|
|
163
|
+
const png = PNG.sync.read(buffer);
|
|
164
|
+
return {
|
|
165
|
+
width: png.width,
|
|
166
|
+
height: png.height,
|
|
167
|
+
data: png.data,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw new Error('Image decoding failed. Install sharp or pngjs: pnpm add sharp pngjs');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Save diff image to file
|
|
177
|
+
*/
|
|
178
|
+
static async saveDiffImage(data, width, height, sourcePath, outputDir) {
|
|
179
|
+
// Create output directory
|
|
180
|
+
if (!existsSync(outputDir)) {
|
|
181
|
+
mkdirSync(outputDir, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
// Generate filename
|
|
184
|
+
const basename = sourcePath.split('/').pop()?.replace(/\.[^.]+$/, '') || 'diff';
|
|
185
|
+
const timestamp = Date.now();
|
|
186
|
+
const filename = `${basename}-diff-${timestamp}.png`;
|
|
187
|
+
const outputPath = join(outputDir, filename);
|
|
188
|
+
try {
|
|
189
|
+
// Try using sharp if available
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
191
|
+
const sharp = require('sharp');
|
|
192
|
+
await sharp(Buffer.from(data), { raw: { width, height, channels: 4 } })
|
|
193
|
+
.png()
|
|
194
|
+
.toFile(outputPath);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
throw new Error('Saving diff image requires sharp. Run: pnpm add sharp');
|
|
198
|
+
}
|
|
199
|
+
return outputPath;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Convenience function to compare two images
|
|
204
|
+
*/
|
|
205
|
+
export async function compareImages(image1Path, image2Path, options) {
|
|
206
|
+
return PixelDiff.compare(image1Path, image2Path, options);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Convenience function to compare two image buffers
|
|
210
|
+
*/
|
|
211
|
+
export async function compareImageBuffers(buffer1, buffer2, options) {
|
|
212
|
+
return PixelDiff.compareBuffers(buffer1, buffer2, options);
|
|
213
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Screenshot Helper Module
|
|
3
|
+
*
|
|
4
|
+
* P0: Screenshot capture and comparison helpers
|
|
5
|
+
* - Full page and element screenshots
|
|
6
|
+
* - Baseline comparison with PixelDiff
|
|
7
|
+
* - Automatic retry on failure
|
|
8
|
+
* - Multiple format support (PNG, JPEG)
|
|
9
|
+
*/
|
|
10
|
+
import type { Page, Locator } from '@playwright/test';
|
|
11
|
+
import { type PixelDiffResult, type PixelDiffOptions } from './pixel-diff.js';
|
|
12
|
+
/**
|
|
13
|
+
* Screenshot capture options
|
|
14
|
+
*/
|
|
15
|
+
export interface ScreenshotCaptureOptions {
|
|
16
|
+
/** Save path for the screenshot */
|
|
17
|
+
path?: string;
|
|
18
|
+
/** Whether to capture full page (default: false) */
|
|
19
|
+
fullPage?: boolean;
|
|
20
|
+
/** Image format (default: png) */
|
|
21
|
+
type?: 'png' | 'jpeg';
|
|
22
|
+
/** JPEG quality (0-100, default: 80) */
|
|
23
|
+
quality?: number;
|
|
24
|
+
/** Maximum retry attempts (default: 1) */
|
|
25
|
+
maxRetries?: number;
|
|
26
|
+
/** Delay before capture in ms (default: 0) */
|
|
27
|
+
delay?: number;
|
|
28
|
+
/** Whether to hide scrollbars (default: false) */
|
|
29
|
+
hideScrollbars?: boolean;
|
|
30
|
+
/** Whether to mask sensitive elements */
|
|
31
|
+
mask?: Array<{
|
|
32
|
+
selector: string;
|
|
33
|
+
color?: string;
|
|
34
|
+
}>;
|
|
35
|
+
/** Whether to update baseline instead of comparing */
|
|
36
|
+
update?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Screenshot comparison result
|
|
40
|
+
*/
|
|
41
|
+
export interface ScreenshotCompareResult {
|
|
42
|
+
/** Whether screenshots match */
|
|
43
|
+
matches: boolean;
|
|
44
|
+
/** Pixel diff result (if comparison performed) */
|
|
45
|
+
diff?: PixelDiffResult;
|
|
46
|
+
/** Path to actual screenshot */
|
|
47
|
+
actualPath: string;
|
|
48
|
+
/** Path to baseline screenshot */
|
|
49
|
+
baselinePath: string;
|
|
50
|
+
/** Path to diff image (if generated) */
|
|
51
|
+
diffPath?: string;
|
|
52
|
+
/** Number of retry attempts */
|
|
53
|
+
attempts: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Screenshot baseline configuration
|
|
57
|
+
*/
|
|
58
|
+
export interface BaselineConfig {
|
|
59
|
+
/** Base directory for baseline images */
|
|
60
|
+
baselineDir: string;
|
|
61
|
+
/** Directory for actual images (during test) */
|
|
62
|
+
actualDir: string;
|
|
63
|
+
/** Directory for diff images */
|
|
64
|
+
diffDir?: string;
|
|
65
|
+
/** Whether to update baselines */
|
|
66
|
+
update?: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Screenshot Helper
|
|
70
|
+
*/
|
|
71
|
+
export declare class Screenshot {
|
|
72
|
+
private page;
|
|
73
|
+
private defaults;
|
|
74
|
+
constructor(page: Page, defaults?: ScreenshotCaptureOptions);
|
|
75
|
+
/**
|
|
76
|
+
* Take a screenshot of the current page
|
|
77
|
+
*/
|
|
78
|
+
take(options?: ScreenshotCaptureOptions): Promise<Buffer>;
|
|
79
|
+
/**
|
|
80
|
+
* Take a screenshot of a specific element
|
|
81
|
+
*/
|
|
82
|
+
takeElement(locator: Locator, options?: ScreenshotCaptureOptions): Promise<Buffer>;
|
|
83
|
+
/**
|
|
84
|
+
* Take screenshot with retry on failure
|
|
85
|
+
*/
|
|
86
|
+
takeWithRetry(options?: ScreenshotCaptureOptions): Promise<Buffer>;
|
|
87
|
+
/**
|
|
88
|
+
* Compare screenshot with baseline
|
|
89
|
+
*/
|
|
90
|
+
compare(baselinePath: string, options?: ScreenshotCaptureOptions & {
|
|
91
|
+
diffOptions?: PixelDiffOptions;
|
|
92
|
+
}): Promise<ScreenshotCompareResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Compare element screenshot with baseline
|
|
95
|
+
*/
|
|
96
|
+
compareElement(locator: Locator, baselinePath: string, options?: ScreenshotCaptureOptions & {
|
|
97
|
+
diffOptions?: PixelDiffOptions;
|
|
98
|
+
}): Promise<ScreenshotCompareResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a full page screenshot with automatic scrolling for very long pages
|
|
101
|
+
*/
|
|
102
|
+
takeFullPageStitched(options?: ScreenshotCaptureOptions): Promise<Buffer>;
|
|
103
|
+
/**
|
|
104
|
+
* Take screenshot at multiple viewport sizes (responsive testing)
|
|
105
|
+
*/
|
|
106
|
+
takeResponsive(sizes: Array<{
|
|
107
|
+
width: number;
|
|
108
|
+
height: number;
|
|
109
|
+
name?: string;
|
|
110
|
+
}>, options?: ScreenshotCaptureOptions): Promise<Array<{
|
|
111
|
+
size: {
|
|
112
|
+
width: number;
|
|
113
|
+
height: number;
|
|
114
|
+
name?: string;
|
|
115
|
+
};
|
|
116
|
+
buffer: Buffer;
|
|
117
|
+
}>>;
|
|
118
|
+
/**
|
|
119
|
+
* Apply visual masks to sensitive elements
|
|
120
|
+
*/
|
|
121
|
+
private applyMasks;
|
|
122
|
+
/**
|
|
123
|
+
* Sleep utility for delays
|
|
124
|
+
*/
|
|
125
|
+
private sleep;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Convenience function to create screenshot helper
|
|
129
|
+
*/
|
|
130
|
+
export declare function screenshot(page: Page, options?: ScreenshotCaptureOptions): Screenshot;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Screenshot Helper Module
|
|
3
|
+
*
|
|
4
|
+
* P0: Screenshot capture and comparison helpers
|
|
5
|
+
* - Full page and element screenshots
|
|
6
|
+
* - Baseline comparison with PixelDiff
|
|
7
|
+
* - Automatic retry on failure
|
|
8
|
+
* - Multiple format support (PNG, JPEG)
|
|
9
|
+
*/
|
|
10
|
+
import { promises as fs } from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { PixelDiff } from './pixel-diff.js';
|
|
13
|
+
/**
|
|
14
|
+
* Screenshot Helper
|
|
15
|
+
*/
|
|
16
|
+
export class Screenshot {
|
|
17
|
+
page;
|
|
18
|
+
defaults;
|
|
19
|
+
constructor(page, defaults = {}) {
|
|
20
|
+
this.page = page;
|
|
21
|
+
this.defaults = defaults;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Take a screenshot of the current page
|
|
25
|
+
*/
|
|
26
|
+
async take(options = {}) {
|
|
27
|
+
const config = { ...this.defaults, ...options };
|
|
28
|
+
// Apply delay if specified
|
|
29
|
+
if (config.delay) {
|
|
30
|
+
await this.sleep(config.delay);
|
|
31
|
+
}
|
|
32
|
+
// Hide scrollbars if requested
|
|
33
|
+
if (config.hideScrollbars) {
|
|
34
|
+
await this.page.addStyleTag({
|
|
35
|
+
content: '::-webkit-scrollbar { display: none; }',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Mask sensitive elements if specified
|
|
39
|
+
if (config.mask && config.mask.length > 0) {
|
|
40
|
+
await this.applyMasks(config.mask);
|
|
41
|
+
}
|
|
42
|
+
const screenshot = await this.page.screenshot({
|
|
43
|
+
path: config.path,
|
|
44
|
+
fullPage: config.fullPage,
|
|
45
|
+
type: config.type || 'png',
|
|
46
|
+
quality: config.quality,
|
|
47
|
+
});
|
|
48
|
+
return screenshot;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Take a screenshot of a specific element
|
|
52
|
+
*/
|
|
53
|
+
async takeElement(locator, options = {}) {
|
|
54
|
+
const config = { ...this.defaults, ...options };
|
|
55
|
+
// Apply delay if specified
|
|
56
|
+
if (config.delay) {
|
|
57
|
+
await this.sleep(config.delay);
|
|
58
|
+
}
|
|
59
|
+
// Wait for element to be visible and stable
|
|
60
|
+
await locator.waitFor({ state: 'visible' });
|
|
61
|
+
const screenshot = await locator.screenshot({
|
|
62
|
+
path: config.path,
|
|
63
|
+
type: config.type || 'png',
|
|
64
|
+
quality: config.quality,
|
|
65
|
+
});
|
|
66
|
+
return screenshot;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Take screenshot with retry on failure
|
|
70
|
+
*/
|
|
71
|
+
async takeWithRetry(options = {}) {
|
|
72
|
+
const config = { ...this.defaults, ...options, maxRetries: options.maxRetries ?? 3 };
|
|
73
|
+
let lastError = null;
|
|
74
|
+
for (let attempt = 1; attempt <= (config.maxRetries || 1); attempt++) {
|
|
75
|
+
try {
|
|
76
|
+
return await this.take({ ...config, path: undefined }); // Don't use path on retries
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
lastError = error;
|
|
80
|
+
if (attempt < (config.maxRetries || 1)) {
|
|
81
|
+
await this.sleep(500 * attempt); // Exponential backoff
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
throw lastError;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compare screenshot with baseline
|
|
89
|
+
*/
|
|
90
|
+
async compare(baselinePath, options = {}) {
|
|
91
|
+
const { diffOptions, ...screenshotOptions } = options;
|
|
92
|
+
const maxRetries = screenshotOptions.maxRetries ?? 1;
|
|
93
|
+
// Ensure baseline directory exists
|
|
94
|
+
await fs.mkdir(path.dirname(baselinePath), { recursive: true });
|
|
95
|
+
// Take actual screenshot
|
|
96
|
+
const actualBuffer = await this.takeWithRetry({
|
|
97
|
+
...screenshotOptions,
|
|
98
|
+
maxRetries,
|
|
99
|
+
});
|
|
100
|
+
// Save actual if baseline doesn't exist
|
|
101
|
+
const baselineExists = await fs.access(baselinePath).then(() => true).catch(() => false);
|
|
102
|
+
if (!baselineExists || screenshotOptions.update) {
|
|
103
|
+
await fs.writeFile(baselinePath, actualBuffer);
|
|
104
|
+
return {
|
|
105
|
+
matches: true,
|
|
106
|
+
actualPath: baselinePath,
|
|
107
|
+
baselinePath,
|
|
108
|
+
attempts: 1,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// Compare with baseline
|
|
112
|
+
const actualPath = screenshotOptions.path || baselinePath.replace('.png', '-actual.png');
|
|
113
|
+
await fs.writeFile(actualPath, actualBuffer);
|
|
114
|
+
const diffResult = await PixelDiff.compare(baselinePath, actualPath, {
|
|
115
|
+
generateDiffImage: false,
|
|
116
|
+
...diffOptions,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
matches: diffResult.identical,
|
|
120
|
+
diff: diffResult,
|
|
121
|
+
actualPath,
|
|
122
|
+
baselinePath,
|
|
123
|
+
attempts: 1,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Compare element screenshot with baseline
|
|
128
|
+
*/
|
|
129
|
+
async compareElement(locator, baselinePath, options = {}) {
|
|
130
|
+
const { diffOptions, ...screenshotOptions } = options;
|
|
131
|
+
// Ensure baseline directory exists
|
|
132
|
+
await fs.mkdir(path.dirname(baselinePath), { recursive: true });
|
|
133
|
+
// Take actual screenshot
|
|
134
|
+
const actualBuffer = await this.takeElement(locator, screenshotOptions);
|
|
135
|
+
// Save actual if baseline doesn't exist
|
|
136
|
+
const baselineExists = await fs.access(baselinePath).then(() => true).catch(() => false);
|
|
137
|
+
if (!baselineExists || screenshotOptions.update) {
|
|
138
|
+
await fs.writeFile(baselinePath, actualBuffer);
|
|
139
|
+
return {
|
|
140
|
+
matches: true,
|
|
141
|
+
actualPath: baselinePath,
|
|
142
|
+
baselinePath,
|
|
143
|
+
attempts: 1,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Compare with baseline
|
|
147
|
+
const actualPath = screenshotOptions.path || baselinePath.replace('.png', '-actual.png');
|
|
148
|
+
await fs.writeFile(actualPath, actualBuffer);
|
|
149
|
+
const diffResult = await PixelDiff.compare(baselinePath, actualPath, {
|
|
150
|
+
generateDiffImage: false,
|
|
151
|
+
...diffOptions,
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
matches: diffResult.identical,
|
|
155
|
+
diff: diffResult,
|
|
156
|
+
actualPath,
|
|
157
|
+
baselinePath,
|
|
158
|
+
attempts: 1,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Create a full page screenshot with automatic scrolling for very long pages
|
|
163
|
+
*/
|
|
164
|
+
async takeFullPageStitched(options = {}) {
|
|
165
|
+
const config = { ...this.defaults, ...options };
|
|
166
|
+
// Get page dimensions
|
|
167
|
+
const scrollHeight = await this.page.evaluate(() => document.documentElement.scrollHeight);
|
|
168
|
+
const viewportHeight = this.page.viewportSize()?.height || 720;
|
|
169
|
+
// If page fits in viewport, use normal fullPage screenshot
|
|
170
|
+
if (scrollHeight <= viewportHeight) {
|
|
171
|
+
return await this.take({ ...config, fullPage: true });
|
|
172
|
+
}
|
|
173
|
+
// For very long pages, take the screenshot and let Playwright handle it
|
|
174
|
+
return await this.take({ ...config, fullPage: true });
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Take screenshot at multiple viewport sizes (responsive testing)
|
|
178
|
+
*/
|
|
179
|
+
async takeResponsive(sizes, options = {}) {
|
|
180
|
+
const results = [];
|
|
181
|
+
for (const size of sizes) {
|
|
182
|
+
await this.page.setViewportSize({ width: size.width, height: size.height });
|
|
183
|
+
await this.page.waitForLoadState('networkidle').catch(() => { }); // Best effort
|
|
184
|
+
const buffer = await this.take(options);
|
|
185
|
+
results.push({ size, buffer });
|
|
186
|
+
}
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Apply visual masks to sensitive elements
|
|
191
|
+
*/
|
|
192
|
+
async applyMasks(masks) {
|
|
193
|
+
const maskColor = masks[0]?.color || '#000000';
|
|
194
|
+
for (const mask of masks) {
|
|
195
|
+
await this.page.locator(mask.selector).evaluateAll((elements, color) => {
|
|
196
|
+
elements.forEach((el) => {
|
|
197
|
+
if (el instanceof HTMLElement) {
|
|
198
|
+
el.style.backgroundColor = color;
|
|
199
|
+
el.style.color = color;
|
|
200
|
+
// Hide text content
|
|
201
|
+
el.childNodes.forEach((node) => {
|
|
202
|
+
if (node instanceof Text) {
|
|
203
|
+
node.textContent = '████████';
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}, maskColor);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Sleep utility for delays
|
|
213
|
+
*/
|
|
214
|
+
sleep(ms) {
|
|
215
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Convenience function to create screenshot helper
|
|
220
|
+
*/
|
|
221
|
+
export function screenshot(page, options) {
|
|
222
|
+
return new Screenshot(page, options);
|
|
223
|
+
}
|
|
@@ -35,7 +35,7 @@ import { doctorCommand } from './commands/doctor.js';
|
|
|
35
35
|
import { askCommand } from './commands/ask.js';
|
|
36
36
|
import { initCommand } from './commands/init.js';
|
|
37
37
|
import { examplesListCommand, examplesCopyCommand, examplesShowCommand } from './commands/examples.js';
|
|
38
|
-
import { runCommand } from './commands/run.js';
|
|
38
|
+
import { runCommand, preflightCommand } from './commands/run.js';
|
|
39
39
|
import { reportCommand } from './commands/report.js';
|
|
40
40
|
import { verifyCommand } from './commands/verify.js';
|
|
41
41
|
import { explainCommand } from './commands/explain.js';
|
|
@@ -125,8 +125,7 @@ program
|
|
|
125
125
|
.command('preflight [pack]')
|
|
126
126
|
.description('Dry-run validation without execution')
|
|
127
127
|
.action(async (pack) => {
|
|
128
|
-
|
|
129
|
-
console.log(chalk.yellow('⚠️ Phase 2 implementation coming soon...'));
|
|
128
|
+
await preflightCommand(pack);
|
|
130
129
|
});
|
|
131
130
|
program
|
|
132
131
|
.command('report')
|
|
@@ -109,7 +109,31 @@ function parseYaml(content) {
|
|
|
109
109
|
if (listMatch) {
|
|
110
110
|
const value = listMatch[1].trim();
|
|
111
111
|
if (Array.isArray(parent)) {
|
|
112
|
-
|
|
112
|
+
// Check if this is an object item (key: value) or a simple value
|
|
113
|
+
const colonIdx = value.indexOf(':');
|
|
114
|
+
if (colonIdx > 0 && !value.startsWith("'") && !value.startsWith('"')) {
|
|
115
|
+
// Object item like "- url: /" or "- key: value"
|
|
116
|
+
const objKey = value.slice(0, colonIdx).trim();
|
|
117
|
+
let objValue = value.slice(colonIdx + 1).trim();
|
|
118
|
+
// Check if next line is more indented (nested object properties)
|
|
119
|
+
if (i < lines.length - 1) {
|
|
120
|
+
const nextIndent = lines[i + 1].search(/\S|$/);
|
|
121
|
+
if (nextIndent > indent) {
|
|
122
|
+
// This is the start of a nested object
|
|
123
|
+
const obj = {};
|
|
124
|
+
obj[objKey] = parseValue(objValue);
|
|
125
|
+
parent.push(obj);
|
|
126
|
+
stack.push({ obj, indent });
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Single-line object item
|
|
131
|
+
parent.push({ [objKey]: parseValue(objValue) });
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Simple value item
|
|
135
|
+
parent.push(parseValue(value));
|
|
136
|
+
}
|
|
113
137
|
}
|
|
114
138
|
continue;
|
|
115
139
|
}
|
|
@@ -144,6 +168,9 @@ function parseYaml(content) {
|
|
|
144
168
|
if (i < lines.length - 1) {
|
|
145
169
|
const nextIndent = lines[i + 1].search(/\S|$/);
|
|
146
170
|
if (nextIndent > indent) {
|
|
171
|
+
const nextLine = lines[i + 1].trim();
|
|
172
|
+
// Check if next line starts a list (array) or a key-value (object)
|
|
173
|
+
const isList = nextLine.startsWith('-');
|
|
147
174
|
if (Array.isArray(parent[key])) {
|
|
148
175
|
// Already an array
|
|
149
176
|
}
|
|
@@ -151,12 +178,18 @@ function parseYaml(content) {
|
|
|
151
178
|
// Nested object
|
|
152
179
|
stack.push({ obj: parent[key], indent });
|
|
153
180
|
}
|
|
154
|
-
else {
|
|
155
|
-
// Start of array
|
|
181
|
+
else if (isList) {
|
|
182
|
+
// Start of array (next line is a list item)
|
|
156
183
|
const arr = [];
|
|
157
184
|
parent[key] = arr;
|
|
158
185
|
stack.push({ obj: arr, indent });
|
|
159
186
|
}
|
|
187
|
+
else {
|
|
188
|
+
// Start of object (next line is a key-value pair)
|
|
189
|
+
const obj = {};
|
|
190
|
+
parent[key] = obj;
|
|
191
|
+
stack.push({ obj, indent });
|
|
192
|
+
}
|
|
160
193
|
}
|
|
161
194
|
}
|
|
162
195
|
}
|