diff --git a/.env.example b/.env.example index 75a8defa..649786b5 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,7 @@ EXPLORER_ADDRESS = 'witnet.network' EXPLORER_DEV_ADDRESS = '0.0.0.0' + +# The password is used for integration testing +# uncomment the line below and add your password to run automatic integration tests with a new or existing database +# PASSWORD = '' +# MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' diff --git a/integration_test/e2e_import_mnemonic_test.dart b/integration_test/e2e_import_mnemonic_test.dart new file mode 100644 index 00000000..85a30e5b --- /dev/null +++ b/integration_test/e2e_import_mnemonic_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:my_wit_wallet/main.dart' as myWitWallet; +import 'package:my_wit_wallet/util/integration_test_utils.dart'; +import 'package:my_wit_wallet/widgets/PaddedButton.dart'; +import 'package:my_wit_wallet/widgets/labeled_checkbox.dart'; + +bool walletsExist = false; +String password = dotenv.env['PASSWORD'] ?? "password"; +String mnemonic = dotenv.env['MNEMONIC'] ?? + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + +void main() async { + testWidgets("Import Mnemonic Test", (WidgetTester tester) async { + myWitWallet.main(); + await tester.pumpAndSettle(); + + walletsExist = isTextOnScreen("Unlock wallet"); + if (walletsExist) { + /// Login Screen + await enterText(tester, TextFormField, password); + await tapButton(tester, "Unlock wallet"); + + /// Dashboard + /// Tap on the first PaddedButton on the screen, which is the identicon + /// and brings up the wallet list. + await tapButton(tester, PaddedButton, index: 0); + await tapButton(tester, FontAwesomeIcons.circlePlus); + } + + /// Create or Import Wallet + await tapButton(tester, "Import wallet"); + await tapButton(tester, "Import from secret security phrase"); + + /// Wallet Security + await scrollUntilVisible( + tester, widgetByLabel("I will be careful, I promise!")); + await tapButton(tester, LabeledCheckbox); + await tapButton(tester, "Continue"); + + /// Enter Mnemonic + await enterText(tester, TextField, mnemonic); + await tapButton(tester, "Continue"); + + /// Enter Wallet Name + await enterText(tester, TextField, "Test Wallet"); + await tapButton(tester, "Continue"); + + /// If the wallet database does not exist we need to enter the password. + if (!walletsExist) { + await enterText(tester, TextFormField, password, index: 0); + await enterText(tester, TextFormField, password, index: 1); + await tapButton(tester, "Continue"); + } + }); +} diff --git a/integration_test/main.dart b/integration_test/main.dart new file mode 100644 index 00000000..6005b50f --- /dev/null +++ b/integration_test/main.dart @@ -0,0 +1,6 @@ +import 'e2e_import_mnemonic_test.dart' as registration; + +void main() { + // register + registration.main(); +} diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 97494f47..6d9c2dab 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B77918AB0DC0CB4B83C5188B /* Pods-Runner.debug-e2e.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-e2e.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-e2e.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,6 +68,7 @@ 11B13D8074A685345B1CD9C7 /* Pods-Runner.debug.xcconfig */, 07DDDEFB8BEB545C96D28C7B /* Pods-Runner.release.xcconfig */, 57B3355B057BAE30BDDD5E3A /* Pods-Runner.profile.xcconfig */, + B77918AB0DC0CB4B83C5188B /* Pods-Runner.debug-e2e.xcconfig */, ); path = Pods; sourceTree = ""; @@ -369,6 +371,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 0; PRODUCT_BUNDLE_IDENTIFIER = io.witnet.myWitWallet; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -379,6 +382,96 @@ }; name = Profile; }; + 5F5490222A84DBB8003C5F01 /* Debug-e2e */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-e2e"; + }; + 5F5490232A84DBB8003C5F01 /* Debug-e2e */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B77918AB0DC0CB4B83C5188B /* Pods-Runner.debug-e2e.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Distribution: Witnet Foundation (SENSITIVE_REPLACE_ME)"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 0; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = SENSITIVE_REPLACE_ME; + ENABLE_BITCODE = NO; + FLUTTER_APPLICATION_PATH = /Users/gabaldon/Witnet/witnet_wallet; + FLUTTER_BUILD_DIR = build; + FLUTTER_BUILD_NAME = 0.0.0; + FLUTTER_BUILD_NUMBER = 0; + FLUTTER_ROOT = /Users/gabaldon/Witnet/flutter; + FLUTTER_TARGET = integration_test/main.dart; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0; + PRODUCT_BUNDLE_IDENTIFIER = io.witnet.myWitWallet; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.witnet.myWitWallet"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-e2e"; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -503,6 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 0; PRODUCT_BUNDLE_IDENTIFIER = io.witnet.myWitWallet; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -532,6 +626,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + MARKETING_VERSION = 0; PRODUCT_BUNDLE_IDENTIFIER = io.witnet.myWitWallet; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -549,6 +644,7 @@ isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, + 5F5490222A84DBB8003C5F01 /* Debug-e2e */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); @@ -559,6 +655,7 @@ isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, + 5F5490232A84DBB8003C5F01 /* Debug-e2e */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/e2e.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/e2e.xcscheme new file mode 100644 index 00000000..70377d0a --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/e2e.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/util/integration_test_utils.dart b/lib/util/integration_test_utils.dart new file mode 100644 index 00000000..4d1c866a --- /dev/null +++ b/lib/util/integration_test_utils.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:my_wit_wallet/widgets/PaddedButton.dart'; + +Finder widgetByType(Type type) => find.byType(type); +Finder widgetByText(String text) => find.text(text); +Finder widgetByIcon(IconData icon) => find.byIcon(icon); +Finder widgetByLabel(String label) => find.bySemanticsLabel(label); +const int defaultDelay = 1000; + +Future tapButton(WidgetTester tester, dynamic value, + {int? index, bool delay = true, int milliseconds = defaultDelay}) async { + Finder finder; + switch (value.runtimeType) { + case Type: + finder = widgetByType(value); + break; + case String: + finder = widgetByText(value); + break; + case IconDataSolid: + finder = widgetByIcon(value); + break; + case PaddedButton: + finder = widgetByType(value); + break; + default: + { + if (value.runtimeType.toString().startsWith("IconData")) { + finder = widgetByIcon(value); + } else { + finder = widgetByType(value); + } + break; + } + } + + await tester.tap(index != null ? finder.at(index) : finder); + await tester.pumpAndSettle(); + if (delay) { + await Future.delayed(Duration(milliseconds: milliseconds)); + } + return true; +} + +Future tapButtonByName(WidgetTester tester, String text, + {int index = 0, + bool delay = true, + int milliseconds = defaultDelay}) async => + await tapButton( + tester, + text, + index: index, + delay: delay, + milliseconds: milliseconds, + ); + +Future tapButtonByType(WidgetTester tester, Type type, + {int index = 0, + bool delay = true, + int milliseconds = defaultDelay}) async => + await tapButton( + tester, + type, + index: index, + delay: delay, + milliseconds: milliseconds, + ); + +Future tapButtonByIndex(WidgetTester tester, dynamic data, + {int index = 0, + bool delay = true, + int milliseconds = defaultDelay}) async => + await tapButton( + tester, + data, + index: index, + delay: delay, + milliseconds: milliseconds, + ); + +Future tapButtonByIcon(WidgetTester tester, IconData icon, + {int index = 0, + bool delay = true, + int milliseconds = defaultDelay}) async => + await tapButton( + tester, + icon, + index: index, + delay: delay, + milliseconds: milliseconds, + ); + +Future tapButtonByLabel(WidgetTester tester, String label, + {int index = 0, + bool delay = true, + int milliseconds = defaultDelay}) async => + await tapButton( + tester, + label, + index: index, + delay: delay, + milliseconds: milliseconds, + ); + +Future enterText(WidgetTester tester, Type type, String text, + {int? index, bool delay = true, int milliseconds = defaultDelay}) async { + index != null + ? await tester.enterText(widgetByType(type).at(index), text) + : await tester.enterText(widgetByType(type), text); + await tester.pumpAndSettle(); + if (delay) { + await Future.delayed(Duration(milliseconds: milliseconds)); + } + return true; +} + +enum ScrollDirection { Up, Down, Left, Right } + +Future scrollUntilVisible(WidgetTester tester, Finder finder, + {int index = 0, bool delay = true, int milliseconds = defaultDelay}) async { + await tester.scrollUntilVisible(finder, -100.0, + duration: Duration(milliseconds: 500), maxScrolls: 100); + await tester.pumpAndSettle(); + if (delay) { + await Future.delayed(Duration(milliseconds: milliseconds)); + } + return true; +} + +bool isTextOnScreen(String text) => + !find.text(text).toString().startsWith('zero widgets with text "$text"');