@sun-asterisk/sungen 3.2.31 → 3.2.33

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 (363) hide show
  1. package/dist/cli/commands/audit.d.ts +1 -0
  2. package/dist/cli/commands/audit.d.ts.map +1 -1
  3. package/dist/cli/commands/audit.js +13 -2
  4. package/dist/cli/commands/audit.js.map +1 -1
  5. package/dist/cli/commands/capability.d.ts.map +1 -1
  6. package/dist/cli/commands/capability.js +46 -17
  7. package/dist/cli/commands/capability.js.map +1 -1
  8. package/dist/cli/commands/delivery.d.ts.map +1 -1
  9. package/dist/cli/commands/delivery.js +39 -18
  10. package/dist/cli/commands/delivery.js.map +1 -1
  11. package/dist/cli/commands/generate.d.ts.map +1 -1
  12. package/dist/cli/commands/generate.js +14 -7
  13. package/dist/cli/commands/generate.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts.map +1 -1
  15. package/dist/cli/commands/init.js +2 -0
  16. package/dist/cli/commands/init.js.map +1 -1
  17. package/dist/cli/commands/results-merge.d.ts.map +1 -1
  18. package/dist/cli/commands/results-merge.js +2 -12
  19. package/dist/cli/commands/results-merge.js.map +1 -1
  20. package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
  21. package/dist/cli/commands/results-sanitize.js +2 -13
  22. package/dist/cli/commands/results-sanitize.js.map +1 -1
  23. package/dist/cli/commands/setup-mobile-env.d.ts +15 -0
  24. package/dist/cli/commands/setup-mobile-env.d.ts.map +1 -0
  25. package/dist/cli/commands/setup-mobile-env.js +65 -0
  26. package/dist/cli/commands/setup-mobile-env.js.map +1 -0
  27. package/dist/cli/index.js +2 -0
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  30. package/dist/dashboard/snapshot-builder.js +6 -10
  31. package/dist/dashboard/snapshot-builder.js.map +1 -1
  32. package/dist/exporters/feature-parser.d.ts +21 -0
  33. package/dist/exporters/feature-parser.d.ts.map +1 -1
  34. package/dist/exporters/feature-parser.js +32 -1
  35. package/dist/exporters/feature-parser.js.map +1 -1
  36. package/dist/exporters/locale-variants.d.ts +7 -1
  37. package/dist/exporters/locale-variants.d.ts.map +1 -1
  38. package/dist/exporters/locale-variants.js +23 -16
  39. package/dist/exporters/locale-variants.js.map +1 -1
  40. package/dist/exporters/matrix/build.d.ts.map +1 -1
  41. package/dist/exporters/matrix/build.js +13 -7
  42. package/dist/exporters/matrix/build.js.map +1 -1
  43. package/dist/exporters/matrix/carry-manual-results.d.ts +70 -0
  44. package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
  45. package/dist/exporters/matrix/carry-manual-results.js +232 -0
  46. package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
  47. package/dist/exporters/matrix/export.d.ts +2 -0
  48. package/dist/exporters/matrix/export.d.ts.map +1 -1
  49. package/dist/exporters/matrix/export.js +7 -1
  50. package/dist/exporters/matrix/export.js.map +1 -1
  51. package/dist/exporters/matrix/wording.d.ts +7 -0
  52. package/dist/exporters/matrix/wording.d.ts.map +1 -1
  53. package/dist/exporters/matrix/wording.js +27 -1
  54. package/dist/exporters/matrix/wording.js.map +1 -1
  55. package/dist/exporters/xlsx-exporter.d.ts +2 -0
  56. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  57. package/dist/exporters/xlsx-exporter.js +7 -2
  58. package/dist/exporters/xlsx-exporter.js.map +1 -1
  59. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  60. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  61. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  62. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  63. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  64. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  65. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  66. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  67. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  68. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  69. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  70. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  71. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  72. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  73. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
  74. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  75. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  76. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  77. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  78. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  79. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  80. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  81. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  82. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
  83. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  84. package/dist/generators/test-generator/code-generator.d.ts +2 -1
  85. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  86. package/dist/generators/test-generator/code-generator.js +122 -15
  87. package/dist/generators/test-generator/code-generator.js.map +1 -1
  88. package/dist/generators/test-generator/diagnostics.d.ts +70 -4
  89. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  90. package/dist/generators/test-generator/diagnostics.js +69 -3
  91. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  92. package/dist/generators/test-generator/patterns/index.d.ts +13 -3
  93. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  94. package/dist/generators/test-generator/patterns/index.js +16 -10
  95. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  96. package/dist/generators/test-generator/patterns/types.d.ts +15 -0
  97. package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
  98. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
  99. package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
  100. package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
  101. package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
  102. package/dist/generators/test-generator/step-mapper.d.ts +81 -0
  103. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  104. package/dist/generators/test-generator/step-mapper.js +187 -13
  105. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  106. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  107. package/dist/generators/test-generator/template-engine.js +18 -0
  108. package/dist/generators/test-generator/template-engine.js.map +1 -1
  109. package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
  110. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  111. package/dist/generators/test-generator/utils/data-resolver.js +39 -8
  112. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  113. package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
  114. package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
  115. package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
  116. package/dist/generators/test-generator/utils/selector-resolver.d.ts +84 -4
  117. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  118. package/dist/generators/test-generator/utils/selector-resolver.js +229 -82
  119. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  120. package/dist/harness/audit.d.ts.map +1 -1
  121. package/dist/harness/audit.js +35 -8
  122. package/dist/harness/audit.js.map +1 -1
  123. package/dist/harness/capability.d.ts +7 -1
  124. package/dist/harness/capability.d.ts.map +1 -1
  125. package/dist/harness/capability.js +15 -12
  126. package/dist/harness/capability.js.map +1 -1
  127. package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
  128. package/dist/harness/config-file.d.ts +17 -0
  129. package/dist/harness/config-file.d.ts.map +1 -0
  130. package/dist/harness/config-file.js +100 -0
  131. package/dist/harness/config-file.js.map +1 -0
  132. package/dist/harness/depth-lint.d.ts.map +1 -1
  133. package/dist/harness/depth-lint.js +2 -1
  134. package/dist/harness/depth-lint.js.map +1 -1
  135. package/dist/harness/flow-check.d.ts.map +1 -1
  136. package/dist/harness/flow-check.js +6 -2
  137. package/dist/harness/flow-check.js.map +1 -1
  138. package/dist/harness/keyword-match.d.ts +59 -0
  139. package/dist/harness/keyword-match.d.ts.map +1 -0
  140. package/dist/harness/keyword-match.js +86 -0
  141. package/dist/harness/keyword-match.js.map +1 -0
  142. package/dist/harness/next-step.d.ts.map +1 -1
  143. package/dist/harness/next-step.js +2 -1
  144. package/dist/harness/next-step.js.map +1 -1
  145. package/dist/harness/parse.d.ts.map +1 -1
  146. package/dist/harness/parse.js +42 -7
  147. package/dist/harness/parse.js.map +1 -1
  148. package/dist/harness/perf.d.ts +7 -1
  149. package/dist/harness/perf.d.ts.map +1 -1
  150. package/dist/harness/perf.js +13 -10
  151. package/dist/harness/perf.js.map +1 -1
  152. package/dist/harness/quality-gates.d.ts +9 -2
  153. package/dist/harness/quality-gates.d.ts.map +1 -1
  154. package/dist/harness/quality-gates.js +16 -4
  155. package/dist/harness/quality-gates.js.map +1 -1
  156. package/dist/harness/query-catalog.js +0 -0
  157. package/dist/harness/query-catalog.js.map +1 -1
  158. package/dist/harness/read-text.d.ts +6 -0
  159. package/dist/harness/read-text.d.ts.map +1 -1
  160. package/dist/harness/read-text.js +7 -1
  161. package/dist/harness/read-text.js.map +1 -1
  162. package/dist/harness/secret-scan.d.ts +27 -1
  163. package/dist/harness/secret-scan.d.ts.map +1 -1
  164. package/dist/harness/secret-scan.js +57 -4
  165. package/dist/harness/secret-scan.js.map +1 -1
  166. package/dist/harness/sensors.d.ts +12 -0
  167. package/dist/harness/sensors.d.ts.map +1 -1
  168. package/dist/harness/sensors.js +41 -18
  169. package/dist/harness/sensors.js.map +1 -1
  170. package/dist/harness/spec-branches.d.ts +7 -20
  171. package/dist/harness/spec-branches.d.ts.map +1 -1
  172. package/dist/harness/spec-branches.js +85 -13
  173. package/dist/harness/spec-branches.js.map +1 -1
  174. package/dist/harness/spec-coverage.d.ts +16 -0
  175. package/dist/harness/spec-coverage.d.ts.map +1 -1
  176. package/dist/harness/spec-coverage.js +131 -12
  177. package/dist/harness/spec-coverage.js.map +1 -1
  178. package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
  179. package/dist/harness/viewpoint-ledger.js +2 -1
  180. package/dist/harness/viewpoint-ledger.js.map +1 -1
  181. package/dist/harness/write-atomic.d.ts +2 -0
  182. package/dist/harness/write-atomic.d.ts.map +1 -0
  183. package/dist/harness/write-atomic.js +62 -0
  184. package/dist/harness/write-atomic.js.map +1 -0
  185. package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
  186. package/dist/orchestrator/ai-skills/writer.js +15 -7
  187. package/dist/orchestrator/ai-skills/writer.js.map +1 -1
  188. package/dist/orchestrator/appium-environment-setup.d.ts +24 -0
  189. package/dist/orchestrator/appium-environment-setup.d.ts.map +1 -0
  190. package/dist/orchestrator/appium-environment-setup.js +157 -0
  191. package/dist/orchestrator/appium-environment-setup.js.map +1 -0
  192. package/dist/orchestrator/assets-drift.d.ts.map +1 -1
  193. package/dist/orchestrator/assets-drift.js +11 -6
  194. package/dist/orchestrator/assets-drift.js.map +1 -1
  195. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +2 -0
  196. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
  197. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
  198. package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
  199. package/dist/orchestrator/project-deps-sync.js +22 -1
  200. package/dist/orchestrator/project-deps-sync.js.map +1 -1
  201. package/dist/orchestrator/project-initializer.d.ts +9 -0
  202. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  203. package/dist/orchestrator/project-initializer.js +63 -7
  204. package/dist/orchestrator/project-initializer.js.map +1 -1
  205. package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
  206. package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
  207. package/dist/orchestrator/security-overrides-catalog.js +63 -0
  208. package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
  209. package/dist/orchestrator/security-overrides.d.ts +51 -0
  210. package/dist/orchestrator/security-overrides.d.ts.map +1 -0
  211. package/dist/orchestrator/security-overrides.js +165 -0
  212. package/dist/orchestrator/security-overrides.js.map +1 -0
  213. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  214. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
  215. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
  216. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  217. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  218. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  219. package/dist/orchestrator/templates/appium-environment/README.md +83 -0
  220. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  221. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  222. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  223. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  224. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  225. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  226. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  227. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  228. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  229. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  230. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  231. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  232. package/dist/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  233. package/dist/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  234. package/dist/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  235. package/dist/orchestrator/templates/appium-environment/setup.sh +88 -0
  236. package/dist/orchestrator/templates/specs-checked-state.d.ts +1 -1
  237. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -1
  238. package/dist/orchestrator/templates/specs-checked-state.js +26 -3
  239. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -1
  240. package/dist/orchestrator/templates/specs-checked-state.ts +31 -6
  241. package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
  242. package/dist/orchestrator/templates/specs-mock.d.ts +2 -1
  243. package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -1
  244. package/dist/orchestrator/templates/specs-mock.js +10 -3
  245. package/dist/orchestrator/templates/specs-mock.js.map +1 -1
  246. package/dist/orchestrator/templates/specs-mock.ts +12 -4
  247. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  248. package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
  249. package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
  250. package/dist/orchestrator/templates/specs-url-assert.js +14 -2
  251. package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
  252. package/dist/orchestrator/templates/specs-url-assert.ts +24 -2
  253. package/dist/orchestrator/templates/wdio.conf.ts +24 -1
  254. package/package.json +3 -3
  255. package/src/cli/commands/audit.ts +12 -3
  256. package/src/cli/commands/capability.ts +63 -19
  257. package/src/cli/commands/delivery.ts +38 -16
  258. package/src/cli/commands/generate.ts +15 -7
  259. package/src/cli/commands/init.ts +3 -0
  260. package/src/cli/commands/results-merge.ts +2 -8
  261. package/src/cli/commands/results-sanitize.ts +2 -9
  262. package/src/cli/commands/setup-mobile-env.ts +64 -0
  263. package/src/cli/index.ts +2 -0
  264. package/src/dashboard/snapshot-builder.ts +6 -8
  265. package/src/exporters/feature-parser.ts +32 -2
  266. package/src/exporters/locale-variants.ts +23 -17
  267. package/src/exporters/matrix/build.ts +14 -8
  268. package/src/exporters/matrix/carry-manual-results.ts +229 -0
  269. package/src/exporters/matrix/export.ts +10 -4
  270. package/src/exporters/matrix/wording.ts +26 -1
  271. package/src/exporters/xlsx-exporter.ts +7 -2
  272. package/src/generators/test-generator/adapters/adapter-interface.ts +2 -2
  273. package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
  274. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  275. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  276. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  277. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  278. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  279. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  280. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  281. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
  282. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  283. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  284. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  285. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
  286. package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  287. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
  288. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  289. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
  290. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  291. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
  292. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  293. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  294. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  295. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  296. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
  297. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  298. package/src/generators/test-generator/code-generator.ts +119 -19
  299. package/src/generators/test-generator/diagnostics.ts +70 -4
  300. package/src/generators/test-generator/patterns/index.ts +16 -9
  301. package/src/generators/test-generator/patterns/types.ts +15 -0
  302. package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
  303. package/src/generators/test-generator/step-mapper.ts +196 -13
  304. package/src/generators/test-generator/template-engine.ts +18 -0
  305. package/src/generators/test-generator/utils/data-resolver.ts +41 -12
  306. package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
  307. package/src/generators/test-generator/utils/selector-resolver.ts +256 -85
  308. package/src/harness/audit.ts +41 -12
  309. package/src/harness/capability.ts +23 -12
  310. package/src/harness/catalog/universal-viewpoints.yaml +5 -5
  311. package/src/harness/config-file.ts +66 -0
  312. package/src/harness/depth-lint.ts +2 -1
  313. package/src/harness/flow-check.ts +6 -2
  314. package/src/harness/keyword-match.ts +85 -0
  315. package/src/harness/next-step.ts +2 -1
  316. package/src/harness/parse.ts +42 -7
  317. package/src/harness/perf.ts +15 -7
  318. package/src/harness/quality-gates.ts +16 -4
  319. package/src/harness/query-catalog.ts +0 -0
  320. package/src/harness/read-text.ts +7 -1
  321. package/src/harness/secret-scan.ts +60 -4
  322. package/src/harness/sensors.ts +50 -19
  323. package/src/harness/spec-branches.ts +72 -14
  324. package/src/harness/spec-coverage.ts +133 -13
  325. package/src/harness/viewpoint-ledger.ts +2 -1
  326. package/src/harness/write-atomic.ts +22 -0
  327. package/src/orchestrator/ai-skills/writer.ts +18 -3
  328. package/src/orchestrator/appium-environment-setup.ts +127 -0
  329. package/src/orchestrator/assets-drift.ts +10 -5
  330. package/src/orchestrator/mobile-runtime-scaffolder.ts +2 -0
  331. package/src/orchestrator/project-deps-sync.ts +23 -1
  332. package/src/orchestrator/project-initializer.ts +69 -7
  333. package/src/orchestrator/security-overrides-catalog.ts +69 -0
  334. package/src/orchestrator/security-overrides.ts +149 -0
  335. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
  336. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
  337. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
  338. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
  339. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  340. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  341. package/src/orchestrator/templates/appium-environment/README.md +83 -0
  342. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  343. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  344. package/src/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  345. package/src/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  346. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  347. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  348. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  349. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  350. package/src/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  351. package/src/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  352. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  353. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  354. package/src/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  355. package/src/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  356. package/src/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  357. package/src/orchestrator/templates/appium-environment/setup.sh +88 -0
  358. package/src/orchestrator/templates/specs-checked-state.ts +31 -6
  359. package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
  360. package/src/orchestrator/templates/specs-mock.ts +12 -4
  361. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
  362. package/src/orchestrator/templates/specs-url-assert.ts +24 -2
  363. package/src/orchestrator/templates/wdio.conf.ts +24 -1
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ # install-android.sh — macOS: JDK ≥ 17, Android SDK root, platform-tools + emulator, shell profile block.
3
+ # Headless via Homebrew (decision D2): `openjdk@21` + the `android-commandlinetools` cask + sdkmanager.
4
+ # Android Studio is optional (only for an on-screen emulator) and is never installed here.
5
+ # Sourced by setup.sh (shares the runner + counters); runnable standalone for debugging.
6
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] || . "$(cd "$(dirname "${BASH_SOURCE[0]}")/../common" && pwd)/lib.sh"
7
+
8
+ JDK_MIN=17
9
+ BREW_SDK_ROOT=$( { have brew && printf '%s/share/android-commandlinetools' "$(brew --prefix)"; } 2>/dev/null || true)
10
+
11
+ # ── JDK ────────────────────────────────────────────────────────────────────────────────────────
12
+ # Any working JDK ≥ 17 is accepted (Temurin, Corretto, Oracle, asdf…) — we never replace a good one.
13
+ # resolve_* run in THIS shell (not a subshell) so their exports survive; *_check run captured.
14
+ resolve_java_home() {
15
+ local major home
16
+ if [ -n "${JAVA_HOME:-}" ] && [ -x "$JAVA_HOME/bin/java" ]; then
17
+ major=$(java_major_of "$JAVA_HOME/bin/java"); [ -n "$major" ] && [ "$major" -ge "$JDK_MIN" ] && return 0
18
+ fi
19
+ if have java; then
20
+ major=$(java_major_of java)
21
+ if [ -n "$major" ] && [ "$major" -ge "$JDK_MIN" ]; then
22
+ home=$(java -XshowSettings:properties -version 2>&1 | awk -F' = ' '/java\.home/{print $2}')
23
+ [ -d "$home" ] && { export JAVA_HOME=$home; return 0; }
24
+ fi
25
+ fi
26
+ # Homebrew openjdk@21 present but not on PATH (brew keeps it keg-only).
27
+ if have brew && [ -x "$(brew --prefix openjdk@21 2>/dev/null)/bin/java" ]; then export JAVA_HOME; JAVA_HOME=$(brew --prefix openjdk@21); return 0; fi
28
+ return 1
29
+ }
30
+ jdk_check() {
31
+ local major
32
+ [ -n "${JAVA_HOME:-}" ] && [ -x "$JAVA_HOME/bin/java" ] || return 1
33
+ major=$(java_major_of "$JAVA_HOME/bin/java"); [ -n "$major" ] && [ "$major" -ge "$JDK_MIN" ] || return 1
34
+ printf 'java %s · JAVA_HOME=%s' "$major" "$JAVA_HOME"
35
+ }
36
+ jdk_install() { brew install openjdk@21 && { export JAVA_HOME; JAVA_HOME=$(brew --prefix openjdk@21); }; }
37
+ jdk_verify() { jdk_check || { printf 'no JDK ≥ %s — brew install openjdk@21 && export JAVA_HOME=$(brew --prefix openjdk@21)' "$JDK_MIN"; return 1; }; }
38
+ resolve_java_home || true
39
+ run_step "JDK ≥ $JDK_MIN" jdk_check jdk_install jdk_verify
40
+
41
+ # ── Android SDK root ───────────────────────────────────────────────────────────────────────────
42
+ # find_sdkmanager <sdk root> → path of sdkmanager (cmdline-tools/latest, any cmdline-tools/*, legacy tools/bin)
43
+ find_sdkmanager() {
44
+ local root=$1 c
45
+ [ -n "$root" ] || return 1
46
+ for c in "$root/cmdline-tools/latest/bin/sdkmanager" "$root"/cmdline-tools/*/bin/sdkmanager "$root/tools/bin/sdkmanager"; do
47
+ [ -x "$c" ] && { printf '%s' "$c"; return 0; }
48
+ done
49
+ return 1
50
+ }
51
+ sdk_root_usable() { [ -n "$1" ] && [ -d "$1" ] && { [ -x "$1/platform-tools/adb" ] || find_sdkmanager "$1" >/dev/null; }; }
52
+ # Precedence: what the user already exports → Android Studio's default → the Homebrew cask root.
53
+ resolve_android_home() {
54
+ local c
55
+ for c in "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}" "$HOME/Library/Android/sdk" "$BREW_SDK_ROOT"; do
56
+ if sdk_root_usable "$c"; then export ANDROID_HOME=$c ANDROID_SDK_ROOT=$c; return 0; fi
57
+ done
58
+ return 1
59
+ }
60
+ sdk_root_check() { sdk_root_usable "${ANDROID_HOME:-}" || return 1; printf 'ANDROID_HOME=%s' "$ANDROID_HOME"; }
61
+ sdk_root_install() { brew install --cask android-commandlinetools && export ANDROID_HOME=$BREW_SDK_ROOT ANDROID_SDK_ROOT=$BREW_SDK_ROOT; }
62
+ sdk_root_verify() {
63
+ find_sdkmanager "${ANDROID_HOME:-}" >/dev/null || { printf 'sdkmanager not found under %s — brew install --cask android-commandlinetools' "${ANDROID_HOME:-<unset>}"; return 1; }
64
+ printf 'ANDROID_HOME=%s' "$ANDROID_HOME"
65
+ }
66
+ resolve_android_home || true
67
+ run_step "Android SDK root (cmdline-tools)" sdk_root_check sdk_root_install sdk_root_verify
68
+
69
+ # ── platform-tools (adb) + emulator ────────────────────────────────────────────────────────────
70
+ sdk_pkgs_check() {
71
+ local v
72
+ [ -x "${ANDROID_HOME:-}/platform-tools/adb" ] && [ -x "$ANDROID_HOME/emulator/emulator" ] || return 1
73
+ v=$("$ANDROID_HOME/platform-tools/adb" version 2>/dev/null) || v='adb'
74
+ printf 'platform-tools + emulator · %s' "$(first_line "$v")"
75
+ }
76
+ sdk_pkgs_install() {
77
+ local sm
78
+ sm=$(find_sdkmanager "${ANDROID_HOME:-}") || { printf 'no sdkmanager — the "Android SDK root" step must succeed first\n' >&2; return 1; }
79
+ # sdkmanager needs a JDK; --sdk_root is mandatory for the cask layout. Licenses are accepted
80
+ # non-interactively (they are Google's SDK terms — the same prompt Android Studio shows).
81
+ yes 2>/dev/null | JAVA_HOME=${JAVA_HOME:-} "$sm" --sdk_root="$ANDROID_HOME" --licenses >/dev/null 2>&1 || true
82
+ JAVA_HOME=${JAVA_HOME:-} "$sm" --sdk_root="$ANDROID_HOME" "platform-tools" "emulator"
83
+ }
84
+ sdk_pkgs_verify() { sdk_pkgs_check || { printf 'adb / emulator still missing — sdkmanager --sdk_root="%s" platform-tools emulator' "${ANDROID_HOME:-}"; return 1; }; }
85
+ run_step "Android platform-tools (adb) + emulator" sdk_pkgs_check sdk_pkgs_install sdk_pkgs_verify
86
+
87
+ # Make the tools reachable for the rest of THIS run (the profile block covers new terminals).
88
+ if [ -n "${ANDROID_HOME:-}" ]; then
89
+ export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
90
+ fi
91
+
92
+ # ── shell profile block (decision D3) ─────────────────────────────────────────────────────────
93
+ PROFILE_TITLE="Shell profile (ANDROID_HOME, JAVA_HOME, PATH)"
94
+ if [ -z "${ANDROID_HOME:-}" ] || [ -z "${JAVA_HOME:-}" ]; then
95
+ log_warn "$PROFILE_TITLE" "skipped — fix the JDK / SDK steps above first, then re-run"
96
+ elif [ "$SUNGEN_ENV_INITIAL_ANDROID_HOME" = "$ANDROID_HOME" ] && [ "$SUNGEN_ENV_INITIAL_JAVA_HOME" = "$JAVA_HOME" ] \
97
+ && [ -n "$SUNGEN_ENV_INITIAL_ADB" ] && grep -qs 'ANDROID_HOME' "$(profile_file)"; then
98
+ # The user's own profile already exports the right values (and adb was on PATH before we touched it) —
99
+ # nothing to add. A value exported only in this terminal session does not count: the profile must name it.
100
+ log_skip "$PROFILE_TITLE" "already exported by your shell profile (ANDROID_HOME, JAVA_HOME, adb on PATH) — no block written"
101
+ else
102
+ profile_step "$PROFILE_TITLE" "$(printf 'export ANDROID_HOME="%s"\nexport ANDROID_SDK_ROOT="$ANDROID_HOME"\nexport JAVA_HOME="%s"\nexport PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"' "$ANDROID_HOME" "$JAVA_HOME")"
103
+ fi
104
+
105
+ # ── Android Studio — optional, GUI, never installed here ───────────────────────────────────────
106
+ if [ -d "/Applications/Android Studio.app" ]; then
107
+ log_skip "Android Studio (optional)" "/Applications/Android Studio.app"
108
+ else
109
+ log_skip "Android Studio (optional)" "not installed — only needed for the AVD manager / an on-screen emulator: https://developer.android.com/studio"
110
+ fi
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ # install-ios.sh — macOS: Xcode Command Line Tools, Xcode.app (checked + guided), license, applesimutils.
3
+ # Xcode.app comes from the App Store and is never installed here (decision D2); xcuitest needs it
4
+ # because WebDriverAgent is built by Xcode at run time.
5
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] || . "$(cd "$(dirname "${BASH_SOURCE[0]}")/../common" && pwd)/lib.sh"
6
+
7
+ # ── Command Line Tools ─────────────────────────────────────────────────────────────────────────
8
+ clt_check() { xcode-select -p 2>/dev/null; }
9
+ # `xcode-select --install` opens Apple's dialog and returns immediately — the install finishes
10
+ # outside this script, so report "needs a manual step" (exit 2) rather than success or failure.
11
+ clt_install() { xcode-select --install >/dev/null 2>&1; return 2; }
12
+ clt_verify() { clt_check || { printf 'finish the "Install Command Line Developer Tools" dialog, then re-run setup.sh'; return 1; }; }
13
+ run_step "Xcode Command Line Tools" clt_check clt_install clt_verify
14
+
15
+ # ── Xcode.app (guided) ─────────────────────────────────────────────────────────────────────────
16
+ # Only a full Xcode answers `xcodebuild -version`; a CLT-only machine errors out here.
17
+ xcode_check() { local v; v=$(xcodebuild -version 2>/dev/null) || return 1; first_line "$v"; }
18
+ run_guided "Xcode.app" xcode_check "not installed — required for iOS (Xcode builds WebDriverAgent). Install Xcode from the App Store, open it once, then re-run setup.sh"
19
+
20
+ # ── license — only meaningful once Xcode.app is there ──────────────────────────────────────────
21
+ if xcode_check >/dev/null 2>&1; then
22
+ license_check() { xcodebuild -license check >/dev/null 2>&1 && printf 'accepted'; }
23
+ license_install() { sudo xcodebuild -license accept; }
24
+ license_verify() { license_check || { printf 'run: sudo xcodebuild -license accept'; return 1; }; }
25
+ run_step "Xcode license" license_check license_install license_verify
26
+ fi
27
+
28
+ # ── applesimutils — Simulator permission steps (`grant permission …`) ──────────────────────────
29
+ asu_check() { have applesimutils && printf 'applesimutils at %s' "$(command -v applesimutils)"; }
30
+ asu_install() { brew tap wix/brew && brew install applesimutils; }
31
+ asu_verify() { asu_check || { printf 'brew tap wix/brew && brew install applesimutils'; return 1; }; }
32
+ run_step "applesimutils (Simulator permission steps)" asu_check asu_install asu_verify
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ # verify-android.sh — macOS: is the Android side usable right now? (adb, env vars, connected devices)
3
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] || . "$(cd "$(dirname "${BASH_SOURCE[0]}")/../common" && pwd)/lib.sh"
4
+
5
+ # Prefer the adb under ANDROID_HOME (this run may have just installed it) over a stale one on PATH.
6
+ android_adb() {
7
+ if [ -x "${ANDROID_HOME:-}/platform-tools/adb" ]; then printf '%s/platform-tools/adb' "$ANDROID_HOME"
8
+ else command -v adb 2>/dev/null; fi
9
+ }
10
+
11
+ adb_check() {
12
+ local adb v
13
+ adb=$(android_adb) && [ -x "$adb" ] || return 1
14
+ v=$("$adb" version 2>/dev/null) || return 1
15
+ printf '%s' "$(first_line "$v")"
16
+ }
17
+ run_check "adb" adb_check "adb not found — re-run without --skip-android, or open a NEW terminal if the profile block was just written"
18
+
19
+ env_check() {
20
+ [ -n "${ANDROID_HOME:-}" ] && [ -n "${JAVA_HOME:-}" ] || return 1
21
+ printf 'ANDROID_HOME=%s · JAVA_HOME=%s' "$ANDROID_HOME" "$JAVA_HOME"
22
+ }
23
+ run_check "ANDROID_HOME / JAVA_HOME" env_check "not set in this shell — open a NEW terminal (profile block) or export them"
24
+
25
+ devices_check() {
26
+ local adb n
27
+ adb=$(android_adb) && [ -x "$adb" ] || return 1
28
+ n=$("$adb" devices 2>/dev/null | awk 'NR>1 && $2=="device"{n++} END{print n+0}')
29
+ if [ "$n" -gt 0 ]; then printf '%s device(s) connected' "$n"
30
+ else printf 'no device connected — plug in a phone (USB debugging on) or start an emulator before /sungen:run-test'; fi
31
+ }
32
+ run_check "Android devices" devices_check "adb is not usable yet" warn
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ # verify-ios.sh — macOS: is the iOS side usable right now? (Xcode selected, Simulator runtimes visible)
3
+ [ -n "${SUNGEN_ENV_LIB_LOADED:-}" ] || . "$(cd "$(dirname "${BASH_SOURCE[0]}")/../common" && pwd)/lib.sh"
4
+
5
+ xcode_version_check() { local v; v=$(xcodebuild -version 2>/dev/null) || return 1; printf '%s · %s' "$(first_line "$v")" "$(xcode-select -p 2>/dev/null)"; }
6
+ run_check "Xcode" xcode_version_check "Xcode.app not installed or not selected — App Store, then: sudo xcode-select -s /Applications/Xcode.app" warn
7
+
8
+ simulators_check() {
9
+ local json n
10
+ json=$(xcrun simctl list devices available -j 2>/dev/null) || return 1
11
+ n=$(printf '%s' "$json" | grep -c '"udid"' || true)
12
+ if [ "${n:-0}" -gt 0 ]; then printf '%s simulator(s) available (xcrun simctl list devices available)' "$n"
13
+ else printf 'no Simulator runtime — Xcode → Settings → Components, add an iOS Simulator runtime'; return 1; fi
14
+ }
15
+ run_check "iOS Simulators" simulators_check "xcrun simctl is not usable — needs Xcode.app (real-iPhone runs need it too: WebDriverAgent signing)" warn
@@ -0,0 +1,153 @@
1
+ # install-android.ps1 - Windows: JDK >= 17, Android SDK root, platform-tools + emulator, User environment.
2
+ # Headless via winget (decision D2): Microsoft OpenJDK 21 + Google's command-line tools zip + sdkmanager.
3
+ # Android Studio is optional (only for an on-screen emulator) and is never installed here.
4
+ # Dot-sourced by setup.ps1 (shares the runner + counters); runnable standalone for debugging.
5
+ if (-not $script:SungenEnvLibLoaded) { . (Join-Path $PSScriptRoot '..\common\lib.ps1') }
6
+
7
+ $JdkMin = 17
8
+ # Google rotates the command-line tools zip name; bump this ONE value when the download 404s.
9
+ # Current builds: https://developer.android.com/studio#command-line-tools-only
10
+ $CmdlineToolsBuild = '13114758'
11
+ $DefaultSdkRoot = Join-Path $env:LOCALAPPDATA 'Android\Sdk'
12
+
13
+ # --- JDK ------------------------------------------------------------------------------------------
14
+ # Any working JDK >= 17 is accepted (Microsoft, Temurin, Oracle, Corretto...) - we never replace a good one.
15
+ function Get-JavaMajor([string]$JavaExe) {
16
+ try { $out = (& $JavaExe -version 2>&1) | Out-String; if ($out -match '"(\d+)(\.\d+)*') { return [int]$Matches[1] } } catch {}
17
+ return 0
18
+ }
19
+ function Get-JavaHomeFromExe([string]$JavaExe) {
20
+ $out = (& $JavaExe -XshowSettings:properties -version 2>&1) | Out-String
21
+ if ($out -match 'java\.home = (.+)') { return $Matches[1].Trim() }
22
+ return $null
23
+ }
24
+ function Test-JdkHome([string]$JdkHome) {
25
+ if (-not $JdkHome) { return $false }
26
+ $exe = Join-Path $JdkHome 'bin\java.exe'
27
+ return ((Test-Path $exe) -and ((Get-JavaMajor $exe) -ge $JdkMin))
28
+ }
29
+ # Resolve-JavaHome - sets $env:JAVA_HOME when a usable JDK is found (env -> PATH -> vendor default folders).
30
+ function Resolve-JavaHome {
31
+ if (Test-JdkHome $env:JAVA_HOME) { return $true }
32
+ $java = Get-Command java -ErrorAction SilentlyContinue
33
+ if ($java -and ((Get-JavaMajor $java.Source) -ge $JdkMin)) {
34
+ $home = Get-JavaHomeFromExe $java.Source
35
+ if ($home -and (Test-Path $home)) { $env:JAVA_HOME = $home; return $true }
36
+ }
37
+ foreach ($root in @((Join-Path $env:ProgramFiles 'Microsoft'), (Join-Path $env:ProgramFiles 'Eclipse Adoptium'), (Join-Path $env:ProgramFiles 'Java'))) {
38
+ if (-not (Test-Path $root)) { continue }
39
+ $cand = Get-ChildItem $root -Directory -Filter 'jdk-*' -ErrorAction SilentlyContinue | Sort-Object Name -Descending |
40
+ Where-Object { Test-JdkHome $_.FullName } | Select-Object -First 1
41
+ if ($cand) { $env:JAVA_HOME = $cand.FullName; return $true }
42
+ }
43
+ return $false
44
+ }
45
+ function Get-JdkDetail { if (Resolve-JavaHome) { "java $(Get-JavaMajor (Join-Path $env:JAVA_HOME 'bin\java.exe')), JAVA_HOME=$env:JAVA_HOME" } }
46
+
47
+ Invoke-Step -Title "JDK >= $JdkMin" -Check { Get-JdkDetail } -Install {
48
+ Invoke-Native winget @('install', '--id', 'Microsoft.OpenJDK.21', '-e', '--silent', '--accept-source-agreements', '--accept-package-agreements')
49
+ Update-SessionPath
50
+ } -Verify { Get-JdkDetail } -Hint 'winget install --id Microsoft.OpenJDK.21 -e (then open a NEW terminal and re-run)'
51
+
52
+ # --- Android SDK root -----------------------------------------------------------------------------
53
+ function Find-SdkManager([string]$Root) {
54
+ if (-not $Root) { return $null }
55
+ foreach ($c in @((Join-Path $Root 'cmdline-tools\latest\bin\sdkmanager.bat'), (Join-Path $Root 'tools\bin\sdkmanager.bat'))) { if (Test-Path $c) { return $c } }
56
+ $any = Get-ChildItem (Join-Path $Root 'cmdline-tools') -Directory -ErrorAction SilentlyContinue |
57
+ ForEach-Object { Join-Path $_.FullName 'bin\sdkmanager.bat' } | Where-Object { Test-Path $_ } | Select-Object -First 1
58
+ return $any
59
+ }
60
+ function Test-SdkRoot([string]$Root) {
61
+ if (-not $Root -or -not (Test-Path $Root)) { return $false }
62
+ return ((Test-Path (Join-Path $Root 'platform-tools\adb.exe')) -or [bool](Find-SdkManager $Root))
63
+ }
64
+ # Precedence: what the user already set -> Android Studio's default (%LOCALAPPDATA%\Android\Sdk).
65
+ function Resolve-AndroidHome {
66
+ foreach ($c in @($env:ANDROID_HOME, $env:ANDROID_SDK_ROOT, $DefaultSdkRoot)) {
67
+ if (Test-SdkRoot $c) { $env:ANDROID_HOME = $c; $env:ANDROID_SDK_ROOT = $c; return $true }
68
+ }
69
+ return $false
70
+ }
71
+ function Install-CmdlineTools {
72
+ # Cmdlet errors (download 404 when Google rotates the zip, unpack failures) must become terminating so
73
+ # Invoke-Step's try/catch reports the real cause instead of only the generic hint.
74
+ $ErrorActionPreference = 'Stop'
75
+ $root = $DefaultSdkRoot
76
+ $url = "https://dl.google.com/android/repository/commandlinetools-win-${CmdlineToolsBuild}_latest.zip"
77
+ $zip = Join-Path $env:TEMP "commandlinetools-win-$CmdlineToolsBuild.zip"
78
+ $staging = Join-Path $env:TEMP "sungen-cmdline-tools-$CmdlineToolsBuild"
79
+ New-Item -ItemType Directory -Force -Path (Join-Path $root 'cmdline-tools') | Out-Null
80
+ Write-Host " downloading $url" -ForegroundColor DarkGray
81
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
82
+ Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
83
+ if (Test-Path $staging) { Remove-Item $staging -Recurse -Force }
84
+ Expand-Archive -Path $zip -DestinationPath $staging -Force
85
+ # The zip unpacks as cmdline-tools\ but sdkmanager only works from cmdline-tools\latest\.
86
+ $latest = Join-Path $root 'cmdline-tools\latest'
87
+ if (Test-Path $latest) { Remove-Item $latest -Recurse -Force }
88
+ Move-Item (Join-Path $staging 'cmdline-tools') $latest
89
+ Remove-Item $zip -Force -ErrorAction SilentlyContinue
90
+ Remove-Item $staging -Recurse -Force -ErrorAction SilentlyContinue
91
+ $env:ANDROID_HOME = $root; $env:ANDROID_SDK_ROOT = $root
92
+ }
93
+ [void](Resolve-AndroidHome)
94
+ Invoke-Step -Title 'Android SDK root (cmdline-tools)' -Check {
95
+ if (Test-SdkRoot $env:ANDROID_HOME) { "ANDROID_HOME=$env:ANDROID_HOME" }
96
+ } -Install { Install-CmdlineTools } -Verify {
97
+ if (Find-SdkManager $env:ANDROID_HOME) { "ANDROID_HOME=$env:ANDROID_HOME" }
98
+ } -Hint "download the command-line tools zip from https://developer.android.com/studio#command-line-tools-only and unpack it to $DefaultSdkRoot\cmdline-tools\latest"
99
+
100
+ # --- platform-tools (adb) + emulator --------------------------------------------------------------
101
+ function Get-SdkPackagesDetail {
102
+ if (-not $env:ANDROID_HOME) { return $null }
103
+ $adb = Join-Path $env:ANDROID_HOME 'platform-tools\adb.exe'; $emu = Join-Path $env:ANDROID_HOME 'emulator\emulator.exe'
104
+ if ((Test-Path $adb) -and (Test-Path $emu)) { $v = (& $adb version) 2>$null | Select-Object -First 1; "platform-tools + emulator, $v" }
105
+ }
106
+ Invoke-Step -Title 'Android platform-tools (adb) + emulator' -Check { Get-SdkPackagesDetail } -Install {
107
+ $sm = Find-SdkManager $env:ANDROID_HOME
108
+ if (-not $sm) { throw 'no sdkmanager - the "Android SDK root" step must succeed first' }
109
+ # sdkmanager needs JAVA_HOME (set above). Licenses are Google's SDK terms - the same prompt Android
110
+ # Studio shows - accepted non-interactively here.
111
+ $yes = ('y' + "`n") * 40
112
+ $yes | & $sm "--sdk_root=$env:ANDROID_HOME" --licenses 2>&1 | Out-Null
113
+ Invoke-Native $sm @("--sdk_root=$env:ANDROID_HOME", 'platform-tools', 'emulator')
114
+ } -Verify { Get-SdkPackagesDetail } -Hint "sdkmanager --sdk_root=$env:ANDROID_HOME platform-tools emulator"
115
+
116
+ # Make the tools reachable for the rest of THIS run (the User environment covers new consoles).
117
+ if ($env:ANDROID_HOME) {
118
+ foreach ($d in @('platform-tools', 'emulator', 'cmdline-tools\latest\bin')) {
119
+ $p = Join-Path $env:ANDROID_HOME $d
120
+ if (-not (($env:Path -split ';') -contains $p)) { $env:Path = "$env:Path;$p" }
121
+ }
122
+ }
123
+
124
+ # --- User environment (decision D3) ---------------------------------------------------------------
125
+ $EnvTitle = 'User environment (ANDROID_HOME, ANDROID_SDK_ROOT, JAVA_HOME, Path)'
126
+ $PathDirs = @('platform-tools', 'emulator', 'cmdline-tools\latest\bin')
127
+ if (-not $env:ANDROID_HOME -or -not $env:JAVA_HOME) {
128
+ Write-Warn $EnvTitle 'skipped - fix the JDK / SDK steps above first, then re-run'
129
+ } elseif ($script:SungenEnvNoProfile) {
130
+ Write-Skip $EnvTitle '-NoProfile'
131
+ } elseif ($script:SungenEnvCheckOnly) {
132
+ $missing = @()
133
+ foreach ($pair in @(@('ANDROID_HOME', $env:ANDROID_HOME), @('ANDROID_SDK_ROOT', $env:ANDROID_HOME), @('JAVA_HOME', $env:JAVA_HOME))) {
134
+ if ([Environment]::GetEnvironmentVariable($pair[0], 'User') -ne $pair[1]) { $missing += $pair[0] }
135
+ }
136
+ $userPath = @(([Environment]::GetEnvironmentVariable('Path', 'User')) -split ';')
137
+ foreach ($d in $PathDirs) { if (-not ($userPath -contains (Join-Path $env:ANDROID_HOME $d))) { $missing += "Path+=$d" } }
138
+ if ($missing.Count -gt 0) { Write-Fail $EnvTitle "not set for your user: $($missing -join ', ') - run without -Check to write them" }
139
+ else { Write-Skip $EnvTitle 'already set for your user' }
140
+ } else {
141
+ $changed = @()
142
+ if (Set-UserEnv 'ANDROID_HOME' $env:ANDROID_HOME) { $changed += 'ANDROID_HOME' }
143
+ if (Set-UserEnv 'ANDROID_SDK_ROOT' $env:ANDROID_HOME) { $changed += 'ANDROID_SDK_ROOT' }
144
+ if (Set-UserEnv 'JAVA_HOME' $env:JAVA_HOME) { $changed += 'JAVA_HOME' }
145
+ foreach ($d in $PathDirs) { if (Add-UserPath (Join-Path $env:ANDROID_HOME $d)) { $changed += "Path+=$d" } }
146
+ if ($changed.Count -gt 0) { Write-Ok $EnvTitle "set for your user: $($changed -join ', ') - open a NEW terminal to apply" }
147
+ else { Write-Skip $EnvTitle 'already set for your user' }
148
+ }
149
+
150
+ # --- Android Studio - optional, GUI, never installed here ------------------------------------------
151
+ $studio = @((Join-Path $env:LOCALAPPDATA 'Programs\Android Studio'), (Join-Path $env:ProgramFiles 'Android\Android Studio')) | Where-Object { Test-Path $_ } | Select-Object -First 1
152
+ if ($studio) { Write-Skip 'Android Studio (optional)' $studio }
153
+ else { Write-Skip 'Android Studio (optional)' 'not installed - only needed for the AVD manager / an on-screen emulator: https://developer.android.com/studio' }
@@ -0,0 +1,26 @@
1
+ # verify-android.ps1 - Windows: is the Android side usable right now? (adb, env vars, connected devices)
2
+ if (-not $script:SungenEnvLibLoaded) { . (Join-Path $PSScriptRoot '..\common\lib.ps1') }
3
+
4
+ # Prefer the adb under ANDROID_HOME (this run may have just installed it) over a stale one on PATH.
5
+ function Get-AndroidAdb {
6
+ if ($env:ANDROID_HOME) { $p = Join-Path $env:ANDROID_HOME 'platform-tools\adb.exe'; if (Test-Path $p) { return $p } }
7
+ $cmd = Get-Command adb -ErrorAction SilentlyContinue
8
+ if ($cmd) { return $cmd.Source }
9
+ return $null
10
+ }
11
+
12
+ Invoke-Check -Title 'adb' -Check {
13
+ $adb = Get-AndroidAdb; if (-not $adb) { return $null }
14
+ (& $adb version) 2>$null | Select-Object -First 1
15
+ } -Hint 'adb not found - re-run without -SkipAndroid, or open a NEW terminal if the environment was just written'
16
+
17
+ Invoke-Check -Title 'ANDROID_HOME / JAVA_HOME' -Check {
18
+ if ($env:ANDROID_HOME -and $env:JAVA_HOME) { "ANDROID_HOME=$env:ANDROID_HOME, JAVA_HOME=$env:JAVA_HOME" }
19
+ } -Hint 'not set in this console - open a NEW terminal (User environment) or set them'
20
+
21
+ Invoke-Check -Title 'Android devices' -Check {
22
+ $adb = Get-AndroidAdb; if (-not $adb) { return $null }
23
+ $lines = @((& $adb devices) 2>$null | Select-Object -Skip 1 | Where-Object { $_ -match '\sdevice$' })
24
+ if ($lines.Count -gt 0) { "$($lines.Count) device(s) connected" }
25
+ else { 'no device connected - plug in a phone (USB debugging on) or start an emulator before /sungen:run-test' }
26
+ } -Hint 'adb is not usable yet' -Warn
@@ -0,0 +1,58 @@
1
+ <#
2
+ .SYNOPSIS
3
+ setup.ps1 - Windows entry point of the sungen Appium environment bundle.
4
+ .DESCRIPTION
5
+ .\appium-environment\setup.ps1 [-Check] [-SkipAndroid] [-NoProfile]
6
+
7
+ Emitted by `sungen init --mobile` / `sungen capability add mobile`, which also run it once.
8
+ Re-run it any time: every step is check -> install -> verify and a satisfied step is skipped.
9
+ -Check report what is missing, install nothing (side-effect free)
10
+ -SkipAndroid skip JDK / Android SDK / uiautomator2
11
+ -NoProfile do not write ANDROID_HOME / JAVA_HOME / Path to your User environment
12
+ iOS (xcuitest) needs Xcode and therefore a Mac - there is no iOS step on Windows.
13
+ Exit: 0 ready (or only warnings) - 1 something failed - 2 unsupported host
14
+ Run as: powershell -NoProfile -ExecutionPolicy Bypass -File .\appium-environment\setup.ps1
15
+ #>
16
+ [CmdletBinding()]
17
+ param([switch]$Check, [switch]$SkipAndroid, [switch]$NoProfile)
18
+
19
+ $ErrorActionPreference = 'Continue'
20
+ if ($PSVersionTable.PSVersion.Major -lt 5) { Write-Host 'Windows PowerShell 5.1 or newer is required.' -ForegroundColor Red; exit 2 }
21
+ if ($env:OS -ne 'Windows_NT') { Write-Host 'setup.ps1 is for Windows - on macOS run ./appium-environment/setup.sh' -ForegroundColor Red; exit 2 }
22
+
23
+ $script:SungenEnvCheckOnly = [bool]$Check
24
+ $script:SungenEnvSkipAndroid = [bool]$SkipAndroid
25
+ $script:SungenEnvNoProfile = [bool]$NoProfile
26
+ $script:ProjectDir = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
27
+ . (Join-Path $PSScriptRoot 'scripts\common\lib.ps1')
28
+
29
+ Set-Location $script:ProjectDir
30
+ if ($script:SungenEnvCheckOnly) { Write-Section 'sungen - Appium environment - check only (Windows)' }
31
+ else { Write-Section 'sungen - Appium environment setup (Windows)' }
32
+ Write-Host "project: $($script:ProjectDir)" -ForegroundColor DarkGray
33
+
34
+ # --- 1/4 prerequisites -----------------------------------------------------------------------------
35
+ Write-Section '1/4 Prerequisites'
36
+ . (Join-Path $PSScriptRoot 'scripts\common\verify-node.ps1')
37
+ Invoke-Check -Title 'winget (Windows Package Manager)' -Check {
38
+ if (Get-Command winget -ErrorAction SilentlyContinue) { $v = (& winget --version) 2>$null | Select-Object -First 1; "winget $v" }
39
+ } -Hint 'winget missing - install "App Installer" from the Microsoft Store (Windows 10 1809+ / 11), then re-run'
40
+
41
+ # --- 2/4 Android -----------------------------------------------------------------------------------
42
+ if (-not $script:SungenEnvSkipAndroid) {
43
+ Write-Section '2/4 Android (JDK, SDK, adb, emulator)'
44
+ . (Join-Path $PSScriptRoot 'scripts\windows\install-android.ps1')
45
+ } else {
46
+ Write-Section '2/4 Android'; Write-Skip 'Android toolchain' '-SkipAndroid'
47
+ }
48
+
49
+ # --- 3/4 Appium ------------------------------------------------------------------------------------
50
+ Write-Section '3/4 Appium (project-local) + platform driver'
51
+ . (Join-Path $PSScriptRoot 'scripts\common\install-appium.ps1')
52
+
53
+ # --- 4/4 health ------------------------------------------------------------------------------------
54
+ Write-Section '4/4 Health check'
55
+ if (-not $script:SungenEnvSkipAndroid) { . (Join-Path $PSScriptRoot 'scripts\windows\verify-android.ps1') }
56
+ . (Join-Path $PSScriptRoot 'scripts\common\verify-appium.ps1')
57
+
58
+ exit (Write-Summary)
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # setup.sh — macOS entry point of the sungen Appium environment bundle.
3
+ #
4
+ # ./appium-environment/setup.sh [--check] [--skip-android] [--skip-ios] [--no-profile]
5
+ #
6
+ # Emitted by `sungen init --mobile` / `sungen capability add mobile`, which also run it once.
7
+ # Re-run it any time: every step is check → install → verify and a satisfied step is skipped.
8
+ # --check report what is missing, install nothing (side-effect free)
9
+ # --skip-android skip JDK / Android SDK / uiautomator2
10
+ # --skip-ios skip Xcode CLT / applesimutils / xcuitest
11
+ # --no-profile do not write the ANDROID_HOME / JAVA_HOME / PATH block to your shell profile
12
+ # Exit: 0 ready (or only warnings) · 1 something failed · 2 unsupported OS / bad flag
13
+ set -u
14
+
15
+ BUNDLE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
16
+ PROJECT_DIR=$(cd "$BUNDLE_DIR/.." && pwd)
17
+ export PROJECT_DIR
18
+ . "$BUNDLE_DIR/scripts/common/lib.sh"
19
+
20
+ usage() { sed -n '4,13p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
21
+
22
+ for arg in "$@"; do
23
+ case "$arg" in
24
+ --check) SUNGEN_ENV_CHECK_ONLY=1 ;;
25
+ --skip-android) SUNGEN_ENV_SKIP_ANDROID=1 ;;
26
+ --skip-ios) SUNGEN_ENV_SKIP_IOS=1 ;;
27
+ --no-profile) SUNGEN_ENV_NO_PROFILE=1 ;;
28
+ -h|--help) usage; exit 0 ;;
29
+ *) printf 'unknown flag: %s\n\n' "$arg" >&2; usage >&2; exit 2 ;;
30
+ esac
31
+ done
32
+
33
+ case "$(uname -s)" in
34
+ Darwin) ;;
35
+ Linux)
36
+ printf 'ℹ Linux detected — this bundle provisions macOS and Windows.\n'
37
+ printf ' Android on Linux: see README.md → "Manual fallback" (JDK + Android SDK via your package manager,\n'
38
+ printf ' then `sungen capability add mobile` for the project-local Appium + uiautomator2).\n'
39
+ exit 0 ;;
40
+ *) printf '✗ unsupported OS: %s (macOS: setup.sh · Windows: setup.ps1)\n' "$(uname -s)" >&2; exit 2 ;;
41
+ esac
42
+
43
+ cd "$PROJECT_DIR"
44
+ if [ "$SUNGEN_ENV_CHECK_ONLY" = 1 ]; then section "sungen · Appium environment — check only (macOS)"
45
+ else section "sungen · Appium environment setup (macOS)"; fi
46
+ printf '%sproject: %s%s\n' "$C_DIM" "$PROJECT_DIR" "$C_RESET"
47
+
48
+ # ── 1/5 prerequisites ──────────────────────────────────────────────────────────────────────────
49
+ section "1/5 Prerequisites"
50
+ . "$BUNDLE_DIR/scripts/common/verify-node.sh"
51
+
52
+ brew_check() { have brew && printf 'brew at %s' "$(command -v brew)"; }
53
+ brew_install() {
54
+ NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || return 1
55
+ # The installer does not touch the running shell — pick brew up for the rest of this run.
56
+ if [ -x /opt/homebrew/bin/brew ]; then eval "$(/opt/homebrew/bin/brew shellenv)"
57
+ elif [ -x /usr/local/bin/brew ]; then eval "$(/usr/local/bin/brew shellenv)"; fi
58
+ }
59
+ brew_verify() { brew_check || { printf 'Homebrew not found after install — https://brew.sh, then re-run'; return 1; }; }
60
+ run_step "Homebrew" brew_check brew_install brew_verify
61
+
62
+ # ── 2/5 Android ────────────────────────────────────────────────────────────────────────────────
63
+ if [ "$SUNGEN_ENV_SKIP_ANDROID" != 1 ]; then
64
+ section "2/5 Android (JDK, SDK, adb, emulator)"
65
+ . "$BUNDLE_DIR/scripts/macos/install-android.sh"
66
+ else
67
+ section "2/5 Android"; log_skip "Android toolchain" "--skip-android"
68
+ fi
69
+
70
+ # ── 3/5 iOS ────────────────────────────────────────────────────────────────────────────────────
71
+ if [ "$SUNGEN_ENV_SKIP_IOS" != 1 ]; then
72
+ section "3/5 iOS (Xcode, applesimutils)"
73
+ . "$BUNDLE_DIR/scripts/macos/install-ios.sh"
74
+ else
75
+ section "3/5 iOS"; log_skip "iOS toolchain" "--skip-ios"
76
+ fi
77
+
78
+ # ── 4/5 Appium ─────────────────────────────────────────────────────────────────────────────────
79
+ section "4/5 Appium (project-local) + platform drivers"
80
+ . "$BUNDLE_DIR/scripts/common/install-appium.sh"
81
+
82
+ # ── 5/5 health ─────────────────────────────────────────────────────────────────────────────────
83
+ section "5/5 Health check"
84
+ [ "$SUNGEN_ENV_SKIP_ANDROID" != 1 ] && . "$BUNDLE_DIR/scripts/macos/verify-android.sh"
85
+ [ "$SUNGEN_ENV_SKIP_IOS" != 1 ] && . "$BUNDLE_DIR/scripts/macos/verify-ios.sh"
86
+ . "$BUNDLE_DIR/scripts/common/verify-appium.sh"
87
+
88
+ print_summary
@@ -38,7 +38,7 @@ const POLL_INTERVAL_MS = 100;
38
38
  export interface DeclaredSignal { class?: string; attribute?: string }
39
39
 
40
40
  /** Where the control publishes its on/off state — decided in the page, from the real element. */
41
- export type StateSource = 'native' | 'aria-pressed' | 'aria-checked' | 'class' | 'attribute' | 'none';
41
+ export type StateSource = 'native' | 'aria-pressed' | 'aria-checked' | 'aria-selected' | 'aria-current' | 'class' | 'attribute' | 'none';
42
42
  export interface ControlProbe {
43
43
  source: StateSource;
44
44
  /** Current state, already normalised. Meaningless when `source` is 'native' (Playwright reads it). */
@@ -110,6 +110,24 @@ export function probeControl(node: unknown, declared?: DeclaredSignal): ControlP
110
110
  }
111
111
  // Role claims checkable but nothing is published — let Playwright raise its own, precise error.
112
112
  if (ariaCheckableRole) return { source: 'native', on: false, buttonLike };
113
+ // `is selected` on a tab, an option, a chip or a nav item. These publish `aria-selected` or
114
+ // `aria-current`, which nothing above reads: the probe fell straight through to 'none' and the
115
+ // step failed saying the control has no on/off state to set, on a control that publishes one in
116
+ // the standard place. Read here rather than in the templates so a suite already generated in the
117
+ // field picks the fix up without regenerating — the same reasoning the URL predicate is a runtime
118
+ // helper for.
119
+ //
120
+ // `aria-current` is not a boolean: the spec allows 'page', 'step', 'location', 'date', 'time' and
121
+ // 'true', and only the literal 'false' (or the attribute's absence) means off. Read that way
122
+ // round, so `aria-current="page"` on the active nav item reads as ON rather than as a stray
123
+ // non-'true' string.
124
+ if (el.hasAttribute('aria-selected')) {
125
+ return { source: 'aria-selected', on: String(el.getAttribute('aria-selected') ?? '').trim().toLowerCase() === 'true', buttonLike };
126
+ }
127
+ if (el.hasAttribute('aria-current')) {
128
+ const raw = String(el.getAttribute('aria-current') ?? '').trim().toLowerCase();
129
+ return { source: 'aria-current', on: raw !== '' && raw !== 'false', buttonLike };
130
+ }
113
131
  return { source: 'none', on: false, buttonLike };
114
132
  }
115
133
 
@@ -132,11 +150,18 @@ const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms
132
150
  */
133
151
  const left = (deadline: number): number => Math.max(250, deadline - Date.now());
134
152
 
153
+ /** The sources that name an ATTRIBUTE the live state can be re-read from. */
154
+ type AttributeSource = 'aria-pressed' | 'aria-checked' | 'aria-selected' | 'aria-current';
155
+
135
156
  /** Read the live state. A REMOVED attribute means off: `aria-pressed={on || undefined}` is a common
136
157
  * React/Vue idiom, so requiring a literal "false" would fail a control that is correctly off. */
137
- async function readOn(target: Locator, attr: 'aria-pressed' | 'aria-checked', deadline: number): Promise<boolean> {
138
- const raw = await target.getAttribute(attr, { timeout: left(deadline) });
139
- return String(raw ?? '').trim().toLowerCase() === 'true';
158
+ async function readOn(target: Locator, attr: AttributeSource, deadline: number): Promise<boolean> {
159
+ const raw = String(await target.getAttribute(attr, { timeout: left(deadline) }) ?? '').trim().toLowerCase();
160
+ // `aria-current` is the one non-boolean of the four: the spec allows 'page', 'step', 'location',
161
+ // 'date', 'time' and 'true', so only the literal 'false' (or absence) means off. Read the other
162
+ // way round it would report the active nav item as OFF.
163
+ if (attr === 'aria-current') return raw !== '' && raw !== 'false';
164
+ return raw === 'true';
140
165
  }
141
166
 
142
167
  /**
@@ -238,12 +263,12 @@ export async function setCheckedState(
238
263
  // Not pressable and publishes nothing — there is no reading of this that makes the step meaningful.
239
264
  throw new Error(
240
265
  `Cannot ${want ? 'check' : 'uncheck'} [${label}] — it is not a checkbox/radio/switch, is not a `
241
- + `button, and publishes no aria-pressed/aria-checked, so it has no on/off state to set. Point the `
266
+ + `button, and publishes no aria-pressed/aria-checked/aria-selected/aria-current, so it has no on/off state to set. Point the `
242
267
  + `selector at the control itself, or give it a real ARIA toggle role.`,
243
268
  );
244
269
  }
245
270
 
246
- const attr = probe.source;
271
+ const attr: AttributeSource = probe.source as AttributeSource;
247
272
  // Already in the requested state → nothing to do. This is the idempotence `check` promises.
248
273
  if (probe.on === want) return;
249
274
  await target.click({ timeout });
@@ -42,18 +42,30 @@ interface LocaleInjection {
42
42
 
43
43
  let cached: LocaleInjection | null | undefined;
44
44
 
45
+ /**
46
+ * Absent config → null, and locale injection is simply off (the documented no-op).
47
+ *
48
+ * A config that EXISTS but does not parse is a different thing and must not read the same way: the
49
+ * header above tells you to hand-edit this file, and a trailing comma would otherwise turn a
50
+ * `SUNGEN_ENV=ja` run into a base-locale run that asserts base-locale strings — green, and wrong.
51
+ * We warn rather than throw, because the injection is genuinely optional and failing every test on
52
+ * it would be a larger change than the fault warrants; the line names the file and the consequence.
53
+ */
45
54
  function loadLocaleConfig(): LocaleInjection | null {
46
55
  if (cached !== undefined) return cached;
56
+ const cfgPath = path.join(__dirname, 'locale-config.json');
57
+ if (!fs.existsSync(cfgPath)) {
58
+ cached = null;
59
+ return null;
60
+ }
47
61
  try {
48
- const cfgPath = path.join(__dirname, 'locale-config.json');
49
- if (!fs.existsSync(cfgPath)) {
50
- cached = null;
51
- return null;
52
- }
53
- const parsed = JSON.parse(fs.readFileSync(cfgPath, 'utf-8')) as LocaleInjection;
54
- cached = parsed;
55
- return parsed;
56
- } catch {
62
+ cached = JSON.parse(fs.readFileSync(cfgPath, 'utf-8')) as LocaleInjection;
63
+ return cached;
64
+ } catch (e) {
65
+ console.error(
66
+ `⚠️ ${cfgPath}: unreadable (${e instanceof Error ? e.message.split('\n')[0] : String(e)}) — ` +
67
+ `locale state is NOT injected, so this SUNGEN_ENV run behaves as the base locale.`,
68
+ );
57
69
  cached = null;
58
70
  return null;
59
71
  }