diff --git a/Taskfile.yml b/Taskfile.yml index ce691f8..fe70527 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -147,9 +147,12 @@ tasks: deploy-android: desc: Build release APK and upload via scp to $ANDROID_APK_SCP_USER@$ANDROID_APK_SCP_HOST:$ANDROID_APK_SCP_PATH - deps: [check, integration-android, build-android] + deps: [check, build-android] dotenv: [".env"] cmds: + # integration-android runs after check (not in parallel) so the two E2E + # test suites don't compete for CPU and slow the Android emulator. + - task: integration-android - scripts/deploy_android.sh run: diff --git a/done.md b/done.md index c5aa4ac..5f57573 100644 --- a/done.md +++ b/done.md @@ -6,6 +6,30 @@ Tasks get moved from next.md to done.md ## Tasks +## Android E2E test verifies APK before deploy + +`task deploy-android` now runs `integration-android` (the full Android E2E test) before +uploading the APK. If the app crashes on start or any E2E step fails, the deploy is skipped. + +Key fixes to make the Android E2E test reliable: + +- `Taskfile.yml`: moved `integration-android` to a sequential `cmds` step after `check`, + so the two E2E suites don't compete for CPU and slow the emulator. +- `stalwart-dev/integration_android_test.sh`: wrapped `force-stop`/`pm clear`/`uninstall` + in a `pm list packages | grep -qF` check — only runs when the package is installed, so + any real failure is surfaced instead of silently suppressed. +- `integration_test/app_e2e_test.dart`: + - `pumpUntil` uses `pump(300ms)` instead of `pumpAndSettle()` so a concurrently + running spinner never blocks settling. + - `accountConnectionStatusProvider` overridden to complete immediately, eliminating the + `CircularProgressIndicator` in `_AccountTile` that caused `pumpAndSettle` to deadlock. + - Search section: `FocusManager.instance.primaryFocus?.unfocus()` dismisses the Android + IME keyboard before polling for results — without this, the soft keyboard reduces + `viewInsets.bottom` to near-zero and `ListView.builder` renders 0 items even though + search results are present. + +--- + ## Override accountConnectionStatusProvider in E2E test (fix Android pumpAndSettle deadlock) `accountConnectionStatusProvider` overridden in `integration_test/app_e2e_test.dart` so diff --git a/integration_test/app_e2e_test.dart b/integration_test/app_e2e_test.dart index 6dadd9e..e8fc9d9 100644 --- a/integration_test/app_e2e_test.dart +++ b/integration_test/app_e2e_test.dart @@ -94,7 +94,11 @@ Future pumpUntil( } await tester.pump(interval); } - await tester.pumpAndSettle(); + // pump(300ms) instead of pumpAndSettle(): a continuously-running animation + // (e.g. a spinner in a concurrent test under CPU load) would prevent + // pumpAndSettle() from ever settling. One bounded pump is enough for any + // route transition to complete. + await tester.pump(const Duration(milliseconds: 300)); } void main() { @@ -151,15 +155,12 @@ void main() { accountConnectionStatusProvider.overrideWith((ref, _) async {}), ], ); - await tester.pumpAndSettle(); + await pumpUntil(tester, find.text('No accounts yet.')); _log('app settled'); // ── Add account ──────────────────────────────────────────────────────── - expect(find.text('No accounts yet.'), findsOneWidget); - await tester.tap(find.widgetWithIcon(FloatingActionButton, Icons.add)); - await tester.pumpAndSettle(); - expect(find.text('Add account'), findsOneWidget); + await pumpUntil(tester, find.text('Add account')); // Step 1 — enter email and continue. await tester.enterText( @@ -302,18 +303,24 @@ void main() { // ── Search ───────────────────────────────────────────────────────────── await tester.tap(find.byIcon(Icons.search)); - await tester.pumpAndSettle(); + await pumpUntil(tester, find.byType(TextField)); // Search by the 'E2E-' prefix — should match the message we just sent. _log('search'); await tester.enterText(find.byType(TextField), 'E2E-'); - // Allow the 300ms debounce timer to fire before polling for results. - await Future.delayed(const Duration(milliseconds: 400)); + // Dismiss the IME keyboard so the results ListView.builder gets full + // body height. On Android the soft keyboard reduces viewInsets.bottom, + // leaving near-zero height for the body — ListView.builder then renders + // 0 items and find.text() always fails even when results are present. + FocusManager.instance.primaryFocus?.unfocus(); + // Future.delayed advances real time so the 300ms debounce Timer fires. + await Future.delayed(const Duration(milliseconds: 500)); await tester.pump(); + await pumpUntil( tester, find.text(subject), - timeout: const Duration(seconds: 20), + timeout: const Duration(seconds: 30), ); _log('search done'); diff --git a/next.md b/next.md index 517800c..1ce6351 100644 --- a/next.md +++ b/next.md @@ -18,11 +18,7 @@ Then commit. ## Tasks -When I download and install the apk, then the app starts, but closes again immediatly. - -I want an automated test, which ensures the apk is functional. - -If that test fails, then the upload should not be done. +make `task deploy-android` faster. More concurrent tasks? Caching? --- diff --git a/stalwart-dev/integration_android_test.sh b/stalwart-dev/integration_android_test.sh index 1e772dd..a07ffbf 100755 --- a/stalwart-dev/integration_android_test.sh +++ b/stalwart-dev/integration_android_test.sh @@ -119,10 +119,12 @@ cd "$ROOT" "$ADB" -s "$EMULATOR_ID" reverse tcp:1025 tcp:"$STALWART_SMTP_PORT" # Clear any leftover app state from previous runs (stale DB, cached APK process). -# Force-stop first so adb uninstall doesn't fail with DELETE_FAILED_INTERNAL_ERROR. -"$ADB" -s "$EMULATOR_ID" shell am force-stop com.example.sharedinbox 2>/dev/null || true -"$ADB" -s "$EMULATOR_ID" shell pm clear com.example.sharedinbox 2>/dev/null || true -"$ADB" -s "$EMULATOR_ID" uninstall com.example.sharedinbox 2>/dev/null || true +# Only run if the package is installed — that way any failure is a real error. +if "$ADB" -s "$EMULATOR_ID" shell pm list packages | grep -qF "com.example.sharedinbox"; then + "$ADB" -s "$EMULATOR_ID" shell am force-stop com.example.sharedinbox + "$ADB" -s "$EMULATOR_ID" shell pm clear com.example.sharedinbox + "$ADB" -s "$EMULATOR_ID" uninstall com.example.sharedinbox +fi ts "flutter test start" fvm flutter test integration_test/ -d "$EMULATOR_ID" | grep -Ev "was tree-shaken|Tree-shaking can be disabled"