@sun-asterisk/sungen 3.2.0-beta.142 → 3.2.0-beta.144

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 (274) hide show
  1. package/dist/capabilities/context-router.d.ts +11 -2
  2. package/dist/capabilities/context-router.d.ts.map +1 -1
  3. package/dist/capabilities/context-router.js +10 -3
  4. package/dist/capabilities/context-router.js.map +1 -1
  5. package/dist/capabilities/discover.js +1 -1
  6. package/dist/capabilities/discover.js.map +1 -1
  7. package/dist/cli/commands/audit.d.ts.map +1 -1
  8. package/dist/cli/commands/audit.js +5 -3
  9. package/dist/cli/commands/audit.js.map +1 -1
  10. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +54 -0
  11. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -0
  12. package/dist/generators/test-generator/adapters/appium/appium-adapter.js +52 -0
  13. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -0
  14. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
  15. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
  16. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
  17. package/dist/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
  18. package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
  19. package/dist/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
  20. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
  21. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
  22. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
  23. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
  24. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
  25. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
  26. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
  27. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
  28. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
  29. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
  30. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
  31. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
  32. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
  33. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
  34. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
  35. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
  36. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
  37. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
  38. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
  39. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
  40. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
  41. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
  42. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
  43. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
  44. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  45. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
  46. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
  47. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
  48. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
  49. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
  50. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
  51. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
  52. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
  53. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
  54. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
  55. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
  56. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
  57. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
  58. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
  59. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
  60. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
  61. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
  62. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
  63. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
  64. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
  65. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
  66. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
  67. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
  68. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  69. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
  70. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
  71. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
  72. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
  73. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
  74. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
  75. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
  76. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
  77. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
  78. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  79. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
  80. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
  81. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
  82. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
  83. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
  84. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
  85. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
  86. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
  87. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
  88. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
  89. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
  90. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
  91. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
  92. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
  93. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
  94. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
  95. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
  96. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
  97. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
  98. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
  99. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
  100. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
  101. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
  102. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
  103. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
  104. package/dist/generators/test-generator/adapters/index.d.ts +1 -0
  105. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  106. package/dist/generators/test-generator/adapters/index.js +9 -1
  107. package/dist/generators/test-generator/adapters/index.js.map +1 -1
  108. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  109. package/dist/generators/test-generator/code-generator.js +3 -2
  110. package/dist/generators/test-generator/code-generator.js.map +1 -1
  111. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  112. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  113. package/dist/generators/test-generator/step-mapper.js +7 -37
  114. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  115. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  116. package/dist/generators/test-generator/template-engine.js +13 -1
  117. package/dist/generators/test-generator/template-engine.js.map +1 -1
  118. package/dist/harness/audit.d.ts +16 -2
  119. package/dist/harness/audit.d.ts.map +1 -1
  120. package/dist/harness/audit.js +74 -11
  121. package/dist/harness/audit.js.map +1 -1
  122. package/dist/harness/capability-plan.d.ts +2 -0
  123. package/dist/harness/capability-plan.d.ts.map +1 -1
  124. package/dist/harness/capability-plan.js +4 -1
  125. package/dist/harness/capability-plan.js.map +1 -1
  126. package/dist/harness/catalog/drivers.yaml +1 -1
  127. package/dist/harness/flow-check.d.ts.map +1 -1
  128. package/dist/harness/flow-check.js +13 -4
  129. package/dist/harness/flow-check.js.map +1 -1
  130. package/dist/harness/parse.d.ts +2 -0
  131. package/dist/harness/parse.d.ts.map +1 -1
  132. package/dist/harness/parse.js +10 -2
  133. package/dist/harness/parse.js.map +1 -1
  134. package/dist/harness/quality-gates.d.ts +6 -0
  135. package/dist/harness/quality-gates.d.ts.map +1 -1
  136. package/dist/harness/quality-gates.js +15 -1
  137. package/dist/harness/quality-gates.js.map +1 -1
  138. package/dist/harness/sensors.d.ts +27 -0
  139. package/dist/harness/sensors.d.ts.map +1 -1
  140. package/dist/harness/sensors.js +91 -21
  141. package/dist/harness/sensors.js.map +1 -1
  142. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  143. package/dist/orchestrator/ai-rules-updater.js +8 -0
  144. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  145. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
  146. package/dist/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
  147. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
  148. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
  149. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
  150. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
  151. package/dist/orchestrator/templates/env.appium.example +25 -0
  152. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
  153. package/dist/orchestrator/templates/wdio.conf.ts +295 -0
  154. package/dist/utils/selector-types.d.ts +1 -1
  155. package/dist/utils/selector-types.d.ts.map +1 -1
  156. package/dist/utils/selector-types.js +5 -0
  157. package/dist/utils/selector-types.js.map +1 -1
  158. package/package.json +3 -3
  159. package/src/capabilities/context-router.ts +15 -3
  160. package/src/capabilities/discover.ts +1 -1
  161. package/src/cli/commands/audit.ts +5 -3
  162. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +57 -0
  163. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
  164. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
  165. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
  166. package/src/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
  167. package/src/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
  168. package/src/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
  169. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
  170. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
  171. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
  172. package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
  173. package/src/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
  174. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
  175. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
  176. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
  177. package/src/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
  178. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
  179. package/src/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
  180. package/src/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
  181. package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
  182. package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
  183. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
  184. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
  185. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
  186. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
  187. package/src/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
  188. package/src/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
  189. package/src/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
  190. package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
  191. package/src/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
  192. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
  193. package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  194. package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
  195. package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
  196. package/src/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
  197. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
  198. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
  199. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
  200. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
  201. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
  202. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
  203. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
  204. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
  205. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
  206. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
  207. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
  208. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
  209. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
  210. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
  211. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
  212. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
  213. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
  214. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
  215. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
  216. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
  217. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  218. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
  219. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
  220. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
  221. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
  222. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
  223. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
  224. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
  225. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
  226. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
  227. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  228. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
  229. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
  230. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
  231. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
  232. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
  233. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
  234. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
  235. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
  236. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
  237. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
  238. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
  239. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
  240. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
  241. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
  242. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
  243. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
  244. package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
  245. package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
  246. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
  247. package/src/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
  248. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
  249. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
  250. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
  251. package/src/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
  252. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
  253. package/src/generators/test-generator/adapters/index.ts +7 -0
  254. package/src/generators/test-generator/code-generator.ts +3 -2
  255. package/src/generators/test-generator/step-mapper.ts +8 -5
  256. package/src/generators/test-generator/template-engine.ts +13 -1
  257. package/src/harness/audit.ts +84 -14
  258. package/src/harness/capability-plan.ts +5 -2
  259. package/src/harness/catalog/drivers.yaml +1 -1
  260. package/src/harness/flow-check.ts +13 -4
  261. package/src/harness/parse.ts +12 -2
  262. package/src/harness/quality-gates.ts +14 -1
  263. package/src/harness/sensors.ts +110 -22
  264. package/src/orchestrator/ai-rules-updater.ts +8 -0
  265. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
  266. package/src/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
  267. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
  268. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
  269. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
  270. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
  271. package/src/orchestrator/templates/env.appium.example +25 -0
  272. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
  273. package/src/orchestrator/templates/wdio.conf.ts +295 -0
  274. package/src/utils/selector-types.ts +5 -0
@@ -11,17 +11,36 @@ import * as path from 'path';
11
11
  import { parse as parseYaml } from 'yaml';
12
12
  import { ScenarioInfo, ViewpointEntry, idPrefix } from './parse';
13
13
 
14
- // Business-critical category codes (project VP-<CAT> prefixes). Configurable later.
15
- const BUSINESS_CRITICAL_CATS = ['LIST', 'CART', 'PRODUCT', 'FILTER', 'CHECKOUT', 'ORDER'];
16
-
17
- // Buckets for coverage-balance.
18
- const BUCKETS: Record<string, string[]> = {
19
- 'business-core': BUSINESS_CRITICAL_CATS,
20
- 'presentation': ['UI'],
21
- 'validation-security': ['VAL', 'SEC', 'SUB'],
22
- 'behavior': ['LOGIC'],
23
- 'navigation': ['NAV'],
24
- };
14
+ // Business-critical category keywords (matched by CONTAINMENT against the VP category, so a
15
+ // compound category like LIST-DISPLAY / ADD-TO-CART / PRODUCT-DISCOVERY classifies correctly).
16
+ const BUSINESS_CRITICAL_CATS = [
17
+ // UI commerce cores
18
+ 'LIST', 'CART', 'PRODUCT', 'FILTER', 'CHECKOUT', 'ORDER', 'DETAIL', 'DISCOVERY', 'CATEGORY', 'BRAND', 'DUPLICATE', 'CONSISTENCY',
19
+ // API / DB capability cores — for an api/db suite the operation IS the business core
20
+ 'API', 'ENDPOINT', 'CRUD', 'QUERY', 'CONTRACT', 'RESOURCE',
21
+ ];
22
+
23
+ // Bucket keyword sets for coverage-balance, in PRECEDENCE order (first match wins). Matched by
24
+ // substring containment so compound categories land in the right bucket (H1): e.g. LIST-DISPLAY
25
+ // → business-core (LIST) not presentation (DISPLAY); CART-TRANSITION → business-core (CART).
26
+ const BUCKET_ORDER: Array<[string, string[]]> = [
27
+ ['business-core', BUSINESS_CRITICAL_CATS],
28
+ ['behavior', ['LOGIC', 'TRANSITION', 'WORKFLOW']],
29
+ ['validation-security', ['VAL', 'SEC', 'SUB', 'AUTH', 'LOGIN']],
30
+ ['navigation', ['NAV']],
31
+ ['presentation', ['UI', 'LAYOUT', 'RESPONSIVE', 'DISPLAY', 'SEO', 'ACCESSIBILITY', 'USABILITY', 'VISUAL']],
32
+ ];
33
+ const BUCKETS: Record<string, string[]> = Object.fromEntries(BUCKET_ORDER);
34
+
35
+ /** Classify a VP category into a balance bucket by keyword containment + precedence (H1). */
36
+ export function bucketForCategory(category: string | undefined): string {
37
+ const cat = (category || '').toUpperCase();
38
+ if (!cat) return 'other';
39
+ for (const [bucket, kws] of BUCKET_ORDER) {
40
+ if (kws.some((k) => cat.includes(k))) return bucket;
41
+ }
42
+ return 'other';
43
+ }
25
44
 
26
45
  export interface ThemeDepth {
27
46
  requires: string; // 'data-assertion' → scenarios on this theme must assert DATA
@@ -242,6 +261,72 @@ export function flowCoveredThemes(
242
261
  return out;
243
262
  }
244
263
 
264
+ // ---------- Sensor: Flow regression-depth (H3) ----------
265
+
266
+ export interface FlowDepthResult {
267
+ stateful: boolean; // the suite mutates a cart/checkout collection (add/remove/quantity)
268
+ countProof: boolean; // asserts a row count / item quantity, not just presence
269
+ teardown: boolean; // removes an item and verifies the empty/zero state
270
+ multiSource: boolean; // adds from >1 distinct source (e.g. main list AND recommended)
271
+ ratio: number; // covered dimensions / 3 (1 when not stateful → neutral)
272
+ missing: string[];
273
+ }
274
+
275
+ /**
276
+ * Grades a STATEFUL flow's regression depth beyond "theme covered": a cart/checkout flow that only
277
+ * proves an item is present is shallower than one that proves the quantity/count, tears the state
278
+ * back down (remove → empty), and exercises every add-to-cart source. The ratio caps the
279
+ * businessDepth headroom (audit.ts) so a thin stateful flow can't reach a perfect score. (H3)
280
+ */
281
+ export function flowRegressionDepth(scenarios: ScenarioInfo[]): FlowDepthResult {
282
+ const hay = scenarios.map((s) => s.haystack);
283
+ const any = (re: RegExp) => hay.some((h) => re.test(h));
284
+ const addsToCart = any(/\b(add to cart|add to basket|added (?:to )?(?:the )?cart|adds? .* cart)\b/i);
285
+ const stateful = (any(/\b(cart|basket|checkout)\b/i) && (addsToCart || any(/\b(remove|delete|quantity|cart line|cart row)\b/i)));
286
+ if (!stateful) return { stateful: false, countProof: false, teardown: false, multiSource: false, ratio: 1, missing: [] };
287
+
288
+ // 1. Count/quantity proof — a row count or item quantity, not just presence of a row.
289
+ const countProof = any(/\b(quantity|qty|two (?:rows|lines|cart)|row count|count column|number of items|one[_ ]row|two[_ ]rows|qty[_ ])/i);
290
+ // 2. Teardown — removes the item and verifies the empty/zero state (the inverse operation).
291
+ const teardown = any(/\b(remove|delete|clear)\b/i) && any(/\b(empty|no items|zero|removed|0 items)\b/i);
292
+ // 3. Multi-source — the cart is fed from >1 source (the main list AND a recommended/related rail).
293
+ const multiSource = any(/\b(recommended|related|you may also|suggest)\b/i) && addsToCart;
294
+
295
+ const dims: Array<[string, boolean]> = [['count-proof', countProof], ['teardown', teardown], ['multi-source', multiSource]];
296
+ const missing = dims.filter(([, v]) => !v).map(([k]) => k);
297
+ return { stateful: true, countProof, teardown, multiSource, ratio: (dims.length - missing.length) / dims.length, missing };
298
+ }
299
+
300
+ // ---------- Sensor: Oracle strength (H4) ----------
301
+
302
+ export interface OracleStrengthResult {
303
+ weak: { name: string; hint: string }[]; // scenarios proving facet membership by a name-substring
304
+ facetClaims: number; // scenarios that touch a category/brand facet (denominator)
305
+ ratio: number; // 1 - weak/facetClaims (1 when none) — caps businessDepth
306
+ }
307
+
308
+ // "see all [<item name/title>] ... contain(s) {{<facet>.term}}" — asserting every item's NAME carries
309
+ // a category/brand term does NOT prove the item BELONGS to that facet (a "Dress" item need not contain
310
+ // "Dress" in its name). The strong oracle is the results-page title/header, a detail-page facet field,
311
+ // an API/DB query, or an explicit @manual:M2 deferral.
312
+ const WEAK_FACET_ORACLE = /\bsee all\b\s*\[[^\]]*\b(name|title|label)\b[^\]]*\][^{[]*\bcontains?\b[^{]*\{\{[^}]*\b(categ|brand|facet|filter|term)/i;
313
+ const FACET_REF = /\{\{[^}]*\b(categ|brand|facet|filter)\b[^}]*\}\}|\b(category|brand)\b/i;
314
+
315
+ export function oracleStrength(scenarios: ScenarioInfo[]): OracleStrengthResult {
316
+ const weak: { name: string; hint: string }[] = [];
317
+ for (const s of scenarios) {
318
+ if (s.manual) continue; // a @manual facet check is a deliberate deferral, not a weak automated oracle
319
+ if (WEAK_FACET_ORACLE.test(s.stepsText)) {
320
+ weak.push({
321
+ name: s.name.slice(0, 80),
322
+ hint: 'asserting every item NAME contains a category/brand term does not prove facet membership — assert the results-page TITLE/header carries the facet, a detail-page facet field, or an API/DB oracle; or defer the exhaustive check to @manual:M2.',
323
+ });
324
+ }
325
+ }
326
+ const facetClaims = scenarios.filter((s) => FACET_REF.test(s.stepsText) || FACET_REF.test(s.name)).length;
327
+ return { weak, facetClaims, ratio: facetClaims ? 1 - weak.length / Math.max(1, facetClaims) : 1 };
328
+ }
329
+
245
330
  /** Collect data-correctness themes (depth.requires) for a page-type + universal. */
246
331
  export function dataThemesFor(catalog: Catalog, pageType: string | null): CatalogTheme[] {
247
332
  const themes: CatalogTheme[] = [];
@@ -258,6 +343,7 @@ export interface BalanceResult {
258
343
  coreCount: number;
259
344
  secondaryCount: number;
260
345
  imbalanced: boolean;
346
+ unclassifiedRatio: number; // share of scenarios that fell into `other` (taxonomy drift signal, H1)
261
347
  note: string;
262
348
  }
263
349
 
@@ -270,23 +356,21 @@ export function coverageBalance(scenarios: ScenarioInfo[]): BalanceResult {
270
356
  for (const s of scenarios) {
271
357
  const cat = s.category || 'NONE';
272
358
  byCategory[cat] = (byCategory[cat] || 0) + 1;
273
- const bucket = Object.entries(BUCKETS).find(([, cats]) => cats.includes(cat))?.[0] || 'other';
274
- byBucket[bucket]++;
359
+ byBucket[bucketForCategory(s.category)]++;
275
360
  }
276
361
 
277
362
  const core = byBucket['business-core'];
278
363
  const secondary = byBucket['presentation'] + byBucket['validation-security'];
279
364
  const imbalanced = secondary > core * 1.5 && core > 0;
280
- return {
281
- byBucket,
282
- byCategory,
283
- coreCount: core,
284
- secondaryCount: secondary,
285
- imbalanced,
286
- note: imbalanced
365
+ const unclassifiedRatio = scenarios.length ? byBucket['other'] / scenarios.length : 0;
366
+ // A high `other` share means the VP taxonomy drifted from the catalog — the balance axis is then
367
+ // unreliable, so we surface it (audit.ts caps the balance contribution on this signal).
368
+ const note = unclassifiedRatio > 0.4
369
+ ? `Taxonomy drift: ${byBucket['other']}/${scenarios.length} scenarios have an unrecognised VP category (bucket=other) — balance is unreliable until the viewpoint codes match the catalog.`
370
+ : imbalanced
287
371
  ? `Secondary viewpoints (presentation+validation/security = ${secondary}) outweigh business-core (${core}) by >1.5x.`
288
- : 'Balanced.',
289
- };
372
+ : 'Balanced.';
373
+ return { byBucket, byCategory, coreCount: core, secondaryCount: secondary, imbalanced, unclassifiedRatio, note };
290
374
  }
291
375
 
292
376
  // ---------- Sensor 4: Duplicate clusters ----------
@@ -300,6 +384,10 @@ export interface DuplicateResult {
300
384
  export function duplicateClusters(scenarios: ScenarioInfo[]): DuplicateResult {
301
385
  const map = new Map<string, ScenarioInfo[]>();
302
386
  for (const s of scenarios) {
387
+ // @manual scenarios compile to a degenerate skeleton (no executable steps), so they cluster
388
+ // with each other even though each is a distinct judgment/capability-manual viewpoint. Excluding
389
+ // them keeps the exact-dup signal about genuinely-copied AUTOMATED scenarios (H2).
390
+ if (s.manual) continue;
303
391
  const arr = map.get(s.stepSkeleton) || [];
304
392
  arr.push(s);
305
393
  map.set(s.stepSkeleton, arr);
@@ -65,6 +65,10 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
65
65
  ['claude-skill-capture-mode-live.md', '.claude/skills/sungen-capture/mode-live.md'],
66
66
  ['claude-skill-capture-mode-local.md', '.claude/skills/sungen-capture/mode-local.md'],
67
67
  ['claude-skill-locale.md', '.claude/skills/sungen-locale/SKILL.md'],
68
+ // Mobile (Appium) skills — relevant for platform: mobile (MOB-5).
69
+ ['claude-skill-mobile-gestures.md', '.claude/skills/sungen-mobile-gestures/SKILL.md'],
70
+ ['claude-skill-capture-mobile.md', '.claude/skills/sungen-capture-mobile/SKILL.md'],
71
+ ['claude-skill-selector-fix-mobile.md', '.claude/skills/sungen-selector-fix-mobile/SKILL.md'],
68
72
 
69
73
  // Agents — Claude Code sub-agents (isolated context). Copilot runs these inline.
70
74
  ['claude-agent-reviewer.md', '.claude/agents/sungen-reviewer.md'],
@@ -96,6 +100,10 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
96
100
  ['github-skill-sungen-capture-mode-live.md', '.github/skills/sungen-capture/mode-live.md'],
97
101
  ['github-skill-sungen-capture-mode-local.md', '.github/skills/sungen-capture/mode-local.md'],
98
102
  ['github-skill-sungen-locale.md', '.github/skills/sungen-locale/SKILL.md'],
103
+ // Mobile (Appium) skills — relevant for platform: mobile (MOB-5).
104
+ ['github-skill-sungen-mobile-gestures.md', '.github/skills/sungen-mobile-gestures/SKILL.md'],
105
+ ['github-skill-sungen-capture-mobile.md', '.github/skills/sungen-capture-mobile/SKILL.md'],
106
+ ['github-skill-sungen-selector-fix-mobile.md', '.github/skills/sungen-selector-fix-mobile/SKILL.md'],
99
107
  ];
100
108
 
101
109
  // Skill/asset directories retired in a previous refactor. `sungen update` removes
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: sungen-capture-mobile
3
+ description: 'Capture a live mobile app screen via Appium MCP — locator tree + screenshot for visual context. Auto-loaded by create-test when the screen is @platform:mobile/android/ios.'
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## Purpose
8
+
9
+ Launch a mobile app on a device/emulator, capture **one locator tree** (`generate_locators`) and **one screenshot**, and save them as visual context for test generation. The mobile analogue of `sungen-capture-live` — same job, Appium MCP instead of Playwright MCP.
10
+
11
+ Use when the target is a native/Flutter/React-Native app (`@platform:android` or `@platform:ios`) running on a connected device or emulator.
12
+
13
+ ---
14
+
15
+ ## Prerequisites
16
+
17
+ - `appium-mcp` connected (see `/mcp`). It runs Appium **embedded** — no separate server needed.
18
+ - A device/emulator booted: `adb devices` shows one for Android; a Simulator for iOS.
19
+ - The app **installed** on the device (`adb install -r <app>.apk` for Android).
20
+ - The screen's `Path:` (in `<screen>/features/*.feature` or `spec.md`) gives the app entry as
21
+ `<appPackage>/<appActivity>` (Android) or bundleId (iOS), e.g. `com.kngroup.media.converter/.MainActivity`.
22
+
23
+ ---
24
+
25
+ ## Steps
26
+
27
+ ### 1. Resolve target app + entry
28
+
29
+ Resolve in this order:
30
+ 1. `Path:` line in the feature / `App ID` in `spec.md` → split into `appPackage` + `appActivity`.
31
+ A dual-id Path (`<pkg>/<activity> | <iosBundleId>`) → take the part for the OS you are exploring
32
+ (left of `|` = Android pkg/activity, right = iOS bundle id).
33
+ 2. If missing → `AskUserQuestion`: *"What is the appPackage/appActivity (Android) or bundleId (iOS)?"*
34
+ 3. **Navigation recipe** — read the feature's `Background:` (the in-app web-path analog). Line 1
35
+ `Given User is on [Home] screen` is the launcher/landing screen the app opens on; each following
36
+ `When/And User tap [...]` (or gesture) is one hop toward the target screen. You will **replay**
37
+ these in step 4.5 so you scan the screen this feature targets — not the launcher. An anchor-only
38
+ Background (no nav steps) ⇒ the target IS the launcher screen ⇒ no navigation needed.
39
+
40
+ ### 2. Select device
41
+
42
+ `select_device` with `platform: android` (or `ios`). If exactly one device, it auto-selects.
43
+ If several, list them and ask the user which `deviceUdid`.
44
+
45
+ ### 3. Create the session
46
+
47
+ `appium_session_management` `action=create`, `platform=android`, capabilities (JSON string):
48
+ ```
49
+ appium:appPackage, appium:appActivity, appium:udid,
50
+ appium:noReset=true, appium:autoGrantPermissions=true, appium:newCommandTimeout=300
51
+ ```
52
+ For iOS, call `prepare_ios_simulator` first, then create with `appium:bundleId`.
53
+
54
+ ### 4. ⚠️ Wait for the screen to actually render
55
+
56
+ **This is the #1 gotcha for Flutter / RN apps.** Right after launch the app often shows a blank
57
+ splash — and the locator tree will contain **zero usable elements** (just an empty `FrameLayout`).
58
+ Do **not** capture yet. Take a quick `appium_screenshot`; if it's blank/splash, wait and re-check
59
+ until real content is drawn. Only then capture. (A Flutter app on a loading screen looks "invisible"
60
+ to Appium; the same app on a rendered screen exposes its full Semantics tree.)
61
+
62
+ ### 4.5. ⚠️ Navigate to the target screen (replay the navigation recipe)
63
+
64
+ **This is what makes capture land on the RIGHT screen** — the mobile equivalent of `page.goto(url)`
65
+ on web. After the launcher screen has rendered, replay the `Background:` nav steps so Appium ends up
66
+ on the screen this feature targets, *then* capture there.
67
+
68
+ For each nav step **after** the `Given User is on [Home] screen` anchor, in order:
69
+ 1. `generate_locators` (or `appium_get_page_source`) on the CURRENT screen.
70
+ 2. Resolve the step's `[Label]` against the live tree — prefer `accessibility id`, then visible
71
+ `text`/`content-desc`, then an `xpath` on a distinctive substring. Gestures (`scroll to [X]`,
72
+ `swipe …`) follow `sungen-mobile-gestures`.
73
+ 3. Perform the tap/gesture, then **wait for the next screen to render** (same blank-splash guard as
74
+ step 4) before the next step.
75
+
76
+ After the LAST step you are on the target screen — capture HERE (steps 5–6).
77
+
78
+ - **Anchor-only Background** (no nav steps) ⇒ the target IS the launcher screen ⇒ skip this step.
79
+ - **A step won't resolve** (its `[Label]` matches no element) ⇒ **STOP. Do not capture the wrong
80
+ screen.** Screenshot where you are and report: *"recipe step N: [Label] not found — fix the
81
+ Background nav path."* Fail loud (run-test would fail at the same step anyway).
82
+ - **No recipe yet** (guided-stub Background, but the target is NOT the launcher) ⇒ drive to the
83
+ screen interactively off the live tree (if unsure which control leads there, `AskUserQuestion`),
84
+ then **report the exact nav steps you took** so create-test/add-screen can write them into the
85
+ Background. Do **not** edit the `.feature` yourself — Gherkin authorship stays with those commands.
86
+
87
+ ### 5. Capture the locator tree (primary AI context)
88
+
89
+ Call **`generate_locators`** — this is the mobile equivalent of the web accessibility snapshot. It
90
+ returns priority-ranked locators per interactable element: `accessibility id`, `id`, platform-native
91
+ (`-android uiautomator` / `-ios predicate string`), and `xpath`, plus `content-desc`/`text`,
92
+ `clickable`, `enabled`. This is what `sungen-tc-generation` and `sungen-selector-keys` consume.
93
+
94
+ If the tree is huge, also `appium_get_page_source` (saved to a file) and grep it — never dump the
95
+ full XML inline.
96
+
97
+ ### 6. Screenshot
98
+
99
+ `appium_screenshot` → save to:
100
+ ```
101
+ qa/screens/<screen>/requirements/ui/mobile-<timestamp>.png
102
+ ```
103
+ `<timestamp>` = `YYYYMMDD-HHMM` local (e.g. `mobile-20260605-1645.png`).
104
+
105
+ ### 7. Detect discrepancies vs spec
106
+
107
+ If `spec.md` exists, cross-check `generate_locators` labels against spec sections (fields in spec but
108
+ not on screen, and vice-versa). Report; do **not** auto-edit `spec.md`.
109
+
110
+ ### 8. End the session
111
+
112
+ `appium_session_management` `action=delete` to free the device. Always clean up.
113
+
114
+ ### 9. Report back
115
+
116
+ > Captured mobile screen `<appPackage>`:
117
+ > - Reached via: <N nav steps replayed from the Background recipe, or "launcher screen — no nav">
118
+ > - Locators: <N> interactable elements (M with accessibility-id)
119
+ > - Screenshot: `requirements/ui/mobile-<timestamp>.png`
120
+ > - Discrepancies vs spec: <count, or "none">
121
+ > - Discovered nav steps (only if the recipe was empty): <the taps you took, for create-test/add-screen to write into the Background>
122
+
123
+ Hand back to the calling command.
124
+
125
+ ---
126
+
127
+ ## Cross-platform discovery (`@platform:mobile` — explore one OS, layer the rest)
128
+
129
+ When the screen is **`@platform:mobile`** (write-once-run-both), discover the **shared** cases first,
130
+ then peel off only what is genuinely OS-specific. You do **not** need to explore both OSes up front —
131
+ Flutter/RN expose the same Semantics on each, so one exploration covers most of it.
132
+
133
+ 1. **Explore whichever OS has a device up** (Android or iOS — symmetric for Flutter). Capture the tree.
134
+ 2. **Classify each component** into THREE buckets (not two):
135
+ - **Shared-exact** → a stable `accessibility id` equal on both OSes (Flutter Semantics → same value on
136
+ Android `content-desc` AND iOS `name`) → flat `type: accessibility-id` key in `<screen>.feature`.
137
+ - **Shared-variant** → SAME step/assertion on both OSes but the LOCATOR differs (composite
138
+ `content-desc` vs `-ios predicate`; partial/dynamic text via `descriptionContains` vs predicate
139
+ `CONTAINS`; a different attribute) → STILL `<screen>.feature`, with `android:`/`ios:` variant keys in
140
+ selectors.yaml. **A native locator alone is NOT a reason to split** — default for composite/partial text.
141
+ - **Sub-feature** → ONLY for genuine SCENARIO divergence: an element on ONE OS only, divergent
142
+ steps/flow, an OS-exclusive gesture/permission/dialog flow, or a case needing a per-platform
143
+ `nth`/`scope` (a variant can't diverge those). → `<screen>-android.feature` / `<screen>-ios.feature`.
144
+ 3. **Report the classification** so create-test/run-test scaffolds the right thing:
145
+ - `<screen>.feature` `@platform:mobile` ← shared-exact AND shared-variant (the vast majority)
146
+ - `<screen>-android.feature` `@platform:android` ← ONLY scenario-divergent Android cases
147
+ - `<screen>-ios.feature` `@platform:ios` ← ONLY scenario-divergent iOS cases
148
+ 4. **Verifying the other OS needs no second exploration** — just running the shared spec there
149
+ (`MOBILE_PLATFORM=both`) IS the check. Re-capture on the 2nd OS only for components that fail, and
150
+ move those into that OS's sub-feature. (Asking devs to add a Semantics label to an unlabeled element
151
+ converts an OS-specific case back into a shared one — prefer that when feasible.)
152
+
153
+ ## Selector-quality notes (carry into selectors.yaml)
154
+
155
+ - **Prefer `accessibility id`** (Android `content-desc` / iOS `accessibilityIdentifier`) — most stable.
156
+ - For **Flutter**, the Semantics label surfaces as `content-desc` ⇒ it usually equals the **visible
157
+ text**, so it is **locale-dependent** ("Good afternoon!", "English"). Keep such values in test-data
158
+ and use `{{variable}}` + locale overlays (see `sungen-locale`).
159
+ - **Composite nodes**: cards may concatenate child text into one `content-desc` with `\n` → prefer an
160
+ xpath `contains(@content-desc,'…')` on a distinctive substring.
161
+ - **Duplicate labels** → use `nth`. **Unlabelled controls** (search fields, icon buttons) → fall back
162
+ to `xpath` / class.
163
+
164
+ ---
165
+
166
+ ## What this skill does NOT do
167
+
168
+ - Does not run tests or generate `selectors.yaml` (that's `/sungen:run-test` + `sungen-selector-fix-mobile`)
169
+ - Does not generate Gherkin (that's `sungen-tc-generation`) — it may *report* discovered nav steps,
170
+ but writing them into the Background is create-test/add-screen's job
171
+ - Does not handle login/auth state (mobile auth is a separate concern — deep link / test build)
172
+ - Captures **the feature's target screen** per invocation — reached by replaying the Background
173
+ navigation recipe (step 4.5), not just the launcher. For a *different* screen, give it its own
174
+ feature + recipe and re-invoke.
175
+
176
+ ---
177
+
178
+ ## Relationship to other capture skills
179
+
180
+ - `sungen-capture-figma` / `sungen-capture-local` — design/image sources (platform-agnostic)
181
+ - `sungen-capture-live` — web (Playwright MCP)
182
+ - `sungen-capture-mobile` — this skill, mobile (Appium MCP)
183
+
184
+ All write to `requirements/ui/` and report back to the caller.
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: sungen-mobile-gestures
3
+ description: 'Mobile gesture patterns (swipe, long-press, scroll-to, pinch, pull-to-refresh) — Gherkin syntax + Appium MCP mapping. Auto-loaded for @platform:mobile/android/ios screens.'
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## Purpose
8
+
9
+ Document the **mobile-only interactions** that have no web equivalent, so the AI can (a) drive them
10
+ during exploration via Appium MCP and (b) write Gherkin steps for them. These are gestures the web
11
+ patterns (`click`, `hover`, `fill`) don't cover.
12
+
13
+ > Codegen status (Phase 3): the Appium adapter now **compiles** these gesture steps to WebdriverIO:
14
+ > - **`tap` / `taps`** — synonym for `click` (→ `.click()`); **`double-tap`** → double-click.
15
+ > - **`scroll to [X]`** → `.scrollIntoView()` (shared `scroll-action` template).
16
+ > - **`swipe <dir> on [X]`** → `mobile: swipeGesture`.
17
+ > - **`long-press [X] [for N seconds]`** → `mobile: longClickGesture`.
18
+ > - **`rotate to landscape|portrait`** → `driver.setOrientation(...)`.
19
+ > - **`pull-to-refresh on [X]`** → fast `mobile: swipeGesture` (direction down).
20
+ > - **`pinch-zoom in|out on [X]`** → `mobile: pinchOpenGesture` / `pinchCloseGesture`.
21
+ > - **`send app to background for N seconds`** → `driver.background(N)`.
22
+ > - **`open notification panel`** → `driver.openNotifications()`. **Android-only** — XCUITest has no
23
+ > notification-shade automation; on iOS the generated step throws loud. Keep such scenarios `@platform:android`.
24
+ > - **`tap top of [X]`** / **`tap [X] at top`** → tap the element's **visible top edge** (`mobile: clickGesture`
25
+ > at top-centre from the element bounds) instead of its centre — use when the centre is occluded by a
26
+ > floating bottom bar so a normal centre tap would hit the bar.
27
+ >
28
+ > Still exploration-only (no codegen yet): grant/deny permissions, clipboard set/get, dismiss system
29
+ > dialog — author with the `appium_*` tool calls below; templates land in a later phase.
30
+ >
31
+ > 📜 **`scroll to [X]` — two failure modes seen, with the real cause (measured):**
32
+ > 1. *"Default scrollable element '//android.widget.ScrollView' not found"* — wdio's mobile scroll runs
33
+ > `$$('//android.widget.ScrollView')` and throws if 0 match. This is **state-dependent**: that node
34
+ > exists on a list/Home screen but NOT on every screen (tool/tab screens may lack it). If scroll runs
35
+ > while the app is on a screen without one → this error. Not "the screen can't scroll".
36
+ > 2. **`.scrollIntoView()` is a NO-OP when the target is already inside the scroll viewport** — even if it's
37
+ > visually **occluded by a floating bottom nav**. scrollIntoView only scrolls things that are *off-screen*;
38
+ > it has no concept of z-order occlusion (verified: element bounds identical before/after, centre tap
39
+ > still hit the nav). So scroll **cannot** fix an occluded-by-overlay element → use **`tap top of [X]`**
40
+ > (visible-point tap), not scroll.
41
+ >
42
+ > `scroll to [X]` is still useful for genuinely **off-screen** items: it uses Android `UiScrollable`
43
+ > (accessibility-id targets) or a `mobile: scrollGesture` fallback.
44
+ >
45
+ > ⚠️ **Flutter note:** `.scrollIntoView()` looks for a native `android.widget.ScrollView`. Flutter
46
+ > (and some RN) scrollables don't expose one, and a screen whose content fits the viewport isn't
47
+ > scrollable at all — there `scroll to` fails or no-ops. For an element merely **occluded by a floating
48
+ > bottom nav** on a non-scrollable screen, neither scroll nor `element.click()` (taps the occluded
49
+ > centre) works; that needs a visible-point/coordinate tap (not yet supported) → tag such a scenario
50
+ > `@manual` for now.
51
+
52
+ ---
53
+
54
+ ## Gesture catalog
55
+
56
+ All map to the `appium_gesture` MCP tool (action + params). Element-relative gestures pass `elementUUID`
57
+ from `appium_find_element`; screen gestures pass `direction` or coordinates.
58
+
59
+ | Intent | Proposed Gherkin | `appium_gesture` |
60
+ |---|---|---|
61
+ | Tap | `User tap [Settings]` | `action=tap, elementUUID` |
62
+ | Double-tap | `User double-tap [Photo]` | `action=double_tap, elementUUID` |
63
+ | Long-press | `User long-press [Item] for 2 seconds` | `action=long_press, elementUUID, duration=2000` |
64
+ | Swipe (dismiss/switch/carousel) | `User swipe left on [Card]` | `action=swipe, elementUUID, direction=left` |
65
+ | Pull-to-refresh | `User pull-to-refresh on [Feed]` | `action=swipe, direction=down, speed=fast` |
66
+ | Scroll a list | `User scroll down on [Feed]` | `action=scroll, direction=down` |
67
+ | Scroll until visible | `User scroll to [Footer]` | `action=scroll_to_element, strategy, selector, direction` |
68
+ | Pinch zoom in/out | `User pinch-zoom in on [Map]` | `action=pinch_zoom, elementUUID, scale` (>1 in, <1 out) |
69
+ | System back | `User go back` | `action=back` |
70
+ | Drag & drop | `User drag [A] onto [B]` | `appium_drag_and_drop` (separate tool) |
71
+
72
+ Other device-level actions (separate MCP tools, future Gherkin):
73
+ - Rotate: `appium_orientation` — `User rotate to landscape`
74
+ - Permission dialog: `appium_mobile_permissions` / `appium_alert` — `User grant [Location] permission`
75
+ - Background/foreground: `appium_app_lifecycle` — `User send app to background for 5 seconds`
76
+ - Clipboard: `appium_mobile_clipboard` — `User paste into [Field]`
77
+ - Notifications: open panel via `appium_mobile_device_control`
78
+
79
+ ---
80
+
81
+ ## Authoring guidance
82
+
83
+ - **Prefer `scroll_to_element` over blind scrolling.** When a target may be off-screen, use
84
+ `appium_gesture action=scroll_to_element` (same strategy+selector as the find) rather than repeated
85
+ `appium_find_element` — it stops when the element appears or the page stops scrolling.
86
+ - **Direction semantics**: `swipe` = dismiss / switch screen / carousel / pull-to-refresh (use
87
+ `speed=fast`); `scroll` = browse content in a list/feed. Choose by intent, not interchangeably.
88
+ - **Long-press duration**: default 2000ms; pass `duration` for context menus that need a longer hold.
89
+ - **Element vs screen**: pass `elementUUID` to gesture relative to an element; omit it (+ `direction`)
90
+ to gesture on the whole screen.
91
+ - **Gestures in the navigation recipe**: a `Background:` step like `When User scroll to [X]` or
92
+ `And User swipe up on [Feed]` is a valid nav hop — `sungen-capture-mobile` replays it to reach the
93
+ target screen before scanning (see "Background = the navigation recipe" in `sungen-gherkin-syntax`).
94
+
95
+ ---
96
+
97
+ ## Selectors for gestures
98
+
99
+ Same rules as `sungen-selector-fix-mobile`: `accessibility-id` first, then `id`, platform-native, xpath.
100
+ For `scroll_to_element`, the `strategy`+`selector` you pass are the target you're scrolling toward — keep
101
+ them stable (accessibility-id) so the scroll terminates reliably.
102
+
103
+ ---
104
+
105
+ ## What this skill does NOT do
106
+
107
+ - Does not implement gesture codegen (templates land in a later phase).
108
+ - Does not replace `sungen-gherkin-syntax` — it supplements it with the mobile-only step vocabulary.
109
+ - Does not cover tap/set-value/assertions (those are the shared Tier-1 patterns already supported).