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

This commit is contained in:
Zack Pollard 2023-02-19 17:50:36 +00:00 committed by GitHub
parent 5ad4e5b614
commit 78a5fe2d37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 54 deletions

View File

@ -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

View File

@ -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();
}

View File

@ -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.");
} }
} }