@sun-asterisk/sungen 3.2.28 → 3.2.30

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 (486) hide show
  1. package/dist/capabilities/discover.d.ts +29 -0
  2. package/dist/capabilities/discover.d.ts.map +1 -1
  3. package/dist/capabilities/discover.js +144 -10
  4. package/dist/capabilities/discover.js.map +1 -1
  5. package/dist/capabilities/registry.d.ts +18 -0
  6. package/dist/capabilities/registry.d.ts.map +1 -1
  7. package/dist/capabilities/registry.js +25 -0
  8. package/dist/capabilities/registry.js.map +1 -1
  9. package/dist/cli/commands/audit.d.ts.map +1 -1
  10. package/dist/cli/commands/audit.js +9 -0
  11. package/dist/cli/commands/audit.js.map +1 -1
  12. package/dist/cli/commands/capability.js +1 -1
  13. package/dist/cli/commands/capability.js.map +1 -1
  14. package/dist/cli/commands/delivery.d.ts.map +1 -1
  15. package/dist/cli/commands/delivery.js +2 -1
  16. package/dist/cli/commands/delivery.js.map +1 -1
  17. package/dist/cli/commands/generate.d.ts.map +1 -1
  18. package/dist/cli/commands/generate.js +6 -2
  19. package/dist/cli/commands/generate.js.map +1 -1
  20. package/dist/cli/commands/inspect.js +1 -1
  21. package/dist/cli/commands/inspect.js.map +1 -1
  22. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  23. package/dist/cli/commands/results-merge.js +9 -2
  24. package/dist/cli/commands/results-merge.js.map +1 -1
  25. package/dist/cli/commands/results-sanitize.d.ts +24 -0
  26. package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
  27. package/dist/cli/commands/results-sanitize.js +126 -0
  28. package/dist/cli/commands/results-sanitize.js.map +1 -0
  29. package/dist/cli/commands/script-check.d.ts.map +1 -1
  30. package/dist/cli/commands/script-check.js +3 -1
  31. package/dist/cli/commands/script-check.js.map +1 -1
  32. package/dist/cli/commands/update.d.ts.map +1 -1
  33. package/dist/cli/commands/update.js +3 -0
  34. package/dist/cli/commands/update.js.map +1 -1
  35. package/dist/dashboard/templates/index.html +1 -1
  36. package/dist/exporters/api-testcase-formatter.d.ts +3 -6
  37. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
  38. package/dist/exporters/api-testcase-formatter.js +7 -10
  39. package/dist/exporters/api-testcase-formatter.js.map +1 -1
  40. package/dist/exporters/feature-parser.d.ts +1 -1
  41. package/dist/exporters/feature-parser.js +1 -1
  42. package/dist/exporters/matrix/build.d.ts.map +1 -1
  43. package/dist/exporters/matrix/build.js +18 -3
  44. package/dist/exporters/matrix/build.js.map +1 -1
  45. package/dist/exporters/matrix/item-labels.d.ts +35 -0
  46. package/dist/exporters/matrix/item-labels.d.ts.map +1 -0
  47. package/dist/exporters/matrix/item-labels.js +90 -0
  48. package/dist/exporters/matrix/item-labels.js.map +1 -0
  49. package/dist/exporters/matrix/render-coverage-sheet.d.ts +14 -0
  50. package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -0
  51. package/dist/exporters/matrix/render-coverage-sheet.js +162 -0
  52. package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -0
  53. package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
  54. package/dist/exporters/matrix/render-csv.js +3 -1
  55. package/dist/exporters/matrix/render-csv.js.map +1 -1
  56. package/dist/exporters/matrix/render-overview-sheet.d.ts +20 -0
  57. package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -0
  58. package/dist/exporters/matrix/render-overview-sheet.js +32 -0
  59. package/dist/exporters/matrix/render-overview-sheet.js.map +1 -0
  60. package/dist/exporters/matrix/render-testcase-sheet.d.ts +30 -0
  61. package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -0
  62. package/dist/exporters/matrix/render-testcase-sheet.js +156 -0
  63. package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -0
  64. package/dist/exporters/matrix/render-xlsx.d.ts +41 -31
  65. package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
  66. package/dist/exporters/matrix/render-xlsx.js +72 -425
  67. package/dist/exporters/matrix/render-xlsx.js.map +1 -1
  68. package/dist/exporters/matrix/result-totals.d.ts +23 -0
  69. package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
  70. package/dist/exporters/matrix/result-totals.js +21 -0
  71. package/dist/exporters/matrix/result-totals.js.map +1 -0
  72. package/dist/exporters/matrix/testcase-summary-band.d.ts +30 -0
  73. package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -0
  74. package/dist/exporters/matrix/testcase-summary-band.js +71 -0
  75. package/dist/exporters/matrix/testcase-summary-band.js.map +1 -0
  76. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  77. package/dist/exporters/matrix/wording.js +13 -0
  78. package/dist/exporters/matrix/wording.js.map +1 -1
  79. package/dist/exporters/matrix/xlsx-cells.d.ts +38 -0
  80. package/dist/exporters/matrix/xlsx-cells.d.ts.map +1 -0
  81. package/dist/exporters/matrix/xlsx-cells.js +52 -0
  82. package/dist/exporters/matrix/xlsx-cells.js.map +1 -0
  83. package/dist/exporters/qa-env-secret-denylist.js +2 -2
  84. package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
  85. package/dist/exporters/report-path-sanitizer.d.ts +38 -0
  86. package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
  87. package/dist/exporters/report-path-sanitizer.js +137 -0
  88. package/dist/exporters/report-path-sanitizer.js.map +1 -0
  89. package/dist/exporters/result-variants.d.ts.map +1 -1
  90. package/dist/exporters/result-variants.js +2 -1
  91. package/dist/exporters/result-variants.js.map +1 -1
  92. package/dist/exporters/sensitive-key.d.ts +31 -0
  93. package/dist/exporters/sensitive-key.d.ts.map +1 -0
  94. package/dist/exporters/sensitive-key.js +50 -0
  95. package/dist/exporters/sensitive-key.js.map +1 -0
  96. package/dist/exporters/test-data-resolver.d.ts +4 -1
  97. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  98. package/dist/exporters/test-data-resolver.js +12 -2
  99. package/dist/exporters/test-data-resolver.js.map +1 -1
  100. package/dist/exporters/xlsx-report-builder.d.ts +12 -1
  101. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
  102. package/dist/exporters/xlsx-report-builder.js +10 -2
  103. package/dist/exporters/xlsx-report-builder.js.map +1 -1
  104. package/dist/generators/gherkin-parser/index.d.ts +10 -0
  105. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  106. package/dist/generators/gherkin-parser/index.js +20 -12
  107. package/dist/generators/gherkin-parser/index.js.map +1 -1
  108. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
  109. package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
  110. package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
  111. package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
  112. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
  113. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  114. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
  115. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
  116. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
  117. package/dist/generators/test-generator/adapters/appium/os-support.d.ts +53 -0
  118. package/dist/generators/test-generator/adapters/appium/os-support.d.ts.map +1 -0
  119. package/dist/generators/test-generator/adapters/appium/os-support.js +200 -0
  120. package/dist/generators/test-generator/adapters/appium/os-support.js.map +1 -0
  121. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  122. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  123. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  124. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
  125. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
  126. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  127. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  128. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  129. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  130. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
  131. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
  132. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  133. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
  134. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
  135. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
  136. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  137. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
  138. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  139. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  140. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
  141. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  142. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
  143. package/dist/generators/test-generator/adapters/index.d.ts +1 -1
  144. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  145. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
  146. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  147. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  148. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  149. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  150. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  151. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  152. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  153. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  154. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  155. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  156. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
  157. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
  158. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  159. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  160. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  161. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  162. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  163. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  164. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  165. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  166. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  167. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  168. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  169. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  170. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  171. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  172. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  173. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
  174. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
  175. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
  176. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
  177. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
  178. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
  179. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
  180. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  181. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  182. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  183. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  184. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
  185. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  186. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  187. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
  188. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
  189. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  190. package/dist/generators/test-generator/code-generator.d.ts +37 -0
  191. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  192. package/dist/generators/test-generator/code-generator.js +165 -40
  193. package/dist/generators/test-generator/code-generator.js.map +1 -1
  194. package/dist/generators/test-generator/diagnostics.d.ts +116 -6
  195. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  196. package/dist/generators/test-generator/diagnostics.js +115 -5
  197. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  198. package/dist/generators/test-generator/patterns/index.d.ts +57 -11
  199. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  200. package/dist/generators/test-generator/patterns/index.js +163 -21
  201. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  202. package/dist/generators/test-generator/patterns/types.d.ts +8 -0
  203. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  204. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
  205. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
  206. package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
  207. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
  208. package/dist/generators/test-generator/step-mapper.d.ts +103 -10
  209. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  210. package/dist/generators/test-generator/step-mapper.js +266 -45
  211. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  212. package/dist/generators/test-generator/template-engine.d.ts +4 -19
  213. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  214. package/dist/generators/test-generator/template-engine.js +17 -12
  215. package/dist/generators/test-generator/template-engine.js.map +1 -1
  216. package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
  217. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  218. package/dist/generators/test-generator/utils/data-resolver.js +19 -2
  219. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  220. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
  221. package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
  222. package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
  223. package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
  224. package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
  225. package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
  226. package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
  227. package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
  228. package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
  229. package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
  230. package/dist/generators/test-generator/utils/nearest-match.js +56 -0
  231. package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
  232. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
  233. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
  234. package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
  235. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
  236. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
  237. package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
  238. package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
  239. package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
  240. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
  241. package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
  242. package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
  243. package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
  244. package/dist/generators/test-generator/utils/selector-resolver.d.ts +53 -2
  245. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  246. package/dist/generators/test-generator/utils/selector-resolver.js +273 -27
  247. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  248. package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
  249. package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
  250. package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
  251. package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
  252. package/dist/harness/audit.d.ts +3 -1
  253. package/dist/harness/audit.d.ts.map +1 -1
  254. package/dist/harness/audit.js +51 -28
  255. package/dist/harness/audit.js.map +1 -1
  256. package/dist/harness/capability-plan.d.ts +4 -0
  257. package/dist/harness/capability-plan.d.ts.map +1 -1
  258. package/dist/harness/capability-plan.js +32 -2
  259. package/dist/harness/capability-plan.js.map +1 -1
  260. package/dist/harness/catalog/drivers.yaml +1 -1
  261. package/dist/harness/catalog/universal-viewpoints.yaml +145 -0
  262. package/dist/harness/data-driven-lint.d.ts.map +1 -1
  263. package/dist/harness/data-driven-lint.js +217 -33
  264. package/dist/harness/data-driven-lint.js.map +1 -1
  265. package/dist/harness/parse.d.ts +6 -0
  266. package/dist/harness/parse.d.ts.map +1 -1
  267. package/dist/harness/parse.js +1 -1
  268. package/dist/harness/parse.js.map +1 -1
  269. package/dist/harness/repair.d.ts.map +1 -1
  270. package/dist/harness/repair.js +8 -1
  271. package/dist/harness/repair.js.map +1 -1
  272. package/dist/harness/script-check.d.ts +2 -2
  273. package/dist/harness/script-check.d.ts.map +1 -1
  274. package/dist/harness/script-check.js +186 -25
  275. package/dist/harness/script-check.js.map +1 -1
  276. package/dist/harness/sensors.d.ts +48 -0
  277. package/dist/harness/sensors.d.ts.map +1 -1
  278. package/dist/harness/sensors.js +26 -1
  279. package/dist/harness/sensors.js.map +1 -1
  280. package/dist/harness/viewpoint-ledger.d.ts +10 -0
  281. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  282. package/dist/harness/viewpoint-ledger.js +23 -2
  283. package/dist/harness/viewpoint-ledger.js.map +1 -1
  284. package/dist/index.d.ts +2 -0
  285. package/dist/index.d.ts.map +1 -1
  286. package/dist/index.js +10 -1
  287. package/dist/index.js.map +1 -1
  288. package/dist/orchestrator/project-initializer.d.ts +6 -0
  289. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  290. package/dist/orchestrator/project-initializer.js +16 -0
  291. package/dist/orchestrator/project-initializer.js.map +1 -1
  292. package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
  293. package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
  294. package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
  295. package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
  296. package/dist/orchestrator/templates/ai-src/commands/delivery.md +4 -3
  297. package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  298. package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  299. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
  300. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
  301. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
  302. package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
  303. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
  304. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
  305. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
  306. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
  307. package/dist/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
  308. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
  309. package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
  310. package/dist/orchestrator/templates/playwright.config.ts +7 -0
  311. package/dist/orchestrator/templates/readme.md +1 -1
  312. package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
  313. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
  314. package/dist/orchestrator/templates/specs-checked-state.js +261 -0
  315. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
  316. package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
  317. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  318. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
  319. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
  320. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
  321. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
  322. package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  323. package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
  324. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  325. package/dist/orchestrator/templates/specs-url-assert.js +65 -24
  326. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  327. package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
  328. package/dist/orchestrator/templates/wdio.conf.ts +105 -1
  329. package/dist/utils/selector-types.d.ts +43 -1
  330. package/dist/utils/selector-types.d.ts.map +1 -1
  331. package/dist/utils/selector-types.js +51 -7
  332. package/dist/utils/selector-types.js.map +1 -1
  333. package/package.json +3 -3
  334. package/src/capabilities/discover.ts +146 -10
  335. package/src/capabilities/registry.ts +30 -0
  336. package/src/cli/commands/audit.ts +9 -0
  337. package/src/cli/commands/capability.ts +1 -1
  338. package/src/cli/commands/delivery.ts +2 -1
  339. package/src/cli/commands/generate.ts +5 -2
  340. package/src/cli/commands/inspect.ts +1 -1
  341. package/src/cli/commands/results-merge.ts +9 -2
  342. package/src/cli/commands/results-sanitize.ts +98 -0
  343. package/src/cli/commands/script-check.ts +3 -1
  344. package/src/cli/commands/update.ts +3 -0
  345. package/src/dashboard/templates/index.html +1 -1
  346. package/src/exporters/api-testcase-formatter.ts +5 -7
  347. package/src/exporters/feature-parser.ts +1 -1
  348. package/src/exporters/matrix/build.ts +17 -3
  349. package/src/exporters/matrix/item-labels.ts +78 -0
  350. package/src/exporters/matrix/render-coverage-sheet.ts +170 -0
  351. package/src/exporters/matrix/render-csv.ts +3 -1
  352. package/src/exporters/matrix/render-overview-sheet.ts +51 -0
  353. package/src/exporters/matrix/render-testcase-sheet.ts +178 -0
  354. package/src/exporters/matrix/render-xlsx.ts +67 -448
  355. package/src/exporters/matrix/result-totals.ts +37 -0
  356. package/src/exporters/matrix/testcase-summary-band.ts +74 -0
  357. package/src/exporters/matrix/wording.ts +13 -0
  358. package/src/exporters/matrix/xlsx-cells.ts +56 -0
  359. package/src/exporters/qa-env-secret-denylist.ts +1 -1
  360. package/src/exporters/report-path-sanitizer.ts +144 -0
  361. package/src/exporters/result-variants.ts +2 -1
  362. package/src/exporters/sensitive-key.ts +48 -0
  363. package/src/exporters/test-data-resolver.ts +12 -2
  364. package/src/exporters/xlsx-report-builder.ts +16 -2
  365. package/src/generators/gherkin-parser/index.ts +22 -11
  366. package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
  367. package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
  368. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
  369. package/src/generators/test-generator/adapters/appium/os-support.ts +176 -0
  370. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
  371. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
  372. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
  373. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
  374. package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
  375. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
  376. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
  377. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
  378. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
  379. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
  380. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
  381. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
  382. package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
  383. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
  384. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
  385. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
  386. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
  387. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
  388. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
  389. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
  390. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
  391. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +62 -23
  392. package/src/generators/test-generator/adapters/index.ts +1 -1
  393. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
  394. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
  395. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
  396. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
  397. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  398. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
  399. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
  400. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
  401. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
  402. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +7 -2
  403. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +7 -2
  404. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
  405. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
  406. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
  407. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
  408. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
  409. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
  410. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
  411. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
  412. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
  413. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
  414. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
  415. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
  416. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
  417. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
  418. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
  419. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +21 -3
  420. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +20 -2
  421. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
  422. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
  423. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
  424. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
  425. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
  426. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
  427. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
  428. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
  429. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
  430. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
  431. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
  432. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  433. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
  434. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
  435. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
  436. package/src/generators/test-generator/code-generator.ts +181 -46
  437. package/src/generators/test-generator/diagnostics.ts +117 -6
  438. package/src/generators/test-generator/patterns/index.ts +175 -24
  439. package/src/generators/test-generator/patterns/types.ts +8 -0
  440. package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
  441. package/src/generators/test-generator/step-mapper.ts +277 -43
  442. package/src/generators/test-generator/template-engine.ts +21 -21
  443. package/src/generators/test-generator/utils/data-resolver.ts +23 -2
  444. package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
  445. package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
  446. package/src/generators/test-generator/utils/nearest-match.ts +55 -0
  447. package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
  448. package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
  449. package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
  450. package/src/generators/test-generator/utils/selector-resolver.ts +333 -28
  451. package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
  452. package/src/harness/audit.ts +55 -30
  453. package/src/harness/capability-plan.ts +33 -1
  454. package/src/harness/catalog/drivers.yaml +1 -1
  455. package/src/harness/catalog/universal-viewpoints.yaml +145 -0
  456. package/src/harness/data-driven-lint.ts +192 -32
  457. package/src/harness/parse.ts +7 -1
  458. package/src/harness/repair.ts +8 -1
  459. package/src/harness/script-check.ts +186 -21
  460. package/src/harness/sensors.ts +75 -1
  461. package/src/harness/viewpoint-ledger.ts +26 -5
  462. package/src/index.ts +7 -0
  463. package/src/orchestrator/project-initializer.ts +17 -0
  464. package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
  465. package/src/orchestrator/templates/ai-src/commands/delivery.md +4 -3
  466. package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
  467. package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
  468. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
  469. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +62 -3
  470. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +126 -11
  471. package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
  472. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +12 -0
  473. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +19 -5
  474. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
  475. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +62 -5
  476. package/src/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
  477. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
  478. package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
  479. package/src/orchestrator/templates/playwright.config.ts +7 -0
  480. package/src/orchestrator/templates/readme.md +1 -1
  481. package/src/orchestrator/templates/specs-checked-state.ts +324 -0
  482. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  483. package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
  484. package/src/orchestrator/templates/specs-url-assert.ts +101 -28
  485. package/src/orchestrator/templates/wdio.conf.ts +105 -1
  486. package/src/utils/selector-types.ts +54 -5
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { ApiCatalogEntry } from './types';
19
+ import { SENSITIVE_NAME, isSensitiveKey } from './sensitive-key';
19
20
 
20
21
  /** A single `@api:<name>(k=v,…)` invocation parsed from a scenario's tags. */
21
22
  export interface ApiCall {
@@ -372,12 +373,6 @@ function formatBodyFile(bodyFile: unknown, args: Record<string, string>, localiz
372
373
  return `${p} (${spec.mimeType || 'application/octet-stream'})`;
373
374
  }
374
375
 
375
- /**
376
- * Credential name matcher (substring, not anchored) for both body-field keys and
377
- * test-data keys — catches variants like confirmPassword / new_password /
378
- * password_confirmation / access_token / client_secret / otp / passcode.
379
- */
380
- export const SENSITIVE_NAME = /pass(word|code)?|passwd|pwd|secret|token|otp|api[-_]?key|apikey|credential|authorization|signature/i;
381
376
  function isSensitiveField(key: string): boolean {
382
377
  return SENSITIVE_NAME.test(key);
383
378
  }
@@ -387,11 +382,14 @@ function isSensitiveField(key: string): boolean {
387
382
  * over the finished Steps/Expected cells, closing the paths key-based masking misses
388
383
  * (nested request bodies, query-string credentials, and the Expected assertion cell).
389
384
  * Values shorter than 3 chars are skipped to avoid masking trivial/coincidental text.
385
+ * Sensitivity is judged by the key segment that NAMES the value (see isSensitiveKey): the
386
+ * test-data map also carries per-index array leaves (`tokens.0.label`), and matching the
387
+ * whole key would redact every field under a list whose name happens to be a credential word.
390
388
  */
391
389
  export function collectSecretValues(testData: Record<string, string>): string[] {
392
390
  const secrets: string[] = [];
393
391
  for (const [k, v] of Object.entries(testData)) {
394
- if (typeof v === 'string' && v.length >= 3 && SENSITIVE_NAME.test(k)) secrets.push(v);
392
+ if (typeof v === 'string' && v.length >= 3 && isSensitiveKey(k)) secrets.push(v);
395
393
  }
396
394
  // Longest-first so a longer secret is redacted before any shorter substring of it.
397
395
  return [...new Set(secrets)].sort((a, b) => b.length - a.length);
@@ -125,7 +125,7 @@ export function extractPriority(tags: string[]): string {
125
125
 
126
126
  /**
127
127
  * Extract testcase type from scenario tags.
128
- * Matches the bare `@manual` AND the reason-coded form `@manual:M1`..`@manual:M9`
128
+ * Matches the bare `@manual` AND the reason-coded form `@manual:M1`..`@manual:M11`
129
129
  * (the canonical way to tag manual scenarios). Without the `startsWith` check a
130
130
  * `@manual:M1` scenario was misclassified as `Auto` → rendered "Not compiled"/"N/A"
131
131
  * in the report instead of "Manual"/"Pending".
@@ -18,6 +18,7 @@ import {
18
18
  } from '../feature-parser';
19
19
  import { getCasesDatasetRows, resolveResultVariants } from '../result-variants';
20
20
  import { renderDynamicTokens } from '../test-data-resolver';
21
+ import { maskCredentialValue } from '../sensitive-key';
21
22
  import { classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction, runtimeVarsOf } from './wording';
22
23
  import { scenarioFingerprint, combinedFingerprint, mapContentFingerprint } from './fingerprint';
23
24
  import {
@@ -100,12 +101,13 @@ function resolveDataPairs(
100
101
  if (row) {
101
102
  for (const [k, v] of Object.entries(row)) {
102
103
  if (k.startsWith('__') || k === 'case' || k === 'name' || k === 'label') continue;
103
- pairs.push(`${k}: ${displayValue(String(v))}`);
104
+ pairs.push(`${k}: ${displayValue(maskCredentialValue(k, String(v)))}`);
104
105
  }
105
106
  }
106
107
  for (const v of vars) {
107
108
  const val = row && v in row ? undefined : testData?.[v]; // row columns already listed
108
- if (val !== undefined) pairs.push(`${v}: ${displayValue(val)}`);
109
+ // Credential-named refs (`accounts.0.password`) render `***` — this column is read outside the run.
110
+ if (val !== undefined) pairs.push(`${v}: ${displayValue(maskCredentialValue(v, val))}`);
109
111
  }
110
112
  return pairs;
111
113
  }
@@ -168,8 +170,16 @@ export function deriveVariants(inputs: Pick<BuildInputs, 'feature' | 'merged' |
168
170
  // Values the scenario itself produces during the run — captured from the page
169
171
  // or bound by a capability precondition. The compiler registers these as
170
172
  // "captured" and skips YAML validation; the matrix does the same.
173
+ // Background steps count too: scenario-merger keeps them OUT of resolvedSteps
174
+ // (they render as Pre-condition, not Steps), so a Background `remember … as
175
+ // {{v}}` registered nowhere — its token then survived into every scenario's
176
+ // shared precondition cell and Gate D blocked the entire unit (#684).
171
177
  const runtimeVars = runtimeVarsOf(
172
- [...m.resolvedSteps.map((st) => st.text), ...m.resolvedExpected.map((st) => st.text)],
178
+ [
179
+ ...inputs.feature.backgroundOrderedSteps.map((st) => st.text),
180
+ ...m.resolvedSteps.map((st) => st.text),
181
+ ...m.resolvedExpected.map((st) => st.text),
182
+ ],
173
183
  tags,
174
184
  );
175
185
 
@@ -500,6 +510,10 @@ export function varyingTriggerNote(dimensions: string | undefined, count: number
500
510
  // before the dash is the axis name.
501
511
  const axis = dimensions?.split('—')[0].trim();
502
512
  const where = axis && axis.length > 0 && axis.length <= 60 ? ` by ${axis}` : '';
513
+ // A single variant is reachable when an item is projected onto one execution
514
+ // mode (the Auto / Manual split) and only one of its variants lands there —
515
+ // nothing "differs" any more, so point at the one row that carries the steps.
516
+ if (count === 1) return `The action is the variant's own — see the row below.`;
503
517
  return `The action differs${where} — run each of the ${count} variants below with its own steps.`;
504
518
  }
505
519
 
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Cell-text derivation shared by the XLSX and CSV matrix renderers — the
3
+ * parent-row summaries (mode, result, coverage) and the small formatters that
4
+ * turn model arrays into readable cell content.
5
+ */
6
+
7
+ import { DeliveryItem } from './types';
8
+
9
+ /** Mode is a coverage dimension: mixed items show both counts. */
10
+ export function itemModeLabel(item: DeliveryItem): string {
11
+ if (item.mode === 'mixed') {
12
+ const auto = item.variants.filter((v) => v.mode === 'auto').length;
13
+ return `Auto ${auto} · Manual ${item.variants.length - auto}`;
14
+ }
15
+ return item.mode === 'manual' ? 'Manual' : 'Auto';
16
+ }
17
+
18
+ /**
19
+ * Parent roll-up label. It must NEVER be exactly one of the variant state words
20
+ * (`Passed`/`Failed`/`Blocked`/`Pending`/`N/A`) — the summary band counts the
21
+ * Result column with exact-match COUNTIF over parent AND child rows, so a parent
22
+ * reading plain "Pending" was counted as an extra pending variant (109 instead
23
+ * of 77 on st-login).
24
+ */
25
+ export function itemResultLabel(item: DeliveryItem): string {
26
+ const { passed, failed, blocked, na } = item.resultCounts;
27
+ const eff = item.variants.length - na;
28
+ const suffix = na > 0 ? ` · ${na} N/A` : '';
29
+ if (item.result === 'na') return 'All N/A';
30
+ if (item.result === 'failed') return `${passed}/${eff} Passed · ${failed} Failed${suffix}`;
31
+ if (item.result === 'blocked') return `${passed}/${eff} Passed · ${blocked} Blocked${suffix}`;
32
+ return `${passed}/${eff} Passed${suffix}`;
33
+ }
34
+
35
+ /**
36
+ * Compact coverage summary for the parent row. A declared `dimensions:` digest
37
+ * wins ("15 variants — required ×3 · format ×9 …"); otherwise a single variant
38
+ * shows its condition and a group falls back to its variant REFS. Concatenating
39
+ * every variant's full title turned this cell into an 800-character paragraph.
40
+ *
41
+ * On a projected item (the Auto / Manual split) the count reflects the variants
42
+ * present in THAT sheet — a mixed item reads "1 variant" on each side, never
43
+ * the item's full total.
44
+ */
45
+ export function coverageSummary(item: DeliveryItem): string {
46
+ const n = item.variants.length;
47
+ const head = `${n} variant${n === 1 ? '' : 's'}:`;
48
+ if (item.dimensions) return `${head} ${item.dimensions}`;
49
+ if (n === 1) return `${head} ${item.variants[0].condition}`;
50
+ return `${head} ${item.variants.map((v) => v.ref).join(' · ')}`;
51
+ }
52
+
53
+ /** ISO date (2026-08-04) — unambiguous across locales (review §9). */
54
+ export function isoDate(startTime: string | undefined): string {
55
+ if (!startTime) return '';
56
+ const d = new Date(startTime);
57
+ if (isNaN(d.getTime())) return '';
58
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
59
+ }
60
+
61
+ export function numbered(lines: string[], start = 1): string {
62
+ if (lines.length === 0) return '';
63
+ if (lines.length === 1 && start === 1) return lines[0];
64
+ return lines.map((l, i) => `${start + i}. ${l}`).join('\n');
65
+ }
66
+
67
+ /** Expected cell = observable outcomes; the HOW moves under a separate heading. */
68
+ export function expectedWithVerification(oracle: string[], verification: string[]): string {
69
+ const expected = numbered(oracle);
70
+ if (verification.length === 0) return expected;
71
+ return `${expected}\nVerification method: ${verification.join(' ')}`;
72
+ }
73
+
74
+ /** The Testcase type cell (column K) — mode plus why it is what it is. */
75
+ export function variantTypeLabel(v: DeliveryItem['variants'][number]): string {
76
+ if (v.mode === 'manual') return `Manual${v.manualReason ? ` (${v.manualReason})` : ''}`;
77
+ return v.mocked ? 'Auto · Mocked' : 'Auto';
78
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Sheet 4 — Coverage: requirement coverage (id ↔ items ↔ status), the
3
+ * target × category grid (with explicit `—` gaps), the mock-verified case
4
+ * tracker, dispositions, and the generation manifest.
5
+ *
6
+ * Reports on the WHOLE model — it is deliberately not split by execution mode,
7
+ * because a requirement is covered or not regardless of who runs the case.
8
+ *
9
+ * Moved out of `render-xlsx.ts` unchanged when the workbook grew to four sheets.
10
+ */
11
+
12
+ import * as ExcelJS from 'exceljs';
13
+ import { renderReportHeaderBand } from '../xlsx-report-builder';
14
+ import { variantState } from './build';
15
+ import { MatrixModel, MapCategory } from './types';
16
+ import { formatTestOutcome, resultTotals } from './result-totals';
17
+ import { GAP_FILL, SOFT_BLUE, dataCell, headerCell, draftBanner } from './xlsx-cells';
18
+
19
+ const CATEGORY_ORDER: MapCategory[] = ['normal', 'abnormal', 'security', 'nfr'];
20
+
21
+ export function addCoverageSheet(wb: ExcelJS.Workbook, model: MatrixModel, sungenVersion?: string): void {
22
+ const ws = wb.addWorksheet('Coverage');
23
+ ws.columns = [
24
+ { width: 3 }, { width: 22 }, { width: 18 }, { width: 16 }, { width: 16 }, { width: 16 },
25
+ { width: 16 }, { width: 44 },
26
+ ];
27
+ renderReportHeaderBand(wb, ws, `${model.unit.toUpperCase()} COVERAGE OVERVIEW`, sungenVersion, model.formNo);
28
+ draftBanner(ws, model);
29
+
30
+ let rowIdx = 7;
31
+
32
+ // --- Performance budget (#569) — advisory: config (qa/perf.yaml) vs measured runs.
33
+ if (model.perf) {
34
+ const p = model.perf;
35
+ dataCell(ws.getRow(rowIdx).getCell(2), 'Performance budget', { bold: true });
36
+ rowIdx++;
37
+ const r = ws.getRow(rowIdx);
38
+ dataCell(r.getCell(2), `p${p.percentile} ${p.metric.replace('_ms', ' time')}`);
39
+ dataCell(r.getCell(3), p.pass ? 'PASS' : 'OVER BUDGET', { bold: true });
40
+ dataCell(r.getCell(4), `measured ${p.measuredMs}ms`);
41
+ dataCell(r.getCell(5), `budget ${p.budgetMs}ms`);
42
+ dataCell(r.getCell(6), `${p.samples} runs`);
43
+ if (!p.pass && p.slowest.length) dataCell(r.getCell(8), `slowest: ${p.slowest.map((x) => `${x.ms}ms ${x.title.slice(0, 40)}`).join(' · ')}`);
44
+ rowIdx += 2;
45
+ }
46
+
47
+ // --- External Dependency Tracking (#580, ISTQB system-test practice): every case that
48
+ // ran against a MOCK is listed centrally. Its Result stays in the canonical vocabulary
49
+ // (the roll-up formulas depend on it); THIS table is where "Pass (Mocked) ≠ Pass (Real)"
50
+ // is visible — a case verified only against mocks is not Done until re-verified real.
51
+ {
52
+ const mockedVars = model.items.flatMap((it) => it.variants.filter((v) => v.mocked).map((v) => ({ it, v })));
53
+ if (mockedVars.length > 0) {
54
+ dataCell(ws.getRow(rowIdx).getCell(2), 'External Dependency Tracking — mock-verified cases (Pass here = Pass (Mocked), not Pass (Real))', { bold: true });
55
+ rowIdx++;
56
+ const hd = ws.getRow(rowIdx);
57
+ dataCell(hd.getCell(2), 'Variant');
58
+ dataCell(hd.getCell(3), 'Mocked dependency');
59
+ dataCell(hd.getCell(4), 'Delivery item');
60
+ dataCell(hd.getCell(5), 'Result');
61
+ dataCell(hd.getCell(8), 'Real-environment re-verify');
62
+ rowIdx++;
63
+ for (const { it, v } of mockedVars) {
64
+ const r = ws.getRow(rowIdx);
65
+ dataCell(r.getCell(2), v.ref);
66
+ dataCell(r.getCell(3), (v.mockDeps ?? []).join(', ') || '(scenario-level @mock)');
67
+ dataCell(r.getCell(4), it.id);
68
+ dataCell(r.getCell(5), variantState(v));
69
+ dataCell(r.getCell(8), '');
70
+ rowIdx++;
71
+ }
72
+ rowIdx++;
73
+ }
74
+ }
75
+
76
+ // --- Requirement coverage (review §6): every requirement id gets ONE explicit status.
77
+ if (model.requirements.length > 0) {
78
+ dataCell(ws.getRow(rowIdx).getCell(2), 'Requirement coverage — every id has an explicit status', { bold: true });
79
+ rowIdx += 1;
80
+ const rh = ws.getRow(rowIdx);
81
+ ws.mergeCells(rowIdx, 4, rowIdx, 6);
82
+ headerCell(rh.getCell(2), 'Requirement');
83
+ headerCell(rh.getCell(3), 'Status');
84
+ headerCell(rh.getCell(4), 'Delivery items');
85
+ headerCell(rh.getCell(7), 'Variants');
86
+ headerCell(rh.getCell(8), 'Note');
87
+ rowIdx += 1;
88
+ for (const r of model.requirements) {
89
+ const row = ws.getRow(rowIdx++);
90
+ dataCell(row.getCell(2), r.id, { bold: true });
91
+ dataCell(row.getCell(3), r.status, {
92
+ center: true,
93
+ ...(r.status === 'gap' ? { fill: GAP_FILL, bold: true } : {}),
94
+ });
95
+ ws.mergeCells(rowIdx - 1, 4, rowIdx - 1, 6);
96
+ dataCell(row.getCell(4), r.items.join(', '));
97
+ dataCell(row.getCell(7), r.variantCount > 0 ? String(r.variantCount) : '', { center: true });
98
+ dataCell(row.getCell(8), r.note);
99
+ }
100
+ rowIdx += 1;
101
+ }
102
+
103
+ // --- Target × category grid ---
104
+ dataCell(ws.getRow(rowIdx).getCell(2), 'Coverage matrix — delivery items (coverage variants) per target × category', { bold: true });
105
+ rowIdx += 1;
106
+ const gridHeader = ws.getRow(rowIdx++);
107
+ headerCell(gridHeader.getCell(2), 'Target');
108
+ CATEGORY_ORDER.forEach((cat, i) => headerCell(gridHeader.getCell(3 + i), cat));
109
+ headerCell(gridHeader.getCell(3 + CATEGORY_ORDER.length), 'Total');
110
+
111
+ const targets = Array.from(new Set(model.items.map((i) => i.target)));
112
+ for (const target of targets) {
113
+ const row = ws.getRow(rowIdx++);
114
+ dataCell(row.getCell(2), target, { bold: true });
115
+ let totalItems = 0;
116
+ let totalVariants = 0;
117
+ CATEGORY_ORDER.forEach((cat, i) => {
118
+ const cell = row.getCell(3 + i);
119
+ const items = model.items.filter((it) => it.target === target && it.category === cat);
120
+ const variants = items.reduce((a, it) => a + it.variants.length, 0);
121
+ totalItems += items.length;
122
+ totalVariants += variants;
123
+ // `—` is an explicit statement: this target × category cell has NO coverage.
124
+ dataCell(cell, items.length === 0 ? '—' : `${items.length} (${variants})`, {
125
+ center: true,
126
+ ...(items.length === 0 ? { fill: GAP_FILL } : {}),
127
+ });
128
+ });
129
+ dataCell(row.getCell(3 + CATEGORY_ORDER.length), `${totalItems} (${totalVariants})`, { center: true, bold: true });
130
+ }
131
+
132
+ // --- Dispositions ---
133
+ if (model.dispositions.length > 0) {
134
+ rowIdx += 1;
135
+ dataCell(ws.getRow(rowIdx).getCell(2), 'Not in the matrix (explicit dispositions)', { bold: true });
136
+ rowIdx += 1;
137
+ const dh = ws.getRow(rowIdx++);
138
+ ['Source', 'Disposition', 'Reason', 'Title'].forEach((l, i) => headerCell(dh.getCell(2 + i), l));
139
+ for (const d of model.dispositions) {
140
+ const row = ws.getRow(rowIdx++);
141
+ dataCell(row.getCell(2), d.vpId);
142
+ dataCell(row.getCell(3), d.as, { center: true });
143
+ dataCell(row.getCell(4), d.reason);
144
+ dataCell(row.getCell(5), d.title);
145
+ }
146
+ }
147
+
148
+ // --- Manifest ---
149
+ rowIdx += 1;
150
+ dataCell(ws.getRow(rowIdx).getCell(2), 'Generation manifest', { bold: true });
151
+ rowIdx += 1;
152
+ const m = model.manifest;
153
+ const manifestRows: Array<[string, string]> = [
154
+ ['Unit', m.unit],
155
+ ['Review state (design approval)', m.approvalState],
156
+ ['Test outcome', formatTestOutcome(resultTotals(model.items))],
157
+ ['Delivery items', String(m.itemCount)],
158
+ ['Coverage variants', String(m.variantCount)],
159
+ ['Source fingerprint', m.sourceFingerprint],
160
+ ['Map fingerprint', m.mapFingerprint],
161
+ ['Transformer version', m.transformerVersion],
162
+ ['Max variants / item (warn)', String(m.thresholds.maxVariantsPerItem)],
163
+ ];
164
+ for (const [k, v] of manifestRows) {
165
+ const row = ws.getRow(rowIdx++);
166
+ dataCell(row.getCell(2), k, { bold: true, fill: SOFT_BLUE });
167
+ ws.mergeCells(rowIdx - 1, 3, rowIdx - 1, 7);
168
+ dataCell(row.getCell(3), v);
169
+ }
170
+ }
@@ -10,6 +10,7 @@ import { clipNoteField } from '../playwright-report-parser';
10
10
  import { variantState } from './build';
11
11
  import { coverageSummary, itemModeLabel, itemResultLabel } from './render-xlsx';
12
12
  import { MatrixModel } from './types';
13
+ import { formatTestOutcome, resultTotals } from './result-totals';
13
14
 
14
15
  const HEADERS = [
15
16
  'Level', 'ID', 'Target', 'Test Intent / Condition', 'Category', 'Priority',
@@ -33,7 +34,8 @@ export function renderMatrixCsv(model: MatrixModel): string {
33
34
  const BOM = '\ufeff'; // Excel compatibility (Vietnamese/Japanese content)
34
35
  const lines: string[] = [];
35
36
  lines.push(`# Test Case & Coverage Matrix — ${model.unit}`);
36
- lines.push(`# delivery items: ${model.manifest.itemCount} · coverage variants: ${model.manifest.variantCount} · state: ${model.manifest.approvalState}`);
37
+ // Two different facts, two different words: the map's design REVIEW state, and the runs' OUTCOME.
38
+ lines.push(`# delivery items: ${model.manifest.itemCount} · coverage variants: ${model.manifest.variantCount} · review: ${model.manifest.approvalState} (design) · test outcome: ${formatTestOutcome(resultTotals(model.items))}`);
37
39
  lines.push(HEADERS.map(esc).join(','));
38
40
 
39
41
  for (const item of model.items) {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Sheet 1 — Overview: the roll-up table over the Auto and Manual sheets.
3
+ *
4
+ * Thin adapter. The table itself is `addOverviewSheet` in the shared report
5
+ * builder — the same one the legacy path uses — so both delivery formats show
6
+ * the customer one identical summary. All this module does is turn the matrix
7
+ * model plus the per-sheet item projections into the summary shape it wants.
8
+ *
9
+ * Every count in that table is a LIVE reference into the detail sheet's band
10
+ * (`'Auto'!D7`), never a frozen number, so it recomputes when QA edits results.
11
+ */
12
+
13
+ import * as ExcelJS from 'exceljs';
14
+ import { addOverviewSheet } from '../xlsx-report-builder';
15
+ import { DeliveryItem, MatrixModel } from './types';
16
+ import { resultTotals } from './result-totals';
17
+
18
+ /** One detail sheet: its tab name and the items projected onto it. */
19
+ export interface SheetProjection {
20
+ sheetName: string;
21
+ items: DeliveryItem[];
22
+ }
23
+
24
+ /**
25
+ * `total` counts VARIANTS only — parent rows are delivery items, not executable
26
+ * cases. It must match what the detail band's `C7=COUNTA(K14:K…)` counts, since
27
+ * a cached value and the live formula beside it have to tell one story. See
28
+ * `testcase-summary-band.ts` for why the count anchors on column K.
29
+ */
30
+ function summaryOf(unit: string, items: DeliveryItem[]): {
31
+ screen: string; total: number; passed: number; failed: number; pending: number; na: number;
32
+ } {
33
+ // The Overview form has no Blocked column: a blocked variant stays inside `Remain` (total − passed − N/A),
34
+ // exactly as the sheet's live band counts it.
35
+ const t = resultTotals(items);
36
+ return { screen: unit, total: t.total, passed: t.passed, failed: t.failed, pending: t.pending, na: t.na };
37
+ }
38
+
39
+ export function addMatrixOverviewSheet(
40
+ wb: ExcelJS.Workbook,
41
+ model: MatrixModel,
42
+ sheets: SheetProjection[],
43
+ sungenVersion?: string,
44
+ ): void {
45
+ addOverviewSheet(
46
+ wb,
47
+ sheets.map((s) => ({ sheetName: s.sheetName, summary: summaryOf(model.unit, s.items) })),
48
+ sungenVersion,
49
+ { titleLabel: `${model.unit.toUpperCase()} TEST CASE & COVERAGE MATRIX`, formNo: model.formNo },
50
+ );
51
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * One BM-2-901-13 testcase sheet of the matrix workbook (Auto or Manual).
3
+ *
4
+ * Layout is the company template, cell for cell: masthead rows 1-4, the summary
5
+ * band on rows 6/7/8, the `*: Mandatory` legend on row 12, the 16 column headers
6
+ * `A..P` on row 13, and data from row 14.
7
+ *
8
+ * Two row levels: one parent row per delivery item, each followed by its variant
9
+ * sub-rows on Excel outline level 1 — the sub-row is where source traceability
10
+ * (VP-id), test data and the manual result/evidence entry live (review B-05/B-06).
11
+ * Variant Result cells carry the state dropdown; the parent Result is a live
12
+ * formula over its children, so a parent can never contradict its variants even
13
+ * after QA edits results by hand.
14
+ */
15
+
16
+ import * as ExcelJS from 'exceljs';
17
+ import { renderReportHeaderBand, ARIAL_FONT } from '../xlsx-report-builder';
18
+ import { redactSecrets } from '../api-testcase-formatter';
19
+ import { clipNoteField } from '../playwright-report-parser';
20
+ import { variantState } from './build';
21
+ import { DeliveryItem, MatrixModel } from './types';
22
+ import { SOFT_BLUE, DRAFT_RED, RESULT_STATES, dataCell, headerCell, draftBanner } from './xlsx-cells';
23
+ import { addSummaryBand } from './testcase-summary-band';
24
+ import {
25
+ coverageSummary, expectedWithVerification, isoDate, itemResultLabel, numbered, variantTypeLabel,
26
+ } from './item-labels';
27
+
28
+ /** Column headers on row 13 — `A..P`, matching the template exactly. */
29
+ export const TESTCASE_HEADERS = [
30
+ 'ID*', 'Target', 'Test Intent / Condition', 'Category', 'Priority', 'Precondition',
31
+ 'Coverage / Test Data', 'Action / Trigger*', 'Expected results*', 'Trace', 'Testcase type',
32
+ 'Test Result*', 'Executed Date*', 'Test Executor*', 'Test Environment',
33
+ 'Note\n(Test evidence, DefectID, Actual result)',
34
+ ];
35
+
36
+ /** Column widths `A..P`, from the template (H widened — Action / Trigger is the longest cell). */
37
+ const COLUMN_WIDTHS = [20, 12.5, 15.38, 16.25, 17, 25.51, 24.88, 50, 41, 12, 13.5, 15.88, 13.5, 16.5, 23, 29];
38
+
39
+ export const HEADER_ROW = 13;
40
+ /** Column L — the one the band's COUNTIFs and the parent roll-ups read. */
41
+ export const RESULT_COL = 12;
42
+
43
+ /** Variant sub-row — only what distinguishes it from its parent (review GAP-01). */
44
+ function variantValues(item: DeliveryItem, v: DeliveryItem['variants'][number], model: MatrixModel): ExcelJS.CellValue[] {
45
+ return [
46
+ v.ref,
47
+ '',
48
+ v.condition,
49
+ '', '',
50
+ numbered(item.preconditionDeltas[v.ref] ?? []),
51
+ numbered(v.data),
52
+ // Only the steps after the parent's shared prefix, numbered so the parent's
53
+ // steps + these read as one continuous procedure.
54
+ numbered(item.triggerDeltas[v.ref] ?? [], item.trigger.length + 1),
55
+ expectedWithVerification(v.oracle, v.verification),
56
+ v.traces.join(', '),
57
+ variantTypeLabel(v),
58
+ variantState(v),
59
+ isoDate(v.result?.startTime),
60
+ '',
61
+ '',
62
+ // Redact BEFORE clipping — same denylist as the testcases CSV Note column.
63
+ v.result?.error ? clipNoteField(redactSecrets(String(v.result.error), model.secretValues)) : '',
64
+ ];
65
+ }
66
+
67
+ /**
68
+ * Live parent roll-up over the child Result cells — precedence
69
+ * failed → blocked → pending → partial → passed. N/A variants leave the
70
+ * denominator (review GAP-06): eff = n − COUNTIF(N/A); all-N/A → "All N/A".
71
+ * The range covers only the children present in THIS sheet, so a mixed item's
72
+ * two parent rows legitimately report two different numbers.
73
+ */
74
+ function rollUpFormula(n: number, firstChild: number, lastChild: number): string {
75
+ const rng = `L${firstChild}:L${lastChild}`;
76
+ const EFF = `(${n}-COUNTIF(${rng},"N/A"))`;
77
+ const NA = `IF(COUNTIF(${rng},"N/A")>0," · "&COUNTIF(${rng},"N/A")&" N/A","")`;
78
+ return (
79
+ `IF(COUNTIF(${rng},"Failed")>0,COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed · "&COUNTIF(${rng},"Failed")&" Failed"&${NA},` +
80
+ `IF(COUNTIF(${rng},"Blocked")>0,COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed · "&COUNTIF(${rng},"Blocked")&" Blocked"&${NA},` +
81
+ `IF(${EFF}=0,"All N/A",` +
82
+ `COUNTIF(${rng},"Passed")&"/"&${EFF}&" Passed"&${NA})))`
83
+ );
84
+ }
85
+
86
+ /** Parent row — only values genuinely COMMON to the whole item (review M-04). */
87
+ function parentValues(item: DeliveryItem, formula: string): ExcelJS.CellValue[] {
88
+ return [
89
+ item.id,
90
+ item.target,
91
+ item.intent,
92
+ item.category,
93
+ item.priority,
94
+ numbered(item.precondition),
95
+ coverageSummary(item),
96
+ // The shared leading steps; each variant's remaining steps sit on its sub-row.
97
+ numbered(item.trigger),
98
+ item.oracle,
99
+ item.traces.join(', '),
100
+ // K (Testcase type) stays blank on a parent — it is not an executable case.
101
+ '',
102
+ { formula, result: itemResultLabel(item) } as ExcelJS.CellValue,
103
+ '',
104
+ '',
105
+ '',
106
+ item.review === 'proposed' ? 'REVIEW REQUIRED' : '',
107
+ ];
108
+ }
109
+
110
+ /**
111
+ * Append one testcase sheet holding `items` (already projected onto this sheet's
112
+ * execution mode). Always called for both Auto and Manual, even when one side is
113
+ * empty: Overview then always has exactly two rows, and QA has somewhere to add
114
+ * hand-written cases. An empty sheet's band reads 0 and its percentages resolve
115
+ * through IFERROR — never `#DIV/0!`.
116
+ */
117
+ export function addTestcaseSheet(
118
+ wb: ExcelJS.Workbook,
119
+ sheetName: string,
120
+ model: MatrixModel,
121
+ items: DeliveryItem[],
122
+ sungenVersion?: string,
123
+ ): void {
124
+ const ws = wb.addWorksheet(sheetName);
125
+ ws.properties.outlineLevelRow = 1;
126
+ ws.columns = COLUMN_WIDTHS.map((width) => ({ width }));
127
+
128
+ renderReportHeaderBand(wb, ws, `${model.unit.toUpperCase()} TEST CASE & COVERAGE MATRIX`, sungenVersion, model.formNo);
129
+ draftBanner(ws, model);
130
+ addSummaryBand(ws, items);
131
+
132
+ const legend = ws.getCell('A12');
133
+ legend.value = '*: Mandatory';
134
+ legend.font = { italic: true, size: 10, name: ARIAL_FONT, color: { argb: 'FF808080' } };
135
+
136
+ const hr = ws.getRow(HEADER_ROW);
137
+ hr.height = 38;
138
+ TESTCASE_HEADERS.forEach((label, i) => headerCell(hr.getCell(1 + i), label));
139
+
140
+ let rowIdx = HEADER_ROW + 1;
141
+ for (const item of items) {
142
+ const parentRowIdx = rowIdx++;
143
+
144
+ // Variant sub-rows FIRST — the parent's formula needs their cell range.
145
+ const firstChild = rowIdx;
146
+ for (const v of item.variants) {
147
+ const vr = ws.getRow(rowIdx++);
148
+ vr.outlineLevel = 1;
149
+ variantValues(item, v, model).forEach((val, i) => dataCell(vr.getCell(1 + i), val, { center: i === 10 }));
150
+ // Manual-execution entry: constrain the Result cell to the known states.
151
+ vr.getCell(RESULT_COL).dataValidation = {
152
+ type: 'list', allowBlank: true, formulae: [`"${RESULT_STATES}"`],
153
+ };
154
+ }
155
+
156
+ const pr = ws.getRow(parentRowIdx);
157
+ const values = parentValues(item, rollUpFormula(item.variants.length, firstChild, rowIdx - 1));
158
+ values.forEach((v, i) => dataCell(pr.getCell(1 + i), v, {
159
+ bold: true,
160
+ fill: SOFT_BLUE,
161
+ center: i === 3 || i === 4,
162
+ ...(i === 15 && item.review === 'proposed' ? { color: DRAFT_RED } : {}),
163
+ }));
164
+ }
165
+
166
+ const lastRow = Math.max(rowIdx - 1, HEADER_ROW);
167
+ ws.autoFilter = { from: { row: HEADER_ROW, column: 1 }, to: { row: lastRow, column: 16 } };
168
+ ws.views = [{ state: 'frozen', xSplit: 0, ySplit: HEADER_ROW }];
169
+ // Print: landscape, fit width only (never squash all rows into one page),
170
+ // repeat the masthead + band + header rows on every page (review GAP-08).
171
+ ws.pageSetup = {
172
+ orientation: 'landscape',
173
+ fitToPage: true,
174
+ fitToWidth: 1,
175
+ fitToHeight: 0,
176
+ printTitlesRow: `1:${HEADER_ROW}`,
177
+ };
178
+ }