@@ -88,97 +88,119 @@ jobs:
8888 adb kill-server >/dev/null 2>&1 || true
8989 adb start-server
9090 script : |
91+ # SDK-170 OBSERVABILITY: capture diagnostics that survive the action exit.
92+ # The android-emulator-runner kills the emulator on its way out, so any
93+ # adb-dependent capture MUST happen before this script returns. We write
94+ # everything under $GITHUB_WORKSPACE so a later step can upload it.
95+ DIAG_DIR="$GITHUB_WORKSPACE/integration-tests/build/diagnostics"
96+ mkdir -p "$DIAG_DIR"
97+
98+ capture_diag() {
99+ local label="$1"
100+ local prefix="$2"
101+ echo "=== diag capture: $label ==="
102+ {
103+ echo "# label: $label"
104+ echo "# t: $(date -u +%FT%TZ)"
105+ echo
106+ echo "## adb devices"
107+ adb devices 2>&1
108+ echo
109+ echo "## top activities"
110+ adb shell dumpsys activity activities 2>&1 | head -200
111+ echo
112+ echo "## window state (focused app + visible windows)"
113+ adb shell dumpsys window 2>&1 | head -200
114+ echo
115+ echo "## connectivity (active default network, link addresses, dns)"
116+ adb shell dumpsys connectivity 2>&1 | head -120
117+ echo
118+ echo "## meminfo (system_server + integration tests)"
119+ adb shell dumpsys meminfo 2>&1 | head -80
120+ echo
121+ echo "## installed iterable packages"
122+ adb shell pm list packages 2>&1 | grep iterable || true
123+ echo
124+ echo "## boot props"
125+ adb shell getprop sys.boot_completed dev.bootcomplete init.svc.netd net.dns1 ro.build.version.release ro.build.version.sdk 2>&1 | head -10
126+ } > "$DIAG_DIR/${prefix}-state.txt" 2>&1
127+
128+ # UiAutomator hierarchy and screenshot — the smoking gun for "button not found"
129+ adb shell uiautomator dump /sdcard/hierarchy.xml >/dev/null 2>&1 || true
130+ adb pull /sdcard/hierarchy.xml "$DIAG_DIR/${prefix}-hierarchy.xml" >/dev/null 2>&1 || echo "(no hierarchy)"
131+ adb shell screencap -p /sdcard/screenshot.png >/dev/null 2>&1 || true
132+ adb pull /sdcard/screenshot.png "$DIAG_DIR/${prefix}-screenshot.png" >/dev/null 2>&1 || echo "(no screenshot)"
133+ }
134+
91135 echo "Emulator is ready! Running tests..."
92136 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-
137+ adb shell pm grant com.iterable.integration.tests android.permission.POST_NOTIFICATIONS || true
138+ adb shell pm grant com.iterable.integration.tests android.permission.INTERNET || true
139+ adb shell pm grant com.iterable.integration.tests android.permission.ACCESS_NETWORK_STATE || true
140+ adb shell pm grant com.iterable.integration.tests android.permission.WAKE_LOCK || true
141+
98142 echo "Running In-App Message MVP test..."
99143 echo "Debug: Checking if APKs are ready..."
100144 ls -la integration-tests/build/outputs/apk/ || echo "APK directory not found"
101-
145+
102146 echo "Debug: Verifying API keys are set..."
103147 echo "ITERABLE_API_KEY length: ${#ITERABLE_API_KEY}"
104148 echo "ITERABLE_SERVER_API_KEY length: ${#ITERABLE_SERVER_API_KEY}"
105149 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 &
150+
151+ # Snapshot pre-test device state. If tests fail because the activity never
152+ # foregrounded, this captures what was on screen *before* we even ran.
153+ capture_diag "pre-test" "00-pre-test"
154+
155+ # Stream full logcat to the workspace so we get an artifact even on success.
156+ adb logcat -c >/dev/null 2>&1 || true
157+ adb logcat > "$DIAG_DIR/10-logcat-full.txt" &
109158 LOGCAT_PID=$!
110-
111- # Run the specific test with better error handling
159+
160+ GRADLE_EXIT=0
112161 ./gradlew :integration-tests:connectedDebugAndroidTest \
113162 -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
163+ --stacktrace --no-daemon || GRADLE_EXIT=$?
164+
165+ # Stop logcat ASAP so post-test capture isn't entangled with it.
124166 kill $LOGCAT_PID 2>/dev/null || true
167+ wait $LOGCAT_PID 2>/dev/null || true
168+
169+ # Always capture post-test state — useful on success too (proves the test
170+ # really did exercise the SDK and surface campaigns).
171+ capture_diag "post-test" "20-post-test"
172+
173+ # Filtered logcat for fast triage; full logcat is also uploaded.
174+ grep -E 'IterableApi|IterableRequest|IterableInApp|IterableEmbedded|IntegrationMainActivity|BaseIntegrationTest|InAppMessageIntegrationTest|PushNotificationIntegrationTest|EmbeddedMessageIntegrationTest|DeepLinkIntegrationTest|FATAL|AndroidRuntime|ANR' \
175+ "$DIAG_DIR/10-logcat-full.txt" > "$DIAG_DIR/11-logcat-filtered.txt" 2>/dev/null || true
176+
177+ echo
178+ echo "=== diagnostics summary ==="
179+ ls -la "$DIAG_DIR"
180+ echo
181+ echo "Last 50 logcat lines (full file uploaded as artifact):"
182+ tail -50 "$DIAG_DIR/10-logcat-full.txt" || true
183+
184+ if [ "$GRADLE_EXIT" != "0" ]; then
185+ echo "::error::Test gradle task failed with exit code $GRADLE_EXIT — see e2e-diagnostics-api-${{ matrix.api-level }} artifact"
186+ exit "$GRADLE_EXIT"
187+ fi
125188 env :
126189 ITERABLE_API_KEY : ${{ secrets.BCIT_ITERABLE_API_KEY }}
127190 ITERABLE_SERVER_API_KEY : ${{ secrets.BCIT_ITERABLE_SERVER_API_KEY }}
128191 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"
192+
193+ - name : Upload E2E diagnostics
194+ if : always()
195+ uses : actions/upload-artifact@v4
196+ with :
197+ name : e2e-diagnostics-api-${{ matrix.api-level }}
198+ path : |
199+ integration-tests/build/diagnostics/
200+ integration-tests/build/reports/
201+ integration-tests/build/outputs/
202+ if-no-files-found : warn
203+ retention-days : 7
182204
183205 # test-summary:
184206 # name: Test Summary
0 commit comments