@sun-asterisk/sungen 3.2.0-beta.141 → 3.2.0-beta.143

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 (242) 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/generators/test-generator/adapters/appium/appium-adapter.d.ts +54 -0
  8. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -0
  9. package/dist/generators/test-generator/adapters/appium/appium-adapter.js +52 -0
  10. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -0
  11. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
  12. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
  13. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
  14. package/dist/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
  15. package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
  16. package/dist/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
  17. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
  18. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
  19. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
  20. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
  21. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
  22. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
  23. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
  24. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
  25. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
  26. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
  27. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
  28. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
  29. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
  30. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
  31. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
  32. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
  33. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
  34. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
  35. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
  36. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
  37. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
  38. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
  39. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
  40. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
  41. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  42. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
  43. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
  44. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
  45. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
  46. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
  47. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
  48. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
  49. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
  50. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
  51. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
  52. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
  53. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
  54. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
  55. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
  56. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
  57. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
  58. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
  59. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
  60. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
  61. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
  62. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
  63. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
  64. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
  65. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  66. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
  67. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
  68. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
  69. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
  70. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
  71. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
  72. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
  73. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
  74. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
  75. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  76. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
  77. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
  78. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
  79. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
  80. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
  81. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
  82. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
  83. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
  84. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
  85. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
  86. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
  87. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
  88. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
  89. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
  90. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
  91. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
  92. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
  93. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
  94. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
  95. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
  96. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
  97. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
  98. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
  99. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
  100. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
  101. package/dist/generators/test-generator/adapters/index.d.ts +1 -0
  102. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  103. package/dist/generators/test-generator/adapters/index.js +9 -1
  104. package/dist/generators/test-generator/adapters/index.js.map +1 -1
  105. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  106. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  107. package/dist/generators/test-generator/step-mapper.js +7 -37
  108. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  109. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  110. package/dist/generators/test-generator/template-engine.js +13 -1
  111. package/dist/generators/test-generator/template-engine.js.map +1 -1
  112. package/dist/harness/audit.d.ts +1 -1
  113. package/dist/harness/audit.d.ts.map +1 -1
  114. package/dist/harness/audit.js +14 -4
  115. package/dist/harness/audit.js.map +1 -1
  116. package/dist/harness/catalog/drivers.yaml +1 -1
  117. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  118. package/dist/orchestrator/ai-rules-updater.js +8 -0
  119. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  120. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
  121. package/dist/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
  122. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
  123. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
  124. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
  125. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
  126. package/dist/orchestrator/templates/env.appium.example +25 -0
  127. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
  128. package/dist/orchestrator/templates/wdio.conf.ts +295 -0
  129. package/dist/utils/selector-types.d.ts +1 -1
  130. package/dist/utils/selector-types.d.ts.map +1 -1
  131. package/dist/utils/selector-types.js +5 -0
  132. package/dist/utils/selector-types.js.map +1 -1
  133. package/package.json +3 -3
  134. package/src/capabilities/context-router.ts +15 -3
  135. package/src/capabilities/discover.ts +1 -1
  136. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +57 -0
  137. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
  138. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
  139. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
  140. package/src/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
  141. package/src/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
  142. package/src/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
  143. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
  144. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
  145. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
  146. package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
  147. package/src/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
  148. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
  149. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
  150. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
  151. package/src/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
  152. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
  153. package/src/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
  154. package/src/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
  155. package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
  156. package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
  157. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
  158. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
  159. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
  160. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
  161. package/src/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
  162. package/src/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
  163. package/src/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
  164. package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
  165. package/src/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
  166. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
  167. package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  168. package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
  169. package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
  170. package/src/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
  171. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
  172. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
  173. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
  174. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
  175. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
  176. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
  177. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
  178. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
  179. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
  180. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
  181. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
  182. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
  183. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
  184. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
  185. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
  186. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
  187. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
  188. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
  189. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
  190. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
  191. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  192. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
  193. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
  194. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
  195. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
  196. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
  197. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
  198. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
  199. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
  200. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
  201. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  202. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
  203. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
  204. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
  205. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
  206. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
  207. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
  208. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
  209. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
  210. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
  211. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
  212. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
  213. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
  214. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
  215. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
  216. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
  217. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
  218. package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
  219. package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
  220. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
  221. package/src/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
  222. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
  223. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
  224. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
  225. package/src/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
  226. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
  227. package/src/generators/test-generator/adapters/index.ts +7 -0
  228. package/src/generators/test-generator/step-mapper.ts +8 -5
  229. package/src/generators/test-generator/template-engine.ts +13 -1
  230. package/src/harness/audit.ts +12 -4
  231. package/src/harness/catalog/drivers.yaml +1 -1
  232. package/src/orchestrator/ai-rules-updater.ts +8 -0
  233. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
  234. package/src/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
  235. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
  236. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
  237. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
  238. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
  239. package/src/orchestrator/templates/env.appium.example +25 -0
  240. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
  241. package/src/orchestrator/templates/wdio.conf.ts +295 -0
  242. package/src/utils/selector-types.ts +5 -0
@@ -0,0 +1,295 @@
1
+ /**
2
+ * WebdriverIO runner config for sungen mobile (Appium) tests.
3
+ * Mobile equivalent of playwright.config.ts. Run with: npm run test:mobile
4
+ *
5
+ * Capabilities default to the app configured at `sungen init --mobile`, but every value can be
6
+ * overridden at runtime via env vars — no need to edit this file to target another app/device:
7
+ * APP_PACKAGE=com.other.app ANDROID_UDID=emulator-5556 npm run test:mobile
8
+ * (the launch activity is auto-detected from APP_PACKAGE; set APP_ACTIVITY only to pin a non-standard one)
9
+ * APP_APK=/path/to/app.apk npm run test:mobile # install + run from an .apk (Appium derives pkg/activity)
10
+ *
11
+ * iOS Simulator (opt in with MOBILE_PLATFORM=ios; needs Xcode + the xcuitest driver):
12
+ * MOBILE_PLATFORM=ios IOS_BUNDLE_ID=com.example.app npm run test:mobile # app already installed on the sim
13
+ * MOBILE_PLATFORM=ios IOS_APP=/path/MyApp.app npm run test:mobile # Appium installs a Simulator build
14
+ * (+ optional IOS_DEVICE='iPhone 16' IOS_VERSION='18.0' IOS_UDID=<sim-udid>)
15
+ *
16
+ * The @wdio/appium-service auto-starts a local Appium server (auto-detecting the uiautomator2 / xcuitest driver).
17
+ */
18
+ /// <reference types="node" />
19
+ /// <reference types="@wdio/globals/types" />
20
+ // Load .env.appium (if present) into process.env FIRST — so every override below (ANDROID_HOME / JAVA_HOME /
21
+ // ANDROID_UDID / APP_PACKAGE / IOS_BUNDLE_ID / MOBILE_PLATFORM / SUNGEN_ENV …) can come from a dedicated
22
+ // .env.appium file instead of shell exports. A separate file (not the generic .env) keeps Appium config from
23
+ // clashing with other .env vars (e.g. FIGMA_PAT). dotenv is a dependency; a missing .env.appium is a no-op.
24
+ import dotenv from 'dotenv';
25
+ dotenv.config({ path: '.env.appium' });
26
+ import * as fs from 'node:fs';
27
+ import * as path from 'node:path';
28
+ import PwShapeReporter from './specs/reporters/pw-shape-reporter';
29
+
30
+ /**
31
+ * Auto-select MOBILE specs and classify each by its target OS (dual-target routing).
32
+ *
33
+ * sungen emits one spec per feature; the test runner differs by platform:
34
+ * - mobile/appium specs import from '@wdio/globals' ← run these with wdio
35
+ * - web/playwright specs import from '@playwright/test' ← run those with `playwright test`
36
+ * The '@wdio/globals' import is the machine-readable "platform = mobile" marker, so we keep
37
+ * only those files and never feed Playwright specs into the (incompatible) mocha runner.
38
+ *
39
+ * Each mobile spec also carries a `// sungen:platform=<mobile|android|ios>` marker (emitted from the
40
+ * feature's @platform:* tag). It routes the spec per OS:
41
+ * - mobile → cross-platform: runs on BOTH the Android and iOS capability sets
42
+ * - android → Android cap only
43
+ * - ios → iOS cap only
44
+ * A spec with no marker (older codegen) defaults to `mobile` (runs on both) so nothing is dropped.
45
+ *
46
+ * Override with SUNGEN_SPECS=<glob-or-path> for a single spec (still classified by its marker).
47
+ */
48
+ function readPlatformMarker(file: string): 'mobile' | 'android' | 'ios' {
49
+ try {
50
+ const m = /sungen:platform=(mobile|android|ios)/.exec(fs.readFileSync(file, 'utf-8'));
51
+ return (m?.[1] as 'mobile' | 'android' | 'ios') || 'mobile';
52
+ } catch {
53
+ return 'mobile';
54
+ }
55
+ }
56
+ function classifyMobileSpecs(root: string): Array<{ file: string; platform: string }> {
57
+ if (!fs.existsSync(root)) return [];
58
+ return (fs.readdirSync(root, { recursive: true }) as string[])
59
+ .filter((rel) => rel.endsWith('.spec.ts'))
60
+ .map((rel) => path.join(root, rel))
61
+ .filter((full) => {
62
+ try {
63
+ return fs.readFileSync(full, 'utf-8').includes('@wdio/globals');
64
+ } catch {
65
+ return false;
66
+ }
67
+ })
68
+ .map((file) => ({ file, platform: readPlatformMarker(file) }));
69
+ }
70
+
71
+ const specEntries = process.env.SUNGEN_SPECS
72
+ ? [process.env.SUNGEN_SPECS].map((file) => ({ file, platform: readPlatformMarker(file) }))
73
+ : classifyMobileSpecs(path.join(process.cwd(), 'specs', 'generated'));
74
+
75
+ // Android cap runs android + mobile (everything except ios-only); iOS cap runs ios + mobile.
76
+ const androidSpecs = specEntries.filter((e) => e.platform !== 'ios').map((e) => e.file);
77
+ const iosSpecs = specEntries.filter((e) => e.platform !== 'android').map((e) => e.file);
78
+ const mobileSpecs = specEntries.map((e) => e.file); // union — top-level fallback
79
+
80
+ // App under test — env overrides the init-configured defaults.
81
+ const APP_PACKAGE = process.env.APP_PACKAGE || '__APP_PACKAGE__';
82
+ const APP_ACTIVITY = process.env.APP_ACTIVITY || '__APP_ACTIVITY__';
83
+ const APP_APK = process.env.APP_APK; // optional: install + launch from an .apk file
84
+ // appActivity is OPTIONAL: UiAutomator2 auto-resolves the package's launcher activity when it's
85
+ // omitted, and the per-scenario relaunch uses activateApp(package) (no activity). So Android needs
86
+ // only APP_PACKAGE — set APP_ACTIVITY only to pin a non-standard launcher. iOS has no activity
87
+ // concept at all (bundleId only). Only pass the cap when APP_ACTIVITY is a real value (not the
88
+ // unfilled scaffold placeholder / empty), so a package-only setup launches cleanly.
89
+ const HAS_REAL_APP_ACTIVITY = !!APP_ACTIVITY && APP_ACTIVITY !== '__APP_ACTIVITY__';
90
+
91
+ // ── iOS (Simulator) — opt in with MOBILE_PLATFORM=ios; every value is env-overridable ──────────
92
+ // MOBILE_PLATFORM=ios IOS_BUNDLE_ID=com.example.app npm run test:mobile
93
+ // - IOS_APP=/path/MyApp.app → Appium installs a SIMULATOR build (.app or zipped .app) first.
94
+ // (A device .ipa won't run on a Simulator — wrong binary/platform.)
95
+ // - IOS_BUNDLE_ID=com.x → attach to an app already installed on the sim (simctl install first).
96
+ // - IOS_DEVICE / IOS_VERSION → pick the simulator; IOS_UDID targets a specific one.
97
+ //
98
+ // MOBILE_PLATFORM selects which capability set(s) run:
99
+ // android (default) → Android cap only (runs @platform:android + @platform:mobile specs)
100
+ // ios → iOS cap only (runs @platform:ios + @platform:mobile specs)
101
+ // both → BOTH caps in one run (a @platform:mobile spec executes on each — write once, run both)
102
+ const MOBILE_PLATFORM = (process.env.MOBILE_PLATFORM || 'android').toLowerCase();
103
+ const RUN_ANDROID = MOBILE_PLATFORM === 'android' || MOBILE_PLATFORM === 'both';
104
+ const RUN_IOS = MOBILE_PLATFORM === 'ios' || MOBILE_PLATFORM === 'both';
105
+ const IOS_APP = process.env.IOS_APP;
106
+ const IOS_BUNDLE_ID = process.env.IOS_BUNDLE_ID || '__IOS_BUNDLE_ID__';
107
+ const IOS_DEVICE = process.env.IOS_DEVICE || '__IOS_DEVICE__';
108
+ const IOS_VERSION = process.env.IOS_VERSION || '__IOS_VERSION__';
109
+ // platformVersion is OPTIONAL on a real device: with IOS_UDID set, XCUITest reads the iOS version off
110
+ // the connected device. Only pass the cap when IOS_VERSION is a real value (not the unfilled scaffold
111
+ // placeholder). On a Simulator set it (or IOS_DEVICE) so Appium picks a matching runtime.
112
+ const HAS_REAL_IOS_VERSION = !!IOS_VERSION && IOS_VERSION !== '__IOS_VERSION__';
113
+ const IOS_UDID = process.env.IOS_UDID;
114
+ // Real iPhone (device) — WebDriverAgent must be code-signed onto it. Set IOS_TEAM_ID (your Apple
115
+ // Team ID) to switch the iOS cap into real-device mode; the Simulator ignores these (they're only
116
+ // emitted when IOS_TEAM_ID is present, so Simulator runs are unchanged). For a real device also set
117
+ // IOS_UDID (the device udid, NOT a sim) and the REAL IOS_BUNDLE_ID (Android package ≠ iOS bundle id).
118
+ const IOS_TEAM_ID = process.env.IOS_TEAM_ID; // → appium:xcodeOrgId
119
+ const IOS_SIGNING_ID = process.env.IOS_SIGNING_ID || 'Apple Development'; // → appium:xcodeSigningId
120
+ const IOS_WDA_BUNDLE_ID = process.env.IOS_WDA_BUNDLE_ID || 'com.sungen.wda'; // → appium:updatedWDABundleId; default gives WDA a unique id (required for free teams) so you needn't set it
121
+
122
+ // ── i18n (device locale) ────────────────────────────────────────────────────
123
+ // SUNGEN_ENV doubles as the locale selector (the same var that swaps the test-data overlay).
124
+ // When its value looks like a locale code ("vi", "ja", "vi-VN"), drive the DEVICE locale via
125
+ // appium:language/appium:locale so an app that localizes from device locale on first launch picks
126
+ // it up, and force a data reset (noReset:false) so that first-launch path actually re-runs (clears
127
+ // any persisted in-app language override). Non-locale envs ("staging", "production") are left
128
+ // untouched, so existing suites are unaffected. SUNGEN_LOCALE overrides explicitly when the device
129
+ // locale must differ from the overlay name. Country defaults are used only when the code omits a region.
130
+ const LOCALE_COUNTRY: Record<string, string> = {
131
+ vi: 'VN',
132
+ ja: 'JP',
133
+ en: 'US',
134
+ ko: 'KR',
135
+ zh: 'CN',
136
+ th: 'TH',
137
+ id: 'ID',
138
+ fr: 'FR',
139
+ de: 'DE',
140
+ es: 'ES',
141
+ pt: 'BR',
142
+ ru: 'RU',
143
+ };
144
+ function resolveLocaleCaps(): Record<string, unknown> {
145
+ const raw = process.env.SUNGEN_LOCALE || process.env.SUNGEN_ENV;
146
+ if (!raw) return {};
147
+ const m = /^([a-z]{2})(?:[-_]([A-Za-z]{2}))?$/.exec(raw.trim());
148
+ if (!m) return {}; // not a locale code (e.g. "staging") → don't touch the device locale
149
+ const language = m[1];
150
+ const country = (m[2] || LOCALE_COUNTRY[language] || language).toUpperCase();
151
+ return {
152
+ 'appium:language': language,
153
+ 'appium:locale': country,
154
+ 'appium:noReset': false,
155
+ };
156
+ }
157
+ const localeCaps = resolveLocaleCaps();
158
+
159
+ export const config: WebdriverIO.Config = {
160
+ runner: 'local',
161
+ tsConfigPath: './tsconfig.json',
162
+
163
+ specs: mobileSpecs,
164
+ // SUNGEN_MAX_INSTANCES=2 lets a MOBILE_PLATFORM=both run execute the Android and iOS capability
165
+ // in PARALLEL (separate devices; uiautomator2/XCUITest do not share ports; the reporter writes
166
+ // per-platform files so results cannot collide). Default stays 1 — serial is the safe choice on
167
+ // a laptop driving an emulator + a simulator at once.
168
+ maxInstances: Number(process.env.SUNGEN_MAX_INSTANCES || 1),
169
+
170
+ // Global auto-wait for $/$$ matchers (toBeDisplayed, click, …). Default is 3s, too short for a
171
+ // heavy app's cold (re)launch — each scenario terminates+activates its app, so the first
172
+ // find/click must wait out the splash/first-frame. 20s keeps slow native/Flutter apps green.
173
+ waitforTimeout: 20000,
174
+
175
+ // Capability set(s) selected by MOBILE_PLATFORM (android | ios | both). Each cap carries its OWN
176
+ // `specs` (per-OS routing): the Android cap runs android + mobile specs, the iOS cap runs ios +
177
+ // mobile specs, so MOBILE_PLATFORM=both executes a @platform:mobile spec on each in a single run.
178
+ // i18n localeCaps (appium:language/locale + noReset:false) apply to both when SUNGEN_ENV is a locale.
179
+ capabilities: [
180
+ ...(RUN_ANDROID
181
+ ? [
182
+ {
183
+ platformName: 'Android',
184
+ 'appium:automationName': 'UiAutomator2',
185
+ 'appium:udid': process.env.ANDROID_UDID || '__ANDROID_UDID__',
186
+ // APP_APK installs from a file (Appium reads pkg/activity from the manifest);
187
+ // otherwise target an already-installed app by package + activity.
188
+ ...(APP_APK
189
+ ? { 'appium:app': APP_APK }
190
+ : {
191
+ 'appium:appPackage': APP_PACKAGE,
192
+ // Omit when unset → UiAutomator2 launches the package's default launcher activity.
193
+ ...(HAS_REAL_APP_ACTIVITY ? { 'appium:appActivity': APP_ACTIVITY } : {}),
194
+ }),
195
+ 'appium:noReset': true,
196
+ 'appium:autoGrantPermissions': true,
197
+ 'appium:newCommandTimeout': 300,
198
+ // i18n: when SUNGEN_ENV/SUNGEN_LOCALE is a locale code, this overrides noReset→false and
199
+ // adds appium:language/appium:locale so the app boots in that locale. Empty otherwise.
200
+ ...localeCaps,
201
+ // Per-capability spec routing (supported by WDIO at runtime; not in the TS cap type → cast).
202
+ specs: androidSpecs,
203
+ } as any,
204
+ ]
205
+ : []),
206
+ ...(RUN_IOS
207
+ ? [
208
+ {
209
+ platformName: 'iOS',
210
+ 'appium:automationName': 'XCUITest',
211
+ 'appium:deviceName': IOS_DEVICE,
212
+ // Omit when unknown (real device, no IOS_VERSION) → XCUITest reads it from the device by udid.
213
+ ...(HAS_REAL_IOS_VERSION ? { 'appium:platformVersion': IOS_VERSION } : {}),
214
+ // target a specific simulator if given (else Appium uses the matching device/version)
215
+ ...(IOS_UDID ? { 'appium:udid': IOS_UDID } : {}),
216
+ // IOS_APP installs a Simulator build (.app/.zip); otherwise attach to an installed bundleId.
217
+ ...(IOS_APP
218
+ ? { 'appium:app': IOS_APP }
219
+ : IOS_BUNDLE_ID
220
+ ? { 'appium:bundleId': IOS_BUNDLE_ID }
221
+ : {}),
222
+ // Real-device WDA signing — emitted only when IOS_TEAM_ID is set (Simulator: omitted → unchanged).
223
+ ...(IOS_TEAM_ID
224
+ ? {
225
+ 'appium:xcodeOrgId': IOS_TEAM_ID,
226
+ 'appium:xcodeSigningId': IOS_SIGNING_ID,
227
+ // Pass -allowProvisioningUpdates -allowProvisioningDeviceRegistration to xcodebuild so it
228
+ // auto-creates/refreshes the WDA profile + registers the device (needed for automatic
229
+ // signing on a real device; without it WDA build fails fast with "xcodebuild code 65").
230
+ 'appium:allowProvisioningDeviceRegistration': true,
231
+ ...(IOS_WDA_BUNDLE_ID ? { 'appium:updatedWDABundleId': IOS_WDA_BUNDLE_ID } : {}),
232
+ }
233
+ : {}),
234
+ 'appium:noReset': true,
235
+ 'appium:newCommandTimeout': 300,
236
+ ...localeCaps,
237
+ specs: iosSpecs,
238
+ } as any,
239
+ ]
240
+ : []),
241
+ ],
242
+
243
+ logLevel: 'warn',
244
+ framework: 'mocha',
245
+ // 'spec' for console + PwShapeReporter for a Playwright-shaped JSON the delivery/dashboard reuse.
246
+ reporters: ['spec', [PwShapeReporter, {}]],
247
+
248
+ // Auto-start a local Appium server on :4723 (base path '/')
249
+ services: ['appium'],
250
+ port: 4723,
251
+ path: '/',
252
+
253
+ mochaOpts: {
254
+ ui: 'bdd',
255
+ timeout: 120000,
256
+ },
257
+
258
+ // iOS + locale: XCUITest's `noReset:false` does NOT actually clear the app's data container
259
+ // (verified live 2026-06-11) — a Flutter app that persisted its language on a previous launch keeps
260
+ // it and ignores the new device locale, so the i18n run asserts the wrong language. Wipe the app
261
+ // data via simctl BEFORE the session so first-launch locale detection re-runs. Android needs no
262
+ // hook (its noReset:false reset works natively).
263
+ beforeSession: async (_config, capabilities) => {
264
+ if (Object.keys(localeCaps).length === 0) return; // not a locale run
265
+ const caps = capabilities as Record<string, unknown>;
266
+ if (String(caps['platformName'] || '').toLowerCase() !== 'ios') return;
267
+ const { execSync } = await import('node:child_process');
268
+ const fsm = await import('node:fs');
269
+ const udid = (caps['appium:udid'] as string) || 'booted';
270
+ // Wipe the session-default app AND every per-feature dual-id bundle the worker's specs declare
271
+ // (__IOS_BUNDLE__ markers) — a multi-app iOS locale run must reset each app, not just the first.
272
+ const bundleIds = new Set<string>();
273
+ if (typeof caps['appium:bundleId'] === 'string') bundleIds.add(caps['appium:bundleId'] as string);
274
+ for (const spec of specs || []) {
275
+ try {
276
+ const src = fsm.readFileSync(String(spec).replace('file://', ''), 'utf-8');
277
+ const m = /__IOS_BUNDLE__ = '([^']+)'/.exec(src);
278
+ if (m) bundleIds.add(m[1]);
279
+ } catch { /* unreadable spec — skip */ }
280
+ }
281
+ for (const bundleId of bundleIds) {
282
+ try { execSync(`xcrun simctl terminate ${udid} ${bundleId}`, { stdio: 'ignore' }); } catch { /* not running */ }
283
+ try {
284
+ const dataDir = execSync(`xcrun simctl get_app_container ${udid} ${bundleId} data`).toString().trim();
285
+ if (dataDir) execSync(`rm -rf "${dataDir}/Library" "${dataDir}/Documents"`);
286
+ console.log(`[sungen] iOS locale run: wiped app data for ${bundleId} (first-launch locale re-detect)`);
287
+ } catch { /* app not installed yet */ }
288
+ }
289
+ },
290
+
291
+ // Per-scenario app reset lives in each generated spec's `beforeEach` (terminate+activate the app
292
+ // from that feature's `Path:`). One run can therefore host screens from different apps — each spec
293
+ // launches its own — so there's no single-app reset hook here. The capabilities above only bootstrap
294
+ // the session (default app / device); the real app per scenario comes from the spec.
295
+ };
@@ -2,6 +2,6 @@
2
2
  * Valid selector types used across scaffold generator, selector resolver, and validator.
3
3
  * Single source of truth — import this instead of hardcoding.
4
4
  */
5
- export declare const VALID_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "page", "column", "locator", "id", "upload", "frame", "table"];
5
+ export declare const VALID_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "page", "column", "locator", "id", "upload", "frame", "table", "accessibility-id", "xpath", "android-uiautomator", "ios-predicate"];
6
6
  export type SelectorType = (typeof VALID_SELECTOR_TYPES)[number];
7
7
  //# sourceMappingURL=selector-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,4HAavB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,iMAkBvB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -18,5 +18,10 @@ exports.VALID_SELECTOR_TYPES = [
18
18
  'upload',
19
19
  'frame',
20
20
  'table',
21
+ // === Mobile (Appium) strategies — resolved by the appium adapter (MOB-3) ===
22
+ 'accessibility-id', // cross-platform — Android content-desc / iOS accessibilityIdentifier
23
+ 'xpath', // cross-platform, last resort
24
+ 'android-uiautomator', // Android only — UiSelector expressions
25
+ 'ios-predicate', // iOS only — NSPredicate strings
21
26
  ];
22
27
  //# sourceMappingURL=selector-types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,oBAAoB,GAAG;IAClC,aAAa;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,IAAI;IACJ,QAAQ;IACR,OAAO;IACP,OAAO;CACC,CAAC"}
1
+ {"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,oBAAoB,GAAG;IAClC,aAAa;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,IAAI;IACJ,QAAQ;IACR,OAAO;IACP,OAAO;IACP,8EAA8E;IAC9E,kBAAkB,EAAK,sEAAsE;IAC7F,OAAO,EAAgB,8BAA8B;IACrD,qBAAqB,EAAE,wCAAwC;IAC/D,eAAe,EAAQ,iCAAiC;CAChD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "3.2.0-beta.141",
3
+ "version": "3.2.0-beta.143",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "rm -rf dist && tsc && npm run copy-templates",
12
- "copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
12
+ "copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && mkdir -p dist/generators/test-generator/adapters/appium/templates/steps && cp -r src/generators/test-generator/adapters/appium/templates/*.hbs dist/generators/test-generator/adapters/appium/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/appium/templates/steps dist/generators/test-generator/adapters/appium/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
13
13
  "build:dashboard": "cd ../../dashboard && npm install --silent && npm run build && cd - && cp ../../dashboard/dist/index.html src/dashboard/templates/index.html",
14
14
  "dev": "tsx src/cli/index.ts",
15
15
  "test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts",
@@ -33,7 +33,7 @@
33
33
  "node": ">=18.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@sungen/driver-ui": "3.2.0-beta.141",
36
+ "@sungen/driver-ui": "3.2.0-beta.143",
37
37
  "@anthropic-ai/sdk": "^0.71.0",
38
38
  "@babel/parser": "^7.28.5",
39
39
  "@babel/traverse": "^7.28.5",
@@ -20,6 +20,12 @@ export interface RouteTask {
20
20
  artifact: string;
21
21
  /** Capability tags present on the target (e.g. ['@query'], ['@api'], ['@cases']). */
22
22
  tags?: string[];
23
+ /**
24
+ * The project's active platform (qa/capabilities.yaml `platform:` — web | mobile | …). A platform
25
+ * capability (mobile/native) is activated by the project's platform, NOT by a tag, so it is keyed
26
+ * separately. `web` has no capability id of its own (the in-core default is `ui`) → no-op.
27
+ */
28
+ platform?: string;
23
29
  }
24
30
 
25
31
  export interface RoutePlan {
@@ -34,11 +40,17 @@ export interface RoutePlan {
34
40
  }
35
41
 
36
42
  class ContextRouter {
37
- /** Which capabilities a set of tags activates: the default capability + any owning a present tag. */
38
- capabilitiesFor(tags: string[] = []): string[] {
43
+ /**
44
+ * Which capabilities a task activates: the default capability + the active platform's capability
45
+ * (when one is registered with `id === platform`) + any capability owning a present tag.
46
+ */
47
+ capabilitiesFor(tags: string[] = [], platform?: string): string[] {
39
48
  const ids = new Set<string>();
40
49
  const def = capabilityRegistry.defaultCapabilityId();
41
50
  if (def) ids.add(def);
51
+ // A platform capability (mobile/native/…) is activated by the project's platform, not by a tag.
52
+ // `web` has no capability of its own → falls through to the default `ui` (byte-identical).
53
+ if (platform && capabilityRegistry.get(platform)) ids.add(platform);
42
54
  for (const cap of capabilityRegistry.all()) {
43
55
  if ((cap.annotations ?? []).some((a) => tags.includes(a))) ids.add(cap.id);
44
56
  }
@@ -47,7 +59,7 @@ class ContextRouter {
47
59
 
48
60
  /** Compute the routing plan for a task (deterministic). */
49
61
  route(task: RouteTask): RoutePlan {
50
- const capabilities = this.capabilitiesFor(task.tags ?? []);
62
+ const capabilities = this.capabilitiesFor(task.tags ?? [], task.platform);
51
63
  // Generic sensors (no capability, or the always-on `core`) run regardless of the task's tags;
52
64
  // capability-specific sensors run only when their capability is in scope.
53
65
  const inScope = (capId?: string) => capId == null || capId === 'core' || capabilities.includes(capId);
@@ -23,7 +23,7 @@ import { LOCAL_DRIVERS, registerCoreCapability } from './builtins';
23
23
  * move to their packages (R5.5/R5.6) they join this list and leave `LOCAL_DRIVERS`; eventually this
24
24
  * becomes a scan of installed `@sungen/driver-*` packages (R5.7).
25
25
  */
26
- const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api'];
26
+ const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api', '@sungen/driver-mobile'];
27
27
 
28
28
  function loadExternalDriver(name: string): void {
29
29
  // Resolve from the user's PROJECT first, then from core's own location. Opt-in drivers
@@ -0,0 +1,57 @@
1
+ import path from 'path';
2
+ import { TestGeneratorAdapter, TestFileData, ScenarioData } from '../adapter-interface';
3
+ import { TemplateEngine } from '../../template-engine';
4
+
5
+ /**
6
+ * AppiumAdapter
7
+ * Generates WebdriverIO + Appium (Mocha) test code using Handlebars templates.
8
+ *
9
+ * Mirrors PlaywrightAdapter: the adapter only renders the file skeleton (imports,
10
+ * describe/it, before/after hooks). Per-step code is produced by StepMapper +
11
+ * PatternRegistry driving this adapter's templates/ directory — the templates here
12
+ * carry the same names the patterns request (click-action, fill-action,
13
+ * visible-assertion, the `locator` partial, …) but emit WebdriverIO instead of Playwright.
14
+ */
15
+ export class AppiumAdapter implements TestGeneratorAdapter {
16
+ readonly frameworkName = 'appium';
17
+ readonly fileExtension = '.spec.ts';
18
+ readonly templatesDir: string;
19
+ private templateEngine: TemplateEngine;
20
+
21
+ constructor() {
22
+ this.templatesDir = path.join(__dirname, 'templates');
23
+ this.templateEngine = new TemplateEngine(this.templatesDir);
24
+ }
25
+
26
+ renderTestFile(data: TestFileData): string {
27
+ return this.templateEngine.renderTestFile(data);
28
+ }
29
+
30
+ renderScenario(data: ScenarioData): string {
31
+ return this.templateEngine.renderScenario(data);
32
+ }
33
+
34
+ renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean; needsDb?: boolean; needsApi?: boolean }): string {
35
+ return this.templateEngine.renderImports(options);
36
+ }
37
+
38
+ renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
39
+ return this.templateEngine.renderBeforeEach(data);
40
+ }
41
+
42
+ renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
43
+ return this.templateEngine.renderBeforeAll(data);
44
+ }
45
+
46
+ renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string {
47
+ return this.templateEngine.renderAfterEach(data);
48
+ }
49
+
50
+ renderAfterAll(data: { steps: Array<{ comment?: string; code: string }> }): string {
51
+ return this.templateEngine.renderAfterAll(data);
52
+ }
53
+
54
+ renderStep(templateName: string, data: any): string {
55
+ return this.templateEngine.renderStep(templateName, data);
56
+ }
57
+ }
@@ -0,0 +1,8 @@
1
+ after(async () => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ afterEach(async () => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ before(async () => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ beforeEach(async () => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,8 @@
1
+ import { $, $$, expect, driver, browser } from '@wdio/globals';
2
+ {{#if runtimeData}}
3
+ import { TestDataLoader } from '{{basePath}}/test-data';
4
+ {{/if}}
5
+
6
+ // This file is auto-generated from Gherkin feature files
7
+ // DO NOT EDIT MANUALLY - changes will be overwritten
8
+ // To modify tests, edit the corresponding .feature file and regenerate
@@ -0,0 +1,8 @@
1
+ it('{{scenarioName}}', async () => {
2
+ {{#each steps}}
3
+ {{#if comment}}
4
+ // {{comment}}
5
+ {{/if}}
6
+ {{code}}
7
+ {{/each}}
8
+ });
@@ -0,0 +1,4 @@
1
+ {{!-- Accept a native alert. Unlike web (handler set before the trigger), mobile alerts are handled
2
+ AFTER they appear; for launch-time permission dialogs prefer the `appium:autoAcceptAlerts` cap.
3
+ Best-effort: ignore if no alert is present. --}}
4
+ try { await driver.acceptAlert(); } catch { /* no native alert present */ }
@@ -0,0 +1,2 @@
1
+ {{!-- Dismiss a native alert (best-effort; ignore if none present). --}}
2
+ try { await driver.dismissAlert(); } catch { /* no native alert present */ }
@@ -0,0 +1,2 @@
1
+ {{!-- Filling a native prompt is iOS-specific and app-dependent; Android rarely has fillable alerts.
2
+ Requested value: '{{fillValue}}'. Handle manually if your alert has a text field. --}}
@@ -0,0 +1,10 @@
1
+ {{!-- WebdriverIO/Appium has no .check(). Read the current state and tap only if it's not already
2
+ on, so the step is idempotent (re-running a passing test won't flip it back off). Falls through
3
+ to a plain tap if the state can't be read. --}}
4
+ {
5
+ const __cb = await {{> locator}};
6
+ let __on = false;
7
+ try { __on = await __cb.isSelected(); }
8
+ catch { try { __on = (await __cb.getAttribute('checked')) === 'true'; } catch { /* state unreadable — tap anyway */ } }
9
+ if (!__on) await __cb.click();
10
+ }
@@ -0,0 +1,2 @@
1
+ {{!-- Tap an element by its visible text / content-desc (dynamic text match). --}}
2
+ {{#if nth}}await $$('//*[contains(@content-desc,"{{escapeQuotes dataValue}}") or contains(@text,"{{escapeQuotes dataValue}}")]')[{{nth}}].click();{{else}}await $('//*[contains(@content-desc,"{{escapeQuotes dataValue}}") or contains(@text,"{{escapeQuotes dataValue}}")]').click();{{/if}}
@@ -0,0 +1,4 @@
1
+ {{!-- Custom (non-native) select: open the control, then tap the option by accessibility-id /
2
+ content-desc (= the option value). Same two-tap shape as select-action on mobile. --}}
3
+ await {{> locator}}.click();
4
+ await $('~{{escapeQuotes selectValue}}').click();
@@ -0,0 +1,3 @@
1
+ // Best-effort dismiss (launch interstitial / promo / ad): tap if it shows within a short grace,
2
+ // otherwise skip — never fails the step.
3
+ try { const __d = await {{> locator}}; if (await __d.waitForDisplayed({ timeout: 2500 }).then(() => true).catch(() => false)) await __d.click(); } catch { /* not present — skip */ }
@@ -0,0 +1,6 @@
1
+ {{!-- Double-tap. Use the platform gesture (element.doubleClick is web-oriented). --}}
2
+ {
3
+ const __el = await {{> locator}};
4
+ if (driver.isIOS) await driver.execute('mobile: doubleTap', { elementId: __el.elementId });
5
+ else await driver.execute('mobile: doubleClickGesture', { elementId: __el.elementId });
6
+ }
@@ -0,0 +1,2 @@
1
+ {{!-- Drag the source element onto the target. WebdriverIO dragAndDrop drives W3C touch actions. --}}
2
+ await (await {{> locator}}).dragAndDrop(await $('{{#switch targetStrategy}}{{#case 'accessibility-id'}}~{{escapeQuotes targetValue}}{{/case}}{{#case 'testid'}}~{{escapeQuotes targetValue}}{{/case}}{{#case 'xpath'}}{{escapeQuotes targetValue}}{{/case}}{{#case 'android-uiautomator'}}android={{escapeQuotes targetValue}}{{/case}}{{#case 'id'}}id={{escapeQuotes targetValue}}{{/case}}{{#default}}~{{#if targetValue}}{{escapeQuotes targetValue}}{{else}}{{escapeQuotes targetName}}{{/if}}{{/default}}{{/switch}}'));
@@ -0,0 +1,2 @@
1
+ {{!-- Mobile has no aria-expanded to verify; expanding/collapsing a row or accordion is just a tap. --}}
2
+ await {{> locator}}.click();
@@ -0,0 +1,14 @@
1
+ {{!-- Robust mobile fill: Flutter often ignores a bare setValue (the field never registers, so a
2
+ dependent submit button stays disabled). Explicitly focus, clear, setValue, then if the field
3
+ still reads empty, retype with addValue. Best-effort read-back never fails the step. --}}
4
+ {
5
+ const __fill = await {{> locator}};
6
+ await __fill.click();
7
+ try { await __fill.clearValue(); } catch { /* not clearable */ }
8
+ await __fill.setValue('{{escapeQuotes fillValue}}');
9
+ try {
10
+ const __got = (await __fill.getText()) || (await __fill.getAttribute('text')) || '';
11
+ const __masked = /[•●*]/.test(__got);
12
+ if (!__masked && __got.trim() === '') await __fill.addValue('{{escapeQuotes fillValue}}');
13
+ } catch { /* read-back unsupported — trust setValue */ }
14
+ }
@@ -0,0 +1,12 @@
1
+ {{!-- Rich-text/contenteditable editors don't exist as such on native mobile — treat as a normal field
2
+ with the robust fill (focus, clear, setValue, read-back, retry). --}}
3
+ {
4
+ const __fill = await {{> locator}};
5
+ await __fill.click();
6
+ try { await __fill.clearValue(); } catch { /* not clearable */ }
7
+ await __fill.setValue('{{escapeQuotes fillValue}}');
8
+ try {
9
+ const __got = (await __fill.getText()) || (await __fill.getAttribute('text')) || '';
10
+ if (!/[•●*]/.test(__got) && __got.trim() === '') await __fill.addValue('{{escapeQuotes fillValue}}');
11
+ } catch { /* read-back unsupported — trust setValue */ }
12
+ }
@@ -0,0 +1,6 @@
1
+ {{!-- Hybrid app: switch into the webview context (mobile equivalent of entering an iframe). --}}
2
+ {
3
+ const __ctxs = await driver.getContexts();
4
+ const __web = __ctxs.find(c => /WEBVIEW|CHROMIUM/i.test(typeof c === 'string' ? c : c.id));
5
+ if (__web) await driver.switchContext(typeof __web === 'string' ? __web : __web.id);
6
+ }