quality-intelligence-engine 2.2.3 → 2.2.4
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/.github/workflows/playwright.yml +27 -0
- package/.github/workflows/quality-intelligence.yml +45 -0
- package/CHANGELOG.md +164 -0
- package/REFACTORING_SUMMARY.md +417 -0
- package/artifacts/failure-analysis/run-1769595909824-login_with_valid_user.md +25 -0
- package/artifacts/failure-analysis/run-1769596445203-login_with_valid_user.md +25 -0
- package/artifacts/failure-analysis/run-1769596573162-login_with_valid_user.md +34 -0
- package/artifacts/failure-analysis/run-1769596591727-login_with_valid_user.md +25 -0
- package/artifacts/failure-analysis/run-1769596600117-login_with_valid_user.md +34 -0
- package/artifacts/failure-analysis/run-1769596782107-login_with_valid_user.md +32 -0
- package/artifacts/failure-analysis/run-1769596940770-login_with_valid_user.md +28 -0
- package/artifacts/failure-analysis/run-1769596960417-login_with_valid_user.md +28 -0
- package/artifacts/failure-analysis/run-1769596981303-login_with_valid_user.md +28 -0
- package/artifacts/failure-analysis/run-1769597351831-login_with_valid_user.md +21 -0
- package/artifacts/failure-analysis/run-1769597486816-login_with_valid_user.md +21 -0
- package/artifacts/failure-analysis/run-1769599708378-login_with_valid_user.md +22 -0
- package/artifacts/failure-analysis/run-1769600327960-login_with_valid_user.md +22 -0
- package/artifacts/failure-analysis/run-1769600596201-saucedemo__login_fails_with_wrong_password_fail.md +22 -0
- package/artifacts/failure-analysis/run-1769600682675-saucedemo__login_fails_with_wrong_password_fail.md +22 -0
- package/artifacts/failure-analysis/run-1769602090701-saucedemo__add_to_cart_button_is_hidden_fail.md +30 -0
- package/artifacts/failure-analysis/run-1769602090701-saucedemo__cart_count_updates_to_2_after_adding_one_item_fail.md +30 -0
- package/artifacts/failure-analysis/run-1769602090701-saucedemo__checkout_navigates_to_payment_page_directly_fail.md +30 -0
- package/artifacts/failure-analysis/run-1769602090701-saucedemo__inventory_shows_10_products_fail.md +30 -0
- package/artifacts/failure-analysis/run-1769602090701-saucedemo__products_sorted_high_to_low_start_with_999_fail.md +30 -0
- package/artifacts/failure-history/1769696674298.json +9 -0
- package/artifacts/failure-history/1769697768622.json +10 -0
- package/artifacts/failure-history/1769697923557.json +10 -0
- package/artifacts/failure-history/1769698160213.json +11 -0
- package/artifacts/failure-history/1769698353440.json +11 -0
- package/artifacts/failure-history/1769698763306.json +11 -0
- package/artifacts/failure-history/1769698878947.json +11 -0
- package/artifacts/failure-history/1769699909817.json +11 -0
- package/artifacts/failure-history/1769703130913.json +11 -0
- package/artifacts/failure-history/1769703142113.json +11 -0
- package/artifacts/failure-history/1769703158978.json +11 -0
- package/artifacts/failure-history/1769703406025.json +11 -0
- package/artifacts/failure-history/1769703422720.json +11 -0
- package/artifacts/failure-history/1769703518837.json +11 -0
- package/artifacts/failure-history/1769703530419.json +11 -0
- package/artifacts/failure-history/1769703577078.json +11 -0
- package/artifacts/failure-history/1769704067098.json +11 -0
- package/artifacts/failure-history/1769704074618.json +11 -0
- package/artifacts/failure-history/1769704084948.json +11 -0
- package/artifacts/failure-history/1769704091950.json +11 -0
- package/artifacts/failure-history/1769704435355.json +11 -0
- package/artifacts/failure-history/1769704832871.json +11 -0
- package/artifacts/failure-history/1769707051830.json +11 -0
- package/artifacts/failure-history/1769739820395.json +11 -0
- package/artifacts/failure-history/1769739820400.json +11 -0
- package/artifacts/failure-history/1769742422254.json +11 -0
- package/artifacts/failure-history/1769743106016.json +11 -0
- package/artifacts/failure-history/1769743121857.json +11 -0
- package/artifacts/failure-history/1769750875212.json +11 -0
- package/artifacts/failure-history/1769750880790.json +11 -0
- package/artifacts/failure-history/1769761177923.json +11 -0
- package/artifacts/failure-history/1769761191176.json +11 -0
- package/bin/qi.ts +37 -0
- package/config/agent.config.json +18 -0
- package/data/failures/fingerprints/0ded7b45.json +10 -0
- package/data/failures/fingerprints/2437666a.json +8 -0
- package/data/failures/fingerprints/3746e3b4.json +42 -0
- package/data/failures/fingerprints/533e258f.json +10 -0
- package/data/failures/fingerprints/56b547d3.json +10 -0
- package/data/failures/fingerprints/693661fa.json +10 -0
- package/data/failures/fingerprints/789126b1.json +8 -0
- package/data/failures/fingerprints/936d995e.json +10 -0
- package/data/failures/fingerprints/ba5f6c0a.json +10 -0
- package/data/failures/fingerprints/f148a261.json +56 -0
- package/data/failures/fingerprints/fa14440f.json +14 -0
- package/data/failures/registry.json +57 -0
- package/data/flakiness/history.json +71 -0
- package/dist/bin/qi.js +0 -0
- package/dist/src/intelligence/failure-fingerprinting/failure.classifier.js +5 -1
- package/dist/src/intelligence/failure-fingerprinting/failure.tracker.js +5 -0
- package/dist/src/playwright/qi.reporter.js +17 -0
- package/final.sanity.test.ts +46 -0
- package/input/raw-failure.json +36 -0
- package/input/raw-pass.json +33 -0
- package/output/run-2026-01-28_02-48-38-420/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
- package/output/run-2026-01-28_02-48-38-420/TEST_FAILURE-Unknown--/analysis.md +50 -0
- package/output/run-2026-01-28_02-48-52-140/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
- package/output/run-2026-01-28_02-48-52-140/TEST_FAILURE-Unknown--/analysis.md +50 -0
- package/output/run-2026-01-28_03-13-16-302/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
- package/output/run-2026-01-28_03-13-16-302/PASS-checkout.spec.ts-API_WARNING/analysis.md +49 -0
- package/output/run-2026-01-28_03-13-16-302/PASS-login.spec.ts-FLAKY_PASS/analysis.md +49 -0
- package/output/run-2026-01-28_03-13-16-302/TEST_FAILURE-Unknown--/analysis.md +50 -0
- package/output/run-2026-01-28_03-29-49-786/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
- package/package.json +4 -10
- package/playwright-results/.last-run.json +17 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/error-context.md +31 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/trace.zip +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/video.webm +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/error-context.md +31 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/error-context.md +31 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/trace.zip +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/video.webm +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/error-context.md +31 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/error-context.md +31 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/trace.zip +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/video.webm +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/error-context.md +31 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/error-context.md +112 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/error-context.md +31 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/error-context.md +31 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/error-context.md +31 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/error-context.md +31 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/error-context.md +31 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/video.webm +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/error-context.md +31 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/test-failed-1.png +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/trace.zip +0 -0
- package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/video.webm +0 -0
- package/playwright.config.ts +42 -0
- package/quality-intelligence-engine-2.2.0.tgz +0 -0
- package/quality-intelligence-engine-2.2.2.tgz +0 -0
- package/sanity.test.ts +33 -0
- package/src/adapters/playwright-folder.adapter.ts +85 -0
- package/src/adapters/playwright-json.adapter.ts +49 -0
- package/src/adapters/playwright.adapter.ts +44 -0
- package/src/cli/qi-test.ts +20 -0
- package/src/cli/qi.ts +67 -0
- package/src/cli/ui/divider.ts +9 -0
- package/src/cli/ui/failureLogger.ts +33 -0
- package/src/configLoader.ts +70 -0
- package/src/console/issue-view.ts +193 -0
- package/src/failure-analysis/failure-analyzer.ts +43 -0
- package/src/final-run.ts +174 -0
- package/src/final-runner.ts +31 -0
- package/src/fixtures/networkCollector.ts +27 -0
- package/src/history/failure-history.store.ts +23 -0
- package/src/history/failure-history.types.ts +12 -0
- package/src/history/failure-trend.analyzer.ts +49 -0
- package/src/index.ts +10 -0
- package/src/integrations/ci/ci-annotator.ts +42 -0
- package/src/intelligence/confidence-calibration.engine.ts +41 -0
- package/src/intelligence/decision-intelligence/confidence.engine.ts +18 -0
- package/src/intelligence/decision-intelligence/decision.config.ts +18 -0
- package/src/intelligence/decision-intelligence/decision.engine.ts +30 -0
- package/src/intelligence/decision-intelligence/decision.types.ts +15 -0
- package/src/intelligence/failure-fingerprinting/failure.classifier.ts +61 -0
- package/src/intelligence/failure-fingerprinting/failure.store.ts +42 -0
- package/src/intelligence/failure-fingerprinting/failure.tracker.ts +28 -0
- package/src/intelligence/failure-fingerprinting/fingerprint.generator.ts +28 -0
- package/src/intelligence/failure-fingerprinting/fingerprint.types.ts +14 -0
- package/src/intelligence/flakiness-intelligence/flakiness.analyzer.ts +23 -0
- package/src/intelligence/flakiness-intelligence/flakiness.fingerprint.ts +14 -0
- package/src/intelligence/flakiness-intelligence/flakiness.store.ts +24 -0
- package/src/intelligence/flakiness-intelligence/flakiness.tracker.ts +42 -0
- package/src/intelligence/flakiness-intelligence/flakiness.types.ts +8 -0
- package/src/intelligence/intelligence.pipeline.ts +20 -0
- package/src/intelligence/llm-explainer.ts +29 -0
- package/src/intelligence/root-cause/rootcause.engine.ts +46 -0
- package/src/intelligence/trend-intelligence/trend.engine.ts +42 -0
- package/src/markdownWriter.ts +68 -0
- package/src/normalizer.ts +31 -0
- package/src/passAnalyzer.ts +38 -0
- package/src/pipeline/ai.summarizer.ts +39 -0
- package/src/pipeline/failure-analysis.pipeline.ts +13 -0
- package/src/pipeline/failure-grouping.pipeline.ts +67 -0
- package/src/playwright/qi.reporter.ts +26 -0
- package/src/reporter.ts +88 -0
- package/src/reporters/qi-reporter.js +34 -0
- package/src/rules.ts +35 -0
- package/src/runManager.ts +21 -0
- package/src/runner.ts +12 -0
- package/src/runtime/networkCollector.ts +36 -0
- package/src/stackParser.ts +22 -0
- package/src/test-run.ts +59 -0
- package/src/types/analysis-result.ts +16 -0
- package/src/types/failure.types.ts +28 -0
- package/src/types/playwright-failure.ts +10 -0
- package/src/types.ts +102 -0
- package/src/utils/artifact.locator.ts +42 -0
- package/src/utils/confidence-constants.ts +111 -0
- package/src/utils/file-utils.ts +146 -0
- package/src/v2/llm/llm-advisor.ts +22 -0
- package/src/v2/pipeline/v2-intelligence.pipeline.ts +21 -0
- package/src/v2/self-healing/self-healer.ts +60 -0
- package/src/v2/trace-intelligence/trace-analyzer.ts +42 -0
- package/src/v2-test-run.ts +74 -0
- package/tests/fixtures.ts +40 -0
- package/tests/saucedemo-login-validation.spec.ts +74 -0
- package/tsconfig.json +16 -0
- package/tsconfig.playwright.json +13 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DecisionContext, Decision } from './decision.types'
|
|
2
|
+
import { DECISION_CONFIG } from './decision.config'
|
|
3
|
+
import { computeConfidence } from './confidence.engine'
|
|
4
|
+
|
|
5
|
+
export function decide(ctx: DecisionContext): {
|
|
6
|
+
confidence: number
|
|
7
|
+
decision: Decision
|
|
8
|
+
reasoning: string[]
|
|
9
|
+
} {
|
|
10
|
+
const confidence = computeConfidence(ctx)
|
|
11
|
+
const reasoning: string[] = []
|
|
12
|
+
|
|
13
|
+
if (confidence < DECISION_CONFIG.thresholds.INVESTIGATE) {
|
|
14
|
+
reasoning.push('Confidence below investigate threshold')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (confidence < DECISION_CONFIG.thresholds.SHIP) {
|
|
18
|
+
reasoning.push('Confidence below ship threshold')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let decision: Decision = 'SHIP'
|
|
22
|
+
|
|
23
|
+
if (confidence < DECISION_CONFIG.thresholds.INVESTIGATE) {
|
|
24
|
+
decision = 'HOLD'
|
|
25
|
+
} else if (confidence < DECISION_CONFIG.thresholds.SHIP) {
|
|
26
|
+
decision = 'INVESTIGATE'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { confidence, decision, reasoning }
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Decision = 'SHIP' | 'INVESTIGATE' | 'HOLD'
|
|
2
|
+
|
|
3
|
+
export interface FailureSignal {
|
|
4
|
+
type: 'NEW' | 'KNOWN' | 'REGRESSED'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface FlakinessSignal {
|
|
8
|
+
severity: 'LOW' | 'MEDIUM' | 'HIGH' | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DecisionContext {
|
|
12
|
+
baseConfidence: number
|
|
13
|
+
failures: FailureSignal[]
|
|
14
|
+
flakiness: FlakinessSignal[]
|
|
15
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FailureFingerprintInput } from "./fingerprint.types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Conservative, regression-safe failure classifier.
|
|
5
|
+
* Supports both legacy (1 arg) and new (3 arg) calls.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* --------- OVERLOADS (CRITICAL FIX) --------- */
|
|
9
|
+
export function classifyFailure(
|
|
10
|
+
input: FailureFingerprintInput
|
|
11
|
+
): string;
|
|
12
|
+
|
|
13
|
+
export function classifyFailure(
|
|
14
|
+
input: FailureFingerprintInput,
|
|
15
|
+
runId: string,
|
|
16
|
+
history?: unknown
|
|
17
|
+
): string;
|
|
18
|
+
|
|
19
|
+
/* --------- IMPLEMENTATION --------- */
|
|
20
|
+
export function classifyFailure(
|
|
21
|
+
input: FailureFingerprintInput,
|
|
22
|
+
_runId?: string,
|
|
23
|
+
_history?: unknown
|
|
24
|
+
): string {
|
|
25
|
+
const haystack = JSON.stringify(input).toLowerCase();
|
|
26
|
+
|
|
27
|
+
/* ---------------- NUMERIC ---------------- */
|
|
28
|
+
if (
|
|
29
|
+
haystack.includes("numeric") ||
|
|
30
|
+
haystack.includes("count") ||
|
|
31
|
+
haystack.includes("mismatch") ||
|
|
32
|
+
haystack.includes("total")
|
|
33
|
+
) {
|
|
34
|
+
return "NUMERIC_MISMATCH";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ---------------- AUTH / LOGIN ---------------- */
|
|
38
|
+
if (
|
|
39
|
+
haystack.includes("login") ||
|
|
40
|
+
haystack.includes("username") ||
|
|
41
|
+
haystack.includes("password") ||
|
|
42
|
+
haystack.includes("credential") ||
|
|
43
|
+
haystack.includes("locked") ||
|
|
44
|
+
haystack.includes("required") ||
|
|
45
|
+
haystack.includes("do not match")
|
|
46
|
+
) {
|
|
47
|
+
return "AUTH_FAILURE";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ---------------- SECURITY ---------------- */
|
|
51
|
+
if (
|
|
52
|
+
haystack.includes("sql") ||
|
|
53
|
+
haystack.includes("injection") ||
|
|
54
|
+
haystack.includes("unauthorized") ||
|
|
55
|
+
haystack.includes("forbidden")
|
|
56
|
+
) {
|
|
57
|
+
return "SECURITY";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return "UNKNOWN";
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type FailureRecord = {
|
|
2
|
+
classification: string;
|
|
3
|
+
runId: string;
|
|
4
|
+
testName?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/* ---------- OVERLOADS ---------- */
|
|
8
|
+
export function recordFailure(
|
|
9
|
+
fingerprint: string,
|
|
10
|
+
runId: number,
|
|
11
|
+
testName: string
|
|
12
|
+
): void;
|
|
13
|
+
|
|
14
|
+
export function recordFailure(
|
|
15
|
+
record: FailureRecord
|
|
16
|
+
): void;
|
|
17
|
+
|
|
18
|
+
/* ---------- IMPLEMENTATION ---------- */
|
|
19
|
+
export function recordFailure(
|
|
20
|
+
arg1: string | FailureRecord,
|
|
21
|
+
arg2?: number,
|
|
22
|
+
arg3?: string
|
|
23
|
+
): void {
|
|
24
|
+
if (typeof arg1 === "object") {
|
|
25
|
+
const { classification, runId, testName } = arg1;
|
|
26
|
+
|
|
27
|
+
// 🔒 internal canonical call
|
|
28
|
+
recordFailure(
|
|
29
|
+
classification,
|
|
30
|
+
Number(runId),
|
|
31
|
+
testName ?? "unknown"
|
|
32
|
+
);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fingerprint = arg1;
|
|
37
|
+
const runId = arg2!;
|
|
38
|
+
const testName = arg3!;
|
|
39
|
+
|
|
40
|
+
// 👉 existing storage logic stays EXACTLY as-is below
|
|
41
|
+
// saveFailure(fingerprint, runId, testName);
|
|
42
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { classifyFailure } from "./failure.classifier";
|
|
2
|
+
import { FailureFingerprintInput } from "./fingerprint.types";
|
|
3
|
+
import { recordFailure } from "./failure.store";
|
|
4
|
+
|
|
5
|
+
export function trackFailure(
|
|
6
|
+
rawFailure: unknown,
|
|
7
|
+
runId: string
|
|
8
|
+
): string {
|
|
9
|
+
/**
|
|
10
|
+
* Treat fingerprint input as OPAQUE.
|
|
11
|
+
* Do NOT assume fields. This avoids regressions.
|
|
12
|
+
*/
|
|
13
|
+
const fingerprintInput = rawFailure as FailureFingerprintInput;
|
|
14
|
+
|
|
15
|
+
// Backward + forward compatible call
|
|
16
|
+
const classification = classifyFailure(
|
|
17
|
+
fingerprintInput,
|
|
18
|
+
runId,
|
|
19
|
+
undefined
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
recordFailure({
|
|
23
|
+
runId,
|
|
24
|
+
classification
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return classification;
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { FailureFingerprintInput } from './fingerprint.types'
|
|
3
|
+
|
|
4
|
+
function normalize(value: string): string {
|
|
5
|
+
return value
|
|
6
|
+
.toLowerCase()
|
|
7
|
+
.replace(/\d+/g, '') // remove dynamic numbers
|
|
8
|
+
.replace(/\s+/g, ' ') // normalize whitespace
|
|
9
|
+
.trim()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function generateFailureFingerprint(
|
|
13
|
+
input: FailureFingerprintInput
|
|
14
|
+
): string {
|
|
15
|
+
const canonical = JSON.stringify({
|
|
16
|
+
failureType: input.failureType,
|
|
17
|
+
pageRoute: normalize(input.pageRoute),
|
|
18
|
+
primaryLocator: normalize(input.primaryLocator),
|
|
19
|
+
assertionIntent: normalize(input.assertionIntent),
|
|
20
|
+
errorClass: normalize(input.errorClass)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return crypto
|
|
24
|
+
.createHash('sha1')
|
|
25
|
+
.update(canonical)
|
|
26
|
+
.digest('hex')
|
|
27
|
+
.slice(0, 8)
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type FailureType =
|
|
2
|
+
| 'timeout'
|
|
3
|
+
| 'assertion'
|
|
4
|
+
| 'locator'
|
|
5
|
+
| 'navigation'
|
|
6
|
+
| 'unknown'
|
|
7
|
+
|
|
8
|
+
export interface FailureFingerprintInput {
|
|
9
|
+
failureType: FailureType
|
|
10
|
+
pageRoute: string
|
|
11
|
+
primaryLocator: string
|
|
12
|
+
assertionIntent: string
|
|
13
|
+
errorClass: string
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { TestOutcome, FlakinessSeverity } from './flakiness.types'
|
|
2
|
+
|
|
3
|
+
export function analyzeFlakiness(
|
|
4
|
+
outcomes: TestOutcome[]
|
|
5
|
+
): FlakinessSeverity | null {
|
|
6
|
+
if (outcomes.length < 4) return null
|
|
7
|
+
|
|
8
|
+
let oscillations = 0
|
|
9
|
+
|
|
10
|
+
for (let i = 1; i < outcomes.length; i++) {
|
|
11
|
+
if (outcomes[i] !== outcomes[i - 1]) {
|
|
12
|
+
oscillations++
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const rate = oscillations / (outcomes.length - 1)
|
|
17
|
+
|
|
18
|
+
if (rate > 0.6) return 'HIGH'
|
|
19
|
+
if (rate > 0.3) return 'MEDIUM'
|
|
20
|
+
if (rate > 0.1) return 'LOW'
|
|
21
|
+
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
|
|
3
|
+
function normalize(value: string | undefined): string {
|
|
4
|
+
if (!value) return 'unknown'
|
|
5
|
+
return value.toLowerCase().trim()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function generateFlakinessFingerprint(
|
|
9
|
+
testName: string | undefined,
|
|
10
|
+
pageRoute: string | undefined
|
|
11
|
+
): string {
|
|
12
|
+
const key = `${normalize(testName)}::${normalize(pageRoute)}`
|
|
13
|
+
return crypto.createHash('md5').update(key).digest('hex')
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
const HISTORY_PATH = path.resolve('data/flakiness/history.json')
|
|
5
|
+
|
|
6
|
+
export type FlakinessHistory = Record<string, ('PASS' | 'FAIL')[]>
|
|
7
|
+
|
|
8
|
+
export function readFlakinessHistory(): FlakinessHistory {
|
|
9
|
+
if (!fs.existsSync(HISTORY_PATH)) {
|
|
10
|
+
return {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const raw = fs.readFileSync(HISTORY_PATH, 'utf-8').trim()
|
|
14
|
+
if (!raw) {
|
|
15
|
+
return {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return JSON.parse(raw)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function writeFlakinessHistory(history: FlakinessHistory): void {
|
|
22
|
+
fs.mkdirSync(path.dirname(HISTORY_PATH), { recursive: true })
|
|
23
|
+
fs.writeFileSync(HISTORY_PATH, JSON.stringify(history, null, 2))
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { generateFlakinessFingerprint } from './flakiness.fingerprint'
|
|
2
|
+
import {
|
|
3
|
+
readFlakinessHistory,
|
|
4
|
+
writeFlakinessHistory,
|
|
5
|
+
FlakinessHistory
|
|
6
|
+
} from './flakiness.store'
|
|
7
|
+
|
|
8
|
+
export type FlakinessSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | null
|
|
9
|
+
|
|
10
|
+
export function trackFlakiness(
|
|
11
|
+
testName: string | undefined,
|
|
12
|
+
pageRoute: string | undefined,
|
|
13
|
+
outcome: 'PASS' | 'FAIL'
|
|
14
|
+
) {
|
|
15
|
+
const fingerprint = generateFlakinessFingerprint(testName, pageRoute)
|
|
16
|
+
const history: FlakinessHistory = readFlakinessHistory()
|
|
17
|
+
|
|
18
|
+
if (!history[fingerprint]) {
|
|
19
|
+
history[fingerprint] = []
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
history[fingerprint].push(outcome)
|
|
23
|
+
writeFlakinessHistory(history)
|
|
24
|
+
|
|
25
|
+
const recent = history[fingerprint].slice(-6)
|
|
26
|
+
|
|
27
|
+
const oscillations = recent.filter(
|
|
28
|
+
(value: 'PASS' | 'FAIL', index: number, arr: ('PASS' | 'FAIL')[]) =>
|
|
29
|
+
index > 0 && value !== arr[index - 1]
|
|
30
|
+
).length
|
|
31
|
+
|
|
32
|
+
let severity: FlakinessSeverity = null
|
|
33
|
+
if (oscillations >= 3) severity = 'HIGH'
|
|
34
|
+
else if (oscillations === 2) severity = 'MEDIUM'
|
|
35
|
+
else if (oscillations === 1) severity = 'LOW'
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
fingerprint,
|
|
39
|
+
history: recent,
|
|
40
|
+
severity
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/intelligence/intelligence.pipeline.ts
|
|
2
|
+
|
|
3
|
+
import { FailureGroupSummary } from "../pipeline/failure-grouping.pipeline";
|
|
4
|
+
import { explainFailureWithLLM } from "./llm-explainer";
|
|
5
|
+
import { explainConfidence } from "./confidence-calibration.engine";
|
|
6
|
+
|
|
7
|
+
export async function enrichWithIntelligence(
|
|
8
|
+
summary: FailureGroupSummary
|
|
9
|
+
) {
|
|
10
|
+
const confidence = explainConfidence(summary);
|
|
11
|
+
const explanation = await explainFailureWithLLM(summary);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
...summary,
|
|
15
|
+
intelligence: {
|
|
16
|
+
confidence,
|
|
17
|
+
explanation
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/intelligence/llm-explainer.ts
|
|
2
|
+
|
|
3
|
+
import { FailureGroupSummary } from "../pipeline/failure-grouping.pipeline";
|
|
4
|
+
|
|
5
|
+
export interface LLMExplanation {
|
|
6
|
+
summary: string;
|
|
7
|
+
reasoning: string[];
|
|
8
|
+
recommendedAction: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function explainFailureWithLLM(
|
|
12
|
+
summary: FailureGroupSummary
|
|
13
|
+
): Promise<LLMExplanation> {
|
|
14
|
+
const dominantCount =
|
|
15
|
+
summary.groupedCounts[summary.dominantCategory] ?? 0;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
summary: `Primary failure detected: ${summary.dominantCategory}`,
|
|
19
|
+
reasoning: [
|
|
20
|
+
`Failure occurred ${dominantCount} times`,
|
|
21
|
+
`Confidence is ${summary.confidence}`
|
|
22
|
+
],
|
|
23
|
+
recommendedAction: [
|
|
24
|
+
"Inspect backend API response",
|
|
25
|
+
"Verify numeric calculations",
|
|
26
|
+
"Check recent code changes"
|
|
27
|
+
]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type RootCauseType = 'APPLICATION_BUG' | 'TEST_BUG' | 'FLAKY'
|
|
2
|
+
|
|
3
|
+
export interface RootCauseInput {
|
|
4
|
+
errorMessage: string
|
|
5
|
+
stack?: string
|
|
6
|
+
retryCount: number
|
|
7
|
+
isAssertionError: boolean
|
|
8
|
+
hasHistory: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RootCauseResult {
|
|
12
|
+
type: RootCauseType
|
|
13
|
+
reason: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function analyzeRootCause(
|
|
17
|
+
input: RootCauseInput
|
|
18
|
+
): RootCauseResult {
|
|
19
|
+
const {
|
|
20
|
+
retryCount,
|
|
21
|
+
isAssertionError,
|
|
22
|
+
hasHistory
|
|
23
|
+
} = input
|
|
24
|
+
|
|
25
|
+
// 🔴 App bug: not assertion + no retries
|
|
26
|
+
if (!isAssertionError && retryCount === 0) {
|
|
27
|
+
return {
|
|
28
|
+
type: 'APPLICATION_BUG',
|
|
29
|
+
reason: 'Failure without retries → application issue likely'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 🟡 Flaky: retries + history
|
|
34
|
+
if (retryCount > 0 && hasHistory) {
|
|
35
|
+
return {
|
|
36
|
+
type: 'FLAKY',
|
|
37
|
+
reason: 'Failure resolved on retry → flaky behavior detected'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 🟢 Default: test bug
|
|
42
|
+
return {
|
|
43
|
+
type: 'TEST_BUG',
|
|
44
|
+
reason: 'Assertion failure with stable history → test logic issue'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type Outcome = 'PASS' | 'FAIL'
|
|
2
|
+
|
|
3
|
+
export type TrendSummary = {
|
|
4
|
+
totalRuns: number
|
|
5
|
+
passRate: number
|
|
6
|
+
failRate: number
|
|
7
|
+
flakiness: 'LOW' | 'MEDIUM' | 'HIGH'
|
|
8
|
+
stability: 'STABLE' | 'UNSTABLE'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function analyzeTrend(
|
|
12
|
+
history: Outcome[],
|
|
13
|
+
windowSize = 10
|
|
14
|
+
): TrendSummary {
|
|
15
|
+
const recent = history.slice(-windowSize)
|
|
16
|
+
|
|
17
|
+
const totalRuns = recent.length
|
|
18
|
+
const passCount = recent.filter(r => r === 'PASS').length
|
|
19
|
+
const failCount = recent.filter(r => r === 'FAIL').length
|
|
20
|
+
|
|
21
|
+
const passRate = totalRuns ? passCount / totalRuns : 0
|
|
22
|
+
const failRate = totalRuns ? failCount / totalRuns : 0
|
|
23
|
+
|
|
24
|
+
const oscillations = recent.filter(
|
|
25
|
+
(v, i, arr) => i > 0 && v !== arr[i - 1]
|
|
26
|
+
).length
|
|
27
|
+
|
|
28
|
+
let flakiness: 'LOW' | 'MEDIUM' | 'HIGH' = 'LOW'
|
|
29
|
+
if (oscillations >= 4) flakiness = 'HIGH'
|
|
30
|
+
else if (oscillations >= 2) flakiness = 'MEDIUM'
|
|
31
|
+
|
|
32
|
+
const stability =
|
|
33
|
+
flakiness === 'HIGH' || failRate > 0.4 ? 'UNSTABLE' : 'STABLE'
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
totalRuns,
|
|
37
|
+
passRate,
|
|
38
|
+
failRate,
|
|
39
|
+
flakiness,
|
|
40
|
+
stability
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { AnalysisResult } from './types';
|
|
4
|
+
|
|
5
|
+
export function writeAnalysisMarkdown(
|
|
6
|
+
baseDir: string,
|
|
7
|
+
issueId: string,
|
|
8
|
+
result: AnalysisResult,
|
|
9
|
+
occurrences: number
|
|
10
|
+
) {
|
|
11
|
+
const issueDir = path.join(baseDir, issueId);
|
|
12
|
+
fs.mkdirSync(issueDir, { recursive: true });
|
|
13
|
+
|
|
14
|
+
const mdPath = path.join(issueDir, 'analysis.md');
|
|
15
|
+
|
|
16
|
+
const content = `
|
|
17
|
+
# ❌ ${result.category}
|
|
18
|
+
|
|
19
|
+
**Occurrences** : ${occurrences}
|
|
20
|
+
**Confidence** : ${result.confidence}
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📍 Failure Location
|
|
25
|
+
- **File** : ${result.location.file}
|
|
26
|
+
- **Line** : ${result.location.line}
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🌐 Failed API Call
|
|
31
|
+
- **Method** : ${result.failedApi.method}
|
|
32
|
+
- **Endpoint** : ${result.failedApi.endpoint}
|
|
33
|
+
- **Status** : ${result.failedApi.status}
|
|
34
|
+
- **Response** : ${result.failedApi.response ?? '-'}
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🧠 Root Cause
|
|
39
|
+
${result.rootCause}
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🔁 Retry Guidance
|
|
44
|
+
${result.retryGuidance}
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## ▶️ Reproduction Steps
|
|
49
|
+
${result.reproductionSteps.map((s, i) => `${i + 1}. ${s}`).join('\n')}
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## ✅ Expected Result
|
|
54
|
+
${result.expected}
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## ❌ Actual Result
|
|
59
|
+
${result.actual}
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🛠 Suggested Actions
|
|
64
|
+
${result.suggestedActions.map(a => `- ${a}`).join('\n')}
|
|
65
|
+
`.trim();
|
|
66
|
+
|
|
67
|
+
fs.writeFileSync(mdPath, content, 'utf-8');
|
|
68
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* src/normalizer.ts */
|
|
2
|
+
|
|
3
|
+
export interface RawFailureItem {
|
|
4
|
+
message?: string;
|
|
5
|
+
api?: {
|
|
6
|
+
response?: unknown;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface NormalizedFailure {
|
|
11
|
+
rootCause?: string;
|
|
12
|
+
actual?: string;
|
|
13
|
+
response?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Public normalizer API
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeRawInput(
|
|
20
|
+
items: unknown[]
|
|
21
|
+
): NormalizedFailure[] {
|
|
22
|
+
return items.map((item): NormalizedFailure => {
|
|
23
|
+
const safeItem = item as RawFailureItem;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
rootCause: safeItem.message,
|
|
27
|
+
actual: safeItem.message,
|
|
28
|
+
response: safeItem.api?.response
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { PassRisk } from './types';
|
|
4
|
+
|
|
5
|
+
export function analyzePasses(): PassRisk[] {
|
|
6
|
+
const passPath = path.join(process.cwd(), 'input/raw-pass.json');
|
|
7
|
+
const items = JSON.parse(fs.readFileSync(passPath, 'utf-8'));
|
|
8
|
+
|
|
9
|
+
const risks: PassRisk[] = [];
|
|
10
|
+
|
|
11
|
+
items.forEach((item: any) => {
|
|
12
|
+
// Flaky pass
|
|
13
|
+
if (item.retries > 0) {
|
|
14
|
+
risks.push({
|
|
15
|
+
test: item.test,
|
|
16
|
+
riskType: 'FLAKY_PASS',
|
|
17
|
+
location: { file: item.test, line: -1 },
|
|
18
|
+
reason: 'Test passed after retry',
|
|
19
|
+
fix: 'Stabilize locator or add explicit waits'
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// API warnings during pass
|
|
24
|
+
item.apiEvents?.forEach((api: any) => {
|
|
25
|
+
if (api.status >= 400 && api.recovered) {
|
|
26
|
+
risks.push({
|
|
27
|
+
test: item.test,
|
|
28
|
+
riskType: 'API_WARNING',
|
|
29
|
+
location: { file: item.test, line: -1 },
|
|
30
|
+
reason: `${api.method} ${api.endpoint} failed with ${api.status} but recovered`,
|
|
31
|
+
fix: 'Investigate backend instability or add retry with backoff'
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return risks;
|
|
38
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type SummaryInput = {
|
|
2
|
+
testName: string
|
|
3
|
+
errorMessage: string
|
|
4
|
+
rootCause: string
|
|
5
|
+
confidence: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function summarizeWithAI(
|
|
9
|
+
input: SummaryInput
|
|
10
|
+
): Promise<string> {
|
|
11
|
+
// 🔒 Safety first — AI is OPTIONAL
|
|
12
|
+
if (!process.env.AI_ENABLED) {
|
|
13
|
+
return deterministicFallback(input)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// 🔹 Placeholder for real LLM call
|
|
18
|
+
// 🔹 Intentionally simple & replaceable
|
|
19
|
+
return `
|
|
20
|
+
This failure occurred in the test "${input.testName}".
|
|
21
|
+
Based on the observed error and system heuristics, the root cause is classified as:
|
|
22
|
+
"${input.rootCause}"
|
|
23
|
+
|
|
24
|
+
The confidence score (${(input.confidence / 100).toFixed(2)}) indicates a stable and repeatable issue.
|
|
25
|
+
Recommended action is to follow the suggested remediation rather than retrying the test.
|
|
26
|
+
`.trim()
|
|
27
|
+
} catch {
|
|
28
|
+
// 🔒 Never crash pipeline because of AI
|
|
29
|
+
return deterministicFallback(input)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deterministicFallback(input: SummaryInput): string {
|
|
34
|
+
return `
|
|
35
|
+
Failure in "${input.testName}" was analyzed deterministically.
|
|
36
|
+
Root cause identified as: ${input.rootCause}.
|
|
37
|
+
Confidence level: ${(input.confidence / 100).toFixed(2)}.
|
|
38
|
+
`.trim()
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/pipeline/failure-analysis.pipeline.ts
|
|
2
|
+
|
|
3
|
+
import { FailureAnalysisResult } from "../types/failure.types";
|
|
4
|
+
import { parsePlaywrightFailureFolders } from "../adapters/playwright-folder.adapter";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Phase 1 – Playwright Failure Intake (Folder-based)
|
|
8
|
+
*/
|
|
9
|
+
export async function runFailureAnalysisFromPlaywright(
|
|
10
|
+
playwrightResultsDir: string
|
|
11
|
+
): Promise<FailureAnalysisResult[]> {
|
|
12
|
+
return parsePlaywrightFailureFolders(playwrightResultsDir);
|
|
13
|
+
}
|