mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-28 06:28:41 +01:00
Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
This commit is contained in:
@@ -1,273 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class CommonTestCases {
|
||||
WidgetTester tester;
|
||||
CommonTestCases(this.tester);
|
||||
|
||||
Future<void> isSpecificPage<T>() async {
|
||||
await tester.pumpAndSettle();
|
||||
hasType<T>();
|
||||
}
|
||||
|
||||
Future<void> tapItemByKey(
|
||||
String key, {
|
||||
bool shouldPumpAndSettle = true,
|
||||
int pumpDuration = 100,
|
||||
Duration timeout = const Duration(seconds: 20),
|
||||
}) async {
|
||||
final widgetFinder = find.byKey(ValueKey(key));
|
||||
|
||||
// Wait for the widget to actually appear rather than pumping a flat 500ms
|
||||
// and hoping. Screens that arrive behind an async transition (PIN setup's
|
||||
// success button is the usual offender) are otherwise a coin flip, and the
|
||||
// failure reads as "key missing" rather than "key not there YET".
|
||||
//
|
||||
// Deliberately a pump loop, not pumpAndSettle: the dashboard runs a
|
||||
// perpetual sync-indicator animation, so settling never completes there.
|
||||
final deadline = DateTime.now().add(timeout);
|
||||
while (!tester.any(widgetFinder) && DateTime.now().isBefore(deadline)) {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
expect(tester.any(widgetFinder), true,
|
||||
reason: 'Widget with key "$key" should be visible '
|
||||
'(waited ${timeout.inSeconds}s)');
|
||||
|
||||
final widget = widgetFinder.first;
|
||||
await tester.tap(widget, warnIfMissed: false);
|
||||
shouldPumpAndSettle
|
||||
? await tester.pumpAndSettle(Duration(milliseconds: pumpDuration))
|
||||
: await tester.pump();
|
||||
}
|
||||
|
||||
Future<void> tapItemByFinder(Finder finder, {bool shouldPumpAndSettle = true}) async {
|
||||
await tester.tap(finder);
|
||||
shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump();
|
||||
}
|
||||
|
||||
void hasText(String text, {bool hasWidget = true}) {
|
||||
final textWidget = find.text(text);
|
||||
expect(textWidget, hasWidget ? findsOneWidget : findsNothing);
|
||||
}
|
||||
|
||||
void hasTextAtLestOnce(String text, {bool hasWidget = true}) {
|
||||
final textWidget = find.text(text);
|
||||
expect(textWidget, hasWidget ? findsAny : findsNothing);
|
||||
}
|
||||
|
||||
void hasType<T>() {
|
||||
final typeWidget = find.byType(T);
|
||||
expect(typeWidget, findsOneWidget);
|
||||
}
|
||||
|
||||
bool isKeyPresent(String key) {
|
||||
final typeWidget = find.byKey(ValueKey(key));
|
||||
return typeWidget.tryEvaluate();
|
||||
}
|
||||
|
||||
void hasValueKey(String key) {
|
||||
final typeWidget = find.byKey(ValueKey(key));
|
||||
expect(typeWidget, findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> swipePage({bool swipeRight = true}) async {
|
||||
await tester.drag(find.byType(PageView), Offset(swipeRight ? -300 : 300, 0));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> swipeByPageKey({required String key, bool swipeRight = true}) async {
|
||||
await tester.drag(find.byKey(ValueKey(key)), Offset(swipeRight ? -300 : 300, 0));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> goBack() async {
|
||||
tester.printToConsole('Routing back to previous screen');
|
||||
final NavigatorState navigator = tester.state(find.byType(Navigator));
|
||||
navigator.pop();
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> startGesture(String key, Offset gestureOffset) async {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final hasKey = isKeyPresent(key);
|
||||
|
||||
tester.printToConsole("Has gestureKey: $hasKey");
|
||||
|
||||
if (!hasKey) return;
|
||||
|
||||
final gesture = await tester.startGesture(tester.getCenter(find.byKey(ValueKey(key))));
|
||||
|
||||
// Drag to the left
|
||||
await gesture.moveBy(gestureOffset);
|
||||
|
||||
// End the gesture
|
||||
await gesture.up();
|
||||
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
Future<void> dragUntilVisible(
|
||||
String childKey,
|
||||
String parentKey, {
|
||||
int maxScrolls = 100,
|
||||
double scrollStep = 50.0,
|
||||
int maxReverseScrolls = 50,
|
||||
}) async {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final itemFinder = find.byKey(ValueKey(childKey));
|
||||
final listFinder = find.byKey(ValueKey(parentKey));
|
||||
|
||||
// Check if the widget is already in the widget tree
|
||||
if (tester.any(itemFinder)) {
|
||||
// Widget is already built and in the tree
|
||||
tester.printToConsole('Child is already present');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the Scrollable associated with the Parent Ad
|
||||
final scrollableFinder = find.descendant(
|
||||
of: listFinder,
|
||||
matching: find.byType(Scrollable),
|
||||
);
|
||||
|
||||
// Ensure that the Scrollable is found
|
||||
expect(
|
||||
scrollableFinder,
|
||||
findsOneWidget,
|
||||
reason: 'Scrollable descendant of the Parent Widget not found.',
|
||||
);
|
||||
|
||||
// Get the initial scroll position
|
||||
final scrollableState = tester.state<ScrollableState>(scrollableFinder);
|
||||
double previousScrollPosition = scrollableState.position.pixels;
|
||||
bool scrollDown = true;
|
||||
bool reversedDirection = false;
|
||||
bool found = false;
|
||||
|
||||
int reverseScrollCount = 0;
|
||||
|
||||
for (int scrolls = 0; scrolls < maxScrolls; scrolls++) {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Check if the widget is visible
|
||||
if (tester.any(itemFinder)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Log current state for debugging
|
||||
tester.printToConsole('Scrolling ${scrollDown ? 'down' : 'up'}, attempt $scrolls');
|
||||
|
||||
// Stop if reverse scroll limit is exceeded
|
||||
if (!scrollDown && reverseScrollCount >= maxReverseScrolls) {
|
||||
tester.printToConsole('Maximum reverse scrolls reached. Widget not found.');
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform scrolling in the current direction
|
||||
await tester.drag(
|
||||
scrollableFinder,
|
||||
scrollDown ? Offset(0, -scrollStep) : Offset(0, scrollStep),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Update the scroll position after the drag
|
||||
final currentScrollPosition = scrollableState.position.pixels;
|
||||
|
||||
if (currentScrollPosition == previousScrollPosition) {
|
||||
// Cannot scroll further in the current direction
|
||||
if (reversedDirection) {
|
||||
// We've already tried both directions
|
||||
tester.printToConsole('Reached the scroll limit in both directions. Widget not found.');
|
||||
break;
|
||||
} else {
|
||||
// Reverse the scroll direction and reset reverse scroll count
|
||||
scrollDown = !scrollDown;
|
||||
reversedDirection = true;
|
||||
reverseScrollCount = 0;
|
||||
tester.printToConsole('Reached the end, reversing direction');
|
||||
}
|
||||
} else {
|
||||
// Update scroll position and reverse scroll count, incrementing only for reverse scrolling
|
||||
previousScrollPosition = currentScrollPosition;
|
||||
if (!scrollDown) reverseScrollCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
tester.printToConsole('Widget not found after $maxScrolls scrolls.');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> scrollItemIntoView(
|
||||
String itemKeyId,
|
||||
double scrollPixels,
|
||||
String scrollableFinderKey,
|
||||
) async {
|
||||
final Finder itemFinder = find.byKey(ValueKey(itemKeyId));
|
||||
|
||||
final scrollableFinder = find.descendant(
|
||||
of: find.byKey(ValueKey(scrollableFinderKey)),
|
||||
matching: find.byType(Scrollable),
|
||||
);
|
||||
|
||||
// Check if the item is already visible
|
||||
if (tester.any(itemFinder)) {
|
||||
tester.printToConsole('Item $itemKeyId is already visible');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if scrollable exists
|
||||
if (!tester.any(scrollableFinder)) {
|
||||
tester.printToConsole('Scrollable not found for $itemKeyId');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await tester.scrollUntilVisible(
|
||||
itemFinder,
|
||||
scrollPixels,
|
||||
scrollable: scrollableFinder,
|
||||
maxScrolls: 10,
|
||||
);
|
||||
|
||||
// Wait for the scroll to complete
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 500));
|
||||
|
||||
// Verify the item is now visible
|
||||
if (!tester.any(itemFinder)) {
|
||||
tester.printToConsole('Item $itemKeyId not found after scrolling');
|
||||
}
|
||||
} catch (e) {
|
||||
tester.printToConsole('Could not scroll to $itemKeyId: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> enterText(String text, String editableTextKey) async {
|
||||
final editableTextWidget = find.byKey(ValueKey((editableTextKey)));
|
||||
|
||||
await tester.enterText(editableTextWidget, text);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
void findWidgetViaDescendant({
|
||||
required FinderBase<Element> of,
|
||||
required FinderBase<Element> matching,
|
||||
}) {
|
||||
final textWidget = find.descendant(of: of, matching: matching);
|
||||
|
||||
expect(textWidget, findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> defaultSleepTime({int seconds = 2}) async =>
|
||||
await Future.delayed(Duration(seconds: seconds));
|
||||
|
||||
Future<void> takeScreenshots(String screenshotName) async {
|
||||
// Pausing this for now
|
||||
// await (tester.binding as IntegrationTestWidgetsFlutterBinding).takeScreenshot(screenshotName);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class CommonTestConstants {
|
||||
static final pin = [0, 8, 0, 1];
|
||||
static final String sendTestAmount = '0.00008';
|
||||
static final String sendTestFiatAmount = '1.00';
|
||||
static final String exchangeTestAmount = '0.01';
|
||||
// Hash Bags is Wownero-first and no longer ships Solana, so the defaults
|
||||
// moved off SOL. WalletType.solana does not exist in this fork.
|
||||
static final WalletType testWalletType = WalletType.wownero;
|
||||
static final String testWalletName = 'Integrated Testing Wallet';
|
||||
static final CryptoCurrency sendTestReceiveCurrency = CryptoCurrency.wow;
|
||||
static final CryptoCurrency exchangeTestReceiveCurrency = CryptoCurrency.btc;
|
||||
static final CryptoCurrency exchangeTestDepositCurrency = CryptoCurrency.wow;
|
||||
static final String testWalletAddress =
|
||||
'Wo3MWeKwtA918DU4c69hVSNgejdWFCRCuWjShRY66mJkU2Hv58eygJWDJS1MNa2Ge5M1WjUkGHJLAjaZYLBZnDh42Q2u1Es9J';
|
||||
}
|
||||
@@ -1,404 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hash_wallet/entities/seed_type.dart';
|
||||
import 'package:hash_wallet/reactions/wallet_utils.dart';
|
||||
import 'package:hash_wallet/wallet_types.g.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:hash_wallet/main.dart' as app;
|
||||
|
||||
import '../robots/create_pin_welcome_page_robot.dart';
|
||||
import '../robots/new_wallet_page_robot.dart';
|
||||
import '../robots/new_wallet_type_page_robot.dart';
|
||||
import '../robots/pre_seed_page_robot.dart';
|
||||
import '../robots/restore_from_seed_or_key_robot.dart';
|
||||
import '../robots/restore_options_page_robot.dart';
|
||||
import '../robots/seed_verification_page_robot.dart';
|
||||
import '../robots/setup_pin_code_robot.dart';
|
||||
import '../robots/wallet_group_description_page_robot.dart';
|
||||
import '../robots/wallet_list_page_robot.dart';
|
||||
import '../robots/wallet_seed_page_robot.dart';
|
||||
import '../robots/welcome_page_robot.dart';
|
||||
import 'common_test_cases.dart';
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
import 'common_test_constants.dart';
|
||||
|
||||
class CommonTestFlows {
|
||||
CommonTestFlows(this._tester)
|
||||
: _commonTestCases = CommonTestCases(_tester),
|
||||
_welcomePageRobot = WelcomePageRobot(_tester),
|
||||
_preSeedPageRobot = PreSeedPageRobot(_tester),
|
||||
_setupPinCodeRobot = SetupPinCodeRobot(_tester),
|
||||
_newWalletPageRobot = NewWalletPageRobot(_tester),
|
||||
_walletSeedPageRobot = WalletSeedPageRobot(_tester),
|
||||
_walletListPageRobot = WalletListPageRobot(_tester),
|
||||
_newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
|
||||
_restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
|
||||
_seedVerificationPageRobot = SeedVerificationPageRobot(_tester),
|
||||
_createPinWelcomePageRobot = CreatePinWelcomePageRobot(_tester),
|
||||
_restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester),
|
||||
_walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester);
|
||||
|
||||
final WidgetTester _tester;
|
||||
final CommonTestCases _commonTestCases;
|
||||
|
||||
final WelcomePageRobot _welcomePageRobot;
|
||||
final PreSeedPageRobot _preSeedPageRobot;
|
||||
final SetupPinCodeRobot _setupPinCodeRobot;
|
||||
final NewWalletPageRobot _newWalletPageRobot;
|
||||
final WalletSeedPageRobot _walletSeedPageRobot;
|
||||
final WalletListPageRobot _walletListPageRobot;
|
||||
final NewWalletTypePageRobot _newWalletTypePageRobot;
|
||||
final RestoreOptionsPageRobot _restoreOptionsPageRobot;
|
||||
final CreatePinWelcomePageRobot _createPinWelcomePageRobot;
|
||||
final SeedVerificationPageRobot _seedVerificationPageRobot;
|
||||
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
|
||||
final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot;
|
||||
|
||||
//* ========== Handles flow to start the app afresh and accept disclaimer =============
|
||||
Future<void> startAppFlow(Key key) async {
|
||||
await app.main(topLevelKey: key);
|
||||
|
||||
await _tester.pump(Duration(seconds: 2));
|
||||
|
||||
// // --------- Disclaimer Page ------------
|
||||
// // Tap checkbox to accept disclaimer
|
||||
// await _disclaimerPageRobot.tapDisclaimerCheckbox();
|
||||
|
||||
// // Tap accept button
|
||||
// await _disclaimerPageRobot.tapAcceptButton();
|
||||
}
|
||||
|
||||
//* ========== Handles flow from welcome to creating a new wallet ===============
|
||||
Future<void> welcomePageToCreateNewWalletFlow(
|
||||
WalletType walletTypeToCreate,
|
||||
List<int> walletPin,
|
||||
) async {
|
||||
await _welcomeToCreateWalletPath(walletTypeToCreate, walletPin);
|
||||
|
||||
await _generateNewWalletDetails();
|
||||
|
||||
await _confirmPreSeedInfo();
|
||||
|
||||
await _confirmWalletDetails();
|
||||
|
||||
await _verifyWalletSeed();
|
||||
}
|
||||
|
||||
//* ========== Handles flow from welcome to restoring wallet from seeds ===============
|
||||
Future<void> welcomePageToRestoreWalletThroughSeedsFlow(
|
||||
WalletType walletTypeToRestore,
|
||||
String walletSeed,
|
||||
List<int> walletPin,
|
||||
) async {
|
||||
await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
|
||||
await _restoreFromSeeds(walletTypeToRestore, walletSeed);
|
||||
}
|
||||
|
||||
//* ========== Handles flow from welcome to restoring wallet from keys ===============
|
||||
Future<void> welcomePageToRestoreWalletThroughKeysFlow(
|
||||
WalletType walletTypeToRestore,
|
||||
List<int> walletPin,
|
||||
) async {
|
||||
await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin);
|
||||
await _restoreFromKeys();
|
||||
}
|
||||
|
||||
void confirmAllAvailableWalletTypeIconsDisplayCorrectly() {
|
||||
for (var walletType in availableWalletTypes) {
|
||||
final imageUrl = walletTypeToCryptoCurrency(walletType).iconPath;
|
||||
|
||||
final walletIconFinder = find.image(
|
||||
Image.asset(
|
||||
imageUrl!,
|
||||
width: 32,
|
||||
height: 32,
|
||||
).image,
|
||||
);
|
||||
|
||||
expect(walletIconFinder, findsAny);
|
||||
}
|
||||
}
|
||||
|
||||
//* ========== Handles creating new wallet flow from wallet list/menu ===============
|
||||
Future<void> createNewWalletFromWalletMenu(WalletType walletTypeToCreate) async {
|
||||
_tester.printToConsole('Creating ${walletTypeToCreate.name} Wallet');
|
||||
|
||||
await _tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
|
||||
await _walletListPageRobot.navigateToCreateNewWalletPage();
|
||||
await _commonTestCases.defaultSleepTime();
|
||||
|
||||
await _selectWalletTypeForWallet(walletTypeToCreate);
|
||||
await _commonTestCases.defaultSleepTime();
|
||||
|
||||
// ---- Wallet Group/New Seed Implementation Comes here
|
||||
await _walletGroupDescriptionPageFlow(true, walletTypeToCreate);
|
||||
|
||||
await _generateNewWalletDetails();
|
||||
|
||||
await _confirmPreSeedInfo();
|
||||
|
||||
await _confirmWalletDetails();
|
||||
|
||||
await _verifyWalletSeed();
|
||||
|
||||
await _commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> _walletGroupDescriptionPageFlow(bool isNewSeed, WalletType walletType) async {
|
||||
if (!isBIP39Wallet(walletType)) return;
|
||||
|
||||
await _walletGroupDescriptionPageRobot.isWalletGroupDescriptionPage();
|
||||
|
||||
if (isNewSeed) {
|
||||
await _walletGroupDescriptionPageRobot.navigateToCreateNewSeedPage();
|
||||
} else {
|
||||
await _walletGroupDescriptionPageRobot.navigateToChooseWalletGroup();
|
||||
}
|
||||
}
|
||||
|
||||
//* ========== Handles restore wallet flow from wallet list/menu ===============
|
||||
Future<void> restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async {
|
||||
_tester.printToConsole('Restoring ${walletType.name} Wallet');
|
||||
await _walletListPageRobot.navigateToRestoreWalletOptionsPage();
|
||||
await _commonTestCases.defaultSleepTime();
|
||||
|
||||
if (!Platform.isLinux) await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
|
||||
if (!Platform.isLinux) await _commonTestCases.defaultSleepTime();
|
||||
|
||||
await _selectWalletTypeForWallet(walletType);
|
||||
await _commonTestCases.defaultSleepTime();
|
||||
|
||||
await _restoreFromSeeds(walletType, walletSeed);
|
||||
await _commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
//* ========== Handles setting up pin code for wallet on first install ===============
|
||||
Future<void> setupPinCodeForWallet(List<int> pin) async {
|
||||
if (Platform.isLinux) return;
|
||||
// ----------- SetupPinCode Page -------------
|
||||
// Confirm initial defaults - Widgets to be displayed etc
|
||||
await _setupPinCodeRobot.isSetupPinCodePage();
|
||||
|
||||
await _setupPinCodeRobot.enterPinCode(pin);
|
||||
await _setupPinCodeRobot.enterPinCode(pin);
|
||||
await _setupPinCodeRobot.tapSuccessButton();
|
||||
}
|
||||
|
||||
Future<void> _welcomeToCreateWalletPath(
|
||||
WalletType walletTypeToCreate,
|
||||
List<int> pin,
|
||||
) async {
|
||||
await _createPinWelcomePageRobot.tapSetAPinButton();
|
||||
|
||||
await setupPinCodeForWallet(pin);
|
||||
|
||||
await _welcomePageRobot.navigateToCreateNewWalletPage();
|
||||
|
||||
await _selectWalletTypeForWallet(walletTypeToCreate);
|
||||
|
||||
if (_welcomePageRobot.hasNewSingleSeedButton()) {
|
||||
await _welcomePageRobot.tapNewSingleSeed();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _welcomeToRestoreFromSeedsOrKeysPath(
|
||||
WalletType walletTypeToRestore,
|
||||
List<int> pin,
|
||||
) async {
|
||||
await _createPinWelcomePageRobot.tapSetAPinButton();
|
||||
|
||||
await setupPinCodeForWallet(pin);
|
||||
|
||||
await _welcomePageRobot.navigateToRestoreWalletPage();
|
||||
|
||||
if (!Platform.isLinux) await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
|
||||
|
||||
await _selectWalletTypeForWallet(walletTypeToRestore);
|
||||
}
|
||||
|
||||
//* ============ Handles New Wallet Type Page ==================
|
||||
Future<void> _selectWalletTypeForWallet(WalletType type) async {
|
||||
// ----------- NewWalletType Page -------------
|
||||
_tester.printToConsole('Selecting wallet type: ${type.name}');
|
||||
|
||||
await _tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
|
||||
await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
|
||||
|
||||
_tester.printToConsole('Tapping wallet type: ${type.name}');
|
||||
await _newWalletTypePageRobot.selectWalletType(type);
|
||||
await _tester.pumpAndSettle(Duration(milliseconds: 500));
|
||||
|
||||
_tester.printToConsole('Pressing next button');
|
||||
await _newWalletTypePageRobot.onNextButtonPressed();
|
||||
|
||||
await _tester.pumpAndSettle(Duration(milliseconds: 2000));
|
||||
|
||||
_tester.printToConsole('Wallet type selection completed for: ${type.name}');
|
||||
}
|
||||
|
||||
//* ============ Handles New Wallet Page ==================
|
||||
Future<void> _generateNewWalletDetails() async {
|
||||
_tester.printToConsole('Attempting to find NewWalletPage...');
|
||||
|
||||
await _tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
|
||||
await _newWalletPageRobot.isNewWalletPage();
|
||||
_tester.printToConsole('NewWalletPage found successfully');
|
||||
|
||||
await _newWalletPageRobot.generateWalletName();
|
||||
|
||||
if (Platform.isLinux) {
|
||||
// manual pin input
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.enterPasswordForWalletRestore(CommonTestConstants.pin.join(""));
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.enterPasswordRepeatForWalletRestore(CommonTestConstants.pin.join(""));
|
||||
}
|
||||
|
||||
await _newWalletPageRobot.onNextButtonPressed();
|
||||
}
|
||||
|
||||
//* ============ Handles Pre Seed Page =====================
|
||||
Future<void> _confirmPreSeedInfo() async {
|
||||
await _preSeedPageRobot.isPreSeedPage();
|
||||
|
||||
await _preSeedPageRobot.onConfirmButtonPressed();
|
||||
}
|
||||
|
||||
//* ============ Handles Wallet Seed Page ==================
|
||||
Future<void> _confirmWalletDetails() async {
|
||||
await _walletSeedPageRobot.isWalletSeedPage();
|
||||
|
||||
_walletSeedPageRobot.confirmWalletDetailsDisplayCorrectly();
|
||||
|
||||
_walletSeedPageRobot.confirmWalletSeedReminderDisplays();
|
||||
|
||||
// await _walletSeedPageRobot.onCopySeedsButtonPressed();
|
||||
|
||||
await _walletSeedPageRobot.onVerifySeedButtonPressed();
|
||||
}
|
||||
|
||||
//* ============ Handles Wallet Seed Verification Page ==================
|
||||
|
||||
Future<void> _verifyWalletSeed() async {
|
||||
await _seedVerificationPageRobot.isSeedVerificationPage();
|
||||
|
||||
_seedVerificationPageRobot.hasTitle();
|
||||
|
||||
await _seedVerificationPageRobot.verifyWalletSeeds();
|
||||
}
|
||||
|
||||
//* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action
|
||||
Future<void> _restoreFromSeeds(WalletType type, String walletSeed) async {
|
||||
// ----------- RestoreFromSeedOrKeys Page -------------
|
||||
|
||||
await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions();
|
||||
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(walletSeed);
|
||||
|
||||
final numberOfWords = walletSeed.split(' ').length;
|
||||
|
||||
if (numberOfWords == 25 && (type == WalletType.monero)) {
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy);
|
||||
|
||||
// Using a constant value of 2831400 for the blockheight as its the restore blockheight for our testing wallet
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight);
|
||||
}
|
||||
|
||||
if (Platform.isLinux) {
|
||||
// manual pin input
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.enterPasswordForWalletRestore(CommonTestConstants.pin.join(""));
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.enterPasswordRepeatForWalletRestore(CommonTestConstants.pin.join(""));
|
||||
}
|
||||
|
||||
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
|
||||
}
|
||||
|
||||
//* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Keys Action
|
||||
Future<void> _restoreFromKeys() async {
|
||||
await _commonTestCases.swipePage();
|
||||
await _commonTestCases.defaultSleepTime();
|
||||
|
||||
await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions(
|
||||
isSeedFormEntry: false,
|
||||
);
|
||||
|
||||
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore('');
|
||||
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
|
||||
}
|
||||
|
||||
//* ====== Utility Function to get test wallet seeds for each wallet type ========
|
||||
String getWalletSeedsByWalletType(WalletType walletType) {
|
||||
switch (walletType) {
|
||||
case WalletType.monero:
|
||||
return secrets.moneroTestWalletSeeds;
|
||||
case WalletType.bitcoin:
|
||||
return secrets.bitcoinTestWalletSeeds;
|
||||
case WalletType.ethereum:
|
||||
return secrets.ethereumTestWalletSeeds;
|
||||
case WalletType.litecoin:
|
||||
return secrets.litecoinTestWalletSeeds;
|
||||
case WalletType.bitcoinCash:
|
||||
return secrets.bitcoinCashTestWalletSeeds;
|
||||
case WalletType.polygon:
|
||||
return secrets.polygonTestWalletSeeds;
|
||||
case WalletType.base:
|
||||
return secrets.baseTestWalletSeeds;
|
||||
case WalletType.arbitrum:
|
||||
return secrets.arbitrumTestWalletSeeds;
|
||||
case WalletType.bsc:
|
||||
return secrets.bscTestWalletSeeds;
|
||||
case WalletType.nano:
|
||||
return secrets.nanoTestWalletSeeds;
|
||||
case WalletType.wownero:
|
||||
return secrets.wowneroTestWalletSeeds;
|
||||
// Hash Bags does not ship SOL/TRX/ZEC/DCR/ZANO/Haven — those WalletType
|
||||
// members no longer exist. dogecoin ships, but has no test seed in
|
||||
// .secrets.g.dart.
|
||||
case WalletType.dogecoin:
|
||||
case WalletType.none:
|
||||
case WalletType.banano:
|
||||
throw Exception("Unable to get seeds for ${walletType}");
|
||||
}
|
||||
}
|
||||
|
||||
//* ====== Utility Function to get test receive address for each wallet type ========
|
||||
String getReceiveAddressByWalletType(WalletType walletType) {
|
||||
switch (walletType) {
|
||||
case WalletType.monero:
|
||||
return secrets.moneroTestWalletReceiveAddress;
|
||||
case WalletType.bitcoin:
|
||||
return secrets.bitcoinTestWalletReceiveAddress;
|
||||
case WalletType.ethereum:
|
||||
return secrets.ethereumTestWalletReceiveAddress;
|
||||
case WalletType.litecoin:
|
||||
return secrets.litecoinTestWalletReceiveAddress;
|
||||
case WalletType.bitcoinCash:
|
||||
return secrets.bitcoinCashTestWalletReceiveAddress;
|
||||
case WalletType.polygon:
|
||||
return secrets.polygonTestWalletReceiveAddress;
|
||||
case WalletType.base:
|
||||
return secrets.baseTestWalletReceiveAddress;
|
||||
case WalletType.arbitrum:
|
||||
return secrets.arbitrumTestWalletReceiveAddress;
|
||||
case WalletType.bsc:
|
||||
return secrets.bscTestWalletReceiveAddress;
|
||||
case WalletType.nano:
|
||||
return secrets.nanoTestWalletReceiveAddress;
|
||||
case WalletType.wownero:
|
||||
return secrets.wowneroTestWalletReceiveAddress;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:hash_wallet/main.dart" as app;
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "benign_errors.dart";
|
||||
|
||||
void integrationTest(String description, Future<void> Function(WidgetTester tester) body) {
|
||||
testWidgets(description, (tester) async {
|
||||
final completer = Completer<void>();
|
||||
|
||||
final run = runZonedGuarded(() async {
|
||||
try {
|
||||
await body(tester);
|
||||
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
} catch (error, stack) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.completeError(error, stack);
|
||||
}
|
||||
}
|
||||
}, (error, stack) {
|
||||
final kind = benignErrorKind(error.toString());
|
||||
|
||||
if (kind != null) {
|
||||
debugPrint("Ignoring benign $kind background error: $error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (completer.isCompleted) {
|
||||
debugPrint("Background error after the test finished: $error");
|
||||
return;
|
||||
}
|
||||
|
||||
completer.completeError(error, stack);
|
||||
});
|
||||
|
||||
unawaited(run);
|
||||
|
||||
await completer.future;
|
||||
});
|
||||
}
|
||||
|
||||
class AppLauncher {
|
||||
AppLauncher(this.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
|
||||
Future<void> launchApp({required String testKey}) async {
|
||||
final bindingHandler = FlutterError.onError;
|
||||
|
||||
await app.main(topLevelKey: ValueKey(testKey));
|
||||
|
||||
_installTestErrorHandler(bindingHandler);
|
||||
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
}
|
||||
|
||||
void _installTestErrorHandler(FlutterExceptionHandler? bindingHandler) {
|
||||
FlutterError.onError = (details) {
|
||||
final reportedByTestFramework = details.library == "Flutter test framework";
|
||||
final kind = reportedByTestFramework ? null : benignErrorKind(details.exceptionAsString());
|
||||
|
||||
if (kind != null) {
|
||||
debugPrint("Ignoring benign $kind error: ${details.exceptionAsString()}");
|
||||
return;
|
||||
}
|
||||
|
||||
bindingHandler?.call(details);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "benign_errors.dart";
|
||||
|
||||
abstract class BaseRobot {
|
||||
BaseRobot(this.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
|
||||
Future<void> isDisplayed();
|
||||
|
||||
void drainBackgroundErrors() {
|
||||
final Object? pending = tester.takeException();
|
||||
|
||||
if (pending == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final kind = benignErrorKind(pending.toString());
|
||||
|
||||
if (kind != null) {
|
||||
tester.printToConsole("Ignoring benign $kind background error: $pending");
|
||||
return;
|
||||
}
|
||||
|
||||
throw TestFailure("Unhandled background error: $pending");
|
||||
}
|
||||
|
||||
Future<void> _pumpStep(Duration step) async {
|
||||
await tester.pump(step);
|
||||
|
||||
drainBackgroundErrors();
|
||||
}
|
||||
|
||||
Future<void> pumpUntilFound(
|
||||
Finder finder, {
|
||||
Duration timeout = const Duration(seconds: 30),
|
||||
Duration step = const Duration(milliseconds: 100),
|
||||
}) async {
|
||||
final endTime = DateTime.now().add(timeout);
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
await _pumpStep(step);
|
||||
|
||||
if (tester.any(finder)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await takeScreenshot("${runtimeType}_pump_until_found_timeout");
|
||||
throw TestFailure("Widget not found after ${timeout.inSeconds}s: $finder");
|
||||
}
|
||||
|
||||
Future<void> pumpUntilGone(
|
||||
Finder finder, {
|
||||
Duration timeout = const Duration(seconds: 30),
|
||||
Duration step = const Duration(milliseconds: 100),
|
||||
}) async {
|
||||
final endTime = DateTime.now().add(timeout);
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
await _pumpStep(step);
|
||||
|
||||
if (!tester.any(finder)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await takeScreenshot("${runtimeType}_pump_until_gone_timeout");
|
||||
throw TestFailure("Widget still present after ${timeout.inSeconds}s: $finder");
|
||||
}
|
||||
|
||||
Future<void> tapByKey(String key, {Duration timeout = const Duration(seconds: 30)}) async {
|
||||
final finder = find.byKey(ValueKey(key));
|
||||
|
||||
await pumpUntilFound(finder, timeout: timeout);
|
||||
|
||||
// A tap during a route transition lands on the animating overlay instead of the widget
|
||||
await settle(max: const Duration(seconds: 2));
|
||||
|
||||
await tester.tap(finder.first, warnIfMissed: false);
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
Future<void> tapWhenVisible(
|
||||
Finder finder, {
|
||||
Duration timeout = const Duration(seconds: 30),
|
||||
}) async {
|
||||
await pumpUntilFound(finder, timeout: timeout);
|
||||
|
||||
await settle(max: const Duration(seconds: 2));
|
||||
|
||||
await tester.tap(finder.first, warnIfMissed: false);
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
Future<void> enterTextByKey(String key, String text) async {
|
||||
final finder = find.byKey(ValueKey(key));
|
||||
|
||||
await pumpUntilFound(finder);
|
||||
|
||||
await tester.enterText(finder.first, text);
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
Future<bool> pumpUntil(
|
||||
bool Function() condition, {
|
||||
Duration timeout = const Duration(seconds: 30),
|
||||
Duration step = const Duration(milliseconds: 100),
|
||||
}) async {
|
||||
final endTime = DateTime.now().add(timeout);
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
await _pumpStep(step);
|
||||
|
||||
if (condition()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> goBack() async {
|
||||
final navigator = tester.state<NavigatorState>(find.byType(Navigator).last);
|
||||
navigator.pop();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
Future<void> dismissModal() async {
|
||||
final navigator = tester.state<NavigatorState>(find.byType(Navigator).first);
|
||||
navigator.pop();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
// We make this bounded instead of pumpAndSettle
|
||||
Future<void> settle({Duration max = const Duration(seconds: 3)}) async {
|
||||
final endTime = DateTime.now().add(max);
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
await _pumpStep(const Duration(milliseconds: 100));
|
||||
|
||||
if (!tester.binding.hasScheduledFrame) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> isSpecificPage<T>({Duration timeout = const Duration(seconds: 30)}) async {
|
||||
await pumpUntilFound(find.byType(T), timeout: timeout);
|
||||
|
||||
await settle();
|
||||
|
||||
hasType<T>();
|
||||
}
|
||||
|
||||
bool isKeyPresent(String key) => tester.any(find.byKey(ValueKey(key)));
|
||||
|
||||
String describeScreen() {
|
||||
final names = tester.allWidgets
|
||||
.map((widget) => widget.runtimeType.toString())
|
||||
.where((name) => name.endsWith("Page") || name.endsWith("Sheet") || name.endsWith("Screen"))
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort();
|
||||
|
||||
return names.join(", ");
|
||||
}
|
||||
|
||||
void hasType<T>() {
|
||||
expect(find.byType(T), findsOneWidget);
|
||||
}
|
||||
|
||||
void hasText(String text) {
|
||||
expect(find.text(text), findsOneWidget);
|
||||
}
|
||||
|
||||
void hasTextAtLeastOnce(String text) {
|
||||
expect(find.text(text), findsAny);
|
||||
}
|
||||
|
||||
String? textByKey(String key) {
|
||||
final finder = find.byKey(ValueKey(key));
|
||||
|
||||
if (!tester.any(finder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tester.widget<Text>(finder.first).data;
|
||||
}
|
||||
|
||||
static bool _surfaceConverted = false;
|
||||
|
||||
Future<void> takeScreenshot(String name) async {
|
||||
try {
|
||||
final binding = tester.binding;
|
||||
|
||||
if (binding is! IntegrationTestWidgetsFlutterBinding) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_surfaceConverted) {
|
||||
await binding.convertFlutterSurfaceToImage();
|
||||
_surfaceConverted = true;
|
||||
}
|
||||
|
||||
var finished = false;
|
||||
|
||||
unawaited(
|
||||
binding.takeScreenshot("${name}_${DateTime.now().millisecondsSinceEpoch}").then(
|
||||
(_) => finished = true,
|
||||
onError: (Object error) {
|
||||
finished = true;
|
||||
tester.printToConsole("Screenshot $name failed: $error");
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final deadline = DateTime.now().add(const Duration(seconds: 10));
|
||||
|
||||
while (!finished && DateTime.now().isBefore(deadline)) {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
if (!finished) {
|
||||
tester.printToConsole("Screenshot $name gave up after 10s");
|
||||
}
|
||||
} catch (e) {
|
||||
tester.printToConsole("Screenshot $name failed: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
String? benignErrorKind(String message) {
|
||||
if (_toleratedAssertions.any(message.contains)) {
|
||||
return "assertion";
|
||||
}
|
||||
|
||||
if (_toleratedAssets.any(message.contains)) {
|
||||
return "asset";
|
||||
}
|
||||
|
||||
if (_toleratedNetworkNoise.any(message.contains)) {
|
||||
return "network";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Debug build assertions. The semantics ones are real accessibility bugs that already
|
||||
// exist on dev, take them off this list once they are fixed.
|
||||
const List<String> _toleratedAssertions = [
|
||||
"overflowed by",
|
||||
"minValue, and maxValue must be valid numbers",
|
||||
"node.parent?._dirty",
|
||||
"RenderBox was not laid out",
|
||||
];
|
||||
|
||||
// HashImageWidget reports these before falling back to the svg
|
||||
const List<String> _toleratedAssets = [
|
||||
"Unable to load asset",
|
||||
"NetworkImageLoadException",
|
||||
"Failed to load network image",
|
||||
];
|
||||
|
||||
// The same failures exception_handler.dart ignores
|
||||
const List<String> _toleratedNetworkNoise = [
|
||||
"SocketException",
|
||||
"HttpException",
|
||||
"ClientException",
|
||||
"HandshakeException",
|
||||
"TimeoutException",
|
||||
"Failed host lookup",
|
||||
"Connection closed",
|
||||
"Connection reset by peer",
|
||||
"Connection refused",
|
||||
"Connection timed out",
|
||||
"Operation timed out",
|
||||
"Network is unreachable",
|
||||
"No route to host",
|
||||
"Broken pipe",
|
||||
"Software caused connection abort",
|
||||
"Connection closed before full header",
|
||||
];
|
||||
@@ -0,0 +1,6 @@
|
||||
class FundedWallet {
|
||||
const FundedWallet({required this.seed, this.passphrase = ""});
|
||||
|
||||
final String seed;
|
||||
final String passphrase;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import "funded_wallet.dart";
|
||||
|
||||
// Empty on purpose and it has to stay empty in git. The manual funds workflow overwrites
|
||||
// this file from the FUNDS_SECRETS_FILE secret, keyed by WalletType name.
|
||||
const Map<String, List<FundedWallet>> fundedWallets = {};
|
||||
@@ -0,0 +1,46 @@
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
class FundsOutcome {
|
||||
static const marker = "FUNDS_RESULT";
|
||||
|
||||
static void ok(WidgetTester tester, String chain, String detail) =>
|
||||
_write(tester, chain, "ok", detail);
|
||||
|
||||
static void empty(WidgetTester tester, String chain, String detail) =>
|
||||
_write(tester, chain, "empty", detail);
|
||||
|
||||
static void waiting(WidgetTester tester, String chain, String detail) =>
|
||||
_write(tester, chain, "waiting", detail);
|
||||
|
||||
static void failed(WidgetTester tester, String chain, String detail) =>
|
||||
_write(tester, chain, statusFor(detail), detail);
|
||||
|
||||
static String statusFor(String detail) {
|
||||
if (_isWalletBalanceLow(detail)) {
|
||||
return "empty";
|
||||
}
|
||||
|
||||
return _isChainTooSlowToSync(detail) ? "waiting" : "failed";
|
||||
}
|
||||
|
||||
static bool needsFurtherReview(String detail) => statusFor(detail) == "failed";
|
||||
|
||||
static bool _isChainTooSlowToSync(String detail) =>
|
||||
detail.toLowerCase().contains("never became ready to send");
|
||||
|
||||
// Low wallet balance is not a failure, we just need to add fundsss
|
||||
static bool _isWalletBalanceLow(String detail) {
|
||||
final wording = detail.toLowerCase();
|
||||
|
||||
return wording.contains("not enough") ||
|
||||
wording.contains("wrong balance") ||
|
||||
wording.contains("insufficient") ||
|
||||
wording.contains("no spendable");
|
||||
}
|
||||
|
||||
static void _write(WidgetTester tester, String chain, String status, String detail) {
|
||||
final clean = detail.replaceAll("\n", " ").replaceAll("|", "/").trim();
|
||||
|
||||
tester.printToConsole("$marker|$chain|$status|$clean");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import "package:hash_wallet/wallet_types.g.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
|
||||
import "test_wallets.dart";
|
||||
|
||||
class TestConfig {
|
||||
static final List<int> pin = [0, 8, 0, 1];
|
||||
|
||||
static const bool isCIBuild = bool.fromEnvironment("CI_BUILD");
|
||||
|
||||
static const String _walletTypesOverride = String.fromEnvironment("TEST_WALLET_TYPES");
|
||||
|
||||
// Solana and ethereum cover the cheap key-derivation chains,
|
||||
// bitcoin covers the electrum family and monero covers the native-library chains.
|
||||
static final List<WalletType> _representativeWalletTypes = [
|
||||
WalletType.solana,
|
||||
WalletType.ethereum,
|
||||
WalletType.bitcoin,
|
||||
WalletType.monero,
|
||||
];
|
||||
|
||||
static List<WalletType> get walletTypesUnderTest {
|
||||
if (_walletTypesOverride == "all") {
|
||||
return availableWalletTypes;
|
||||
}
|
||||
|
||||
if (_walletTypesOverride.isNotEmpty) {
|
||||
return _walletTypesOverride
|
||||
.split(",")
|
||||
.map((name) => WalletType.values.byName(name.trim()))
|
||||
.where(availableWalletTypes.contains)
|
||||
.toList();
|
||||
}
|
||||
|
||||
return _representativeWalletTypes.where(availableWalletTypes.contains).toList();
|
||||
}
|
||||
|
||||
static const String _fundsFlows = String.fromEnvironment("FLOWS", defaultValue: "all");
|
||||
|
||||
static const String _spend = String.fromEnvironment("SPEND", defaultValue: "false");
|
||||
|
||||
static const String _swapFrom = String.fromEnvironment("SWAP_FROM");
|
||||
|
||||
static const String swapReceive = String.fromEnvironment("SWAP_RECEIVE");
|
||||
|
||||
static WalletType swapDepositType(List<WalletType> funded) =>
|
||||
_swapFrom.isEmpty ? funded.first : _walletTypeByName(_swapFrom, "SWAP_FROM");
|
||||
|
||||
static bool shouldRunFundsFlow(String flow) => _fundsFlows == "all" || _fundsFlows == flow;
|
||||
|
||||
static bool shouldDryRun(String flow) => shouldRunFundsFlow(flow);
|
||||
|
||||
static bool shouldSpend(String flow) => _spend == "true" && shouldRunFundsFlow(flow);
|
||||
|
||||
static const String _fundedChainsOverride = String.fromEnvironment(
|
||||
"CHAINS",
|
||||
defaultValue: "auto",
|
||||
);
|
||||
|
||||
static List<WalletType> get fundedWalletTypesUnderTest {
|
||||
if (_fundedChainsOverride == "auto") {
|
||||
return TestWallets.fundedWalletTypes;
|
||||
}
|
||||
|
||||
return _fundedChainsOverride
|
||||
.split(",")
|
||||
.map((name) => _walletTypeByName(name, "CHAINS"))
|
||||
.where((type) => TestWallets.fundedWalletsFor(type).isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static const Map<String, String> _fundsSendAmounts = {
|
||||
"solana": "0.0001",
|
||||
"ethereum": "0.00001",
|
||||
"polygon": "0.01",
|
||||
"base": "0.00001",
|
||||
"arbitrum": "0.00001",
|
||||
"bsc": "0.000001",
|
||||
"tron": "1",
|
||||
"bitcoin": "0.00001",
|
||||
"litecoin": "0.001",
|
||||
"bitcoinCash": "0.0002",
|
||||
"dogecoin": "2",
|
||||
"monero": "0.0001",
|
||||
"wownero": "0.1",
|
||||
"zcash": "0.0001",
|
||||
"nano": "0.000001",
|
||||
"decred": "0.001",
|
||||
"zano": "0.001",
|
||||
};
|
||||
|
||||
static String fundsSendAmountFor(WalletType type) => _fundsSendAmounts[type.name] ?? "0.0001";
|
||||
|
||||
// Making this two hours to accommodate monero that takes a longgg time syncing
|
||||
// also, this would only affect the funded tests which need actual funds to test
|
||||
static const Duration walletSyncBudget = Duration(hours: 2);
|
||||
|
||||
static WalletType _walletTypeByName(String name, String input) {
|
||||
final trimmed = name.trim();
|
||||
|
||||
final type = WalletType.values.where((value) => value.name == trimmed).toList();
|
||||
|
||||
if (type.isEmpty) {
|
||||
throw ArgumentError(
|
||||
'Unknown wallet type "$trimmed" in $input, '
|
||||
"valid names: ${WalletType.values.map((value) => value.name).join(", ")}",
|
||||
);
|
||||
}
|
||||
|
||||
return type.first;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import "package:hash_wallet/.secrets.g.dart" as secrets;
|
||||
import "package:hash_wallet/wallet_types.g.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
|
||||
import "funded_wallet.dart";
|
||||
import "funded_wallets.dart";
|
||||
|
||||
class TestWallets {
|
||||
static List<WalletType> get fundedWalletTypes =>
|
||||
availableWalletTypes.where((type) => fundedWalletsFor(type).isNotEmpty).toList();
|
||||
|
||||
static List<FundedWallet> fundedWalletsFor(WalletType type) =>
|
||||
(fundedWallets[type.name] ?? []).where((wallet) => wallet.seed.trim().isNotEmpty).toList();
|
||||
|
||||
static String seedFor(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return secrets.moneroTestWalletSeeds;
|
||||
case WalletType.bitcoin:
|
||||
return secrets.bitcoinTestWalletSeeds;
|
||||
case WalletType.ethereum:
|
||||
return secrets.ethereumTestWalletSeeds;
|
||||
case WalletType.litecoin:
|
||||
return secrets.litecoinTestWalletSeeds;
|
||||
case WalletType.bitcoinCash:
|
||||
return secrets.bitcoinCashTestWalletSeeds;
|
||||
case WalletType.polygon:
|
||||
return secrets.polygonTestWalletSeeds;
|
||||
case WalletType.solana:
|
||||
return secrets.solanaTestWalletSeeds;
|
||||
case WalletType.base:
|
||||
return secrets.baseTestWalletSeeds;
|
||||
case WalletType.arbitrum:
|
||||
return secrets.arbitrumTestWalletSeeds;
|
||||
case WalletType.bsc:
|
||||
return secrets.bscTestWalletSeeds;
|
||||
case WalletType.robinhood:
|
||||
return secrets.robinhoodTestWalletSeeds;
|
||||
case WalletType.tron:
|
||||
return secrets.tronTestWalletSeeds;
|
||||
case WalletType.nano:
|
||||
return secrets.nanoTestWalletSeeds;
|
||||
case WalletType.wownero:
|
||||
return secrets.wowneroTestWalletSeeds;
|
||||
case WalletType.zano:
|
||||
return secrets.zanoTestWalletSeeds;
|
||||
case WalletType.decred:
|
||||
return secrets.decredTestWalletSeeds;
|
||||
case WalletType.dogecoin:
|
||||
return secrets.dogeTestWalletSeeds;
|
||||
case WalletType.zcash:
|
||||
return secrets.zcashTestWalletSeeds;
|
||||
case WalletType.none:
|
||||
case WalletType.haven:
|
||||
case WalletType.banano:
|
||||
throw Exception("No test wallet seed available for ${type.name}");
|
||||
}
|
||||
}
|
||||
|
||||
static String receiveAddressFor(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return secrets.moneroTestWalletReceiveAddress;
|
||||
case WalletType.bitcoin:
|
||||
return secrets.bitcoinTestWalletReceiveAddress;
|
||||
case WalletType.ethereum:
|
||||
return secrets.ethereumTestWalletReceiveAddress;
|
||||
case WalletType.litecoin:
|
||||
return secrets.litecoinTestWalletReceiveAddress;
|
||||
case WalletType.bitcoinCash:
|
||||
return secrets.bitcoinCashTestWalletReceiveAddress;
|
||||
case WalletType.polygon:
|
||||
return secrets.polygonTestWalletReceiveAddress;
|
||||
case WalletType.base:
|
||||
return secrets.baseTestWalletReceiveAddress;
|
||||
case WalletType.arbitrum:
|
||||
return secrets.arbitrumTestWalletReceiveAddress;
|
||||
case WalletType.bsc:
|
||||
return secrets.bscTestWalletReceiveAddress;
|
||||
case WalletType.robinhood:
|
||||
return secrets.robinhoodTestWalletReceiveAddress;
|
||||
case WalletType.solana:
|
||||
return secrets.solanaTestWalletReceiveAddress;
|
||||
case WalletType.tron:
|
||||
return secrets.tronTestWalletReceiveAddress;
|
||||
case WalletType.nano:
|
||||
return secrets.nanoTestWalletReceiveAddress;
|
||||
case WalletType.wownero:
|
||||
return secrets.wowneroTestWalletReceiveAddress;
|
||||
case WalletType.zano:
|
||||
case WalletType.decred:
|
||||
case WalletType.dogecoin:
|
||||
case WalletType.zcash:
|
||||
case WalletType.none:
|
||||
case WalletType.haven:
|
||||
case WalletType.banano:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
static String get moneroLegacySeed => secrets.moneroLegacyTestWalletSeeds;
|
||||
|
||||
static String get moneroRestoreBlockHeight => secrets.moneroTestWalletBlockHeight;
|
||||
|
||||
static String get zcashRestoreHeight => secrets.zcashTestWalletRestoreHeight;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/test_config.dart";
|
||||
import "../robots/auth_page_robot.dart";
|
||||
|
||||
class AuthFlows {
|
||||
AuthFlows(this.tester) : _authPageRobot = AuthPageRobot(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final AuthPageRobot _authPageRobot;
|
||||
|
||||
Future<void> authenticateWithPin({List<int>? pin, bool required = true}) async {
|
||||
final prompted = await _waitForPinWidget(
|
||||
timeout: required ? const Duration(seconds: 30) : const Duration(seconds: 5),
|
||||
);
|
||||
|
||||
if (!prompted) {
|
||||
expect(required, false, reason: "The pin was never asked for");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await _authPageRobot.enterPinCode(pin ?? TestConfig.pin);
|
||||
}
|
||||
|
||||
bool isPinWidgetShown() => _authPageRobot.onAuthPage();
|
||||
|
||||
Future<bool> _waitForPinWidget({Duration timeout = const Duration(seconds: 30)}) async {
|
||||
final endTime = DateTime.now().add(timeout);
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
|
||||
if (_authPageRobot.onAuthPage()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/sync_status.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/test_config.dart";
|
||||
import "../core/test_wallets.dart";
|
||||
import "../robots/home_page_robot.dart";
|
||||
import "../robots/new_dashboard_robot.dart";
|
||||
import "../robots/new_send_page_robot.dart";
|
||||
import "onboarding_flows.dart";
|
||||
|
||||
class FundsFlows {
|
||||
FundsFlows(this.tester)
|
||||
: _onboardingFlows = OnboardingFlows(tester),
|
||||
_dashboardRobot = NewDashboardRobot(tester),
|
||||
_homePageRobot = HomePageRobot(tester),
|
||||
_sendRobot = NewSendPageRobot(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final OnboardingFlows _onboardingFlows;
|
||||
final NewDashboardRobot _dashboardRobot;
|
||||
final HomePageRobot _homePageRobot;
|
||||
final NewSendPageRobot _sendRobot;
|
||||
|
||||
bool _hasRestoredAnyWallet = false;
|
||||
|
||||
Future<bool> openFundedWallet(WalletType type) async {
|
||||
final wallets = TestWallets.fundedWalletsFor(type);
|
||||
|
||||
for (int index = 0; index < wallets.length; index++) {
|
||||
final wallet = wallets[index];
|
||||
|
||||
tester.printToConsole("Trying funded wallet ${index + 1}/${wallets.length} for ${type.name}");
|
||||
|
||||
if (_hasRestoredAnyWallet) {
|
||||
await _dashboardRobot.openWalletsTab();
|
||||
|
||||
final isWalletListReady = await _dashboardRobot.pumpUntil(
|
||||
() =>
|
||||
tester.any(find.byKey(const ValueKey("wallet_list_page_restore_wallet_button_key"))),
|
||||
timeout: const Duration(seconds: 60),
|
||||
);
|
||||
|
||||
expect(
|
||||
isWalletListReady,
|
||||
true,
|
||||
reason: "The wallet list restore button didn't show before ${type.name}, "
|
||||
"Currently have: ${_dashboardRobot.describeScreen()} on screen",
|
||||
);
|
||||
|
||||
await _onboardingFlows.restoreAdditionalWalletFromWalletList(
|
||||
type,
|
||||
seed: wallet.seed,
|
||||
passphrase: wallet.passphrase,
|
||||
);
|
||||
} else {
|
||||
await _onboardingFlows.restoreFirstWalletFromSeed(
|
||||
type,
|
||||
seed: wallet.seed,
|
||||
passphrase: wallet.passphrase,
|
||||
);
|
||||
}
|
||||
|
||||
_hasRestoredAnyWallet = true;
|
||||
|
||||
await _waitForDashboard(type);
|
||||
await _homePageRobot.isDisplayed();
|
||||
|
||||
if (await _hasSpendableBalance()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
tester.printToConsole("Wallet ${index + 1} for ${type.name} has no spendable balance");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> returnToHome({int maxPops = 6}) async {
|
||||
for (int attempt = 0; attempt <= maxPops; attempt++) {
|
||||
if (await _homePageRobot.pumpUntil(
|
||||
() => _isHomeInFront,
|
||||
timeout: const Duration(seconds: 3),
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
await _homePageRobot.dismissModal();
|
||||
} catch (e) {
|
||||
tester.printToConsole("Nothing left to pop while returning home: $e");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _isHomeInFront;
|
||||
}
|
||||
|
||||
Future<void> _waitForDashboard(WalletType type) async {
|
||||
final reached = await _dashboardRobot.pumpUntil(
|
||||
() => tester.any(find.byKey(const ValueKey("new_dashboard_page_key"))),
|
||||
timeout: TestConfig.walletSyncBudget,
|
||||
);
|
||||
|
||||
if (reached) {
|
||||
return;
|
||||
}
|
||||
|
||||
final status = getIt.get<AppStore>().wallet?.syncStatus;
|
||||
final stillSyncing = status is SyncingSyncStatus || status is SyncronizingSyncStatus;
|
||||
|
||||
throw TestFailure(
|
||||
stillSyncing
|
||||
? "${type.name} never became ready to send, it is still syncing (${status.runtimeType})"
|
||||
: "${type.name} never reached the dashboard, its status was ${status.runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
bool get _isHomeInFront => _homePageRobot.isInFront && !_sendRobot.isSendFlowOpen;
|
||||
|
||||
Future<bool> _hasSpendableBalance({Duration timeout = TestConfig.walletSyncBudget}) async {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final endTime = DateTime.now().add(timeout);
|
||||
DateTime? syncedSince;
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
|
||||
final wallet = appStore.wallet;
|
||||
|
||||
if (wallet == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wallet.balance.values.any((balance) => balance.available.sign > 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wallet.syncStatus is SyncedSyncStatus) {
|
||||
syncedSince ??= DateTime.now();
|
||||
|
||||
if (DateTime.now().difference(syncedSince) > const Duration(seconds: 15)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
syncedSince = null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
import "dart:io";
|
||||
|
||||
import "package:hash_wallet/entities/seed_type.dart";
|
||||
import "package:hash_wallet/reactions/wallet_utils.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/test_config.dart";
|
||||
import "../core/test_wallets.dart";
|
||||
import "../robots/create_pin_welcome_page_robot.dart";
|
||||
import "../robots/lightning_username_page_robot.dart";
|
||||
import "../robots/new_wallet_page_robot.dart";
|
||||
import "../robots/new_wallet_type_page_robot.dart";
|
||||
import "../robots/pre_seed_page_robot.dart";
|
||||
import "../robots/restore_from_seed_or_key_robot.dart";
|
||||
import "../robots/restore_options_page_robot.dart";
|
||||
import "../robots/seed_verification_page_robot.dart";
|
||||
import "../robots/setup_pin_code_robot.dart";
|
||||
import "../robots/wallet_group_description_page_robot.dart";
|
||||
import "../robots/wallet_group_existing_seed_page_robot.dart";
|
||||
import "../robots/wallet_groups_display_page_robot.dart";
|
||||
import "../robots/wallet_list_page_robot.dart";
|
||||
import "../robots/wallet_seed_page_robot.dart";
|
||||
import "../robots/welcome_page_robot.dart";
|
||||
|
||||
class OnboardingFlows {
|
||||
OnboardingFlows(this.tester)
|
||||
: _welcomePageRobot = WelcomePageRobot(tester),
|
||||
_preSeedPageRobot = PreSeedPageRobot(tester),
|
||||
_setupPinCodeRobot = SetupPinCodeRobot(tester),
|
||||
_newWalletPageRobot = NewWalletPageRobot(tester),
|
||||
_walletSeedPageRobot = WalletSeedPageRobot(tester),
|
||||
_walletListPageRobot = WalletListPageRobot(tester),
|
||||
_newWalletTypePageRobot = NewWalletTypePageRobot(tester),
|
||||
_seedVerificationPageRobot = SeedVerificationPageRobot(tester),
|
||||
_createPinWelcomePageRobot = CreatePinWelcomePageRobot(tester),
|
||||
_restoreOptionsPageRobot = RestoreOptionsPageRobot(tester),
|
||||
_restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester),
|
||||
_lightningUsernamePageRobot = LightningUsernamePageRobot(tester),
|
||||
_walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(tester),
|
||||
_walletGroupsDisplayPageRobot = WalletGroupsDisplayPageRobot(tester),
|
||||
_walletGroupExistingSeedPageRobot = WalletGroupExistingSeedPageRobot(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
|
||||
final WelcomePageRobot _welcomePageRobot;
|
||||
final PreSeedPageRobot _preSeedPageRobot;
|
||||
final SetupPinCodeRobot _setupPinCodeRobot;
|
||||
final NewWalletPageRobot _newWalletPageRobot;
|
||||
final WalletSeedPageRobot _walletSeedPageRobot;
|
||||
final WalletListPageRobot _walletListPageRobot;
|
||||
final NewWalletTypePageRobot _newWalletTypePageRobot;
|
||||
final SeedVerificationPageRobot _seedVerificationPageRobot;
|
||||
final CreatePinWelcomePageRobot _createPinWelcomePageRobot;
|
||||
final RestoreOptionsPageRobot _restoreOptionsPageRobot;
|
||||
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
|
||||
final LightningUsernamePageRobot _lightningUsernamePageRobot;
|
||||
final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot;
|
||||
final WalletGroupsDisplayPageRobot _walletGroupsDisplayPageRobot;
|
||||
final WalletGroupExistingSeedPageRobot _walletGroupExistingSeedPageRobot;
|
||||
|
||||
Future<void> createFirstWallet(WalletType type, {List<int>? pin}) async {
|
||||
await _createPinWelcomePageRobot.tapSetAPinButton();
|
||||
|
||||
await setupPinCode(pin ?? TestConfig.pin);
|
||||
|
||||
await _welcomePageRobot.navigateToCreateNewWalletPage();
|
||||
|
||||
await _selectWalletType(type);
|
||||
|
||||
if (_welcomePageRobot.hasNewSingleSeedButton()) {
|
||||
await _welcomePageRobot.tapNewSingleSeed();
|
||||
}
|
||||
|
||||
await _completeWalletCreationSteps(type);
|
||||
}
|
||||
|
||||
Future<void> createAdditionalWalletFromWalletList(WalletType type) async {
|
||||
tester.printToConsole("Creating ${type.name} wallet");
|
||||
|
||||
await startCreatingWalletFromWalletList(type);
|
||||
|
||||
await _completeWalletCreationSteps(type);
|
||||
}
|
||||
|
||||
Future<void> createWalletInGroupOf(WalletType type, String existingWalletName) async {
|
||||
tester.printToConsole("Adding a ${type.name} wallet to the seed of $existingWalletName");
|
||||
|
||||
await _walletListPageRobot.navigateToCreateNewWalletPage();
|
||||
|
||||
await _selectWalletType(type);
|
||||
|
||||
await _walletGroupDescriptionPageRobot.isDisplayed();
|
||||
await _walletGroupDescriptionPageRobot.navigateToChooseWalletGroup();
|
||||
|
||||
await _walletGroupsDisplayPageRobot.isDisplayed();
|
||||
await _walletGroupsDisplayPageRobot.selectWallet(existingWalletName);
|
||||
await _walletGroupsDisplayPageRobot.continueWithSelectedSeed();
|
||||
|
||||
await _newWalletPageRobot.isDisplayed();
|
||||
await _newWalletPageRobot.generateWalletName();
|
||||
await _newWalletPageRobot.onNextButtonPressed();
|
||||
|
||||
await _walletGroupExistingSeedPageRobot.isDisplayed();
|
||||
await _walletGroupExistingSeedPageRobot.openWallet();
|
||||
}
|
||||
|
||||
Future<void> startCreatingWalletFromWalletList(WalletType type) async {
|
||||
await _walletListPageRobot.navigateToCreateNewWalletPage();
|
||||
|
||||
await _selectWalletType(type);
|
||||
|
||||
if (isBIP39Wallet(type)) {
|
||||
await _walletGroupDescriptionPageRobot.isDisplayed();
|
||||
await _walletGroupDescriptionPageRobot.navigateToCreateNewSeedPage();
|
||||
}
|
||||
|
||||
await _newWalletPageRobot.isDisplayed();
|
||||
}
|
||||
|
||||
Future<void> restoreFirstWalletFromSeed(
|
||||
WalletType type, {
|
||||
String? seed,
|
||||
String passphrase = "",
|
||||
List<int>? pin,
|
||||
}) async {
|
||||
await startRestoringFirstWallet(type, pin: pin);
|
||||
|
||||
await _restoreFromSeed(type, seed ?? TestWallets.seedFor(type), passphrase);
|
||||
}
|
||||
|
||||
Future<void> startRestoringFirstWallet(WalletType type, {List<int>? pin}) async {
|
||||
await _createPinWelcomePageRobot.tapSetAPinButton();
|
||||
|
||||
await setupPinCode(pin ?? TestConfig.pin);
|
||||
|
||||
await _welcomePageRobot.navigateToRestoreWalletPage();
|
||||
|
||||
// Desktop skips the restore options page and goes straight to the type list.
|
||||
if (!Platform.isLinux) {
|
||||
await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
|
||||
}
|
||||
|
||||
await _selectWalletType(type);
|
||||
|
||||
await _restoreFromSeedOrKeysPageRobot.isDisplayed();
|
||||
}
|
||||
|
||||
Future<void> restoreAdditionalWalletFromWalletList(
|
||||
WalletType type, {
|
||||
String? seed,
|
||||
String passphrase = "",
|
||||
}) async {
|
||||
tester.printToConsole("Restoring ${type.name} wallet");
|
||||
|
||||
await _walletListPageRobot.navigateToRestoreWalletOptionsPage();
|
||||
|
||||
if (!Platform.isLinux) {
|
||||
await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
|
||||
}
|
||||
|
||||
await _selectWalletType(type);
|
||||
|
||||
await _restoreFromSeed(type, seed ?? TestWallets.seedFor(type), passphrase);
|
||||
}
|
||||
|
||||
Future<void> _restoreFromSeed(WalletType type, String seed, String passphrase) async {
|
||||
await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions();
|
||||
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(seed);
|
||||
|
||||
if (passphrase.isNotEmpty) {
|
||||
await _restoreFromSeedOrKeysPageRobot.enablePassphrase();
|
||||
}
|
||||
|
||||
// 25 word monero seeds are legacy seeds and need their restore block height entered.
|
||||
if (seed.split(" ").length == 25 && type == WalletType.monero) {
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy);
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.enterBlockHeightForWalletRestore(TestWallets.moneroRestoreBlockHeight);
|
||||
}
|
||||
|
||||
if (type == WalletType.zcash && TestWallets.zcashRestoreHeight.isNotEmpty) {
|
||||
await _restoreFromSeedOrKeysPageRobot
|
||||
.enterBlockHeightForWalletRestore(TestWallets.zcashRestoreHeight);
|
||||
}
|
||||
|
||||
if (Platform.isLinux) {
|
||||
final password = TestConfig.pin.join("");
|
||||
await _restoreFromSeedOrKeysPageRobot.enterPasswordForWalletRestore(password);
|
||||
await _restoreFromSeedOrKeysPageRobot.enterPasswordRepeatForWalletRestore(password);
|
||||
}
|
||||
|
||||
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
|
||||
|
||||
if (passphrase.isNotEmpty) {
|
||||
await _restoreFromSeedOrKeysPageRobot.enterPassphrase(passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setupPinCode(List<int> pin) async {
|
||||
// Desktop builds take a wallet password on the new wallet form instead of a pin.
|
||||
if (Platform.isLinux) {
|
||||
return;
|
||||
}
|
||||
|
||||
await _setupPinCodeRobot.isDisplayed();
|
||||
|
||||
await _setupPinCodeRobot.enterPinCode(pin);
|
||||
await _setupPinCodeRobot.enterPinCode(pin);
|
||||
await _setupPinCodeRobot.tapSuccessButton();
|
||||
}
|
||||
|
||||
Future<void> _selectWalletType(WalletType type) async {
|
||||
await _newWalletTypePageRobot.isDisplayed();
|
||||
|
||||
await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type);
|
||||
await _newWalletTypePageRobot.selectWalletType(type);
|
||||
}
|
||||
|
||||
Future<void> _completeWalletCreationSteps(WalletType type) async {
|
||||
await _generateNewWalletDetails();
|
||||
|
||||
await _preSeedPageRobot.isDisplayed();
|
||||
await _preSeedPageRobot.onConfirmButtonPressed();
|
||||
|
||||
await _confirmWalletSeedDetails();
|
||||
|
||||
await _verifyWalletSeed();
|
||||
|
||||
if (type == WalletType.bitcoin) {
|
||||
await _lightningUsernamePageRobot.isDisplayed();
|
||||
await _lightningUsernamePageRobot.tapSkip();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _generateNewWalletDetails() async {
|
||||
await _newWalletPageRobot.isDisplayed();
|
||||
|
||||
await _newWalletPageRobot.generateWalletName();
|
||||
|
||||
if (Platform.isLinux) {
|
||||
final password = TestConfig.pin.join("");
|
||||
await _restoreFromSeedOrKeysPageRobot.enterPasswordForWalletRestore(password);
|
||||
await _restoreFromSeedOrKeysPageRobot.enterPasswordRepeatForWalletRestore(password);
|
||||
}
|
||||
|
||||
await _newWalletPageRobot.onNextButtonPressed();
|
||||
}
|
||||
|
||||
Future<void> _confirmWalletSeedDetails() async {
|
||||
await _walletSeedPageRobot.isDisplayed();
|
||||
|
||||
_walletSeedPageRobot.confirmWalletDetailsDisplayCorrectly();
|
||||
_walletSeedPageRobot.confirmWalletSeedReminderDisplays();
|
||||
|
||||
await _walletSeedPageRobot.onVerifySeedButtonPressed();
|
||||
}
|
||||
|
||||
Future<void> _verifyWalletSeed() async {
|
||||
await _seedVerificationPageRobot.isDisplayed();
|
||||
|
||||
_seedVerificationPageRobot.hasTitle();
|
||||
|
||||
await _seedVerificationPageRobot.verifyWalletSeeds();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../robots/new_dashboard_robot.dart";
|
||||
import "auth_flows.dart";
|
||||
|
||||
class WalletFlows {
|
||||
WalletFlows(this.tester)
|
||||
: _authFlows = AuthFlows(tester),
|
||||
_dashboardRobot = NewDashboardRobot(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final AuthFlows _authFlows;
|
||||
final NewDashboardRobot _dashboardRobot;
|
||||
|
||||
Future<void> _expandWalletGroups({int maxGroups = 10}) async {
|
||||
for (int index = 0; index < maxGroups; index++) {
|
||||
final tile = find.byKey(ValueKey("group_wallets_expansion_tile_widget_$index"));
|
||||
|
||||
if (!tester.any(tile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.tap(tile, warnIfMissed: false);
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> switchToWallet(String name) async {
|
||||
await _dashboardRobot.openWalletsTab();
|
||||
|
||||
final walletFinder = find.text(name);
|
||||
|
||||
// Wallets sharing a seed are listed under their group's name, so the wallet itself is
|
||||
// not on screen until that group is opened.
|
||||
if (!tester.any(walletFinder)) {
|
||||
await _expandWalletGroups();
|
||||
}
|
||||
|
||||
await _dashboardRobot.pumpUntilFound(walletFinder);
|
||||
|
||||
await tester.tap(walletFinder.first, warnIfMissed: false);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
|
||||
await _authFlows.authenticateWithPin(required: false);
|
||||
|
||||
// The row does nothing at all when that wallet is already the open one, and the load
|
||||
// itself runs behind a progress overlay, so the store is the only thing that tells us
|
||||
// the switch happened.
|
||||
final switched = await _dashboardRobot.pumpUntil(
|
||||
() => getIt.get<AppStore>().wallet?.name == name,
|
||||
timeout: const Duration(minutes: 2),
|
||||
);
|
||||
|
||||
expect(switched, true, reason: "Wallet never became $name after tapping its row");
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'components/common_test_constants.dart';
|
||||
import 'components/common_test_flows.dart';
|
||||
import 'robots/auth_page_robot.dart';
|
||||
import 'robots/dashboard_page_robot.dart';
|
||||
import 'robots/exchange_confirm_page_robot.dart';
|
||||
import 'robots/exchange_page_robot.dart';
|
||||
import 'robots/exchange_trade_page_robot.dart';
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
DashboardPageRobot dashboardPageRobot;
|
||||
ExchangePageRobot exchangePageRobot;
|
||||
ExchangeConfirmPageRobot exchangeConfirmPageRobot;
|
||||
AuthPageRobot authPageRobot;
|
||||
ExchangeTradePageRobot exchangeTradePageRobot;
|
||||
CommonTestFlows commonTestFlows;
|
||||
|
||||
group('Startup Test', () {
|
||||
testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL',
|
||||
(tester) async {
|
||||
authPageRobot = AuthPageRobot(tester);
|
||||
exchangePageRobot = ExchangePageRobot(tester);
|
||||
dashboardPageRobot = DashboardPageRobot(tester);
|
||||
exchangeTradePageRobot = ExchangeTradePageRobot(tester);
|
||||
exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
|
||||
commonTestFlows = CommonTestFlows(tester);
|
||||
|
||||
await commonTestFlows.startAppFlow(ValueKey('funds_exchange_test_app_key'));
|
||||
|
||||
await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
|
||||
CommonTestConstants.testWalletType,
|
||||
secrets.solanaTestWalletSeeds,
|
||||
CommonTestConstants.pin,
|
||||
);
|
||||
|
||||
// ----------- RestoreFromSeedOrKeys Page -------------
|
||||
await dashboardPageRobot.navigateToExchangePage();
|
||||
|
||||
// ----------- Exchange Page -------------
|
||||
await exchangePageRobot.isExchangePage();
|
||||
exchangePageRobot.hasResetButton();
|
||||
await exchangePageRobot.displayBothExchangeCards();
|
||||
exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards();
|
||||
exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards();
|
||||
|
||||
await exchangePageRobot
|
||||
.selectDepositCurrency(CommonTestConstants.exchangeTestDepositCurrency);
|
||||
await exchangePageRobot
|
||||
.selectReceiveCurrency(CommonTestConstants.exchangeTestReceiveCurrency);
|
||||
|
||||
await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
|
||||
await exchangePageRobot.enterDepositRefundAddress(
|
||||
depositAddress: CommonTestConstants.testWalletAddress);
|
||||
|
||||
await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
|
||||
|
||||
await exchangePageRobot.onExchangeButtonPressed();
|
||||
|
||||
await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
|
||||
|
||||
final onAuthPage = authPageRobot.onAuthPage();
|
||||
if (onAuthPage) {
|
||||
await authPageRobot.enterPinCode(CommonTestConstants.pin);
|
||||
}
|
||||
|
||||
final onAuthPageDesktop = authPageRobot.onAuthPageDesktop();
|
||||
if (onAuthPageDesktop) {
|
||||
await authPageRobot.enterPassword(CommonTestConstants.pin.join(""));
|
||||
}
|
||||
|
||||
// ----------- Exchange Confirm Page -------------
|
||||
await exchangeConfirmPageRobot.isExchangeConfirmPage();
|
||||
|
||||
exchangeConfirmPageRobot.confirmComponentsOfTradeDisplayProperly();
|
||||
await exchangeConfirmPageRobot.confirmCopyTradeIdToClipBoardWorksProperly();
|
||||
await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
|
||||
|
||||
// ----------- Exchange Trade Page -------------
|
||||
await exchangeTradePageRobot.isExchangeTradePage();
|
||||
exchangeTradePageRobot.hasInformationDialog();
|
||||
await exchangeTradePageRobot.onGotItButtonPressed();
|
||||
|
||||
await exchangeTradePageRobot.onSendFromCakeButtonPressed();
|
||||
|
||||
await exchangeTradePageRobot.handleConfirmSendResult();
|
||||
|
||||
await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../core/app_launcher.dart";
|
||||
import "../core/funds_outcome.dart";
|
||||
import "../core/test_config.dart";
|
||||
import "../flows/auth_flows.dart";
|
||||
import "../flows/funds_flows.dart";
|
||||
import "../robots/home_page_robot.dart";
|
||||
import "../robots/new_send_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Every funded chain can build a transaction without sending it", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final authFlows = AuthFlows(tester);
|
||||
final fundsFlows = FundsFlows(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final sendRobot = NewSendPageRobot(tester);
|
||||
|
||||
if (!TestConfig.shouldDryRun("send")) {
|
||||
tester.printToConsole("FLOWS excludes send, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
final walletTypes = TestConfig.fundedWalletTypesUnderTest;
|
||||
|
||||
expect(walletTypes.isNotEmpty, true, reason: "No funded wallet seeds configured");
|
||||
|
||||
await appLauncher.launchApp(testKey: "send_dry_run_test_app_key");
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final failures = <String>[];
|
||||
|
||||
for (final type in walletTypes) {
|
||||
tester.printToConsole("Send dry run starting for ${type.name}");
|
||||
|
||||
try {
|
||||
final opened = await fundsFlows.openFundedWallet(type);
|
||||
|
||||
if (!opened) {
|
||||
tester.printToConsole("${type.name} has nothing spendable, top it up");
|
||||
FundsOutcome.empty(tester, type.name, "every funded wallet is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
expect(appStore.wallet?.type, type);
|
||||
|
||||
final ownAddress = appStore.wallet!.walletAddresses.address;
|
||||
|
||||
await homePageRobot.openSendSheet();
|
||||
await sendRobot.isDisplayed();
|
||||
|
||||
await sendRobot.enterAddress(ownAddress);
|
||||
await sendRobot.enterAmount(TestConfig.fundsSendAmountFor(type));
|
||||
await sendRobot.tapSendButtonWhenReady();
|
||||
|
||||
await authFlows.authenticateWithPin(required: false);
|
||||
|
||||
await sendRobot.confirmTransactionBuilt();
|
||||
|
||||
await sendRobot.confirmSendButtonReplacesSwiper();
|
||||
|
||||
tester.printToConsole("${type.name} built a transaction, leaving it unsent");
|
||||
FundsOutcome.ok(tester, type.name, "built and signed, not broadcast");
|
||||
|
||||
expect(
|
||||
await fundsFlows.returnToHome(),
|
||||
true,
|
||||
reason: "${type.name} finished but the send flow would not close",
|
||||
);
|
||||
} catch (e) {
|
||||
if (FundsOutcome.needsFurtherReview(e.toString())) {
|
||||
failures.add("${type.name}: $e");
|
||||
} else {
|
||||
tester.printToConsole("${type.name} needs attention rather than a fix: $e");
|
||||
}
|
||||
|
||||
FundsOutcome.failed(tester, type.name, e.toString());
|
||||
|
||||
if (!await fundsFlows.returnToHome()) {
|
||||
// The app is hanging or found itself somewhere we don't know,
|
||||
// no need to continue running anything else
|
||||
for (final skipped in walletTypes.skip(walletTypes.indexOf(type) + 1)) {
|
||||
FundsOutcome.waiting(
|
||||
tester,
|
||||
skipped.name,
|
||||
"never ran, ${type.name} left the app somewhere it could not come back from",
|
||||
);
|
||||
}
|
||||
|
||||
if (FundsOutcome.needsFurtherReview(e.toString())) {
|
||||
failures.add("${type.name}: could not get back to the home page, "
|
||||
"the chains after this one never ran");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(failures, isEmpty, reason: "Failed chains:\n${failures.join("\n")}");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../core/app_launcher.dart";
|
||||
import "../core/funds_outcome.dart";
|
||||
import "../core/test_config.dart";
|
||||
import "../flows/auth_flows.dart";
|
||||
import "../flows/funds_flows.dart";
|
||||
import "../robots/home_page_robot.dart";
|
||||
import "../robots/new_send_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// tests everything up to the point of swiping
|
||||
integrationTest("Send a real self transaction on every funded chain", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final fundsFlows = FundsFlows(tester);
|
||||
final authFlows = AuthFlows(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final sendRobot = NewSendPageRobot(tester);
|
||||
|
||||
if (!TestConfig.shouldSpend("send")) {
|
||||
tester.printToConsole("FLOWS excludes send, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
final walletTypes = TestConfig.fundedWalletTypesUnderTest;
|
||||
|
||||
expect(walletTypes.isNotEmpty, true, reason: "No funded wallet seeds configured");
|
||||
|
||||
await appLauncher.launchApp(testKey: "send_funds_test_app_key");
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final failures = <String>[];
|
||||
|
||||
for (final type in walletTypes) {
|
||||
tester.printToConsole("Funds send starting for ${type.name}");
|
||||
|
||||
try {
|
||||
final opened = await fundsFlows.openFundedWallet(type);
|
||||
|
||||
if (!opened) {
|
||||
tester.printToConsole("${type.name} has nothing spendable, top it up");
|
||||
FundsOutcome.empty(tester, type.name, "every funded wallet is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
expect(appStore.wallet?.type, type);
|
||||
|
||||
// Making it a self send so the funds remain inside the funded wallet, only fees are spent.
|
||||
final ownAddress = appStore.wallet!.walletAddresses.address;
|
||||
|
||||
await homePageRobot.openSendSheet();
|
||||
await sendRobot.isDisplayed();
|
||||
|
||||
await sendRobot.enterAddress(ownAddress);
|
||||
await sendRobot.enterAmount(TestConfig.fundsSendAmountFor(type));
|
||||
await sendRobot.tapSendButtonWhenReady();
|
||||
|
||||
await authFlows.authenticateWithPin(required: false);
|
||||
|
||||
await sendRobot.swipeToConfirm();
|
||||
await sendRobot.confirmTransactionCommitted();
|
||||
|
||||
// The send page sits under the confirm sheet, so one pop leaves it up and the next
|
||||
// chain would try to restore from behind it.
|
||||
expect(
|
||||
await fundsFlows.returnToHome(),
|
||||
true,
|
||||
reason: "${type.name} broadcast but the send flow would not close",
|
||||
);
|
||||
|
||||
tester.printToConsole("FUNDS_SEND_OK: ${type.name}");
|
||||
FundsOutcome.ok(tester, type.name, "self send broadcast");
|
||||
} catch (e) {
|
||||
if (FundsOutcome.needsFurtherReview(e.toString())) {
|
||||
failures.add("${type.name}: $e");
|
||||
} else {
|
||||
tester.printToConsole("${type.name} needs attention rather than a fix: $e");
|
||||
}
|
||||
|
||||
FundsOutcome.failed(tester, type.name, e.toString());
|
||||
tester.printToConsole("FUNDS_SEND_FAIL: ${type.name}: $e");
|
||||
|
||||
if (!await fundsFlows.returnToHome()) {
|
||||
for (final skipped in walletTypes.skip(walletTypes.indexOf(type) + 1)) {
|
||||
FundsOutcome.waiting(
|
||||
tester,
|
||||
skipped.name,
|
||||
"never ran, ${type.name} left the app somewhere it could not come back from",
|
||||
);
|
||||
}
|
||||
|
||||
if (FundsOutcome.needsFurtherReview(e.toString())) {
|
||||
failures.add("${type.name}: could not get back to the home page, "
|
||||
"the chains after this one never ran");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(failures, isEmpty, reason: "Failed chains:\n${failures.join("\n")}");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../core/app_launcher.dart";
|
||||
import "../core/funds_outcome.dart";
|
||||
import "../core/test_config.dart";
|
||||
import "../flows/funds_flows.dart";
|
||||
import "../robots/home_page_robot.dart";
|
||||
import "../robots/new_swap_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// tests everything up to the point of creating the trade
|
||||
integrationTest("Swap prices a real deposit without creating a trade", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final fundsFlows = FundsFlows(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final swapRobot = NewSwapPageRobot(tester);
|
||||
|
||||
if (!TestConfig.shouldDryRun("swap")) {
|
||||
tester.printToConsole("FLOWS excludes swap, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
final walletTypes = TestConfig.fundedWalletTypesUnderTest;
|
||||
|
||||
expect(walletTypes.isNotEmpty, true, reason: "No funded wallet seeds configured");
|
||||
|
||||
await appLauncher.launchApp(testKey: "swap_dry_run_test_app_key");
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
||||
final type = TestConfig.swapDepositType(walletTypes);
|
||||
|
||||
expect(
|
||||
walletTypes,
|
||||
contains(type),
|
||||
reason: "SWAP_FROM=${type.name} is not among the funded wallets under test",
|
||||
);
|
||||
|
||||
final opened = await fundsFlows.openFundedWallet(type);
|
||||
|
||||
expect(opened, true, reason: "Every funded wallet for ${type.name} came up empty");
|
||||
expect(appStore.wallet?.type, type);
|
||||
|
||||
await homePageRobot.openSwapSheet();
|
||||
await swapRobot.isDisplayed();
|
||||
|
||||
if (TestConfig.swapReceive.isNotEmpty) {
|
||||
await swapRobot.chooseReceiveCurrency(CryptoCurrency.fromString(TestConfig.swapReceive));
|
||||
}
|
||||
|
||||
await swapRobot.enterMinimumViableDepositAmount();
|
||||
await swapRobot.enterTestReceiveAddress();
|
||||
|
||||
await swapRobot.confirmQuoteReceived();
|
||||
await swapRobot.confirmSwapButtonEnabled();
|
||||
|
||||
tester.printToConsole("${type.name} got a quote and the swap button enabled, no trade created");
|
||||
FundsOutcome.ok(tester, type.name, "quoted, swap button enabled, no trade created");
|
||||
|
||||
await swapRobot.dismissModal();
|
||||
await homePageRobot.isDisplayed();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../core/app_launcher.dart";
|
||||
import "../core/funds_outcome.dart";
|
||||
import "../core/test_config.dart";
|
||||
import "../core/test_wallets.dart";
|
||||
import "../flows/auth_flows.dart";
|
||||
import "../flows/funds_flows.dart";
|
||||
import "../robots/home_page_robot.dart";
|
||||
import "../robots/new_send_page_robot.dart";
|
||||
import "../robots/new_swap_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Create a real swap and broadcast its deposit", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final fundsFlows = FundsFlows(tester);
|
||||
final authFlows = AuthFlows(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final sendRobot = NewSendPageRobot(tester);
|
||||
final swapRobot = NewSwapPageRobot(tester);
|
||||
|
||||
if (!TestConfig.shouldSpend("swap")) {
|
||||
tester.printToConsole("FLOWS excludes swap, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
final walletTypes = TestConfig.fundedWalletTypesUnderTest;
|
||||
|
||||
expect(walletTypes.isNotEmpty, true, reason: "No funded wallet seeds configured");
|
||||
|
||||
final type = TestConfig.swapDepositType(walletTypes);
|
||||
|
||||
expect(
|
||||
walletTypes,
|
||||
contains(type),
|
||||
reason: "SWAP_FROM=${type.name} is not among the funded wallets under test",
|
||||
);
|
||||
|
||||
await appLauncher.launchApp(testKey: "swap_funds_test_app_key");
|
||||
|
||||
final opened = await fundsFlows.openFundedWallet(type);
|
||||
|
||||
expect(
|
||||
opened,
|
||||
true,
|
||||
reason: "${type.name}: none of the ${TestWallets.fundedWalletsFor(type).length} funded "
|
||||
"wallets has a spendable balance, top them up before the next run",
|
||||
);
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
expect(appStore.wallet?.type, type);
|
||||
|
||||
await homePageRobot.openSwapSheet();
|
||||
await swapRobot.isDisplayed();
|
||||
|
||||
if (TestConfig.swapReceive.isNotEmpty) {
|
||||
await swapRobot.chooseReceiveCurrency(CryptoCurrency.fromString(TestConfig.swapReceive));
|
||||
}
|
||||
|
||||
await swapRobot.enterMinimumViableDepositAmount();
|
||||
final payoutAddress = await swapRobot.enterTestReceiveAddress();
|
||||
await swapRobot.confirmQuoteReceived();
|
||||
await swapRobot.confirmSwapButtonEnabled();
|
||||
|
||||
await swapRobot.tapSwapButton();
|
||||
|
||||
await authFlows.authenticateWithPin(required: false);
|
||||
|
||||
await swapRobot.confirmTradeCreated();
|
||||
swapRobot.confirmPayoutAddress(payoutAddress);
|
||||
|
||||
await sendRobot.swipeToConfirm();
|
||||
await swapRobot.confirmDepositCommitted();
|
||||
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
tester.printToConsole("FUNDS_SWAP_OK: ${type.name}");
|
||||
FundsOutcome.ok(tester, type.name, "trade created and deposit broadcast");
|
||||
});
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:hash_wallet/core/auth_service.dart';
|
||||
import 'package:hash_wallet/core/secure_storage.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/store/authentication_store.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:hash_wallet/store/wallet_list_store.dart';
|
||||
import 'package:hash_wallet/view_model/link_view_model.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockAppStore extends Mock implements AppStore {}
|
||||
|
||||
class MockAuthService extends Mock implements AuthService {}
|
||||
|
||||
class MockSettingsStore extends Mock implements SettingsStore {}
|
||||
|
||||
class MockAuthenticationStore extends Mock implements AuthenticationStore {}
|
||||
|
||||
class MockWalletListStore extends Mock implements WalletListStore {}
|
||||
|
||||
class MockLinkViewModel extends Mock implements LinkViewModel {}
|
||||
|
||||
class MockHiveInterface extends Mock implements HiveInterface {}
|
||||
|
||||
class MockHiveBox extends Mock implements Box<dynamic> {}
|
||||
|
||||
class MockSecureStorage extends Mock implements SecureStorage {}
|
||||
@@ -1,99 +0,0 @@
|
||||
import 'package:hash_wallet/core/auth_service.dart';
|
||||
import 'package:hash_wallet/core/secure_storage.dart';
|
||||
import 'package:hash_wallet/di.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:hash_wallet/store/authentication_store.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:hash_wallet/store/wallet_list_store.dart';
|
||||
import 'package:hash_wallet/view_model/link_view_model.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
|
||||
class TestHelpers {
|
||||
static void setup() {
|
||||
// Fallback values can also be declared here
|
||||
registerDependencies();
|
||||
}
|
||||
|
||||
static void registerDependencies() {
|
||||
getAndRegisterAppStore();
|
||||
getAndRegisterAuthService();
|
||||
getAndRegisterSettingsStore();
|
||||
getAndRegisterAuthenticationStore();
|
||||
getAndRegisterWalletListStore();
|
||||
|
||||
getAndRegisterLinkViewModel();
|
||||
getAndRegisterSecureStorage();
|
||||
getAndRegisterHiveInterface();
|
||||
}
|
||||
|
||||
static MockSettingsStore getAndRegisterSettingsStore() {
|
||||
_removeRegistrationIfExists<SettingsStore>();
|
||||
final service = MockSettingsStore();
|
||||
getIt.registerSingleton<SettingsStore>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static MockAppStore getAndRegisterAppStore() {
|
||||
_removeRegistrationIfExists<AppStore>();
|
||||
final service = MockAppStore();
|
||||
final settingsStore = getAndRegisterSettingsStore();
|
||||
|
||||
when(() => service.settingsStore).thenAnswer((invocation) => settingsStore);
|
||||
getIt.registerSingleton<AppStore>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static MockAuthService getAndRegisterAuthService() {
|
||||
_removeRegistrationIfExists<AuthService>();
|
||||
final service = MockAuthService();
|
||||
getIt.registerSingleton<AuthService>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static MockAuthenticationStore getAndRegisterAuthenticationStore() {
|
||||
_removeRegistrationIfExists<AuthenticationStore>();
|
||||
final service = MockAuthenticationStore();
|
||||
when(() => service.state).thenReturn(AuthenticationState.uninitialized);
|
||||
getIt.registerSingleton<AuthenticationStore>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static MockWalletListStore getAndRegisterWalletListStore() {
|
||||
_removeRegistrationIfExists<WalletListStore>();
|
||||
final service = MockWalletListStore();
|
||||
getIt.registerSingleton<WalletListStore>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static MockLinkViewModel getAndRegisterLinkViewModel() {
|
||||
_removeRegistrationIfExists<LinkViewModel>();
|
||||
final service = MockLinkViewModel();
|
||||
getIt.registerSingleton<LinkViewModel>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static MockHiveInterface getAndRegisterHiveInterface() {
|
||||
_removeRegistrationIfExists<HiveInterface>();
|
||||
final service = MockHiveInterface();
|
||||
getIt.registerSingleton<HiveInterface>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static MockSecureStorage getAndRegisterSecureStorage() {
|
||||
_removeRegistrationIfExists<SecureStorage>();
|
||||
final service = MockSecureStorage();
|
||||
getIt.registerSingleton<SecureStorage>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
static void _removeRegistrationIfExists<T extends Object>() {
|
||||
if (getIt.isRegistered<T>()) {
|
||||
getIt.unregister<T>();
|
||||
}
|
||||
}
|
||||
|
||||
static void tearDown() => getIt.reset();
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
null
|
||||
@@ -1,36 +1,20 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/auth/auth_page.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/auth/auth_page.dart";
|
||||
import "package:flutter/widgets.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import 'pin_code_widget_robot.dart';
|
||||
import "pin_code_widget_robot.dart";
|
||||
|
||||
class AuthPageRobot extends PinCodeWidgetRobot {
|
||||
AuthPageRobot(this.tester)
|
||||
: commonTestCases = CommonTestCases(tester),
|
||||
super(tester);
|
||||
AuthPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<AuthPage>();
|
||||
}
|
||||
|
||||
bool onAuthPage() {
|
||||
final hasPinButtons = find.byKey(ValueKey('pin_code_button_3_key'));
|
||||
final hasPinButtons = find.byKey(const ValueKey("pin_code_button_3_key"));
|
||||
final hasPin = hasPinButtons.tryEvaluate();
|
||||
return hasPin;
|
||||
}
|
||||
|
||||
bool onAuthPageDesktop() {
|
||||
final hasWalletPasswordInput = find.byKey(ValueKey('enter_wallet_password'));
|
||||
return hasWalletPasswordInput.tryEvaluate();
|
||||
}
|
||||
|
||||
Future<void> isAuthPage() async {
|
||||
await commonTestCases.isSpecificPage<AuthPage>();
|
||||
await commonTestCases.takeScreenshots('auth_page');
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
commonTestCases.hasText(S.current.setup_pin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import "package:hash_wallet/entities/fiat_api_mode.dart";
|
||||
import "package:hash_wallet/src/screens/settings/connection_sync_page.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class ConnectionSyncPageRobot extends BaseRobot {
|
||||
ConnectionSyncPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<ConnectionSyncPage>();
|
||||
}
|
||||
|
||||
Future<void> setFiatApiMode(FiatApiMode mode) async {
|
||||
await tapByKey("fiat_api");
|
||||
|
||||
final unselected = find.byKey(ValueKey("picker_items_index_${mode.title}_button_key"));
|
||||
final selected =
|
||||
find.byKey(ValueKey("picker_items_index_${mode.title}_selected_item_button_key"));
|
||||
|
||||
await pumpUntil(() => tester.any(unselected) || tester.any(selected));
|
||||
|
||||
await tester.tap(tester.any(unselected) ? unselected : selected);
|
||||
|
||||
await settle();
|
||||
}
|
||||
|
||||
void hasFiatApiMode(FiatApiMode mode) {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byKey(const ValueKey("fiat_api")),
|
||||
matching: find.text(mode.title),
|
||||
),
|
||||
findsOneWidget,
|
||||
reason: "The fiat api row does not show ${mode.title}",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import "package:hash_wallet/src/screens/contact/contact_list_page.dart";
|
||||
import "package:hash_wallet/src/screens/contact/contact_page.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class ContactRobot extends BaseRobot {
|
||||
ContactRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await pumpUntilFound(find.byType(ContactListPage));
|
||||
}
|
||||
|
||||
Future<void> openAddContact() async {
|
||||
await tapByKey("contact_list_page_add_contact_button_key");
|
||||
|
||||
await pumpUntilFound(find.byType(ContactPage));
|
||||
}
|
||||
|
||||
Future<void> enterName(String name) async {
|
||||
await enterTextByKey("contact_page_name_textfield_key", name);
|
||||
}
|
||||
|
||||
Future<void> chooseCurrency(String currencyName) async {
|
||||
await tapByKey("contact_page_currency_picker_button_key");
|
||||
|
||||
await tapByKey("picker_items_index_${currencyName}_button_key");
|
||||
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> enterAddress(String address) async {
|
||||
final input = find.byKey(const ValueKey("contact_page_address_input_key"));
|
||||
|
||||
await pumpUntilFound(input);
|
||||
|
||||
final field = find.descendant(of: input, matching: find.byType(EditableText));
|
||||
|
||||
await tester.enterText(field.first, address);
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
Future<void> save() async {
|
||||
await tapByKey("contact_page_save_button_key");
|
||||
|
||||
await pumpUntilGone(find.byType(ContactPage));
|
||||
|
||||
await settle();
|
||||
}
|
||||
|
||||
void expectContactSaved(String name, String address) {
|
||||
final page = tester.widget<ContactListPage>(find.byType(ContactListPage));
|
||||
|
||||
final saved = page.contactListViewModel.contacts.where((c) => c.name == name).toList();
|
||||
|
||||
expect(saved, isNotEmpty, reason: "The contact $name was never saved");
|
||||
|
||||
expect(
|
||||
saved.first.address,
|
||||
address,
|
||||
reason: "The address book stored a different address than it was given",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,16 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/welcome/create_pin_welcome_page.dart';
|
||||
import 'package:hash_wallet/wallet_type_utils.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/welcome/create_pin_welcome_page.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class CreatePinWelcomePageRobot {
|
||||
CreatePinWelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class CreatePinWelcomePageRobot extends BaseRobot {
|
||||
CreatePinWelcomePageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isCreatePinWelcomePage() async {
|
||||
await commonTestCases.isSpecificPage<CreatePinWelcomePage>();
|
||||
await commonTestCases.takeScreenshots('create_pin_welcome_page');
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
String title;
|
||||
if (isMoneroOnly) {
|
||||
title = S.current.monero_com;
|
||||
}
|
||||
|
||||
title = S.current.cake_wallet;
|
||||
|
||||
commonTestCases.hasText(title);
|
||||
}
|
||||
|
||||
void hasDescription() {
|
||||
String description;
|
||||
if (isMoneroOnly) {
|
||||
description = S.current.monero_com_wallet_text;
|
||||
}
|
||||
|
||||
description = S.current.new_first_wallet_text;
|
||||
|
||||
commonTestCases.hasText(description);
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<CreatePinWelcomePage>();
|
||||
}
|
||||
|
||||
Future<void> tapSetAPinButton() async {
|
||||
await commonTestCases.tapItemByKey('create_pin_welcome_page_create_a_pin_button_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("create_pin_welcome_page_create_a_pin_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import 'package:hash_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class DashboardMenuWidgetRobot {
|
||||
DashboardMenuWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> hasMenuWidget() async {
|
||||
await commonTestCases.takeScreenshots('menu_widget_page');
|
||||
commonTestCases.hasType<MenuWidget>();
|
||||
}
|
||||
|
||||
void displaysTheCorrectWalletNameAndSubName() {
|
||||
final menuWidgetState = tester.state<MenuWidgetState>(find.byType(MenuWidget));
|
||||
|
||||
final walletName = menuWidgetState.widget.dashboardViewModel.name;
|
||||
commonTestCases.hasText(walletName);
|
||||
|
||||
final walletSubName = menuWidgetState.widget.dashboardViewModel.subname;
|
||||
if (walletSubName.isNotEmpty) {
|
||||
commonTestCases.hasText(walletSubName);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> navigateToWalletMenu() async {
|
||||
await commonTestCases.tapItemByKey('dashboard_page_menu_widget_wallet_menu_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> navigateToSecurityAndBackupPage() async {
|
||||
await commonTestCases.tapItemByKey(
|
||||
'dashboard_page_menu_widget_security_and_backup_button_key',
|
||||
);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/dashboard_page.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import 'dashboard_menu_widget_robot.dart';
|
||||
|
||||
class DashboardPageRobot {
|
||||
DashboardPageRobot(this.tester)
|
||||
: commonTestCases = CommonTestCases(tester),
|
||||
dashboardMenuWidgetRobot = DashboardMenuWidgetRobot(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final DashboardMenuWidgetRobot dashboardMenuWidgetRobot;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isDashboardPage() async {
|
||||
await commonTestCases.isSpecificPage<DashboardPage>();
|
||||
await commonTestCases.takeScreenshots('dashboard_page');
|
||||
}
|
||||
|
||||
Future<void> confirmWalletTypeIsDisplayedCorrectly(
|
||||
WalletType type, {
|
||||
bool isHaven = false,
|
||||
}) async {
|
||||
final cryptoBalanceWidget =
|
||||
tester.widget<CryptoBalanceWidget>(find.byType(CryptoBalanceWidget));
|
||||
final hasAccounts = cryptoBalanceWidget.dashboardViewModel.balanceViewModel.hasAccounts;
|
||||
|
||||
if (hasAccounts) {
|
||||
final walletName = cryptoBalanceWidget.dashboardViewModel.name;
|
||||
commonTestCases.hasText(walletName);
|
||||
} else {
|
||||
final walletName = walletTypeToString(type);
|
||||
final assetName = isHaven ? '$walletName Assets' : walletName;
|
||||
commonTestCases.hasText(assetName);
|
||||
}
|
||||
await commonTestCases.defaultSleepTime(seconds: 5);
|
||||
}
|
||||
|
||||
void confirmServiceUpdateButtonDisplays() {
|
||||
commonTestCases.hasValueKey('dashboard_page_services_update_button_key');
|
||||
}
|
||||
|
||||
void confirmSyncIndicatorButtonDisplays() {
|
||||
commonTestCases.hasValueKey('dashboard_page_sync_indicator_button_key');
|
||||
}
|
||||
|
||||
void confirmMenuButtonDisplays() {
|
||||
commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key');
|
||||
}
|
||||
|
||||
Future<void> confirmRightCryptoAssetTitleDisplaysPerPageView(
|
||||
WalletType type, {
|
||||
bool isHaven = false,
|
||||
}) async {
|
||||
//Balance Page
|
||||
await confirmWalletTypeIsDisplayedCorrectly(type, isHaven: isHaven);
|
||||
|
||||
// Swipe to Cake features Page
|
||||
await swipeDashboardTab(false);
|
||||
commonTestCases.hasText('Cake ${S.current.features}');
|
||||
|
||||
// Swipe back to balance
|
||||
await swipeDashboardTab(true);
|
||||
|
||||
// Swipe to Transactions Page
|
||||
await swipeDashboardTab(true);
|
||||
commonTestCases.hasText(S.current.transactions);
|
||||
|
||||
// Swipe back to balance
|
||||
await swipeDashboardTab(false);
|
||||
await commonTestCases.defaultSleepTime(seconds: 3);
|
||||
}
|
||||
|
||||
Future<void> swipeDashboardTab(bool swipeRight) async {
|
||||
await commonTestCases.swipeByPageKey(
|
||||
key: 'dashboard_page_view_key',
|
||||
swipeRight: swipeRight,
|
||||
);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> openDrawerMenu() async {
|
||||
await commonTestCases.tapItemByKey('dashboard_page_wallet_menu_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> navigateToBuyPage() async {
|
||||
await commonTestCases.tapItemByKey('dashboard_page_buy_action_button_key');
|
||||
}
|
||||
|
||||
Future<void> navigateToWalletsListPage({bool isDrawer = false}) async {
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
|
||||
await commonTestCases.tapItemByKey(
|
||||
isDrawer
|
||||
? 'dashboard_page_menu_widget_wallet_menu_button_key'
|
||||
: 'dashboard_page__wallet_list_button_key',
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 2000));
|
||||
}
|
||||
|
||||
Future<void> navigateToSendPage() async {
|
||||
await commonTestCases.tapItemByKey('dashboard_page_send_action_button_key');
|
||||
}
|
||||
|
||||
Future<void> navigateToSellPage() async {
|
||||
await commonTestCases.tapItemByKey('dashboard_page_sell_action_button_key');
|
||||
}
|
||||
|
||||
Future<void> navigateToReceivePage() async {
|
||||
await commonTestCases.tapItemByKey('dashboard_page_receive_action_button_key');
|
||||
}
|
||||
|
||||
Future<void> navigateToExchangePage() async {
|
||||
await commonTestCases.tapItemByKey('dashboard_page_swap_action_button_key');
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import 'package:hash_wallet/src/screens/disclaimer/disclaimer_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class DisclaimerPageRobot {
|
||||
DisclaimerPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isDisclaimerPage() async {
|
||||
await commonTestCases.isSpecificPage<DisclaimerPage>();
|
||||
await commonTestCases.takeScreenshots('disclaimer_page');
|
||||
}
|
||||
|
||||
void hasCheckIcon(bool hasBeenTapped) {
|
||||
// The checked Icon should not be available initially, until user taps the checkbox
|
||||
final checkIcon = find.byKey(ValueKey('disclaimer_check_icon_key'));
|
||||
expect(checkIcon, hasBeenTapped ? findsOneWidget : findsNothing);
|
||||
}
|
||||
|
||||
void hasDisclaimerCheckbox() {
|
||||
final checkBox = find.byKey(ValueKey('disclaimer_check_key'));
|
||||
expect(checkBox, findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> tapDisclaimerCheckbox() async {
|
||||
await commonTestCases.tapItemByKey('disclaimer_check_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> tapAcceptButton() async {
|
||||
await commonTestCases.tapItemByKey('disclaimer_accept_button_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import "package:hash_wallet/entities/fiat_currency.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/currency_picker/currency_picker_search_field.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/currency_picker/fiat_currency_row.dart";
|
||||
import "package:hash_wallet/src/screens/settings/display_settings_page.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class DisplaySettingsPageRobot extends BaseRobot {
|
||||
DisplaySettingsPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<DisplaySettingsPage>();
|
||||
}
|
||||
|
||||
Future<void> openLanguagePicker() async {
|
||||
await tapByKey("display_settings_language");
|
||||
}
|
||||
|
||||
Future<void> chooseLanguage(String code) async {
|
||||
await tapByKey("picker_items_index_${code}_button_key");
|
||||
|
||||
await settle();
|
||||
}
|
||||
|
||||
bool hasFiatCurrencyRow() =>
|
||||
tester.any(find.byKey(const ValueKey("display_settings_fiat_currency")));
|
||||
|
||||
Future<void> openFiatCurrencyPicker() async {
|
||||
await tapByKey("display_settings_fiat_currency");
|
||||
|
||||
await pumpUntilFound(find.byType(FiatCurrencyPickerSheet));
|
||||
}
|
||||
|
||||
Future<void> chooseFiatCurrency(FiatCurrency currency) async {
|
||||
final searchField = find.descendant(
|
||||
of: find.byType(CurrencyPickerSearchField),
|
||||
matching: find.byType(TextFormField),
|
||||
);
|
||||
|
||||
await tester.enterText(searchField, currency.fullName);
|
||||
|
||||
final row = find.byWidgetPredicate(
|
||||
(widget) => widget is FiatCurrencyRow && widget.currency == currency,
|
||||
);
|
||||
|
||||
await pumpUntilFound(row);
|
||||
|
||||
await tester.tap(row.first);
|
||||
|
||||
await pumpUntilGone(find.byType(FiatCurrencyPickerSheet));
|
||||
}
|
||||
|
||||
void expectFiatCurrencyShown(FiatCurrency currency) {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byKey(const ValueKey("display_settings_fiat_currency")),
|
||||
matching: find.text(currency.title),
|
||||
),
|
||||
findsOneWidget,
|
||||
reason: "The currency row still does not show ${currency.title}",
|
||||
);
|
||||
}
|
||||
|
||||
void expectLanguageShown(String displayName) {
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byKey(const ValueKey("display_settings_language")),
|
||||
matching: find.text(displayName),
|
||||
),
|
||||
findsOneWidget,
|
||||
reason: "The language row still does not show $displayName",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class ExchangeConfirmPageRobot {
|
||||
ExchangeConfirmPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isExchangeConfirmPage() async {
|
||||
await commonTestCases.isSpecificPage<ExchangeConfirmPage>();
|
||||
await commonTestCases.takeScreenshots('exchange_confirm_page');
|
||||
}
|
||||
|
||||
void confirmComponentsOfTradeDisplayProperly() {
|
||||
final ExchangeConfirmPage exchangeConfirmPage = tester.widget(find.byType(ExchangeConfirmPage));
|
||||
final trade = exchangeConfirmPage.trade;
|
||||
|
||||
commonTestCases.hasText(trade.id);
|
||||
commonTestCases.hasText('${trade.provider.title} ${S.current.trade_id}');
|
||||
|
||||
commonTestCases.hasValueKey('exchange_confirm_page_saved_id_button_key');
|
||||
commonTestCases.hasValueKey('exchange_confirm_page_copy_to_clipboard_button_key');
|
||||
}
|
||||
|
||||
Future<void> confirmCopyTradeIdToClipBoardWorksProperly() async {
|
||||
final ExchangeConfirmPage exchangeConfirmPage = tester.widget(find.byType(ExchangeConfirmPage));
|
||||
final trade = exchangeConfirmPage.trade;
|
||||
|
||||
await commonTestCases.tapItemByKey('exchange_confirm_page_copy_to_clipboard_button_key');
|
||||
|
||||
ClipboardData? clipboardData = await Clipboard.getData('text/plain');
|
||||
|
||||
expect(clipboardData?.text, trade.id);
|
||||
}
|
||||
|
||||
Future<void> onSavedTradeIdButtonPressed() async {
|
||||
await tester.pumpAndSettle();
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await commonTestCases.tapItemByKey('exchange_confirm_page_saved_id_button_key');
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import '../components/common_test_constants.dart';
|
||||
import 'auth_page_robot.dart';
|
||||
|
||||
class ExchangePageRobot {
|
||||
ExchangePageRobot(this.tester)
|
||||
: commonTestCases = CommonTestCases(tester),
|
||||
authPageRobot = AuthPageRobot(tester);
|
||||
|
||||
WidgetTester tester;
|
||||
AuthPageRobot authPageRobot;
|
||||
CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isExchangePage() async {
|
||||
await commonTestCases.isSpecificPage<ExchangePage>();
|
||||
await commonTestCases.takeScreenshots('exchange_page');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
void hasResetButton() {
|
||||
commonTestCases.hasText(S.current.reset);
|
||||
}
|
||||
|
||||
void displaysPresentProviderPicker() {
|
||||
commonTestCases.hasType<PresentProviderPicker>();
|
||||
}
|
||||
|
||||
Future<void> displayBothExchangeCards() async {
|
||||
final ExchangePage exchangeCard = tester.widget<ExchangePage>(
|
||||
find.byType(ExchangePage),
|
||||
);
|
||||
|
||||
final depositKey = exchangeCard.depositKey;
|
||||
final receiveKey = exchangeCard.receiveKey;
|
||||
|
||||
final depositExchangeCard = find.byKey(depositKey);
|
||||
expect(depositExchangeCard, findsOneWidget);
|
||||
|
||||
final receiveExchangeCard = find.byKey(receiveKey);
|
||||
expect(receiveExchangeCard, findsOneWidget);
|
||||
}
|
||||
|
||||
void confirmRightComponentsDisplayOnDepositExchangeCards() {
|
||||
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||
final depositCardPrefix = 'deposit_exchange_card';
|
||||
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_title_key');
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_currency_picker_button_key');
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_selected_currency_text_key');
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_amount_textfield_key');
|
||||
|
||||
exchangePage.depositKey.currentState!.changeLimits(min: '0.1');
|
||||
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_min_limit_text_key');
|
||||
|
||||
final initialCurrency = exchangeViewModel.depositCurrency;
|
||||
if (initialCurrency.tag != null) {
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_selected_currency_tag_text_key');
|
||||
}
|
||||
|
||||
if (exchangeViewModel.hasAllAmount) {
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_send_all_button_key');
|
||||
}
|
||||
|
||||
if (exchangeViewModel.isMoneroWallet) {
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_address_book_button_key');
|
||||
}
|
||||
|
||||
if (exchangeViewModel.isDepositAddressEnabled) {
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_editable_address_textfield_key');
|
||||
} else {
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_non_editable_address_textfield_key');
|
||||
commonTestCases.hasValueKey('${depositCardPrefix}_copy_refund_address_button_key');
|
||||
}
|
||||
|
||||
// commonTestCases.hasValueKey('${depositCardPrefix}_max_limit_text_key');
|
||||
}
|
||||
|
||||
void confirmRightComponentsDisplayOnReceiveExchangeCards() {
|
||||
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||
final receiveCardPrefix = 'receive_exchange_card';
|
||||
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_title_key');
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_currency_picker_button_key');
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_selected_currency_text_key');
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_amount_textfield_key');
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_min_limit_text_key');
|
||||
|
||||
final initialCurrency = exchangeViewModel.receiveCurrency;
|
||||
if (initialCurrency.tag != null) {
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_selected_currency_tag_text_key');
|
||||
}
|
||||
|
||||
if (exchangeViewModel.hasAllAmount) {
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_send_all_button_key');
|
||||
}
|
||||
|
||||
if (exchangeViewModel.isMoneroWallet) {
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_address_book_button_key');
|
||||
}
|
||||
|
||||
commonTestCases.hasValueKey('${receiveCardPrefix}_editable_address_textfield_key');
|
||||
}
|
||||
|
||||
Future<void> selectDepositCurrency(CryptoCurrency depositCurrency) async {
|
||||
final depositPrefix = 'deposit_exchange_card';
|
||||
final currencyPickerKey = '${depositPrefix}_currency_picker_button_key';
|
||||
final currencyPickerDialogKey = '${depositPrefix}_currency_picker_dialog_button_key';
|
||||
|
||||
await commonTestCases.tapItemByKey(currencyPickerKey);
|
||||
commonTestCases.hasValueKey(currencyPickerDialogKey);
|
||||
|
||||
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||
|
||||
if (depositCurrency == exchangeViewModel.depositCurrency) {
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await commonTestCases
|
||||
.tapItemByKey('picker_items_index_${depositCurrency.name}_selected_item_button_key');
|
||||
return;
|
||||
}
|
||||
|
||||
await commonTestCases.enterText(depositCurrency.name, 'search_bar_widget_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key');
|
||||
}
|
||||
|
||||
Future<void> selectReceiveCurrency(CryptoCurrency receiveCurrency) async {
|
||||
final receivePrefix = 'receive_exchange_card';
|
||||
final currencyPickerKey = '${receivePrefix}_currency_picker_button_key';
|
||||
final currencyPickerDialogKey = '${receivePrefix}_currency_picker_dialog_button_key';
|
||||
|
||||
await commonTestCases.tapItemByKey(currencyPickerKey);
|
||||
commonTestCases.hasValueKey(currencyPickerDialogKey);
|
||||
|
||||
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||
|
||||
if (receiveCurrency == exchangeViewModel.receiveCurrency) {
|
||||
await commonTestCases
|
||||
.tapItemByKey('picker_items_index_${receiveCurrency.name}_selected_item_button_key');
|
||||
return;
|
||||
}
|
||||
|
||||
await commonTestCases.enterText(receiveCurrency.name, 'search_bar_widget_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key');
|
||||
}
|
||||
|
||||
Future<void> enterDepositAmount(String amount) async {
|
||||
await commonTestCases.enterText(amount, 'deposit_exchange_card_amount_textfield_key');
|
||||
}
|
||||
|
||||
Future<void> enterDepositRefundAddress({String? depositAddress}) async {
|
||||
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||
|
||||
if (exchangeViewModel.isDepositAddressEnabled && depositAddress != null) {
|
||||
await commonTestCases.enterText(
|
||||
depositAddress, 'deposit_exchange_card_editable_address_textfield_key');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> enterReceiveAddress(String receiveAddress) async {
|
||||
await commonTestCases.enterText(
|
||||
receiveAddress,
|
||||
'receive_exchange_card_editable_address_textfield_key',
|
||||
);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onExchangeButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('exchange_page_exchange_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
bool hasMaxLimitError() {
|
||||
final maxErrorText = find.text(S.current.error_text_input_above_maximum_limit);
|
||||
|
||||
bool hasMaxError = maxErrorText.tryEvaluate();
|
||||
|
||||
return hasMaxError;
|
||||
}
|
||||
|
||||
bool hasMinLimitError() {
|
||||
final minErrorText = find.text(S.current.error_text_input_below_minimum_limit);
|
||||
|
||||
bool hasMinError = minErrorText.tryEvaluate();
|
||||
|
||||
return hasMinError;
|
||||
}
|
||||
|
||||
bool hasTradeCreationFailureError() {
|
||||
final tradeCreationFailureDialogButton =
|
||||
find.byKey(ValueKey('exchange_page_trade_creation_failure_dialog_button_key'));
|
||||
|
||||
bool hasTradeCreationFailure = tradeCreationFailureDialogButton.tryEvaluate();
|
||||
tester.printToConsole('Trade not created error: $hasTradeCreationFailure');
|
||||
return hasTradeCreationFailure;
|
||||
}
|
||||
|
||||
Future<void> onTradeCreationFailureDialogButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('exchange_page_trade_creation_failure_dialog_button_key');
|
||||
}
|
||||
|
||||
/// Handling Trade Failure Errors or errors shown through the Failure Dialog.
|
||||
///
|
||||
/// Simulating the user's flow and response when this error comes up.
|
||||
/// Examples are:
|
||||
/// - No provider can handle this trade error,
|
||||
/// - Trade amount below limit error.
|
||||
Future<void> _handleTradeCreationFailureErrors() async {
|
||||
tester.printToConsole('Inside trade creation failure handle');
|
||||
bool isTradeCreationFailure = false;
|
||||
|
||||
isTradeCreationFailure = hasTradeCreationFailureError();
|
||||
|
||||
int maxRetries = 3;
|
||||
int retries = 0;
|
||||
|
||||
while (isTradeCreationFailure && retries < maxRetries) {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await onTradeCreationFailureDialogButtonPressed();
|
||||
|
||||
await commonTestCases.defaultSleepTime(seconds: 5);
|
||||
|
||||
await onExchangeButtonPressed();
|
||||
|
||||
await _handleAuthPage();
|
||||
|
||||
isTradeCreationFailure = hasTradeCreationFailureError();
|
||||
retries++;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the min limit error.
|
||||
///
|
||||
/// Simulates the user's flow and response when it comes up.
|
||||
///
|
||||
/// Has a max retry of 20 times.
|
||||
Future<void> _handleMinLimitError(String initialAmount) async {
|
||||
double amount;
|
||||
|
||||
amount = double.parse(initialAmount);
|
||||
|
||||
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||
|
||||
amount = (exchangeViewModel.limits.min ?? 0.0) + 1.0;
|
||||
|
||||
await enterDepositAmount(amount.toString());
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await onExchangeButtonPressed();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// Handles the max limit error.
|
||||
///
|
||||
/// Simulates the user's flow and response when it comes up.
|
||||
///
|
||||
/// Has a max retry of 20 times.
|
||||
Future<void> _handleMaxLimitError(String initialAmount) async {
|
||||
double amount;
|
||||
|
||||
amount = double.parse(initialAmount);
|
||||
|
||||
ExchangePage exchangePage = tester.widget(find.byType(ExchangePage));
|
||||
final exchangeViewModel = exchangePage.exchangeViewModel;
|
||||
|
||||
amount = (exchangeViewModel.limits.max ?? 0.0) - 1.0;
|
||||
|
||||
await enterDepositAmount(amount.toString());
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await onExchangeButtonPressed();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> handleErrors(String initialAmount) async {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
bool isMinLimitError = hasMinLimitError();
|
||||
|
||||
if (isMinLimitError) {
|
||||
tester.printToConsole('Has min limit error');
|
||||
await _handleMinLimitError(initialAmount);
|
||||
}
|
||||
|
||||
bool isMaxLimitError = hasMaxLimitError();
|
||||
|
||||
if (isMaxLimitError) {
|
||||
await _handleMaxLimitError(initialAmount);
|
||||
}
|
||||
|
||||
tester.printToConsole('No limits error, proceeding with flow');
|
||||
|
||||
await _handleAuthPage();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await _handleTradeCreationFailureErrors();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> _handleAuthPage() async {
|
||||
final onAuthPage = authPageRobot.onAuthPage();
|
||||
if (onAuthPage) {
|
||||
await authPageRobot.enterPinCode(CommonTestConstants.pin);
|
||||
}
|
||||
|
||||
final onAuthPageDesktop = authPageRobot.onAuthPageDesktop();
|
||||
if (onAuthPageDesktop) {
|
||||
await authPageRobot.enterPassword(CommonTestConstants.pin.join(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:hash_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class ExchangeTradeExternalSendPageRobot {
|
||||
ExchangeTradeExternalSendPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isExchangeTradeExternalSendPage() async {
|
||||
await commonTestCases.isSpecificPage<ExchangeTradeExternalSendPage>();
|
||||
await commonTestCases.takeScreenshots('exchange_trade_external_send_page');
|
||||
}
|
||||
|
||||
Future<void> verifySendDetailsItemsDisplayProperly() async {
|
||||
final widget =
|
||||
tester.widget<ExchangeTradeExternalSendPage>(find.byType(ExchangeTradeExternalSendPage));
|
||||
final exchangeTradeViewModel = widget.exchangeTradeViewModel;
|
||||
final items = exchangeTradeViewModel.items.where((item) => item.isExternalSendDetail).toList();
|
||||
|
||||
for (var item in items) {
|
||||
commonTestCases.hasValueKey('exchange_trade_external_send_page_send_item_${item.title}_key');
|
||||
tester.printToConsole('${item.title} present on screen');
|
||||
}
|
||||
|
||||
commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onContinueButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('exchange_trade_external_send_page_continue_button_key');
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hash_wallet/core/execution_state.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class ExchangeTradePageRobot {
|
||||
ExchangeTradePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isExchangeTradePage() async {
|
||||
await commonTestCases.isSpecificPage<ExchangeTradePage>();
|
||||
await commonTestCases.takeScreenshots('exchange_trade_page');
|
||||
}
|
||||
|
||||
void hasInformationDialog() {
|
||||
commonTestCases.hasValueKey('information_page_dialog_key');
|
||||
}
|
||||
|
||||
Future<void> onGotItButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('information_page_got_it_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onSendFromExternalButtonPressed() async {
|
||||
tester.printToConsole('Routing to send from external details page');
|
||||
|
||||
await commonTestCases.tapItemByKey(
|
||||
'exchange_trade_page_send_from_external_button_key',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onSendFromCakeButtonPressed() async {
|
||||
tester.printToConsole('Now sending from cake');
|
||||
|
||||
await commonTestCases.tapItemByKey(
|
||||
'exchange_trade_page_send_from_cake_button_key',
|
||||
shouldPumpAndSettle: false,
|
||||
);
|
||||
|
||||
final Completer<void> completer = Completer<void>();
|
||||
|
||||
// Loop to wait for the async operation to complete
|
||||
while (true) {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
|
||||
final ExchangeTradeState state = tester.state(find.byType(ExchangeTradeForm));
|
||||
final execState = state.widget.exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
bool isDone = execState is ExecutedSuccessfullyState;
|
||||
bool isFailed = execState is FailureState;
|
||||
|
||||
tester.printToConsole('isDone: $isDone');
|
||||
tester.printToConsole('isFailed: $isFailed');
|
||||
|
||||
if (isDone || isFailed) {
|
||||
tester.printToConsole(
|
||||
isDone ? 'Completer is done' : 'Completer is done though operation failed');
|
||||
completer.complete();
|
||||
await tester.pump();
|
||||
break;
|
||||
} else {
|
||||
tester.printToConsole('Completer is not done');
|
||||
await tester.pump();
|
||||
}
|
||||
}
|
||||
|
||||
await expectLater(completer.future, completes);
|
||||
|
||||
tester.printToConsole('Done confirming sending');
|
||||
|
||||
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||
}
|
||||
|
||||
Future<void> onSendButtonOnConfirmSendingDialogPressed() async {
|
||||
tester.printToConsole('Send Button on Confirm Dialog Triggered');
|
||||
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||
|
||||
final sendText = find.text(S.current.send);
|
||||
bool hasText = sendText.tryEvaluate();
|
||||
|
||||
if (hasText) {
|
||||
await commonTestCases.tapItemByFinder(sendText);
|
||||
|
||||
await commonTestCases.defaultSleepTime(seconds: 4);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onCancelButtonOnConfirmSendingDialogPressed() async {
|
||||
tester.printToConsole('Cancel Button on Confirm Dialog Triggered');
|
||||
|
||||
await commonTestCases.tapItemByKey(
|
||||
'exchange_trade_page_confirm_sending_dialog_cancel_button_key',
|
||||
);
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onSendFailureDialogButtonPressed() async {
|
||||
await commonTestCases.defaultSleepTime(seconds: 6);
|
||||
|
||||
tester.printToConsole('Send Button Failure Dialog Triggered');
|
||||
|
||||
await commonTestCases.tapItemByKey('exchange_trade_page_send_failure_dialog_button_key');
|
||||
}
|
||||
|
||||
Future<bool> hasErrorWhileSending() async {
|
||||
await tester.pump();
|
||||
|
||||
tester.printToConsole('Checking if there is an error');
|
||||
|
||||
final errorDialog = find.byKey(
|
||||
ValueKey('exchange_trade_page_send_failure_dialog_button_key'),
|
||||
);
|
||||
|
||||
bool hasError = errorDialog.tryEvaluate();
|
||||
|
||||
tester.printToConsole('Has error: $hasError');
|
||||
|
||||
return hasError;
|
||||
}
|
||||
|
||||
Future<void> handleConfirmSendResult() async {
|
||||
bool hasError = false;
|
||||
|
||||
hasError = await hasErrorWhileSending();
|
||||
|
||||
int maxRetries = 20;
|
||||
int retries = 0;
|
||||
|
||||
while (hasError && retries < maxRetries) {
|
||||
tester.printToConsole('hasErrorInLoop: $hasError');
|
||||
await tester.pump();
|
||||
|
||||
await onSendFailureDialogButtonPressed();
|
||||
tester.printToConsole('Failure button tapped');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await onSendFromCakeButtonPressed();
|
||||
tester.printToConsole('Confirm sending button tapped');
|
||||
|
||||
hasError = await hasErrorWhileSending();
|
||||
|
||||
retries++;
|
||||
}
|
||||
|
||||
if (!hasError) {
|
||||
tester.printToConsole('No error, proceeding with flow');
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import "package:hash_wallet/new-ui/pages/home_page.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/coins_page/assets_history/history_tile.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/coins_page/top_bar_widget/sync_bar.dart";
|
||||
import "package:cw_core/sync_status.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class HomePageRobot extends BaseRobot {
|
||||
HomePageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
final shown = await pumpUntil(() => isInFront);
|
||||
|
||||
expect(shown, true, reason: "The home tab never came to the front");
|
||||
}
|
||||
|
||||
bool get isInFront {
|
||||
final stackFinder = find.byType(IndexedStack);
|
||||
|
||||
if (!tester.any(stackFinder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final stack = tester.widget<IndexedStack>(stackFinder.first);
|
||||
final index = stack.children.indexWhere((child) => child is NewHomePage);
|
||||
|
||||
return index >= 0 && stack.index == index;
|
||||
}
|
||||
|
||||
Future<void> hasWalletName(String name) async {
|
||||
final nameFinder = find.byKey(const ValueKey("home_page_wallet_name_text_key"));
|
||||
final syncBarFinder = find.byType(SyncBar);
|
||||
|
||||
final shown = await pumpUntil(() => tester.any(nameFinder) || tester.any(syncBarFinder));
|
||||
|
||||
expect(shown, true, reason: "Top bar showed neither the wallet name nor the sync bar");
|
||||
|
||||
if (!tester.any(nameFinder)) {
|
||||
tester.printToConsole("Sync bar is in place of the wallet name, not checking it on screen");
|
||||
return;
|
||||
}
|
||||
|
||||
expect(textByKey("home_page_wallet_name_text_key"), name);
|
||||
}
|
||||
|
||||
Future<void> openSendSheet() async {
|
||||
await tapByKey("home_page_send_button_key");
|
||||
}
|
||||
|
||||
Future<void> openReceiveSheet() async {
|
||||
await tapByKey("home_page_receive_button_key");
|
||||
}
|
||||
|
||||
Future<void> openSwapSheet() async {
|
||||
await tapByKey("home_page_swap_button_key");
|
||||
}
|
||||
|
||||
Future<void> openSettingsSheet() async {
|
||||
await tapByKey("home_page_settings_button_key");
|
||||
}
|
||||
|
||||
Future<void> openHistoryTab() async {
|
||||
final tabFinder = find.byKey(const ValueKey("line_tab_switcher_1_key"));
|
||||
|
||||
if (!tester.any(tabFinder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await tapWhenVisible(tabFinder);
|
||||
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> confirmTransactionHistoryVisible({
|
||||
Duration timeout = const Duration(minutes: 3),
|
||||
}) async {
|
||||
final hasTransactions = await pumpUntil(
|
||||
() => _dashboardTransactionCount() > 0,
|
||||
timeout: timeout,
|
||||
);
|
||||
|
||||
expect(
|
||||
hasTransactions,
|
||||
true,
|
||||
reason: "No transactions arrived within ${timeout.inSeconds}s",
|
||||
);
|
||||
|
||||
await openHistoryTab();
|
||||
|
||||
await pumpUntilFound(find.byType(HistoryTile));
|
||||
|
||||
expect(
|
||||
tester.widgetList(find.byType(HistoryTile)).length,
|
||||
lessThanOrEqualTo(3),
|
||||
reason: "The home page preview should never render more than the short history",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> openAllTransactions() async {
|
||||
final actionButton = find.byKey(const ValueKey("assets_history_action_button_key"));
|
||||
final historyBar = find.byKey(const ValueKey("history_top_bar_key"));
|
||||
|
||||
await tapWhenVisible(tester.any(actionButton) ? actionButton : historyBar);
|
||||
|
||||
await pumpUntilFound(find.byKey(const ValueKey("history_modal_key")));
|
||||
}
|
||||
|
||||
Future<void> confirmAllTransactionsVisible() async {
|
||||
await pumpUntilFound(_allViewTiles);
|
||||
|
||||
final rendered = tester.widgetList(_allViewTiles).length;
|
||||
|
||||
if (_dashboardTransactionCount() > 3) {
|
||||
expect(
|
||||
rendered,
|
||||
greaterThan(3),
|
||||
reason: "The All view rendered $rendered tiles, no more than the home preview does",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
expect(_allViewTiles, findsWidgets);
|
||||
}
|
||||
|
||||
String firstTransactionIdInAllView() {
|
||||
final tile = tester.widgetList<HistoryTile>(_allViewTiles).first;
|
||||
final key = tile.key! as ValueKey<String>;
|
||||
|
||||
return key.value.substring("home_page_transaction_".length, key.value.length - "_key".length);
|
||||
}
|
||||
|
||||
Future<void> openFirstTransactionDetails() async {
|
||||
await pumpUntilFound(_allViewTiles);
|
||||
|
||||
await tester.tap(_allViewTiles.first);
|
||||
|
||||
await pumpUntilFound(find.byType(TransactionDetailsModal));
|
||||
|
||||
await settle();
|
||||
}
|
||||
|
||||
String openedTransactionId() {
|
||||
final modal = tester.widget<TransactionDetailsModal>(find.byType(TransactionDetailsModal));
|
||||
|
||||
return modal.transactionDetailsViewModel.transactionInfo.id;
|
||||
}
|
||||
|
||||
void hasTransactionIdRow() {
|
||||
expect(
|
||||
find.byKey(const ValueKey("standard_list_item_transaction_details_id_key")),
|
||||
findsOneWidget,
|
||||
reason: "The details opened without showing the transaction id",
|
||||
);
|
||||
}
|
||||
|
||||
// The home preview stays mounted underneath the modal, so its tiles have to be left out.
|
||||
Finder get _allViewTiles => find.descendant(
|
||||
of: find.byKey(const ValueKey("history_modal_key")),
|
||||
matching: find.byType(HistoryTile),
|
||||
);
|
||||
|
||||
Future<void> confirmSyncIndicatorShown({Duration timeout = const Duration(minutes: 3)}) async {
|
||||
final syncBar = find.byType(SyncBar);
|
||||
final tick = find.descendant(of: syncBar, matching: find.byIcon(Icons.check));
|
||||
|
||||
SyncStatus? lastStatus;
|
||||
var sawIndicator = false;
|
||||
|
||||
final synced = await pumpUntil(
|
||||
() {
|
||||
lastStatus = _dashboardStatus();
|
||||
|
||||
if (lastStatus == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final keyed = find.descendant(
|
||||
of: syncBar,
|
||||
matching: find.byKey(ValueKey(lastStatus.runtimeType)),
|
||||
);
|
||||
|
||||
sawIndicator = sawIndicator || tester.any(keyed) || tester.any(tick);
|
||||
|
||||
return lastStatus is SyncedSyncStatus;
|
||||
},
|
||||
timeout: timeout,
|
||||
);
|
||||
|
||||
expect(
|
||||
synced,
|
||||
true,
|
||||
reason: "The wallet never synced within ${timeout.inMinutes}m, "
|
||||
"it reported ${lastStatus.runtimeType}",
|
||||
);
|
||||
|
||||
expect(
|
||||
sawIndicator,
|
||||
true,
|
||||
reason: "The sync bar never showed a status while the wallet synced",
|
||||
);
|
||||
}
|
||||
|
||||
SyncStatus? _dashboardStatus() {
|
||||
final finder = find.byType(NewHomePage);
|
||||
|
||||
if (!tester.any(finder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tester.widget<NewHomePage>(finder.first).dashboardViewModel.status;
|
||||
}
|
||||
|
||||
int _dashboardTransactionCount() {
|
||||
final finder = find.byType(NewHomePage);
|
||||
|
||||
if (!tester.any(finder)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return tester.widget<NewHomePage>(finder.first).dashboardViewModel.transactions.length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class LightningUsernamePageRobot extends BaseRobot {
|
||||
LightningUsernamePageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await pumpUntilFound(find.byKey(const ValueKey("lightning_username_page_skip_button_key")));
|
||||
}
|
||||
|
||||
Future<void> tapSkip() async {
|
||||
await tapByKey("lightning_username_page_skip_button_key");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import "package:hash_wallet/generated/i18n.dart";
|
||||
import "package:hash_wallet/src/screens/nodes/widgets/node_list_row.dart";
|
||||
import "package:hash_wallet/src/screens/settings/manage_nodes_page.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class ManageNodesPageRobot extends BaseRobot {
|
||||
ManageNodesPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<ManageNodesPage>();
|
||||
}
|
||||
|
||||
String currentNodeUri() {
|
||||
final page = tester.widget<ManageNodesPage>(find.byType(ManageNodesPage));
|
||||
|
||||
return page.nodeListViewModel.currentNode.uriRaw;
|
||||
}
|
||||
|
||||
Future<void> switchToAnotherNode() async {
|
||||
final rows = find.byType(NodeListRow);
|
||||
|
||||
await pumpUntilFound(rows.first);
|
||||
|
||||
expect(
|
||||
tester.widgetList(rows).length,
|
||||
greaterThan(1),
|
||||
reason: "Only the current node is listed, there is nothing to switch to",
|
||||
);
|
||||
|
||||
await tapWhenVisible(rows.at(1));
|
||||
|
||||
await tapWhenVisible(find.text(S.current.change));
|
||||
|
||||
await settle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class NewDashboardRobot extends BaseRobot {
|
||||
NewDashboardRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed({Duration timeout = const Duration(seconds: 30)}) async {
|
||||
await pumpUntilFound(find.byKey(const ValueKey("new_dashboard_page_key")), timeout: timeout);
|
||||
}
|
||||
|
||||
Future<void> openHomeTab() async {
|
||||
await tapByKey("dashboard_page_home_action_button_key");
|
||||
}
|
||||
|
||||
Future<void> openWalletsTab() async {
|
||||
await tapByKey("dashboard_page_wallets_action_button_key");
|
||||
}
|
||||
|
||||
Future<void> openContactsTab() async {
|
||||
await tapByKey("dashboard_page_contacts_action_button_key");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import "package:hash_wallet/new-ui/pages/receive_page.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class NewReceivePageRobot extends BaseRobot {
|
||||
NewReceivePageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await pumpUntilFound(find.byType(NewReceivePage));
|
||||
}
|
||||
|
||||
Future<void> confirmAddressMatches(String expected) async {
|
||||
final wanted = _normalise(expected);
|
||||
|
||||
await pumpUntilFound(find.byKey(const ValueKey("receive_page_address_key")));
|
||||
|
||||
final matched = await pumpUntil(() => _renderedAddress() == wanted);
|
||||
|
||||
expect(
|
||||
matched,
|
||||
true,
|
||||
reason: "Receive page should show $wanted but showed ${_renderedAddress()}",
|
||||
);
|
||||
}
|
||||
|
||||
String _normalise(String address) =>
|
||||
address.replaceAll("bitcoincash:", "").replaceAll(RegExp(r"\s"), "");
|
||||
|
||||
String? _renderedAddress() {
|
||||
final root = find.byKey(const ValueKey("receive_page_address_key"));
|
||||
|
||||
if (!tester.any(root)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final rich = find.descendant(of: root, matching: find.byType(RichText));
|
||||
|
||||
if (tester.any(rich)) {
|
||||
final text = _normalise(tester.widget<RichText>(rich.first).text.toPlainText());
|
||||
|
||||
if (text.isNotEmpty) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
final plain = find.descendant(of: root, matching: find.byType(Text));
|
||||
|
||||
if (tester.any(plain)) {
|
||||
return _normalise(tester.widget<Text>(plain.first).data ?? "");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import "package:hash_wallet/core/execution_state.dart";
|
||||
import "package:hash_wallet/new-ui/pages/send_page.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/confirm_swiper.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/send_page/send_confirm_bottom_widget.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/send_page/send_confirm_sheet.dart";
|
||||
import "package:hash_wallet/view_model/send/send_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
import "../core/test_config.dart";
|
||||
|
||||
class NewSendPageRobot extends BaseRobot {
|
||||
NewSendPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await pumpUntilFound(find.byType(NewSendPage));
|
||||
}
|
||||
|
||||
Future<void> enterAddress(String address) async {
|
||||
await _enterTextInInput("send_page_address_input_key", address);
|
||||
}
|
||||
|
||||
Future<void> enterAmount(String amount) async {
|
||||
await _enterTextInInput("send_page_amount_input_key", amount);
|
||||
}
|
||||
|
||||
String enteredAddress() => _textInInput("send_page_address_input_key");
|
||||
|
||||
Future<void> pickContactFromAddressBook(String contactName) async {
|
||||
await tapByKey("send_page_address_book_button_key");
|
||||
|
||||
final tabs = find.byType(Tab);
|
||||
|
||||
await pumpUntilFound(tabs);
|
||||
|
||||
await tester.tap(tabs.at(1));
|
||||
|
||||
await settle();
|
||||
|
||||
final contactFinder = find.byKey(ValueKey(contactName));
|
||||
|
||||
await pumpUntilFound(contactFinder);
|
||||
|
||||
await tester.tap(contactFinder.first);
|
||||
|
||||
await pumpUntilGone(contactFinder);
|
||||
}
|
||||
|
||||
Future<void> tapSendButton() async {
|
||||
await tapByKey("send_page_send_button_key");
|
||||
}
|
||||
|
||||
Future<void> tapSendButtonWhenReady({Duration timeout = TestConfig.walletSyncBudget}) async {
|
||||
final ready = await pumpUntil(() => _model()?.isReadyForSend ?? false, timeout: timeout);
|
||||
|
||||
expect(
|
||||
ready,
|
||||
true,
|
||||
reason: "The wallet never became ready to send within ${timeout.inMinutes}m, "
|
||||
"so the send button stayed disabled",
|
||||
);
|
||||
|
||||
await tapSendButton();
|
||||
}
|
||||
|
||||
bool get isSendFlowOpen =>
|
||||
tester.any(find.byType(NewSendPage)) || tester.any(find.byType(SendConfirmSheet));
|
||||
|
||||
Future<void> swipeToConfirm({Duration timeout = const Duration(seconds: 90)}) async {
|
||||
final finder = find.byKey(const ValueKey("send_page_confirm_swiper_key"));
|
||||
|
||||
await pumpUntilFound(finder, timeout: timeout);
|
||||
|
||||
await settle(max: const Duration(seconds: 2));
|
||||
|
||||
final width = tester.view.physicalSize.width / tester.view.devicePixelRatio;
|
||||
await tester.drag(finder.first, Offset(width, 0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
Future<void> confirmSendButtonReplacesSwiper() async {
|
||||
final swiper = find.byKey(const ValueKey("send_page_confirm_swiper_key"));
|
||||
|
||||
await pumpUntilFound(swiper);
|
||||
|
||||
tester.binding.platformDispatcher.accessibilityFeaturesTestValue =
|
||||
const FakeAccessibilityFeatures(accessibleNavigation: true);
|
||||
addTearDown(tester.binding.platformDispatcher.clearAccessibilityFeaturesTestValue);
|
||||
|
||||
await settle(max: const Duration(seconds: 2));
|
||||
|
||||
final button = find.descendant(of: swiper, matching: find.byType(NewPrimaryButton));
|
||||
|
||||
expect(
|
||||
tester.any(button),
|
||||
true,
|
||||
reason: "Accessible navigation is on but the sheet still only offers the swiper",
|
||||
);
|
||||
|
||||
expect(
|
||||
tester.widget<NewPrimaryButton>(button.first).onPressed,
|
||||
isNotNull,
|
||||
reason: "The send button a screen reader gets is disabled",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> confirmTransactionBuilt({Duration timeout = const Duration(minutes: 3)}) async {
|
||||
await pumpUntil(
|
||||
() => tester.any(find.byType(ConfirmSwiper)) || _model()?.state is FailureState,
|
||||
timeout: timeout,
|
||||
);
|
||||
|
||||
if (tester.any(find.byType(ConfirmSwiper))) {
|
||||
return;
|
||||
}
|
||||
|
||||
fail("No confirm sheet after ${timeout.inSeconds}s. ${_describeSendState()}");
|
||||
}
|
||||
|
||||
Future<void> confirmTransactionCommitted({
|
||||
Duration timeout = const Duration(minutes: 3),
|
||||
}) async {
|
||||
await pumpUntilFound(find.byType(TransactionCommittedScreenActionButton), timeout: timeout);
|
||||
}
|
||||
|
||||
Future<void> expectNoTransactionBuilt({
|
||||
Duration window = const Duration(seconds: 15),
|
||||
}) async {
|
||||
final offered = await pumpUntil(
|
||||
() => tester.any(find.byType(ConfirmSwiper)),
|
||||
timeout: window,
|
||||
);
|
||||
|
||||
expect(
|
||||
offered,
|
||||
false,
|
||||
reason:
|
||||
"The send screen showed the swiper, wanting to broadcast a transaction it should have refused",
|
||||
);
|
||||
}
|
||||
|
||||
SendViewModel? _model() {
|
||||
final sheet = find.byType(SendConfirmSheet);
|
||||
|
||||
if (tester.any(sheet)) {
|
||||
return tester.widget<SendConfirmSheet>(sheet.first).sendViewModel;
|
||||
}
|
||||
|
||||
final page = find.byType(NewSendPage);
|
||||
|
||||
if (tester.any(page)) {
|
||||
return tester.widget<NewSendPage>(page.first).sendViewModel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String _describeSendState() {
|
||||
final model = _model();
|
||||
|
||||
final available = model?.wallet.balance.values
|
||||
.map((balance) => balance.available)
|
||||
.where((amount) => amount.sign > 0)
|
||||
.join(", ");
|
||||
|
||||
final errorFinder = find.byType(TransactionErrorActions);
|
||||
final error = tester.any(errorFinder)
|
||||
? tester.widget<TransactionErrorActions>(errorFinder.first).errorText
|
||||
: model?.state is FailureState
|
||||
? (model!.state as FailureState).error
|
||||
: null;
|
||||
|
||||
return "state=${model?.state.runtimeType}, "
|
||||
"wallet holds ${available == null || available.isEmpty ? "nothing" : available}"
|
||||
"${error == null ? "" : ", the chain said: $error"}";
|
||||
}
|
||||
|
||||
String _textInInput(String inputKey) {
|
||||
final inputFinder = find.byKey(ValueKey(inputKey));
|
||||
|
||||
if (!tester.any(inputFinder)) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
final field = find.descendant(of: inputFinder, matching: find.byType(EditableText));
|
||||
|
||||
return tester.widget<EditableText>(field.first).controller.text;
|
||||
}
|
||||
|
||||
Future<void> _enterTextInInput(String inputKey, String text) async {
|
||||
final inputFinder = find.byKey(ValueKey(inputKey));
|
||||
|
||||
await pumpUntilFound(inputFinder);
|
||||
|
||||
final fieldFinder = find.descendant(of: inputFinder, matching: find.byType(EditableText));
|
||||
|
||||
await tester.enterText(fieldFinder.first, text);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import "package:hash_wallet/new-ui/pages/settings_page.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class NewSettingsPageRobot extends BaseRobot {
|
||||
NewSettingsPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await pumpUntilFound(find.byType(SettingsMainPage));
|
||||
}
|
||||
|
||||
Future<void> openRow(String route) async {
|
||||
await scrollUntilRowVisible(route);
|
||||
await tapByKey(route);
|
||||
}
|
||||
|
||||
Future<void> scrollUntilRowVisible(String route) async {
|
||||
final rowFinder = find.byKey(ValueKey(route));
|
||||
|
||||
if (tester.any(rowFinder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final scrollableFinder = find.descendant(
|
||||
of: find.byType(SettingsMainPage),
|
||||
matching: find.byType(Scrollable),
|
||||
);
|
||||
|
||||
await tester.scrollUntilVisible(
|
||||
rowFinder,
|
||||
200,
|
||||
scrollable: scrollableFinder.first,
|
||||
maxScrolls: 30,
|
||||
);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
Future<void> confirmLeafPageDisplayed(Type pageType) async {
|
||||
await pumpUntilFound(find.byType(pageType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import "package:hash_wallet/core/execution_state.dart";
|
||||
import "package:hash_wallet/exchange/limits_state.dart";
|
||||
import "package:hash_wallet/new-ui/pages/swap_page.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/currency_picker/currency_picker_row.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/currency_picker/currency_picker_search_field.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/swap_page/swap_amount_box.dart";
|
||||
import "package:hash_wallet/new-ui/widgets/swap_page/swap_confirm_sheet.dart";
|
||||
import "package:hash_wallet/src/widgets/primary_button.dart";
|
||||
import "package:hash_wallet/view_model/send/send_view_model_state.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
import "../core/test_config.dart";
|
||||
import "../core/test_wallets.dart";
|
||||
|
||||
class NewSwapPageRobot extends BaseRobot {
|
||||
NewSwapPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await pumpUntilFound(find.byType(NewSwapPage));
|
||||
}
|
||||
|
||||
Future<void> enterDepositAmount(String amount) async {
|
||||
await enterTextByKey("swap_page_deposit_amount_field_key", amount);
|
||||
}
|
||||
|
||||
Future<void> chooseReceiveCurrency(CryptoCurrency currency) async {
|
||||
await tapByKey("swap_amount_box_receive_currency_button_key");
|
||||
|
||||
final search = find.descendant(
|
||||
of: find.byType(CurrencyPickerSearchField),
|
||||
matching: find.byType(EditableText),
|
||||
);
|
||||
|
||||
await pumpUntilFound(search);
|
||||
|
||||
await tester.enterText(search.first, currency.title);
|
||||
await settle(max: const Duration(seconds: 2));
|
||||
|
||||
await tapWhenVisible(
|
||||
find.byWidgetPredicate(
|
||||
(widget) => widget is CurrencyPickerRow && _isSameCurrency(widget.currency, currency),
|
||||
),
|
||||
);
|
||||
|
||||
final chosen = await pumpUntil(
|
||||
() => _isSameCurrency(
|
||||
tester.widget<NewSwapPage>(find.byType(NewSwapPage)).exchangeViewModel.receiveCurrency,
|
||||
currency,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
chosen,
|
||||
true,
|
||||
reason: "The picker did not switch the receive currency to ${currency.title}",
|
||||
);
|
||||
}
|
||||
|
||||
static bool _isSameCurrency(CryptoCurrency one, CryptoCurrency other) =>
|
||||
one.title.toLowerCase() == other.title.toLowerCase() &&
|
||||
(one.tag ?? "").toLowerCase() == (other.tag ?? "").toLowerCase();
|
||||
|
||||
Future<void> confirmQuoteReceived({Duration timeout = const Duration(seconds: 90)}) async {
|
||||
final received = await pumpUntil(() => _bestRate() > 0, timeout: timeout);
|
||||
|
||||
expect(received, true, reason: "No provider quote arrived within ${timeout.inSeconds}s");
|
||||
}
|
||||
|
||||
Future<void> enterMinimumViableDepositAmount() async {
|
||||
final limitsLoaded = await pumpUntil(
|
||||
() => _limitsState() is LimitsLoadedSuccessfully,
|
||||
timeout: const Duration(seconds: 90),
|
||||
);
|
||||
|
||||
expect(limitsLoaded, true, reason: "Provider limits never loaded");
|
||||
|
||||
final min = _limitsMin() ?? 0;
|
||||
|
||||
// 5 percent over the minimum, so a small rate move does not invalidate the trade.
|
||||
final amount = min > 0 ? (min * 1.05).toStringAsFixed(8) : "0.01";
|
||||
await enterDepositAmount(amount);
|
||||
}
|
||||
|
||||
Future<String> enterTestReceiveAddress() async {
|
||||
final exchangeViewModel =
|
||||
tester.widget<NewSwapPage>(find.byType(NewSwapPage)).exchangeViewModel;
|
||||
final receiveType = cryptoCurrencyOrTokenToWalletType(exchangeViewModel.receiveCurrency);
|
||||
final address = receiveType == null ? "" : TestWallets.receiveAddressFor(receiveType);
|
||||
|
||||
expect(
|
||||
address.isNotEmpty,
|
||||
true,
|
||||
reason: "No test receive address for ${exchangeViewModel.receiveCurrency.title}",
|
||||
);
|
||||
|
||||
final receiveCard = find.byWidgetPredicate(
|
||||
(widget) => widget is SwapAmountBox && widget.isReceiverCard,
|
||||
);
|
||||
|
||||
tester.state<SwapAmountBoxState>(receiveCard).addressController.text = address;
|
||||
await settle();
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
Future<void> confirmSwapButtonEnabled({Duration timeout = TestConfig.walletSyncBudget}) async {
|
||||
final button = find.byKey(const ValueKey("exchange_page_exchange_button_key"));
|
||||
|
||||
final enabled = await pumpUntil(
|
||||
() => tester.any(button) && !tester.widget<LoadingPrimaryButton>(button).isDisabled,
|
||||
timeout: timeout,
|
||||
);
|
||||
|
||||
expect(
|
||||
enabled,
|
||||
true,
|
||||
reason: "The swap button stayed disabled with an amount, a quote and a receive address",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> tapSwapButton() async {
|
||||
await tapByKey("exchange_page_exchange_button_key");
|
||||
}
|
||||
|
||||
Future<void> confirmTradeCreated({Duration timeout = const Duration(minutes: 2)}) async {
|
||||
await pumpUntilFound(find.byType(SwapConfirmSheet), timeout: timeout);
|
||||
}
|
||||
|
||||
void confirmPayoutAddress(String expected) {
|
||||
final sheet = tester.widget<SwapConfirmSheet>(find.byType(SwapConfirmSheet));
|
||||
final payout = sheet.exchangeTradeViewModel.trade.payoutAddress ?? "";
|
||||
final isHex = expected.startsWith("0x");
|
||||
|
||||
expect(
|
||||
isHex ? payout.toLowerCase() : payout,
|
||||
isHex ? expected.toLowerCase() : expected,
|
||||
reason: "The trade pays out somewhere other than the test wallet",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> confirmDepositCommitted({Duration timeout = const Duration(minutes: 3)}) async {
|
||||
ExecutionState? lastState;
|
||||
|
||||
final committed = await pumpUntil(
|
||||
() {
|
||||
final sheet = find.byType(SwapConfirmSheet);
|
||||
|
||||
if (!tester.any(sheet)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lastState =
|
||||
tester.widget<SwapConfirmSheet>(sheet).exchangeTradeViewModel.sendViewModel.state;
|
||||
|
||||
return lastState is TransactionCommitted;
|
||||
},
|
||||
timeout: timeout,
|
||||
);
|
||||
|
||||
expect(
|
||||
committed,
|
||||
true,
|
||||
reason: "The deposit never committed within ${timeout.inMinutes}m, "
|
||||
"the sheet reported ${lastState.runtimeType}",
|
||||
);
|
||||
|
||||
expect(
|
||||
lastState,
|
||||
isA<TransactionCommitted>(),
|
||||
reason: "The confirm sheet closed before the deposit committed, "
|
||||
"it last reported ${lastState.runtimeType}",
|
||||
);
|
||||
|
||||
await pumpUntilGone(find.byType(NewSwapPage));
|
||||
}
|
||||
|
||||
double? _limitsMin() {
|
||||
final finder = find.byType(NewSwapPage);
|
||||
|
||||
if (!tester.any(finder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tester.widget<NewSwapPage>(finder.first).exchangeViewModel.limits.min;
|
||||
}
|
||||
|
||||
LimitsState? _limitsState() {
|
||||
final finder = find.byType(NewSwapPage);
|
||||
|
||||
if (!tester.any(finder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tester.widget<NewSwapPage>(finder.first).exchangeViewModel.limitsState;
|
||||
}
|
||||
|
||||
double _bestRate() {
|
||||
final finder = find.byType(NewSwapPage);
|
||||
|
||||
if (!tester.any(finder)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return tester.widget<NewSwapPage>(finder.first).exchangeViewModel.bestRate;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,32 @@
|
||||
import 'package:hash_wallet/src/screens/new_wallet/new_wallet_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/new_wallet/new_wallet_page.dart";
|
||||
import "package:hash_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class NewWalletPageRobot {
|
||||
NewWalletPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class NewWalletPageRobot extends BaseRobot {
|
||||
NewWalletPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isNewWalletPage() async {
|
||||
await commonTestCases.isSpecificPage<NewWalletPage>();
|
||||
await commonTestCases.takeScreenshots('new_wallet_page');
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<NewWalletPage>();
|
||||
}
|
||||
|
||||
Future<void> enterWalletName(String walletName) async {
|
||||
await commonTestCases.enterText(
|
||||
walletName,
|
||||
'new_wallet_page_wallet_name_textformfield_key',
|
||||
);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await enterTextByKey("new_wallet_page_wallet_name_textformfield_key", walletName);
|
||||
}
|
||||
|
||||
Future<void> generateWalletName() async {
|
||||
await commonTestCases.tapItemByKey(
|
||||
'new_wallet_page_wallet_name_textformfield_generate_name_button_key',
|
||||
);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("new_wallet_page_wallet_name_textformfield_generate_name_button_key");
|
||||
}
|
||||
|
||||
Future<void> expectNameRejected() async {
|
||||
await pumpUntilFound(find.byType(AlertWithOneAction));
|
||||
|
||||
hasType<NewWalletPage>();
|
||||
}
|
||||
|
||||
Future<void> onNextButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('new_wallet_page_confirm_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("new_wallet_page_confirm_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,29 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
|
||||
import 'package:hash_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/new_wallet/new_wallet_type_page.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class NewWalletTypePageRobot {
|
||||
NewWalletTypePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class NewWalletTypePageRobot extends BaseRobot {
|
||||
NewWalletTypePageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isNewWalletTypePage() async {
|
||||
await commonTestCases.isSpecificPage<NewWalletTypePage>();
|
||||
await commonTestCases.takeScreenshots('new_wallet_type_page');
|
||||
}
|
||||
|
||||
void displaysCorrectTitle(bool isCreate) {
|
||||
commonTestCases.hasText(
|
||||
isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet,
|
||||
);
|
||||
}
|
||||
|
||||
void hasWalletTypeForm() {
|
||||
commonTestCases.hasType<WalletTypeForm>();
|
||||
}
|
||||
|
||||
void displaysCorrectImage(ThemeType type) {
|
||||
final walletTypeImage = Image.asset('assets/images/wallet_type.png').image;
|
||||
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png').image;
|
||||
|
||||
find.image(
|
||||
type == ThemeType.dark ? walletTypeImage : walletTypeLightImage,
|
||||
);
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<NewWalletTypePage>();
|
||||
}
|
||||
|
||||
Future<void> findParticularWalletTypeInScrollableList(WalletType type) async {
|
||||
await tester.pump(Duration(milliseconds: 1000));
|
||||
|
||||
final scrollableWidget = find.descendant(
|
||||
of: find.byKey(Key('new_wallet_type_scrollable_key')),
|
||||
of: find.byKey(const Key("new_wallet_type_scrollable_key")),
|
||||
matching: find.byType(Scrollable),
|
||||
);
|
||||
|
||||
final targetWidget = find.byKey(ValueKey('new_wallet_type_${type.name}_button_key'));
|
||||
final targetWidget = find.byKey(ValueKey("new_wallet_type_${type.name}_button_key"));
|
||||
|
||||
tester.printToConsole('Attempting to scroll to wallet type ${type.name}');
|
||||
tester.printToConsole("Attempting to scroll to wallet type ${type.name}");
|
||||
|
||||
await tester.pump(Duration(milliseconds: 500));
|
||||
await pumpUntilFound(scrollableWidget.first);
|
||||
|
||||
await tester.scrollUntilVisible(
|
||||
targetWidget,
|
||||
@@ -58,12 +32,14 @@ class NewWalletTypePageRobot {
|
||||
maxScrolls: 20,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
await settle();
|
||||
|
||||
expect(tester.any(targetWidget), true,
|
||||
reason: 'Wallet type ${type.name} should be visible after scrolling');
|
||||
expect(
|
||||
tester.any(targetWidget),
|
||||
true,
|
||||
reason: "Wallet type ${type.name} should be visible after scrolling",
|
||||
);
|
||||
|
||||
// Additional check to ensure the widget is actually on screen
|
||||
final widgetRect = tester.getRect(targetWidget);
|
||||
final screenSize = tester.view.physicalSize;
|
||||
final screenRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
|
||||
@@ -71,17 +47,13 @@ class NewWalletTypePageRobot {
|
||||
expect(
|
||||
screenRect.overlaps(widgetRect),
|
||||
true,
|
||||
reason: 'Wallet type ${type.name} should be within screen bounds',
|
||||
reason: "Wallet type ${type.name} should be within screen bounds",
|
||||
);
|
||||
|
||||
tester.printToConsole('Wallet type ${type.name} is now visible and tappable');
|
||||
tester.printToConsole("Wallet type ${type.name} is now visible and tappable");
|
||||
}
|
||||
|
||||
Future<void> selectWalletType(WalletType type) async {
|
||||
await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key');
|
||||
}
|
||||
|
||||
Future<void> onNextButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('new_wallet_type_next_button_key');
|
||||
await tapByKey("new_wallet_type_${type.name}_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,24 @@
|
||||
import 'package:hash_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/pin_code/pin_code_widget.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class PinCodeWidgetRobot {
|
||||
PinCodeWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class PinCodeWidgetRobot extends BaseRobot {
|
||||
PinCodeWidgetRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
@override
|
||||
Future<void> isDisplayed() async => hasPinCodeWidget();
|
||||
|
||||
void hasPinCodeWidget() {
|
||||
final pinCodeWidget = find.bySubtype<PinCodeWidget>();
|
||||
expect(pinCodeWidget, findsOneWidget);
|
||||
}
|
||||
|
||||
void hasNumberButtonsVisible() {
|
||||
// Confirmation for buttons 1-9
|
||||
for (var i = 1; i < 10; i++) {
|
||||
commonTestCases.hasValueKey('pin_code_button_${i}_key');
|
||||
Future<void> enterPinCode(List<int> pinCode) async {
|
||||
for (final pin in pinCode) {
|
||||
await tapByKey("pin_code_button_${pin}_key");
|
||||
}
|
||||
|
||||
// Confirmation for 0 button
|
||||
commonTestCases.hasValueKey('pin_code_button_0_key');
|
||||
}
|
||||
|
||||
Future<void> enterPassword(String password, {int pumpDuration = 100}) async {
|
||||
await commonTestCases.enterText(
|
||||
password,
|
||||
'enter_wallet_password',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await commonTestCases.tapItemByKey(
|
||||
'unlock',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> enterPinCode(List<int> pinCode, {int pumpDuration = 100}) async {
|
||||
for (int pin in pinCode) {
|
||||
await commonTestCases.tapItemByKey(
|
||||
'pin_code_button_${pin}_key',
|
||||
pumpDuration: pumpDuration,
|
||||
);
|
||||
}
|
||||
|
||||
await commonTestCases.takeScreenshots('pin_code_widget');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await settle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import 'package:hash_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/new-ui/pages/seed/pre_seed_page.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class PreSeedPageRobot {
|
||||
PreSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class PreSeedPageRobot extends BaseRobot {
|
||||
PreSeedPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isPreSeedPage() async {
|
||||
await commonTestCases.isSpecificPage<PreSeedPage>();
|
||||
await commonTestCases.takeScreenshots('pre_seed_page');
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<PreSeedPage>();
|
||||
}
|
||||
|
||||
// The button is only built once all three boxes are ticked
|
||||
Future<void> onConfirmButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('pre_seed_page_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("pre_seed_page_only_way_checkbox_key");
|
||||
await tapByKey("pre_seed_page_write_down_checkbox_key");
|
||||
await tapByKey("pre_seed_page_never_share_checkbox_key");
|
||||
await tapByKey("pre_seed_page_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +1,101 @@
|
||||
import 'package:hash_wallet/entities/seed_type.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:hash_wallet/src/widgets/validable_annotated_editable_text.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/entities/seed_type.dart";
|
||||
import "package:hash_wallet/src/screens/restore/wallet_restore_page.dart";
|
||||
import "package:hash_wallet/src/widgets/primary_button.dart";
|
||||
import "package:hash_wallet/src/widgets/validable_annotated_editable_text.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class RestoreFromSeedOrKeysPageRobot {
|
||||
RestoreFromSeedOrKeysPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class RestoreFromSeedOrKeysPageRobot extends BaseRobot {
|
||||
RestoreFromSeedOrKeysPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isRestoreFromSeedKeyPage() async {
|
||||
await commonTestCases.isSpecificPage<WalletRestorePage>();
|
||||
await commonTestCases.takeScreenshots('wallet_restore_page');
|
||||
}
|
||||
|
||||
Future<void> confirmViewComponentsDisplayProperlyPerPageView() async {
|
||||
commonTestCases.hasText(S.current.wallet_name);
|
||||
commonTestCases.hasText(S.current.enter_seed_phrase);
|
||||
commonTestCases.hasText(S.current.restore_title_from_seed);
|
||||
|
||||
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_name_textfield_key');
|
||||
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key');
|
||||
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
|
||||
commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key');
|
||||
|
||||
commonTestCases.hasText(S.current.private_key, hasWidget: false);
|
||||
commonTestCases.hasText(S.current.restore_title_from_keys, hasWidget: false);
|
||||
|
||||
await commonTestCases.swipePage();
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
commonTestCases.hasText(S.current.wallet_name);
|
||||
commonTestCases.hasText(S.current.private_key);
|
||||
commonTestCases.hasText(S.current.restore_title_from_keys);
|
||||
|
||||
commonTestCases.hasText(S.current.enter_seed_phrase, hasWidget: false);
|
||||
commonTestCases.hasText(S.current.restore_title_from_seed, hasWidget: false);
|
||||
|
||||
await commonTestCases.swipePage(swipeRight: false);
|
||||
}
|
||||
|
||||
void confirmRestoreButtonDisplays() {
|
||||
commonTestCases.hasValueKey('wallet_restore_seed_or_key_restore_button_key');
|
||||
}
|
||||
|
||||
void confirmAdvancedSettingButtonDisplays() {
|
||||
commonTestCases.hasValueKey('wallet_restore_advanced_settings_button_key');
|
||||
}
|
||||
|
||||
Future<void> enterWalletNameText(String walletName, {bool isSeedFormEntry = true}) async {
|
||||
await commonTestCases.enterText(
|
||||
walletName,
|
||||
'wallet_restore_from_${isSeedFormEntry ? 'seed' : 'keys'}_wallet_name_textfield_key',
|
||||
);
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WalletRestorePage>();
|
||||
}
|
||||
|
||||
Future<void> selectWalletNameFromAvailableOptions({bool isSeedFormEntry = true}) async {
|
||||
await commonTestCases.tapItemByKey(
|
||||
await tapByKey(
|
||||
'wallet_restore_from_${isSeedFormEntry ? 'seed' : 'keys'}_wallet_name_refresh_button_key',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> enterSeedPhraseForWalletRestore(String text) async {
|
||||
ValidatableAnnotatedEditableTextState seedTextState =
|
||||
await tester.state(find.byType(ValidatableAnnotatedEditableText));
|
||||
final ValidatableAnnotatedEditableTextState seedTextState =
|
||||
tester.state(find.byType(ValidatableAnnotatedEditableText));
|
||||
|
||||
seedTextState.widget.controller.text = text;
|
||||
await tester.pumpAndSettle();
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> enterPasswordForWalletRestore(String text) async {
|
||||
await commonTestCases.enterText(
|
||||
text,
|
||||
'password',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await enterTextByKey("password", text);
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> enterPasswordRepeatForWalletRestore(String text) async {
|
||||
await commonTestCases.enterText(
|
||||
text,
|
||||
'repeat_wallet_password',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await enterTextByKey("repeat_wallet_password", text);
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> enterBlockHeightForWalletRestore(String blockHeight) async {
|
||||
await commonTestCases.enterText(
|
||||
blockHeight,
|
||||
'wallet_restore_from_seed_blockheight_textfield_key',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await enterTextByKey("wallet_restore_from_seed_blockheight_textfield_key", blockHeight);
|
||||
await settle();
|
||||
}
|
||||
|
||||
Future<void> chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType selectedType) async {
|
||||
await commonTestCases.tapItemByKey('wallet_restore_from_seed_seedtype_picker_button_key');
|
||||
await tapByKey("wallet_restore_from_seed_seedtype_picker_button_key");
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${selectedType.title}_button_key');
|
||||
await tapByKey("picker_items_index_${selectedType.title}_button_key");
|
||||
}
|
||||
|
||||
Future<void> onPasteSeedPhraseButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key');
|
||||
}
|
||||
|
||||
Future<void> enterPrivateKeyForWalletRestore(String privateKey) async {
|
||||
await commonTestCases.enterText(
|
||||
privateKey,
|
||||
'wallet_restore_from_key_private_key_textfield_key',
|
||||
Future<void> expectRestoreRefused({Duration window = const Duration(seconds: 10)}) async {
|
||||
final button = tester.widget<LoadingPrimaryButton>(
|
||||
find.byKey(const ValueKey("wallet_restore_seed_or_key_restore_button_key")),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
button.isDisabled,
|
||||
true,
|
||||
reason: "The restore button accepted a seed the wallet cannot parse",
|
||||
);
|
||||
|
||||
final left = await pumpUntil(
|
||||
() => !tester.any(find.byType(WalletRestorePage)),
|
||||
timeout: window,
|
||||
);
|
||||
|
||||
expect(left, false, reason: "A seed the wallet cannot parse still restored something");
|
||||
}
|
||||
|
||||
Future<void> onRestoreWalletButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("wallet_restore_seed_or_key_restore_button_key");
|
||||
}
|
||||
|
||||
Future<void> enablePassphrase() async {
|
||||
await tapByKey("wallet_restore_has_passphrase_checkbox_key");
|
||||
await settle();
|
||||
|
||||
final page = tester.widget<WalletRestorePage>(find.byType(WalletRestorePage));
|
||||
|
||||
expect(
|
||||
page.walletRestoreViewModel.hasPassphrase,
|
||||
true,
|
||||
reason: "The passphrase box did not tick, the wallet would restore without it",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> enterPassphrase(String passphrase) async {
|
||||
await enterTextByKey(
|
||||
"add_passphrase_bottom_sheet_widget_passphrase_textfield_key",
|
||||
passphrase,
|
||||
);
|
||||
await enterTextByKey(
|
||||
"add_passphrase_bottom_sheet_widget_confirm_passphrase_textfield_key",
|
||||
passphrase,
|
||||
);
|
||||
|
||||
await tapByKey("add_passphrase_bottom_sheet_widget_restore_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,16 @@
|
||||
import 'package:hash_wallet/src/screens/restore/restore_options_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/restore/restore_options_page.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class RestoreOptionsPageRobot {
|
||||
RestoreOptionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class RestoreOptionsPageRobot extends BaseRobot {
|
||||
RestoreOptionsPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isRestoreOptionsPage() async {
|
||||
await commonTestCases.isSpecificPage<RestoreOptionsPage>();
|
||||
await commonTestCases.takeScreenshots('restore_options');
|
||||
}
|
||||
|
||||
void hasRestoreOptionsButton() {
|
||||
commonTestCases.hasValueKey('restore_options_from_seeds_or_keys_button_key');
|
||||
commonTestCases.hasValueKey('restore_options_from_backup_button_key');
|
||||
commonTestCases.hasValueKey('restore_options_from_hardware_wallet_button_key');
|
||||
commonTestCases.hasValueKey('restore_options_from_qr_button_key');
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<RestoreOptionsPage>();
|
||||
}
|
||||
|
||||
Future<void> navigateToRestoreFromSeedsOrKeysPage() async {
|
||||
await commonTestCases.tapItemByKey('restore_options_from_seeds_or_keys_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> navigateToRestoreFromBackupPage() async {
|
||||
await commonTestCases.tapItemByKey('restore_options_from_backup_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> navigateToRestoreFromHardwareWalletPage() async {
|
||||
await commonTestCases.tapItemByKey('restore_options_from_hardware_wallet_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> backAndVerify() async {
|
||||
await commonTestCases.goBack();
|
||||
await isRestoreOptionsPage();
|
||||
await tapByKey("restore_options_from_seeds_or_keys_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/settings/security_backup_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class SecurityAndBackupPageRobot {
|
||||
SecurityAndBackupPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isSecurityAndBackupPage() async {
|
||||
await commonTestCases.isSpecificPage<SecurityBackupPage>();
|
||||
await commonTestCases.takeScreenshots('security_backup_page');
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
commonTestCases.hasText(S.current.security_and_backup);
|
||||
}
|
||||
|
||||
Future<void> navigateToShowKeysPage() async {
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
|
||||
await commonTestCases.tapItemByKey('security_backup_page_show_keys_button_key');
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import "package:hash_wallet/src/screens/settings/security_backup_page.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class SecurityBackupPageRobot extends BaseRobot {
|
||||
SecurityBackupPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<SecurityBackupPage>();
|
||||
}
|
||||
|
||||
Future<void> openChangePin() async {
|
||||
await tapByKey("security_backup_page_change_pin_button_key");
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,19 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/generated/i18n.dart";
|
||||
import "package:hash_wallet/src/screens/seed/seed_verification/seed_verification_page.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class SeedVerificationPageRobot {
|
||||
SeedVerificationPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class SeedVerificationPageRobot extends BaseRobot {
|
||||
SeedVerificationPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isSeedVerificationPage() async {
|
||||
await commonTestCases.isSpecificPage<SeedVerificationPage>();
|
||||
await commonTestCases.takeScreenshots('seed_verification_page');
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<SeedVerificationPage>();
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
commonTestCases.hasText(S.current.verify_seed);
|
||||
hasText(S.current.verify_seed);
|
||||
}
|
||||
|
||||
Future<void> verifyWalletSeeds() async {
|
||||
@@ -28,18 +25,23 @@ class SeedVerificationPageRobot {
|
||||
while (!walletSeedViewModel.isVerificationComplete &&
|
||||
walletSeedViewModel.verificationWordCount != 0) {
|
||||
final currentCorrectWord = walletSeedViewModel.currentCorrectWord;
|
||||
final currentStep = walletSeedViewModel.currentStepIndex;
|
||||
|
||||
commonTestCases.hasTextAtLestOnce(currentCorrectWord);
|
||||
hasTextAtLeastOnce(currentCorrectWord);
|
||||
|
||||
await commonTestCases.tapItemByKey(
|
||||
'seed_verification_option_${currentCorrectWord}_button_key',
|
||||
await tapByKey("seed_verification_option_${currentCorrectWord}_button_key");
|
||||
|
||||
final hasMovedOn = await pumpUntil(
|
||||
() =>
|
||||
walletSeedViewModel.isVerificationComplete ||
|
||||
walletSeedViewModel.currentStepIndex != currentStep,
|
||||
);
|
||||
|
||||
await commonTestCases.defaultSleepTime(seconds: 1);
|
||||
if (!hasMovedOn) {
|
||||
throw TestFailure("Seed verification never moved past step $currentStep");
|
||||
}
|
||||
}
|
||||
|
||||
await commonTestCases.tapItemByKey('wallet_seed_page_open_wallet_button_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("wallet_seed_page_open_wallet_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,716 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:hash_wallet/core/execution_state.dart';
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/send/send_page.dart';
|
||||
import 'package:hash_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:hash_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:hash_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import '../components/common_test_constants.dart';
|
||||
import 'auth_page_robot.dart';
|
||||
import 'package:hash_wallet/src/widgets/standard_slide_button_widget.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/dashboard_page.dart';
|
||||
|
||||
class SendPageRobot {
|
||||
SendPageRobot({required this.tester})
|
||||
: commonTestCases = CommonTestCases(tester),
|
||||
authPageRobot = AuthPageRobot(tester);
|
||||
|
||||
WidgetTester tester;
|
||||
CommonTestCases commonTestCases;
|
||||
AuthPageRobot authPageRobot;
|
||||
|
||||
Future<void> isSendPage() async {
|
||||
await commonTestCases.isSpecificPage<SendPage>();
|
||||
await commonTestCases.takeScreenshots('send_page');
|
||||
}
|
||||
|
||||
Future<void> waitForSendPage() async {
|
||||
tester.printToConsole('Waiting for SendPage to be available');
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
final maxDuration = Duration(seconds: 30);
|
||||
|
||||
while (stopwatch.elapsed < maxDuration) {
|
||||
await tester.pump(Duration(milliseconds: 500));
|
||||
|
||||
// Check if we're still on auth page
|
||||
if (authPageRobot.onAuthPage()) {
|
||||
tester.printToConsole('Still on auth page, waiting...');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if SendPage is available
|
||||
final sendPageFinder = find.byType(SendPage);
|
||||
if (sendPageFinder.tryEvaluate()) {
|
||||
tester.printToConsole('SendPage found!');
|
||||
return;
|
||||
}
|
||||
|
||||
tester.printToConsole('SendPage not found yet, waiting...');
|
||||
}
|
||||
|
||||
throw Exception('SendPage not found after ${maxDuration.inSeconds} seconds');
|
||||
}
|
||||
|
||||
Future<void> checkIfSendPageIsVisible() async {
|
||||
tester.printToConsole('Confirming SendPage is visible');
|
||||
|
||||
if (authPageRobot.onAuthPage()) {
|
||||
tester.printToConsole('Auth page is currently visible');
|
||||
await _handleAuthPage();
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
await waitForSendPage();
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
commonTestCases.hasText(S.current.send);
|
||||
}
|
||||
|
||||
void confirmViewComponentsDisplayProperly() {
|
||||
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||
final sendViewModel = sendPage.sendViewModel;
|
||||
|
||||
commonTestCases.hasValueKey('send_page_address_textfield_key');
|
||||
commonTestCases.hasValueKey('send_page_note_textfield_key');
|
||||
commonTestCases.hasValueKey('send_page_amount_textfield_key');
|
||||
commonTestCases.hasValueKey('send_page_add_template_button_key');
|
||||
|
||||
if (sendViewModel.hasMultipleTokens) {
|
||||
commonTestCases.hasValueKey('send_page_currency_picker_button_key');
|
||||
}
|
||||
|
||||
if (!sendViewModel.isBatchSending) {
|
||||
commonTestCases.hasValueKey('send_page_send_all_button_key');
|
||||
}
|
||||
|
||||
if (!sendViewModel.isFiatDisabled) {
|
||||
commonTestCases.hasValueKey('send_page_fiat_amount_textfield_key');
|
||||
}
|
||||
|
||||
if (sendViewModel.feesViewModel.hasFees) {
|
||||
commonTestCases.hasValueKey('send_page_select_fee_priority_button_key');
|
||||
}
|
||||
|
||||
if (sendViewModel.hasCoinControl) {
|
||||
commonTestCases.hasValueKey('send_page_unspent_coin_button_key');
|
||||
}
|
||||
|
||||
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient) {
|
||||
commonTestCases.hasValueKey('send_page_add_receiver_button_key');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> selectReceiveCurrency(CryptoCurrency receiveCurrency) async {
|
||||
final currencyPickerKey = 'send_page_currency_picker_button_key';
|
||||
final currencyPickerDialogKey = 'send_page_currency_picker_dialog_button_key';
|
||||
|
||||
await commonTestCases.tapItemByKey(currencyPickerKey);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
// Check if picker dialog is present
|
||||
if (!commonTestCases.isKeyPresent(currencyPickerDialogKey)) {
|
||||
tester.printToConsole('Currency picker dialog not found, may already be selected');
|
||||
return;
|
||||
}
|
||||
|
||||
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||
final sendViewModel = sendPage.sendViewModel;
|
||||
|
||||
if (receiveCurrency == sendViewModel.selectedCryptoCurrency) {
|
||||
await commonTestCases
|
||||
.tapItemByKey('picker_items_index_${receiveCurrency.name}_selected_item_button_key');
|
||||
return;
|
||||
}
|
||||
|
||||
await commonTestCases.enterText(receiveCurrency.title, 'search_bar_widget_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.fullName}_button_key');
|
||||
}
|
||||
|
||||
Future<void> enterReceiveAddress(String receiveAddress) async {
|
||||
await commonTestCases.enterText(receiveAddress, 'send_page_address_textfield_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> enterSendAmount(String amount, {bool isFiat = false}) async {
|
||||
await commonTestCases.enterText(
|
||||
amount,
|
||||
isFiat ? 'send_page_fiat_amount_textfield_key' : 'send_page_amount_textfield_key',
|
||||
);
|
||||
}
|
||||
|
||||
String _getTextFromField(ValueKey<String> key) {
|
||||
final field = find.byKey(key);
|
||||
if (!field.tryEvaluate()) return '';
|
||||
final baseTextFormField = tester.widget<BaseTextFormField>(field);
|
||||
return baseTextFormField.controller?.text ?? '';
|
||||
}
|
||||
|
||||
/// Validates wallet balance for $1 send by checking against the converted crypto amount
|
||||
/// Returns true if wallet has sufficient balance, false otherwise
|
||||
Future<bool> validateWalletBalanceForOneDollarSend() async {
|
||||
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||
final sendViewModel = sendPage.sendViewModel;
|
||||
|
||||
final balance = await sendViewModel.sendingBalance;
|
||||
|
||||
await setupOneDollarSend();
|
||||
|
||||
// Get the crypto amount that was set for the $1 send
|
||||
String cryptoAmount = _getTextFromField(ValueKey('send_page_amount_textfield_key'));
|
||||
if (cryptoAmount.isEmpty || cryptoAmount == '0' || cryptoAmount == '0.0') {
|
||||
cryptoAmount = '0.001'; // fallback amount
|
||||
}
|
||||
|
||||
final amount = double.tryParse(cryptoAmount) ?? 0.0;
|
||||
|
||||
tester.printToConsole(
|
||||
'Wallet balance: $balance, sending amount for \$${CommonTestConstants.sendTestFiatAmount}: $cryptoAmount',
|
||||
);
|
||||
|
||||
if (balance.isEmpty || double.tryParse(balance) == null) {
|
||||
tester.printToConsole('Invalid wallet balance: $balance');
|
||||
return false;
|
||||
}
|
||||
|
||||
final balanceValue = double.parse(balance);
|
||||
if (balanceValue < amount) {
|
||||
tester.printToConsole(
|
||||
'Insufficient balance for \$${CommonTestConstants.sendTestFiatAmount} send: $balanceValue < $amount',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
tester.printToConsole(
|
||||
'Wallet has sufficient balance for \$${CommonTestConstants.sendTestFiatAmount} send',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> selectTransactionPriority({TransactionPriority? priority}) async {
|
||||
SendPage sendPage = tester.widget(find.byType(SendPage));
|
||||
final sendViewModel = sendPage.sendViewModel;
|
||||
|
||||
if (!sendViewModel.feesViewModel.hasFees || priority == null) return;
|
||||
|
||||
final transactionPriorityPickerKey = 'send_page_select_fee_priority_button_key';
|
||||
await commonTestCases.tapItemByKey(transactionPriorityPickerKey);
|
||||
|
||||
if (priority == sendViewModel.feesViewModel.transactionPriority) {
|
||||
await commonTestCases
|
||||
.tapItemByKey('picker_items_index_${priority.title}_selected_item_button_key');
|
||||
return;
|
||||
}
|
||||
|
||||
await commonTestCases.dragUntilVisible(
|
||||
'picker_items_index_${priority.title}_button_key',
|
||||
'picker_scrollbar_key',
|
||||
);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${priority.title}_button_key');
|
||||
}
|
||||
|
||||
Future<void> onSendButtonPressed() async {
|
||||
tester.printToConsole('Pressing send');
|
||||
|
||||
await checkIfSendPageIsVisible();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
final sendPage = tester.widget<SendPage>(find.byType(SendPage));
|
||||
|
||||
while (true) {
|
||||
bool isReadyForSend = sendPage.sendViewModel.isReadyForSend;
|
||||
await tester.pump();
|
||||
if (isReadyForSend) {
|
||||
tester.printToConsole('Is ready for send');
|
||||
break;
|
||||
} else {
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tester.pumpAndSettle();
|
||||
tester.printToConsole('not yet ready for send');
|
||||
}
|
||||
}
|
||||
await commonTestCases.tapItemByKey(
|
||||
'send_page_send_button_key',
|
||||
shouldPumpAndSettle: false,
|
||||
);
|
||||
|
||||
await _waitForSendTransactionCompletion();
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> _waitForSendTransactionCompletion() async {
|
||||
await tester.pump();
|
||||
final Completer<void> completer = Completer<void>();
|
||||
|
||||
// Loop to wait for the async operation to complete
|
||||
while (true) {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
|
||||
tester.printToConsole('Before _handleAuth');
|
||||
|
||||
await _handleAuthPage();
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
tester.printToConsole('After _handleAuth');
|
||||
|
||||
await tester.pump();
|
||||
|
||||
if (authPageRobot.onAuthPage()) {
|
||||
tester.printToConsole('Still on auth page, continuing to wait');
|
||||
continue;
|
||||
}
|
||||
|
||||
final sendPageFinder = find.byType(SendPage);
|
||||
if (!sendPageFinder.tryEvaluate()) {
|
||||
tester.printToConsole('SendPage not found yet, continuing to wait');
|
||||
continue;
|
||||
}
|
||||
|
||||
final sendPage = tester.widget<SendPage>(sendPageFinder);
|
||||
final state = sendPage.sendViewModel.state;
|
||||
|
||||
await tester.pump();
|
||||
|
||||
bool isDone = state is ExecutedSuccessfullyState || state is TransactionCommitted;
|
||||
bool isFailed = state is FailureState;
|
||||
|
||||
tester.printToConsole('isDone: $isDone');
|
||||
tester.printToConsole('isFailed: $isFailed');
|
||||
|
||||
if (isDone || isFailed) {
|
||||
tester.printToConsole(
|
||||
isDone ? 'Completer is done' : 'Completer is done though operation failed',
|
||||
);
|
||||
completer.complete();
|
||||
await tester.pump();
|
||||
break;
|
||||
} else {
|
||||
tester.printToConsole('Completer is not done');
|
||||
await tester.pump();
|
||||
}
|
||||
}
|
||||
|
||||
await expectLater(completer.future, completes);
|
||||
|
||||
tester.printToConsole('Done confirming sending operation');
|
||||
}
|
||||
|
||||
Future<void> _handleAuthPage() async {
|
||||
tester.printToConsole('Inside _handleAuth');
|
||||
|
||||
final onAuthPageDesktop = authPageRobot.onAuthPageDesktop();
|
||||
if (onAuthPageDesktop) {
|
||||
await authPageRobot.enterPassword(CommonTestConstants.pin.join(""));
|
||||
await commonTestCases.defaultSleepTime();
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.pump();
|
||||
tester.printToConsole('starting auth checks');
|
||||
|
||||
final authPage = authPageRobot.onAuthPage();
|
||||
|
||||
tester.printToConsole('hasAuth:$authPage');
|
||||
|
||||
if (authPage) {
|
||||
await tester.pump();
|
||||
tester.printToConsole('Starting inner _handleAuth loop checks');
|
||||
|
||||
try {
|
||||
await authPageRobot.enterPinCode(CommonTestConstants.pin, pumpDuration: 500);
|
||||
tester.printToConsole('Auth done');
|
||||
|
||||
await tester.pump(Duration(seconds: 3));
|
||||
|
||||
tester.printToConsole('Auth pump done');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
} catch (e) {
|
||||
tester.printToConsole('Auth failed, retrying: $e');
|
||||
await tester.pump();
|
||||
|
||||
await commonTestCases.defaultSleepTime(seconds: 1);
|
||||
await _handleAuthPage();
|
||||
}
|
||||
} else {
|
||||
tester.printToConsole('No auth page detected, proceeding');
|
||||
}
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
//* ------ On Sending Failure ------------
|
||||
Future<bool> hasErrorWhileSending() async {
|
||||
await tester.pump();
|
||||
|
||||
tester.printToConsole('Checking if there is an error');
|
||||
|
||||
final errorDialog = find.byKey(ValueKey('send_page_send_failure_dialog_button_key'));
|
||||
|
||||
bool hasError = errorDialog.tryEvaluate();
|
||||
|
||||
tester.printToConsole('Has error: $hasError');
|
||||
|
||||
return hasError;
|
||||
}
|
||||
|
||||
Future<void> onSendFailureDialogButtonPressed() async {
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
tester.printToConsole('Send Button Failure Dialog Triggered');
|
||||
|
||||
await commonTestCases.tapItemByKey('send_page_send_failure_dialog_button_key');
|
||||
}
|
||||
|
||||
//* ------ On Sending Success ------------
|
||||
Future<void> onSendSliderOnConfirmSendingBottomSheetDragged() async {
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tester.pump();
|
||||
|
||||
if (commonTestCases.isKeyPresent('send_page_confirm_sending_bottom_sheet_key')) {
|
||||
tester.printToConsole('Found confirm sending bottom sheet, starting slider drag');
|
||||
|
||||
final accessibleButton = find.byType(PrimaryButton);
|
||||
if (accessibleButton.tryEvaluate()) {
|
||||
tester.printToConsole('Found accessible navigation button, tapping it');
|
||||
await tester.tap(accessibleButton);
|
||||
await tester.pumpAndSettle();
|
||||
} else {
|
||||
await _performSendSliderDrag();
|
||||
}
|
||||
|
||||
tester.printToConsole('Slider/button action completed, waiting for transaction completion');
|
||||
|
||||
// Check if bottom sheet is dismissed
|
||||
if (commonTestCases.isKeyPresent('send_page_confirm_sending_bottom_sheet_key')) {
|
||||
tester.printToConsole('Bottom sheet still present, waiting a bit more for dismissal');
|
||||
await commonTestCases.defaultSleepTime(seconds: 3);
|
||||
|
||||
// If still present, try one more manual dismissal attempt
|
||||
if (commonTestCases.isKeyPresent('send_page_confirm_sending_bottom_sheet_key')) {
|
||||
tester.printToConsole('Bottom sheet still present, attempting final manual dismissal');
|
||||
await tester.tapAt(Offset(200, 200)); // Tap outside
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for transaction completion
|
||||
await _waitForCommitTransactionCompletion();
|
||||
await commonTestCases.defaultSleepTime(seconds: 2);
|
||||
} else {
|
||||
tester.printToConsole('Confirm sending bottom sheet not found, waiting and retrying');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tester.pump();
|
||||
await onSendSliderOnConfirmSendingBottomSheetDragged();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _performSendSliderDrag() async {
|
||||
final sliderFinder = find.byKey(const ValueKey('standard_slide_button_widget_slider_key'));
|
||||
expect(sliderFinder, findsOneWidget, reason: 'Slider should be found');
|
||||
|
||||
// Find the StandardSlideButton widget to get the main container
|
||||
final slideButtonFinder = find.byType(StandardSlideButton);
|
||||
expect(slideButtonFinder, findsOneWidget, reason: 'StandardSlideButton should be found');
|
||||
|
||||
// Get the main container bounds (the outer container, not the slider container)
|
||||
final mainContainerRect = tester.getRect(slideButtonFinder);
|
||||
final containerWidth = mainContainerRect.width;
|
||||
|
||||
final sideMargin = 4.0;
|
||||
final sliderWidth = 42.0;
|
||||
final effectiveMaxWidth = containerWidth - 2 * sideMargin;
|
||||
final threshold = effectiveMaxWidth - sliderWidth - 10;
|
||||
|
||||
// Add a small buffer to ensure we exceed the threshold
|
||||
final dragDistance = threshold + 20;
|
||||
|
||||
tester.printToConsole(
|
||||
'Main container width: $containerWidth, Threshold: $threshold, Drag distance: $dragDistance',
|
||||
);
|
||||
|
||||
// Start the drag
|
||||
await tester.drag(sliderFinder, Offset(dragDistance, 0));
|
||||
|
||||
// Wait for the drag to complete and trigger onHorizontalDragEnd
|
||||
await tester.pump(Duration(milliseconds: 100));
|
||||
|
||||
// Release the drag (this should trigger onHorizontalDragEnd)
|
||||
await tester.pump(Duration(seconds: 2));
|
||||
|
||||
tester.printToConsole('Drag completed, waiting for callback');
|
||||
|
||||
// Wait for the slide completion callback to trigger and bottom sheet to dismiss
|
||||
await _waitForBottomSheetDismissal();
|
||||
}
|
||||
|
||||
Future<void> _waitForBottomSheetDismissal() async {
|
||||
tester.printToConsole('Waiting for bottom sheet dismissal');
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
final maxDuration = Duration(seconds: 10);
|
||||
|
||||
while (stopwatch.elapsed < maxDuration) {
|
||||
await tester.pump(Duration(milliseconds: 500));
|
||||
|
||||
// Check if the confirm bottom sheet is still present
|
||||
if (!commonTestCases.isKeyPresent('send_page_confirm_sending_bottom_sheet_key')) {
|
||||
tester.printToConsole('Bottom sheet dismissed successfully!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if transaction has started (this indicates slider was successful)
|
||||
if (_isTransactionStarted()) {
|
||||
tester.printToConsole(
|
||||
'Transaction started, slider was successful even if bottom sheet is still visible');
|
||||
return;
|
||||
}
|
||||
|
||||
tester.printToConsole('Bottom sheet still present, waiting');
|
||||
}
|
||||
|
||||
tester.printToConsole('Bottom sheet dismissal timeout reached, trying manual dismissal');
|
||||
|
||||
// If the bottom sheet is still present after timeout, try to manually dismiss it
|
||||
// This might happen if the slider drag didn't trigger the onSlideComplete properly
|
||||
if (commonTestCases.isKeyPresent('send_page_confirm_sending_bottom_sheet_key')) {
|
||||
tester.printToConsole('Attempting manual bottom sheet dismissal');
|
||||
|
||||
// Try to find and tap a close button or back button
|
||||
final closeButton = find.byIcon(Icons.close);
|
||||
if (closeButton.tryEvaluate()) {
|
||||
await tester.tap(closeButton);
|
||||
await tester.pumpAndSettle();
|
||||
tester.printToConsole('Manual close button tapped');
|
||||
} else {
|
||||
// Try to pop the bottom sheet by tapping outside or using back gesture
|
||||
await tester.tapAt(Offset(100, 100)); // Tap outside the bottom sheet
|
||||
await tester.pumpAndSettle();
|
||||
tester.printToConsole('Tapped outside bottom sheet');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _isTransactionStarted() {
|
||||
try {
|
||||
// Check if SendPage is available and transaction state has changed
|
||||
final sendPageFinder = find.byType(SendPage);
|
||||
if (!sendPageFinder.tryEvaluate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final sendPage = tester.widget<SendPage>(sendPageFinder);
|
||||
final state = sendPage.sendViewModel.state;
|
||||
|
||||
// Check if we're in a transaction-related state
|
||||
return state is TransactionCommitting ||
|
||||
state is IsExecutingState ||
|
||||
state is TransactionCommitted ||
|
||||
state is ExecutedSuccessfullyState;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _waitForCommitTransactionCompletion() async {
|
||||
tester.printToConsole('Starting to wait for transaction completion');
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
final maxDuration = Duration(seconds: 60);
|
||||
|
||||
while (stopwatch.elapsed < maxDuration) {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
|
||||
// Check if the confirm bottom sheet is gone (transaction started)
|
||||
if (!commonTestCases.isKeyPresent('send_page_confirm_sending_bottom_sheet_key')) {
|
||||
tester.printToConsole('Confirm bottom sheet disappeared, transaction may be processing');
|
||||
}
|
||||
|
||||
// Check if we've navigated back to the dashboard (for cases of successful transaction)
|
||||
final dashboardFinder = find.byType(DashboardPage);
|
||||
if (dashboardFinder.tryEvaluate()) {
|
||||
tester.printToConsole('Dashboard detected, transaction completed successfully!');
|
||||
await tester.pump();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if SendPage is still available (transaction still processing)
|
||||
final sendPageFinder = find.byType(SendPage);
|
||||
if (sendPageFinder.tryEvaluate()) {
|
||||
final sendPage = tester.widget<SendPage>(sendPageFinder);
|
||||
final state = sendPage.sendViewModel.state;
|
||||
|
||||
bool isDone = state is ExecutedSuccessfullyState || state is TransactionCommitted;
|
||||
bool isFailed = state is FailureState;
|
||||
|
||||
tester.printToConsole('Transaction state: $state');
|
||||
tester.printToConsole('isDone: $isDone');
|
||||
tester.printToConsole('isFailed: $isFailed');
|
||||
|
||||
if (isDone) {
|
||||
tester.printToConsole('Transaction committed successfully!');
|
||||
await tester.pump();
|
||||
return;
|
||||
} else if (isFailed) {
|
||||
tester.printToConsole('Transaction failed: $state');
|
||||
await tester.pump();
|
||||
return;
|
||||
} else {
|
||||
tester.printToConsole('Transaction still processing');
|
||||
await tester.pump();
|
||||
}
|
||||
} else {
|
||||
// SendPage not found, check if we're on dashboard or still processing
|
||||
if (dashboardFinder.tryEvaluate()) {
|
||||
tester.printToConsole('Dashboard detected - transaction completed successfully!');
|
||||
await tester.pump();
|
||||
return;
|
||||
} else {
|
||||
tester.printToConsole(
|
||||
'SendPage not found, but dashboard not yet visible - continuing to wait...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stopwatch.elapsed >= maxDuration) {
|
||||
tester.printToConsole('Transaction completion timeout reached');
|
||||
}
|
||||
|
||||
tester.printToConsole('Done waiting for transaction completion');
|
||||
}
|
||||
|
||||
//* ---- Handle Transaction Success Flow -----
|
||||
Future<void> handleTransactionSuccessFlow() async {
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
// Wait for any success dialogs to appear
|
||||
await tester.pump(Duration(seconds: 2));
|
||||
|
||||
// Check for contact addition dialog first (if new contact address exists)
|
||||
final contactDialog = find.byKey(ValueKey('send_page_add_contact_bottom_sheet_yes_button_key'));
|
||||
if (contactDialog.tryEvaluate()) {
|
||||
tester.printToConsole('Found contact addition dialog, selecting Yes');
|
||||
|
||||
// Check if the button is actually visible and tappable
|
||||
final buttonRect = tester.getRect(contactDialog);
|
||||
final screenSize = tester.view.physicalSize / tester.view.devicePixelRatio;
|
||||
|
||||
if (buttonRect.bottom <= screenSize.height && buttonRect.top >= 0) {
|
||||
await commonTestCases.tapItemByKey('send_page_add_contact_bottom_sheet_yes_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
} else {
|
||||
tester.printToConsole('Contact dialog button is off-screen, skipping');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the main success dialog
|
||||
final successDialog = find.byKey(ValueKey('send_page_sent_dialog_ok_button_key'));
|
||||
if (successDialog.tryEvaluate()) {
|
||||
tester.printToConsole('Found transaction success dialog, closing it');
|
||||
await commonTestCases.tapItemByKey('send_page_sent_dialog_ok_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
}
|
||||
|
||||
//* ---- Fiat/Crypto Amount Validation -----
|
||||
Future<void> testFiatAmountEntry() async {
|
||||
tester.printToConsole('Testing fiat amount entry...');
|
||||
|
||||
await enterSendAmount('');
|
||||
await enterSendAmount('', isFiat: true);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await enterSendAmount(CommonTestConstants.sendTestFiatAmount, isFiat: true);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
// Wait for conversion to complete
|
||||
await tester.pump(Duration(seconds: 3));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Next we get the crypto amount value and validate it's not 0
|
||||
final cryptoAmount = _getTextFromField(ValueKey('send_page_amount_textfield_key'));
|
||||
|
||||
tester.printToConsole(
|
||||
'Crypto amount after entering \$${CommonTestConstants.sendTestFiatAmount}: $cryptoAmount',
|
||||
);
|
||||
|
||||
if (cryptoAmount.isNotEmpty && cryptoAmount != '0' && cryptoAmount != '0.0') {
|
||||
tester.printToConsole('Fiat to crypto conversion working - crypto amount: $cryptoAmount');
|
||||
} else {
|
||||
tester.printToConsole(
|
||||
'Fiat to crypto conversion may not be working - crypto amount: $cryptoAmount',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> testCryptoAmountEntry() async {
|
||||
tester.printToConsole('Testing crypto amount entry...');
|
||||
|
||||
await enterSendAmount('');
|
||||
await enterSendAmount('', isFiat: true);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
String cryptoAmount = '0.001';
|
||||
|
||||
await enterSendAmount(cryptoAmount);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
// Wait for conversion to complete
|
||||
await tester.pump(Duration(seconds: 3));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Get the fiat amount value and validate it's not 0
|
||||
final fiatAmount = _getTextFromField(ValueKey('send_page_fiat_amount_textfield_key'));
|
||||
|
||||
tester.printToConsole('Fiat amount after entering $cryptoAmount: $fiatAmount');
|
||||
|
||||
if (fiatAmount.isNotEmpty && fiatAmount != '0' && fiatAmount != '0.0') {
|
||||
tester.printToConsole('Crypto to fiat conversion working, fiat amount: $fiatAmount');
|
||||
} else {
|
||||
tester.printToConsole(
|
||||
'Crypto to fiat conversion may not be working - fiat amount: $fiatAmount');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setupOneDollarSend() async {
|
||||
// Clear existing amounts
|
||||
await enterSendAmount('');
|
||||
await enterSendAmount('', isFiat: true);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await enterSendAmount(CommonTestConstants.sendTestFiatAmount, isFiat: true);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
// Wait for conversion to complete
|
||||
await tester.pump(Duration(seconds: 3));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Get the converted crypto amount
|
||||
String cryptoAmount = _getTextFromField(ValueKey('send_page_amount_textfield_key'));
|
||||
if (cryptoAmount.isEmpty || cryptoAmount == '0' || cryptoAmount == '0.0') {
|
||||
cryptoAmount = '0.001'; // fallback amount
|
||||
}
|
||||
|
||||
tester.printToConsole(
|
||||
'Sending $cryptoAmount (equivalent to \$${CommonTestConstants.sendTestFiatAmount}) to test wallet',
|
||||
);
|
||||
|
||||
// Update the amount field with the converted value
|
||||
await enterSendAmount(cryptoAmount);
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,16 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/setup_pin_code/setup_pin_code.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import 'pin_code_widget_robot.dart';
|
||||
import "pin_code_widget_robot.dart";
|
||||
|
||||
class SetupPinCodeRobot extends PinCodeWidgetRobot {
|
||||
SetupPinCodeRobot(this.tester)
|
||||
: commonTestCases = CommonTestCases(tester),
|
||||
super(tester);
|
||||
SetupPinCodeRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isSetupPinCodePage() async {
|
||||
await commonTestCases.isSpecificPage<SetupPinCodePage>();
|
||||
await commonTestCases.takeScreenshots('setup_pin_code_page');
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
commonTestCases.hasText(S.current.setup_pin);
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<SetupPinCodePage>();
|
||||
}
|
||||
|
||||
Future<void> tapSuccessButton() async {
|
||||
await commonTestCases.tapItemByKey('setup_pin_code_success_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("setup_pin_code_success_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import "package:hash_wallet/new-ui/pages/seed/show_keys_disclaimer_page.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class ShowKeysDisclaimerPageRobot extends BaseRobot {
|
||||
ShowKeysDisclaimerPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<ShowKeysDisclaimerPage>();
|
||||
}
|
||||
|
||||
Future<void> tapShowKeys() async {
|
||||
await tapByKey("show_keys_disclaimer_page_button_key");
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:hash_wallet/src/screens/send/transaction_success_info_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class TransactionSuccessInfoRobot {
|
||||
TransactionSuccessInfoRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isTransactionSuccessInfoPage() async {
|
||||
await commonTestCases.isSpecificPage<TransactionSuccessPage>();
|
||||
await commonTestCases.takeScreenshots('transaction_success_info_page');
|
||||
}
|
||||
|
||||
Future<void> onConfirmButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('transaction_success_info_page_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/dashboard/pages/transactions_page.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/date_section_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/trade_list_item.dart';
|
||||
import 'package:hash_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class TransactionsPageRobot {
|
||||
TransactionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isTransactionsPage() async {
|
||||
await commonTestCases.isSpecificPage<TransactionsPage>();
|
||||
await commonTestCases.takeScreenshots('transactions_page');
|
||||
}
|
||||
|
||||
Future<void> confirmTransactionsPageConstantsDisplayProperly() async {
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
final transactionsPage = tester.widget<TransactionsPage>(find.byType(TransactionsPage));
|
||||
final dashboardViewModel = transactionsPage.dashboardViewModel;
|
||||
if (dashboardViewModel.status is SyncingSyncStatus) {
|
||||
commonTestCases.hasValueKey('transactions_page_syncing_alert_card_key');
|
||||
commonTestCases.hasText(S.current.syncing_wallet_alert_title);
|
||||
commonTestCases.hasText(S.current.syncing_wallet_alert_content);
|
||||
}
|
||||
|
||||
commonTestCases.hasValueKey('transactions_page_header_row_key');
|
||||
commonTestCases.hasText(S.current.transactions);
|
||||
commonTestCases.hasValueKey('transactions_page_header_row_transaction_filter_button_key');
|
||||
}
|
||||
|
||||
Future<void> confirmTransactionHistoryListDisplaysCorrectly(bool hasTxHistoryWhileSyncing) async {
|
||||
try {
|
||||
final transactionsPage = tester.widget<TransactionsPage>(find.byType(TransactionsPage));
|
||||
final dashboardViewModel = transactionsPage.dashboardViewModel;
|
||||
|
||||
await _waitForListToBeReady();
|
||||
|
||||
if (!hasTxHistoryWhileSyncing) {
|
||||
await _waitForSyncToComplete(dashboardViewModel);
|
||||
} else {
|
||||
await tester.pump(Duration(seconds: 2));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
await _performComprehensiveItemCheck(dashboardViewModel);
|
||||
} catch (e) {
|
||||
tester.printToConsole('Error in transaction history check: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _waitForSyncToComplete(DashboardViewModel dashboardViewModel) async {
|
||||
const maxWaitTime = Duration(minutes: 2);
|
||||
final endTime = DateTime.now().add(maxWaitTime);
|
||||
|
||||
tester.printToConsole('Waiting for wallet to sync...');
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
await tester.pump(Duration(seconds: 2));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
if (dashboardViewModel.status is SyncedSyncStatus) {
|
||||
tester.printToConsole('Wallet synced successfully');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have now have transaction items available
|
||||
if (dashboardViewModel.items.isNotEmpty) {
|
||||
tester.printToConsole('Items available while syncing, proceeding with test');
|
||||
return;
|
||||
}
|
||||
|
||||
tester.printToConsole('Sync status: ${dashboardViewModel.status.runtimeType}');
|
||||
}
|
||||
|
||||
tester.printToConsole('Warning: Wallet did not sync within expected time, proceeding anyway');
|
||||
}
|
||||
|
||||
Future<void> _performComprehensiveItemCheck(DashboardViewModel dashboardViewModel) async {
|
||||
try {
|
||||
await _waitForItemsToLoad(dashboardViewModel);
|
||||
|
||||
final itemsToProcess = dashboardViewModel.items.where((item) {
|
||||
if (item is DateSectionItem) return false;
|
||||
if (item is TransactionListItem) {
|
||||
return !(item.hasTokens && item.assetOfTransaction == null);
|
||||
}
|
||||
return true;
|
||||
}).toList();
|
||||
|
||||
if (itemsToProcess.isEmpty) {
|
||||
tester.printToConsole('No transaction items to process - checking for placeholder');
|
||||
_verifyPlaceholder();
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a temporary limit to prevent the test from taking too long
|
||||
final maxItemsToProcess = 100;
|
||||
final itemsToCheck = itemsToProcess.take(maxItemsToProcess).toList();
|
||||
|
||||
tester.printToConsole(
|
||||
'Processing ${itemsToCheck.length} items out of ${itemsToProcess.length} total items',
|
||||
);
|
||||
|
||||
await _processVisibleItems(itemsToCheck, dashboardViewModel);
|
||||
|
||||
// Try to scroll and process more items if needed
|
||||
await _processItemsWithScrolling(itemsToCheck, dashboardViewModel);
|
||||
} catch (e) {
|
||||
tester.printToConsole('Error in comprehensive item check: $e');
|
||||
try {
|
||||
_verifyPlaceholder();
|
||||
} catch (placeholderError) {
|
||||
tester.printToConsole('Could not verify placeholder either: $placeholderError');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _waitForItemsToLoad(DashboardViewModel dashboardViewModel) async {
|
||||
const maxWaitTime = Duration(seconds: 30);
|
||||
final endTime = DateTime.now().add(maxWaitTime);
|
||||
|
||||
while (DateTime.now().isBefore(endTime)) {
|
||||
if (dashboardViewModel.items.isNotEmpty) {
|
||||
tester.printToConsole('Items loaded: ${dashboardViewModel.items.length}');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if placeholder is shown
|
||||
if (tester.any(find.byKey(ValueKey('transactions_page_placeholder_transactions_text_key')))) {
|
||||
tester.printToConsole('Placeholder is shown - no items to load');
|
||||
return;
|
||||
}
|
||||
|
||||
await tester.pump(Duration(seconds: 1));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
tester.printToConsole('Warning: No items loaded and no placeholder shown within expected time');
|
||||
}
|
||||
|
||||
Future<void> _processVisibleItems(
|
||||
List<dynamic> items,
|
||||
DashboardViewModel dashboardViewModel,
|
||||
) async {
|
||||
try {
|
||||
int processedCount = 0;
|
||||
|
||||
for (var item in items) {
|
||||
try {
|
||||
final keyId = (item.key as ValueKey<String>).value;
|
||||
|
||||
// Check if item is already visible
|
||||
if (tester.any(find.byKey(ValueKey(keyId)))) {
|
||||
tester.printToConsole('Processing visible item: $keyId');
|
||||
await _verifyItemDisplay(item, dashboardViewModel);
|
||||
processedCount++;
|
||||
}
|
||||
} catch (itemError) {
|
||||
tester.printToConsole('Error processing item: $itemError');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
tester.printToConsole('Processed $processedCount visible items\n');
|
||||
} catch (e) {
|
||||
tester.printToConsole('Error in visible items processing: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _processItemsWithScrolling(
|
||||
List<dynamic> items,
|
||||
DashboardViewModel dashboardViewModel,
|
||||
) async {
|
||||
try {
|
||||
final scrollableFinder = find.descendant(
|
||||
of: find.byKey(ValueKey('transactions_page_list_view_builder_key')),
|
||||
matching: find.byType(Scrollable),
|
||||
);
|
||||
|
||||
if (!tester.any(scrollableFinder)) {
|
||||
tester.printToConsole('No scrollable found, skipping scroll processing');
|
||||
return;
|
||||
}
|
||||
|
||||
int processedCount = 0;
|
||||
const maxScrollAttempts = 10;
|
||||
|
||||
for (var item in items) {
|
||||
try {
|
||||
final keyId = (item.key as ValueKey<String>).value;
|
||||
|
||||
// Skip if already processed
|
||||
if (tester.any(find.byKey(ValueKey(keyId)))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to scroll to the item
|
||||
bool found = false;
|
||||
for (int attempt = 0; attempt < maxScrollAttempts; attempt++) {
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 200));
|
||||
|
||||
if (tester.any(find.byKey(ValueKey(keyId)))) {
|
||||
tester.printToConsole('Found item after scrolling: $keyId');
|
||||
await _verifyItemDisplay(item, dashboardViewModel);
|
||||
processedCount++;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform scroll
|
||||
try {
|
||||
await tester.drag(scrollableFinder, Offset(0, -100));
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 300));
|
||||
} catch (scrollError) {
|
||||
tester.printToConsole('Scroll failed: $scrollError');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
tester.printToConsole('Could not find item after scrolling: $keyId');
|
||||
}
|
||||
} catch (itemError) {
|
||||
tester.printToConsole('Error processing item with scrolling: $itemError');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
tester.printToConsole('Processed $processedCount items with scrolling');
|
||||
} catch (e) {
|
||||
tester.printToConsole('Error in scroll processing: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _verifyPlaceholder() {
|
||||
commonTestCases.hasValueKey('transactions_page_placeholder_transactions_text_key');
|
||||
commonTestCases.hasText(S.current.placeholder_transactions);
|
||||
}
|
||||
|
||||
Future<void> _waitForListToBeReady() async {
|
||||
const maxWaitAttempts = 10;
|
||||
int attempts = 0;
|
||||
|
||||
while (attempts < maxWaitAttempts) {
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 500));
|
||||
|
||||
// Check if the list view is present
|
||||
final listViewFinder = find.byKey(ValueKey('transactions_page_list_view_builder_key'));
|
||||
if (tester.any(listViewFinder)) {
|
||||
tester.printToConsole('List view is ready');
|
||||
return;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
tester.printToConsole('Waiting for list view to be ready, attempt $attempts\n');
|
||||
}
|
||||
|
||||
tester.printToConsole('List view did not become ready within expected time');
|
||||
}
|
||||
|
||||
Future<void> _verifyItemDisplay(dynamic item, DashboardViewModel dashboardViewModel) async {
|
||||
// Execute the proper check depending on item type.
|
||||
switch (item.runtimeType) {
|
||||
case TransactionListItem:
|
||||
final transactionItem = item as TransactionListItem;
|
||||
tester.printToConsole(transactionItem.formattedTitle);
|
||||
tester.printToConsole(transactionItem.formattedFiatAmount);
|
||||
tester.printToConsole('\n');
|
||||
await _verifyTransactionListItemDisplay(transactionItem, dashboardViewModel);
|
||||
break;
|
||||
|
||||
case AnonpayTransactionListItem:
|
||||
await _verifyAnonpayTransactionListItemDisplay(item as AnonpayTransactionListItem);
|
||||
break;
|
||||
|
||||
case TradeListItem:
|
||||
await _verifyTradeListItemDisplay(item as TradeListItem);
|
||||
break;
|
||||
|
||||
case OrderListItem:
|
||||
await _verifyOrderListItemDisplay(item as OrderListItem);
|
||||
break;
|
||||
|
||||
default:
|
||||
tester.printToConsole('Unhandled item type: ${item.runtimeType}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _verifyTransactionListItemDisplay(
|
||||
TransactionListItem item,
|
||||
DashboardViewModel dashboardViewModel,
|
||||
) async {
|
||||
final keyId =
|
||||
'${dashboardViewModel.type.name}_transaction_history_item_${item.transaction.id}_key';
|
||||
|
||||
if (!tester.any(find.byKey(ValueKey(keyId)))) {
|
||||
tester.printToConsole(
|
||||
'Could not find transaction item with key: $keyId for transaction ${item.transaction.id}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.hasTokens && item.assetOfTransaction == null) return;
|
||||
|
||||
try {
|
||||
//* ==============Confirm it has the right key for this item ========
|
||||
commonTestCases.hasValueKey(keyId);
|
||||
|
||||
//* ======Confirm it displays the properly formatted amount==========
|
||||
commonTestCases.findWidgetViaDescendant(
|
||||
of: find.byKey(ValueKey(keyId)),
|
||||
matching: find.text(item.formattedCryptoAmount),
|
||||
);
|
||||
|
||||
//* ======Confirm it displays the properly formatted date============
|
||||
final formattedDate = DateFormat('HH:mm').format(item.transaction.date);
|
||||
commonTestCases.findWidgetViaDescendant(
|
||||
of: find.byKey(ValueKey(keyId)),
|
||||
matching: find.text(formattedDate),
|
||||
);
|
||||
|
||||
//* ======Confirm it displays the properly formatted fiat amount=====
|
||||
final formattedFiatAmount =
|
||||
dashboardViewModel.balanceViewModel.isFiatDisabled ? '' : item.formattedFiatAmount;
|
||||
if (formattedFiatAmount.isNotEmpty) {
|
||||
commonTestCases.findWidgetViaDescendant(
|
||||
of: find.byKey(ValueKey(keyId)),
|
||||
matching: find.text(formattedFiatAmount),
|
||||
);
|
||||
}
|
||||
|
||||
//* ======Confirm it displays the right image based on the transaction direction=====
|
||||
final imageToUse = item.transaction.direction == TransactionDirection.incoming
|
||||
? 'assets/images/down_arrow.png'
|
||||
: 'assets/images/up_arrow.png';
|
||||
|
||||
find.widgetWithImage(Container, AssetImage(imageToUse));
|
||||
} catch (e) {
|
||||
tester.printToConsole('Error verifying transaction item ${item.transaction.id}: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _verifyAnonpayTransactionListItemDisplay(AnonpayTransactionListItem item) async {
|
||||
final keyId = 'anonpay_invoice_transaction_list_item_${item.transaction.invoiceId}_key';
|
||||
|
||||
//* ==============Confirm it has the right key for this item ========
|
||||
commonTestCases.hasValueKey(keyId);
|
||||
|
||||
//* ==============Confirm it displays the correct provider =========================
|
||||
commonTestCases.hasText(item.transaction.provider);
|
||||
|
||||
//* ===========Confirm it displays the properly formatted amount with currency ========
|
||||
final currency = item.transaction.fiatAmount != null
|
||||
? item.transaction.fiatEquiv ?? ''
|
||||
: CryptoCurrency.fromFullName(item.transaction.coinTo).name.toUpperCase();
|
||||
|
||||
final amount =
|
||||
item.transaction.fiatAmount?.toString() ?? (item.transaction.amountTo?.toString() ?? '');
|
||||
|
||||
final amountCurrencyText = amount + ' ' + currency;
|
||||
|
||||
commonTestCases.hasText(amountCurrencyText);
|
||||
|
||||
//* ======Confirm it displays the properly formatted date=================
|
||||
final formattedDate = DateFormat('HH:mm').format(item.transaction.createdAt);
|
||||
commonTestCases.hasText(formattedDate);
|
||||
|
||||
//* ===============Confirm it displays the right image====================
|
||||
find.widgetWithImage(ClipRRect, AssetImage('assets/images/trocador.png'));
|
||||
}
|
||||
|
||||
Future<void> _verifyTradeListItemDisplay(TradeListItem item) async {
|
||||
final keyId = 'trade_list_item_${item.trade.id}_key';
|
||||
final from = item.trade.from?.toString() ?? '';
|
||||
final to = item.trade.to?.toString() ?? '';
|
||||
|
||||
//* ==============Confirm it has the right key for this item ========
|
||||
commonTestCases.hasValueKey(keyId);
|
||||
|
||||
//* ==============Confirm it displays the correct provider =========================
|
||||
final conversionFlow = '$from → $to';
|
||||
|
||||
commonTestCases.hasText(conversionFlow);
|
||||
|
||||
//* ===========Confirm it displays the properly formatted amount with its crypto tag ========
|
||||
|
||||
final amountCryptoText = item.tradeFormattedAmount + ' ' + from;
|
||||
|
||||
commonTestCases.hasText(amountCryptoText);
|
||||
|
||||
//* ======Confirm it displays the properly formatted date=================
|
||||
final createdAtFormattedDate =
|
||||
item.trade.createdAt != null ? DateFormat('HH:mm').format(item.trade.createdAt!) : null;
|
||||
|
||||
if (createdAtFormattedDate != null) {
|
||||
commonTestCases.hasText(createdAtFormattedDate);
|
||||
}
|
||||
|
||||
//* ===============Confirm it displays the right image====================
|
||||
commonTestCases.hasValueKey(item.trade.provider.image);
|
||||
}
|
||||
|
||||
Future<void> _verifyOrderListItemDisplay(OrderListItem item) async {
|
||||
final keyId = 'order_list_item_${item.order.id}_key';
|
||||
|
||||
//* ==============Confirm it has the right key for this item ========
|
||||
commonTestCases.hasValueKey(keyId);
|
||||
|
||||
//* ==============Confirm it displays the correct provider =========================
|
||||
final orderFlow = '${item.order.from!} → ${item.order.to}';
|
||||
|
||||
commonTestCases.hasText(orderFlow);
|
||||
|
||||
//* ===========Confirm it displays the properly formatted amount with its crypto tag ========
|
||||
|
||||
final amountCryptoText = item.orderFormattedAmount + ' ' + item.order.to!;
|
||||
|
||||
commonTestCases.hasText(amountCryptoText);
|
||||
|
||||
//* ======Confirm it displays the properly formatted date=================
|
||||
final createdAtFormattedDate = DateFormat('HH:mm').format(item.order.createdAt);
|
||||
|
||||
commonTestCases.hasText(createdAtFormattedDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import "package:hash_wallet/src/screens/wallet/wallet_edit_page.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WalletEditPageRobot extends BaseRobot {
|
||||
WalletEditPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WalletEditPage>();
|
||||
}
|
||||
|
||||
Future<void> enterName(String name) async {
|
||||
final input = find.byKey(const ValueKey("wallet_edit_page_name_input_key"));
|
||||
|
||||
await pumpUntilFound(input);
|
||||
|
||||
final field = find.descendant(of: input, matching: find.byType(EditableText));
|
||||
|
||||
await tester.enterText(field.first, name);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
Future<void> save() async {
|
||||
await tapByKey("wallet_edit_page_save_button_key");
|
||||
}
|
||||
|
||||
Future<void> tapDelete() async {
|
||||
await tapByKey("wallet_edit_page_delete_button_key");
|
||||
}
|
||||
|
||||
Future<void> confirmDelete() async {
|
||||
await tapByKey("wallet_edit_page_confirm_delete_button_key");
|
||||
}
|
||||
|
||||
Future<void> expectNameTakenRefusal() async {
|
||||
await pumpUntilFound(find.byKey(const ValueKey("wallet_edit_page_name_taken_ok_button_key")));
|
||||
|
||||
hasType<WalletEditPage>();
|
||||
}
|
||||
|
||||
Future<void> dismissNameTakenRefusal() async {
|
||||
await tapByKey("wallet_edit_page_name_taken_ok_button_key");
|
||||
|
||||
await settle();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,25 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/new_wallet/wallet_group_description_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/new_wallet/wallet_group_description_page.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WalletGroupDescriptionPageRobot {
|
||||
WalletGroupDescriptionPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class WalletGroupDescriptionPageRobot extends BaseRobot {
|
||||
WalletGroupDescriptionPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isWalletGroupDescriptionPage() async {
|
||||
await commonTestCases.isSpecificPage<WalletGroupDescriptionPage>();
|
||||
await commonTestCases.takeScreenshots('wallet_group_description_page');
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WalletGroupDescriptionPage>();
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
commonTestCases.hasText(S.current.wallet_group);
|
||||
}
|
||||
|
||||
bool hasNewSingleSeedButton() {
|
||||
return commonTestCases.isKeyPresent('wallet_group_description_page_create_new_seed_button_key');
|
||||
}
|
||||
bool hasNewSingleSeedButton() =>
|
||||
isKeyPresent("wallet_group_description_page_create_new_seed_button_key");
|
||||
|
||||
Future<void> navigateToCreateNewSeedPage() async {
|
||||
if (hasNewSingleSeedButton()) {
|
||||
await commonTestCases.tapItemByKey(
|
||||
'wallet_group_description_page_create_new_seed_button_key',
|
||||
);
|
||||
await tapByKey("wallet_group_description_page_create_new_seed_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> navigateToChooseWalletGroup() async {
|
||||
await commonTestCases.tapItemByKey(
|
||||
'wallet_group_description_page_choose_wallet_group_button_key',
|
||||
);
|
||||
await tapByKey("wallet_group_description_page_choose_wallet_group_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import "package:hash_wallet/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WalletGroupExistingSeedPageRobot extends BaseRobot {
|
||||
WalletGroupExistingSeedPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WalletGroupExistingSeedDescriptionPage>();
|
||||
}
|
||||
|
||||
Future<void> openWallet() async {
|
||||
await tapByKey("wallet_group_existing_seed_description_page_open_wallet_button_key");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import "package:hash_wallet/src/screens/new_wallet/wallet_group_display_page.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WalletGroupsDisplayPageRobot extends BaseRobot {
|
||||
WalletGroupsDisplayPageRobot(super.tester);
|
||||
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WalletGroupsDisplayPage>();
|
||||
}
|
||||
|
||||
Future<void> selectWallet(String name) async {
|
||||
await tapWhenVisible(find.text(name));
|
||||
}
|
||||
|
||||
Future<void> continueWithSelectedSeed() async {
|
||||
await tapByKey("wallet_group_display_page_next_button_key");
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,38 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:hash_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||
import 'package:hash_wallet/store/app_store.dart';
|
||||
import 'package:cw_core/monero_wallet_keys.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
import "package:hash_wallet/reactions/wallet_connect.dart";
|
||||
import "package:hash_wallet/src/screens/wallet_keys/wallet_keys_page.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/monero_wallet_keys.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:cw_monero/monero_wallet.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:polyseed/polyseed.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WalletKeysAndSeedPageRobot {
|
||||
WalletKeysAndSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class WalletKeysAndSeedPageRobot extends BaseRobot {
|
||||
WalletKeysAndSeedPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isWalletKeysAndSeedPage() async {
|
||||
await commonTestCases.isSpecificPage<WalletKeysPage>();
|
||||
await commonTestCases.takeScreenshots('wallet_keys_page');
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WalletKeysPage>();
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
final walletKeysPage = tester.widget<WalletKeysPage>(find.byType(WalletKeysPage));
|
||||
final walletKeysViewModel = walletKeysPage.walletKeysViewModel;
|
||||
commonTestCases.hasText(walletKeysViewModel.title);
|
||||
}
|
||||
|
||||
void hasShareWarning() {
|
||||
commonTestCases.hasText(S.current.do_not_share_warning_text.toUpperCase());
|
||||
}
|
||||
int _verifiedCredentials = 0;
|
||||
|
||||
Future<void> confirmWalletCredentials(WalletType walletType) async {
|
||||
_verifiedCredentials = 0;
|
||||
|
||||
final walletKeysPage = tester.widget<WalletKeysPage>(find.byType(WalletKeysPage));
|
||||
final walletKeysViewModel = walletKeysPage.walletKeysViewModel;
|
||||
|
||||
final appStore = walletKeysViewModel.appStore;
|
||||
final walletName = walletType.name;
|
||||
bool hasSeed = appStore.wallet!.seed != null;
|
||||
bool hasHexSeed = appStore.wallet!.hexSeed != null;
|
||||
bool hasPrivateKey = appStore.wallet!.privateKey != null;
|
||||
final hasSeed = appStore.wallet!.seed != null;
|
||||
final hasHexSeed = appStore.wallet!.hexSeed != null;
|
||||
final hasPrivateKey = appStore.wallet!.privateKey != null;
|
||||
|
||||
if (walletType == WalletType.monero) {
|
||||
final moneroWallet = appStore.wallet as MoneroWalletBase;
|
||||
final moneroWallet = appStore.wallet! as MoneroWalletBase;
|
||||
final lang = PolyseedLang.getByPhrase(moneroWallet.seed);
|
||||
final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish);
|
||||
|
||||
@@ -57,51 +47,89 @@ class WalletKeysAndSeedPageRobot {
|
||||
if (walletType == WalletType.bitcoin ||
|
||||
walletType == WalletType.litecoin ||
|
||||
walletType == WalletType.bitcoinCash) {
|
||||
final seedWords = appStore.wallet!.seed!.split(" ");
|
||||
for (var seedWord in seedWords) {
|
||||
commonTestCases.hasTextAtLestOnce(seedWord);
|
||||
}
|
||||
tester.printToConsole('$walletName wallet has seeds properly displayed');
|
||||
_showsSeedWords(appStore.wallet!.seed!);
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has seeds properly displayed");
|
||||
}
|
||||
|
||||
// Hash Bags does not ship Solana or Tron; those WalletType members were
|
||||
// removed with the chains, so only the EVM chains remain here.
|
||||
if (isEVMCompatibleChain(walletType)) {
|
||||
if (hasSeed) {
|
||||
final seedWords = appStore.wallet!.seed!.split(" ");
|
||||
for (var seedWord in seedWords) {
|
||||
commonTestCases.hasTextAtLestOnce(seedWord);
|
||||
}
|
||||
tester.printToConsole('$walletName wallet has seeds properly displayed');
|
||||
_showsSeedWords(appStore.wallet!.seed!);
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has seeds properly displayed");
|
||||
}
|
||||
if (hasPrivateKey) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
|
||||
commonTestCases.hasText(appStore.wallet!.privateKey!);
|
||||
tester.printToConsole('$walletName wallet has private key properly displayed');
|
||||
await _openKeysTab();
|
||||
_showsCredentialOnce(appStore.wallet!.privateKey!, "private key");
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has private key properly displayed");
|
||||
}
|
||||
}
|
||||
|
||||
if (walletType == WalletType.nano || walletType == WalletType.banano) {
|
||||
if (hasSeed) {
|
||||
final seedWords = appStore.wallet!.seed!.split(" ");
|
||||
for (var seedWord in seedWords) {
|
||||
commonTestCases.hasTextAtLestOnce(seedWord);
|
||||
}
|
||||
tester.printToConsole('$walletName wallet has seeds properly displayed');
|
||||
_showsSeedWords(appStore.wallet!.seed!);
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has seeds properly displayed");
|
||||
}
|
||||
if (hasHexSeed) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
|
||||
commonTestCases.hasText(appStore.wallet!.hexSeed!);
|
||||
tester.printToConsole('$walletName wallet has hexSeed properly displayed');
|
||||
await _openKeysTab();
|
||||
_showsCredentialOnce(appStore.wallet!.hexSeed!, "hex seed");
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has hexSeed properly displayed");
|
||||
}
|
||||
if (hasPrivateKey) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
|
||||
commonTestCases.hasText(appStore.wallet!.privateKey!);
|
||||
tester.printToConsole('$walletName wallet has private key properly displayed');
|
||||
await _openKeysTab();
|
||||
_showsCredentialOnce(appStore.wallet!.privateKey!, "private key");
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has private key properly displayed");
|
||||
}
|
||||
}
|
||||
|
||||
await commonTestCases.defaultSleepTime(seconds: 5);
|
||||
_expectSomethingWasVerified(walletType);
|
||||
}
|
||||
|
||||
void _expectSomethingWasVerified(WalletType walletType) {
|
||||
expect(
|
||||
_verifiedCredentials,
|
||||
greaterThan(0),
|
||||
reason: "Nothing was verified for ${walletType.name}, this suite covers no credential "
|
||||
"for that type so it cannot tell a working keys page from a broken one",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openKeysTab() async {
|
||||
await tapByKey("wallet_keys_page_keys");
|
||||
|
||||
await settle();
|
||||
}
|
||||
|
||||
void _showsCredential(String value, String label) {
|
||||
expect(
|
||||
tester.any(find.text(value, skipOffstage: false)),
|
||||
true,
|
||||
reason: "The keys page did not show the wallet's $label",
|
||||
);
|
||||
}
|
||||
|
||||
void _showsCredentialOnce(String value, String label) {
|
||||
final rows = tester.widgetList(find.text(value, skipOffstage: false)).length;
|
||||
|
||||
expect(
|
||||
rows,
|
||||
1,
|
||||
reason: "The keys page showed $rows rows for the wallet's $label, expected one",
|
||||
);
|
||||
}
|
||||
|
||||
void _showsSeedWords(String seed) {
|
||||
final words = seed.split(" ");
|
||||
|
||||
for (var index = 0; index < words.length; index++) {
|
||||
_showsCredential(words[index], "seed word ${index + 1}");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _confirmMoneroWalletCredentials(
|
||||
@@ -111,68 +139,42 @@ class WalletKeysAndSeedPageRobot {
|
||||
String legacySeed,
|
||||
) async {
|
||||
final keys = appStore.wallet!.keys as MoneroWalletKeys;
|
||||
|
||||
final hasPublicSpendKey = commonTestCases.isKeyPresent(
|
||||
'${walletName}_wallet_public_spend_key_item_key',
|
||||
);
|
||||
final hasPrivateSpendKey = commonTestCases.isKeyPresent(
|
||||
'${walletName}_wallet_private_spend_key_item_key',
|
||||
);
|
||||
final hasPublicViewKey = commonTestCases.isKeyPresent(
|
||||
'${walletName}_wallet_public_view_key_item_key',
|
||||
);
|
||||
final hasPrivateViewKey = commonTestCases.isKeyPresent(
|
||||
'${walletName}_wallet_private_view_key_item_key',
|
||||
);
|
||||
final hasSeeds = seed.isNotEmpty;
|
||||
final hasSeedLegacy = Polyseed.isValidSeed(seed);
|
||||
|
||||
if (hasPublicSpendKey) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
|
||||
commonTestCases.hasText(keys.publicSpendKey);
|
||||
tester.printToConsole('$walletName wallet has public spend key properly displayed');
|
||||
}
|
||||
if (hasPrivateSpendKey) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
|
||||
commonTestCases.hasText(keys.privateSpendKey);
|
||||
tester.printToConsole('$walletName wallet has private spend key properly displayed');
|
||||
}
|
||||
if (hasPublicViewKey) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
|
||||
commonTestCases.hasText(keys.publicViewKey);
|
||||
tester.printToConsole('$walletName wallet has public view key properly displayed');
|
||||
}
|
||||
if (hasPrivateViewKey) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
|
||||
commonTestCases.hasText(keys.privateViewKey);
|
||||
tester.printToConsole('$walletName wallet has private view key properly displayed');
|
||||
}
|
||||
await _openKeysTab();
|
||||
|
||||
_showsCredentialOnce(keys.publicSpendKey, "public spend key");
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has public spend key properly displayed");
|
||||
|
||||
_showsCredentialOnce(keys.privateSpendKey, "private spend key");
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has private spend key properly displayed");
|
||||
|
||||
_showsCredentialOnce(keys.publicViewKey, "public view key");
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has public view key properly displayed");
|
||||
|
||||
_showsCredentialOnce(keys.privateViewKey, "private view key");
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has private view key properly displayed");
|
||||
|
||||
if (hasSeeds) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_seed');
|
||||
final seedWords = seed.split(" ");
|
||||
for (var seedWord in seedWords) {
|
||||
commonTestCases.hasTextAtLestOnce(seedWord);
|
||||
}
|
||||
tester.printToConsole('$walletName wallet has seeds properly displayed');
|
||||
await tapByKey("wallet_keys_page_seed");
|
||||
await settle();
|
||||
|
||||
_showsSeedWords(seed);
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has seeds properly displayed");
|
||||
}
|
||||
if (hasSeedLegacy) {
|
||||
await commonTestCases.tapItemByKey('wallet_keys_page_seed_legacy');
|
||||
final seedWords = legacySeed.split(" ");
|
||||
for (var seedWord in seedWords) {
|
||||
commonTestCases.hasTextAtLestOnce(seedWord);
|
||||
}
|
||||
tester.printToConsole('$walletName wallet has legacy seeds properly displayed');
|
||||
await tapByKey("wallet_keys_page_seed_legacy");
|
||||
await settle();
|
||||
|
||||
_showsSeedWords(legacySeed);
|
||||
_verifiedCredentials++;
|
||||
tester.printToConsole("$walletName wallet has legacy seeds properly displayed");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> backToDashboard() async {
|
||||
tester.printToConsole('Going back to dashboard from credentials page');
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
|
||||
await commonTestCases.goBack();
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 1000));
|
||||
|
||||
await commonTestCases.goBack();
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 2000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/wallet_list/wallet_list_page.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WalletListPageRobot {
|
||||
WalletListPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class WalletListPageRobot extends BaseRobot {
|
||||
WalletListPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
final shown = await pumpUntil(_isShowing);
|
||||
|
||||
Future<void> isWalletListPage() async {
|
||||
await commonTestCases.isSpecificPage<WalletListPageRobot>();
|
||||
await commonTestCases.takeScreenshots('wallet_list_page');
|
||||
expect(shown, true, reason: "The wallets tab never came to the front");
|
||||
}
|
||||
|
||||
void displaysCorrectTitle() {
|
||||
commonTestCases.hasText(S.current.wallets);
|
||||
Future<void> openEditFor(String walletName) async {
|
||||
await tapByKey("wallet_list_edit_${walletName}_button_key");
|
||||
}
|
||||
|
||||
bool hasWallet(String walletName) => tester.any(
|
||||
find.descendant(
|
||||
of: find.byType(WalletListPage),
|
||||
matching: find.text(walletName),
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> navigateToCreateNewWalletPage() async {
|
||||
await commonTestCases.tapItemByKey('wallet_list_page_create_new_wallet_button_key');
|
||||
await tapByKey("wallet_list_page_create_new_wallet_button_key");
|
||||
}
|
||||
|
||||
Future<void> navigateToRestoreWalletOptionsPage() async {
|
||||
await commonTestCases.tapItemByKey('wallet_list_page_restore_wallet_button_key');
|
||||
await tapByKey("wallet_list_page_restore_wallet_button_key");
|
||||
}
|
||||
|
||||
bool _isShowing() {
|
||||
final stackFinder = find.byType(IndexedStack);
|
||||
|
||||
if (!tester.any(stackFinder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final stack = tester.widget<IndexedStack>(stackFinder.first);
|
||||
final index = stack.children.indexWhere((child) => child is WalletListPage);
|
||||
|
||||
return index >= 0 && stack.index == index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,19 @@
|
||||
import 'package:hash_wallet/generated/i18n.dart';
|
||||
import 'package:hash_wallet/src/screens/seed/wallet_seed_page.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/generated/i18n.dart";
|
||||
import "package:hash_wallet/src/screens/seed/wallet_seed_page.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WalletSeedPageRobot {
|
||||
WalletSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class WalletSeedPageRobot extends BaseRobot {
|
||||
WalletSeedPageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isWalletSeedPage() async {
|
||||
await commonTestCases.isSpecificPage<WalletSeedPage>();
|
||||
await commonTestCases.takeScreenshots('wallet_seed_page');
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WalletSeedPage>();
|
||||
}
|
||||
|
||||
Future<void> onVerifySeedButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_seed_page_verify_seed_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onSaveSeedButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_seed_page_save_seeds_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onConfirmButtonOnSeedAlertDialogPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_confirm_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onBackButtonOnSeedAlertDialogPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_back_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("wallet_seed_page_verify_seed_button_key");
|
||||
}
|
||||
|
||||
void confirmWalletDetailsDisplayCorrectly() {
|
||||
@@ -42,23 +23,18 @@ class WalletSeedPageRobot {
|
||||
|
||||
final walletName = walletSeedViewModel.name;
|
||||
final walletSeeds = walletSeedViewModel.seedSplit;
|
||||
commonTestCases.hasText(walletName);
|
||||
for (var seed in walletSeeds) {
|
||||
commonTestCases.hasTextAtLestOnce(seed);
|
||||
hasText(walletName);
|
||||
|
||||
for (var index = 0; index < walletSeeds.length; index++) {
|
||||
expect(
|
||||
tester.any(find.text(walletSeeds[index])),
|
||||
true,
|
||||
reason: "The seed page did not show seed word ${index + 1}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void confirmWalletSeedReminderDisplays() {
|
||||
commonTestCases.hasText(S.current.cake_seeds_save_disclaimer);
|
||||
}
|
||||
|
||||
Future<void> onSaveSeedsButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_seed_page_save_seeds_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> onCopySeedsButtonPressed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_seed_page_copy_seeds_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
hasText(S.current.cake_seeds_save_disclaimer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,28 @@
|
||||
import 'package:hash_wallet/src/screens/welcome/welcome_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import "package:hash_wallet/src/screens/welcome/welcome_page.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
import "../core/base_robot.dart";
|
||||
|
||||
class WelcomePageRobot {
|
||||
WelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
class WelcomePageRobot extends BaseRobot {
|
||||
WelcomePageRobot(super.tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isWelcomePage() async {
|
||||
await commonTestCases.isSpecificPage<WelcomePage>();
|
||||
await commonTestCases.takeScreenshots('welcome_page');
|
||||
}
|
||||
|
||||
void confirmActionButtonsDisplay() {
|
||||
final createNewWalletButton = find.byKey(ValueKey('welcome_page_create_new_wallet_button_key'));
|
||||
|
||||
final restoreWalletButton = find.byKey(ValueKey('welcome_page_restore_wallet_button_key'));
|
||||
|
||||
expect(createNewWalletButton, findsOneWidget);
|
||||
expect(restoreWalletButton, findsOneWidget);
|
||||
@override
|
||||
Future<void> isDisplayed() async {
|
||||
await isSpecificPage<WelcomePage>();
|
||||
}
|
||||
|
||||
Future<void> navigateToCreateNewWalletPage() async {
|
||||
await commonTestCases.tapItemByKey('welcome_page_create_new_wallet_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("welcome_page_create_new_wallet_button_key");
|
||||
}
|
||||
|
||||
bool hasNewSingleSeedButton() {
|
||||
return commonTestCases.isKeyPresent('wallet_group_description_page_create_new_seed_button_key');
|
||||
}
|
||||
bool hasNewSingleSeedButton() =>
|
||||
isKeyPresent("wallet_group_description_page_create_new_seed_button_key");
|
||||
|
||||
Future<void> tapNewSingleSeed() async {
|
||||
await commonTestCases.tapItemByKey('wallet_group_description_page_create_new_seed_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
await tapByKey("wallet_group_description_page_create_new_seed_button_key");
|
||||
}
|
||||
|
||||
Future<void> navigateToRestoreWalletPage() async {
|
||||
await commonTestCases.tapItemByKey('welcome_page_restore_wallet_button_key');
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
|
||||
Future<void> backAndVerify() async {
|
||||
await commonTestCases.goBack();
|
||||
await isWelcomePage();
|
||||
await tapByKey("welcome_page_restore_wallet_button_key");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_wallets.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/contact_robot.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("A saved contact keeps the address it was given", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final contactRobot = ContactRobot(tester);
|
||||
|
||||
const contactName = "Integration Test Contact";
|
||||
final contactAddress = TestWallets.receiveAddressFor(WalletType.solana);
|
||||
|
||||
await appLauncher.launchApp(testKey: "address_book_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await dashboardRobot.openContactsTab();
|
||||
await contactRobot.isDisplayed();
|
||||
|
||||
await contactRobot.openAddContact();
|
||||
|
||||
await contactRobot.enterName(contactName);
|
||||
await contactRobot.chooseCurrency("sol");
|
||||
await contactRobot.enterAddress(contactAddress);
|
||||
|
||||
await contactRobot.save();
|
||||
|
||||
await contactRobot.isDisplayed();
|
||||
|
||||
contactRobot.expectContactSaved(contactName, contactAddress);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import "package:hash_wallet/routes.dart";
|
||||
import "package:hash_wallet/src/screens/wallet_keys/wallet_keys_page.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_config.dart";
|
||||
import "../../flows/auth_flows.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_settings_page_robot.dart";
|
||||
import "../../robots/security_backup_page_robot.dart";
|
||||
import "../../robots/setup_pin_code_robot.dart";
|
||||
import "../../robots/show_keys_disclaimer_page_robot.dart";
|
||||
import "../../robots/wallet_keys_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Changing the pin replaces the one that unlocks the wallet", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final authFlows = AuthFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final settingsRobot = NewSettingsPageRobot(tester);
|
||||
final securityRobot = SecurityBackupPageRobot(tester);
|
||||
final setupPinRobot = SetupPinCodeRobot(tester);
|
||||
final walletKeysRobot = WalletKeysAndSeedPageRobot(tester);
|
||||
final disclaimerRobot = ShowKeysDisclaimerPageRobot(tester);
|
||||
|
||||
final newPin = [1, 2, 3, 4];
|
||||
|
||||
expect(newPin, isNot(TestConfig.pin), reason: "The new pin has to differ from the old one");
|
||||
|
||||
await appLauncher.launchApp(testKey: "change_pin_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSettingsSheet();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.securityBackupPage);
|
||||
await securityRobot.isDisplayed();
|
||||
|
||||
await securityRobot.openChangePin();
|
||||
await authFlows.authenticateWithPin();
|
||||
|
||||
await setupPinRobot.isDisplayed();
|
||||
await setupPinRobot.enterPinCode(newPin);
|
||||
await setupPinRobot.enterPinCode(newPin);
|
||||
await setupPinRobot.tapSuccessButton();
|
||||
|
||||
await securityRobot.isDisplayed();
|
||||
|
||||
await securityRobot.goBack();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.showKeysDisclaimer);
|
||||
await disclaimerRobot.isDisplayed();
|
||||
await disclaimerRobot.tapShowKeys();
|
||||
await authFlows.authenticateWithPin(pin: newPin);
|
||||
|
||||
await walletKeysRobot.isDisplayed();
|
||||
|
||||
expect(find.byType(WalletKeysPage), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_wallet_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("A wallet name already in use is refused", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final newWalletPageRobot = NewWalletPageRobot(tester);
|
||||
|
||||
const walletType = WalletType.solana;
|
||||
|
||||
await appLauncher.launchApp(testKey: "duplicate_wallet_name_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(walletType);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final existingName = getIt.get<AppStore>().wallet!.name;
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
|
||||
await onboardingFlows.startCreatingWalletFromWalletList(walletType);
|
||||
|
||||
await newWalletPageRobot.enterWalletName(existingName);
|
||||
await newWalletPageRobot.onNextButtonPressed();
|
||||
|
||||
await newWalletPageRobot.expectNameRejected();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/entities/fiat_api_mode.dart";
|
||||
import "package:hash_wallet/entities/fiat_currency.dart";
|
||||
import "package:hash_wallet/routes.dart";
|
||||
import "package:hash_wallet/store/settings_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/connection_sync_page_robot.dart";
|
||||
import "../../robots/display_settings_page_robot.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_settings_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Turning the fiat api off hides the currency setting, turning it on returns it",
|
||||
(tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final settingsRobot = NewSettingsPageRobot(tester);
|
||||
final connectionRobot = ConnectionSyncPageRobot(tester);
|
||||
final displayRobot = DisplaySettingsPageRobot(tester);
|
||||
|
||||
const wanted = FiatCurrency.eur;
|
||||
|
||||
await appLauncher.launchApp(testKey: "fiat_currency_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final settingsStore = getIt.get<SettingsStore>();
|
||||
|
||||
expect(
|
||||
settingsStore.fiatCurrency,
|
||||
isNot(wanted),
|
||||
reason: "The wallet already starts on $wanted, so changing to it would prove nothing",
|
||||
);
|
||||
|
||||
await homePageRobot.openSettingsSheet();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
if (settingsStore.fiatApiMode != FiatApiMode.disabled) {
|
||||
await settingsRobot.openRow(Routes.connectionSync);
|
||||
await connectionRobot.isDisplayed();
|
||||
|
||||
await connectionRobot.setFiatApiMode(FiatApiMode.disabled);
|
||||
connectionRobot.hasFiatApiMode(FiatApiMode.disabled);
|
||||
|
||||
expect(settingsStore.fiatApiMode, FiatApiMode.disabled);
|
||||
|
||||
await connectionRobot.goBack();
|
||||
await settingsRobot.isDisplayed();
|
||||
}
|
||||
|
||||
await settingsRobot.openRow(Routes.displaySettingsPage);
|
||||
await displayRobot.isDisplayed();
|
||||
|
||||
expect(
|
||||
displayRobot.hasFiatCurrencyRow(),
|
||||
false,
|
||||
reason: "The currency setting is still showing while the fiat api is off",
|
||||
);
|
||||
|
||||
await displayRobot.goBack();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.connectionSync);
|
||||
await connectionRobot.isDisplayed();
|
||||
|
||||
await connectionRobot.setFiatApiMode(FiatApiMode.enabled);
|
||||
connectionRobot.hasFiatApiMode(FiatApiMode.enabled);
|
||||
|
||||
expect(settingsStore.fiatApiMode, FiatApiMode.enabled);
|
||||
|
||||
await connectionRobot.goBack();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.displaySettingsPage);
|
||||
await displayRobot.isDisplayed();
|
||||
|
||||
expect(
|
||||
displayRobot.hasFiatCurrencyRow(),
|
||||
true,
|
||||
reason: "The currency setting did not come back after the fiat api was turned on",
|
||||
);
|
||||
|
||||
await displayRobot.openFiatCurrencyPicker();
|
||||
await displayRobot.chooseFiatCurrency(wanted);
|
||||
|
||||
displayRobot.expectFiatCurrencyShown(wanted);
|
||||
|
||||
expect(
|
||||
settingsStore.fiatCurrency,
|
||||
wanted,
|
||||
reason: "The row shows ${wanted.title} but the setting behind it did not change",
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_wallets.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/restore_from_seed_or_key_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("A seed the wallet cannot parse restores nothing", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final restoreRobot = RestoreFromSeedOrKeysPageRobot(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
const walletType = WalletType.solana;
|
||||
|
||||
await appLauncher.launchApp(testKey: "invalid_seed_test_app_key");
|
||||
|
||||
await onboardingFlows.startRestoringFirstWallet(walletType);
|
||||
|
||||
// Twelve words that are not in the wordlist. Restoring from this would either fail
|
||||
// outright or, worse it derives some other wallet the user has no keys for.
|
||||
await restoreRobot.enterSeedPhraseForWalletRestore(
|
||||
"alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima",
|
||||
);
|
||||
|
||||
await restoreRobot.onRestoreWalletButtonPressed();
|
||||
|
||||
await restoreRobot.expectRestoreRefused();
|
||||
|
||||
// The same form with the real seed goes through. Without this the test above would pass
|
||||
// just as happily if the form were broken and restored nothing for any seed at all.
|
||||
await restoreRobot.selectWalletNameFromAvailableOptions();
|
||||
await restoreRobot.enterSeedPhraseForWalletRestore(TestWallets.seedFor(walletType));
|
||||
await restoreRobot.onRestoreWalletButtonPressed();
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
expect(getIt.get<AppStore>().wallet?.type, walletType);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import "package:hash_wallet/routes.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/display_settings_page_robot.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_settings_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Changing the language changes what the settings screen shows", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final settingsRobot = NewSettingsPageRobot(tester);
|
||||
final displayRobot = DisplaySettingsPageRobot(tester);
|
||||
|
||||
// Not the default, so seeing it afterwards means the change took rather than the screen
|
||||
// having always said so.
|
||||
const languageCode = "de";
|
||||
const languageName = "Deutsch (German)";
|
||||
|
||||
await appLauncher.launchApp(testKey: "language_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSettingsSheet();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.displaySettingsPage);
|
||||
await displayRobot.isDisplayed();
|
||||
|
||||
await displayRobot.openLanguagePicker();
|
||||
await displayRobot.chooseLanguage(languageCode);
|
||||
|
||||
displayRobot.expectLanguageShown(languageName);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_config.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Create a wallet for each configured wallet type", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "onboarding_create_test_app_key");
|
||||
|
||||
final walletTypes = TestConfig.walletTypesUnderTest;
|
||||
final firstType = walletTypes.first;
|
||||
|
||||
tester.printToConsole("Creating first wallet: ${firstType.name}");
|
||||
|
||||
await onboardingFlows.createFirstWallet(firstType);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
expect(appStore.wallet?.type, firstType);
|
||||
|
||||
await homePageRobot.hasWalletName(appStore.wallet!.name);
|
||||
|
||||
for (final type in walletTypes.skip(1)) {
|
||||
tester.printToConsole("Creating additional wallet: ${type.name}");
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
|
||||
await onboardingFlows.createAdditionalWalletFromWalletList(type);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
expect(appStore.wallet?.type, type);
|
||||
|
||||
await homePageRobot.hasWalletName(appStore.wallet!.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_config.dart";
|
||||
import "../../core/test_wallets.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Restore a wallet from seed for each configured wallet type", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "onboarding_restore_test_app_key");
|
||||
|
||||
final walletTypes = TestConfig.walletTypesUnderTest;
|
||||
final firstType = walletTypes.first;
|
||||
|
||||
tester.printToConsole("Restoring first wallet: ${firstType.name}");
|
||||
|
||||
await onboardingFlows.restoreFirstWalletFromSeed(firstType);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
expect(appStore.wallet?.type, firstType);
|
||||
|
||||
await homePageRobot.hasWalletName(appStore.wallet!.name);
|
||||
|
||||
await _expectRestoredTheRightWallet(homePageRobot, appStore, firstType);
|
||||
|
||||
for (final type in walletTypes.skip(1)) {
|
||||
tester.printToConsole("Restoring additional wallet: ${type.name}");
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
|
||||
await onboardingFlows.restoreAdditionalWalletFromWalletList(type);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
expect(appStore.wallet?.type, type);
|
||||
|
||||
await homePageRobot.hasWalletName(appStore.wallet!.name);
|
||||
|
||||
await _expectRestoredTheRightWallet(homePageRobot, appStore, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _expectRestoredTheRightWallet(
|
||||
HomePageRobot robot,
|
||||
AppStore appStore,
|
||||
WalletType type,
|
||||
) async {
|
||||
final expectedAddress = TestWallets.receiveAddressFor(type);
|
||||
|
||||
if (expectedAddress.isEmpty) {
|
||||
robot.tester.printToConsole("No known address for ${type.name}, derivation not verified");
|
||||
return;
|
||||
}
|
||||
|
||||
final settled = await robot.pumpUntil(
|
||||
() {
|
||||
final addresses = appStore.wallet?.walletAddresses;
|
||||
|
||||
if (addresses == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return addresses.address == expectedAddress ||
|
||||
addresses.addressesMap.containsKey(expectedAddress) ||
|
||||
addresses.allAddressesMap.containsKey(expectedAddress);
|
||||
},
|
||||
timeout: const Duration(seconds: 60),
|
||||
);
|
||||
|
||||
expect(
|
||||
settled,
|
||||
true,
|
||||
reason: "${type.name} restored from a known seed derived "
|
||||
"${appStore.wallet?.walletAddresses.address} and does not know $expectedAddress",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_receive_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Receive sheet shows the opened wallet's own address", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final receiveRobot = NewReceivePageRobot(tester);
|
||||
|
||||
const walletType = WalletType.solana;
|
||||
|
||||
await appLauncher.launchApp(testKey: "receive_address_test_app_key");
|
||||
|
||||
await onboardingFlows.restoreFirstWalletFromSeed(walletType);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final expectedAddress = getIt.get<AppStore>().wallet!.walletAddresses.address;
|
||||
expect(expectedAddress.isNotEmpty, true);
|
||||
|
||||
await homePageRobot.openReceiveSheet();
|
||||
|
||||
await receiveRobot.isDisplayed();
|
||||
await receiveRobot.confirmAddressMatches(expectedAddress);
|
||||
|
||||
await receiveRobot.dismissModal();
|
||||
await homePageRobot.isDisplayed();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/routes.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/auth_flows.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_settings_page_robot.dart";
|
||||
import "../../robots/show_keys_disclaimer_page_robot.dart";
|
||||
import "../../robots/wallet_keys_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Displayed seed and keys match the opened wallet", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final authFlows = AuthFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final settingsRobot = NewSettingsPageRobot(tester);
|
||||
final walletKeysRobot = WalletKeysAndSeedPageRobot(tester);
|
||||
final disclaimerRobot = ShowKeysDisclaimerPageRobot(tester);
|
||||
|
||||
// These three cover the different key display paths on the wallet keys page.
|
||||
final walletTypes = [WalletType.solana, WalletType.bitcoin, WalletType.monero];
|
||||
|
||||
await appLauncher.launchApp(testKey: "seed_confirmation_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(walletTypes.first);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
||||
for (final type in walletTypes) {
|
||||
if (type != walletTypes.first) {
|
||||
await dashboardRobot.openWalletsTab();
|
||||
await onboardingFlows.createAdditionalWalletFromWalletList(type);
|
||||
await homePageRobot.isDisplayed();
|
||||
}
|
||||
|
||||
expect(appStore.wallet?.type, type);
|
||||
|
||||
tester.printToConsole("Confirming displayed credentials for ${type.name}");
|
||||
|
||||
await homePageRobot.openSettingsSheet();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.showKeysDisclaimer);
|
||||
await disclaimerRobot.isDisplayed();
|
||||
await disclaimerRobot.tapShowKeys();
|
||||
await authFlows.authenticateWithPin();
|
||||
|
||||
await walletKeysRobot.isDisplayed();
|
||||
await walletKeysRobot.confirmWalletCredentials(type);
|
||||
|
||||
await settingsRobot.goBack();
|
||||
await settingsRobot.dismissModal();
|
||||
|
||||
await homePageRobot.isDisplayed();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_wallets.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/contact_robot.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_send_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("A contact picked on the send screen fills in its own address", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final contactRobot = ContactRobot(tester);
|
||||
final sendRobot = NewSendPageRobot(tester);
|
||||
|
||||
const contactName = "Send Book Contact";
|
||||
final contactAddress = TestWallets.receiveAddressFor(WalletType.solana);
|
||||
|
||||
await appLauncher.launchApp(testKey: "send_from_address_book_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await dashboardRobot.openContactsTab();
|
||||
await contactRobot.isDisplayed();
|
||||
|
||||
await contactRobot.openAddContact();
|
||||
await contactRobot.enterName(contactName);
|
||||
await contactRobot.chooseCurrency("sol");
|
||||
await contactRobot.enterAddress(contactAddress);
|
||||
await contactRobot.save();
|
||||
|
||||
await contactRobot.isDisplayed();
|
||||
|
||||
await dashboardRobot.openHomeTab();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSendSheet();
|
||||
await sendRobot.isDisplayed();
|
||||
|
||||
await sendRobot.pickContactFromAddressBook(contactName);
|
||||
|
||||
expect(
|
||||
sendRobot.enteredAddress(),
|
||||
contactAddress,
|
||||
reason: "The send screen was filled with an address the contact does not hold",
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_wallets.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_send_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Send screen refuses what it cannot send", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final sendRobot = NewSendPageRobot(tester);
|
||||
|
||||
const walletType = WalletType.solana;
|
||||
|
||||
await appLauncher.launchApp(testKey: "send_validation_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(walletType);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSendSheet();
|
||||
await sendRobot.isDisplayed();
|
||||
|
||||
await sendRobot.tapSendButtonWhenReady(timeout: const Duration(minutes: 5));
|
||||
await sendRobot.expectNoTransactionBuilt();
|
||||
|
||||
// test that no transaction is built when the address is wrong or cannot be parsed
|
||||
await sendRobot.enterAddress("not-a-real-address");
|
||||
await sendRobot.enterAmount("0.0001");
|
||||
await sendRobot.tapSendButton();
|
||||
|
||||
await sendRobot.expectNoTransactionBuilt();
|
||||
|
||||
// test that no transaction is built even if the address is valid but no balance
|
||||
await sendRobot.enterAddress(TestWallets.receiveAddressFor(walletType));
|
||||
await sendRobot.enterAmount("0.0001");
|
||||
await sendRobot.tapSendButton();
|
||||
|
||||
await sendRobot.expectNoTransactionBuilt();
|
||||
|
||||
await sendRobot.dismissModal();
|
||||
await homePageRobot.isDisplayed();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import "package:hash_wallet/new-ui/pages/about_page.dart";
|
||||
import "package:hash_wallet/routes.dart";
|
||||
import "package:hash_wallet/src/screens/settings/connection_sync_page.dart";
|
||||
import "package:hash_wallet/src/screens/settings/display_settings_page.dart";
|
||||
import "package:hash_wallet/src/screens/settings/manage_nodes_page.dart";
|
||||
import "package:hash_wallet/src/screens/settings/other_settings_page.dart";
|
||||
import "package:hash_wallet/src/screens/settings/privacy_page.dart";
|
||||
import "package:hash_wallet/src/screens/settings/security_backup_page.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_settings_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Every settings row opens its page and backs out", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final settingsRobot = NewSettingsPageRobot(tester);
|
||||
|
||||
final rowsUnderTest = <String, Type>{
|
||||
Routes.manageNodes: ManageNodesPage,
|
||||
Routes.privacyPage: PrivacyPage,
|
||||
Routes.otherSettingsPage: OtherSettingsPage,
|
||||
Routes.connectionSync: ConnectionSyncPage,
|
||||
Routes.displaySettingsPage: DisplaySettingsPage,
|
||||
Routes.securityBackupPage: SecurityBackupPage,
|
||||
Routes.aboutPage: AboutPage,
|
||||
};
|
||||
|
||||
await appLauncher.launchApp(testKey: "settings_nav_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSettingsSheet();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
for (final entry in rowsUnderTest.entries) {
|
||||
tester.printToConsole("Opening settings row ${entry.key}");
|
||||
|
||||
await settingsRobot.openRow(entry.key);
|
||||
await settingsRobot.confirmLeafPageDisplayed(entry.value);
|
||||
|
||||
await settingsRobot.goBack();
|
||||
await settingsRobot.isDisplayed();
|
||||
}
|
||||
|
||||
await settingsRobot.dismissModal();
|
||||
await homePageRobot.isDisplayed();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import "package:hash_wallet/routes.dart";
|
||||
import "package:hash_wallet/src/screens/wallet_keys/wallet_keys_page.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/auth_flows.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_settings_page_robot.dart";
|
||||
import "../../robots/show_keys_disclaimer_page_robot.dart";
|
||||
import "../../robots/wallet_keys_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Seed and keys page rejects a wrong pin and accepts the right one",
|
||||
(tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final authFlows = AuthFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final settingsRobot = NewSettingsPageRobot(tester);
|
||||
final walletKeysRobot = WalletKeysAndSeedPageRobot(tester);
|
||||
final disclaimerRobot = ShowKeysDisclaimerPageRobot(tester);
|
||||
|
||||
final wrongPin = [9, 9, 9, 9];
|
||||
|
||||
await appLauncher.launchApp(testKey: "show_keys_auth_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSettingsSheet();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.showKeysDisclaimer);
|
||||
await disclaimerRobot.isDisplayed();
|
||||
await disclaimerRobot.tapShowKeys();
|
||||
|
||||
await authFlows.authenticateWithPin(pin: wrongPin);
|
||||
|
||||
// A wrong pin must keep the keys page locked away.
|
||||
await settingsRobot.settle(max: const Duration(seconds: 3));
|
||||
expect(find.byType(WalletKeysPage), findsNothing);
|
||||
expect(authFlows.isPinWidgetShown(), true);
|
||||
|
||||
await authFlows.authenticateWithPin();
|
||||
|
||||
await walletKeysRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.goBack();
|
||||
await settingsRobot.dismissModal();
|
||||
await homePageRobot.isDisplayed();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/auth_flows.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/wallet_edit_page_robot.dart";
|
||||
import "../../robots/wallet_list_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Deleting a wallet takes it off the list and out of storage", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final authFlows = AuthFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final walletListRobot = WalletListPageRobot(tester);
|
||||
final walletEditRobot = WalletEditPageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "wallet_delete_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final deletedWallet = appStore.wallet!.name;
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
await onboardingFlows.createAdditionalWalletFromWalletList(WalletType.ethereum);
|
||||
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final keptWallet = appStore.wallet!.name;
|
||||
|
||||
expect(keptWallet, isNot(deletedWallet), reason: "The second wallet never became the open one");
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
await walletListRobot.isDisplayed();
|
||||
|
||||
await walletListRobot.openEditFor(deletedWallet);
|
||||
await walletEditRobot.isDisplayed();
|
||||
|
||||
await walletEditRobot.tapDelete();
|
||||
await authFlows.authenticateWithPin(required: false);
|
||||
|
||||
await walletEditRobot.confirmDelete();
|
||||
|
||||
await walletListRobot.isDisplayed();
|
||||
|
||||
final removed = await walletListRobot.pumpUntil(
|
||||
() => !walletListRobot.hasWallet(deletedWallet),
|
||||
timeout: const Duration(minutes: 1),
|
||||
);
|
||||
|
||||
expect(removed, true, reason: "$deletedWallet is still on the list after being deleted");
|
||||
|
||||
expect(
|
||||
walletListRobot.hasWallet(keptWallet),
|
||||
true,
|
||||
reason: "Deleting $deletedWallet took $keptWallet with it",
|
||||
);
|
||||
|
||||
final stored = await WalletInfo.getAll();
|
||||
final names = stored.map((wallet) => wallet.name).toList();
|
||||
|
||||
expect(names, isNot(contains(deletedWallet)));
|
||||
expect(names, contains(keptWallet));
|
||||
|
||||
expect(
|
||||
appStore.wallet?.name,
|
||||
keptWallet,
|
||||
reason: "Deleting another wallet changed which one is open",
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../flows/wallet_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("A wallet added to an existing seed shares it and both stay reachable",
|
||||
(tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final walletFlows = WalletFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
const firstType = WalletType.solana;
|
||||
const secondType = WalletType.ethereum;
|
||||
|
||||
await appLauncher.launchApp(testKey: "wallet_group_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(firstType);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final firstWalletName = appStore.wallet!.name;
|
||||
final sharedSeed = appStore.wallet!.seed;
|
||||
|
||||
expect(sharedSeed, isNotNull, reason: "The first wallet has no seed to share");
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
|
||||
await onboardingFlows.createWalletInGroupOf(secondType, firstWalletName);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final secondWalletName = appStore.wallet!.name;
|
||||
|
||||
expect(
|
||||
secondWalletName,
|
||||
isNot(firstWalletName),
|
||||
reason: "Adding to a group opened the wallet that was already there",
|
||||
);
|
||||
|
||||
expect(appStore.wallet?.type, secondType);
|
||||
|
||||
expect(
|
||||
appStore.wallet!.seed,
|
||||
sharedSeed,
|
||||
reason: "The wallet added to the group derived its own seed instead of sharing one",
|
||||
);
|
||||
|
||||
await walletFlows.switchToWallet(firstWalletName);
|
||||
|
||||
await homePageRobot.isDisplayed();
|
||||
await homePageRobot.hasWalletName(firstWalletName);
|
||||
|
||||
expect(appStore.wallet?.seed, sharedSeed);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/wallet_edit_page_robot.dart";
|
||||
import "../../robots/wallet_list_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("A wallet can be renamed, but not onto a name already in use", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final walletListRobot = WalletListPageRobot(tester);
|
||||
final walletEditRobot = WalletEditPageRobot(tester);
|
||||
|
||||
const renamedTo = "Renamed Test Wallet";
|
||||
|
||||
await appLauncher.launchApp(testKey: "wallet_rename_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
final renamedWallet = appStore.wallet!.name;
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
await onboardingFlows.createAdditionalWalletFromWalletList(WalletType.ethereum);
|
||||
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final openWallet = appStore.wallet!.name;
|
||||
|
||||
expect(openWallet, isNot(renamedWallet), reason: "The second wallet never became the open one");
|
||||
|
||||
await dashboardRobot.openWalletsTab();
|
||||
await walletListRobot.isDisplayed();
|
||||
|
||||
await walletListRobot.openEditFor(renamedWallet);
|
||||
await walletEditRobot.isDisplayed();
|
||||
|
||||
await walletEditRobot.enterName(openWallet);
|
||||
await walletEditRobot.save();
|
||||
|
||||
await walletEditRobot.expectNameTakenRefusal();
|
||||
await walletEditRobot.dismissNameTakenRefusal();
|
||||
|
||||
await walletEditRobot.enterName(renamedTo);
|
||||
await walletEditRobot.save();
|
||||
|
||||
await walletListRobot.isDisplayed();
|
||||
|
||||
final renamed = await walletListRobot.pumpUntil(() => walletListRobot.hasWallet(renamedTo));
|
||||
|
||||
expect(renamed, true, reason: "The list never showed $renamedTo after the rename was saved");
|
||||
|
||||
expect(
|
||||
walletListRobot.hasWallet(renamedWallet),
|
||||
false,
|
||||
reason: "The list still shows $renamedWallet next to the name it was renamed to",
|
||||
);
|
||||
|
||||
final stored = await WalletInfo.getAll();
|
||||
final names = stored.map((wallet) => wallet.name).toList();
|
||||
|
||||
expect(names, contains(renamedTo));
|
||||
expect(names, isNot(contains(renamedWallet)));
|
||||
|
||||
expect(
|
||||
appStore.wallet?.name,
|
||||
openWallet,
|
||||
reason: "Renaming another wallet changed which one is open",
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../core/test_wallets.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("A 25 word monero seed restores through the legacy path", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
final legacySeed = TestWallets.moneroLegacySeed;
|
||||
|
||||
expect(
|
||||
legacySeed.split(" ").length,
|
||||
25,
|
||||
reason: "The legacy seed secret is not a 25 word seed, this suite would restore a polyseed",
|
||||
);
|
||||
|
||||
await appLauncher.launchApp(testKey: "monero_legacy_seed_test_app_key");
|
||||
|
||||
await onboardingFlows.restoreFirstWalletFromSeed(WalletType.monero, seed: legacySeed);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
||||
expect(appStore.wallet?.type, WalletType.monero);
|
||||
|
||||
expect(
|
||||
appStore.wallet?.seed?.split(" ").length,
|
||||
25,
|
||||
reason: "The wallet came back with a seed of a different length than the one restored",
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import "package:hash_wallet/routes.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/manage_nodes_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_settings_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Switching nodes changes which one the wallet is pointed at", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final settingsRobot = NewSettingsPageRobot(tester);
|
||||
final nodesRobot = ManageNodesPageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "node_switching_test_app_key");
|
||||
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSettingsSheet();
|
||||
await settingsRobot.isDisplayed();
|
||||
|
||||
await settingsRobot.openRow(Routes.manageNodes);
|
||||
await nodesRobot.isDisplayed();
|
||||
|
||||
final initialNodeUri = nodesRobot.currentNodeUri();
|
||||
|
||||
expect(initialNodeUri, isNotEmpty, reason: "The wallet started with no node at all");
|
||||
|
||||
await nodesRobot.switchToAnotherNode();
|
||||
|
||||
expect(
|
||||
nodesRobot.currentNodeUri(),
|
||||
isNot(initialNodeUri),
|
||||
reason: "The node was confirmed but the wallet is still pointed at $initialNodeUri",
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
import "../../robots/new_swap_page_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Swap sheet fetches a provider quote, no trade is created", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
final swapRobot = NewSwapPageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "swap_quote_test_app_key");
|
||||
|
||||
await onboardingFlows.restoreFirstWalletFromSeed(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.openSwapSheet();
|
||||
await swapRobot.isDisplayed();
|
||||
|
||||
await swapRobot.enterDepositAmount("1");
|
||||
|
||||
await swapRobot.confirmQuoteReceived();
|
||||
|
||||
await swapRobot.dismissModal();
|
||||
await homePageRobot.isDisplayed();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Restored wallet connects to a node and starts syncing", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "sync_status_test_app_key");
|
||||
|
||||
await onboardingFlows.restoreFirstWalletFromSeed(WalletType.bitcoin);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.confirmSyncIndicatorShown();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Tapping a transaction opens the details of that transaction", (tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "transaction_details_test_app_key");
|
||||
|
||||
await onboardingFlows.restoreFirstWalletFromSeed(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
await homePageRobot.confirmTransactionHistoryVisible();
|
||||
|
||||
await homePageRobot.openAllTransactions();
|
||||
await homePageRobot.confirmAllTransactionsVisible();
|
||||
|
||||
final tapped = homePageRobot.firstTransactionIdInAllView();
|
||||
|
||||
expect(tapped, isNotEmpty, reason: "The tile carries no transaction id to check against");
|
||||
|
||||
await homePageRobot.openFirstTransactionDetails();
|
||||
|
||||
// A list that hands the wrong transaction to the details screen shows one person's
|
||||
// amounts under another's id, and nothing on the screen would say so.
|
||||
expect(
|
||||
homePageRobot.openedTransactionId(),
|
||||
tapped,
|
||||
reason: "The details opened for a different transaction than the one tapped",
|
||||
);
|
||||
|
||||
homePageRobot.hasTransactionIdRow();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import "package:hash_wallet/di.dart";
|
||||
import "package:hash_wallet/store/app_store.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("Transaction history renders for a wallet with known transactions",
|
||||
(tester) async {
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
await appLauncher.launchApp(testKey: "transaction_history_test_app_key");
|
||||
|
||||
await onboardingFlows.restoreFirstWalletFromSeed(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
expect(getIt.get<AppStore>().wallet?.type, WalletType.solana);
|
||||
|
||||
await homePageRobot.confirmTransactionHistoryVisible();
|
||||
|
||||
await homePageRobot.openAllTransactions();
|
||||
|
||||
await homePageRobot.confirmAllTransactionsVisible();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copy this file into integration_test/suites/tier0/ or tier1/, rename it to
|
||||
// <feature>_test.dart and replace the example steps with your journey.
|
||||
// The runner only picks up files ending in _test.dart, this template never runs.
|
||||
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:integration_test/integration_test.dart";
|
||||
|
||||
import "../../core/app_launcher.dart";
|
||||
import "../../flows/onboarding_flows.dart";
|
||||
import "../../robots/home_page_robot.dart";
|
||||
import "../../robots/new_dashboard_robot.dart";
|
||||
|
||||
void main() {
|
||||
// Required first line in every suite, integrationTest contains background app errors
|
||||
// that testWidgets would turn into failures.
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
integrationTest("One sentence describing the journey under test", (tester) async {
|
||||
// Robots and flows take the tester, create the ones your journey needs.
|
||||
final appLauncher = AppLauncher(tester);
|
||||
final onboardingFlows = OnboardingFlows(tester);
|
||||
final dashboardRobot = NewDashboardRobot(tester);
|
||||
final homePageRobot = HomePageRobot(tester);
|
||||
|
||||
// Boots the real app, the key names the suite in failure output.
|
||||
await appLauncher.launchApp(testKey: "example_feature_test_app_key");
|
||||
|
||||
// Every suite starts from a fresh install, so get to a working wallet first.
|
||||
// Use restoreFirstWalletFromSeed instead when your feature needs a known wallet.
|
||||
await onboardingFlows.createFirstWallet(WalletType.solana);
|
||||
|
||||
await dashboardRobot.isDisplayed();
|
||||
await homePageRobot.isDisplayed();
|
||||
|
||||
// Drive your feature through its robot from here. Robots own the finders and
|
||||
// assertions for their screen, the suite only sequences them:
|
||||
//
|
||||
// await homePageRobot.openSettingsSheet();
|
||||
// await mySettingsRobot.isDisplayed();
|
||||
// await mySettingsRobot.doTheThing();
|
||||
//
|
||||
// Rules of thumb:
|
||||
// - Wait with pumpUntilFound and pumpUntil, never with sleeps or pumpAndSettle.
|
||||
// - Tap and read widgets by ValueKey, add keys to lib/ widgets when they are missing.
|
||||
// - Assert app state through getIt (AppStore) or the page widget's view model.
|
||||
// - A widget being in the tree does not mean it is on screen. Every dashboard tab
|
||||
// stays mounted in an IndexedStack, so find.byType on a tab passes from any other
|
||||
// tab. Check what is in front, the way WalletListPageRobot does.
|
||||
// - A button being findable does not mean it is enabled. Tapping a disabled button
|
||||
// does nothing at all and reads later as a missing widget somewhere else, so wait
|
||||
// on whatever the screen itself gates on.
|
||||
// - Add the finished suite to the table in docs/INTEGRATION_TESTS.md.
|
||||
});
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import 'package:hash_wallet/wallet_types.g.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../components/common_test_constants.dart';
|
||||
import '../components/common_test_flows.dart';
|
||||
import '../robots/auth_page_robot.dart';
|
||||
import '../robots/dashboard_page_robot.dart';
|
||||
import '../robots/security_and_backup_page_robot.dart';
|
||||
import '../robots/wallet_keys_robot.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
AuthPageRobot authPageRobot;
|
||||
CommonTestFlows commonTestFlows;
|
||||
DashboardPageRobot dashboardPageRobot;
|
||||
WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot;
|
||||
SecurityAndBackupPageRobot securityAndBackupPageRobot;
|
||||
|
||||
testWidgets(
|
||||
'Confirm if the seeds display properly',
|
||||
(tester) async {
|
||||
// Store the original FlutterError.onError handler
|
||||
final originalOnError = FlutterError.onError;
|
||||
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
debugPrint('FlutterError caught: ${details.exception}');
|
||||
};
|
||||
|
||||
try {
|
||||
authPageRobot = AuthPageRobot(tester);
|
||||
commonTestFlows = CommonTestFlows(tester);
|
||||
dashboardPageRobot = DashboardPageRobot(tester);
|
||||
walletKeysAndSeedPageRobot = WalletKeysAndSeedPageRobot(tester);
|
||||
securityAndBackupPageRobot = SecurityAndBackupPageRobot(tester);
|
||||
|
||||
await commonTestFlows.startAppFlow(
|
||||
ValueKey('confirm_creds_display_correctly_flow_app_key'),
|
||||
);
|
||||
|
||||
await commonTestFlows.welcomePageToCreateNewWalletFlow(
|
||||
WalletType.solana,
|
||||
CommonTestConstants.pin,
|
||||
);
|
||||
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
|
||||
|
||||
await _confirmSeedsFlowForWalletType(
|
||||
WalletType.solana,
|
||||
authPageRobot,
|
||||
dashboardPageRobot,
|
||||
securityAndBackupPageRobot,
|
||||
walletKeysAndSeedPageRobot,
|
||||
tester,
|
||||
);
|
||||
|
||||
// Do the same for other available wallet types
|
||||
for (var walletType in availableWalletTypes) {
|
||||
if (walletType == WalletType.solana) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await dashboardPageRobot.navigateToWalletsListPage(isDrawer: true);
|
||||
|
||||
await commonTestFlows.createNewWalletFromWalletMenu(walletType);
|
||||
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
|
||||
|
||||
await _confirmSeedsFlowForWalletType(
|
||||
walletType,
|
||||
authPageRobot,
|
||||
dashboardPageRobot,
|
||||
securityAndBackupPageRobot,
|
||||
walletKeysAndSeedPageRobot,
|
||||
tester,
|
||||
);
|
||||
}
|
||||
|
||||
await Future.delayed(Duration(seconds: 15));
|
||||
} finally {
|
||||
FlutterError.onError = originalOnError;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmSeedsFlowForWalletType(
|
||||
WalletType walletType,
|
||||
AuthPageRobot authPageRobot,
|
||||
DashboardPageRobot dashboardPageRobot,
|
||||
SecurityAndBackupPageRobot securityAndBackupPageRobot,
|
||||
WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot,
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await dashboardPageRobot.openDrawerMenu();
|
||||
await dashboardPageRobot.dashboardMenuWidgetRobot.navigateToSecurityAndBackupPage();
|
||||
|
||||
await securityAndBackupPageRobot.navigateToShowKeysPage();
|
||||
|
||||
final onAuthPage = authPageRobot.onAuthPage();
|
||||
if (onAuthPage) {
|
||||
await authPageRobot.enterPinCode(CommonTestConstants.pin);
|
||||
}
|
||||
|
||||
final onAuthPageDesktop = authPageRobot.onAuthPageDesktop();
|
||||
if (onAuthPageDesktop) {
|
||||
await authPageRobot.enterPassword(CommonTestConstants.pin.join(""));
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await walletKeysAndSeedPageRobot.isWalletKeysAndSeedPage();
|
||||
walletKeysAndSeedPageRobot.hasTitle();
|
||||
walletKeysAndSeedPageRobot.hasShareWarning();
|
||||
|
||||
await walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType);
|
||||
|
||||
await walletKeysAndSeedPageRobot.backToDashboard();
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import 'package:hash_wallet/wallet_types.g.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../components/common_test_constants.dart';
|
||||
import '../components/common_test_flows.dart';
|
||||
import '../robots/dashboard_page_robot.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
CommonTestFlows commonTestFlows;
|
||||
DashboardPageRobot dashboardPageRobot;
|
||||
|
||||
testWidgets(
|
||||
'Create Wallet Flow',
|
||||
(tester) async {
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
debugPrint('FlutterError caught: ${details.exception}');
|
||||
};
|
||||
commonTestFlows = CommonTestFlows(tester);
|
||||
dashboardPageRobot = DashboardPageRobot(tester);
|
||||
|
||||
// Start the app
|
||||
await commonTestFlows.startAppFlow(
|
||||
ValueKey('create_wallets_through_seeds_test_app_key'),
|
||||
);
|
||||
|
||||
await commonTestFlows.welcomePageToCreateNewWalletFlow(
|
||||
WalletType.solana,
|
||||
CommonTestConstants.pin,
|
||||
);
|
||||
|
||||
// Confirm it actually restores a solana wallet
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
|
||||
|
||||
// Do the same for other available wallet types
|
||||
for (var walletType in availableWalletTypes) {
|
||||
if (walletType == WalletType.solana) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await dashboardPageRobot.navigateToWalletsListPage();
|
||||
|
||||
await commonTestFlows.createNewWalletFromWalletMenu(walletType);
|
||||
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
|
||||
}
|
||||
|
||||
// Goes to the wallet menu and provides a confirmation that all the wallets were correctly restored
|
||||
await dashboardPageRobot.navigateToWalletsListPage();
|
||||
|
||||
commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly();
|
||||
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../components/common_test_constants.dart';
|
||||
import '../components/common_test_flows.dart';
|
||||
import '../robots/dashboard_page_robot.dart';
|
||||
import '../robots/exchange_confirm_page_robot.dart';
|
||||
import '../robots/exchange_page_robot.dart';
|
||||
import '../robots/exchange_trade_external_send_page_robot.dart';
|
||||
import '../robots/exchange_trade_page_robot.dart';
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
CommonTestFlows commonTestFlows;
|
||||
ExchangePageRobot exchangePageRobot;
|
||||
DashboardPageRobot dashboardPageRobot;
|
||||
ExchangeTradePageRobot exchangeTradePageRobot;
|
||||
ExchangeConfirmPageRobot exchangeConfirmPageRobot;
|
||||
ExchangeTradeExternalSendPageRobot exchangeTradeExternalSendPageRobot;
|
||||
|
||||
testWidgets('Exchange flow', (tester) async {
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
debugPrint('FlutterError caught: ${details.exception}');
|
||||
};
|
||||
|
||||
commonTestFlows = CommonTestFlows(tester);
|
||||
exchangePageRobot = ExchangePageRobot(tester);
|
||||
dashboardPageRobot = DashboardPageRobot(tester);
|
||||
exchangeTradePageRobot = ExchangeTradePageRobot(tester);
|
||||
exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester);
|
||||
exchangeTradeExternalSendPageRobot = ExchangeTradeExternalSendPageRobot(tester);
|
||||
|
||||
await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key'));
|
||||
await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
|
||||
CommonTestConstants.testWalletType,
|
||||
secrets.solanaTestWalletSeeds,
|
||||
CommonTestConstants.pin,
|
||||
);
|
||||
await dashboardPageRobot.navigateToExchangePage();
|
||||
|
||||
// ----------- Exchange Page -------------
|
||||
await exchangePageRobot.selectDepositCurrency(CommonTestConstants.exchangeTestDepositCurrency);
|
||||
await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.exchangeTestReceiveCurrency);
|
||||
|
||||
await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount);
|
||||
await exchangePageRobot.enterDepositRefundAddress(
|
||||
depositAddress: CommonTestConstants.testWalletAddress,
|
||||
);
|
||||
await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
|
||||
await exchangePageRobot.onExchangeButtonPressed();
|
||||
await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount);
|
||||
|
||||
await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed();
|
||||
|
||||
await exchangeTradePageRobot.onGotItButtonPressed();
|
||||
await exchangeTradePageRobot.onSendFromExternalButtonPressed();
|
||||
|
||||
await exchangeTradeExternalSendPageRobot.isExchangeTradeExternalSendPage();
|
||||
await exchangeTradeExternalSendPageRobot.verifySendDetailsItemsDisplayProperly();
|
||||
await exchangeTradeExternalSendPageRobot.onContinueButtonPressed();
|
||||
});
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import 'package:hash_wallet/wallet_types.g.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../components/common_test_constants.dart';
|
||||
import '../components/common_test_flows.dart';
|
||||
import '../robots/dashboard_page_robot.dart';
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
CommonTestFlows commonTestFlows;
|
||||
DashboardPageRobot dashboardPageRobot;
|
||||
|
||||
testWidgets(
|
||||
'Restoring Wallets Through Seeds',
|
||||
(tester) async {
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
debugPrint('FlutterError caught: ${details.exception}');
|
||||
};
|
||||
|
||||
commonTestFlows = CommonTestFlows(tester);
|
||||
dashboardPageRobot = DashboardPageRobot(tester);
|
||||
|
||||
// Start the app
|
||||
await commonTestFlows.startAppFlow(
|
||||
ValueKey('restore_wallets_through_seeds_test_app_key'),
|
||||
);
|
||||
|
||||
// Restore the first wallet type: Solana
|
||||
await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
|
||||
WalletType.solana,
|
||||
secrets.solanaTestWalletSeeds,
|
||||
CommonTestConstants.pin,
|
||||
);
|
||||
|
||||
// Confirm it actually restores a solana wallet
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
|
||||
|
||||
// Do the same for other available wallet types
|
||||
for (var walletType in availableWalletTypes) {
|
||||
if (walletType == WalletType.solana) {
|
||||
continue;
|
||||
}
|
||||
final seed = commonTestFlows.getWalletSeedsByWalletType(walletType);
|
||||
if (seed.isEmpty) {
|
||||
tester.printToConsole("----------------------------");
|
||||
tester.printToConsole("- Skipped wallet: ${walletType}");
|
||||
tester.printToConsole("- Make sure to add seed to secrets");
|
||||
tester.printToConsole("----------------------------");
|
||||
continue;
|
||||
}
|
||||
|
||||
await dashboardPageRobot.navigateToWalletsListPage();
|
||||
|
||||
await commonTestFlows.restoreWalletFromWalletMenu(
|
||||
walletType,
|
||||
seed,
|
||||
);
|
||||
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType);
|
||||
}
|
||||
|
||||
// Goes to the wallet menu and provides a visual confirmation that all the wallets were correctly restored
|
||||
await dashboardPageRobot.navigateToWalletsListPage();
|
||||
|
||||
commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly();
|
||||
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
import '../components/common_test_constants.dart';
|
||||
import '../components/common_test_flows.dart';
|
||||
import '../components/common_test_cases.dart';
|
||||
import '../robots/dashboard_page_robot.dart';
|
||||
import '../robots/send_page_robot.dart';
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
SendPageRobot sendPageRobot;
|
||||
CommonTestFlows commonTestFlows;
|
||||
DashboardPageRobot dashboardPageRobot;
|
||||
CommonTestCases commonTestCases;
|
||||
|
||||
testWidgets('Send flow with wallet switching for insufficient balance - testing \$1 send',
|
||||
(tester) async {
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
debugPrint('FlutterError caught: ${details.exception}');
|
||||
};
|
||||
|
||||
commonTestFlows = CommonTestFlows(tester);
|
||||
sendPageRobot = SendPageRobot(tester: tester);
|
||||
dashboardPageRobot = DashboardPageRobot(tester);
|
||||
commonTestCases = CommonTestCases(tester);
|
||||
|
||||
await commonTestFlows.startAppFlow(ValueKey('send_test_app_key'));
|
||||
|
||||
// Various wallet options we want to use to test the send flow
|
||||
final walletConfigs = [
|
||||
{
|
||||
'type': WalletType.solana,
|
||||
'seed': secrets.solanaTestWalletSeeds2,
|
||||
'name': 'Solana Wallet 1',
|
||||
},
|
||||
{
|
||||
'type': WalletType.solana,
|
||||
'seed': secrets.solanaTestWalletSeeds,
|
||||
'name': 'Solana Wallet 2',
|
||||
},
|
||||
{
|
||||
'type': WalletType.ethereum,
|
||||
'seed': secrets.ethereumTestWalletSeeds,
|
||||
'name': 'Ethereum Wallet',
|
||||
},
|
||||
];
|
||||
|
||||
bool hasSufficientBalance = false;
|
||||
WalletType? successfulWalletType;
|
||||
|
||||
// We try each wallet configuration until we find one with sufficient balance
|
||||
for (int i = 0; i < walletConfigs.length; i++) {
|
||||
final config = walletConfigs[i];
|
||||
|
||||
if ((config['seed'] as String).isEmpty) {
|
||||
tester.printToConsole('Skipping ${config['name']}, seed is empty');
|
||||
continue;
|
||||
}
|
||||
|
||||
tester.printToConsole('Trying ${config['name']}');
|
||||
|
||||
// For first wallet, we restore wallet afresh, for subsequent ones, we do a wallet switch
|
||||
if (i == 0) {
|
||||
await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
|
||||
config['type'] as WalletType,
|
||||
config['seed'] as String,
|
||||
CommonTestConstants.pin,
|
||||
);
|
||||
} else {
|
||||
await commonTestCases.goBack();
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await dashboardPageRobot.navigateToWalletsListPage();
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestFlows.restoreWalletFromWalletMenu(
|
||||
config['type'] as WalletType,
|
||||
config['seed'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(config['type'] as WalletType);
|
||||
|
||||
// Navigate to send page
|
||||
await dashboardPageRobot.navigateToSendPage();
|
||||
await sendPageRobot.checkIfSendPageIsVisible();
|
||||
|
||||
await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress);
|
||||
await sendPageRobot.selectReceiveCurrency(CommonTestConstants.sendTestReceiveCurrency);
|
||||
|
||||
await sendPageRobot.selectTransactionPriority();
|
||||
|
||||
// Main check to see if this wallet has sufficient balance for $1 send
|
||||
final hasBalance = await sendPageRobot.validateWalletBalanceForOneDollarSend();
|
||||
if (hasBalance) {
|
||||
hasSufficientBalance = true;
|
||||
successfulWalletType = config['type'] as WalletType;
|
||||
tester.printToConsole(
|
||||
'${config['name']} has sufficient balance for \$${CommonTestConstants.sendTestFiatAmount} send, proceeding with send',
|
||||
);
|
||||
break;
|
||||
} else {
|
||||
tester.printToConsole(
|
||||
'${config['name']} has insufficient balance for \$${CommonTestConstants.sendTestFiatAmount} send',
|
||||
);
|
||||
if (i < walletConfigs.length - 1) {
|
||||
tester.printToConsole('Switching to next wallet...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We only proceed with the send if we have sufficient balance
|
||||
if (hasSufficientBalance && successfulWalletType != null) {
|
||||
tester.printToConsole('Performing send transaction with $successfulWalletType wallet');
|
||||
|
||||
await sendPageRobot.testFiatAmountEntry();
|
||||
await sendPageRobot.testCryptoAmountEntry();
|
||||
|
||||
await sendPageRobot.onSendButtonPressed();
|
||||
await sendPageRobot.onSendSliderOnConfirmSendingBottomSheetDragged();
|
||||
await sendPageRobot.handleTransactionSuccessFlow();
|
||||
} else {
|
||||
tester.printToConsole(
|
||||
'Test completed without sending - all available wallets have insufficient balance for \$${CommonTestConstants.sendTestFiatAmount} send',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../components/common_test_constants.dart';
|
||||
import '../components/common_test_flows.dart';
|
||||
import '../robots/dashboard_page_robot.dart';
|
||||
import '../robots/transactions_page_robot.dart';
|
||||
import 'package:hash_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
CommonTestFlows commonTestFlows;
|
||||
DashboardPageRobot dashboardPageRobot;
|
||||
TransactionsPageRobot transactionsPageRobot;
|
||||
|
||||
/// Two Test Scenarios
|
||||
/// - Fully Synchronizes and display the transaction history either immediately or few seconds after fully synchronizing
|
||||
/// - Displays the transaction history progressively as synchronizing happens
|
||||
testWidgets('Transaction history flow', (tester) async {
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
debugPrint('FlutterError caught: ${details.exception}');
|
||||
};
|
||||
|
||||
commonTestFlows = CommonTestFlows(tester);
|
||||
dashboardPageRobot = DashboardPageRobot(tester);
|
||||
transactionsPageRobot = TransactionsPageRobot(tester);
|
||||
|
||||
await commonTestFlows.startAppFlow(
|
||||
ValueKey('confirm_creds_display_correctly_flow_app_key'),
|
||||
);
|
||||
|
||||
/// Test Scenario 1 - Displays transaction history list while synchronizing.
|
||||
///
|
||||
/// For Solana/Tron WalletTypes.
|
||||
await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow(
|
||||
WalletType.solana,
|
||||
secrets.solanaTestWalletSeeds,
|
||||
CommonTestConstants.pin,
|
||||
);
|
||||
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana);
|
||||
|
||||
await dashboardPageRobot.swipeDashboardTab(true);
|
||||
|
||||
await transactionsPageRobot.isTransactionsPage();
|
||||
|
||||
await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly();
|
||||
|
||||
// Wait time for the first scenario to ensure proper loading
|
||||
await tester.pump(Duration(seconds: 3));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(true);
|
||||
|
||||
/// Test Scenario 2 - Displays transaction history list after fully synchronizing.
|
||||
///
|
||||
/// For Bitcoin/Monero/Wownero WalletTypes.
|
||||
await dashboardPageRobot.navigateToWalletsListPage();
|
||||
|
||||
await commonTestFlows.restoreWalletFromWalletMenu(
|
||||
WalletType.bitcoin,
|
||||
secrets.bitcoinTestWalletSeeds,
|
||||
);
|
||||
|
||||
await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.bitcoin);
|
||||
|
||||
await dashboardPageRobot.swipeDashboardTab(true);
|
||||
|
||||
await transactionsPageRobot.isTransactionsPage();
|
||||
|
||||
await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly();
|
||||
|
||||
// Wait time for the second scenario to ensure proper loading
|
||||
await tester.pump(Duration(seconds: 3));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(false);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user