mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
feat(mobile): Add integration tests (#1359)
This commit is contained in:
parent
e5d798581c
commit
f4c90426a5
10 changed files with 246 additions and 15 deletions
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
|
docker/upload
|
||||||
uploads
|
uploads
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
|
mobile/gradle.properties
|
||||||
|
|
|
@ -114,8 +114,9 @@
|
||||||
"library_page_new_album": "New album",
|
"library_page_new_album": "New album",
|
||||||
"login_form_button_text": "Login",
|
"login_form_button_text": "Login",
|
||||||
"login_form_email_hint": "youremail@email.com",
|
"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_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_email": "Invalid Email",
|
||||||
"login_form_err_invalid_url": "Invalid URL",
|
"login_form_err_invalid_url": "Invalid URL",
|
||||||
"login_form_err_leading_whitespace": "Leading whitespace",
|
"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';
|
import 'constants/hive_box.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await Hive.initFlutter();
|
await initApp();
|
||||||
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
runApp(getMainWidget());
|
||||||
Hive.registerAdapter(HiveBackupAlbumsAdapter());
|
}
|
||||||
Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
|
|
||||||
Hive.registerAdapter(ImmichLoggerMessageAdapter());
|
|
||||||
|
|
||||||
|
Future<void> openBoxes() async {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
Hive.openBox<ImmichLoggerMessage>(immichLoggerBox),
|
Hive.openBox<ImmichLoggerMessage>(immichLoggerBox),
|
||||||
Hive.openBox(userInfoBox),
|
Hive.openBox(userInfoBox),
|
||||||
|
@ -47,6 +46,16 @@ void main() async {
|
||||||
if (!Platform.isAndroid) Hive.openBox(backgroundBackupInfoBox),
|
if (!Platform.isAndroid) Hive.openBox(backgroundBackupInfoBox),
|
||||||
EasyLocalization.ensureInitialized(),
|
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(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
const SystemUiOverlayStyle(
|
const SystemUiOverlayStyle(
|
||||||
|
@ -65,15 +74,15 @@ void main() async {
|
||||||
|
|
||||||
// Initialize Immich Logger Service
|
// Initialize Immich Logger Service
|
||||||
ImmichLogger().init();
|
ImmichLogger().init();
|
||||||
|
}
|
||||||
|
|
||||||
runApp(
|
Widget getMainWidget() {
|
||||||
EasyLocalization(
|
return EasyLocalization(
|
||||||
supportedLocales: locales,
|
supportedLocales: locales,
|
||||||
path: translationsPath,
|
path: translationsPath,
|
||||||
useFallbackTranslations: true,
|
useFallbackTranslations: true,
|
||||||
fallbackLocale: locales.first,
|
fallbackLocale: locales.first,
|
||||||
child: const ProviderScope(child: ImmichApp()),
|
child: const ProviderScope(child: ImmichApp()),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -223,6 +223,11 @@ class ServerEndpointInput extends StatelessWidget {
|
||||||
parsedUrl.host.isEmpty) {
|
parsedUrl.host.isEmpty) {
|
||||||
return 'login_form_err_invalid_url'.tr();
|
return 'login_form_err_invalid_url'.tr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parsedUrl.scheme.startsWith("https")) {
|
||||||
|
return 'login_form_err_http_insecure'.tr();
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +239,7 @@ class ServerEndpointInput extends StatelessWidget {
|
||||||
labelText: 'login_form_endpoint_url'.tr(),
|
labelText: 'login_form_endpoint_url'.tr(),
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
hintText: 'login_form_endpoint_hint'.tr(),
|
hintText: 'login_form_endpoint_hint'.tr(),
|
||||||
|
errorMaxLines: 4
|
||||||
),
|
),
|
||||||
validator: _validateInput,
|
validator: _validateInput,
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
|
|
|
@ -307,6 +307,11 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.0"
|
||||||
|
flutter_driver:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_hooks:
|
flutter_hooks:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -392,6 +397,11 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
fuchsia_remote_debug_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -504,6 +514,11 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.0"
|
||||||
|
integration_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1041,6 +1056,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
sync_http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sync_http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.1"
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1202,6 +1224,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.10"
|
version: "2.0.10"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "9.0.0"
|
||||||
wakelock:
|
wakelock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1251,6 +1280,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
webdriver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webdriver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -57,6 +57,8 @@ dev_dependencies:
|
||||||
build_runner: ^2.2.1
|
build_runner: ^2.2.1
|
||||||
auto_route_generator: ^5.0.2
|
auto_route_generator: ^5.0.2
|
||||||
flutter_launcher_icons: "^0.9.2"
|
flutter_launcher_icons: "^0.9.2"
|
||||||
|
integration_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
Loading…
Reference in a new issue