1010jobs :
1111 inapp-e2e-tests :
1212 name : In-App Message E2E Tests
13- runs-on : macos-15-intel
14-
13+ # SDK-170: macOS Intel runners (2 cores / 3GB AVD on HVF) starved system_server during
14+ # cold boot and produced cascading ANRs (systemui / nexuslauncher / gms / phone …),
15+ # leaving a system dialog on top of MainActivity so UiAutomator could not find the
16+ # in-app button. Ubuntu runners with KVM acceleration and 4 vCPU / 16GB stop the storm.
17+ runs-on : ubuntu-latest
18+
1519 strategy :
1620 matrix :
1721 api-level : [34] # MVP testing on most relevant API level only
18-
22+
1923 steps :
2024 - name : Checkout code
2125 uses : actions/checkout@v4
22-
26+
27+ - name : Enable KVM device permissions
28+ run : |
29+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
30+ | sudo tee /etc/udev/rules.d/99-kvm.rules
31+ sudo udevadm control --reload-rules
32+ sudo udevadm trigger --name-match=kvm
33+
2334 - name : Set up JDK 17
2435 uses : actions/setup-java@v4
2536 with :
@@ -69,15 +80,15 @@ jobs:
6980 ./gradlew :integration-tests:assembleDebug :integration-tests:assembleDebugAndroidTest --no-daemon &
7081 echo "Build started in background..."
7182
72- - name : Run UI Tests with Emulator (Intel / x86_64)
83+ - name : Run UI Tests with Emulator (KVM / x86_64)
7384 uses : ReactiveCircus/android-emulator-runner@v2
7485 with :
7586 api-level : ${{ matrix.api-level }}
7687 target : google_apis
7788 arch : x86_64
7889 profile : pixel_6
79- cores : 2
80- ram-size : 3072M
90+ cores : 4
91+ ram-size : 4096M
8192 heap-size : 576M
8293 force-avd-creation : true
8394 disable-animations : true
@@ -87,98 +98,30 @@ jobs:
8798 # Clean + start adb after platform-tools exist (avoids tcp:5037 noise)
8899 adb kill-server >/dev/null 2>&1 || true
89100 adb start-server
90- script : |
91- echo "Emulator is ready! Running tests..."
92- echo "Setting up permissions..."
93- adb shell pm grant com.iterable.integration.tests android.permission.POST_NOTIFICATIONS
94- adb shell pm grant com.iterable.integration.tests android.permission.INTERNET
95- adb shell pm grant com.iterable.integration.tests android.permission.ACCESS_NETWORK_STATE
96- adb shell pm grant com.iterable.integration.tests android.permission.WAKE_LOCK
97-
98- echo "Running In-App Message MVP test..."
99- echo "Debug: Checking if APKs are ready..."
100- ls -la integration-tests/build/outputs/apk/ || echo "APK directory not found"
101-
102- echo "Debug: Verifying API keys are set..."
103- echo "ITERABLE_API_KEY length: ${#ITERABLE_API_KEY}"
104- echo "ITERABLE_SERVER_API_KEY length: ${#ITERABLE_SERVER_API_KEY}"
105- echo "ITERABLE_TEST_USER_EMAIL: $ITERABLE_TEST_USER_EMAIL"
106-
107- # Start logcat in background for crash debugging
108- adb logcat > /tmp/test-logcat.log &
109- LOGCAT_PID=$!
110-
111- # Run the specific test with better error handling
112- ./gradlew :integration-tests:connectedDebugAndroidTest \
113- -Pandroid.testInstrumentationRunnerArguments.class=com.iterable.integration.tests.InAppMessageIntegrationTest#testInAppMessageMVP \
114- --stacktrace --no-daemon || {
115- echo "Test failed! Collecting crash logs..."
116- kill $LOGCAT_PID 2>/dev/null || true
117- echo "=== CRASH LOGS ==="
118- tail -100 /tmp/test-logcat.log
119- echo "=== END CRASH LOGS ==="
120- exit 1
121- }
122-
123- # Stop logcat
124- kill $LOGCAT_PID 2>/dev/null || true
101+ # The android-emulator-runner action runs each line of an inline `script:`
102+ # in a fresh `/bin/sh -c`, so cross-line variables and bash functions don't
103+ # survive. Externalise the whole thing to a single bash file that runs in
104+ # one process — see .github/scripts/run-e2e.sh for the actual logic.
105+ script : bash "$GITHUB_WORKSPACE/.github/scripts/run-e2e.sh"
125106 env :
126107 ITERABLE_API_KEY : ${{ secrets.BCIT_ITERABLE_API_KEY }}
127108 ITERABLE_SERVER_API_KEY : ${{ secrets.BCIT_ITERABLE_SERVER_API_KEY }}
128109 ITERABLE_TEST_USER_EMAIL : ${{ secrets.BCIT_ITERABLE_TEST_USER_EMAIL }}
129-
130- # - name: Generate Test Report
131- # if: always()
132- # run: |
133- # echo "Generating E2E test report..."
134- # ./gradlew :integration-tests:jacocoIntegrationTestReport
135-
136- # - name: Collect Test Logs
137- # if: always()
138- # run: |
139- # echo "Collecting E2E test logs..."
140- # adb logcat -d > integration-tests/build/e2e-test-logs.txt
141-
142- # # Also collect specific test logs
143- # adb logcat -d | grep -E "(InAppMessageIntegrationTest|BaseIntegrationTest|IterableApi)" > integration-tests/build/inapp-specific-logs.txt
144-
145- # - name: Take Screenshots for Debugging
146- # if: always()
147- # run: |
148- # echo "Taking screenshots for debugging..."
149- # mkdir -p integration-tests/screenshots
150- # adb shell screencap -p /sdcard/screenshot.png
151- # adb pull /sdcard/screenshot.png integration-tests/screenshots/final-state-api-${{ matrix.api-level }}.png
152-
153- # - name: Upload Test Results
154- # if: always()
155- # uses: actions/upload-artifact@v4
156- # with:
157- # name: inapp-e2e-test-results-api-${{ matrix.api-level }}
158- # path: |
159- # integration-tests/build/reports/
160- # integration-tests/build/outputs/
161- # integration-tests/build/e2e-test-logs.txt
162- # integration-tests/build/inapp-specific-logs.txt
163-
164- # - name: Upload Coverage Report
165- # if: always()
166- # uses: actions/upload-artifact@v4
167- # with:
168- # name: inapp-e2e-coverage-api-${{ matrix.api-level }}
169- # path: integration-tests/build/reports/jacoco/
170-
171- # - name: Upload Screenshots
172- # if: always()
173- # uses: actions/upload-artifact@v4
174- # with:
175- # name: inapp-e2e-screenshots-api-${{ matrix.api-level }}
176- # path: integration-tests/screenshots/
177-
178- # - name: Cleanup
179- # if: always()
180- # run: |
181- # echo "Test cleanup completed"
110+
111+ # SDK-170: do NOT upload integration-tests/build/outputs/ — that path contains the
112+ # built APKs which embed BuildConfig.ITERABLE_API_KEY and BuildConfig.ITERABLE_SERVER_API_KEY
113+ # as compile-time string constants. On a public repo, anyone who can download the
114+ # artifact could `strings`/`apktool` the APK and recover both keys.
115+ - name : Upload E2E diagnostics
116+ if : always()
117+ uses : actions/upload-artifact@v4
118+ with :
119+ name : e2e-diagnostics-api-${{ matrix.api-level }}
120+ path : |
121+ integration-tests/build/diagnostics/
122+ integration-tests/build/reports/
123+ if-no-files-found : warn
124+ retention-days : 7
182125
183126 # test-summary:
184127 # name: Test Summary
0 commit comments