mirror of
https://github.com/immich-app/immich.git
synced 2024-11-28 09:33:27 +02:00
feat(mobile): Add integration tests (#1359)
This commit is contained in:
parent
e5d798581c
commit
f4c90426a5
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(
|
||||
supportedLocales: locales,
|
||||
path: translationsPath,
|
||||
useFallbackTranslations: true,
|
||||
fallbackLocale: locales.first,
|
||||
child: const ProviderScope(child: ImmichApp()),
|
||||
),
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user