@sun-asterisk/sungen 3.2.0 → 3.2.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -0,0 +1,316 @@
1
+ ---
2
+ name: sungen-selector-fix-mobile
3
+ description: 'Mobile selector fixing — Appium MCP exploration, accessibility-id-first strategy, run via wdio. Auto-loaded by run-test for @platform:mobile/android/ios screens.'
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## Strategy: Phased Execution (mobile)
8
+
9
+ Mobile analogue of `sungen-selector-fix`. Same philosophy — pre-generate selectors, smoke-check, fix
10
+ in priority waves — but using **Appium MCP** for exploration and **WebdriverIO** (`npm run test:mobile`)
11
+ to run, instead of Playwright MCP / `playwright test`.
12
+
13
+ **Never run blindly.** Pre-generate selectors → smoke check → widen.
14
+
15
+ ---
16
+
17
+ ## Phase 0: Pre-run Selector Generation (Appium MCP)
18
+
19
+ Populate `selectors.yaml` from the live app before compiling so tests don't fail on missing keys.
20
+
21
+ ### When to run
22
+ - `selectors.yaml` missing/empty, or `[Reference]` keys lack entries and aren't auto-inferable.
23
+ - User re-scans after a UI change.
24
+
25
+ ### Steps
26
+ 1. **Confirm** via `AskUserQuestion`: *"Generate selectors from the live app via Appium MCP now?"* —
27
+ **Yes, scan device** / **Skip (use existing)** / **Cancel**.
28
+ 2. **Collect references**: parse the `.feature` for every `[Reference]` + type. Deduplicate.
29
+ 3. **Launch the app** (delegate to `sungen-capture-mobile` or inline):
30
+ `select_device` → `appium_session_management(create)` with appPackage/appActivity from the feature `Path:`.
31
+ 4. **⚠️ Wait for render** before capturing — a Flutter/RN splash exposes zero elements. Screenshot first;
32
+ if blank, wait and re-check.
33
+ 5. **Capture**: `generate_locators` (priority-ranked locators) for the current screen. Navigate
34
+ (`appium_gesture tap` / `back`) to reach each screen referenced in the feature, capturing per screen.
35
+ 6. **Generate YAML entries**:
36
+ - Keys: follow `sungen-selector-keys` (lowercase, Unicode preserved, `--N` suffix for duplicates).
37
+ - **Selector priority (mobile)** — see Step 3 table: `accessibility-id` > `id` > platform-native > `xpath`.
38
+ - Copy `content-desc`/`accessibility id` **character-for-character** from `generate_locators`. Never
39
+ infer from the Gherkin label.
40
+ - **i18n / Flutter**: `content-desc` often equals the visible text and is locale-dependent. Use
41
+ `{{variable}}` in `value` and add `lbl_*` keys to test-data + locale overlays (see `sungen-locale`).
42
+ 7. **Duplicate check**: if a `content-desc` appears more than once in the tree, add `nth`.
43
+ 8. **Merge, don't overwrite**: keep user-authored entries; add only missing keys.
44
+ 9. **Confirm + write**, then **compile**: `sungen generate --screen <screen> --framework appium`.
45
+ 10. **End session** (`action=delete`) when done exploring.
46
+
47
+ ---
48
+
49
+ ## Phase 0.5: Mobile auth — the auth contract (guided builder)
50
+
51
+ Mobile has **no `storageState`** like web. Instead, `@auth:<role>` features authenticate at runtime
52
+ through an **auth contract** at `specs/.auth-mobile/<role>.json` (gitignored): the generated spec's
53
+ `before(all)` hook (`__ensureAuth`) reaches the logged-in state — **idempotent** (skips login when the
54
+ authed marker is already present thanks to `noReset:true`) and **fail-loud** (throws when the contract
55
+ is missing or login fails, so @auth scenarios are blocked instead of silently running logged-out).
56
+
57
+ **Pre-flight (BEFORE Phase 1):** when the feature carries `@auth:<role>`, check the contract exists:
58
+
59
+ ```bash
60
+ ls specs/.auth-mobile/<role>.json 2>/dev/null || echo MISSING
61
+ ```
62
+
63
+ Present → continue to Phase 1 (the hook handles everything). Missing → run the **guided builder**:
64
+
65
+ ### Guided contract builder
66
+
67
+ 1. `AskUserQuestion`:
68
+ - **Build the contract now (Recommended)** — AI drives the app via Appium MCP and writes the file
69
+ - **I'll write it by hand** — print the schema below and stop
70
+ - **Skip** — @auth scenarios will fail loud with instructions
71
+ 2. **Logged-out baseline**: Appium session (`select_device` → `appium_session_management(create)`,
72
+ `noReset:true`) → activate the app → `appium_get_page_source` → keep the element list.
73
+ 3. **Reach the login form**: ask the user how to navigate there (typical: account/profile tab → login
74
+ entry). Record EVERY tap on the way as a `loginSteps` entry, in order.
75
+ 4. **Credentials**: never echo passwords into chat. Reference them as `{{email}}` / `{{password}}` in
76
+ `loginSteps` and have the user fill the `credentials` map in the file themselves (it is gitignored).
77
+ 5. **Log in once** — either AI drives (`appium_gesture(tap)` + `appium_set_value`) with values the user
78
+ provides, or the user logs in by hand on the device. Both are fine; what matters is the end state.
79
+ 6. **Derive the `authedMarker`**: fresh `appium_get_page_source` → DIFF against the step-2 baseline.
80
+ Candidates = elements present ONLY after login (member-only menu item, avatar, logout). Prefer
81
+ accessibility-id; verify with `appium_find_element`. Never pick an element guests also have.
82
+ 7. **Write the contract** (schema below) and **validate**: terminate + relaunch the app → the marker is
83
+ still present (session persisted). Full loop: run one @auth scenario via
84
+ `SUNGEN_SPECS=<spec> npm run test:mobile`.
85
+
86
+ ### Contract schema — `specs/.auth-mobile/<role>.json` (gitignored)
87
+
88
+ ```jsonc
89
+ {
90
+ "authedMarker": { "type": "accessibility-id", "value": "<element only present when logged in>" },
91
+ "credentials": { "email": "...", "password": "..." }, // keys are free-form per auth method
92
+ "loginSteps": [
93
+ { "action": "tap", "selector": { "type": "accessibility-id", "value": "<account tab>" } },
94
+ { "action": "tap", "selector": { "type": "accessibility-id", "value": "<login entry>" } },
95
+ { "action": "fill", "selector": { "type": "accessibility-id", "value": "<email field>" }, "value": "{{email}}" },
96
+ { "action": "fill", "selector": { "type": "accessibility-id", "value": "<password field>" }, "value": "{{password}}" },
97
+ { "action": "tap", "selector": { "type": "accessibility-id", "value": "<submit>" } }
98
+ // app shows a launch interstitial/ad? add its dismiss tap as the FIRST step
99
+ ]
100
+ }
101
+ ```
102
+
103
+ - `selector.type`: `accessibility-id` | `xpath` | `android-uiautomator` | `ios-predicate` | `id` | `text`/`label`/`placeholder`
104
+ - `action`: `tap` | `fill` (robust fill: type → read back → retry) | `wait`
105
+ - Session model: `noReset:true` keeps the login alive across scenarios. i18n runs flip `noReset:false`
106
+ (device-locale re-detect) which wipes the session — re-login in the contract or keep @auth suites on
107
+ the base locale.
108
+ - The non-automatable parts (OTP, captcha, biometrics) stay manual: have the user complete them once in
109
+ step 5; the persisted session + `authedMarker` check carries every later run.
110
+ ---
111
+
112
+ ## Phases 1–4: Run waves via WebdriverIO
113
+
114
+ Mobile runs go through the wdio runner (auto-selects `@wdio/globals` specs — the mobile ones):
115
+
116
+ ```bash
117
+ # Smoke: a single scenario / spec
118
+ SUNGEN_SPECS='./specs/generated/<screen>/<feature>.spec.ts' npm run test:mobile
119
+
120
+ # Full mobile suite
121
+ npm run test:mobile
122
+ ```
123
+
124
+ - **Phase 1 (smoke)**: run the one screen's spec. Max 2 fix attempts.
125
+ - **Phase 2 (priority)**: run the screen/feature suite. Max 2 fix attempts.
126
+ - **Phase 3 (full)**: `npm run test:mobile`. Max 1 fix attempt.
127
+ - **Phase 4 (regression)**: one final `npm run test:mobile`. No fix loop.
128
+
129
+ > wdio/mocha tag-grep differs from Playwright. For a subset, scope with `SUNGEN_SPECS` or
130
+ > `--mochaOpts.grep '<scenario name>'`.
131
+
132
+ ---
133
+
134
+ ## Diagnosis & Fix (mobile)
135
+
136
+ ### Step 1: Parse failures — and distinguish selector vs. state
137
+
138
+ | Error pattern | Likely root cause | Fix target |
139
+ |---|---|---|
140
+ | `element wasn't found` / `can't call click on "~X"` | **selector mismatch** *or* **app on the wrong screen** | selectors.yaml **or** scenario state (see ⚠️ below) |
141
+ | `waitForDisplayed` timeout | wrong locator, or element not yet rendered | selectors.yaml / add wait |
142
+ | `toHaveText` mismatch | wrong expected data (or locale) | test-data.yaml |
143
+ | `session not created` / `app not installed` | capability / install issue | wdio.conf capabilities / `adb install` |
144
+
145
+ **⚠️ State, not selector.** The most common mobile false alarm: a selector "not found" because the app
146
+ is **on a different screen than the scenario assumed** (e.g. a prior scenario left it deep in a sub-page;
147
+ `appium:noReset=true` keeps state across the whole run). Before "fixing" a selector, **verify the app's
148
+ current screen** with `appium_screenshot`. If the app is simply on the wrong screen, the fix is in the
149
+ **scenario/navigation** (ensure each scenario starts from a known state — e.g. an explicit "navigate to
150
+ Home" step, `back`, or relaunch), not in `selectors.yaml`.
151
+
152
+ ### Step 2: Targeted MCP exploration
153
+ 1. `select_device` + `appium_session_management(create)` (or reuse an open session).
154
+ 2. Navigate to the failing screen (`appium_gesture`), **wait for render**.
155
+ 3. `generate_locators` (or `appium_find_element` to test one candidate). Fix all broken selectors from
156
+ this snapshot. Use `appium_ai` (vision) only as a last resort for low-accessibility screens.
157
+
158
+ ### Step 3: Fix broken selectors — mobile priority
159
+
160
+ | Priority | type | When |
161
+ |---|---|---|
162
+ | 1 | `accessibility-id` | element has `content-desc` / accessibilityIdentifier (most stable) |
163
+ | 2 | `id` | Android resource-id present |
164
+ | 3 | `android-uiautomator` / `ios-predicate` | platform-native, when 1–2 unavailable |
165
+ | 4 | `xpath` | last resort — slow & brittle; prefer `contains(@content-desc,'…')` |
166
+
167
+ Common fixes:
168
+ - Name mismatch → copy exact `content-desc` from `generate_locators`.
169
+ - Multiple matches → add `nth`.
170
+ - Composite `\n` content-desc → `xpath` with `contains` on a unique substring.
171
+ - No label (icon button, search field) → `xpath` / class, or ask devs to add a Semantics label / `Key`.
172
+ - Locale-dependent text → `{{variable}}` + locale overlay (don't hardcode "English"/"Good afternoon!").
173
+
174
+ ### Step 4: Recompile after fix
175
+ ```bash
176
+ sungen generate --screen <screen> --framework appium
177
+ ```
178
+ Then re-run only the current phase.
179
+
180
+ ---
181
+
182
+ ## Cross-platform selectors (`@platform:mobile` — write once, run both)
183
+
184
+ A `@platform:mobile` feature runs on **both** the Android and iOS capability sets in one
185
+ `MOBILE_PLATFORM=both` run, so **every selector must resolve on both OSes**. Only `accessibility-id`
186
+ is truly cross-platform: a Flutter `Semantics(label/identifier)` surfaces as Android `content-desc`
187
+ **and** iOS `name`/`accessibilityIdentifier`, so a single `~Label` matches on each.
188
+
189
+ **Rule for `@platform:mobile`: use `type: accessibility-id` for everything.** Avoid the platform-only
190
+ strategies — a spec that uses them passes on the OS you tested and silently fails on the other:
191
+
192
+ | Strategy | OK on | NOT on | Why it breaks cross-platform |
193
+ |---|---|---|---|
194
+ | `accessibility-id` (`~Label`) | Android + iOS | — | ✅ the one to use |
195
+ | `//*[contains(@content-desc,'…')]` / `@text` xpath | Android | iOS | iOS has no `content-desc`/`text` attribute |
196
+ | `android-uiautomator` (UiScrollable, UiSelector) | Android | iOS | UiAutomator2 only |
197
+ | `id=` (resource-id) | Android | iOS | Android resource-id only |
198
+ | `-ios predicate string:` / `-ios class chain` | iOS | Android | XCUITest only |
199
+
200
+ When scanning a `@platform:mobile` screen with Appium MCP, **verify on both a booted Android device
201
+ and an iOS simulator** and keep only selectors whose value matches on each (for Flutter the label is
202
+ normally identical). If a required element is labeled on one OS but not the other (e.g. an icon button
203
+ with no Semantics label on Android), either ask devs to add a matching `Semantics`/`accessibilityIdentifier`,
204
+ or **split the feature** into `@platform:android` + `@platform:ios` files (each may then use its native
205
+ strategy). Do NOT smuggle an Android-only xpath into a `@platform:mobile` selector set.
206
+
207
+ > The app id may differ per OS (e.g. a Flutter `.dev` flavor on iOS). You don't handle that in
208
+ > selectors — the generated spec resolves it at runtime (`driver.isIOS` → iOS bundle id from the
209
+ > session caps, else the Android package from `Path:`). Selectors only need to be label-stable.
210
+
211
+ For `@platform:android`-only or `@platform:ios`-only features, the **full** Step 3 priority table
212
+ applies — platform-native strategies are fine because the spec runs on that one OS.
213
+
214
+ ---
215
+
216
+ ## Per-platform selector variants (`android:` / `ios:` in selectors.yaml)
217
+
218
+ When a logical element can't be one cross-platform selector — **composite** content-desc, **partial/dynamic**
219
+ text (greeting, substrings), or **per-OS-divergent** labels — give the key `android:` and `ios:` sub-selectors
220
+ instead of a flat `type`/`value`. For a `@platform:mobile` feature the generated spec renders a runtime
221
+ `driver.isIOS ? '<ios>' : '<android>'` per key (so ONE shared scenario resolves the right native selector on
222
+ each OS); a `@platform:android` / `@platform:ios` feature picks that variant directly.
223
+
224
+ ```yaml
225
+ # composite on Android (contains) / predicate on iOS — one shared scenario, both OSes
226
+ "settings--title":
227
+ android: { type: xpath, value: "//*[contains(@content-desc,'{{settings_subtitle}}')]" }
228
+ ios: { type: ios-predicate, value: "name CONTAINS '{{settings_subtitle}}' OR label CONTAINS '{{settings_subtitle}}'" }
229
+ ```
230
+
231
+ - Define **both** variants for a `@platform:mobile` key (a missing side falls back to the bare entry).
232
+ - `{{var}}` interpolation works inside each variant. **A shared `nth`/`scope`/`name` (at the key's top
233
+ level) applies to both OSes, but `nth`/`scope`/`name` CANNOT diverge per-platform** — the codegen ternary
234
+ swaps only strategy+value (`appium-selector-expr.hbs`). If Android and iOS need a DIFFERENT `nth`/`scope`,
235
+ that's a sub-feature, not a variant.
236
+ - **Prefer a single exact `accessibility-id`** when the label is genuinely exact on both OSes (e.g. a clean
237
+ Flutter Semantics label) — only reach for variants when exact can't work. Verified example: "Output Format"
238
+ is exact → one `accessibility-id` passes both; "Select Image"/"Select Video" are composite → need variants.
239
+ - This is the in-place alternative to splitting into per-OS sub-features: variants keep the scenario shared
240
+ and only the SELECTOR differs per OS (no scenario duplication). Use sub-features when the *scenario itself*
241
+ (steps/flow) differs per OS, variants when only the *locator* differs.
242
+
243
+ > Below-fold on iOS: `scroll to` IS cross-platform (Android UiScrollable / iOS swipe-loop), and
244
+ > `tap top of` on iOS taps the TOP-LEFT of the element — on short iOS viewports a bottom-row card may
245
+ > peek only a few pt above a floating bottom nav whose raised centre FAB occludes the top-CENTRE
246
+ > (verified live on the converter; top-left tap opens it). If a list genuinely cannot scroll further
247
+ > and the element stays fully occluded, only then split that case to `@platform:android`.
248
+
249
+ ## Layered workflow & per-OS sub-features
250
+
251
+ A screen's mobile coverage is **layered**. The shared file is the DEFAULT — exact accessibility-id OR
252
+ `android:`/`ios:` selector variants (when only the locator differs). Push a case to a per-OS sub-feature
253
+ ONLY on genuine SCENARIO divergence (different steps/flow, OS-only element, per-platform `nth`/`scope`):
254
+
255
+ | File | Tag | Runs on | Selectors |
256
+ |---|---|---|---|
257
+ | `<screen>.feature` | `@platform:mobile` | Android **and** iOS | `accessibility-id` only |
258
+ | `<screen>-android.feature` | `@platform:android` | Android only | full Android arsenal (`id`, `-android uiautomator`, `@content-desc` xpath, …) |
259
+ | `<screen>-ios.feature` | `@platform:ios` | iOS only | full iOS arsenal (`-ios predicate`, …) |
260
+
261
+ **Discovery order (explore one OS, run the other):**
262
+ 1. Explore the available OS (`sungen-capture-mobile`), write the **shared** cases into `<screen>.feature`
263
+ with accessibility-id selectors.
264
+ 2. Run them on the other OS: `MOBILE_PLATFORM=both npm run test:mobile`. Green ⇒ done, no second
265
+ exploration needed.
266
+ 3. For any element that **fails** on the 2nd OS, first add an `android:`/`ios:` selector VARIANT to its key
267
+ (the usual fix — composite content-desc / predicate / partial text) and re-run; the scenario stays
268
+ shared. Move the case into `<screen>-<os>.feature` ONLY if the FLOW itself diverges or a per-platform
269
+ `nth`/`scope` is required (a variant can't diverge those).
270
+
271
+ **Running the layers** (each `.feature` compiles to its own spec, routed by its `// sungen:platform=` marker):
272
+ ```bash
273
+ MOBILE_PLATFORM=both npm run test:mobile # shared specs on BOTH + each OS's own sub-feature specs
274
+ MOBILE_PLATFORM=android npm run test:mobile # shared + @platform:android only
275
+ MOBILE_PLATFORM=ios npm run test:mobile # shared + @platform:ios only
276
+ ```
277
+ (`wdio.conf` gives the Android cap the android+mobile specs and the iOS cap the ios+mobile specs, so a
278
+ shared spec runs on each and a sub-feature spec runs only on its OS.)
279
+
280
+ ## Scrolling to off-screen elements (Flutter caveat)
281
+
282
+ `scroll to [X]` compiles to `.scrollIntoView()`, which needs a native `android.widget.ScrollView`.
283
+ Flutter/RN scrollables usually don't expose one → it errors ("Default scrollable element not found").
284
+ For a **Flutter scrollable list**, prefer making the *target selector* auto-scroll via an
285
+ `android-uiautomator` UiScrollable instead of a separate scroll step:
286
+
287
+ ```yaml
288
+ "hindi option":
289
+ type: android-uiautomator
290
+ value: 'new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().descriptionContains("हिन्दी"))'
291
+ ```
292
+
293
+ When `$()` resolves that selector, UiAutomator2 scrolls the list until the element is found — no
294
+ separate `scroll to` needed. If the screen **isn't scrollable** (content fits the viewport) and an
295
+ element is merely occluded by a floating bottom bar, neither scroll nor `element.click()` works → tag
296
+ that scenario `@manual` until visible-point tapping lands.
297
+
298
+ ## Flutter / React-Native note
299
+
300
+ These render to a canvas, but expose a **Semantics tree** to Appium as `content-desc` once a real screen
301
+ is drawn — so UiAutomator2 + `accessibility-id` usually works (no flutter-driver needed) **provided** the
302
+ app sets semantic labels. If `generate_locators` returns almost nothing on a populated screen, the app
303
+ likely lacks semantics → escalate: ask devs to add `Semantics`/`Key`, or consider `appium-flutter-driver`
304
+ (needs a debug/profile build). A blank result on a *loading* screen just means "wait for render".
305
+
306
+ ---
307
+
308
+ ## Attempt Budget Summary
309
+
310
+ | Phase | What runs | Max fix attempts |
311
+ |---|---|---|
312
+ | 0. Pre-gen | Appium MCP `generate_locators` → selectors.yaml | 1 scan |
313
+ | 1. Smoke | one screen spec via wdio | 2 |
314
+ | 2. Priority | screen/feature suite | 2 |
315
+ | 3. Full | `npm run test:mobile` | 1 |
316
+ | 4. Regression | final `npm run test:mobile` | 0 |
@@ -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 → ask the user: *"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, ask the user), then
84
+ **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).