11name : ' Appium E2E - Local Android'
2- description : ' Boot an Android emulator, start Appium, and run the shared OneSignal Appium E2E tests against a local .apk. Uses raw sdkmanager/avdmanager/emulator commands so each phase is a separate, cacheable, debuggable step .'
2+ description : ' Boot an Android emulator via reactivecircus/android-emulator-runner , start Appium, and run the shared OneSignal Appium E2E tests against a local .apk.'
33
44inputs :
55 app-path :
@@ -34,7 +34,7 @@ inputs:
3434 description : ' Port Appium listens on'
3535 required : false
3636 default : ' 4723'
37- boot-timeout-seconds :
37+ emulator- boot-timeout :
3838 description : ' Max seconds to wait for emulator boot completion'
3939 required : false
4040 default : ' 900'
@@ -45,172 +45,126 @@ runs:
4545 - name : Setup Bun
4646 uses : oven-sh/setup-bun@v2
4747
48- - name : Resolve paths
48+ - name : Resolve appium dir
4949 shell : bash
5050 run : |
5151 APPIUM_DIR=$(cd "${{ github.action_path }}/../../../appium" && pwd -P)
5252 echo "APPIUM_DIR=$APPIUM_DIR" >> "$GITHUB_ENV"
5353
54- if [ -z "${ANDROID_HOME:-}" ]; then
55- if [ -n "${ANDROID_SDK_ROOT:-}" ]; then
56- ANDROID_HOME="$ANDROID_SDK_ROOT"
57- else
58- ANDROID_HOME="/usr/local/lib/android/sdk"
59- fi
60- echo "ANDROID_HOME=$ANDROID_HOME" >> "$GITHUB_ENV"
61- fi
62-
63- CMDLINE_BIN=$(ls -d "$ANDROID_HOME"/cmdline-tools/*/bin 2>/dev/null | tail -1 || true)
64- if [ -z "$CMDLINE_BIN" ]; then
65- echo "::error::No Android cmdline-tools found under $ANDROID_HOME/cmdline-tools/"
66- ls -la "$ANDROID_HOME/cmdline-tools" 2>/dev/null || true
67- exit 1
68- fi
69- echo "$CMDLINE_BIN" >> "$GITHUB_PATH"
70- echo "$ANDROID_HOME/emulator" >> "$GITHUB_PATH"
71- echo "$ANDROID_HOME/platform-tools" >> "$GITHUB_PATH"
72- echo "Using cmdline-tools at: $CMDLINE_BIN"
73-
7454 - name : Enable KVM
7555 shell : bash
7656 run : |
7757 echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
7858 sudo udevadm control --reload-rules
7959 sudo udevadm trigger --name-match=kvm
8060
81- - name : Cache Android system image
61+ - name : AVD cache
8262 uses : actions/cache@v4
63+ id : avd-cache
8364 with :
84- path : ${{ env.ANDROID_HOME }}/system-images/android-${{ inputs.api-level }}/${{ inputs.target }}/${{ inputs.arch }}
85- key : android-sysimg-${{ inputs.api-level }}-${{ inputs.target }}-${{ inputs.arch }}
86-
87- - name : Install SDK packages
88- shell : bash
89- env :
90- API_LEVEL : ${{ inputs.api-level }}
91- TARGET : ${{ inputs.target }}
92- ARCH : ${{ inputs.arch }}
93- run : |
94- yes | sdkmanager --licenses >/dev/null 2>&1 || true
95- sdkmanager --install \
96- "platforms;android-$API_LEVEL" \
97- "system-images;android-$API_LEVEL;$TARGET;$ARCH" \
98- "emulator" \
99- "platform-tools" > /tmp/sdkmanager.log 2>&1 || {
100- echo "::error::sdkmanager install failed"
101- tail -40 /tmp/sdkmanager.log
102- exit 1
103- }
104-
105- - name : Create AVD
106- shell : bash
107- env :
108- API_LEVEL : ${{ inputs.api-level }}
109- TARGET : ${{ inputs.target }}
110- ARCH : ${{ inputs.arch }}
111- PROFILE : ${{ inputs.profile }}
112- run : |
113- echo "no" | avdmanager create avd \
114- -n test \
115- -k "system-images;android-$API_LEVEL;$TARGET;$ARCH" \
116- -d "$PROFILE" \
117- --force
65+ path : |
66+ ~/.android/avd/*
67+ ~/.android/adb*
68+ key : avd-${{ inputs.api-level }}-${{ inputs.target }}-${{ inputs.arch }}-${{ inputs.profile }}
11869
119- - name : Boot emulator
70+ - name : Create AVD snapshot
71+ if : steps.avd-cache.outputs.cache-hit != 'true'
72+ uses : reactivecircus/android-emulator-runner@v2
73+ with :
74+ api-level : ${{ inputs.api-level }}
75+ target : ${{ inputs.target }}
76+ arch : ${{ inputs.arch }}
77+ profile : ${{ inputs.profile }}
78+ ram-size : 4096M
79+ heap-size : 1024M
80+ disable-animations : false
81+ force-avd-creation : false
82+ emulator-options : -no-window -gpu swiftshader_indirect -no-boot-anim -no-snapshot-save
83+ emulator-boot-timeout : ${{ inputs.emulator-boot-timeout }}
84+ script : echo "Generated AVD snapshot for caching"
85+
86+ # reactivecircus/android-emulator-runner@v2 executes `script:` line-by-line
87+ # via separate `sh -c` calls, which breaks multi-line constructs and state
88+ # (e.g. APPIUM_PID=$! ends up in a throwaway shell). We stage the full test
89+ # flow to a file here and invoke it from the action as a single line.
90+ - name : Stage Appium test script
12091 shell : bash
121- env :
122- BOOT_TIMEOUT : ${{ inputs.boot-timeout-seconds }}
12392 run : |
124- nohup "$ANDROID_HOME/emulator/emulator" \
125- -avd test \
126- -no-window -no-audio -no-boot-anim \
127- -gpu swiftshader_indirect \
128- -no-snapshot-save \
129- -memory 4096 \
130- > emulator.log 2>&1 &
131- echo "EMULATOR_PID=$!" >> "$GITHUB_ENV"
132-
133- adb wait-for-device
134-
135- echo "Waiting for boot completion (timeout ${BOOT_TIMEOUT}s)..."
136- deadline=$(( $(date +%s) + BOOT_TIMEOUT ))
137- while :; do
138- boot=$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r' || true)
139- if [ "$boot" = "1" ]; then
140- break
141- fi
142- if [ "$(date +%s)" -ge "$deadline" ]; then
143- echo "::error::Emulator boot timed out"
144- echo "--- emulator.log (last 100 lines) ---"
145- tail -100 emulator.log || true
146- exit 1
147- fi
148- sleep 2
149- done
150- adb shell input keyevent 82 >/dev/null 2>&1 || true
151- echo "Emulator booted"
93+ cat > "$RUNNER_TEMP/appium-android.sh" <<'SCRIPT_EOF'
94+ #!/usr/bin/env bash
95+ set -euo pipefail
15296
153- - name : Install Appium and driver
154- shell : bash
155- run : |
97+ echo "::group::Install Appium + uiautomator2 driver"
15698 bun add -g appium@3
15799 appium driver install uiautomator2
100+ echo "::endgroup::"
158101
159- - name : Start Appium
160- shell : bash
161- env :
162- APPIUM_PORT : ${{ inputs.appium-port }}
163- run : |
164- appium --port "$APPIUM_PORT" --log-level error > appium.log 2>&1 &
165- echo "APPIUM_PID=$!" >> "$GITHUB_ENV"
166- i=0
167- while [ "$i" -lt 30 ]; do
102+ echo "::group::Start Appium"
103+ appium --port "$APPIUM_PORT" --log-level error > "$GITHUB_WORKSPACE/appium.log" 2>&1 &
104+ APPIUM_PID=$!
105+ echo "APPIUM_PID=$APPIUM_PID"
106+
107+ for _ in $(seq 1 60); do
168108 if curl -sf "http://localhost:$APPIUM_PORT/status" >/dev/null; then
169- echo "Appium ready"
170- exit 0
109+ break
171110 fi
172111 sleep 1
173- i=$((i + 1))
174112 done
175- echo "::error::Appium did not become ready on port $APPIUM_PORT"
176- cat appium.log || true
177- exit 1
113+ if ! curl -sf "http://localhost:$APPIUM_PORT/status" >/dev/null; then
114+ echo "::error::Appium did not become ready on port $APPIUM_PORT"
115+ tail -100 "$GITHUB_WORKSPACE/appium.log" || true
116+ exit 1
117+ fi
118+ echo "Appium ready"
119+ echo "::endgroup::"
178120
179- - name : Install test dependencies
180- shell : bash
181- working-directory : ${{ env.APPIUM_DIR }}
182- run : bun install --frozen-lockfile
121+ echo "::group::Install test dependencies"
122+ cd "$APPIUM_DIR"
123+ bun install --frozen-lockfile
124+ echo "::endgroup::"
125+
126+ echo "::group::Run WDIO tests"
127+ set +e
128+ bunx wdio run wdio.android.conf.ts
129+ WDIO_EXIT=$?
130+ set -e
131+ echo "::endgroup::"
132+
133+ echo "::group::Stop Appium"
134+ kill "$APPIUM_PID" 2>/dev/null || true
135+ echo "::endgroup::"
136+
137+ exit "$WDIO_EXIT"
138+ SCRIPT_EOF
139+ chmod +x "$RUNNER_TEMP/appium-android.sh"
183140
184141 - name : Run Appium tests
185- shell : bash
186- working-directory : ${{ env.APPIUM_DIR }}
142+ uses : reactivecircus/android-emulator-runner@v2
187143 env :
188- SDK_TYPE : ${{ inputs.sdk-type }}
189- PLATFORM : android
144+ APPIUM_PORT : ${{ inputs.appium-port }}
190145 APP_PATH : ${{ inputs.app-path }}
146+ SDK_TYPE : ${{ inputs.sdk-type }}
191147 ONESIGNAL_APP_ID : ${{ inputs.onesignal-app-id }}
192148 ONESIGNAL_API_KEY : ${{ inputs.onesignal-api-key }}
193- run : bunx wdio run wdio.android.conf.ts
194-
195- - name : Shut down emulator and Appium
196- if : always()
197- shell : bash
198- run : |
199- adb emu kill 2>/dev/null || true
200- if [ -n "${APPIUM_PID:-}" ]; then
201- kill "$APPIUM_PID" 2>/dev/null || true
202- fi
203- if [ -n "${EMULATOR_PID:-}" ]; then
204- kill "$EMULATOR_PID" 2>/dev/null || true
205- fi
206-
207- - name : Upload Appium + emulator logs
149+ with :
150+ api-level : ${{ inputs.api-level }}
151+ target : ${{ inputs.target }}
152+ arch : ${{ inputs.arch }}
153+ profile : ${{ inputs.profile }}
154+ ram-size : 4096M
155+ heap-size : 1024M
156+ disable-animations : true
157+ force-avd-creation : false
158+ emulator-options : -no-snapshot-save -no-window -gpu swiftshader_indirect -no-boot-anim
159+ emulator-boot-timeout : ${{ inputs.emulator-boot-timeout }}
160+ script : bash "$RUNNER_TEMP/appium-android.sh"
161+
162+ - name : Upload Appium + test results
208163 if : always()
209164 uses : actions/upload-artifact@v5
210165 with :
211166 name : appium-local-android-${{ inputs.sdk-type }}
212167 path : |
213168 ${{ env.APPIUM_DIR }}/results/
214- ${{ github.workspace }}/emulator.log
215169 ${{ github.workspace }}/appium.log
216170 if-no-files-found : ignore
0 commit comments