Skip to content

Commit

Permalink
CW-727/728-Automated-Integrated-Tests (#1514)
Browse files Browse the repository at this point in the history
* feat: Integration tests setup and tests for Disclaimer, Welcome and Setup Pin Code pages

* feat: Integration test flow from start to restoring a wallet successfully done

* test: Dashboard view test and linking to flow

* feat: Testing the Exchange flow section, selecting sending and receiving currencies

* test: Successfully create an exchange section

* feat: Implement flow up to sending section

* test: Complete Exchange flow

* fix dependency issue

* test: Final cleanups

* feat: Add CI to run automated integration tests withan android emulator

* feat: Adjust Automated integration test CI to run on ubuntu 20.04-a

* fix: Move integration test CI into PR test build CI

* ci: Add automated test ci which is a streamlined replica of pr test build ci

* ci: Re-add step to access branch name

* ci: Add KVM

* ci: Add filepath to trigger the test run from

* ci: Add required key

* ci: Add required key

* ci: Add missing secret key

* ci: Add missing secret key

* ci: Add nano secrets to workflow

* ci: Switch step to free space on runner

* ci: Remove timeout from workflow

* ci: Confirm impact that removing copy_monero_deps would have on entire workflow time

* ci: Update CI and temporarily remove cache related to emulator

* ci: Remove dynamic java version

* ci: Temporarily switch CI

* ci: Switch to 11.x jdk

* ci: Temporarily switch CI

* ci: Revert ubuntu version

* ci: Add more api levels

* ci: Add more target options

* ci: Settled on stable emulator matrix options

* ci: Add more target options

* ci: Modify flow

* ci: Streamline api levels to 28 and 29

* ci: One more trial

* ci: Switch to flutter drive

* ci: Reduce options

* ci: Remove haven from test

* ci: Check for solana in list

* ci: Adjust amounts and currencies for exchange flow

* ci: Set write response on failure to true

* ci: Split ci to funds and non funds related tests

* test: Test for Send flow scenario and minor restructuring for test folders and files

* chore: cleanup

* ci: Pause CI for now

* ci: Pause CI for now

* ci: Pause CI for now

* Fix: Add keys back to currency amount textfield widget

* fix: Switch variable name

* fix: remove automation for now

* test: Updating send page robot and also syncing branch with main

---------

Co-authored-by: OmarHatem <[email protected]>
  • Loading branch information
Blazebrain and OmarHatem28 authored Sep 22, 2024
1 parent 32e119e commit 4adb81c
Show file tree
Hide file tree
Showing 67 changed files with 2,381 additions and 240 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/pr_test_build_android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
jobs:
PR_test_build:
runs-on: ubuntu-20.04
strategy:
matrix:
api-level: [29]
env:
STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
macos/Runner/Configs/AppInfo.xcconfig


integration_test/playground.dart

# Monero.dart (Monero_C)
scripts/monero_c
# iOS generated framework bin
Expand Down
4 changes: 2 additions & 2 deletions cw_nano/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,10 @@ packages:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "7.0.0"
fixnum:
dependency: transitive
description:
Expand Down
96 changes: 96 additions & 0 deletions integration_test/components/common_test_cases.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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}) async {
final widget = find.byKey(ValueKey(key));
await tester.tap(widget);
shouldPumpAndSettle ? await tester.pumpAndSettle() : 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 hasType<T>() {
final typeWidget = find.byType(T);
expect(typeWidget, findsOneWidget);
}

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> scrollUntilVisible(String childKey, String parentScrollableKey,
{double delta = 300}) async {
final scrollableWidget = find.descendant(
of: find.byKey(Key(parentScrollableKey)),
matching: find.byType(Scrollable),
);

final isAlreadyVisibile = isWidgetVisible(find.byKey(ValueKey(childKey)));

if (isAlreadyVisibile) return;

await tester.scrollUntilVisible(
find.byKey(ValueKey(childKey)),
delta,
scrollable: scrollableWidget,
);
}

bool isWidgetVisible(Finder finder) {
try {
final Element element = finder.evaluate().single;
final RenderBox renderBox = element.renderObject as RenderBox;
return renderBox.paintBounds
.shift(renderBox.localToGlobal(Offset.zero))
.overlaps(tester.binding.renderViews.first.paintBounds);
} catch (e) {
return false;
}
}

Future<void> enterText(String text, String editableTextKey) async {
final editableTextWidget = find.byKey(ValueKey((editableTextKey)));

await tester.enterText(editableTextWidget, text);

await tester.pumpAndSettle();
}

Future<void> defaultSleepTime({int seconds = 2}) async =>
await Future.delayed(Duration(seconds: seconds));
}
13 changes: 13 additions & 0 deletions integration_test/components/common_test_constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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 exchangeTestAmount = '8';
static final WalletType testWalletType = WalletType.solana;
static final String testWalletName = 'Integrated Testing Wallet';
static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol;
static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol;
static final String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L';
}
101 changes: 101 additions & 0 deletions integration_test/components/common_test_flows.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/main.dart' as app;

import '../robots/disclaimer_page_robot.dart';
import '../robots/new_wallet_type_page_robot.dart';
import '../robots/restore_from_seed_or_key_robot.dart';
import '../robots/restore_options_page_robot.dart';
import '../robots/setup_pin_code_robot.dart';
import '../robots/welcome_page_robot.dart';
import 'common_test_cases.dart';
import 'common_test_constants.dart';

class CommonTestFlows {
CommonTestFlows(this._tester)
: _commonTestCases = CommonTestCases(_tester),
_welcomePageRobot = WelcomePageRobot(_tester),
_setupPinCodeRobot = SetupPinCodeRobot(_tester),
_disclaimerPageRobot = DisclaimerPageRobot(_tester),
_newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
_restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
_restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester);

final WidgetTester _tester;
final CommonTestCases _commonTestCases;

final WelcomePageRobot _welcomePageRobot;
final SetupPinCodeRobot _setupPinCodeRobot;
final DisclaimerPageRobot _disclaimerPageRobot;
final NewWalletTypePageRobot _newWalletTypePageRobot;
final RestoreOptionsPageRobot _restoreOptionsPageRobot;
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;

Future<void> startAppFlow(Key key) async {
await app.main(topLevelKey: ValueKey('send_flow_test_app_key'));

await _tester.pumpAndSettle();

// --------- Disclaimer Page ------------
// Tap checkbox to accept disclaimer
await _disclaimerPageRobot.tapDisclaimerCheckbox();

// Tap accept button
await _disclaimerPageRobot.tapAcceptButton();
}

Future<void> restoreWalletThroughSeedsFlow() async {
await _welcomeToRestoreFromSeedsPath();
await _restoreFromSeeds();
}

Future<void> restoreWalletThroughKeysFlow() async {
await _welcomeToRestoreFromSeedsPath();
await _restoreFromKeys();
}

Future<void> _welcomeToRestoreFromSeedsPath() async {
// --------- Welcome Page ---------------
await _welcomePageRobot.navigateToRestoreWalletPage();

// ----------- Restore Options Page -----------
// Route to restore from seeds page to continue flow
await _restoreOptionsPageRobot.navigateToRestoreFromSeedsPage();

// ----------- SetupPinCode Page -------------
// Confirm initial defaults - Widgets to be displayed etc
await _setupPinCodeRobot.isSetupPinCodePage();

await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, true);
await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false);
await _setupPinCodeRobot.tapSuccessButton();

// ----------- NewWalletType Page -------------
// Confirm scroll behaviour works properly
await _newWalletTypePageRobot
.findParticularWalletTypeInScrollableList(CommonTestConstants.testWalletType);

// Select a wallet and route to next page
await _newWalletTypePageRobot.selectWalletType(CommonTestConstants.testWalletType);
await _newWalletTypePageRobot.onNextButtonPressed();
}

Future<void> _restoreFromSeeds() async {
// ----------- RestoreFromSeedOrKeys Page -------------
await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);
await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.solanaTestWalletSeeds);
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
}

Future<void> _restoreFromKeys() async {
await _commonTestCases.swipePage();
await _commonTestCases.defaultSleepTime();

await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName);

await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore('');
await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed();
}
}
84 changes: 84 additions & 0 deletions integration_test/funds_related_tests.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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';

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.restoreWalletThroughSeedsFlow();

// ----------- RestoreFromSeedOrKeys Page -------------
await dashboardPageRobot.navigateToExchangePage();

// ----------- Exchange Page -------------
await exchangePageRobot.isExchangePage();
exchangePageRobot.hasResetButton();
await exchangePageRobot.displayBothExchangeCards();
exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards();
exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards();

await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency);
await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency);

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, false);
}

// ----------- 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.onConfirmSendingButtonPressed();

await exchangeTradePageRobot.handleConfirmSendResult();

await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed();
});
});
}
25 changes: 25 additions & 0 deletions integration_test/helpers/mocks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/wallet_list_store.dart';
import 'package:cake_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{}
Loading

0 comments on commit 4adb81c

Please sign in to comment.