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
|
@@ -1,864 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QA360 Playwright UI Adapter (Extended)
|
|
3
|
-
* Complete UI E2E testing with all Playwright actions
|
|
4
|
-
*
|
|
5
|
-
* Playwright++ Features:
|
|
6
|
-
* - Video recording (always/retain-on-fail/never)
|
|
7
|
-
* - Automatic screenshots (before/after steps, on error)
|
|
8
|
-
* - Trace capture for debugging
|
|
9
|
-
* - Artifacts management with CAS
|
|
10
|
-
* - HTML report generation
|
|
11
|
-
*/
|
|
12
|
-
import { chromium, firefox, webkit } from '@playwright/test';
|
|
13
|
-
import { SecurityRedactor } from '../security/redactor.js';
|
|
14
|
-
import { createAssertionsEngine } from '../assertions/index.js';
|
|
15
|
-
import { UIArtifactsManager } from '../artifacts/index.js';
|
|
16
|
-
import { HTMLReporter } from '../reporting/index.js';
|
|
17
|
-
import { mkdirSync, existsSync } from 'fs';
|
|
18
|
-
import { join } from 'path';
|
|
19
|
-
export class PlaywrightUiAdapter {
|
|
20
|
-
browser;
|
|
21
|
-
context;
|
|
22
|
-
page;
|
|
23
|
-
redactor;
|
|
24
|
-
auth;
|
|
25
|
-
assertions;
|
|
26
|
-
// Storage for artifacts
|
|
27
|
-
artifactDir;
|
|
28
|
-
videoDir;
|
|
29
|
-
traceDir;
|
|
30
|
-
// Playwright++: Artifacts manager
|
|
31
|
-
artifactsManager;
|
|
32
|
-
failureCount = 0;
|
|
33
|
-
currentTestId;
|
|
34
|
-
allScreenshots = [];
|
|
35
|
-
allVideos = [];
|
|
36
|
-
allTraces = [];
|
|
37
|
-
constructor() {
|
|
38
|
-
this.redactor = SecurityRedactor.forLogs();
|
|
39
|
-
this.artifactDir = '.qa360/artifacts/ui';
|
|
40
|
-
this.videoDir = `${this.artifactDir}/videos`;
|
|
41
|
-
this.traceDir = `${this.artifactDir}/traces`;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Set authentication credentials for requests
|
|
45
|
-
*/
|
|
46
|
-
setAuth(credentials) {
|
|
47
|
-
this.auth = credentials;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Execute UI smoke tests with accessibility
|
|
51
|
-
* Playwright++: Supports artifacts, screenshots, video, trace, HTML reporting
|
|
52
|
-
*/
|
|
53
|
-
async runSmokeTests(config) {
|
|
54
|
-
const startTime = Date.now();
|
|
55
|
-
const outputDir = config.artifacts?.outputDir || this.artifactDir;
|
|
56
|
-
// Ensure output directories exist
|
|
57
|
-
for (const dir of [outputDir, this.videoDir, this.traceDir]) {
|
|
58
|
-
if (!existsSync(dir)) {
|
|
59
|
-
mkdirSync(dir, { recursive: true });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// Initialize artifacts manager if Playwright++ features enabled
|
|
63
|
-
const artifactsEnabled = config.artifacts?.screenshots !== 'never' ||
|
|
64
|
-
config.artifacts?.video !== 'never' ||
|
|
65
|
-
config.artifacts?.trace !== 'never';
|
|
66
|
-
if (artifactsEnabled) {
|
|
67
|
-
this.artifactsManager = new UIArtifactsManager(outputDir, '.qa360/runs/cas');
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
// Store auth config
|
|
71
|
-
this.auth = config.auth;
|
|
72
|
-
this.failureCount = 0;
|
|
73
|
-
this.allScreenshots = [];
|
|
74
|
-
this.allVideos = [];
|
|
75
|
-
this.allTraces = [];
|
|
76
|
-
await this.setupBrowser(config);
|
|
77
|
-
const results = [];
|
|
78
|
-
const pages = config.target.pages || [config.target.baseUrl];
|
|
79
|
-
console.log(`🖥️ Running UI smoke tests (${pages.length} pages)`);
|
|
80
|
-
// Optional login first
|
|
81
|
-
if (config.login) {
|
|
82
|
-
await this.performLogin(config.login);
|
|
83
|
-
}
|
|
84
|
-
for (const pageUrl of pages) {
|
|
85
|
-
// Start artifacts for this test
|
|
86
|
-
this.currentTestId = `smoke-${this.failureCount}`;
|
|
87
|
-
this.artifactsManager?.startTest(this.currentTestId);
|
|
88
|
-
const testResult = await this.testPage(pageUrl, config);
|
|
89
|
-
results.push(testResult);
|
|
90
|
-
if (testResult.success) {
|
|
91
|
-
const a11yInfo = testResult.accessibility ?
|
|
92
|
-
` | A11y: ${testResult.accessibility.score}% (${testResult.accessibility.violations.length} issues)` : '';
|
|
93
|
-
console.log(` ✅ ${pageUrl} -> ${testResult.loadTime}ms${a11yInfo}`);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
this.failureCount++;
|
|
97
|
-
console.log(` ❌ ${pageUrl} -> ${testResult.error}`);
|
|
98
|
-
// Check bail condition
|
|
99
|
-
if (config.bail && this.failureCount >= config.bail) {
|
|
100
|
-
console.log(` 🛑 Bailing after ${this.failureCount} failures`);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
this.artifactsManager?.endTest();
|
|
105
|
-
}
|
|
106
|
-
// Run E2E tests if defined
|
|
107
|
-
let e2eResults = [];
|
|
108
|
-
if (config.target.uiTests && config.target.uiTests.length > 0) {
|
|
109
|
-
console.log(`🧪 Running E2E tests (${config.target.uiTests.length} tests)`);
|
|
110
|
-
for (const test of config.target.uiTests) {
|
|
111
|
-
if (test.enabled !== false) {
|
|
112
|
-
// Start artifacts for this test
|
|
113
|
-
this.currentTestId = `e2e-${test.name}`;
|
|
114
|
-
this.artifactsManager?.startTest(this.currentTestId);
|
|
115
|
-
const result = await this.runE2eTest(test, config);
|
|
116
|
-
e2eResults.push(result);
|
|
117
|
-
const status = result.success ? '✅' : '❌';
|
|
118
|
-
console.log(` ${status} ${test.name} (${result.duration}ms)`);
|
|
119
|
-
if (!result.success) {
|
|
120
|
-
console.log(` Error: ${result.error}`);
|
|
121
|
-
this.failureCount++;
|
|
122
|
-
// Check bail condition
|
|
123
|
-
if (config.bail && this.failureCount >= config.bail) {
|
|
124
|
-
console.log(` 🛑 Bailing after ${this.failureCount} failures`);
|
|
125
|
-
this.artifactsManager?.endTest();
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
this.artifactsManager?.endTest();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
const summary = this.calculateSummary(results, e2eResults);
|
|
134
|
-
const junit = this.generateJUnit(results, e2eResults);
|
|
135
|
-
// Playwright++: Generate HTML report if requested
|
|
136
|
-
if (config.htmlReport) {
|
|
137
|
-
await this.generateHtmlReport(config, results, e2eResults, summary);
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
success: summary.failed === 0,
|
|
141
|
-
results,
|
|
142
|
-
e2eResults,
|
|
143
|
-
summary,
|
|
144
|
-
junit
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
finally {
|
|
148
|
-
await this.cleanup(config);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Run a single E2E test
|
|
153
|
-
* Playwright++: Takes before/after screenshots, captures artifacts on failure
|
|
154
|
-
*/
|
|
155
|
-
async runE2eTest(test, config) {
|
|
156
|
-
const startTime = Date.now();
|
|
157
|
-
const steps = [];
|
|
158
|
-
const screenshotMode = config.artifacts?.screenshots || 'never';
|
|
159
|
-
try {
|
|
160
|
-
// Determine starting URL
|
|
161
|
-
const startUrl = test.url || `${config.target.baseUrl.replace(/\/$/, '')}${test.path || ''}`;
|
|
162
|
-
// Navigate to start URL
|
|
163
|
-
console.log(` 📍 Navigate to: ${startUrl}`);
|
|
164
|
-
await this.page.goto(startUrl, { timeout: test.timeout || config.timeout || 30000 });
|
|
165
|
-
// Take initial screenshot
|
|
166
|
-
if (screenshotMode === 'always') {
|
|
167
|
-
await this.artifactsManager?.takeScreenshot(this.page, {}, {
|
|
168
|
-
testId: this.currentTestId || 'unknown',
|
|
169
|
-
type: 'screenshot',
|
|
170
|
-
tags: ['initial'],
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
// Initialize assertions engine
|
|
174
|
-
this.assertions = createAssertionsEngine(this.page);
|
|
175
|
-
// Execute each step
|
|
176
|
-
for (let i = 0; i < test.steps.length; i++) {
|
|
177
|
-
const step = test.steps[i];
|
|
178
|
-
// Take before screenshot if configured
|
|
179
|
-
if (screenshotMode === 'always') {
|
|
180
|
-
await this.artifactsManager?.takeBeforeScreenshot(this.page, step.action || 'step', i);
|
|
181
|
-
}
|
|
182
|
-
const stepResult = await this.executeStep(step, config);
|
|
183
|
-
steps.push(stepResult);
|
|
184
|
-
// Take after screenshot (always on failure, or always if configured)
|
|
185
|
-
if (screenshotMode === 'always' || (screenshotMode === 'only-on-failure' && !stepResult.success)) {
|
|
186
|
-
await this.artifactsManager?.takeAfterScreenshot(this.page, step.action || 'step', i, stepResult.success);
|
|
187
|
-
}
|
|
188
|
-
if (!stepResult.success) {
|
|
189
|
-
// Take error screenshot
|
|
190
|
-
await this.artifactsManager?.takeErrorScreenshot(this.page, new Error(stepResult.error || 'Step failed'), step.action);
|
|
191
|
-
return {
|
|
192
|
-
test,
|
|
193
|
-
success: false,
|
|
194
|
-
steps,
|
|
195
|
-
duration: Date.now() - startTime,
|
|
196
|
-
error: stepResult.error,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
test,
|
|
202
|
-
success: true,
|
|
203
|
-
steps,
|
|
204
|
-
duration: Date.now() - startTime,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
// Take error screenshot
|
|
209
|
-
await this.artifactsManager?.takeErrorScreenshot(this.page, error);
|
|
210
|
-
return {
|
|
211
|
-
test,
|
|
212
|
-
success: false,
|
|
213
|
-
steps,
|
|
214
|
-
duration: Date.now() - startTime,
|
|
215
|
-
error: this.redactor.redact(error instanceof Error ? error.message : 'Unknown error'),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Execute a single UI test step
|
|
221
|
-
* Playwright++: Enhanced error handling with artifacts
|
|
222
|
-
*/
|
|
223
|
-
async executeStep(step, config) {
|
|
224
|
-
const startTime = Date.now();
|
|
225
|
-
let screenshot;
|
|
226
|
-
try {
|
|
227
|
-
const { action, selector, value, options = {}, expected } = step;
|
|
228
|
-
const opts = { timeout: 5000, ...options };
|
|
229
|
-
switch (action) {
|
|
230
|
-
case 'navigate':
|
|
231
|
-
await this.page.goto(value, opts);
|
|
232
|
-
break;
|
|
233
|
-
case 'click':
|
|
234
|
-
await this.page.click(selector, opts);
|
|
235
|
-
break;
|
|
236
|
-
case 'dblClick':
|
|
237
|
-
await this.page.dblclick(selector, opts);
|
|
238
|
-
break;
|
|
239
|
-
case 'rightClick':
|
|
240
|
-
await this.page.click(selector, { ...opts, button: 'right' });
|
|
241
|
-
break;
|
|
242
|
-
case 'hover':
|
|
243
|
-
await this.page.hover(selector, opts);
|
|
244
|
-
break;
|
|
245
|
-
case 'focus':
|
|
246
|
-
await this.page.focus(selector, opts);
|
|
247
|
-
break;
|
|
248
|
-
case 'fill':
|
|
249
|
-
await this.page.fill(selector, value, opts);
|
|
250
|
-
break;
|
|
251
|
-
case 'type':
|
|
252
|
-
await this.page.type(selector, value, opts);
|
|
253
|
-
break;
|
|
254
|
-
case 'clear':
|
|
255
|
-
await this.page.fill(selector, '', opts);
|
|
256
|
-
break;
|
|
257
|
-
case 'select':
|
|
258
|
-
if (value !== undefined) {
|
|
259
|
-
await this.page.selectOption(selector, value, opts);
|
|
260
|
-
}
|
|
261
|
-
break;
|
|
262
|
-
case 'check':
|
|
263
|
-
await this.page.check(selector, opts);
|
|
264
|
-
break;
|
|
265
|
-
case 'uncheck':
|
|
266
|
-
await this.page.uncheck(selector, opts);
|
|
267
|
-
break;
|
|
268
|
-
case 'upload':
|
|
269
|
-
if (value !== undefined) {
|
|
270
|
-
await this.page.setInputFiles(selector, value, opts);
|
|
271
|
-
}
|
|
272
|
-
break;
|
|
273
|
-
case 'press': {
|
|
274
|
-
const delay = options?.delay;
|
|
275
|
-
const pressOpts = delay !== undefined ? { delay } : {};
|
|
276
|
-
await this.page.keyboard.press(value, pressOpts);
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
case 'waitFor':
|
|
280
|
-
case 'waitForSelector':
|
|
281
|
-
await this.page.waitForSelector(selector, opts);
|
|
282
|
-
break;
|
|
283
|
-
case 'waitForNavigation':
|
|
284
|
-
await this.page.waitForNavigation(opts);
|
|
285
|
-
break;
|
|
286
|
-
case 'waitForTimeout':
|
|
287
|
-
await this.page.waitForTimeout(parseInt(value, 10));
|
|
288
|
-
break;
|
|
289
|
-
case 'scroll':
|
|
290
|
-
if (selector) {
|
|
291
|
-
await this.page.evaluate((sel) => {
|
|
292
|
-
const el = document.querySelector(sel);
|
|
293
|
-
if (el)
|
|
294
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
295
|
-
}, selector);
|
|
296
|
-
}
|
|
297
|
-
break;
|
|
298
|
-
case 'dragAndDrop':
|
|
299
|
-
await this.page.dragAndDrop(selector, value, opts);
|
|
300
|
-
break;
|
|
301
|
-
case 'tap':
|
|
302
|
-
await this.page.tap(selector, opts);
|
|
303
|
-
break;
|
|
304
|
-
default:
|
|
305
|
-
throw new Error(`Unknown action: ${action}`);
|
|
306
|
-
}
|
|
307
|
-
// Add wait after action if specified
|
|
308
|
-
if (step.wait) {
|
|
309
|
-
await this.page.waitForTimeout(step.wait);
|
|
310
|
-
}
|
|
311
|
-
// Take screenshot on failure if configured (will be checked in catch block)
|
|
312
|
-
// or take screenshot if requested
|
|
313
|
-
// Verify expected outcomes
|
|
314
|
-
if (expected) {
|
|
315
|
-
await this.verifyExpected(expected);
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
step,
|
|
319
|
-
success: true,
|
|
320
|
-
duration: Date.now() - startTime,
|
|
321
|
-
screenshot,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
catch (error) {
|
|
325
|
-
// Take screenshot on failure
|
|
326
|
-
try {
|
|
327
|
-
const buffer = await this.page.screenshot({ type: 'png' });
|
|
328
|
-
screenshot = `data:image/png;base64,${buffer.toString('base64')}`;
|
|
329
|
-
}
|
|
330
|
-
catch { }
|
|
331
|
-
return {
|
|
332
|
-
step,
|
|
333
|
-
success: false,
|
|
334
|
-
duration: Date.now() - startTime,
|
|
335
|
-
error: this.redactor.redact(error instanceof Error ? error.message : 'Unknown error'),
|
|
336
|
-
screenshot,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Verify expected outcomes after a step
|
|
342
|
-
*/
|
|
343
|
-
async verifyExpected(expected) {
|
|
344
|
-
if (!expected)
|
|
345
|
-
return;
|
|
346
|
-
if (expected.url !== undefined) {
|
|
347
|
-
const currentUrl = this.page.url();
|
|
348
|
-
if (currentUrl !== expected.url) {
|
|
349
|
-
throw new Error(`URL mismatch: expected "${expected.url}", got "${currentUrl}"`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (expected.urlContains !== undefined) {
|
|
353
|
-
const currentUrl = this.page.url();
|
|
354
|
-
if (!currentUrl.includes(expected.urlContains)) {
|
|
355
|
-
throw new Error(`URL does not contain "${expected.urlContains}": "${currentUrl}"`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if (expected.visible !== undefined) {
|
|
359
|
-
const element = this.page.locator(expected.visible);
|
|
360
|
-
if (!(await element.isVisible())) {
|
|
361
|
-
throw new Error(`Expected element to be visible: ${expected.visible}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (expected.hidden !== undefined) {
|
|
365
|
-
const element = this.page.locator(expected.hidden);
|
|
366
|
-
if (!(await element.isHidden())) {
|
|
367
|
-
throw new Error(`Expected element to be hidden: ${expected.hidden}`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (expected.elementText) {
|
|
371
|
-
const element = this.page.locator(expected.elementText.selector);
|
|
372
|
-
const text = await element.textContent();
|
|
373
|
-
if (text !== expected.elementText.text) {
|
|
374
|
-
throw new Error(`Text mismatch for ${expected.elementText.selector}: expected "${expected.elementText.text}", got "${text}"`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Test single page with accessibility
|
|
380
|
-
*/
|
|
381
|
-
async testPage(pageUrl, config) {
|
|
382
|
-
try {
|
|
383
|
-
const startTime = Date.now();
|
|
384
|
-
// Navigate to page
|
|
385
|
-
const response = await this.page.goto(pageUrl, {
|
|
386
|
-
timeout: config.timeout || 30000,
|
|
387
|
-
waitUntil: 'networkidle'
|
|
388
|
-
});
|
|
389
|
-
const loadTime = Date.now() - startTime;
|
|
390
|
-
if (!response || !response.ok()) {
|
|
391
|
-
return {
|
|
392
|
-
page: pageUrl,
|
|
393
|
-
success: false,
|
|
394
|
-
loadTime,
|
|
395
|
-
error: `HTTP ${response?.status() || 'unknown'}: Failed to load page`
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
// Take screenshot based on config
|
|
399
|
-
const screenshot = await this.takeScreenshot(pageUrl, config.target.screenshot);
|
|
400
|
-
// Get DOM snapshot
|
|
401
|
-
const domSnapshot = await this.getDomSnapshot();
|
|
402
|
-
// Run accessibility tests
|
|
403
|
-
const accessibility = await this.runAccessibilityTests(config.budgets);
|
|
404
|
-
// Check if accessibility meets budget
|
|
405
|
-
const a11yPassed = !config.budgets?.a11y_min ||
|
|
406
|
-
(accessibility?.score !== undefined && accessibility.score >= config.budgets.a11y_min);
|
|
407
|
-
return {
|
|
408
|
-
page: pageUrl,
|
|
409
|
-
success: a11yPassed,
|
|
410
|
-
loadTime,
|
|
411
|
-
screenshot,
|
|
412
|
-
accessibility,
|
|
413
|
-
domSnapshot,
|
|
414
|
-
error: a11yPassed ? undefined :
|
|
415
|
-
`Accessibility score ${accessibility?.score || 0}% below budget ${config.budgets?.a11y_min}%`
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
catch (error) {
|
|
419
|
-
return {
|
|
420
|
-
page: pageUrl,
|
|
421
|
-
success: false,
|
|
422
|
-
loadTime: 0,
|
|
423
|
-
error: this.redactor.redact(error instanceof Error ? error.message : 'Unknown error')
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Setup browser with all options
|
|
429
|
-
* Playwright++: Enhanced video/trace recording support
|
|
430
|
-
*/
|
|
431
|
-
async setupBrowser(config) {
|
|
432
|
-
// Determine browser type
|
|
433
|
-
const browserType = config.target.browser || 'chromium';
|
|
434
|
-
const browserTypeObj = browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium;
|
|
435
|
-
// Determine headed mode (CLI override > target config > default headless)
|
|
436
|
-
const headless = config.cliHeaded ?? config.target.headless ?? true;
|
|
437
|
-
// Launch browser
|
|
438
|
-
this.browser = await browserTypeObj.launch({
|
|
439
|
-
headless,
|
|
440
|
-
args: ['--no-sandbox', '--disable-dev-shm-usage'],
|
|
441
|
-
slowMo: config.target.slowMo || 0,
|
|
442
|
-
});
|
|
443
|
-
// Build extra HTTP headers with auth
|
|
444
|
-
const extraHTTPHeaders = {
|
|
445
|
-
'User-Agent': 'QA360-UI-Test/1.0'
|
|
446
|
-
};
|
|
447
|
-
if (this.auth?.headers) {
|
|
448
|
-
Object.assign(extraHTTPHeaders, this.auth.headers);
|
|
449
|
-
}
|
|
450
|
-
// Setup viewport based on device or explicit config
|
|
451
|
-
let viewport = { width: 1280, height: 720 };
|
|
452
|
-
if (config.target.device === 'mobile') {
|
|
453
|
-
viewport = { width: 375, height: 667 };
|
|
454
|
-
}
|
|
455
|
-
else if (config.target.device === 'tablet') {
|
|
456
|
-
viewport = { width: 768, height: 1024 };
|
|
457
|
-
}
|
|
458
|
-
else if (config.target.viewport) {
|
|
459
|
-
viewport = config.target.viewport;
|
|
460
|
-
}
|
|
461
|
-
// Playwright++: Determine video recording mode
|
|
462
|
-
const videoMode = config.artifacts?.video ?? config.target.video;
|
|
463
|
-
const shouldRecordVideo = videoMode === 'always' || videoMode === 'retain-on-failure';
|
|
464
|
-
// Create context with video recording if enabled
|
|
465
|
-
const recordVideo = shouldRecordVideo
|
|
466
|
-
? { dir: this.videoDir, size: viewport }
|
|
467
|
-
: undefined;
|
|
468
|
-
this.context = await this.browser.newContext({
|
|
469
|
-
viewport,
|
|
470
|
-
userAgent: 'QA360-UI-Test/1.0',
|
|
471
|
-
extraHTTPHeaders,
|
|
472
|
-
recordVideo,
|
|
473
|
-
});
|
|
474
|
-
// Playwright++: Start tracing if enabled
|
|
475
|
-
const traceMode = config.artifacts?.trace ?? config.target.trace;
|
|
476
|
-
if (traceMode === 'always' || traceMode === 'on-first-failure' || traceMode === 'retain-on-failure') {
|
|
477
|
-
// Start tracing - will be saved in cleanup
|
|
478
|
-
// Note: Playwright's trace API is context.startTracing()
|
|
479
|
-
// Implementation depends on Playwright version
|
|
480
|
-
}
|
|
481
|
-
// Add cookies from auth credentials after context creation
|
|
482
|
-
if (this.auth?.cookies && this.auth.cookies.length > 0) {
|
|
483
|
-
await this.context.addCookies(this.auth.cookies.map(c => ({
|
|
484
|
-
name: c.name,
|
|
485
|
-
value: c.value,
|
|
486
|
-
domain: c.domain || '',
|
|
487
|
-
path: c.path || '/',
|
|
488
|
-
httpOnly: c.httpOnly || false,
|
|
489
|
-
secure: c.secure || false
|
|
490
|
-
})));
|
|
491
|
-
}
|
|
492
|
-
this.page = await this.context.newPage();
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Determine if video should be recorded
|
|
496
|
-
*/
|
|
497
|
-
shouldRecordVideo(mode) {
|
|
498
|
-
return mode === 'always' || mode === 'retain-on-fail';
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Playwright++: Generate HTML report
|
|
502
|
-
*/
|
|
503
|
-
async generateHtmlReport(config, results, e2eResults, summary) {
|
|
504
|
-
const reportPath = config.htmlReport || join(config.artifacts?.outputDir || this.artifactDir, 'report.html');
|
|
505
|
-
const timestamp = new Date().toISOString();
|
|
506
|
-
const reportData = {
|
|
507
|
-
title: `QA360 UI Test Report - ${timestamp}`,
|
|
508
|
-
summary: {
|
|
509
|
-
total: summary.total,
|
|
510
|
-
passed: summary.passed,
|
|
511
|
-
failed: summary.failed,
|
|
512
|
-
skipped: 0,
|
|
513
|
-
duration: summary.avgLoadTime * summary.total,
|
|
514
|
-
timestamp,
|
|
515
|
-
},
|
|
516
|
-
tests: [
|
|
517
|
-
...results.map((r, i) => ({
|
|
518
|
-
id: `smoke-${i}`,
|
|
519
|
-
name: `UI Smoke: ${r.page}`,
|
|
520
|
-
status: (r.success ? 'passed' : 'failed'),
|
|
521
|
-
duration: r.loadTime,
|
|
522
|
-
error: r.error,
|
|
523
|
-
steps: [],
|
|
524
|
-
artifacts: r.artifacts,
|
|
525
|
-
})),
|
|
526
|
-
...e2eResults.map((r, i) => ({
|
|
527
|
-
id: `e2e-${i}`,
|
|
528
|
-
name: r.test.name || 'E2E Test',
|
|
529
|
-
status: (r.success ? 'passed' : 'failed'),
|
|
530
|
-
duration: r.duration,
|
|
531
|
-
error: r.error,
|
|
532
|
-
steps: r.steps.map((s, j) => ({
|
|
533
|
-
name: s.step.action || `Step ${j + 1}`,
|
|
534
|
-
action: s.step.action || 'step',
|
|
535
|
-
selector: s.step.selector,
|
|
536
|
-
value: s.step.value,
|
|
537
|
-
status: (s.success ? 'passed' : 'failed'),
|
|
538
|
-
duration: s.duration,
|
|
539
|
-
error: s.error,
|
|
540
|
-
})),
|
|
541
|
-
artifacts: undefined,
|
|
542
|
-
})),
|
|
543
|
-
],
|
|
544
|
-
artifacts: {
|
|
545
|
-
screenshots: this.allScreenshots.map(s => ({
|
|
546
|
-
path: s.localPath,
|
|
547
|
-
timestamp: s.metadata.timestamp,
|
|
548
|
-
type: 'after',
|
|
549
|
-
})),
|
|
550
|
-
videos: this.allVideos.map(v => ({ path: v, duration: 0 })),
|
|
551
|
-
traces: this.allTraces.map(t => ({ path: t, format: 'zip' })),
|
|
552
|
-
},
|
|
553
|
-
environment: {
|
|
554
|
-
browser: config.target.browser || 'chromium',
|
|
555
|
-
platform: process.platform,
|
|
556
|
-
nodeVersion: process.version,
|
|
557
|
-
},
|
|
558
|
-
};
|
|
559
|
-
HTMLReporter.generate(reportData, reportPath);
|
|
560
|
-
console.log(`\n📊 HTML report generated: ${reportPath}`);
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* Perform login if configured
|
|
564
|
-
*/
|
|
565
|
-
async performLogin(login) {
|
|
566
|
-
if (!login || !login.username || !login.password) {
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
console.log(` 🔐 Performing login...`);
|
|
570
|
-
const loginUrl = login.url || this.page.url();
|
|
571
|
-
await this.page.goto(loginUrl);
|
|
572
|
-
// Fill login form
|
|
573
|
-
const usernameSelector = login.usernameSelector ||
|
|
574
|
-
'input[name="username"], input[name="email"], input[type="email"], #username, #email';
|
|
575
|
-
const passwordSelector = login.passwordSelector ||
|
|
576
|
-
'input[name="password"], input[type="password"], #password';
|
|
577
|
-
const submitSelector = login.submitSelector ||
|
|
578
|
-
'button[type="submit"], input[type="submit"], button:has-text("Login"), button:has-text("Sign in")';
|
|
579
|
-
await this.page.fill(usernameSelector, login.username);
|
|
580
|
-
await this.page.fill(passwordSelector, login.password);
|
|
581
|
-
await this.page.click(submitSelector);
|
|
582
|
-
// Wait for navigation or login completion
|
|
583
|
-
try {
|
|
584
|
-
await this.page.waitForLoadState('networkidle', { timeout: 10000 });
|
|
585
|
-
}
|
|
586
|
-
catch {
|
|
587
|
-
// Continue even if navigation doesn't complete
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Run accessibility tests using axe-core
|
|
592
|
-
*/
|
|
593
|
-
async runAccessibilityTests(budgets) {
|
|
594
|
-
try {
|
|
595
|
-
// Inject axe-core
|
|
596
|
-
await this.page.addScriptTag({
|
|
597
|
-
url: 'https://unpkg.com/axe-core@4.8.2/axe.min.js'
|
|
598
|
-
});
|
|
599
|
-
// Run axe analysis
|
|
600
|
-
const axeResults = await this.page.evaluate(() => {
|
|
601
|
-
return new Promise((resolve) => {
|
|
602
|
-
// @ts-ignore - axe is injected
|
|
603
|
-
if (typeof axe !== 'undefined') {
|
|
604
|
-
// @ts-ignore
|
|
605
|
-
axe.run((err, results) => {
|
|
606
|
-
if (err) {
|
|
607
|
-
resolve({ violations: [], passes: [], incomplete: [] });
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
resolve(results);
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
else {
|
|
615
|
-
resolve({ violations: [], passes: [], incomplete: [] });
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
// Process violations
|
|
620
|
-
const violations = axeResults.violations?.map((violation) => ({
|
|
621
|
-
id: violation.id,
|
|
622
|
-
impact: violation.impact || 'minor',
|
|
623
|
-
description: violation.description || violation.help || 'Accessibility issue',
|
|
624
|
-
nodes: violation.nodes?.length || 0
|
|
625
|
-
})) || [];
|
|
626
|
-
// Calculate score (simple scoring: 100 - weighted violations)
|
|
627
|
-
const criticalCount = violations.filter((v) => v.impact === 'critical').length;
|
|
628
|
-
const seriousCount = violations.filter((v) => v.impact === 'serious').length;
|
|
629
|
-
const moderateCount = violations.filter((v) => v.impact === 'moderate').length;
|
|
630
|
-
const minorCount = violations.filter((v) => v.impact === 'minor').length;
|
|
631
|
-
const score = Math.max(0, 100 - (criticalCount * 25 +
|
|
632
|
-
seriousCount * 10 +
|
|
633
|
-
moderateCount * 5 +
|
|
634
|
-
minorCount * 1));
|
|
635
|
-
return {
|
|
636
|
-
score: Math.round(score),
|
|
637
|
-
violations
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
catch (error) {
|
|
641
|
-
console.log(` ⚠️ Accessibility test failed: ${error}`);
|
|
642
|
-
return {
|
|
643
|
-
score: 0,
|
|
644
|
-
violations: [{
|
|
645
|
-
id: 'axe-error',
|
|
646
|
-
impact: 'critical',
|
|
647
|
-
description: 'Failed to run accessibility analysis',
|
|
648
|
-
nodes: 0
|
|
649
|
-
}]
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Get DOM snapshot for debugging
|
|
655
|
-
*/
|
|
656
|
-
async getDomSnapshot() {
|
|
657
|
-
try {
|
|
658
|
-
const snapshot = await this.page.evaluate(() => {
|
|
659
|
-
return {
|
|
660
|
-
title: document.title,
|
|
661
|
-
url: window.location.href,
|
|
662
|
-
elements: {
|
|
663
|
-
buttons: document.querySelectorAll('button, input[type="button"], input[type="submit"]').length,
|
|
664
|
-
links: document.querySelectorAll('a[href]').length,
|
|
665
|
-
forms: document.querySelectorAll('form').length,
|
|
666
|
-
inputs: document.querySelectorAll('input, textarea, select').length
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
});
|
|
670
|
-
return snapshot;
|
|
671
|
-
}
|
|
672
|
-
catch {
|
|
673
|
-
return {
|
|
674
|
-
title: 'Unknown',
|
|
675
|
-
url: this.page.url(),
|
|
676
|
-
elements: { buttons: 0, links: 0, forms: 0, inputs: 0 }
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Take screenshot for debugging
|
|
682
|
-
*/
|
|
683
|
-
async takeScreenshot(pageUrl, mode) {
|
|
684
|
-
const shouldTake = mode === 'always' || mode === 'only-on-fail';
|
|
685
|
-
if (!shouldTake)
|
|
686
|
-
return '';
|
|
687
|
-
try {
|
|
688
|
-
const screenshot = await this.page.screenshot({
|
|
689
|
-
type: 'png',
|
|
690
|
-
fullPage: false // Just viewport for performance
|
|
691
|
-
});
|
|
692
|
-
// Return base64 data URL for embedding
|
|
693
|
-
return `data:image/png;base64,${screenshot.toString('base64')}`;
|
|
694
|
-
}
|
|
695
|
-
catch {
|
|
696
|
-
return '';
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Calculate test summary
|
|
701
|
-
*/
|
|
702
|
-
calculateSummary(results, e2eResults = []) {
|
|
703
|
-
const smokeTests = results.length;
|
|
704
|
-
const e2eTests = e2eResults.length;
|
|
705
|
-
const total = smokeTests + e2eTests;
|
|
706
|
-
const smokePassed = results.filter(r => r.success).length;
|
|
707
|
-
const e2ePassed = e2eResults.filter(r => r.success).length;
|
|
708
|
-
const passed = smokePassed + e2ePassed;
|
|
709
|
-
const failed = total - passed;
|
|
710
|
-
const avgLoadTime = smokeTests > 0 ?
|
|
711
|
-
Math.round(results.reduce((sum, r) => sum + r.loadTime, 0) / smokeTests) : 0;
|
|
712
|
-
const a11yScores = results
|
|
713
|
-
.map(r => r.accessibility?.score)
|
|
714
|
-
.filter((score) => typeof score === 'number');
|
|
715
|
-
const avgA11yScore = a11yScores.length > 0 ?
|
|
716
|
-
Math.round(a11yScores.reduce((sum, score) => sum + score, 0) / a11yScores.length) : 0;
|
|
717
|
-
return {
|
|
718
|
-
total,
|
|
719
|
-
passed,
|
|
720
|
-
failed,
|
|
721
|
-
avgLoadTime,
|
|
722
|
-
avgA11yScore
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Generate JUnit XML fragment
|
|
727
|
-
*/
|
|
728
|
-
generateJUnit(results, e2eResults = []) {
|
|
729
|
-
const summary = this.calculateSummary(results, e2eResults);
|
|
730
|
-
const timestamp = new Date().toISOString();
|
|
731
|
-
let junit = `<?xml version="1.0" encoding="UTF-8"?>
|
|
732
|
-
<testsuites>
|
|
733
|
-
<testsuite name="UI Smoke Tests" tests="${results.length}" failures="${results.filter(r => !r.success).length}" time="${summary.avgLoadTime / 1000}" timestamp="${timestamp}">
|
|
734
|
-
`;
|
|
735
|
-
for (const result of results) {
|
|
736
|
-
const testName = `UI Test: ${result.page}`;
|
|
737
|
-
const time = result.loadTime / 1000;
|
|
738
|
-
junit += ` <testcase name="${this.escapeXml(testName)}" time="${time}">
|
|
739
|
-
`;
|
|
740
|
-
if (!result.success) {
|
|
741
|
-
junit += ` <failure message="${this.escapeXml(result.error || 'Test failed')}">${this.escapeXml(JSON.stringify(result, null, 2))}</failure>
|
|
742
|
-
`;
|
|
743
|
-
}
|
|
744
|
-
junit += ` </testcase>
|
|
745
|
-
`;
|
|
746
|
-
}
|
|
747
|
-
junit += ` </testsuite>
|
|
748
|
-
`;
|
|
749
|
-
// Add E2E test suite
|
|
750
|
-
if (e2eResults.length > 0) {
|
|
751
|
-
const e2eFailed = e2eResults.filter(r => !r.success).length;
|
|
752
|
-
const e2eDuration = e2eResults.reduce((sum, r) => sum + r.duration, 0) / 1000;
|
|
753
|
-
junit += ` <testsuite name="E2E Tests" tests="${e2eResults.length}" failures="${e2eFailed}" time="${e2eDuration}" timestamp="${timestamp}">
|
|
754
|
-
`;
|
|
755
|
-
for (const result of e2eResults) {
|
|
756
|
-
const testName = result.test.name;
|
|
757
|
-
const time = result.duration / 1000;
|
|
758
|
-
junit += ` <testcase name="${this.escapeXml(testName)}" time="${time}">
|
|
759
|
-
`;
|
|
760
|
-
if (!result.success) {
|
|
761
|
-
const failedSteps = result.steps.filter(s => !s.success);
|
|
762
|
-
const failureDetails = failedSteps.map(s => `${s.step.action}: ${s.error}`).join('; ');
|
|
763
|
-
junit += ` <failure message="${this.escapeXml(result.error || 'Test failed')}">${this.escapeXml(failureDetails)}</failure>
|
|
764
|
-
`;
|
|
765
|
-
}
|
|
766
|
-
junit += ` </testcase>
|
|
767
|
-
`;
|
|
768
|
-
}
|
|
769
|
-
junit += ` </testsuite>
|
|
770
|
-
`;
|
|
771
|
-
}
|
|
772
|
-
junit += `</testsuites>`;
|
|
773
|
-
return junit;
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* Escape XML special characters
|
|
777
|
-
*/
|
|
778
|
-
escapeXml(str) {
|
|
779
|
-
return str
|
|
780
|
-
.replace(/&/g, '&')
|
|
781
|
-
.replace(/</g, '<')
|
|
782
|
-
.replace(/>/g, '>')
|
|
783
|
-
.replace(/"/g, '"')
|
|
784
|
-
.replace(/'/g, ''');
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Cleanup browser resources and manage artifacts
|
|
788
|
-
*/
|
|
789
|
-
async cleanup(config) {
|
|
790
|
-
const videoMode = config.artifacts?.video ?? config.target.video;
|
|
791
|
-
const traceMode = config.artifacts?.trace ?? config.target.trace;
|
|
792
|
-
const hasFailures = this.failureCount > 0;
|
|
793
|
-
// Save video artifacts if configured and failures occurred
|
|
794
|
-
if (this.context && videoMode === 'retain-on-failure') {
|
|
795
|
-
if (hasFailures) {
|
|
796
|
-
// Retain videos for failed tests - video is saved automatically by Playwright
|
|
797
|
-
console.log(' 📹 Video artifacts retained on failure');
|
|
798
|
-
}
|
|
799
|
-
else {
|
|
800
|
-
// No failures, video will be automatically deleted by Playwright
|
|
801
|
-
console.log(' ✓ No failures - video artifacts cleaned up');
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
// Save trace artifacts if configured and failures occurred
|
|
805
|
-
if (this.context && (traceMode === 'retain-on-failure' || traceMode === 'on-first-failure')) {
|
|
806
|
-
if (hasFailures && traceMode === 'retain-on-failure') {
|
|
807
|
-
// Stop tracing and save for failed tests
|
|
808
|
-
const tracePath = join(this.artifactDir, 'traces', `trace-${Date.now()}.zip`);
|
|
809
|
-
await this.context.tracing.stop({ path: tracePath });
|
|
810
|
-
console.log(` 📊 Trace retained: ${tracePath}`);
|
|
811
|
-
}
|
|
812
|
-
else if (!hasFailures) {
|
|
813
|
-
// Stop tracing without saving
|
|
814
|
-
await this.context.tracing.stop();
|
|
815
|
-
console.log(' ✓ No failures - trace artifacts cleaned up');
|
|
816
|
-
}
|
|
817
|
-
else if (traceMode === 'on-first-failure') {
|
|
818
|
-
// Already saved on first failure, just stop
|
|
819
|
-
await this.context.tracing.stop();
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
// Close browser resources
|
|
823
|
-
if (this.page) {
|
|
824
|
-
await this.page.close();
|
|
825
|
-
}
|
|
826
|
-
if (this.context) {
|
|
827
|
-
await this.context.close();
|
|
828
|
-
}
|
|
829
|
-
if (this.browser) {
|
|
830
|
-
await this.browser.close();
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
|
-
* Validate UI target configuration
|
|
835
|
-
*/
|
|
836
|
-
static validateConfig(target) {
|
|
837
|
-
const errors = [];
|
|
838
|
-
if (!target.baseUrl) {
|
|
839
|
-
errors.push('UI target requires baseUrl');
|
|
840
|
-
}
|
|
841
|
-
else {
|
|
842
|
-
try {
|
|
843
|
-
new URL(target.baseUrl);
|
|
844
|
-
}
|
|
845
|
-
catch {
|
|
846
|
-
errors.push('UI target baseUrl must be a valid URL');
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
if (target.pages) {
|
|
850
|
-
for (const page of target.pages) {
|
|
851
|
-
try {
|
|
852
|
-
new URL(page, target.baseUrl);
|
|
853
|
-
}
|
|
854
|
-
catch {
|
|
855
|
-
errors.push(`Invalid page URL: ${page}`);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
return {
|
|
860
|
-
valid: errors.length === 0,
|
|
861
|
-
errors
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
}
|