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