Ver código fonte

test(app): fix integration test and improve reliability and speed (#1792)

Zack Pollard 2 anos atrás
pai
commit
78a5fe2d37

+ 48 - 12
.github/workflows/test.yml

@@ -86,8 +86,9 @@ jobs:
       - uses: actions/checkout@v3
       - uses: actions/setup-java@v3
         with:
-          distribution: 'adopt'
-          java-version: '11'
+          distribution: 'zulu'
+          java-version: '12.x'
+          cache: 'gradle'
       - name: Cache android SDK
         uses: actions/cache@v3
         id: android-sdk
@@ -96,24 +97,59 @@ jobs:
           path: |
             /usr/local/lib/android/
             ~/.android
+      - name: Cache Gradle
+        uses: actions/cache@v3
+        with:
+          path: |
+            ./mobile/build/
+            ./mobile/android/.gradle/
+          key: ${{ runner.os }}-flutter-${{ hashFiles('**/*.gradle*', 'pubspec.lock') }}
       - name: Setup Android SDK
         if: steps.android-sdk.outputs.cache-hit != 'true'
         uses: android-actions/setup-android@v2
-      - name: Setup Flutter SDK
-        uses: subosito/flutter-action@v2
+      - name: AVD cache
+        uses: actions/cache@v3
+        id: avd-cache
         with:
-          channel: 'stable'
-          flutter-version: '3.7.3'
-      - name: Run integration tests
+          path: |
+            ~/.android/avd/*
+            ~/.android/adb*
+          key: avd-29
+      - name: create AVD and generate snapshot for caching
+        if: steps.avd-cache.outputs.cache-hit != 'true'
         uses: reactivecircus/android-emulator-runner@v2.27.0
         with:
           working-directory: ./mobile
+          cores: 2
           api-level: 29
           arch: x86_64
           profile: pixel
           target: default
-          emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
-          disable-linux-hw-accel: false
-          script: |
-            flutter pub get
-            flutter test integration_test
+          force-avd-creation: false
+          emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+          disable-animations: false
+          script: echo "Generated AVD snapshot for caching."
+      - name: Setup Flutter SDK
+        uses: subosito/flutter-action@v2
+        with:
+          channel: 'stable'
+          flutter-version: '3.7.3'
+          cache: true
+      - name: Run integration tests
+        uses: Wandalen/wretry.action@master
+        with:
+          action: reactivecircus/android-emulator-runner@v2.27.0
+          with: |
+            working-directory: ./mobile
+            cores: 2
+            api-level: 29
+            arch: x86_64
+            profile: pixel
+            target: default
+            force-avd-creation: false
+            emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+            disable-animations: true
+            script: |
+              flutter pub get
+              flutter test integration_test
+          attempt_limit: 3

+ 16 - 1
mobile/integration_test/test_utils/general_helper.dart

@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:hive/hive.dart';
@@ -43,7 +45,6 @@ class ImmichTestHelper {
     // Load main Widget
     await tester.pumpWidget(app.getMainWidget(db));
     // Post run tasks
-    await tester.pumpAndSettle();
     await EasyLocalization.ensureInitialized();
   }
 }
@@ -62,3 +63,17 @@ void immichWidgetTest(
     semanticsEnabled: false,
   );
 }
+
+Future<void> pumpUntilFound(
+    WidgetTester tester,
+    Finder finder, {
+      Duration timeout = const Duration(seconds: 120),
+    }) async {
+  bool found = false;
+  final timer = Timer(timeout, () => throw TimeoutException("Pump until has timed out"));
+  while (found != true) {
+    await tester.pump();
+    found = tester.any(finder);
+  }
+  timer.cancel();
+}

+ 12 - 40
mobile/integration_test/test_utils/login_helper.dart

@@ -2,33 +2,20 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 
+import 'general_helper.dart';
+
 class ImmichTestLoginHelper {
   final WidgetTester tester;
 
   ImmichTestLoginHelper(this.tester);
 
-  Future<void> waitForLoginScreen({int timeoutSeconds = 20}) async {
-    for (var i = 0; i < timeoutSeconds; i++) {
-      // Search for "IMMICH" test in the app bar
-      final result = find.text("IMMICH");
-      if (tester.any(result)) {
-        // Wait 5s until everything settled
-        await tester.pump(const Duration(seconds: 5));
-        return;
-      }
-
-      // Wait 1s before trying again
-      await Future.delayed(const Duration(seconds: 1));
-    }
-
-    fail("Timeout while waiting for login screen");
+  Future<void> waitForLoginScreen() async {
+    await pumpUntilFound(tester, find.text("Login"));
   }
 
   Future<bool> acknowledgeNewServerVersion() async {
+    await pumpUntilFound(tester, find.text("Acknowledge"));
     final result = find.text("Acknowledge");
-    if (!tester.any(result)) {
-      return false;
-    }
 
     await tester.tap(result);
     await tester.pump();
@@ -43,17 +30,17 @@ class ImmichTestLoginHelper {
   }) async {
     final loginForms = find.byType(TextFormField);
 
-    await tester.pump(const Duration(milliseconds: 500));
     await tester.enterText(loginForms.at(0), email);
+    await tester.pump();
 
-    await tester.pump(const Duration(milliseconds: 500));
     await tester.enterText(loginForms.at(1), password);
+    await tester.pump();
 
-    await tester.pump(const Duration(milliseconds: 500));
     await tester.enterText(loginForms.at(2), server);
+    await tester.pump();
 
     await tester.testTextInput.receiveAction(TextInputAction.done);
-    await tester.pumpAndSettle();
+    await tester.pump();
   }
 
   Future<void> enterCredentialsOf(LoginCredentials credentials) async {
@@ -65,32 +52,17 @@ class ImmichTestLoginHelper {
   }
 
   Future<void> pressLoginButton() async {
+    await pumpUntilFound(tester, find.textContaining("login_form_button_text".tr()));
     final button = find.textContaining("login_form_button_text".tr());
     await tester.tap(button);
   }
 
   Future<void> assertLoginSuccess({int timeoutSeconds = 15}) async {
-    for (var i = 0; i < timeoutSeconds * 2; i++) {
-      if (tester.any(find.text("home_page_building_timeline".tr()))) {
-        return;
-      }
-
-      await tester.pump(const Duration(milliseconds: 500));
-    }
-
-    fail("Login failed.");
+    await pumpUntilFound(tester, find.text("home_page_building_timeline".tr()));
   }
 
   Future<void> assertLoginFailed({int timeoutSeconds = 15}) async {
-    for (var i = 0; i < timeoutSeconds * 2; i++) {
-      if (tester.any(find.text("login_form_failed_login".tr()))) {
-        return;
-      }
-
-      await tester.pump(const Duration(milliseconds: 500));
-    }
-
-    fail("Timeout.");
+      await pumpUntilFound(tester, find.text("login_form_failed_login".tr()));
   }
 }