quality-intelligence-engine 2.2.2 → 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.
Files changed (257) hide show
  1. package/.github/workflows/playwright.yml +27 -0
  2. package/.github/workflows/quality-intelligence.yml +45 -0
  3. package/CHANGELOG.md +164 -0
  4. package/REFACTORING_SUMMARY.md +417 -0
  5. package/artifacts/failure-analysis/run-1769595909824-login_with_valid_user.md +25 -0
  6. package/artifacts/failure-analysis/run-1769596445203-login_with_valid_user.md +25 -0
  7. package/artifacts/failure-analysis/run-1769596573162-login_with_valid_user.md +34 -0
  8. package/artifacts/failure-analysis/run-1769596591727-login_with_valid_user.md +25 -0
  9. package/artifacts/failure-analysis/run-1769596600117-login_with_valid_user.md +34 -0
  10. package/artifacts/failure-analysis/run-1769596782107-login_with_valid_user.md +32 -0
  11. package/artifacts/failure-analysis/run-1769596940770-login_with_valid_user.md +28 -0
  12. package/artifacts/failure-analysis/run-1769596960417-login_with_valid_user.md +28 -0
  13. package/artifacts/failure-analysis/run-1769596981303-login_with_valid_user.md +28 -0
  14. package/artifacts/failure-analysis/run-1769597351831-login_with_valid_user.md +21 -0
  15. package/artifacts/failure-analysis/run-1769597486816-login_with_valid_user.md +21 -0
  16. package/artifacts/failure-analysis/run-1769599708378-login_with_valid_user.md +22 -0
  17. package/artifacts/failure-analysis/run-1769600327960-login_with_valid_user.md +22 -0
  18. package/artifacts/failure-analysis/run-1769600596201-saucedemo__login_fails_with_wrong_password_fail.md +22 -0
  19. package/artifacts/failure-analysis/run-1769600682675-saucedemo__login_fails_with_wrong_password_fail.md +22 -0
  20. package/artifacts/failure-analysis/run-1769602090701-saucedemo__add_to_cart_button_is_hidden_fail.md +30 -0
  21. package/artifacts/failure-analysis/run-1769602090701-saucedemo__cart_count_updates_to_2_after_adding_one_item_fail.md +30 -0
  22. package/artifacts/failure-analysis/run-1769602090701-saucedemo__checkout_navigates_to_payment_page_directly_fail.md +30 -0
  23. package/artifacts/failure-analysis/run-1769602090701-saucedemo__inventory_shows_10_products_fail.md +30 -0
  24. package/artifacts/failure-analysis/run-1769602090701-saucedemo__products_sorted_high_to_low_start_with_999_fail.md +30 -0
  25. package/artifacts/failure-history/1769696674298.json +9 -0
  26. package/artifacts/failure-history/1769697768622.json +10 -0
  27. package/artifacts/failure-history/1769697923557.json +10 -0
  28. package/artifacts/failure-history/1769698160213.json +11 -0
  29. package/artifacts/failure-history/1769698353440.json +11 -0
  30. package/artifacts/failure-history/1769698763306.json +11 -0
  31. package/artifacts/failure-history/1769698878947.json +11 -0
  32. package/artifacts/failure-history/1769699909817.json +11 -0
  33. package/artifacts/failure-history/1769703130913.json +11 -0
  34. package/artifacts/failure-history/1769703142113.json +11 -0
  35. package/artifacts/failure-history/1769703158978.json +11 -0
  36. package/artifacts/failure-history/1769703406025.json +11 -0
  37. package/artifacts/failure-history/1769703422720.json +11 -0
  38. package/artifacts/failure-history/1769703518837.json +11 -0
  39. package/artifacts/failure-history/1769703530419.json +11 -0
  40. package/artifacts/failure-history/1769703577078.json +11 -0
  41. package/artifacts/failure-history/1769704067098.json +11 -0
  42. package/artifacts/failure-history/1769704074618.json +11 -0
  43. package/artifacts/failure-history/1769704084948.json +11 -0
  44. package/artifacts/failure-history/1769704091950.json +11 -0
  45. package/artifacts/failure-history/1769704435355.json +11 -0
  46. package/artifacts/failure-history/1769704832871.json +11 -0
  47. package/artifacts/failure-history/1769707051830.json +11 -0
  48. package/artifacts/failure-history/1769739820395.json +11 -0
  49. package/artifacts/failure-history/1769739820400.json +11 -0
  50. package/artifacts/failure-history/1769742422254.json +11 -0
  51. package/artifacts/failure-history/1769743106016.json +11 -0
  52. package/artifacts/failure-history/1769743121857.json +11 -0
  53. package/artifacts/failure-history/1769750875212.json +11 -0
  54. package/artifacts/failure-history/1769750880790.json +11 -0
  55. package/artifacts/failure-history/1769761177923.json +11 -0
  56. package/artifacts/failure-history/1769761191176.json +11 -0
  57. package/bin/qi.ts +37 -0
  58. package/config/agent.config.json +18 -0
  59. package/data/failures/fingerprints/0ded7b45.json +10 -0
  60. package/data/failures/fingerprints/2437666a.json +8 -0
  61. package/data/failures/fingerprints/3746e3b4.json +42 -0
  62. package/data/failures/fingerprints/533e258f.json +10 -0
  63. package/data/failures/fingerprints/56b547d3.json +10 -0
  64. package/data/failures/fingerprints/693661fa.json +10 -0
  65. package/data/failures/fingerprints/789126b1.json +8 -0
  66. package/data/failures/fingerprints/936d995e.json +10 -0
  67. package/data/failures/fingerprints/ba5f6c0a.json +10 -0
  68. package/data/failures/fingerprints/f148a261.json +56 -0
  69. package/data/failures/fingerprints/fa14440f.json +14 -0
  70. package/data/failures/registry.json +57 -0
  71. package/data/flakiness/history.json +71 -0
  72. package/dist/bin/qi.js +0 -0
  73. package/dist/src/intelligence/failure-fingerprinting/failure.classifier.js +5 -1
  74. package/dist/src/intelligence/failure-fingerprinting/failure.tracker.js +5 -0
  75. package/dist/src/playwright/qi.reporter.js +17 -0
  76. package/final.sanity.test.ts +46 -0
  77. package/input/raw-failure.json +36 -0
  78. package/input/raw-pass.json +33 -0
  79. package/output/run-2026-01-28_02-48-38-420/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
  80. package/output/run-2026-01-28_02-48-38-420/TEST_FAILURE-Unknown--/analysis.md +50 -0
  81. package/output/run-2026-01-28_02-48-52-140/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
  82. package/output/run-2026-01-28_02-48-52-140/TEST_FAILURE-Unknown--/analysis.md +50 -0
  83. package/output/run-2026-01-28_03-13-16-302/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
  84. package/output/run-2026-01-28_03-13-16-302/PASS-checkout.spec.ts-API_WARNING/analysis.md +49 -0
  85. package/output/run-2026-01-28_03-13-16-302/PASS-login.spec.ts-FLAKY_PASS/analysis.md +49 -0
  86. package/output/run-2026-01-28_03-13-16-302/TEST_FAILURE-Unknown--/analysis.md +50 -0
  87. package/output/run-2026-01-28_03-29-49-786/BACKEND_API_FAILURE-AuthController.java-/users/{id}/analysis.md +50 -0
  88. package/package.json +4 -10
  89. package/playwright-results/.last-run.json +17 -0
  90. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/error-context.md +31 -0
  91. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/test-failed-1.png +0 -0
  92. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/trace.zip +0 -0
  93. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium/video.webm +0 -0
  94. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/error-context.md +31 -0
  95. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/test-failed-1.png +0 -0
  96. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/trace.zip +0 -0
  97. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--chromium-retry1/video.webm +0 -0
  98. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/error-context.md +31 -0
  99. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/test-failed-1.png +0 -0
  100. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/trace.zip +0 -0
  101. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox/video.webm +0 -0
  102. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/error-context.md +31 -0
  103. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/test-failed-1.png +0 -0
  104. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/trace.zip +0 -0
  105. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--firefox-retry1/video.webm +0 -0
  106. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/error-context.md +31 -0
  107. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/test-failed-1.png +0 -0
  108. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/trace.zip +0 -0
  109. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit/video.webm +0 -0
  110. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/error-context.md +31 -0
  111. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/test-failed-1.png +0 -0
  112. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/trace.zip +0 -0
  113. package/playwright-results/saucedemo-email-like-valid-d5dc6-rname-format-rejected-FAIL--webkit-retry1/video.webm +0 -0
  114. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/error-context.md +112 -0
  115. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/test-failed-1.png +0 -0
  116. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/trace.zip +0 -0
  117. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium/video.webm +0 -0
  118. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/error-context.md +112 -0
  119. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/test-failed-1.png +0 -0
  120. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/trace.zip +0 -0
  121. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--chromium-retry1/video.webm +0 -0
  122. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/error-context.md +112 -0
  123. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/test-failed-1.png +0 -0
  124. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/trace.zip +0 -0
  125. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox/video.webm +0 -0
  126. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/error-context.md +112 -0
  127. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/test-failed-1.png +0 -0
  128. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/trace.zip +0 -0
  129. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--firefox-retry1/video.webm +0 -0
  130. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/error-context.md +112 -0
  131. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/test-failed-1.png +0 -0
  132. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/trace.zip +0 -0
  133. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit/video.webm +0 -0
  134. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/error-context.md +112 -0
  135. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/test-failed-1.png +0 -0
  136. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/trace.zip +0 -0
  137. package/playwright-results/saucedemo-numeric-mismatch-7c4f3-to-cart-button-hidden-FAIL--webkit-retry1/video.webm +0 -0
  138. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/error-context.md +112 -0
  139. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/test-failed-1.png +0 -0
  140. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/trace.zip +0 -0
  141. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium/video.webm +0 -0
  142. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/error-context.md +112 -0
  143. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/test-failed-1.png +0 -0
  144. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/trace.zip +0 -0
  145. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--chromium-retry1/video.webm +0 -0
  146. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/error-context.md +112 -0
  147. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/test-failed-1.png +0 -0
  148. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/trace.zip +0 -0
  149. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox/video.webm +0 -0
  150. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/error-context.md +112 -0
  151. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/test-failed-1.png +0 -0
  152. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/trace.zip +0 -0
  153. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--firefox-retry1/video.webm +0 -0
  154. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/error-context.md +112 -0
  155. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/test-failed-1.png +0 -0
  156. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/trace.zip +0 -0
  157. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit/video.webm +0 -0
  158. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/error-context.md +112 -0
  159. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/test-failed-1.png +0 -0
  160. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/trace.zip +0 -0
  161. package/playwright-results/saucedemo-numeric-mismatch-cd423-entory-count-mismatch-FAIL--webkit-retry1/video.webm +0 -0
  162. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/error-context.md +31 -0
  163. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/test-failed-1.png +0 -0
  164. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/trace.zip +0 -0
  165. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium/video.webm +0 -0
  166. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/error-context.md +31 -0
  167. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/test-failed-1.png +0 -0
  168. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/trace.zip +0 -0
  169. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--chromium-retry1/video.webm +0 -0
  170. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/error-context.md +31 -0
  171. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/test-failed-1.png +0 -0
  172. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/trace.zip +0 -0
  173. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox/video.webm +0 -0
  174. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/error-context.md +31 -0
  175. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/test-failed-1.png +0 -0
  176. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/trace.zip +0 -0
  177. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--firefox-retry1/video.webm +0 -0
  178. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/error-context.md +31 -0
  179. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/test-failed-1.png +0 -0
  180. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/trace.zip +0 -0
  181. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit/video.webm +0 -0
  182. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/error-context.md +31 -0
  183. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/test-failed-1.png +0 -0
  184. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/trace.zip +0 -0
  185. package/playwright-results/saucedemo-numeric-mismatch-f085e-E-unauthorized-access-FAIL--webkit-retry1/video.webm +0 -0
  186. package/playwright.config.ts +42 -0
  187. package/quality-intelligence-engine-2.2.0.tgz +0 -0
  188. package/quality-intelligence-engine-2.2.2.tgz +0 -0
  189. package/sanity.test.ts +33 -0
  190. package/src/adapters/playwright-folder.adapter.ts +85 -0
  191. package/src/adapters/playwright-json.adapter.ts +49 -0
  192. package/src/adapters/playwright.adapter.ts +44 -0
  193. package/src/cli/qi-test.ts +20 -0
  194. package/src/cli/qi.ts +67 -0
  195. package/src/cli/ui/divider.ts +9 -0
  196. package/src/cli/ui/failureLogger.ts +33 -0
  197. package/src/configLoader.ts +70 -0
  198. package/src/console/issue-view.ts +193 -0
  199. package/src/failure-analysis/failure-analyzer.ts +43 -0
  200. package/src/final-run.ts +174 -0
  201. package/src/final-runner.ts +31 -0
  202. package/src/fixtures/networkCollector.ts +27 -0
  203. package/src/history/failure-history.store.ts +23 -0
  204. package/src/history/failure-history.types.ts +12 -0
  205. package/src/history/failure-trend.analyzer.ts +49 -0
  206. package/src/index.ts +10 -0
  207. package/src/integrations/ci/ci-annotator.ts +42 -0
  208. package/src/intelligence/confidence-calibration.engine.ts +41 -0
  209. package/src/intelligence/decision-intelligence/confidence.engine.ts +18 -0
  210. package/src/intelligence/decision-intelligence/decision.config.ts +18 -0
  211. package/src/intelligence/decision-intelligence/decision.engine.ts +30 -0
  212. package/src/intelligence/decision-intelligence/decision.types.ts +15 -0
  213. package/src/intelligence/failure-fingerprinting/failure.classifier.ts +61 -0
  214. package/src/intelligence/failure-fingerprinting/failure.store.ts +42 -0
  215. package/src/intelligence/failure-fingerprinting/failure.tracker.ts +28 -0
  216. package/src/intelligence/failure-fingerprinting/fingerprint.generator.ts +28 -0
  217. package/src/intelligence/failure-fingerprinting/fingerprint.types.ts +14 -0
  218. package/src/intelligence/flakiness-intelligence/flakiness.analyzer.ts +23 -0
  219. package/src/intelligence/flakiness-intelligence/flakiness.fingerprint.ts +14 -0
  220. package/src/intelligence/flakiness-intelligence/flakiness.store.ts +24 -0
  221. package/src/intelligence/flakiness-intelligence/flakiness.tracker.ts +42 -0
  222. package/src/intelligence/flakiness-intelligence/flakiness.types.ts +8 -0
  223. package/src/intelligence/intelligence.pipeline.ts +20 -0
  224. package/src/intelligence/llm-explainer.ts +29 -0
  225. package/src/intelligence/root-cause/rootcause.engine.ts +46 -0
  226. package/src/intelligence/trend-intelligence/trend.engine.ts +42 -0
  227. package/src/markdownWriter.ts +68 -0
  228. package/src/normalizer.ts +31 -0
  229. package/src/passAnalyzer.ts +38 -0
  230. package/src/pipeline/ai.summarizer.ts +39 -0
  231. package/src/pipeline/failure-analysis.pipeline.ts +13 -0
  232. package/src/pipeline/failure-grouping.pipeline.ts +67 -0
  233. package/src/playwright/qi.reporter.ts +26 -0
  234. package/src/reporter.ts +88 -0
  235. package/src/reporters/qi-reporter.js +34 -0
  236. package/src/rules.ts +35 -0
  237. package/src/runManager.ts +21 -0
  238. package/src/runner.ts +12 -0
  239. package/src/runtime/networkCollector.ts +36 -0
  240. package/src/stackParser.ts +22 -0
  241. package/src/test-run.ts +59 -0
  242. package/src/types/analysis-result.ts +16 -0
  243. package/src/types/failure.types.ts +28 -0
  244. package/src/types/playwright-failure.ts +10 -0
  245. package/src/types.ts +102 -0
  246. package/src/utils/artifact.locator.ts +42 -0
  247. package/src/utils/confidence-constants.ts +111 -0
  248. package/src/utils/file-utils.ts +146 -0
  249. package/src/v2/llm/llm-advisor.ts +22 -0
  250. package/src/v2/pipeline/v2-intelligence.pipeline.ts +21 -0
  251. package/src/v2/self-healing/self-healer.ts +60 -0
  252. package/src/v2/trace-intelligence/trace-analyzer.ts +42 -0
  253. package/src/v2-test-run.ts +74 -0
  254. package/tests/fixtures.ts +40 -0
  255. package/tests/saucedemo-login-validation.spec.ts +74 -0
  256. package/tsconfig.json +16 -0
  257. 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,8 @@
1
+ export type TestOutcome = 'PASS' | 'FAIL'
2
+
3
+ export type FlakinessSeverity = 'LOW' | 'MEDIUM' | 'HIGH'
4
+
5
+ export interface FlakinessFingerprintInput {
6
+ testName: string
7
+ pageRoute: string
8
+ }
@@ -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
+ }