mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(mobile): Add integration tests (#1359)
This commit is contained in:
		
							
								
								
									
										44
									
								
								.github/workflows/test_mobile.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								.github/workflows/test_mobile.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
name: Flutter Integration Tests
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ "main" ]
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: macos-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          distribution: 'adopt'
 | 
			
		||||
          java-version: '11'
 | 
			
		||||
      - name: Cache android SDK
 | 
			
		||||
        uses: actions/cache@v2
 | 
			
		||||
        id: android-sdk
 | 
			
		||||
        with:
 | 
			
		||||
          key: android-sdk
 | 
			
		||||
          path: |
 | 
			
		||||
            /usr/local/lib/android/
 | 
			
		||||
            ~/.android
 | 
			
		||||
      - 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@v1
 | 
			
		||||
        with:
 | 
			
		||||
          channel: 'stable'
 | 
			
		||||
      - name: Run integration tests
 | 
			
		||||
        uses: reactivecircus/android-emulator-runner@v2.27.0
 | 
			
		||||
        with:
 | 
			
		||||
          working-directory: ./mobile
 | 
			
		||||
          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
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -6,3 +6,5 @@
 | 
			
		||||
docker/upload
 | 
			
		||||
uploads
 | 
			
		||||
coverage
 | 
			
		||||
 | 
			
		||||
mobile/gradle.properties
 | 
			
		||||
 
 | 
			
		||||
@@ -114,8 +114,9 @@
 | 
			
		||||
  "library_page_new_album": "New album",
 | 
			
		||||
  "login_form_button_text": "Login",
 | 
			
		||||
  "login_form_email_hint": "youremail@email.com",
 | 
			
		||||
  "login_form_endpoint_hint": "http://your-server-ip:port/",
 | 
			
		||||
  "login_form_endpoint_hint": "https://your-server-ip:port/",
 | 
			
		||||
  "login_form_endpoint_url": "Server Endpoint URL",
 | 
			
		||||
  "login_form_err_http_insecure": "Http is unencrypted and therefore not recommended. Please use https unless you are using Immich exclusively in your home network.",
 | 
			
		||||
  "login_form_err_invalid_email": "Invalid Email",
 | 
			
		||||
  "login_form_err_invalid_url": "Invalid URL",
 | 
			
		||||
  "login_form_err_leading_whitespace": "Leading whitespace",
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
 | 
			
		||||
import '../test_utils/general_helper.dart';
 | 
			
		||||
import '../test_utils/login_helper.dart';
 | 
			
		||||
 | 
			
		||||
void main() async {
 | 
			
		||||
  await ImmichTestHelper.initialize();
 | 
			
		||||
 | 
			
		||||
  group("Login input validation test", () {
 | 
			
		||||
    immichWidgetTest("Test http warning message", (tester) async {
 | 
			
		||||
      await ImmichTestLoginHelper.waitForLoginScreen(tester);
 | 
			
		||||
      await ImmichTestLoginHelper.acknowledgeNewServerVersion(tester);
 | 
			
		||||
 | 
			
		||||
      // Test https URL
 | 
			
		||||
      await ImmichTestLoginHelper.enterLoginCredentials(
 | 
			
		||||
        tester,
 | 
			
		||||
        server: "https://demo.immich.app/api",
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      await tester.pump(const Duration(milliseconds: 300));
 | 
			
		||||
 | 
			
		||||
      expect(find.text("login_form_err_http_insecure".tr()), findsNothing);
 | 
			
		||||
 | 
			
		||||
      // Test http URL
 | 
			
		||||
      await ImmichTestLoginHelper.enterLoginCredentials(
 | 
			
		||||
        tester,
 | 
			
		||||
        server: "http://demo.immich.app/api",
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      await tester.pump(const Duration(milliseconds: 300));
 | 
			
		||||
 | 
			
		||||
      expect(find.text("login_form_err_http_insecure".tr()), findsOneWidget);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								mobile/integration_test/test_utils/general_helper.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								mobile/integration_test/test_utils/general_helper.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
import 'package:hive/hive.dart';
 | 
			
		||||
import 'package:immich_mobile/main.dart';
 | 
			
		||||
import 'package:integration_test/integration_test.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:immich_mobile/main.dart' as app;
 | 
			
		||||
 | 
			
		||||
class ImmichTestHelper {
 | 
			
		||||
 | 
			
		||||
  static Future<IntegrationTestWidgetsFlutterBinding> initialize() async {
 | 
			
		||||
    final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 | 
			
		||||
    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
 | 
			
		||||
 | 
			
		||||
    // Load hive, localization...
 | 
			
		||||
    await app.initApp();
 | 
			
		||||
 | 
			
		||||
    return binding;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Future<void> loadApp(WidgetTester tester) async {
 | 
			
		||||
    // Clear all data from Hive
 | 
			
		||||
    await Hive.deleteFromDisk();
 | 
			
		||||
    await app.openBoxes();
 | 
			
		||||
    // Load main Widget
 | 
			
		||||
    await tester.pumpWidget(app.getMainWidget());
 | 
			
		||||
    // Post run tasks
 | 
			
		||||
    await tester.pumpAndSettle();
 | 
			
		||||
    await EasyLocalization.ensureInitialized();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void immichWidgetTest(String description, Future<void> Function(WidgetTester) test) {
 | 
			
		||||
  testWidgets(description, (widgetTester) async {
 | 
			
		||||
    await ImmichTestHelper.loadApp(widgetTester);
 | 
			
		||||
    await test(widgetTester);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								mobile/integration_test/test_utils/login_helper.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								mobile/integration_test/test_utils/login_helper.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_test/flutter_test.dart';
 | 
			
		||||
 | 
			
		||||
class ImmichTestLoginHelper {
 | 
			
		||||
  static Future<void> waitForLoginScreen(WidgetTester tester,
 | 
			
		||||
      {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");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Future<bool> acknowledgeNewServerVersion(WidgetTester tester) async {
 | 
			
		||||
    final result = find.text("Acknowledge");
 | 
			
		||||
    if (!tester.any(result)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await tester.tap(result);
 | 
			
		||||
    await tester.pump();
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Future<void> enterLoginCredentials(
 | 
			
		||||
    WidgetTester tester, {
 | 
			
		||||
    String server = "",
 | 
			
		||||
    String email = "",
 | 
			
		||||
    String password = "",
 | 
			
		||||
  }) async {
 | 
			
		||||
    final loginForms = find.byType(TextFormField);
 | 
			
		||||
 | 
			
		||||
    await tester.pump(const Duration(milliseconds: 500));
 | 
			
		||||
    await tester.enterText(loginForms.at(0), email);
 | 
			
		||||
 | 
			
		||||
    await tester.pump(const Duration(milliseconds: 500));
 | 
			
		||||
    await tester.enterText(loginForms.at(1), password);
 | 
			
		||||
 | 
			
		||||
    await tester.pump(const Duration(milliseconds: 500));
 | 
			
		||||
    await tester.enterText(loginForms.at(2), server);
 | 
			
		||||
 | 
			
		||||
    await tester.pump(const Duration(milliseconds: 500));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -29,12 +29,11 @@ import 'package:immich_mobile/utils/immich_app_theme.dart';
 | 
			
		||||
import 'constants/hive_box.dart';
 | 
			
		||||
 | 
			
		||||
void main() async {
 | 
			
		||||
  await Hive.initFlutter();
 | 
			
		||||
  Hive.registerAdapter(HiveSavedLoginInfoAdapter());
 | 
			
		||||
  Hive.registerAdapter(HiveBackupAlbumsAdapter());
 | 
			
		||||
  Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
 | 
			
		||||
  Hive.registerAdapter(ImmichLoggerMessageAdapter());
 | 
			
		||||
  await initApp();
 | 
			
		||||
  runApp(getMainWidget());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Future<void> openBoxes() async {
 | 
			
		||||
  await Future.wait([
 | 
			
		||||
    Hive.openBox<ImmichLoggerMessage>(immichLoggerBox),
 | 
			
		||||
    Hive.openBox(userInfoBox),
 | 
			
		||||
@@ -47,6 +46,16 @@ void main() async {
 | 
			
		||||
    if (!Platform.isAndroid) Hive.openBox(backgroundBackupInfoBox),
 | 
			
		||||
    EasyLocalization.ensureInitialized(),
 | 
			
		||||
  ]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Future<void> initApp() async {
 | 
			
		||||
  await Hive.initFlutter();
 | 
			
		||||
  Hive.registerAdapter(HiveSavedLoginInfoAdapter());
 | 
			
		||||
  Hive.registerAdapter(HiveBackupAlbumsAdapter());
 | 
			
		||||
  Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
 | 
			
		||||
  Hive.registerAdapter(ImmichLoggerMessageAdapter());
 | 
			
		||||
 | 
			
		||||
  await openBoxes();
 | 
			
		||||
 | 
			
		||||
  SystemChrome.setSystemUIOverlayStyle(
 | 
			
		||||
    const SystemUiOverlayStyle(
 | 
			
		||||
@@ -65,15 +74,15 @@ void main() async {
 | 
			
		||||
 | 
			
		||||
  // Initialize Immich Logger Service
 | 
			
		||||
  ImmichLogger().init();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  runApp(
 | 
			
		||||
    EasyLocalization(
 | 
			
		||||
Widget getMainWidget() {
 | 
			
		||||
  return EasyLocalization(
 | 
			
		||||
    supportedLocales: locales,
 | 
			
		||||
    path: translationsPath,
 | 
			
		||||
    useFallbackTranslations: true,
 | 
			
		||||
    fallbackLocale: locales.first,
 | 
			
		||||
    child: const ProviderScope(child: ImmichApp()),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -223,6 +223,11 @@ class ServerEndpointInput extends StatelessWidget {
 | 
			
		||||
        parsedUrl.host.isEmpty) {
 | 
			
		||||
      return 'login_form_err_invalid_url'.tr();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!parsedUrl.scheme.startsWith("https")) {
 | 
			
		||||
      return 'login_form_err_http_insecure'.tr();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -234,6 +239,7 @@ class ServerEndpointInput extends StatelessWidget {
 | 
			
		||||
        labelText: 'login_form_endpoint_url'.tr(),
 | 
			
		||||
        border: const OutlineInputBorder(),
 | 
			
		||||
        hintText: 'login_form_endpoint_hint'.tr(),
 | 
			
		||||
        errorMaxLines: 4
 | 
			
		||||
      ),
 | 
			
		||||
      validator: _validateInput,
 | 
			
		||||
      autovalidateMode: AutovalidateMode.always,
 | 
			
		||||
 
 | 
			
		||||
@@ -307,6 +307,11 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.4.0"
 | 
			
		||||
  flutter_driver:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  flutter_hooks:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -392,6 +397,11 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.2"
 | 
			
		||||
  fuchsia_remote_debug_protocol:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  glob:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -504,6 +514,11 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.5.0"
 | 
			
		||||
  integration_test:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  intl:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -1041,6 +1056,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.1.1"
 | 
			
		||||
  sync_http:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: sync_http
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.3.1"
 | 
			
		||||
  synchronized:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -1202,6 +1224,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.10"
 | 
			
		||||
  vm_service:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: vm_service
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "9.0.0"
 | 
			
		||||
  wakelock:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -1251,6 +1280,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.2.0"
 | 
			
		||||
  webdriver:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: webdriver
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.0"
 | 
			
		||||
  win32:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,8 @@ dev_dependencies:
 | 
			
		||||
  build_runner: ^2.2.1
 | 
			
		||||
  auto_route_generator: ^5.0.2
 | 
			
		||||
  flutter_launcher_icons: "^0.9.2"
 | 
			
		||||
  integration_test:
 | 
			
		||||
    sdk: flutter
 | 
			
		||||
 | 
			
		||||
flutter:
 | 
			
		||||
  uses-material-design: true
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user