diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart new file mode 100644 index 000000000..d2031e929 --- /dev/null +++ b/integration_test/app_test.dart @@ -0,0 +1,99 @@ +import "package:flutter/material.dart"; +import "package:flutter_test/flutter_test.dart"; +import "package:integration_test/integration_test.dart"; +import 'package:photos/main.dart' as app; +import "package:scrollable_positioned_list/scrollable_positioned_list.dart"; + +void main() { + group("App test", () { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + testWidgets("Demo test", (tester) async { + app.main(); + try { + await tester.pumpAndSettle(const Duration(seconds: 5)); + + await dismissUpdateAppDialog(tester); + + //Click the sign in button on the landing page + final signInButton = find.byKey(const ValueKey("signInButton")); + await tester.tap(signInButton); + await tester.pumpAndSettle(); + + //Enter the email address and click the login button + final emailInputField = find.byKey(const ValueKey("emailInputField")); + final logInButton = find.byKey(const ValueKey("logInButton")); + await tester.enterText(emailInputField, "enter email here"); + await tester.pumpAndSettle(const Duration(seconds: 3)); + await findAndTapFAB(tester, logInButton); + + //Enter OTT and click the verify button + final ottVerificationInputField = + find.byKey(const ValueKey("ottVerificationInputField")); + final verifyOttButton = find.byKey(const ValueKey("verifyOttButton")); + await tester.tap(ottVerificationInputField); + await tester.pumpAndSettle(); + await tester.enterText(ottVerificationInputField, "enter otp here"); + await tester.pumpAndSettle(); + await findAndTapFAB(tester, verifyOttButton); + + //Enter password and click the verify button + final passwordInputField = + find.byKey(const ValueKey("passwordInputField")); + final verifyPasswordButton = + find.byKey(const ValueKey("verifyPasswordButton")); + await tester.enterText(passwordInputField, "ente password here"); + await tester.pumpAndSettle(); + await findAndTapFAB(tester, verifyPasswordButton); + + await tester.pumpAndSettle(const Duration(seconds: 2)); + await dismissUpdateAppDialog(tester); + + //Grant permission to access photos + final grantPermissionButton = + find.byKey(const ValueKey("grantPermissionButton")); + await tester.tap(grantPermissionButton); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + //Manually grant permission to access photos within 3 seconds + await tester.pumpAndSettle(const Duration(seconds: 3)); + + //Skip backup + final skipBackupButton = find.byKey(const ValueKey("skipBackupButton")); + await tester.tap(skipBackupButton); + await tester.pumpAndSettle(const Duration(seconds: 3)); + + //scroll gallery + final scrollablePositionedList = find.byType(ScrollablePositionedList); + await tester.fling( + scrollablePositionedList, + const Offset(0, -4000), + 4000, + ); + await tester.pumpAndSettle(); + await tester.fling( + scrollablePositionedList, + const Offset(0, 4000), + 4000, + ); + await tester.pumpAndSettle(); + } catch (e) { + print("\n$e\n"); + } + }); + }); +} + +Future findAndTapFAB(WidgetTester tester, Finder finder) async { + final RenderBox box = tester.renderObject(finder); + final Offset desiredOffset = Offset(box.size.width - 10, box.size.height / 2); + // Calculate the global position of the desired offset within the widget. + final Offset globalPosition = box.localToGlobal(desiredOffset); + await tester.tapAt(globalPosition); + await tester.pumpAndSettle(const Duration(seconds: 3)); +} + +Future dismissUpdateAppDialog(WidgetTester tester) async { + await tester.tapAt(const Offset(0, 0)); + await tester.pumpAndSettle(); +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6522f5705..80cf4bc4d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -99,6 +99,8 @@ PODS: - in_app_purchase_storekit (0.0.1): - Flutter - FlutterMacOS + - integration_test (0.0.1): + - Flutter - libwebp (1.2.4): - libwebp/demux (= 1.2.4) - libwebp/mux (= 1.2.4) @@ -195,6 +197,7 @@ DEPENDENCIES: - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - image_editor_common (from `.symlinks/plugins/image_editor_common/ios`) - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - media_extension (from `.symlinks/plugins/media_extension/ios`) - motionphoto (from `.symlinks/plugins/motionphoto/ios`) @@ -276,6 +279,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_editor_common/ios" in_app_purchase_storekit: :path: ".symlinks/plugins/in_app_purchase_storekit/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" local_auth_ios: :path: ".symlinks/plugins/local_auth_ios/ios" media_extension: @@ -345,6 +350,7 @@ SPEC CHECKSUMS: GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 in_app_purchase_storekit: 6b297e2b5eab9fa3251a492d57301722e4132a71 + integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605 Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9cb678dfa..3ced1bd66 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -289,6 +289,7 @@ "${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework", "${BUILT_PRODUCTS_DIR}/image_editor_common/image_editor_common.framework", "${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework", + "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", "${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework", "${BUILT_PRODUCTS_DIR}/local_auth_ios/local_auth_ios.framework", "${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework", @@ -345,6 +346,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor_common.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework", diff --git a/lib/ui/account/login_page.dart b/lib/ui/account/login_page.dart index c0dec1868..6f84b602d 100644 --- a/lib/ui/account/login_page.dart +++ b/lib/ui/account/login_page.dart @@ -53,6 +53,7 @@ class _LoginPageState extends State { ), body: _getBody(), floatingActionButton: DynamicFAB( + key: const ValueKey("logInButton"), isKeypadOpen: isKeypadOpen, isFormValid: _emailIsValid, buttonText: S.of(context).logInLabel, @@ -87,6 +88,7 @@ class _LoginPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: TextFormField( + key: const ValueKey("emailInputField"), autofillHints: const [AutofillHints.email], decoration: InputDecoration( fillColor: _emailInputFieldColor, @@ -186,9 +188,9 @@ class _LoginPageState extends State { }, ), ), - Expanded( + const Expanded( flex: 1, - child: Container(), + child: SizedBox.shrink(), ) ], ), diff --git a/lib/ui/account/ott_verification_page.dart b/lib/ui/account/ott_verification_page.dart index 6f2915eeb..f13641121 100644 --- a/lib/ui/account/ott_verification_page.dart +++ b/lib/ui/account/ott_verification_page.dart @@ -64,6 +64,7 @@ class _OTTVerificationPageState extends State { ), body: _getBody(), floatingActionButton: DynamicFAB( + key: const ValueKey("verifyOttButton"), isKeypadOpen: isKeypadOpen, isFormValid: _verificationCodeController.text.isNotEmpty, buttonText: S.of(context).verify, @@ -146,6 +147,7 @@ class _OTTVerificationPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), child: TextFormField( + key: const ValueKey("ottVerificationInputField"), style: Theme.of(context).textTheme.subtitle1, decoration: InputDecoration( filled: true, diff --git a/lib/ui/account/password_reentry_page.dart b/lib/ui/account/password_reentry_page.dart index 15c7318df..67d0f5976 100644 --- a/lib/ui/account/password_reentry_page.dart +++ b/lib/ui/account/password_reentry_page.dart @@ -66,6 +66,7 @@ class _PasswordReentryPageState extends State { ), body: _getBody(), floatingActionButton: DynamicFAB( + key: const ValueKey("verifyPasswordButton"), isKeypadOpen: isKeypadOpen, isFormValid: _passwordController.text.isNotEmpty, buttonText: S.of(context).verifyPassword, @@ -169,6 +170,7 @@ class _PasswordReentryPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: TextFormField( + key: const ValueKey("passwordInputField"), autofillHints: const [AutofillHints.password], decoration: InputDecoration( hintText: S.of(context).enterYourPassword, diff --git a/lib/ui/backup_folder_selection_page.dart b/lib/ui/backup_folder_selection_page.dart index 9977660b7..a895a857a 100644 --- a/lib/ui/backup_folder_selection_page.dart +++ b/lib/ui/backup_folder_selection_page.dart @@ -185,6 +185,7 @@ class _BackupFolderSelectionPageState extends State { bottom: Platform.isIOS ? 48 : 32, ), child: GestureDetector( + key: const ValueKey("skipBackupButton"), onTap: () { Navigator.of(context).pop(); }, diff --git a/lib/ui/home/grant_permissions_widget.dart b/lib/ui/home/grant_permissions_widget.dart index 90e37e6d6..6dddffc86 100644 --- a/lib/ui/home/grant_permissions_widget.dart +++ b/lib/ui/home/grant_permissions_widget.dart @@ -92,6 +92,7 @@ class GrantPermissionsWidget extends StatelessWidget { bottom: 16, ), child: OutlinedButton( + key: const ValueKey("grantPermissionButton"), child: Text(S.of(context).grantPermission), onPressed: () async { final state = await PhotoManager.requestPermissionExtend(); diff --git a/lib/ui/home/landing_page_widget.dart b/lib/ui/home/landing_page_widget.dart index 495efea41..02bfedde8 100644 --- a/lib/ui/home/landing_page_widget.dart +++ b/lib/ui/home/landing_page_widget.dart @@ -116,6 +116,7 @@ class _LandingPageWidgetState extends State { child: Hero( tag: "log_in", child: ElevatedButton( + key: const ValueKey("signInButton"), style: Theme.of(context).colorScheme.optionalActionButtonStyle, onPressed: _navigateToSignInPage, diff --git a/lib/ui/settings/app_update_dialog.dart b/lib/ui/settings/app_update_dialog.dart index 3501d1acf..a4c6bee52 100644 --- a/lib/ui/settings/app_update_dialog.dart +++ b/lib/ui/settings/app_update_dialog.dart @@ -112,6 +112,7 @@ class _AppUpdateDialogState extends State { return WillPopScope( onWillPop: () async => !shouldForceUpdate, child: AlertDialog( + key: const ValueKey("updateAppDialog"), title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/ui/viewer/file/thumbnail_widget.dart b/lib/ui/viewer/file/thumbnail_widget.dart index f66335045..0f1c8292b 100644 --- a/lib/ui/viewer/file/thumbnail_widget.dart +++ b/lib/ui/viewer/file/thumbnail_widget.dart @@ -58,9 +58,12 @@ class _ThumbnailWidgetState extends State { bool _isLoadingRemoteThumbnail = false; bool _errorLoadingRemoteThumbnail = false; ImageProvider? _imageProvider; + int? optimizedImageHeight; + int? optimizedImageWidth; @override void initState() { + changeSmallestSideToThumbnailSize(); super.initState(); } @@ -83,6 +86,17 @@ class _ThumbnailWidgetState extends State { } } + void changeSmallestSideToThumbnailSize() { + if (widget.file!.width == 0 || widget.file!.height == 0) { + return; + } + if (widget.file!.width < widget.file!.height) { + optimizedImageWidth = widget.thumbnailSize; + } else { + optimizedImageHeight = widget.thumbnailSize; + } + } + @override Widget build(BuildContext context) { if (widget.file!.isRemoteFile) { @@ -93,7 +107,13 @@ class _ThumbnailWidgetState extends State { Widget? image; if (_imageProvider != null) { image = Image( - image: _imageProvider!, + image: optimizedImageHeight != null || optimizedImageWidth != null + ? ResizeImage( + _imageProvider!, + width: optimizedImageWidth, + height: optimizedImageHeight, + ) + : _imageProvider!, fit: widget.fit, ); } diff --git a/pubspec.lock b/pubspec.lock index f1d87e341..f6923ed73 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: archive - sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" url: "https://pub.dev" source: hosted - version: "3.3.6" + version: "3.3.2" args: dependency: transitive description: @@ -614,6 +614,11 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + flutter_driver: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" flutter_easyloading: dependency: "direct main" description: @@ -877,6 +882,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -1013,6 +1023,11 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.5+2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -1804,6 +1819,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" syncfusion_flutter_core: dependency: "direct main" description: @@ -2130,6 +2153,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 + url: "https://pub.dev" + source: hosted + version: "3.0.1" webkit_inspection_protocol: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 285c9afbf..a65917ace 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -144,8 +144,12 @@ dev_dependencies: flutter_test: sdk: flutter freezed: ^2.3.2 + integration_test: + sdk: flutter json_serializable: ^6.6.1 test: + flutter_driver: + sdk: flutter flutter_icons: android: "launcher_icon"