@sun-asterisk/sungen 3.2.32 → 3.2.34-beta.1

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 (503) hide show
  1. package/dist/cli/commands/audit.d.ts +5 -0
  2. package/dist/cli/commands/audit.d.ts.map +1 -1
  3. package/dist/cli/commands/audit.js +64 -11
  4. package/dist/cli/commands/audit.js.map +1 -1
  5. package/dist/cli/commands/capability.d.ts.map +1 -1
  6. package/dist/cli/commands/capability.js +40 -18
  7. package/dist/cli/commands/capability.js.map +1 -1
  8. package/dist/cli/commands/changed.d.ts.map +1 -1
  9. package/dist/cli/commands/changed.js +2 -3
  10. package/dist/cli/commands/changed.js.map +1 -1
  11. package/dist/cli/commands/dashboard.d.ts.map +1 -1
  12. package/dist/cli/commands/dashboard.js +2 -28
  13. package/dist/cli/commands/dashboard.js.map +1 -1
  14. package/dist/cli/commands/defect.d.ts +7 -0
  15. package/dist/cli/commands/defect.d.ts.map +1 -0
  16. package/dist/cli/commands/defect.js +68 -0
  17. package/dist/cli/commands/defect.js.map +1 -0
  18. package/dist/cli/commands/delivery.d.ts.map +1 -1
  19. package/dist/cli/commands/delivery.js +113 -59
  20. package/dist/cli/commands/delivery.js.map +1 -1
  21. package/dist/cli/commands/generate.d.ts.map +1 -1
  22. package/dist/cli/commands/generate.js +14 -7
  23. package/dist/cli/commands/generate.js.map +1 -1
  24. package/dist/cli/commands/results-merge.d.ts +9 -0
  25. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  26. package/dist/cli/commands/results-merge.js +9 -12
  27. package/dist/cli/commands/results-merge.js.map +1 -1
  28. package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
  29. package/dist/cli/commands/results-sanitize.js +2 -13
  30. package/dist/cli/commands/results-sanitize.js.map +1 -1
  31. package/dist/cli/commands/select.d.ts +12 -0
  32. package/dist/cli/commands/select.d.ts.map +1 -0
  33. package/dist/cli/commands/select.js +122 -0
  34. package/dist/cli/commands/select.js.map +1 -0
  35. package/dist/cli/commands/triage.d.ts +7 -0
  36. package/dist/cli/commands/triage.d.ts.map +1 -0
  37. package/dist/cli/commands/triage.js +98 -0
  38. package/dist/cli/commands/triage.js.map +1 -0
  39. package/dist/cli/commands/update.d.ts.map +1 -1
  40. package/dist/cli/commands/update.js +3 -0
  41. package/dist/cli/commands/update.js.map +1 -1
  42. package/dist/cli/commands/viewpoint.d.ts +7 -0
  43. package/dist/cli/commands/viewpoint.d.ts.map +1 -0
  44. package/dist/cli/commands/viewpoint.js +70 -0
  45. package/dist/cli/commands/viewpoint.js.map +1 -0
  46. package/dist/cli/index.js +13 -2
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  49. package/dist/dashboard/snapshot-builder.js +6 -10
  50. package/dist/dashboard/snapshot-builder.js.map +1 -1
  51. package/dist/dashboard/templates/index.html +1 -1
  52. package/dist/dashboard/types.d.ts +1 -0
  53. package/dist/dashboard/types.d.ts.map +1 -1
  54. package/dist/exporters/csv-exporter.d.ts +4 -0
  55. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  56. package/dist/exporters/csv-exporter.js +12 -11
  57. package/dist/exporters/csv-exporter.js.map +1 -1
  58. package/dist/exporters/feature-parser.d.ts +29 -0
  59. package/dist/exporters/feature-parser.d.ts.map +1 -1
  60. package/dist/exporters/feature-parser.js +44 -1
  61. package/dist/exporters/feature-parser.js.map +1 -1
  62. package/dist/exporters/json-exporter.d.ts.map +1 -1
  63. package/dist/exporters/json-exporter.js +7 -0
  64. package/dist/exporters/json-exporter.js.map +1 -1
  65. package/dist/exporters/locale-variants.d.ts +7 -1
  66. package/dist/exporters/locale-variants.d.ts.map +1 -1
  67. package/dist/exporters/locale-variants.js +23 -16
  68. package/dist/exporters/locale-variants.js.map +1 -1
  69. package/dist/exporters/manual-hybrid-oracle.d.ts +54 -0
  70. package/dist/exporters/manual-hybrid-oracle.d.ts.map +1 -0
  71. package/dist/exporters/manual-hybrid-oracle.js +114 -0
  72. package/dist/exporters/manual-hybrid-oracle.js.map +1 -0
  73. package/dist/exporters/matrix/build.d.ts.map +1 -1
  74. package/dist/exporters/matrix/build.js +68 -19
  75. package/dist/exporters/matrix/build.js.map +1 -1
  76. package/dist/exporters/matrix/carry-manual-results.d.ts +89 -0
  77. package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
  78. package/dist/exporters/matrix/carry-manual-results.js +302 -0
  79. package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
  80. package/dist/exporters/matrix/export.d.ts +9 -1
  81. package/dist/exporters/matrix/export.d.ts.map +1 -1
  82. package/dist/exporters/matrix/export.js +21 -3
  83. package/dist/exporters/matrix/export.js.map +1 -1
  84. package/dist/exporters/matrix/gates.js +6 -0
  85. package/dist/exporters/matrix/gates.js.map +1 -1
  86. package/dist/exporters/matrix/render-csv.js +1 -1
  87. package/dist/exporters/matrix/render-csv.js.map +1 -1
  88. package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -1
  89. package/dist/exporters/matrix/render-testcase-sheet.js +10 -4
  90. package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -1
  91. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  92. package/dist/exporters/matrix/render-xlsx.js +3 -0
  93. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  94. package/dist/exporters/matrix/types.d.ts +13 -1
  95. package/dist/exporters/matrix/types.d.ts.map +1 -1
  96. package/dist/exporters/matrix/types.js.map +1 -1
  97. package/dist/exporters/matrix/wording.d.ts +8 -1
  98. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  99. package/dist/exporters/matrix/wording.js +37 -2
  100. package/dist/exporters/matrix/wording.js.map +1 -1
  101. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  102. package/dist/exporters/playwright-report-parser.js +1 -0
  103. package/dist/exporters/playwright-report-parser.js.map +1 -1
  104. package/dist/exporters/run-environment.d.ts +32 -0
  105. package/dist/exporters/run-environment.d.ts.map +1 -0
  106. package/dist/exporters/run-environment.js +125 -0
  107. package/dist/exporters/run-environment.js.map +1 -0
  108. package/dist/exporters/scenario-merger.d.ts +18 -0
  109. package/dist/exporters/scenario-merger.d.ts.map +1 -1
  110. package/dist/exporters/scenario-merger.js +49 -3
  111. package/dist/exporters/scenario-merger.js.map +1 -1
  112. package/dist/exporters/test-data-resolver.d.ts +0 -4
  113. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  114. package/dist/exporters/test-data-resolver.js +0 -7
  115. package/dist/exporters/test-data-resolver.js.map +1 -1
  116. package/dist/exporters/types.d.ts +5 -0
  117. package/dist/exporters/types.d.ts.map +1 -1
  118. package/dist/exporters/xlsx-exporter.d.ts +2 -0
  119. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  120. package/dist/exporters/xlsx-exporter.js +7 -2
  121. package/dist/exporters/xlsx-exporter.js.map +1 -1
  122. package/dist/generators/gherkin-parser/index.d.ts +11 -4
  123. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  124. package/dist/generators/gherkin-parser/index.js +49 -19
  125. package/dist/generators/gherkin-parser/index.js.map +1 -1
  126. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +4 -0
  127. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  128. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  129. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  130. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  131. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
  132. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
  133. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
  134. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  135. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  136. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
  137. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
  138. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
  139. package/dist/generators/test-generator/code-generator.d.ts +2 -1
  140. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  141. package/dist/generators/test-generator/code-generator.js +90 -5
  142. package/dist/generators/test-generator/code-generator.js.map +1 -1
  143. package/dist/generators/test-generator/diagnostics.d.ts +36 -5
  144. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  145. package/dist/generators/test-generator/diagnostics.js +35 -4
  146. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  147. package/dist/generators/test-generator/patterns/index.d.ts +12 -2
  148. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  149. package/dist/generators/test-generator/patterns/index.js +15 -9
  150. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  151. package/dist/generators/test-generator/patterns/types.d.ts +15 -0
  152. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  153. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
  154. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
  155. package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
  156. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
  157. package/dist/generators/test-generator/step-mapper.d.ts +46 -0
  158. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  159. package/dist/generators/test-generator/step-mapper.js +84 -9
  160. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  161. package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
  162. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  163. package/dist/generators/test-generator/utils/data-resolver.js +39 -8
  164. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  165. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
  166. package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
  167. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
  168. package/dist/generators/test-generator/utils/selector-resolver.d.ts +37 -4
  169. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  170. package/dist/generators/test-generator/utils/selector-resolver.js +212 -79
  171. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  172. package/dist/harness/audit.d.ts +3 -0
  173. package/dist/harness/audit.d.ts.map +1 -1
  174. package/dist/harness/audit.js +60 -19
  175. package/dist/harness/audit.js.map +1 -1
  176. package/dist/harness/capability.d.ts +7 -1
  177. package/dist/harness/capability.d.ts.map +1 -1
  178. package/dist/harness/capability.js +15 -12
  179. package/dist/harness/capability.js.map +1 -1
  180. package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
  181. package/dist/harness/config-file.d.ts +17 -0
  182. package/dist/harness/config-file.d.ts.map +1 -0
  183. package/dist/harness/config-file.js +100 -0
  184. package/dist/harness/config-file.js.map +1 -0
  185. package/dist/harness/counted-assertions.d.ts +2 -0
  186. package/dist/harness/counted-assertions.d.ts.map +1 -0
  187. package/dist/harness/counted-assertions.js +38 -0
  188. package/dist/harness/counted-assertions.js.map +1 -0
  189. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  190. package/dist/harness/data-driven-lint.js +69 -27
  191. package/dist/harness/data-driven-lint.js.map +1 -1
  192. package/dist/harness/defect-draft.d.ts +14 -0
  193. package/dist/harness/defect-draft.d.ts.map +1 -0
  194. package/dist/harness/defect-draft.js +143 -0
  195. package/dist/harness/defect-draft.js.map +1 -0
  196. package/dist/harness/defects.d.ts +24 -0
  197. package/dist/harness/defects.d.ts.map +1 -0
  198. package/dist/harness/defects.js +118 -0
  199. package/dist/harness/defects.js.map +1 -0
  200. package/dist/harness/depth-lint.d.ts.map +1 -1
  201. package/dist/harness/depth-lint.js +2 -1
  202. package/dist/harness/depth-lint.js.map +1 -1
  203. package/dist/harness/flow-check.d.ts.map +1 -1
  204. package/dist/harness/flow-check.js +6 -2
  205. package/dist/harness/flow-check.js.map +1 -1
  206. package/dist/harness/keyword-match.d.ts +59 -0
  207. package/dist/harness/keyword-match.d.ts.map +1 -0
  208. package/dist/harness/keyword-match.js +86 -0
  209. package/dist/harness/keyword-match.js.map +1 -0
  210. package/dist/harness/manifest.d.ts.map +1 -1
  211. package/dist/harness/manifest.js +2 -2
  212. package/dist/harness/manifest.js.map +1 -1
  213. package/dist/harness/next-step.d.ts +2 -4
  214. package/dist/harness/next-step.d.ts.map +1 -1
  215. package/dist/harness/next-step.js +50 -5
  216. package/dist/harness/next-step.js.map +1 -1
  217. package/dist/harness/parse.d.ts +45 -0
  218. package/dist/harness/parse.d.ts.map +1 -1
  219. package/dist/harness/parse.js +142 -24
  220. package/dist/harness/parse.js.map +1 -1
  221. package/dist/harness/perf.d.ts +7 -1
  222. package/dist/harness/perf.d.ts.map +1 -1
  223. package/dist/harness/perf.js +13 -10
  224. package/dist/harness/perf.js.map +1 -1
  225. package/dist/harness/quality-gates.d.ts +9 -2
  226. package/dist/harness/quality-gates.d.ts.map +1 -1
  227. package/dist/harness/quality-gates.js +22 -8
  228. package/dist/harness/quality-gates.js.map +1 -1
  229. package/dist/harness/query-catalog.js +0 -0
  230. package/dist/harness/query-catalog.js.map +1 -1
  231. package/dist/harness/read-text.d.ts +21 -0
  232. package/dist/harness/read-text.d.ts.map +1 -1
  233. package/dist/harness/read-text.js +32 -1
  234. package/dist/harness/read-text.js.map +1 -1
  235. package/dist/harness/script-check.d.ts.map +1 -1
  236. package/dist/harness/script-check.js +7 -1
  237. package/dist/harness/script-check.js.map +1 -1
  238. package/dist/harness/secret-scan.d.ts +27 -1
  239. package/dist/harness/secret-scan.d.ts.map +1 -1
  240. package/dist/harness/secret-scan.js +57 -4
  241. package/dist/harness/secret-scan.js.map +1 -1
  242. package/dist/harness/select-by-ids.d.ts +31 -0
  243. package/dist/harness/select-by-ids.d.ts.map +1 -0
  244. package/dist/harness/select-by-ids.js +106 -0
  245. package/dist/harness/select-by-ids.js.map +1 -0
  246. package/dist/harness/sensors.d.ts +20 -1
  247. package/dist/harness/sensors.d.ts.map +1 -1
  248. package/dist/harness/sensors.js +133 -39
  249. package/dist/harness/sensors.js.map +1 -1
  250. package/dist/harness/spec-branches.d.ts +0 -18
  251. package/dist/harness/spec-branches.d.ts.map +1 -1
  252. package/dist/harness/spec-branches.js +24 -3
  253. package/dist/harness/spec-branches.js.map +1 -1
  254. package/dist/harness/spec-coverage.d.ts.map +1 -1
  255. package/dist/harness/spec-coverage.js +14 -4
  256. package/dist/harness/spec-coverage.js.map +1 -1
  257. package/dist/harness/triage-signals.d.ts +20 -0
  258. package/dist/harness/triage-signals.d.ts.map +1 -0
  259. package/dist/harness/triage-signals.js +30 -0
  260. package/dist/harness/triage-signals.js.map +1 -0
  261. package/dist/harness/triage-unit.d.ts +23 -0
  262. package/dist/harness/triage-unit.d.ts.map +1 -0
  263. package/dist/harness/triage-unit.js +142 -0
  264. package/dist/harness/triage-unit.js.map +1 -0
  265. package/dist/harness/triage.d.ts +28 -0
  266. package/dist/harness/triage.d.ts.map +1 -0
  267. package/dist/harness/triage.js +99 -0
  268. package/dist/harness/triage.js.map +1 -0
  269. package/dist/harness/ui-inventory.d.ts +40 -0
  270. package/dist/harness/ui-inventory.d.ts.map +1 -0
  271. package/dist/harness/ui-inventory.js +191 -0
  272. package/dist/harness/ui-inventory.js.map +1 -0
  273. package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
  274. package/dist/harness/viewpoint-baseline.js +7 -1
  275. package/dist/harness/viewpoint-baseline.js.map +1 -1
  276. package/dist/harness/viewpoint-draft-header.d.ts +7 -0
  277. package/dist/harness/viewpoint-draft-header.d.ts.map +1 -0
  278. package/dist/harness/viewpoint-draft-header.js +100 -0
  279. package/dist/harness/viewpoint-draft-header.js.map +1 -0
  280. package/dist/harness/viewpoint-ledger.d.ts +13 -3
  281. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  282. package/dist/harness/viewpoint-ledger.js +106 -19
  283. package/dist/harness/viewpoint-ledger.js.map +1 -1
  284. package/dist/harness/viewpoint-mode.d.ts +9 -0
  285. package/dist/harness/viewpoint-mode.d.ts.map +1 -0
  286. package/dist/harness/viewpoint-mode.js +52 -0
  287. package/dist/harness/viewpoint-mode.js.map +1 -0
  288. package/dist/harness/viewpoint-priority.d.ts +19 -0
  289. package/dist/harness/viewpoint-priority.d.ts.map +1 -0
  290. package/dist/harness/viewpoint-priority.js +36 -0
  291. package/dist/harness/viewpoint-priority.js.map +1 -0
  292. package/dist/harness/viewpoint-review-open.d.ts +6 -0
  293. package/dist/harness/viewpoint-review-open.d.ts.map +1 -0
  294. package/dist/harness/viewpoint-review-open.js +90 -0
  295. package/dist/harness/viewpoint-review-open.js.map +1 -0
  296. package/dist/harness/viewpoint-scan-refs.d.ts +50 -0
  297. package/dist/harness/viewpoint-scan-refs.d.ts.map +1 -0
  298. package/dist/harness/viewpoint-scan-refs.js +200 -0
  299. package/dist/harness/viewpoint-scan-refs.js.map +1 -0
  300. package/dist/harness/viewpoint-scan.d.ts +41 -0
  301. package/dist/harness/viewpoint-scan.d.ts.map +1 -0
  302. package/dist/harness/viewpoint-scan.js +118 -0
  303. package/dist/harness/viewpoint-scan.js.map +1 -0
  304. package/dist/harness/write-atomic.d.ts +2 -0
  305. package/dist/harness/write-atomic.d.ts.map +1 -0
  306. package/dist/harness/write-atomic.js +62 -0
  307. package/dist/harness/write-atomic.js.map +1 -0
  308. package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
  309. package/dist/orchestrator/ai-skills/writer.js +15 -7
  310. package/dist/orchestrator/ai-skills/writer.js.map +1 -1
  311. package/dist/orchestrator/assets-drift.d.ts.map +1 -1
  312. package/dist/orchestrator/assets-drift.js +11 -6
  313. package/dist/orchestrator/assets-drift.js.map +1 -1
  314. package/dist/orchestrator/flow-manager.d.ts +0 -1
  315. package/dist/orchestrator/flow-manager.d.ts.map +1 -1
  316. package/dist/orchestrator/flow-manager.js +3 -52
  317. package/dist/orchestrator/flow-manager.js.map +1 -1
  318. package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
  319. package/dist/orchestrator/project-deps-sync.js +22 -1
  320. package/dist/orchestrator/project-deps-sync.js.map +1 -1
  321. package/dist/orchestrator/project-initializer.d.ts +8 -0
  322. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  323. package/dist/orchestrator/project-initializer.js +49 -7
  324. package/dist/orchestrator/project-initializer.js.map +1 -1
  325. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  326. package/dist/orchestrator/screen-manager.js +8 -62
  327. package/dist/orchestrator/screen-manager.js.map +1 -1
  328. package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
  329. package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
  330. package/dist/orchestrator/security-overrides-catalog.js +63 -0
  331. package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
  332. package/dist/orchestrator/security-overrides.d.ts +51 -0
  333. package/dist/orchestrator/security-overrides.d.ts.map +1 -0
  334. package/dist/orchestrator/security-overrides.js +165 -0
  335. package/dist/orchestrator/security-overrides.js.map +1 -0
  336. package/dist/orchestrator/templates/ai-src/agents/discovery.md +11 -3
  337. package/dist/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
  338. package/dist/orchestrator/templates/ai-src/commands/create-test.md +32 -24
  339. package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
  340. package/dist/orchestrator/templates/ai-src/commands/run-test.md +78 -15
  341. package/dist/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
  342. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  343. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +25 -1
  344. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
  345. package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
  346. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
  347. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  348. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
  349. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
  350. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
  351. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
  352. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
  353. package/dist/orchestrator/templates/playwright.config.ts +4 -2
  354. package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
  355. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  356. package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
  357. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  358. package/dist/orchestrator/templates/specs-url-assert.js +38 -4
  359. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  360. package/dist/orchestrator/templates/specs-url-assert.ts +47 -4
  361. package/dist/orchestrator/templates/wdio.conf.ts +24 -1
  362. package/dist/orchestrator/trace-mode-install.d.ts +14 -0
  363. package/dist/orchestrator/trace-mode-install.d.ts.map +1 -0
  364. package/dist/orchestrator/trace-mode-install.js +132 -0
  365. package/dist/orchestrator/trace-mode-install.js.map +1 -0
  366. package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +23 -0
  367. package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
  368. package/dist/orchestrator/viewpoint-pattern-catalog.js +48 -0
  369. package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
  370. package/dist/orchestrator/viewpoint-scaffold.d.ts +3 -0
  371. package/dist/orchestrator/viewpoint-scaffold.d.ts.map +1 -0
  372. package/dist/orchestrator/viewpoint-scaffold.js +70 -0
  373. package/dist/orchestrator/viewpoint-scaffold.js.map +1 -0
  374. package/package.json +3 -3
  375. package/src/cli/commands/audit.ts +59 -12
  376. package/src/cli/commands/capability.ts +54 -19
  377. package/src/cli/commands/changed.ts +2 -4
  378. package/src/cli/commands/dashboard.ts +1 -26
  379. package/src/cli/commands/defect.ts +49 -0
  380. package/src/cli/commands/delivery.ts +106 -56
  381. package/src/cli/commands/generate.ts +15 -7
  382. package/src/cli/commands/results-merge.ts +9 -9
  383. package/src/cli/commands/results-sanitize.ts +2 -9
  384. package/src/cli/commands/select.ts +88 -0
  385. package/src/cli/commands/triage.ts +57 -0
  386. package/src/cli/commands/update.ts +3 -0
  387. package/src/cli/commands/viewpoint.ts +58 -0
  388. package/src/cli/index.ts +13 -2
  389. package/src/dashboard/snapshot-builder.ts +6 -8
  390. package/src/dashboard/templates/index.html +1 -1
  391. package/src/dashboard/types.ts +1 -0
  392. package/src/exporters/csv-exporter.ts +12 -11
  393. package/src/exporters/feature-parser.ts +44 -2
  394. package/src/exporters/json-exporter.ts +6 -1
  395. package/src/exporters/locale-variants.ts +23 -17
  396. package/src/exporters/manual-hybrid-oracle.ts +112 -0
  397. package/src/exporters/matrix/build.ts +71 -19
  398. package/src/exporters/matrix/carry-manual-results.ts +292 -0
  399. package/src/exporters/matrix/export.ts +24 -4
  400. package/src/exporters/matrix/gates.ts +6 -0
  401. package/src/exporters/matrix/render-csv.ts +1 -1
  402. package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
  403. package/src/exporters/matrix/render-xlsx.ts +3 -0
  404. package/src/exporters/matrix/types.ts +13 -1
  405. package/src/exporters/matrix/wording.ts +36 -2
  406. package/src/exporters/playwright-report-parser.ts +1 -0
  407. package/src/exporters/run-environment.ts +89 -0
  408. package/src/exporters/scenario-merger.ts +51 -5
  409. package/src/exporters/test-data-resolver.ts +0 -6
  410. package/src/exporters/types.ts +5 -0
  411. package/src/exporters/xlsx-exporter.ts +7 -2
  412. package/src/generators/gherkin-parser/index.ts +50 -18
  413. package/src/generators/test-generator/adapters/adapter-interface.ts +6 -2
  414. package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  415. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  416. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  417. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
  418. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
  419. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
  420. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  421. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  422. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
  423. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
  424. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
  425. package/src/generators/test-generator/code-generator.ts +77 -5
  426. package/src/generators/test-generator/diagnostics.ts +36 -5
  427. package/src/generators/test-generator/patterns/index.ts +15 -8
  428. package/src/generators/test-generator/patterns/types.ts +15 -0
  429. package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
  430. package/src/generators/test-generator/step-mapper.ts +90 -8
  431. package/src/generators/test-generator/utils/data-resolver.ts +41 -12
  432. package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
  433. package/src/generators/test-generator/utils/selector-resolver.ts +238 -82
  434. package/src/harness/audit.ts +69 -24
  435. package/src/harness/capability.ts +23 -12
  436. package/src/harness/catalog/universal-viewpoints.yaml +5 -5
  437. package/src/harness/config-file.ts +66 -0
  438. package/src/harness/counted-assertions.ts +33 -0
  439. package/src/harness/data-driven-lint.ts +82 -24
  440. package/src/harness/defect-draft.ts +115 -0
  441. package/src/harness/defects.ts +94 -0
  442. package/src/harness/depth-lint.ts +2 -1
  443. package/src/harness/flow-check.ts +6 -2
  444. package/src/harness/keyword-match.ts +85 -0
  445. package/src/harness/manifest.ts +2 -2
  446. package/src/harness/next-step.ts +47 -5
  447. package/src/harness/parse.ts +151 -27
  448. package/src/harness/perf.ts +15 -7
  449. package/src/harness/quality-gates.ts +22 -8
  450. package/src/harness/query-catalog.ts +0 -0
  451. package/src/harness/read-text.ts +32 -1
  452. package/src/harness/script-check.ts +7 -1
  453. package/src/harness/secret-scan.ts +60 -4
  454. package/src/harness/select-by-ids.ts +83 -0
  455. package/src/harness/sensors.ts +141 -40
  456. package/src/harness/spec-branches.ts +6 -3
  457. package/src/harness/spec-coverage.ts +14 -4
  458. package/src/harness/triage-signals.ts +46 -0
  459. package/src/harness/triage-unit.ts +113 -0
  460. package/src/harness/triage.ts +127 -0
  461. package/src/harness/ui-inventory.ts +164 -0
  462. package/src/harness/viewpoint-baseline.ts +7 -1
  463. package/src/harness/viewpoint-draft-header.ts +55 -0
  464. package/src/harness/viewpoint-ledger.ts +113 -25
  465. package/src/harness/viewpoint-mode.ts +49 -0
  466. package/src/harness/viewpoint-priority.ts +38 -0
  467. package/src/harness/viewpoint-review-open.ts +51 -0
  468. package/src/harness/viewpoint-scan-refs.ts +154 -0
  469. package/src/harness/viewpoint-scan.ts +111 -0
  470. package/src/harness/write-atomic.ts +22 -0
  471. package/src/orchestrator/ai-skills/writer.ts +18 -3
  472. package/src/orchestrator/assets-drift.ts +10 -5
  473. package/src/orchestrator/flow-manager.ts +3 -53
  474. package/src/orchestrator/project-deps-sync.ts +23 -1
  475. package/src/orchestrator/project-initializer.ts +54 -6
  476. package/src/orchestrator/screen-manager.ts +7 -61
  477. package/src/orchestrator/security-overrides-catalog.ts +69 -0
  478. package/src/orchestrator/security-overrides.ts +149 -0
  479. package/src/orchestrator/templates/ai-src/agents/discovery.md +11 -3
  480. package/src/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
  481. package/src/orchestrator/templates/ai-src/commands/create-test.md +32 -24
  482. package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
  483. package/src/orchestrator/templates/ai-src/commands/run-test.md +78 -15
  484. package/src/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
  485. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  486. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +25 -1
  487. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
  488. package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
  489. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
  490. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  491. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
  492. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
  493. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
  494. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
  495. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
  496. package/src/orchestrator/templates/playwright.config.ts +4 -2
  497. package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
  498. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  499. package/src/orchestrator/templates/specs-url-assert.ts +47 -4
  500. package/src/orchestrator/templates/wdio.conf.ts +24 -1
  501. package/src/orchestrator/trace-mode-install.ts +94 -0
  502. package/src/orchestrator/viewpoint-pattern-catalog.ts +61 -0
  503. package/src/orchestrator/viewpoint-scaffold.ts +36 -0
@@ -154,7 +154,9 @@ For append: read highest `VP-<CAT>-<NNN>`, continue from next number. Never modi
154
154
  Auto-detected by `create-test` before invoking this skill:
155
155
  - `spec.md` — primary, always read if present
156
156
  - `spec_figma.md` — Figma supplement (PAT flow already done — just read the file)
157
- - `ui/*.png` — visual context
157
+ - `ui/*.{png,jpg,jpeg,webp,gif}` — design images. **Every one is read**, and turned into the Design Inventory below — not just "visual context".
158
+ - `ui/figma-meta.md` — Figma MCP capture (`sungen-capture` mode figma-mcp): `## Components` and `## Text Content` feed the Design Inventory; `## Text Content` also supplies exact labels for `test-data.yaml`.
159
+ - `ui-inventory.md` — the Design Inventory (written by create-test, see § Design Inventory). When it already exists, keep its ids and append new items; never renumber.
158
160
  - `test-viewpoint.md` — test conditions checklist; scan entirely regardless of format.
159
161
  Format varies across projects: hierarchical table (Level 1–4 columns), flat checklist,
160
162
  prose, or mixed. Regardless of format:
@@ -181,6 +183,40 @@ This means: do not test other screens' UI layout or navigation. It does NOT mean
181
183
 
182
184
  ---
183
185
 
186
+ ## Design Inventory (UI items)
187
+
188
+ **When:** the unit holds any design source — images in `requirements/ui/`, `ui/figma-meta.md`, or `spec_figma.md`. Without one, skip this section.
189
+
190
+ **Why:** a design read "for visual context" leaves no trace. An item that exists only on the design (a column `spec.md` never names) or contradicts the spec is then missed by the author AND the reviewer, and "deliberately deferred" looks exactly like "never seen". `sungen audit` checks this file (`UI-INVENTORY-MISSING`, `UI-INVENTORY-INCOMPLETE`, `UI-ITEM-UNCOVERED`).
191
+
192
+ **Reading order:** `spec.md` (authoritative) → `spec_figma.md` → `ui/figma-meta.md` (`## Components`, `## Text Content`) → **every** image in `ui/`. Many images: group them (primary screen vs states/variants) but read them all — an unread state screen is an un-inventoried one.
193
+
194
+ **Write `requirements/ui-inventory.md`** — one row per component, table column, button/link, field, state (empty/error/loading/disabled), and design annotation:
195
+
196
+ ```markdown
197
+ # UI Inventory — <screen>
198
+ images read: 3/3 · figma-meta.md: read · spec_figma.md: absent
199
+
200
+ | ID | Item | Kind | Source | Seen in |
201
+ |---|---|---|---|---|
202
+ | UI-001 | Job List table | table | spec+design | ui/list.png |
203
+ | UI-002 | Contract plan | column | spec+design | ui/list.png |
204
+ | UI-014 | Salary range | column | design-only | ui/list.png |
205
+ | UI-015 | Status badge "Closed" | column | conflict — spec says "Expired" | ui/list.png |
206
+ ```
207
+
208
+ - **Source:** `spec+design` (both name it) · `design-only` (only the design shows it) · `conflict` (they disagree — say how in the cell). `spec.md` stays authoritative; a conflict is reported, never silently resolved in either direction.
209
+ - **IDs** `UI-NNN` are append-only, like scenario ids: re-runs keep existing rows and continue numbering.
210
+ - `images read: N/N` counts the images you actually opened against the images in the folder.
211
+
212
+ **Account for every `design-only` / `conflict` item in the `.feature`** — either:
213
+ - **cover it**: tag the scenario that proves it `@ui:UI-014`, or
214
+ - **defer it with a reason** in a feature comment: `# UI-DEFERRED: UI-002..013 — per-column checks are owned by job-detail`. A deferral without a reason defers nothing.
215
+
216
+ `spec+design` items are covered through the spec (`@spec:`), and citing their ids is optional.
217
+
218
+ ---
219
+
184
220
  ## Generation Workflow
185
221
 
186
222
  ### Step 1 — Orient: patterns + risk
@@ -219,6 +255,10 @@ Viewpoint items: [TV-01 – <exact wording from test-viewpoint row 1>]
219
255
  [TV-02 – <exact wording from test-viewpoint row 2>]
220
256
  → one entry per row/bullet in test-viewpoint.md, regardless of format
221
257
  → leave empty if no test-viewpoint.md present
258
+ UI items: [UI-014 – Salary range column (design-only) → covered by VP-TAB-012]
259
+ [UI-002..013 – per-column checks → deferred: owned by job-detail]
260
+ → one entry per Design Inventory row: covered / deferred-with-reason / uncovered
261
+ → leave empty if the unit has no design source
222
262
  Spec gaps: [G1 – behavior undocumented → verify with dev]
223
263
  ```
224
264
 
@@ -345,7 +385,7 @@ Security: [S1 – admin only]
345
385
  - **`--accept-viewpoint` is the QA's command, not yours.** `sungen next` shows it as a `⚖ decide`
346
386
  step. Present the declared-id diff and stop; a yardstick accepted by the process it measures
347
387
  reads 100% by construction.
348
- - **Match the scenarios to the file — never the file to the scenarios.** A filled `test-viewpoint.md` is an input; do not rewrite its declarations to fit what you generated. That is not compliance, it is moving the yardstick: `traceability` + `atomicLedger` then read 100% by construction and a dropped viewpoint stops being missing from anything. Disagree with the taxonomy → propose the diff and ask. `sungen audit` reports `VIEWPOINT-BASELINE-CHANGED` and excludes both axes until a human accepts the change (`sungen audit --screen <name> --accept-viewpoint`).
388
+ - **Match the scenarios to the file — never the file to the scenarios.** A filled `test-viewpoint.md` is an input; do not rewrite its declarations to fit what you generated. That is not compliance, it is moving the yardstick: `traceability` + `atomicLedger` then read 100% by construction and a dropped viewpoint stops being missing from anything. Disagree with the taxonomy → report the diff (ids added / removed and why) in `requirements/viewpoint-review.md`; the QA decides — no mid-run question. `sungen audit` reports `VIEWPOINT-BASELINE-CHANGED` and excludes both axes until a human accepts the change (`sungen audit --screen <name> --accept-viewpoint`).
349
389
  2. **Spec-coverage triggers** (`TRIGGER-UNCOVERED`, gate-FAIL) — the Validation-Rules table lists a **trigger** per constraint (e.g. `blur, submit`). Generate one scenario **per (constraint × trigger)** — a `format` rule validating *on blur AND on submit* needs BOTH a blur scenario (`press Tab`) and a submit scenario (`click [Submit]` / `press Enter`). Never collapse the trigger × input matrix to one representative case.
350
390
  3. **Claim-Proof** (`CLAIM-UNPROVEN`) — a title claiming `all`/`only`/`every`/`single`/`correct`/`same`/`changes`/`hidden`/`cleared`/`restored`/`independent`/`sanitized`/`announces` MUST have the matching assertion (`see all …`, count, `remember`+compare, `is hidden`, return-and-assert-empty, etc.). If the title promises it, the steps must prove it.
351
391
  - **Negative / absence claims** (`does not` / `no` / `never` / `prevents` / `không` / `chưa` — any language; `no-side-effect/no-duplicate`, `negative-claim/absence`): the `Then` must **differ** between the claim holding and not holding. A terminal `see [X] page` that looks identical whether or not the bad thing happened proves nothing. For a side-effect that should NOT repeat (re-submit on back, re-charge, duplicate order, resend OTP), assert the **count is unchanged** (`User see [Records] table with {{one}}` / `row with {{count}}`); if it's not UI-observable, mark `@manual` with a request-count oracle (shape below). This is general — it covers any side-effect, not a fixed verb list.
@@ -152,6 +152,7 @@ Build a mapping table: for each applicable group, does the feature have a matchi
152
152
  | **Account Status** | Deleted / blocked / inactive account login | VP-LOGIC | corresponding account data → error message |
153
153
  | **Security** | SQL Injection; XSS; Data Integrity (edit dropdown/remove `disabled` via DevTools → server rejects). SQL on LIKE/search fields → 2 TCs per the `sungen-viewpoint` SQL 2-layer rule. | VP-SEC | `with {{xss_*}}`/`{{sql_*}}`; data-integrity usually `@manual` |
154
154
  | **Cross-surface outcomes** | Admin action → outcome on user-facing surface (portal, mobile, widget); spec defines display condition on another URL | VP-LOGIC-CS | `@high @manual` per surface per business rule — at minimum 1 per cross-surface rule; use `@auth:role` + `@extend` if surface reachable in same test run |
155
+ | **Design items** *(when `requirements/ui/*`, `ui/figma-meta.md` or `spec_figma.md` exist)* | Every component / column / button / state the design shows is in `ui-inventory.md`; each `design-only` / `conflict` item is covered or deferred with a reason. Read the images yourself — the inventory can be incomplete | UI-NNN | `@ui:UI-NNN` on the proving scenario, or `# UI-DEFERRED: UI-NNN — <reason>`; rules in `sungen-tc-generation` § Design Inventory |
155
156
 
156
157
  **Mobile units add four categories** (`@platform:mobile/android/ios` only). Each maps to a theme the
157
158
  `sungen audit` mobile catalog scores, so a gap here is a scored gap, not a stylistic note — the
@@ -374,6 +375,12 @@ If `selectors/<name>.yaml` exists, count lines matching `@needs-live-verify` (pr
374
375
  | Behavior Handling / Account Status | ... | ... |
375
376
  | Security (XSS/SQLi) | ... | ... |
376
377
  | Cross-surface outcomes | ... | ... |
378
+ | Design items | n/a / ✓ / ✗ | ... |
379
+
380
+ ### Design ↔ Feature (when the unit holds design sources)
381
+ - Images read: <n>/<n> · inventory items: <n> (covered <n> · deferred <n> · uncovered <n>)
382
+ - Not inventoried: <item> (<image>) …
383
+ - Mislabelled / unproven: UI-NNN …
377
384
 
378
385
  ### ✗ Must-fix
379
386
  1. [DIMENSION] VP-XXX issue description
@@ -15,6 +15,24 @@ This skill is a **router**. The detailed checklists live in 6 group files — lo
15
15
 
16
16
  > All checklist items are written in English. Render scenario names, step text, and test IDs in English.
17
17
 
18
+ ## Authoring (mode 2) — drafting `test-viewpoint.md`
19
+
20
+ Draft `requirements/test-viewpoint.md` only when the file is missing, is an untouched scaffold
21
+ (headings, `<…>` placeholders, empty tables), or has one or more VP rows and every one lacks a
22
+ `Kỳ vọng` — a file with no VP ids that is not the scaffold is QA-written (mode 1). Draft it from
23
+ `viewpoint-template.md` in this folder (the QA's sections, in Vietnamese): the file's first line is
24
+ `drafted-by: ai`; every VP row has a non-empty `Kỳ vọng`; whatever the spec leaves open becomes a
25
+ closed question in mục 5, never an invented expectation. Fill sections 1 and 3 and the Coverage
26
+ table from the patterns and Shared Checks below. A viewpoint without `drafted-by: ai` is QA-owned:
27
+ read it, never edit it.
28
+
29
+ ## Review (both modes) — `review-checklist.md`
30
+
31
+ create-test reviews every viewpoint with `review-checklist.md` in this folder, fed by
32
+ `sungen viewpoint scan --json`, and writes `requirements/viewpoint-review.md`. On a QA-owned file
33
+ that review file is the only output, limited to the checks its `Mode 1` column allows; on your own
34
+ draft, run every check and fix the draft first.
35
+
18
36
  ## Routing table
19
37
 
20
38
  | UI element on the screen | Pattern | Read file |
@@ -0,0 +1,63 @@
1
+ # Viewpoint review checklist
2
+
3
+ create-test runs this review of `requirements/test-viewpoint.md` inline, in both modes, and asks
4
+ no question. Inputs: the viewpoint; the scan JSON (`sungen viewpoint scan --screen <name> --json`,
5
+ a flow takes `--flow <name>`); `spec.md`, `ui-inventory.md` and the message table when present.
6
+ Output: `requirements/viewpoint-review.md` (format below).
7
+
8
+ ## Rules
9
+ - **Mode 1 — QA file (no `drafted-by: ai`): read only.** Never edit `test-viewpoint.md`. Every gap
10
+ goes into the review file; nothing is filled in. Look for what is **missing or contradicts the
11
+ spec**, not for how the QA writes: run only the checks the `Mode 1` column allows, as narrowed
12
+ there. A value the spec does not mention is not "invented". Wording the QA checks by eye ("Hiển thị đúng", "Khớp design"), their question style and
13
+ their environment marks are the QA's convention, never a finding.
14
+ - **Mode 2 — `drafted-by: ai`: review your own draft.** Run the checks, fix the draft, run them
15
+ again — checks 4 and 9 field by field, before the review is written. The review file keeps only
16
+ what needs a QA decision: questions, inferred expectations, scope calls.
17
+ - **Cite or drop.** Every finding names at least one VP id from the scan's `vpRows`, or an item ref
18
+ (`item N`, a spec id). A gap with no VP yet cites the item or spec line it comes from. A finding
19
+ you cannot anchor is not written.
20
+ - **Distinct ids.** The scan lists a row per table occurrence: an id repeated in the questions table
21
+ (mục 5) is a reference, not a second VP. Count and cite each id once.
22
+ - **No VP ids** (scan `note`: file has no VP ids): cite the file's own row labels (`DT-2 case 3`, a
23
+ field name) with the line number, plus item refs.
24
+ - **Meaning, not format.** Headings, column order, wording style and language are never findings.
25
+ - The scan's `withoutScenario` is generation work (a scenario per VP id), not a review finding.
26
+ - **Audit findings** (create-test may route one here, check `audit`) enter only when they cite a VP id
27
+ from the scan's `vpRows` and the fix is an edit the QA can make in this file. A finding about
28
+ sungen itself — the page type, an id it read in a message code or a section-8 table — stays in the
29
+ hand-back, never in the review.
30
+
31
+ ## Checks
32
+ One check, one fix hint. **Output order: 2, 9, 3, 6, 1, 4, 5, 7, 8** — highest impact first.
33
+
34
+ | # | Check | Mode 1 | Read | Suggestion to write |
35
+ |---|-------|--------|------|---------------------|
36
+ | 1 | Each VP row says what to test and what to expect: `Kỳ vọng` is not empty and not vague ("OK", "đúng", "hiển thị đúng", "như spec"). | empty `Kỳ vọng` only | scan `rowsWithoutExpected`, the rows | Name the observable: message code, target screen, control state, value. |
37
+ | 2 | Every expectation is traceable to the spec, the UI, the message table, a Design Decision (mục 6) or a Shared Check of this skill. One found in none of them is invented, unless mục 5 holds its question (`Nguồn: Suy luận → Q-0x`). | only an expectation that contradicts what the spec states; a value the spec does not mention (a URL, a screen id, a rule of thumb) is the QA's knowledge, not a finding | spec, UI, message table, mục 6 | Cite the source, or turn the expectation into a closed question in mục 5. |
38
+ | 3 | A rule the spec applies to several fields names every field (`Ô` column, or one row per field). | ✓ | spec rules | Name the missing fields. |
39
+ | 4 | Each accepted-input class in the spec (whitelist, value list, allowed characters, conversion such as trim, case, full→half width) has a positive VP — also a whitelist or list the spec names but does not reproduce. | ✓ | spec rules | Add an "accepted, no error" VP for the class, citing the list by name; not a question in mục 5. |
40
+ | 5 | A VP that needs a real device, account, external service or elapsed time is marked `# MANUAL/ENV`. | — | scan `manualRows`, the rows | Mark the row or its group heading. |
41
+ | 6 | Each entry of the scan's `uncitedItems`: deliberately out of scope (mục 6 or the scope note says so), or a gap? | ✓ | scan `uncitedItems`, mục 6, mục 7 | Add a VP citing `item N`, or a Design Decision saying why it is out. |
42
+ | 7 | Questions are closed (yes/no, or pick one of the listed options) and carry `Major` / `Minor`. | — | mục 5 | Rewrite as a closed question; add the level. |
43
+ | 8 | An edge case (mục 4) or decision (mục 6) states testable behaviour that no VP id carries — alone or together: an edge case split across several rows is carried. | ✓ | mục 4, mục 6 | Add a VP row. If a row already covers it, name that id in this finding — not in mục 4, where an id counts as a second declaration (`VIEWPOINT-ID-REUSED`). |
44
+ | 9 | A row's `Kỳ vọng` is only a question pointer (`→ Q-0x`) although the spec states the expected result for that condition. | ✓ | spec, mục 5 | Write the spec's expectation in the row; keep the question for the part the spec leaves open. |
45
+
46
+ ## Output — `requirements/viewpoint-review.md`
47
+ Overwrite it on every run, never append. Write Problem and Suggestion in the viewpoint's language;
48
+ one row per finding, or one row listing every id that shares the same fix.
49
+
50
+ ```markdown
51
+ # Viewpoint review — <unit>
52
+ > Date: <YYYY-MM-DD> · test-viewpoint.md sha256: <hash> · Mode: <1 — QA file, review only | 2 — AI draft>
53
+ > QA: tick `[x]` once handled, or delete the row. `sungen audit` reports the rows still open.
54
+
55
+ | # | VP / item | Check | Problem | Suggestion | [ ] |
56
+ |---|-----------|-------|---------|------------|-----|
57
+ | 1 | VP-…-NNN · item N | 2 | <what is wrong, in one sentence> | <the fix> | [ ] |
58
+ ```
59
+
60
+ - `sha256` of `test-viewpoint.md` as reviewed (`shasum -a 256` / `sha256sum`); mode 2: after your fixes.
61
+ - At most 20 rows, in check order. Past 20, one line under the table: `+N more (checks …)`.
62
+ - Nothing to report: keep the header, write `No open points.` instead of the table.
63
+ - The hand-back counts the rows: `Viewpoint review: N điểm cần QA xem`.
@@ -0,0 +1,158 @@
1
+ # Test Viewpoint — <MÃ MÀN> <Tên màn>
2
+ <!--
3
+ Template Test Viewpoint (skill sungen-viewpoint). Dòng mẫu và hướng dẫn nằm trong comment, nên khung chưa điền khai báo 0 viewpoint. Điền: chép dòng mẫu ra ngoài comment rồi sửa; nhóm không áp dụng thì xoá, ghi lý do ở bảng Coverage (mục 7).
4
+ Nháp do AI soạn (unit chưa có viewpoint QA): dòng ĐẦU TIÊN của file là `drafted-by: ai`, đặt ngoài comment. Nháp không bao giờ được lấy làm chuẩn đo; QA duyệt xong thì accept (`sungen audit … --accept-viewpoint`), accept xoá dòng đó và từ đó file thuộc QA. `sungen add` / `sungen add-flow` không ghi dòng này.
5
+ -->
6
+ > **Tài liệu đầu vào:** <đặc tả / design / bảng message của màn>
7
+ > **Loại tài liệu:** bảng quan điểm kiểm thử — KHÔNG phải test case chi tiết. Mỗi quan điểm gắn mã `VP-…` để truy vết sang test case.
8
+ > **Nguyên tắc:** mỗi dòng trả lời câu hỏi "test CÁI GÌ" và "kỳ vọng ra sao"; điểm spec chưa rõ thì ghi ở mục 5, **không tự bịa kỳ vọng** — spec đã nói thì ghi kỳ vọng theo spec, chỉ phần spec bỏ ngỏ mới thành câu hỏi. Quan điểm cần môi trường/thiết bị/thời gian thật: đánh dấu `# MANUAL/ENV`.
9
+
10
+ ## 0. Metadata
11
+ | Trường | Giá trị |
12
+ |--------|---------|
13
+ | Màn hình | <mã + tên gốc / tên tiếng Việt, route> |
14
+ | Vị trí trong flow | <vào từ đâu, đi tiếp tới đâu> |
15
+ | Primary UI pattern | <form / list / detail / create / login / search…> |
16
+ | Nền tảng | <trình duyệt / thiết bị / độ phân giải cần test> |
17
+ | Đặc thù cần nhấn | <rủi ro hoặc đặc điểm quan trọng nhất của màn> |
18
+ | Tiền điều kiện | <đăng nhập? role? dữ liệu cần chuẩn bị?> |
19
+ | Ngày tạo viewpoint | <YYYY-MM-DD> |
20
+
21
+ ## 1. UI Patterns Identified
22
+ *(Nhận diện màn dùng pattern nào → quyết định nhóm viewpoint nào cần áp dụng.)*
23
+
24
+ | # | Pattern | Áp dụng? | Ghi chú |
25
+ |---|---------|----------|---------|
26
+ <!-- Áp dụng? = ✅ / ❌ (❌ thì ghi lý do). Một dòng cho mỗi pattern, số + tên theo catalog sungen-viewpoint (web 1–17, mobile thêm 18–32):
27
+ | 1 | Form & Inputs | ✅ | <các ô nhập, validate lúc nào> |
28
+ | 12 | Modal / Dialog | ❌ | <lý do không áp dụng> |
29
+ -->
30
+
31
+ ## 2. Priority Viewpoints
32
+ ### Viewpoint grouping — ưu tiên theo nhóm VP
33
+ | Nhóm VP | Priority | Lý do |
34
+ |---------|----------|-------|
35
+ <!-- Priority = High / Med / Low; một dòng cho mỗi nhóm có ở mục 3: VP-VAL (Validation) · VP-LOGIC (Nghiệp vụ) · VP-UI-BTN (Nút & trạng thái) · VP-NAV (Điều hướng) · VP-AUTH (Quyền / tiền điều kiện) · VP-SEC (Bảo mật) · VP-UI (Hiển thị). Ví dụ:
36
+ | VP-VAL (Validation) | High | <lý do> | -->
37
+
38
+ ## 3. Viewpoints chi tiết
39
+ *(Pri = priority. Nguồn = số item trong spec/UI. Mỗi dòng = "test cái gì".)*
40
+ <!--
41
+ - Mọi bảng mục 3 dùng cùng cột. Mã VP-<NHÓM>[-<FIELD>]-NNN ở cột đầu, mỗi mã khai báo một lần. Flow: mã FL-…, thêm nhóm liên màn (dữ liệu / quyền giữ qua các màn).
42
+ - Kỳ vọng = thứ nhìn thấy được: mã message, màn đích, trạng thái control. Kỳ vọng trống thì dòng KHÔNG tính là viewpoint.
43
+ - Ô: để trống khi cả nhóm là một ô. Một luật áp cho nhiều ô: liệt kê đủ từng ô, không lấy 1 ô làm đại diện.
44
+ - Pri = High / Med / Low. Nguồn = `item N` · `SPEC <id>` · `Suy luận → Q-0x` (suy luận bắt buộc có câu hỏi ở mục 5). Cần thiết bị / tài khoản / thời gian thật: `# MANUAL/ENV` trong ô Quan điểm hoặc tiêu đề nhóm.
45
+ -->
46
+
47
+ ### A. VP-UI — Hiển thị tĩnh & trạng thái ban đầu
48
+ | VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
49
+ |----|--------------------|---|---------|-----|-------|
50
+ <!-- | VP-UI-001 | Trạng thái lúc mở màn | | <nhãn, placeholder, giá trị mặc định, nút chính disabled / enabled> | | item N | -->
51
+
52
+ ### B. VP-VAL-<FIELD> — <Tên field> (item x)
53
+ | VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
54
+ |----|--------------------|---|---------|-----|-------|
55
+ <!--
56
+ | VP-VAL-…-001 | Rỗng → blur / submit | | → <mã message> | High | |
57
+ | VP-VAL-…-002 | Chỉ khoảng trắng / khoảng trắng đầu-cuối | | | | |
58
+ | VP-VAL-…-003 | Biên độ dài: min−1 / min / max / max+1 | | | High | |
59
+ | VP-VAL-…-004 | Sai định dạng / ký tự không cho phép | | | | |
60
+ | VP-VAL-…-008 | Mọi ký tự / giá trị spec cho phép (whitelist, danh sách, chuyển đổi) được chấp nhận | | Không lỗi, giá trị giữ nguyên / đã chuyển đổi | High | |
61
+ | VP-VAL-…-005 | Full-width / half-width | | | | |
62
+ | VP-VAL-…-006 | Paste / autofill | | | | |
63
+ | VP-VAL-…-007 | Đang lỗi → sửa đúng → message biến mất | | | | |
64
+ -->
65
+ *(Lặp nhóm B cho từng field.)*
66
+
67
+ ### C. VP-LOGIC — Nghiệp vụ chính / submit
68
+ | VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
69
+ |----|--------------------|---|---------|-----|-------|
70
+ <!--
71
+ | VP-LOGIC-001 | Happy path | | | High | |
72
+ | VP-LOGIC-002 | Server trả lỗi nghiệp vụ | | | | |
73
+ | VP-LOGIC-003 | Lỗi mạng / server `# MANUAL/ENV` | | | | |
74
+ -->
75
+
76
+ ### D. VP-UI-BTN — Nút & trạng thái
77
+ | VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
78
+ |----|--------------------|---|---------|-----|-------|
79
+ <!--
80
+ | VP-UI-BTN-001 | Chưa đủ điều kiện → disabled | | | High | |
81
+ | VP-UI-BTN-002 | Đủ điều kiện → enabled | | | High | |
82
+ | VP-UI-BTN-003 | Loading sau khi bấm | | | | |
83
+ | VP-UI-BTN-004 | Bấm kép → chỉ 1 request `# MANUAL/ENV` | | | High | |
84
+ -->
85
+
86
+ ### E. VP-NAV — Điều hướng
87
+ | VP | Quan điểm kiểm thử | Ô | Kỳ vọng | Pri | Nguồn |
88
+ |----|--------------------|---|---------|-----|-------|
89
+ <!-- | VP-NAV-001 | | | → <màn đích>, cùng tab / tab mới | | | -->
90
+
91
+ ### F. VP-AUTH — Quyền / tiền điều kiện truy cập (nếu có)
92
+ ### G. VP-SEC — Bảo mật (nếu có)
93
+ ### H. VP-UI-PLTF — Nền tảng / thiết bị (nếu có)
94
+
95
+ ### DT-1 — Bảng quyết định: <luật phụ thuộc ≥ 2 điều kiện> (nếu có)
96
+ <!-- Mỗi Case = 1 dòng có mã VP. Cột giữa là điều kiện (✓ / ✗ / giá trị), rồi Hành động và Kỳ vọng. Ví dụ:
97
+ | VP | <Điều kiện 1> | <Điều kiện 2> | Hành động | Kỳ vọng | Pri | Nguồn |
98
+ |----|---------------|---------------|-----------|---------|-----|-------|
99
+ | VP-LOGIC-010 | ✓ | ✗ | Tick nốt ô con còn lại | "Chọn tất cả" tự → ✓ | High | item N |
100
+ -->
101
+
102
+ ## 4. Edge Cases
103
+ *(Ca dễ bị sót khi viết test case.)*
104
+ <!--
105
+ - <biên nối tiếp: max rồi max+1>
106
+ - <bấm nút đúng lúc field vừa thành không hợp lệ>
107
+ - <mất mạng trước khi bấm vs trong lúc chờ phản hồi>
108
+ - <phiên hết hạn giữa chừng>
109
+ -->
110
+
111
+ ## 5. Open questions / Điểm CHƯA CHỐT
112
+ *(Spec chưa nêu → KHÔNG tự bịa kỳ vọng; ghi thành câu hỏi đóng để hỏi BA/dev.)*
113
+
114
+ | Mã | Mức | VP liên quan | Câu hỏi đóng cần BA/dev xác nhận |
115
+ |-----|-----|--------------|----------------------------------|
116
+ <!-- Mức: Major = test case của VP liên quan chờ câu trả lời; Minor = không chặn. -->
117
+ <!-- | Q-01 | Major / Minor | VP-… | <câu hỏi cụ thể, trả lời được bằng có/không hoặc chọn 1 phương án> | -->
118
+
119
+ ## Known issues — lỗi đã biết (KHÔNG phải câu hỏi)
120
+ *(Lỗi đã xác nhận, còn tồn tại: scenario của VP đó gắn `@known-defect:DEF-NNN` (bản ghi ở `qa/defects/`), đỏ có chủ đích. Spec chưa rõ thì ghi ở mục 5.)*
121
+
122
+ | KI | VP liên quan | Lỗi | Hiện tượng |
123
+ |----|--------------|-----|------------|
124
+ <!-- | KI-1 | VP-… | DEF-001 | <thấy gì, khác Kỳ vọng ở đâu> | -->
125
+
126
+ ## 6. Design Decisions
127
+ *(Quyết định có chủ đích của spec — KHÔNG báo "thiếu" hoặc "bug" cho các mục này.)*
128
+ <!--
129
+ - D1: <vd: nút disabled tới khi đủ điều kiện> (SPEC <id>)
130
+ - D2: <vd: lỗi đăng nhập không nói rõ sai email hay sai mật khẩu>
131
+ -->
132
+
133
+ ## 7. Traceability & tự nhận giới hạn
134
+ - **Tổng: N viewpoint** — liệt kê số lượng theo từng nhóm. Mọi item trong spec có ít nhất 1 VP trỏ về.
135
+ - ⚠ **Giới hạn:** bảng chứng minh "mọi VP đã liệt kê có kỳ vọng" — KHÔNG chứng minh "đã liệt kê ĐỦ VP". Đối chiếu lại với đặc tả gốc + bảng message trước khi viết test case.
136
+ - **Scope note:** <VP nào cần môi trường/thiết bị thật, VP nào test ở tầng API/E2E>.
137
+ - Còn **K điểm chưa chốt** (Q-…) — test case cho VP liên quan chờ câu trả lời.
138
+
139
+ ### Coverage — độ phủ tự kiểm
140
+ <!-- Mỗi nhóm một dòng. Trạng thái: `có VP` (✓) · `để sau → <đích>` (# MANUAL/ENV, unit/flow khác, Q-0x) · `Không áp dụng` (➖) kèm lý do.
141
+ Thêm dòng nền tảng theo catalog sungen-viewpoint khi màn có: web — XSS / SQL injection / URL (Shared Checks); mobile — Bàn phím ảo (30) · Vòng đời app & xoay màn (32) · Offline (27) · Quyền hệ thống (22) · Deep-link (26). -->
142
+ | Nhóm kiểm tra | Trạng thái | VP / lý do |
143
+ |---------------|------------|------------|
144
+ | Happy path | | |
145
+ | Negative | | |
146
+ | Biên | | |
147
+ | Charset | | |
148
+ | Trạng thái nút | | |
149
+ | Điều hướng | | |
150
+ | Quyền | | |
151
+ | Bảo mật | | |
152
+ | Nền tảng | | |
153
+ | Trạng thái tải / rỗng / lỗi | | |
154
+
155
+ ## 8. References · Phụ lục — Mã thông báo dùng trong tài liệu
156
+ | Mã | Nghĩa | Kiểu hiển thị |
157
+ |----|-------|---------------|
158
+ <!-- | M01 | <nội dung message> | <dưới ô nhập / trên form / toast / mail> | -->
@@ -105,8 +105,10 @@ export default defineConfig({
105
105
  /* Per-action timeout (click, fill, etc.) */
106
106
  actionTimeout: 10_000,
107
107
 
108
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
109
- trace: 'on-first-retry',
108
+ /* Trace for a failed test. CI: recorded on the first retry (CI retries). Local: no retries, so
109
+ 'on-first-retry' would never record one — keep the trace of every failed test instead.
110
+ See https://playwright.dev/docs/trace-viewer */
111
+ trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure',
110
112
 
111
113
  /**
112
114
  * Capture screenshot after each test failure.
@@ -42,18 +42,30 @@ interface LocaleInjection {
42
42
 
43
43
  let cached: LocaleInjection | null | undefined;
44
44
 
45
+ /**
46
+ * Absent config → null, and locale injection is simply off (the documented no-op).
47
+ *
48
+ * A config that EXISTS but does not parse is a different thing and must not read the same way: the
49
+ * header above tells you to hand-edit this file, and a trailing comma would otherwise turn a
50
+ * `SUNGEN_ENV=ja` run into a base-locale run that asserts base-locale strings — green, and wrong.
51
+ * We warn rather than throw, because the injection is genuinely optional and failing every test on
52
+ * it would be a larger change than the fault warrants; the line names the file and the consequence.
53
+ */
45
54
  function loadLocaleConfig(): LocaleInjection | null {
46
55
  if (cached !== undefined) return cached;
56
+ const cfgPath = path.join(__dirname, 'locale-config.json');
57
+ if (!fs.existsSync(cfgPath)) {
58
+ cached = null;
59
+ return null;
60
+ }
47
61
  try {
48
- const cfgPath = path.join(__dirname, 'locale-config.json');
49
- if (!fs.existsSync(cfgPath)) {
50
- cached = null;
51
- return null;
52
- }
53
- const parsed = JSON.parse(fs.readFileSync(cfgPath, 'utf-8')) as LocaleInjection;
54
- cached = parsed;
55
- return parsed;
56
- } catch {
62
+ cached = JSON.parse(fs.readFileSync(cfgPath, 'utf-8')) as LocaleInjection;
63
+ return cached;
64
+ } catch (e) {
65
+ console.error(
66
+ `⚠️ ${cfgPath}: unreadable (${e instanceof Error ? e.message.split('\n')[0] : String(e)}) — ` +
67
+ `locale state is NOT injected, so this SUNGEN_ENV run behaves as the base locale.`,
68
+ );
57
69
  cached = null;
58
70
  return null;
59
71
  }
@@ -46,6 +46,16 @@ export interface UrlExpectation {
46
46
  declared?: string;
47
47
  /** The test-data key behind `declared`, so a failure can name what an author must go fix. */
48
48
  dataRef?: string;
49
+ /**
50
+ * The page selector declared `query: any` — the query string is not part of this page's identity,
51
+ * so SILENCE from the step stops meaning "there must be no query" (#748).
52
+ *
53
+ * Emitted only for a page whose entry says so. The default stays strict: a bare page means a bare
54
+ * page, which is the #464 assertion and the reason a filter regression cannot read as green.
55
+ * It is the no-`dataRef` branch alone that this relaxes — a step that declares `with {{v}}` still
56
+ * asserts every param it names, on this page as on any other.
57
+ */
58
+ ignoreQuery?: boolean;
49
59
  }
50
60
 
51
61
  /**
@@ -113,8 +123,14 @@ function locationMatches(pathname: RegExp, hostname: RegExp | undefined, u: URL)
113
123
  * tracking/UTM param the test never cared about cannot turn it red.
114
124
  * A declared fragment is asserted too; a declared value carrying no query
115
125
  * at all constrains the path only (the compiler warns: SG-W014).
126
+ *
127
+ * `query: any` on the page's selector entry overrides the FIRST rule only (#748): silence then
128
+ * means "the query is not mine to judge" instead of "there must be none". It is the escape hatch
129
+ * for a page whose query is generated at run time — `/admins/detail?id=<the row just clicked>` —
130
+ * which the strict rule makes unassertable and `with {{v}}` cannot express, since that asserts a
131
+ * literal. The second rule is untouched: declare params and they are still all checked.
116
132
  */
117
- export function urlMatches({ pathname, hostname, declared, dataRef }: UrlExpectation): (u: URL) => boolean {
133
+ export function urlMatches({ pathname, hostname, declared, dataRef, ignoreQuery }: UrlExpectation): (u: URL) => boolean {
118
134
  return (u: URL): boolean => {
119
135
  if (!locationMatches(pathname, hostname, u)) return false;
120
136
 
@@ -123,7 +139,13 @@ export function urlMatches({ pathname, hostname, declared, dataRef }: UrlExpecta
123
139
  // undefined, and they mean opposite things: the first is a real assertion (a bare page must be
124
140
  // bare), the second is broken test-data. Only the generator knows which, and it says so by
125
141
  // emitting `dataRef` exactly when the step carried a `with {{v}}`.
126
- if (!dataRef) return u.search === '';
142
+ // `query: any` on the page entry (#748): the query is not part of this page's identity, so
143
+ // silence from the step is not an assertion about it. Without this there is NO way to express
144
+ // "this path, whatever the query" — `with {{v}}` asserts a LITERAL value, and a detail page
145
+ // keyed by `?id=<the row just clicked>` has no literal to declare. Checked before the bare-page
146
+ // rule, never instead of the declared-value rule below: a step that names params still asserts
147
+ // them here exactly as anywhere else.
148
+ if (!dataRef) return ignoreQuery ? true : u.search === '';
127
149
 
128
150
  // Silence is the whole bug class this assertion exists to kill, so an unusable declared value
129
151
  // is an ERROR, not a quiet verdict. Returning false would read as "the app is wrong"; these
@@ -183,6 +205,26 @@ interface SettleOpts { settleMs?: number; networkIdleMs?: number }
183
205
  * 3000) bounds step 1. Both are runtime knobs, so a slow environment is a config change, not a
184
206
  * regenerate.
185
207
  */
208
+ /**
209
+ * A millisecond budget from the environment. `Number()` alone turned `"1s"` / `"abc"` into NaN and
210
+ * `""` into 0 (`??` keeps an empty string): the hold loop then never ran, so a late redirect passed
211
+ * unseen, and a 0 / NaN networkidle timeout means "no timeout" to Playwright — the step waited out
212
+ * the whole test (#856). Unset or empty means the default; a malformed value falls back to it and
213
+ * says so, once per variable. `allowZero` for a knob where 0 is a real choice (no hold).
214
+ */
215
+ const warnedEnv = new Set<string>();
216
+ function msEnv(name: string, fallback: number, allowZero = false): number {
217
+ const raw = process.env[name]?.trim();
218
+ if (!raw) return fallback;
219
+ const value = Number(raw);
220
+ if (Number.isFinite(value) && (value > 0 || (allowZero && value === 0))) return value;
221
+ if (!warnedEnv.has(name)) {
222
+ warnedEnv.add(name);
223
+ console.warn(`[sungen] ${name}=${JSON.stringify(raw)} is not a ${allowZero ? 'non-negative' : 'positive'} number of milliseconds — using the default ${fallback}.`);
224
+ }
225
+ return fallback;
226
+ }
227
+
186
228
  async function settleAndHold(
187
229
  page: SettleablePage,
188
230
  matches: (u: URL) => boolean,
@@ -190,8 +232,9 @@ async function settleAndHold(
190
232
  label: string,
191
233
  consequence: string,
192
234
  ): Promise<void> {
193
- const settleMs = opts.settleMs ?? Number(process.env.SUNGEN_URL_SETTLE_MS ?? 1000);
194
- const networkIdleMs = opts.networkIdleMs ?? Number(process.env.SUNGEN_URL_NETWORKIDLE_MS ?? 3000);
235
+ // 0 holds nothing (the operator's call); 0 for networkidle would mean "no timeout", so it is refused.
236
+ const settleMs = opts.settleMs ?? msEnv('SUNGEN_URL_SETTLE_MS', 1000, true);
237
+ const networkIdleMs = opts.networkIdleMs ?? msEnv('SUNGEN_URL_NETWORKIDLE_MS', 3000);
195
238
  const arrivedAt = page.url();
196
239
 
197
240
  await page.waitForLoadState('networkidle', { timeout: networkIdleMs }).catch(() => undefined);
@@ -48,6 +48,14 @@ function evidenceSlug(s: string): string {
48
48
  .slice(0, 80) || 'unnamed';
49
49
  }
50
50
 
51
+ /**
52
+ * True only in the launcher process. `@wdio/runner` re-parses this very config inside EVERY worker
53
+ * it spawns, so anything logged at module scope prints once per spec unless it is gated. WDIO's own
54
+ * "am I a worker" test is this env var (`@wdio/local-runner` sets it when spawning; `@wdio/utils`
55
+ * reads it) — reuse it rather than inventing a second signal.
56
+ */
57
+ const IS_LAUNCHER = !process.env.WDIO_WORKER_ID;
58
+
51
59
  /**
52
60
  * Auto-select MOBILE specs and classify each by its target OS (dual-target routing).
53
61
  *
@@ -82,7 +90,12 @@ function classifyMobileSpecs(root: string): Array<{ file: string; platform: stri
82
90
  .filter((full) => {
83
91
  try {
84
92
  return fs.readFileSync(full, 'utf-8').includes('@wdio/globals');
85
- } catch {
93
+ } catch (e) {
94
+ // A spec listed by readdir that cannot be read cannot be classified, so it is excluded.
95
+ // Excluding it SILENTLY is the fault: WDIO only fails the run when the spec list is
96
+ // EMPTY ("No specs found to run"), so dropping one spec out of ten leaves a green suite
97
+ // containing a test that never executed. Name it, and let the count below show the shortfall.
98
+ if (IS_LAUNCHER) console.warn(`[sungen] could not read ${full} — EXCLUDED from this run: ${(e as Error).message}`);
86
99
  return false;
87
100
  }
88
101
  })
@@ -98,6 +111,16 @@ const androidSpecs = specEntries.filter((e) => e.platform !== 'ios').map((e) =>
98
111
  const iosSpecs = specEntries.filter((e) => e.platform !== 'android').map((e) => e.file);
99
112
  const mobileSpecs = specEntries.map((e) => e.file); // union — top-level fallback
100
113
 
114
+ // What DISCOVERY found — a mobile suite is only trustworthy if you can see how many specs it
115
+ // resolved: WDIO fails loudly on an EMPTY list but says nothing when the list is merely short,
116
+ // which is exactly what a dropped spec looks like.
117
+ //
118
+ // Two things this number is not. It is not an error on 0: WDIO already exits 1 there
119
+ // ("No specs found to run"), and a `--shard` that legitimately receives none must still return 0.
120
+ // And under `--shard` it is the PRE-SHARD total, because ConfigParser slices `specs` down to the
121
+ // shard's portion only after this file has finished evaluating.
122
+ if (IS_LAUNCHER) console.log(`[sungen] specs: ${specEntries.length} mobile spec(s) — android ${androidSpecs.length}, ios ${iosSpecs.length}`);
123
+
101
124
  // App under test — env overrides the init-configured defaults.
102
125
  const APP_PACKAGE = process.env.APP_PACKAGE || '__APP_PACKAGE__';
103
126
  const APP_ACTIVITY = process.env.APP_ACTIVITY || '__APP_ACTIVITY__';
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Move an existing project's `use.trace` off the old default so local runs keep a trace.
3
+ *
4
+ * The old template paired `trace: 'on-first-retry'` with `retries: process.env.CI ? 2 : 0`: off CI
5
+ * there is no retry, so there is never a "first retry" and a QA fixing a test on their machine never
6
+ * got a trace. The template now records `on-first-retry` on CI and `retain-on-failure` locally.
7
+ *
8
+ * `playwright.config.ts` is written once at `sungen init` and never re-synced (see
9
+ * report-sanitize-reporter-install.ts, the same problem for the sanitize reporter), so `sungen update`
10
+ * calls this. It rewrites the trace line ONLY while it is still the old default — any other value is
11
+ * a choice the user made, and a wrong edit to a user's config is worse than a missing trace. The same
12
+ * goes for a `trace` inside `projects: [...]`: that is a per-project override, not the template default.
13
+ */
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+
17
+ /** The template's trace value: cheap on CI (retries exist there), a trace for every local failure. */
18
+ export const TRACE_MODE_EXPRESSION = "process.env.CI ? 'on-first-retry' : 'retain-on-failure'";
19
+
20
+ /**
21
+ * 'patched' — the old default was rewritten now
22
+ * 'present' — already the template expression (idempotent)
23
+ * 'custom' — another value, no trace line, more than one old-default line, or one inside
24
+ * `projects: [...]`; nothing written
25
+ * 'absent' — no playwright.config.ts (a mobile-only project, or not a sungen project)
26
+ */
27
+ export type TraceModeOutcome = 'patched' | 'present' | 'custom' | 'absent';
28
+
29
+ const escapeRegExp = (s: string): string => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
+
31
+ // Both are anchored to the start of a line, so a commented-out copy (`// trace: …`) never counts.
32
+ // OLD_DEFAULT keeps the indentation in $1 and leaves the trailing comma, comment and EOL alone.
33
+ const OLD_DEFAULT = /^([ \t]*trace:[ \t]*)(['"])on-first-retry\2/gm;
34
+ const PRESENT = new RegExp(`^[ \\t]*trace:[ \\t]*${escapeRegExp(TRACE_MODE_EXPRESSION)}`, 'm');
35
+
36
+ // The old template's one-line comment, only when it sits directly above the (patched) trace line.
37
+ // A comment the user wrote is theirs and stays.
38
+ const OLD_COMMENT = new RegExp(
39
+ `^([ \\t]*)/\\* Collect trace when retrying the failed test\\. See https://playwright\\.dev/docs/trace-viewer \\*/(\\r?\\n)(?=[ \\t]*trace:[ \\t]*${escapeRegExp(TRACE_MODE_EXPRESSION)})`,
40
+ 'm',
41
+ );
42
+ const newComment = (indent: string, eol: string): string =>
43
+ `${indent}/* Trace for a failed test. CI: recorded on the first retry (CI retries). Local: no retries, so${eol}` +
44
+ `${indent} 'on-first-retry' would never record one — keep the trace of every failed test instead.${eol}` +
45
+ `${indent} See https://playwright.dev/docs/trace-viewer */${eol}`;
46
+
47
+ /**
48
+ * [start, end] offsets of each live `projects: [...]` array, matched by bracket depth. Brackets inside
49
+ * a `//` or `/* *\/` comment or a quoted string are skipped: one stray `]` in a note would otherwise
50
+ * close the span early and expose a per-project override to the patch.
51
+ */
52
+ function projectsSpans(src: string): Array<[number, number]> {
53
+ const spans: Array<[number, number]> = [];
54
+ for (const m of src.matchAll(/^[ \t]*projects\s*:\s*\[/gm)) {
55
+ const open = m.index! + m[0].length - 1;
56
+ let depth = 0;
57
+ for (let i = open; i < src.length; i++) {
58
+ const ch = src[i];
59
+ if (ch === '/' && src[i + 1] === '/') { const nl = src.indexOf('\n', i); i = nl < 0 ? src.length : nl; continue; }
60
+ if (ch === '/' && src[i + 1] === '*') { const end = src.indexOf('*/', i + 2); i = end < 0 ? src.length : end + 1; continue; }
61
+ if (ch === "'" || ch === '"' || ch === '`') {
62
+ for (i++; i < src.length && src[i] !== ch; i++) if (src[i] === '\\') i++;
63
+ continue;
64
+ }
65
+ if (ch === '[') depth++;
66
+ else if (ch === ']' && --depth === 0) { spans.push([open, i]); break; }
67
+ }
68
+ }
69
+ return spans;
70
+ }
71
+
72
+ export function patchTraceMode(cwd: string, dryRun: boolean = false): TraceModeOutcome {
73
+ const configPath = path.join(cwd, 'playwright.config.ts');
74
+ if (!fs.existsSync(configPath)) return 'absent';
75
+ const src = fs.readFileSync(configPath, 'utf8');
76
+ if (PRESENT.test(src)) return 'present';
77
+ const hits = [...src.matchAll(OLD_DEFAULT)];
78
+ if (hits.length !== 1) return 'custom'; // none: the user's own value · several: ambiguous, don't guess
79
+ const at = hits[0].index!;
80
+ if (projectsSpans(src).some(([s, e]) => at > s && at < e)) return 'custom'; // a per-project override
81
+ if (!dryRun) {
82
+ const patched = src
83
+ .replace(OLD_DEFAULT, `$1${TRACE_MODE_EXPRESSION}`)
84
+ .replace(OLD_COMMENT, (_m, indent: string, eol: string) => newComment(indent, eol));
85
+ fs.writeFileSync(configPath, patched);
86
+ }
87
+ return 'patched';
88
+ }
89
+
90
+ /** `sungen update` step: patch the trace mode and say so when it changed. */
91
+ export function installTraceMode(cwd: string, dryRun: boolean = false): void {
92
+ if (patchTraceMode(cwd, dryRun) !== 'patched') return;
93
+ console.log(`${dryRun ? '📋 would update' : '✓ updated'} playwright.config.ts: local runs now keep a trace for every failed test (trace: ${TRACE_MODE_EXPRESSION})`);
94
+ }