@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
@@ -13,7 +13,8 @@ user-invocable: false
13
13
  - **Actor**: Always `User`, always active voice.
14
14
  - **Value**: `with {{snake_case}}` — never hardcode static data.
15
15
  - **State**: `is <keyword>` — never use `{{}}` for states.
16
- - **Parent scope**: `in [Parent] parentType` — optional, only when page has 2+ similar blocks needing disambiguation.
16
+ - **Parent scope**: `in [Parent] parentType` — optional, only when page has 2+ similar blocks needing disambiguation. Container types: `table` `list` `list-item` `section` `dialog` `modal` `drawer` `overlay` `form` `card` `item` `row` `group` `panel` `region` `container` `tab` `menu`. Anything else is not a container, and the clause is DROPPED with `SG-W028` rather than silently widening the step to the whole page.
17
+ - **Parent scope BY CONTENT** (web only): `… with {{v}} in [Parent] parentType with {{v2}}` — the SECOND `with` picks the one container whose content is `{{v2}}`, for a list of look-alike cards/rows. One `with` after the clause still belongs to the TARGET (`see [first] row in [apply list] table with {{job_name}}`), so the by-content form always carries two. On the mobile adapter it reports `SG-W028` — an Appium parent chain has no content filter.
17
18
 
18
19
  ## Keyword → Action Rules
19
20
 
@@ -35,7 +36,7 @@ AND → inherits from preceding keyword
35
36
  ### Setup / Form / Interaction
36
37
 
37
38
  ```
38
- User is on [T] page | page with {{v}} | dialog
39
+ User is on [T] page | page with {{v}} | dialog # {{v}} = query/fragment ("?id=200", "#tab") APPENDED to the page value — a bare "200" onto "/x?id=199" gives "?id=199200"
39
40
  User fill [T] field | textarea | search | slider | date-picker with {{v}} # date-picker: web only unless qa/app.yaml declares widgets.datepicker — appium refuses it
40
41
  User fill [T] uploader with {{f}} [web]
41
42
  User clear [T] field
@@ -45,6 +46,8 @@ User select [T] dropdown with {{v}}
45
46
  User click [T] button | tab | column | breadcrumb
46
47
  User click [T] row with {{v}}
47
48
  User try to click [T] button | link # DISABLED element only — see rule below (v3.3) [web]
49
+ User click [T] overlay at position "top-left" # corner preset: top-left | top-right | bottom-left | bottom-right [web]
50
+ User click [T] overlay at offset x=24px, y=10% # px or % per axis, from the element's top-left [web]
48
51
  User double click [T] element
49
52
  User hover [T] icon | row # no-op on mobile (see Platform Support)
50
53
  User drag [T] to [T2]
@@ -57,7 +60,17 @@ User expand | collapse [T] row
57
60
  scenario asserts nothing happens (cooldown re-send, submit before the form is valid). `click` waits for
58
61
  `enabled` → it can only time out there; `try to click` compiles to `.click({ force: true })`. Always
59
62
  pair it with the proof: `Then User see [T] TYPE is disabled` + an assertion that the effect did NOT occur.
60
- NEVER use it for a click that is supposed to work — it deletes the actionability wait.
63
+ NEVER use it for a click that is supposed to work — it deletes the actionability wait. (`force: true` is
64
+ reachable ONLY through `try to click` — there is no separate `force` keyword.)
65
+
66
+ **click at position rule** (web only): a plain `click` lands on the element's CENTRE. To click a
67
+ modal backdrop where the dialog is not, or any control whose centre is covered, append
68
+ `at position "<corner>"` (5% in from that corner) or `at offset x=<n>px|%, y=<n>px|%`. The position
69
+ is checked against the element's real box at run time (an offset past it fails naming the size). The
70
+ suffix is refused at compile time (`SG-E026`, the step throws instead of clicking the centre) when it
71
+ is misspelt or unquoted, when a value is negative / above 100% / missing an axis, when anything
72
+ follows it, and when it is combined with `try to click`, a `with {{v}}` text filter, `… and accept [OK]
73
+ alert`, or `double click` — those forms take no position.
61
74
 
62
75
  ### Alert / Keyboard / Wait / Scroll
63
76
 
@@ -68,12 +81,11 @@ User click [OK | Cancel] alert # two-step form: must come BEF
68
81
  User fill [T] alert with {{v}} # no-op on mobile — native prompt fill is app-specific
69
82
  User see [message text] alert
70
83
  User press Escape key | [Enter] key | Tab key 5 times | Enter on [T] field
71
- User wait for N seconds | [T] page # [T] page: web waits for the URL; mobile pauses (settle) — see Platform Support
84
+ User wait for N seconds | [T] page # [T] page: web waits for the URL, then HOLDS it (see Pattern 8); mobile pauses (settle) — see Platform Support
72
85
  User wait for [T] TYPE is visible | hidden | enabled | disabled # ANY reference (v3.3)
73
86
  User wait for [T] TYPE with {{v}} # until it shows the value
74
87
  User wait for [T] table to refresh # filter/search/pagination round-trip (v3.3)
75
88
  User scroll to [T] section
76
- User switch to [T] frame | [main] frame # web: iframe; mobile: hybrid-app WebView context (no-op if the screen has no WebView)
77
89
  ```
78
90
 
79
91
  > **Browser alerts (native `window.confirm/alert/prompt` only):** prefer the compound form —
@@ -86,6 +98,36 @@ User switch to [T] frame | [main] frame # web: iframe; mobile: hybrid-
86
98
  > `wait for N seconds` stays a last resort. `table to refresh` watches the app's loading
87
99
  > indicator (`qa/app.yaml` `feedback.loading.indicator`, default `[aria-busy="true"]`).
88
100
 
101
+ ### Scope control
102
+
103
+ ```
104
+ User is on [T] dialog # ENTER dialog scope, rooted at [T]'s selectors.yaml entry
105
+ User use dialog # ENTER dialog scope, unnamed — plain page.getByRole('dialog')
106
+ User close dialog | dismiss dialog | exit dialog # EXIT dialog scope (three spellings, identical)
107
+ User switch to [T] frame # ENTER frame scope — web: iframe; mobile: hybrid-app WebView context
108
+ User switch to [main] frame # EXIT frame scope (no-op if the screen has no WebView)
109
+ ```
110
+
111
+ > **Dialog scope is STICKY — it ends only where you end it.** Once a scenario enters dialog scope,
112
+ > EVERY later locator in that scenario is prefixed with the dialog root, **including the steps that
113
+ > run after the dialog has closed**. Nothing implicit drops it: not a click on the dialog's own
114
+ > Close/OK button, and **not** `wait for [T] dialog is hidden` — that step asserts the dialog went
115
+ > away and then keeps resolving the following steps INSIDE it, which can never succeed. Only
116
+ > `close`/`dismiss`/`exit dialog`, or the start of the next scenario, drops the scope. The failure
117
+ > has a recognisable shape: a step after the dialog closed times out reading "element not found"
118
+ > while the element is plainly on screen — you are not missing a selector, you are still scoped
119
+ > inside a dead dialog.
120
+ >
121
+ > ```gherkin
122
+ > Given User is on [Confirm Delete] dialog # scope enters here — implicitly, but it still needs an EXPLICIT exit
123
+ > When User click [OK] button # scoped — correct, the button IS in the dialog
124
+ > And User exit dialog # ← REQUIRED: the click closed the dialog, the scope did not
125
+ > Then User see [Deleted] toast # page-rooted again
126
+ > ```
127
+ >
128
+ > Dialog scope is `[web]`-effective only — no Appium template reads `inDialog`, so on mobile the
129
+ > following steps resolve against the whole screen (`SG-W021`). Frame scope works on both.
130
+
89
131
  ### Positional table rows (v3.3) `[web]`
90
132
 
91
133
  ```
@@ -103,6 +145,9 @@ two content-filtered queries can hit different rows if the table re-renders in b
103
145
  ```
104
146
  # 1. Visibility: User see [T] type (NEVER add "is visible") | is hidden
105
147
  # 2. Text (toHaveText): User see [T] message | header | label with {{v}}
148
+ # `label` forks on the entry: declared by IDENTITY (xpath/accessibility-id/testid/id/css)
149
+ # → asserts THAT element contains {{v}} (add `exact: true` for a strict match); no entry,
150
+ # or `type: text` → matches the caption+value text pair anywhere on screen
106
151
  # 3. Partial (toContainText): User see [T] text contains {{v}}
107
152
  # 3b. Format (shape, not value): User see [T] label matches "yyyy/mm/dd ~ yyyy/mm/dd"
108
153
  # 4. Input (toHaveValue): User see [T] field | date-picker | search | slider with {{v}}
@@ -139,6 +184,20 @@ detail:
139
184
  anchor: { type: 'role', value: 'heading', name: 'Item Detail' } # the rendered-content proof
140
185
  ```
141
186
 
187
+ **`wait for [T] page` shares that hold, and only that.** Since 3.2.30 the wait compiles to
188
+ `waitForPage(page, {…})` on the same helper: it arrives, settles, holds the URL, and fails naming
189
+ where the user went — so the steps AFTER the wait no longer run against a page mid-redirect, and
190
+ `/vi/search` no longer resolves on `/vi/search-results`. Two differences from the `Then` side:
191
+
192
+ - **A wait does not constrain the query.** `see [Results] page` with no `with {{v}}` demands a bare
193
+ URL; `wait for [Results] page` after submitting a search tolerates the `?q=…` it lands on. Prove
194
+ the query in the `Then` that follows.
195
+ - **A wait is not an oracle.** It never counts toward a scenario's assertions — `ORACLE-URL-ONLY`
196
+ and the assertionless-test check both ignore it. Always follow it with a real `Then`.
197
+
198
+ An absolute page `value` (`https://app.example.com/reception/:id`) compiles its host into a
199
+ separate anchored `hostname:` — the same path on another host no longer satisfies the wait.
200
+
142
201
  **Pattern 8 — the page assertion judges pathname AND query together.** Both `see [T] page` and
143
202
  `is on [T] page` (Then-side) compile to ONE web-first check — `expectPage(page, {…})`, built on
144
203
  `toHaveURL(urlMatches({…}))` and retrying like any other assertion — comparing the **exact** pathname
@@ -153,6 +212,8 @@ detail:
153
212
  | `… page with {{v}}`, `v` = `/vi/search` (no query) | **path only**, and the compiler warns (`SG-W014`): the step asserts less than it reads as |
154
213
  | `… page with {{v}}`, `v` empty or key missing at run time | **throws, naming the key** — an unusable value is never a silent pass |
155
214
 
215
+ **Navigating** with data (`Given User is on [T] page with {{v}}`) appends `{{v}}` to the page selector's value **verbatim** — no merging, by design (the primary use is injecting malformed query input, which the compiler must not sanitize). So the page value carries the PATH only and `{{v}}` carries the whole query: `value: '/job/detail'` + `job_query: "?id=200"`. A page value that already holds `?id=199` plus `{{v}}` = `"200"` navigates to `?id=199200`. A rooted `{{v}}` (`/other/path`) replaces the page's path; an absolute URL replaces everything.
216
+
156
217
  So pass `with {{v}}` whenever the URL carries state worth proving, and leave it off when the page
157
218
  must be reached clean. A dynamic segment in the selector value (`/users/:id/profile`) wildcards to
158
219
  `[^/]+` on its own, still anchored — `/users/42/profile/edit` does not match. Params compare
@@ -206,6 +267,15 @@ User see [Table] table match data:
206
267
 
207
268
  Row scope: `see [Ref] row in [Table] table with {{v}}` enters scope. Subsequent `see [Col] column with {{v}}` checks cell in that row. Use `table match data:` for multi-row verification.
208
269
 
270
+ **[Col] does not have to be the DOM header text.** `see [Col] column in [T] table` looks for a header whose accessible name is the reference, which cannot match a page in another language. Keep the reference readable — it is what the delivered test case shows — and declare the real header on the table entry (or on a `<col>--columnheader` entry's `name:`, which also takes `{{variables}}` for per-locale text):
271
+ ```yaml
272
+ article list:
273
+ type: role
274
+ value: table
275
+ columns:
276
+ category: { index: 2, header: '記事分類' }
277
+ ```
278
+
209
279
  **[Ref] of a row step is a display label UNLESS declared in selectors.yaml.** Without an entry the step checks the value exists in ANY row — a name like `[First Contact Row]` does NOT position the row, and an ordering rule would pass even when the row moved (SG-W010 warns on positional-looking undeclared labels). To assert a POSITION, declare the row (a `type: locator` value RELATIVE to the table):
210
280
  ```yaml
211
281
  first contact row:
@@ -214,6 +284,15 @@ first contact row:
214
284
  ```
215
285
  → compiles to `expect(table.locator('tbody tr:first-child')).toContainText(v)` — the exact row must hold the value — and still enters row scope for `[Col] column` checks.
216
286
 
287
+ **A list built from `<div>`s is a table only if you tell sungen what a row is.** Every table step finds rows by `getByRole('row')`; a `<div>` list has none, so `is hidden` and `is empty` would pass on 0 rows (asserting nothing) and counts/clicks would time out. Declare the row SHAPE on the **table** entry — `rows:` is relative to the table and drives all seven table steps:
288
+ ```yaml
289
+ job table:
290
+ type: locator
291
+ value: 'div.job-list'
292
+ rows: 'div.job-row' # what EVERY row matches — NOT a row label's entry
293
+ ```
294
+ Two different keys, two meanings: `rows:` on the table = every row (the shape); a `type: locator` entry on a **row label** = ONE positioned row (`tbody tr:first-child`). A shape put on a row label fails at run time with a `toHaveCount(1)` message that points back here. A CSS-located table with no `rows:` gets `SG-W016`. On a shaped table `table row {{n}}` counts from the first data row (no header offset).
295
+
217
296
  ### Browser storage `[web]`
218
297
 
219
298
  ```
@@ -374,7 +453,7 @@ entirely (a *pattern* gap, not a template gap), which is what the catalogue belo
374
453
 
375
454
  **Mobile-only `[mobile]`** — the gesture catalog (swipe, long-press, pinch-zoom, pull-to-refresh,
376
455
  rotate, background/foreground, notifications, grant-permission, clipboard set, set-geolocation,
377
- hide-keyboard, tap-top-of) has no web counterpart, and neither do the lifecycle / device-state /
456
+ hide-keyboard, tap-top-of, `go back`) has no web counterpart, and neither do the lifecycle / device-state /
378
457
  timing steps (`restart the app`, `reinstall the app`, `turn airplane mode|wifi|mobile data on|off`,
379
458
  `turn device location on|off` — the latter two Android-only, throw on iOS — the window
380
459
  assertions `see [X] disappear within N seconds` / `still visible after N seconds`, and the
@@ -387,8 +466,9 @@ Full syntax → `sungen-mobile-gestures`.
387
466
  | Step | Web | Mobile |
388
467
  |---|---|---|
389
468
  | `see [T] page` \| `page with {{v}}` | asserts path+query | **silent no-op** — asserts nothing; the scenario passes regardless. Assert a screen-marker element instead |
390
- | `is on [T] page` \| `open [T] page` (Given/When) | navigates via URL | no-op — the app is already launched; use tap/gesture steps for mobile screen changes |
391
- | `wait for [T] page` | waits for the URL | fixed `driver.pause(500)` settle — not a real wait condition |
469
+ | `is on [T] page` \| `open [T] page` (Given/When) | navigates via URL | `is on` = the app-launch anchor, correctly a no-op. `open`/`navigate to` = a no-op that **reports `SG-W026`** — it promises a screen change and performs none; use tap/gesture steps instead |
470
+ | `wait for [T] page` | waits for the URL, then settles and HOLDS it (`waitForPage`) | fixed `driver.pause(500)` settle — not a real wait condition |
471
+ | `go back` (OS/hardware back) | **not a web step** — reports `SG-W020`; re-enter with `is on [X] page` or tag `@manual` | Android: `KEYCODE_BACK`, the real hardware key. iOS: WDA **navigation**-back, *not* the user's edge swipe-back (iOS has no hardware back). Distinct from `tap [Back] button`: system back skips the screen's own handler unless the app wires it |
392
472
  | `hover [T] icon \| row` | real hover | no-op — hover-revealed content is normally already visible on mobile; use `tap` |
393
473
  | `fill [T] alert with {{v}}` | fills native `prompt()` | no-op (comment only) — app-specific, handle manually |
394
474
  | `switch to [T] frame` | enters an `<iframe>` | switches a hybrid app's WebView context; no-op on a pure-native screen. **Needs `APPIUM_CHROMEDRIVER_AUTODOWNLOAD=1`** (.env.appium) or the switch fails with "No Chromedriver found". INSIDE the webview, selectors are **css/xpath only** — UiSelector-based types and the text-value asserts (`label with {{v}}`) don't apply there; assert element presence via css/xpath refs, then `switch to [main] frame` back |
@@ -477,7 +557,17 @@ award:
477
557
  | `upload` | — | — | file inputs |
478
558
  | `frame` | iframe selector | — | iframes |
479
559
 
480
- Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `columns`
560
+ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `columns` `checked` `selected`
561
+
562
+ `checked: { class: <cls> }` or `checked: { attribute: <attr> }` — how a checkbox-like control that is
563
+ NOT a real checkbox (a `<div>` tile with no `role`/`aria-checked`; `.check()` throws "Not a checkbox or
564
+ radio button") shows its state. `selected:` is an ALIAS of the same signal, for a card/tab picker where
565
+ `checked:` reads wrong; declare ONE spelling and ONE of class/attribute. `check`/`uncheck` click only
566
+ when the state differs, then assert it; `is checked` / `is unchecked` / `is selected` (plain or
567
+ `with {{v}}`) assert it. The class is read on the located element OR on a descendant (a checkmark
568
+ `<svg>` that only mounts when on); the attribute is read on the located element itself. Other states on
569
+ the same entry (`is focused`, …) keep their native matchers. The value must be ONE plain class token or
570
+ attribute name — whitespace, a quote or a backslash is refused at compile time, naming the entry.
481
571
 
482
572
  `scope` (e.g. `scope: dialog`) is `[web]`-effective only — no Appium template reads `inDialog`, so
483
573
  on mobile a dialog-scoped ref still resolves against the whole screen (`SG-W021`).
@@ -794,7 +884,32 @@ Only when `@cleanup:*` tags aren't enough — feature-specific logic.
794
884
 
795
885
  ### Layer 3: `@beforeAll` / `@afterAll` (optional)
796
886
 
797
- For one-time setup/teardown.
887
+ For one-time setup/teardown. A hook scenario takes `@api:` / `@query:` preconditions exactly like an
888
+ ordinary scenario (override args and `@concurrent:N` included) — they run inside the hook and bind
889
+ `{{name}}`:
890
+
891
+ ```gherkin
892
+ @beforeAll @api:login(email={{email}},password={{pw}}) @api:list_items(token={{login.body.token}})
893
+ Scenario: seed the session once before any scenario
894
+ Then expect {{login.status}} is 200
895
+ And expect {{list_items.status}} is 200
896
+
897
+ @afterAll @api:cleanup
898
+ Scenario: drop the seeded data after the last scenario
899
+ Then expect {{cleanup.status}} is 204
900
+ ```
901
+
902
+ - A variable bound in `@beforeAll` (`{{login.body.token}}`) is readable from EVERY scenario of the
903
+ feature (it runs before each of them). `@afterEach`/`@afterAll` bindings stay hook-local.
904
+ - A failing hook skips every test in the file, so hook steps are COLLECTED: each `Then` that fails is
905
+ recorded and the next step still runs; a failing action step (`Given`/`When`) stops the hook there.
906
+ The hook fails ONCE with the full list (`Hook preconditions not met`), not with the first failure.
907
+ - `@cases` and `@extend` are not supported on a hook — they are ignored with a compile warning.
908
+ - A hook has no `page` fixture on a per-scenario (non-`@serial`) feature — keep its steps to `@api:`/
909
+ `@query:` preconditions and `expect` assertions there.
798
910
 
799
911
  **Rendering order in `.spec.ts`:**
800
- `test.describe` → `test.use(storageState)` → `test.use(autoCleanup)` → `test.beforeAll` → `test.beforeEach` → `test.afterEach` → `test.afterAll` → `test()` blocks
912
+ `test.describe` → `test.use(storageState)` → `test.use(autoCleanup)` → `test.beforeAll` → `test.beforeEach` → `test.afterEach` → `test.afterAll` → `test()` blocks.
913
+ On a `@serial` feature the user `@beforeAll`/`@afterAll` blocks are registered right after the built-in
914
+ `beforeAll` (page creation) and BEFORE the built-in `afterAll` (page close): Playwright runs `afterAll`
915
+ hooks in registration order, so the shared `page` is still open for the user's teardown steps.
@@ -17,6 +17,21 @@ patterns (`click`, `hover`, `fill`) don't cover.
17
17
  > - **`swipe <dir> on [X]`** → `mobile: swipeGesture`.
18
18
  > - **`long-press [X] [for N seconds]`** → `mobile: longClickGesture`.
19
19
  > - **`rotate to landscape|portrait`** → `driver.setOrientation(...)`.
20
+ > - **`go back`** (also `goes back` · `press the back button` · `hardware|system|device back` ·
21
+ > `back gesture` · `swipe back`) → Android `mobile: pressKey` with keycode 4 (KEYCODE_BACK — the
22
+ > real hardware key); iOS `driver.back()`, which is WebDriverAgent's **navigation** back and *not*
23
+ > the user's edge swipe-back gesture — iOS has no hardware back key. **This is NOT `tap [Back]`:**
24
+ > Android's system back does not run the screen's own handler unless the app wires it
25
+ > (`onBackPressed` / `BackHandler`), so a defect on the OS path is invisible to an in-app tap —
26
+ > which is why the viewpoint ledger refuses the in-app button as an answer to its "OS/hardware
27
+ > back gesture" theme. The step takes **no `[Ref]`**; that absence is what selects it. A phrasing
28
+ > that names a target (`go back to [List] screen`) is refused rather than reinterpreted, so pick
29
+ > one form: `go back`, or a tap.
30
+ > **`swipe back` is only a spelling of this step, not a swipe.** It compiles to the same
31
+ > `KEYCODE_BACK` / `driver.back()` as every other phrasing above, so on iOS — where the edge
32
+ > swipe-back *is* what a real user does — a scenario named for the gesture never performs it. If
33
+ > the edge swipe itself is under test, that needs a left-edge `mobile: swipe`, which no step
34
+ > currently emits: tag it `@manual` rather than letting the wording imply coverage it does not have.
20
35
  > - **`pull-to-refresh on [X]`** → fast `mobile: swipeGesture` (direction down).
21
36
  > - **`pinch-zoom in|out on [X]`** → `mobile: pinchOpenGesture` / `pinchCloseGesture`.
22
37
  > - **`send app to background for N seconds`** → `driver.background(N)`.
@@ -108,7 +123,7 @@ from `appium_find_element`; screen gestures pass `direction` or coordinates.
108
123
  | Scroll a list | `User scroll down on [Feed]` | `action=scroll, direction=down` |
109
124
  | Scroll until visible | `User scroll to [Footer]` | `action=scroll_to_element, strategy, selector, direction` |
110
125
  | Pinch zoom in/out | `User pinch-zoom in on [Map]` | `action=pinch_zoom, elementUUID, scale` (>1 in, <1 out) |
111
- | System back | `User go back` | `action=back` |
126
+ | System back | `User go back` | `action=back` — **compiles** (see codegen status; per-OS) |
112
127
  | Drag & drop | `User drag [A] onto [B]` | `appium_drag_and_drop` (separate tool) |
113
128
 
114
129
  Other device-level actions (separate MCP tools for exploration; Gherkin now compiles — see codegen
@@ -96,6 +96,7 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
96
96
  - Toast/alert named from content (no aria-label) → `feedback.toast: { role: status, named_by: content }` + `name: null` on the selector entries.
97
97
  - Loading indicator for `wait for [T] table to refresh` → `feedback.loading.indicator` (skip if the app sets `aria-busy`).
98
98
  - **Every qa/app.yaml edit MUST carry provenance** in `detection:`: who/when (`detected_by`) and the observed evidence per key (`evidence:`). A profile without evidence is a guess a human cannot review.
99
+ 7c. **Checkbox-like control that is not a checkbox** (`.check()` failed with `Not a checkbox or radio button`; the snapshot shows a `<div>`/`<button>` tile with no `role="checkbox"` and no `aria-checked`): keep the locator that finds the tile and add the state signal to the SAME entry — `checked: { class: '<class present when on>' }` (a class on that element itself, e.g. `bg-selected`) or `checked: { attribute: '<attr>' }` (a `'true'`/`'false'` attribute such as `data-checked`); on a card/tab picker write `selected:` instead — same signal, different spelling. This is what makes `check`/`uncheck`/`is checked`/`is selected` compile for the tile. ONE plain class token or attribute name only — whitespace or a quoted fragment is refused at compile time, naming the entry. Same shape as `expanded:` for accordions; `checked.class` matches on the located element OR a descendant carrying it (the checkmark icon), while `checked.attribute` is read on the located element itself.
99
100
  8. **Merge, don't overwrite**: preserve the page selector and any user-authored entries in `selectors.yaml`. Only add missing keys.
100
101
  - **Type-collision check** (see `sungen-selector-keys` § Collision rule): before writing a base key, check whether it already exists with a *different* `type`. If so, give **both** entries a `--type` suffix instead of clobbering or sharing the base key — a base-key entry of the wrong type gets picked up by any step referencing that label. Watch `page` especially: a `[X] page` step reads `value` as a URL, so if `X` already holds a button (`value: 'button'`) the compiler won't use the button's value as a URL, but it can't invent the real one either — it falls back to the feature's own path and warns `SG-W013`, asserting a page that isn't `X`'s. Write `X--page` (URL from the live page / feature `Path:`) alongside `X--button`.
101
102
  9. **Show summary + confirm**: list the keys that will be added, ask the user to approve, then write the file.
@@ -428,6 +429,17 @@ users:
428
429
 
429
430
  **How to build `columns`**: count column headers in snapshot (left to right, 0-indexed). Map each `[Col] column` reference from feature file to its index.
430
431
 
432
+ **A list built from `<div>`s (no `<table>`, no `role="row"` in the snapshot)** — the table steps still work, but you must declare the row SHAPE on the table entry, or every row step resolves 0 rows (`is hidden`/`is empty` pass vacuously — `SG-W016` warns about exactly this):
433
+
434
+ ```yaml
435
+ job list:
436
+ type: 'locator'
437
+ value: 'div.job-list'
438
+ rows: 'div.job-row' # RELATIVE to the table: what every row matches
439
+ ```
440
+
441
+ Pick `rows` from the snapshot as the repeating child that holds one record (a class like `.job-row`, `[data-testid="job-item"]`, or `> div`). Do **not** put it on a row label's entry — that key positions ONE row (`div.job-row:first-child`) and strict-mode-fails when it matches many.
442
+
431
443
  ---
432
444
 
433
445
  ## Detail Screens with Dynamic IDs
@@ -219,35 +219,49 @@ If no YAML key exists, the resolver infers from the Gherkin element type:
219
219
  | `[X] button` | `getByRole('button', { name: 'X' })` | Button's accessible name = X |
220
220
  | `[X] link` | `getByRole('link', { name: 'X' })` | Link text = X |
221
221
  | `[X] heading` / `header` | `getByRole('heading', { name: 'X' })` | Heading text = X |
222
- | `[X] checkbox` | `getByRole('checkbox', { name: 'X' })` | Checkbox label = X |
222
+ | `[X] checkbox` | `getByRole('checkbox', { name: 'X' })` | Checkbox label = X — a checkbox-LIKE tile with no `role`/`aria-checked` needs an explicit entry plus `checked: { class: <cls> }` or `checked: { attribute: <attr> }` (read on the located element itself; `expanded.class` reads a descendant) |
223
223
  | `[X] radio` | `getByRole('radio', { name: 'X' })` | Radio label = X |
224
224
  | `[X] field` | `getByPlaceholder('X')` | Placeholder text = X AND field has a placeholder |
225
225
  | `[X] text` / `message` / `label` | `getByText('X')` | Visible text = X (partial match) |
226
226
  | `[X] logo/image/icon` | `getByRole('img', { name: 'X' })` | Image alt = X |
227
227
  | `[X] search` | `getByRole('searchbox', { name: 'X' })` | Searchbox label = X |
228
- | `[X] option` | `getByRole('option', { name: 'X' })` | Option text = X |
228
+ | `[X] option` | `getByRole('option')` — **no name** | web only (see the list-member note below) |
229
229
  | `[X] slider` | `getByRole('slider', { name: 'X' })` | Slider label = X |
230
230
  | `[X] toggle` | `getByRole('switch', { name: 'X' })` | Toggle label = X |
231
231
  | `[X] tab` | `getByRole('tab', { name: 'X' })` | Tab text = X |
232
232
  | `[X] table` | `getByRole('table', { name: 'X' })` | Table aria-label = X |
233
233
  | `[X] list` | `getByRole('list', { name: 'X' })` | List aria-label = X |
234
- | `[X] column` | `getByRole('columnheader', { name: 'X' })` | Column header text = X |
234
+ | `[X] column` | `getByRole('columnheader', { name: 'X' })` | Column header text = X — **overridable**, see the column-header note below |
235
235
  | `[X] dialog` / `modal` / `drawer` | `getByRole('dialog', { name: 'X' })` | Dialog aria-label/heading = X |
236
236
  | `[X] dropdown` / `select` | `getByRole('combobox', { name: 'X' })` | Combobox label = X |
237
237
  | `[X] menuitem` | `getByRole('menuitem', { name: 'X' })` | Menu item text = X |
238
238
  | `[X] progressbar` | `getByRole('progressbar', { name: 'X' })` | Progressbar label = X |
239
239
  | `[X] section` | `getByRole('region', { name: 'X' })` | Section aria-label = X |
240
240
  | `[X] card` | `getByRole('article', { name: 'X' })` | Card aria-label = X |
241
- | `[X] item` | `getByRole('listitem', { name: 'X' })` | List item text = X |
242
- | `[X] cell` | `getByRole('cell', { name: 'X' })` | Cell text = X |
241
+ | `[X] item` | `getByRole('listitem')` — **no name** | web only (see the list-member note below) |
242
+ | `[X] cell` | `getByRole('cell')` — **no name** | web only (see the list-member note below) |
243
243
  | `[X] spinner` | `getByRole('status', { name: 'X' })` | Spinner aria-label = X |
244
244
  | `[X] breadcrumb` | `getByRole('navigation', { name: 'X' })` | Navigation aria-label = X |
245
245
  | `[X] badge` / `tooltip` / `tag` | `getByText('X')` | Visible text = X |
246
246
 
247
+ **Column headers: the `[Reference]` is not required to be the DOM text.** `[X] column` defaults its accessible name to the reference, which is unmatchable when the page is not in the reference's language (a `[Category]` column whose header reads 記事分類). Declare the real header instead of renaming the reference — the reference is what ships in the delivered test case. Most specific first: the table entry's `columns.<col>.header`, then a `<col>--columnheader` entry's `name:`, then the reference text. A `header` may hold `{{variables}}` for per-locale text, like any `name:`. A same-named entry that is NOT a column header is ignored and cannot hijack the lookup.
248
+
249
+ **Special note on the list members `[X] item` / `[X] option` / `[X] cell`** — the only rows above that behave differently per platform:
250
+
251
+ - **Web**: they infer the role with **no `name`**, on purpose. A list member takes its accessible name from its own CONTENT, so a name defaulted from the `[Reference]` text matches zero elements — and `toBeHidden()` PASSES on an empty locator, so the scenario would go green having proved nothing. The role alone is the whole locator; `nth` or a count assertion narrows it. An explicit `name:` you write yourself is always honoured.
252
+ - **Mobile**: they do **not** auto-infer at all. A native a11y tree has no role concept, so with no name there is nothing to build an accessibility-id from — the old behaviour emitted the role token itself (`~listitem`), which no tree ever contains. `sungen generate` now fails naming the key to add, and selector generation (Phase 0) scans the live page for it instead of skipping. **Write an explicit entry**, e.g. `job list--item: { type: accessibility-id, value: 'job-row' }`.
253
+
254
+ **On mobile, `type: role` needs a `name:`.** The rule above is not about these three types — it is about a role with **no accessible name**, whichever way it got that way. On mobile such a role compiles to the role token (`~status`) and can never match, so it is refused at compile time. That includes `name: null`, which is the web opt-out for a content-named role: it has no mobile meaning, and `SG-W003` (which recommends it) does not fire on mobile for that reason. On a native app, prefer `type: accessibility-id`, `type: xpath` or `type: id` over `type: role` — the device exposes content-desc / accessibilityIdentifier, not ARIA roles.
255
+
247
256
  **Special note on `[X] field`**: `getByPlaceholder('X')` only works when (1) the field has a placeholder attribute AND (2) the placeholder text equals X. For fields without placeholders (floating labels, aria-label), write explicit YAML: `type: label, value: "Actual label text"`.
248
257
 
249
258
  **Only add a YAML entry when** auto-infer cannot work: DOM name differs from Gherkin label, need `testid`, need `nth`, need `exact: true`, need `name: null`, or the field type requires explicit config.
250
259
 
260
+ **`nth:` takes an integer `>= -1`** — `0`, `1`, `2`… are zero-based indexes and `-1` is the LAST
261
+ match (`.last()` on web, `.at(-1)` on mobile). Nothing else is an index: `nth: -2`, `nth: 'last'`
262
+ and `nth: 1.5` are ignored, the selector stops filtering by position, and the compiler reports
263
+ `SG-W018`. Reach for `nth` only when `exact: true` or `scope` cannot disambiguate.
264
+
251
265
  ### `name: null` — name-from-content roles (toast, alert, spinner)
252
266
 
253
267
  For a `type: role` entry **without** `name:`, the compiler defaults the accessible-name filter to the Gherkin `[Reference]` text. That is right for buttons/links — and **always wrong** for roles whose accessible name derives from their CONTENT (`status`, `alert`, `log`, `marquee`, `timer`): a toast's name is its message text, never the string "Success Toast". The compiler warns (`SG-W003`). Opt out explicitly:
@@ -309,7 +309,7 @@ Security: [S1 – admin only]
309
309
  **Depth is a GATE dimension (harness-roadmap P1) — self-raise, never silently go shallow:**
310
310
  - For every data-correctness theme the catalog marks `depth.requires: data-assertion`, emit its `depth.template` shape by **default** — don't wait for the repair loop. `sungen audit` measures `businessDepth` (ratio of these scenarios that assert data) against an intent threshold (functional ≥ 0.70); below it the **gate FAILs**.
311
311
  - **Verify depth deterministically before the gate:** run `sungen depth-lint --screen <name>`. It classifies every shallow business-critical scenario into **deepen-in-place** (add the theme's value assertion — the printed `template` is a hint, fit it to the actual claim) vs **cross-screen** (route to a flow / `@manual:Mx`). Clear the `deepen` list first — this is the mechanical way to hit `businessDepth` on the first pass instead of churning repair rounds. Never fake a value assertion onto a visibility/behavior scenario the lint over-counts; leave it and note the over-count.
312
- - `depth.cross_screen: true` (cart / detail / filter / brand correctness) → write the deep capture/compare shape as an **automated flow scenario** (in the flow — do NOT leave a full-step `@manual` duplicate on the screen). `@manual` is **only** for genuine judgment (M6 visual/UX · M8 not-worth · M9 human) or a missing capability (M1–M5/M7), and it **must** carry a reason code (`@manual:Mx`, or a reason comment the planner can infer). A `@manual` scenario that still has full automatable steps (a data assertion, no visual/mock/a11y judgment) is now flagged by `sungen audit` as `MANUAL-AUTOMATABLE`, and business-critical scenarios you defer to `@manual` are reported as `DEPTH-DEFERRED` (they do NOT silently inflate `businessDepth`). Deferring automatable work to `@manual` lowers quality — automate it in the flow instead.
312
+ - `depth.cross_screen: true` (cart / detail / filter / brand correctness) → write the deep capture/compare shape as an **automated flow scenario** (in the flow — do NOT leave a full-step `@manual` duplicate on the screen). `@manual` is **only** for genuine judgment (M6 visual/UX · M8 not-worth · M9 human), a device/OS limit on mobile (M10 OS capability · M11 real-device only), or a missing capability (M1–M5/M7), and it **must** carry a reason code (`@manual:Mx`, or a reason comment the planner can infer). A `@manual` scenario that still has full automatable steps (a data assertion, no visual/mock/a11y judgment) is now flagged by `sungen audit` as `MANUAL-AUTOMATABLE`, and business-critical scenarios you defer to `@manual` are reported as `DEPTH-DEFERRED` (they do NOT silently inflate `businessDepth`). Deferring automatable work to `@manual` lowers quality — automate it in the flow instead.
313
313
  - **Pick the right `@manual:Mx` code — it decides which driver can later automate the case** (`sungen audit` flags a code↔reason mismatch). Tag the code that matches the **oracle the reason describes**:
314
314
 
315
315
  | The reason needs… | Code | Unblocked by |
@@ -321,9 +321,11 @@ Security: [S1 – admin only]
321
321
  | an external dependency (email, payment gateway, download) | `M5` | mail-file / contract |
322
322
  | visual / UX / responsive / a11y judgment | `M6` | — (keep manual) |
323
323
  | not worth automating · true human judgment | `M8` / `M9` | — (keep manual) |
324
+ | **mobile** — what the OS/driver will not expose (notification shade, app switcher/recents, permission variance by OS version, call interruption) | `M10` | — (keep manual) |
325
+ | **mobile** — what an emulator/simulator cannot be (biometrics, real camera, NFC, a real push token) | `M11` | — (keep manual) |
324
326
 
325
327
  e.g. "submit a payload then check the subscribers **table**" is an API+DB oracle → `@manual:M2` (NOT `M1`); "seed a DB with zero products" is a data state → `M1`; "throttle the network" → `M3`.
326
- - **Prefer automation-ready `@requires:<cap>` over prose `@manual`.** When you *can* write the steps for a capability-manual case (an API/DB oracle, a seeded state), write it **automation-ready** — the real `@api`/`@query`/… steps tagged `@requires:<cap>` (e.g. `@requires:db @query:subscriber_row`) — instead of a prose `@manual:M2`. It compiles to a skipped-with-reason stub until `sungen capability add <cap>`, then runs as a real test with **no rewrite**. Reserve prose `@manual:Mx` for cases whose steps genuinely can't be expressed (M6/M8/M9 judgment, or a capability with no driver). `sungen audit` reports these as `AUTOMATION-READY-PENDING` (not a gap, not manual).
328
+ - **Prefer automation-ready `@requires:<cap>` over prose `@manual`.** When you *can* write the steps for a capability-manual case (an API/DB oracle, a seeded state), write it **automation-ready** — the real `@api`/`@query`/… steps tagged `@requires:<cap>` (e.g. `@requires:db @query:subscriber_row`) — instead of a prose `@manual:M2`. It compiles to a skipped-with-reason stub until `sungen capability add <cap>`, then runs as a real test with **no rewrite**. Reserve prose `@manual:Mx` for cases whose steps genuinely can't be expressed (M6/M8/M9 judgment, M10/M11 device-or-OS limits on mobile, or a capability with no driver). `sungen audit` reports these as `AUTOMATION-READY-PENDING` (not a gap, not manual).
327
329
  - **If the spec lacks the concrete value** a deep assertion needs (exact message, price, count): still write the deep shape with a `{{var}}` placeholder and leave a `# SPEC-GAP: <field> value not in spec` comment — do **not** downgrade to `see [X] section`. A visible gap is better than a silent shallow pass.
328
330
  - **Blind-Spot Memory:** before finishing, run `sungen blindspot list --prompt` (Bash) and make sure the suite satisfies each recorded pattern (e.g. "for any Add/Create action: check success + resulting data state + duplicate/double-submit"). These are gaps QA hit before — don't repeat them.
329
331
 
@@ -478,6 +480,90 @@ And User wait for [Page Title] heading is visible
478
480
 
479
481
  ---
480
482
 
483
+ ## Mobile — the two axes web has no concept of
484
+
485
+ Applies to a `@platform:mobile/android/ios` unit. Read `sungen-viewpoint` →
486
+ `group-f-mobile-native.md` for *what to check* per native pattern (it carries the theme↔catalog
487
+ map the audit scores against); this section is the two **cross-cutting** axes that multiply every
488
+ other scenario on a device, and that a web-shaped coverage map has no row for.
489
+
490
+ ### Axis 1 — the app lifecycle state machine
491
+
492
+ A browser tab is either open or closed. An app has five states, and a screen can be wrong in any of
493
+ them. Generate lifecycle scenarios from the transitions, not from the states.
494
+
495
+ ```
496
+ launch (cold)
497
+ │
498
+ ▼
499
+ ┌──────────────► FOREGROUND ──────────────┐
500
+ │ │ │
501
+ │ background N seconds restart (kill)
502
+ │ ▼ ▼
503
+ │ BACKGROUNDED RELAUNCHED ── reinstall ──► FIRST-RUN
504
+ │ │ (install state cleared)
505
+ └──── resume ─────────┤
506
+ ▼
507
+ PROCESS DEATH (the OS reclaims a backgrounded app)
508
+ ```
509
+
510
+ | Transition | Step | Availability |
511
+ |---|---|---|
512
+ | foreground → backgrounded → resumed | `User background the app for 5 seconds` | both OSes |
513
+ | foreground → killed → relaunched | `User restart the app` | both OSes — **the** persistence oracle |
514
+ | → first-run | `User reinstall the app` | both OSes; needs the session to have installed via `APP_APK` / `IOS_APP` |
515
+ | a clean state *between* scenarios | `@reset:none\|soft\|data\|full` on the Feature | a tag, never a step — a step that tries reports **SG-W023** |
516
+ | backgrounded → process death → resumed | *no step* | `@manual:M10` (OS capability) |
517
+
518
+ **Every lifecycle scenario needs a DATA oracle.** "The screen is still there" passes on an empty
519
+ shell and on a silently re-logged-in session. Assert the value that must have survived:
520
+
521
+ ```gherkin
522
+ @high @platform:mobile
523
+ Scenario: VP-LOGIC-001 The session survives a cold restart
524
+ Given User is on [Account] screen
525
+ When User restart the app
526
+ Then User see [Account Name] text contains {{user_name}}
527
+ ```
528
+
529
+ Minimum for any mobile unit: **one resume scenario** (the screen owes its own data back) and, when
530
+ the screen persists anything — a session, a setting, a dismissal, a show-once card — **one restart
531
+ scenario per persisted thing**. Rotation belongs here too: `User rotate to landscape` must not lose
532
+ entered values or fire the request twice.
533
+
534
+ ### Axis 2 — permission × network decision table
535
+
536
+ These two multiply: a permission-gated feature behaves differently offline, and the honest expected
537
+ result is often "which message wins". Build the table, then **test only the rows with distinct
538
+ outcomes** — the usual decision-table discipline, not all 12 combinations.
539
+
540
+ | # | Permission | Network | Expected | How |
541
+ |---|---|---|---|---|
542
+ | 1 | granted | online | the happy path, feature content asserted | `User grant camera permission` — both OSes (iOS Simulator needs `applesimutils`) |
543
+ | 2 | granted | offline | the spec's cached/queued behaviour + a clear message; entered data never silently lost | `User turn wifi off` → `@platform:android` (XCUITest cannot toggle connectivity); iOS is `@manual:M10` |
544
+ | 3 | granted | off → on | recovers and refreshes with no manual reload | `@platform:android` |
545
+ | 4 | denied | online | an in-app explanation with a route to Settings — never a crash or a dead button | `@manual:M10` — no step denies a permission |
546
+ | 5 | denied | offline | one message wins per spec; do NOT generate this row unless the spec says which | `@manual:M10` |
547
+ | 6 | never-ask-again | online | no request loop; the Settings route is shown | `@manual:M10` |
548
+ | 7 | revoked while running | online | the app re-checks instead of trusting a stale grant | `@manual:M10` |
549
+
550
+ Row 1 is `@high` and automated. Rows 2–3 are `@high` and automated **on Android only** — tag them,
551
+ do not write them platform-neutral and hope. Rows 4–7 carry `@manual:M10`, which is a reason code
552
+ and a real oracle, not a shrug: write the steps a human follows and the value they must see.
553
+
554
+ ### Two mobile traps worth naming while generating
555
+
556
+ - **A `page` assertion proves nothing on mobile.** A native app has no URL, so `Then User see [Login]
557
+ page` compiles to an empty comment (**SG-W022**) and the scenario passes without looking. The
558
+ `screen` spelling is different and is what you want: it treats the target as a plain element and
559
+ emits a real visibility assertion — so name a marker only that screen has (`Then User see [Login
560
+ Submit] button`, or `Then User see [Login] screen` against a screen-root selector), and pair it
561
+ with the previous screen's marker going `is hidden`.
562
+ - **An `open [X] page` / `navigate to` step on mobile navigates nowhere** (**SG-W026**) — the app was
563
+ already launched by the capabilities. Change screens with a real tap or gesture.
564
+
565
+ ---
566
+
481
567
  ## Cleanup & Hooks
482
568
 
483
569
  Add cleanup tags per the `sungen-gherkin-syntax` Cleanup table. Key rules: