@sun-asterisk/sungen 3.2.31 → 3.2.32

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 (177) hide show
  1. package/dist/cli/commands/audit.d.ts.map +1 -1
  2. package/dist/cli/commands/audit.js +3 -1
  3. package/dist/cli/commands/audit.js.map +1 -1
  4. package/dist/cli/commands/capability.d.ts.map +1 -1
  5. package/dist/cli/commands/capability.js +11 -0
  6. package/dist/cli/commands/capability.js.map +1 -1
  7. package/dist/cli/commands/init.d.ts.map +1 -1
  8. package/dist/cli/commands/init.js +2 -0
  9. package/dist/cli/commands/init.js.map +1 -1
  10. package/dist/cli/commands/setup-mobile-env.d.ts +15 -0
  11. package/dist/cli/commands/setup-mobile-env.d.ts.map +1 -0
  12. package/dist/cli/commands/setup-mobile-env.js +65 -0
  13. package/dist/cli/commands/setup-mobile-env.js.map +1 -0
  14. package/dist/cli/index.js +2 -0
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  17. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  18. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  19. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  20. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  21. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  22. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  23. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  24. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  25. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  26. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  27. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +26 -0
  28. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  29. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  30. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  31. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  32. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  33. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  34. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +13 -2
  35. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  36. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  37. package/dist/generators/test-generator/code-generator.js +56 -13
  38. package/dist/generators/test-generator/code-generator.js.map +1 -1
  39. package/dist/generators/test-generator/diagnostics.d.ts +39 -2
  40. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  41. package/dist/generators/test-generator/diagnostics.js +38 -1
  42. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  43. package/dist/generators/test-generator/patterns/index.d.ts +1 -1
  44. package/dist/generators/test-generator/patterns/index.js +1 -1
  45. package/dist/generators/test-generator/step-mapper.d.ts +35 -0
  46. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  47. package/dist/generators/test-generator/step-mapper.js +103 -4
  48. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  49. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  50. package/dist/generators/test-generator/template-engine.js +18 -0
  51. package/dist/generators/test-generator/template-engine.js.map +1 -1
  52. package/dist/generators/test-generator/utils/selector-resolver.d.ts +47 -0
  53. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  54. package/dist/generators/test-generator/utils/selector-resolver.js +17 -3
  55. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  56. package/dist/harness/audit.d.ts.map +1 -1
  57. package/dist/harness/audit.js +23 -4
  58. package/dist/harness/audit.js.map +1 -1
  59. package/dist/harness/next-step.d.ts.map +1 -1
  60. package/dist/harness/next-step.js +2 -1
  61. package/dist/harness/next-step.js.map +1 -1
  62. package/dist/harness/parse.d.ts.map +1 -1
  63. package/dist/harness/parse.js +42 -7
  64. package/dist/harness/parse.js.map +1 -1
  65. package/dist/harness/spec-branches.d.ts +7 -2
  66. package/dist/harness/spec-branches.d.ts.map +1 -1
  67. package/dist/harness/spec-branches.js +62 -11
  68. package/dist/harness/spec-branches.js.map +1 -1
  69. package/dist/harness/spec-coverage.d.ts +16 -0
  70. package/dist/harness/spec-coverage.d.ts.map +1 -1
  71. package/dist/harness/spec-coverage.js +119 -10
  72. package/dist/harness/spec-coverage.js.map +1 -1
  73. package/dist/orchestrator/appium-environment-setup.d.ts +24 -0
  74. package/dist/orchestrator/appium-environment-setup.d.ts.map +1 -0
  75. package/dist/orchestrator/appium-environment-setup.js +157 -0
  76. package/dist/orchestrator/appium-environment-setup.js.map +1 -0
  77. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +2 -0
  78. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
  79. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
  80. package/dist/orchestrator/project-initializer.d.ts +1 -0
  81. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  82. package/dist/orchestrator/project-initializer.js +14 -0
  83. package/dist/orchestrator/project-initializer.js.map +1 -1
  84. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -1
  85. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +55 -2
  86. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  87. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  88. package/dist/orchestrator/templates/appium-environment/README.md +83 -0
  89. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  90. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  91. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  92. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  93. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  94. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  95. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  96. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  97. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  98. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  99. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  100. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  101. package/dist/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  102. package/dist/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  103. package/dist/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  104. package/dist/orchestrator/templates/appium-environment/setup.sh +88 -0
  105. package/dist/orchestrator/templates/specs-checked-state.d.ts +1 -1
  106. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -1
  107. package/dist/orchestrator/templates/specs-checked-state.js +26 -3
  108. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -1
  109. package/dist/orchestrator/templates/specs-checked-state.ts +31 -6
  110. package/dist/orchestrator/templates/specs-mock.d.ts +2 -1
  111. package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -1
  112. package/dist/orchestrator/templates/specs-mock.js +10 -3
  113. package/dist/orchestrator/templates/specs-mock.js.map +1 -1
  114. package/dist/orchestrator/templates/specs-mock.ts +12 -4
  115. package/package.json +3 -3
  116. package/src/cli/commands/audit.ts +2 -1
  117. package/src/cli/commands/capability.ts +12 -1
  118. package/src/cli/commands/init.ts +3 -0
  119. package/src/cli/commands/setup-mobile-env.ts +64 -0
  120. package/src/cli/index.ts +2 -0
  121. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  122. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  123. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  124. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  125. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  126. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  127. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  128. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  129. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  130. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  131. package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  132. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +26 -0
  133. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  134. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  135. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  136. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  137. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  138. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  139. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +13 -2
  140. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  141. package/src/generators/test-generator/code-generator.ts +66 -17
  142. package/src/generators/test-generator/diagnostics.ts +39 -2
  143. package/src/generators/test-generator/patterns/index.ts +1 -1
  144. package/src/generators/test-generator/step-mapper.ts +106 -5
  145. package/src/generators/test-generator/template-engine.ts +18 -0
  146. package/src/generators/test-generator/utils/selector-resolver.ts +18 -3
  147. package/src/harness/audit.ts +24 -5
  148. package/src/harness/next-step.ts +2 -1
  149. package/src/harness/parse.ts +42 -7
  150. package/src/harness/spec-branches.ts +67 -12
  151. package/src/harness/spec-coverage.ts +121 -11
  152. package/src/orchestrator/appium-environment-setup.ts +127 -0
  153. package/src/orchestrator/mobile-runtime-scaffolder.ts +2 -0
  154. package/src/orchestrator/project-initializer.ts +15 -1
  155. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -1
  156. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +55 -2
  157. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  158. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  159. package/src/orchestrator/templates/appium-environment/README.md +83 -0
  160. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  161. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  162. package/src/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  163. package/src/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  164. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  165. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  166. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  167. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  168. package/src/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  169. package/src/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  170. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  171. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  172. package/src/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  173. package/src/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  174. package/src/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  175. package/src/orchestrator/templates/appium-environment/setup.sh +88 -0
  176. package/src/orchestrator/templates/specs-checked-state.ts +31 -6
  177. package/src/orchestrator/templates/specs-mock.ts +12 -4
@@ -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
@@ -26,7 +26,7 @@ export interface DeclaredSignal {
26
26
  attribute?: string;
27
27
  }
28
28
  /** Where the control publishes its on/off state — decided in the page, from the real element. */
29
- export type StateSource = 'native' | 'aria-pressed' | 'aria-checked' | 'class' | 'attribute' | 'none';
29
+ export type StateSource = 'native' | 'aria-pressed' | 'aria-checked' | 'aria-selected' | 'aria-current' | 'class' | 'attribute' | 'none';
30
30
  export interface ControlProbe {
31
31
  source: StateSource;
32
32
  /** Current state, already normalised. Meaningless when `source` is 'native' (Playwright reads it). */
@@ -1 +1 @@
1
- {"version":3,"file":"specs-checked-state.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-checked-state.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAchD,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAAG,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;AAEtE,iGAAiG;AACjG,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;AACtG,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,sGAAsG;IACtG,EAAE,EAAE,OAAO,CAAC;IACZ,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,YAAY,CAkDnF;AA4BD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAC7C,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC,IAAI,CAAC,CA6Gf;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EACjD,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
1
+ {"version":3,"file":"specs-checked-state.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-checked-state.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAchD,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAAG,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;AAEtE,iGAAiG;AACjG,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,eAAe,GAAG,cAAc,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;AACzI,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,sGAAsG;IACtG,EAAE,EAAE,OAAO,CAAC;IACZ,mGAAmG;IACnG,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,YAAY,CAoEnF;AAmCD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAC7C,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC,IAAI,CAAC,CA6Gf;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EACjD,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
@@ -73,6 +73,24 @@ function probeControl(node, declared) {
73
73
  // Role claims checkable but nothing is published — let Playwright raise its own, precise error.
74
74
  if (ariaCheckableRole)
75
75
  return { source: 'native', on: false, buttonLike };
76
+ // `is selected` on a tab, an option, a chip or a nav item. These publish `aria-selected` or
77
+ // `aria-current`, which nothing above reads: the probe fell straight through to 'none' and the
78
+ // step failed saying the control has no on/off state to set, on a control that publishes one in
79
+ // the standard place. Read here rather than in the templates so a suite already generated in the
80
+ // field picks the fix up without regenerating — the same reasoning the URL predicate is a runtime
81
+ // helper for.
82
+ //
83
+ // `aria-current` is not a boolean: the spec allows 'page', 'step', 'location', 'date', 'time' and
84
+ // 'true', and only the literal 'false' (or the attribute's absence) means off. Read that way
85
+ // round, so `aria-current="page"` on the active nav item reads as ON rather than as a stray
86
+ // non-'true' string.
87
+ if (el.hasAttribute('aria-selected')) {
88
+ return { source: 'aria-selected', on: String(el.getAttribute('aria-selected') ?? '').trim().toLowerCase() === 'true', buttonLike };
89
+ }
90
+ if (el.hasAttribute('aria-current')) {
91
+ const raw = String(el.getAttribute('aria-current') ?? '').trim().toLowerCase();
92
+ return { source: 'aria-current', on: raw !== '' && raw !== 'false', buttonLike };
93
+ }
76
94
  return { source: 'none', on: false, buttonLike };
77
95
  }
78
96
  /** The declaration both helpers forward into the page. Undefined when nothing was declared, so a
@@ -91,8 +109,13 @@ const left = (deadline) => Math.max(250, deadline - Date.now());
91
109
  /** Read the live state. A REMOVED attribute means off: `aria-pressed={on || undefined}` is a common
92
110
  * React/Vue idiom, so requiring a literal "false" would fail a control that is correctly off. */
93
111
  async function readOn(target, attr, deadline) {
94
- const raw = await target.getAttribute(attr, { timeout: left(deadline) });
95
- return String(raw ?? '').trim().toLowerCase() === 'true';
112
+ const raw = String(await target.getAttribute(attr, { timeout: left(deadline) }) ?? '').trim().toLowerCase();
113
+ // `aria-current` is the one non-boolean of the four: the spec allows 'page', 'step', 'location',
114
+ // 'date', 'time' and 'true', so only the literal 'false' (or absence) means off. Read the other
115
+ // way round it would report the active nav item as OFF.
116
+ if (attr === 'aria-current')
117
+ return raw !== '' && raw !== 'false';
118
+ return raw === 'true';
96
119
  }
97
120
  /**
98
121
  * Drive `target` to the requested on/off state, idempotently.
@@ -184,7 +207,7 @@ async function setCheckedState(target, want, label, options) {
184
207
  }
185
208
  // Not pressable and publishes nothing — there is no reading of this that makes the step meaningful.
186
209
  throw new Error(`Cannot ${want ? 'check' : 'uncheck'} [${label}] — it is not a checkbox/radio/switch, is not a `
187
- + `button, and publishes no aria-pressed/aria-checked, so it has no on/off state to set. Point the `
210
+ + `button, and publishes no aria-pressed/aria-checked/aria-selected/aria-current, so it has no on/off state to set. Point the `
188
211
  + `selector at the control itself, or give it a real ARIA toggle role.`);
189
212
  }
190
213
  const attr = probe.source;