You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	test(app): fix integration test and improve reliability and speed (#1792)
This commit is contained in:
		
							
								
								
									
										62
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										62
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -86,8 +86,9 @@ jobs: | |||||||
|       - uses: actions/checkout@v3 |       - uses: actions/checkout@v3 | ||||||
|       - uses: actions/setup-java@v3 |       - uses: actions/setup-java@v3 | ||||||
|         with: |         with: | ||||||
|           distribution: 'adopt' |           distribution: 'zulu' | ||||||
|           java-version: '11' |           java-version: '12.x' | ||||||
|  |           cache: 'gradle' | ||||||
|       - name: Cache android SDK |       - name: Cache android SDK | ||||||
|         uses: actions/cache@v3 |         uses: actions/cache@v3 | ||||||
|         id: android-sdk |         id: android-sdk | ||||||
| @@ -96,24 +97,59 @@ jobs: | |||||||
|           path: | |           path: | | ||||||
|             /usr/local/lib/android/ |             /usr/local/lib/android/ | ||||||
|             ~/.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 |       - name: Setup Android SDK | ||||||
|         if: steps.android-sdk.outputs.cache-hit != 'true' |         if: steps.android-sdk.outputs.cache-hit != 'true' | ||||||
|         uses: android-actions/setup-android@v2 |         uses: android-actions/setup-android@v2 | ||||||
|  |       - name: AVD cache | ||||||
|  |         uses: actions/cache@v3 | ||||||
|  |         id: avd-cache | ||||||
|  |         with: | ||||||
|  |           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 | ||||||
|  |           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 |       - name: Setup Flutter SDK | ||||||
|         uses: subosito/flutter-action@v2 |         uses: subosito/flutter-action@v2 | ||||||
|         with: |         with: | ||||||
|           channel: 'stable' |           channel: 'stable' | ||||||
|           flutter-version: '3.7.3' |           flutter-version: '3.7.3' | ||||||
|  |           cache: true | ||||||
|       - name: Run integration tests |       - name: Run integration tests | ||||||
|         uses: reactivecircus/android-emulator-runner@v2.27.0 |         uses: Wandalen/wretry.action@master | ||||||
|         with: |         with: | ||||||
|           working-directory: ./mobile |           action: reactivecircus/android-emulator-runner@v2.27.0 | ||||||
|           api-level: 29 |           with: | | ||||||
|           arch: x86_64 |             working-directory: ./mobile | ||||||
|           profile: pixel |             cores: 2 | ||||||
|           target: default |             api-level: 29 | ||||||
|           emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim |             arch: x86_64 | ||||||
|           disable-linux-hw-accel: false |             profile: pixel | ||||||
|           script: | |             target: default | ||||||
|             flutter pub get |             force-avd-creation: false | ||||||
|             flutter test integration_test |             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 | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter_test/flutter_test.dart'; | import 'package:flutter_test/flutter_test.dart'; | ||||||
| import 'package:hive/hive.dart'; | import 'package:hive/hive.dart'; | ||||||
| @@ -43,7 +45,6 @@ class ImmichTestHelper { | |||||||
|     // Load main Widget |     // Load main Widget | ||||||
|     await tester.pumpWidget(app.getMainWidget(db)); |     await tester.pumpWidget(app.getMainWidget(db)); | ||||||
|     // Post run tasks |     // Post run tasks | ||||||
|     await tester.pumpAndSettle(); |  | ||||||
|     await EasyLocalization.ensureInitialized(); |     await EasyLocalization.ensureInitialized(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -62,3 +63,17 @@ void immichWidgetTest( | |||||||
|     semanticsEnabled: false, |     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(); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -2,33 +2,20 @@ import 'package:easy_localization/easy_localization.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_test/flutter_test.dart'; | import 'package:flutter_test/flutter_test.dart'; | ||||||
|  |  | ||||||
|  | import 'general_helper.dart'; | ||||||
|  |  | ||||||
| class ImmichTestLoginHelper { | class ImmichTestLoginHelper { | ||||||
|   final WidgetTester tester; |   final WidgetTester tester; | ||||||
|  |  | ||||||
|   ImmichTestLoginHelper(this.tester); |   ImmichTestLoginHelper(this.tester); | ||||||
|  |  | ||||||
|   Future<void> waitForLoginScreen({int timeoutSeconds = 20}) async { |   Future<void> waitForLoginScreen() async { | ||||||
|     for (var i = 0; i < timeoutSeconds; i++) { |     await pumpUntilFound(tester, find.text("Login")); | ||||||
|       // 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<bool> acknowledgeNewServerVersion() async { |   Future<bool> acknowledgeNewServerVersion() async { | ||||||
|  |     await pumpUntilFound(tester, find.text("Acknowledge")); | ||||||
|     final result = find.text("Acknowledge"); |     final result = find.text("Acknowledge"); | ||||||
|     if (!tester.any(result)) { |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await tester.tap(result); |     await tester.tap(result); | ||||||
|     await tester.pump(); |     await tester.pump(); | ||||||
| @@ -43,17 +30,17 @@ class ImmichTestLoginHelper { | |||||||
|   }) async { |   }) async { | ||||||
|     final loginForms = find.byType(TextFormField); |     final loginForms = find.byType(TextFormField); | ||||||
|  |  | ||||||
|     await tester.pump(const Duration(milliseconds: 500)); |  | ||||||
|     await tester.enterText(loginForms.at(0), email); |     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.enterText(loginForms.at(1), password); | ||||||
|  |     await tester.pump(); | ||||||
|  |  | ||||||
|     await tester.pump(const Duration(milliseconds: 500)); |  | ||||||
|     await tester.enterText(loginForms.at(2), server); |     await tester.enterText(loginForms.at(2), server); | ||||||
|  |     await tester.pump(); | ||||||
|  |  | ||||||
|     await tester.testTextInput.receiveAction(TextInputAction.done); |     await tester.testTextInput.receiveAction(TextInputAction.done); | ||||||
|     await tester.pumpAndSettle(); |     await tester.pump(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> enterCredentialsOf(LoginCredentials credentials) async { |   Future<void> enterCredentialsOf(LoginCredentials credentials) async { | ||||||
| @@ -65,32 +52,17 @@ class ImmichTestLoginHelper { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> pressLoginButton() async { |   Future<void> pressLoginButton() async { | ||||||
|  |     await pumpUntilFound(tester, find.textContaining("login_form_button_text".tr())); | ||||||
|     final button = find.textContaining("login_form_button_text".tr()); |     final button = find.textContaining("login_form_button_text".tr()); | ||||||
|     await tester.tap(button); |     await tester.tap(button); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> assertLoginSuccess({int timeoutSeconds = 15}) async { |   Future<void> assertLoginSuccess({int timeoutSeconds = 15}) async { | ||||||
|     for (var i = 0; i < timeoutSeconds * 2; i++) { |     await pumpUntilFound(tester, find.text("home_page_building_timeline".tr())); | ||||||
|       if (tester.any(find.text("home_page_building_timeline".tr()))) { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       await tester.pump(const Duration(milliseconds: 500)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fail("Login failed."); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> assertLoginFailed({int timeoutSeconds = 15}) async { |   Future<void> assertLoginFailed({int timeoutSeconds = 15}) async { | ||||||
|     for (var i = 0; i < timeoutSeconds * 2; i++) { |       await pumpUntilFound(tester, find.text("login_form_failed_login".tr())); | ||||||
|       if (tester.any(find.text("login_form_failed_login".tr()))) { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       await tester.pump(const Duration(milliseconds: 500)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fail("Timeout."); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user