From 25a83930f9b3beb8774dc23edffc77596d99030b Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 20 Dec 2024 10:36:54 +0100 Subject: [PATCH 01/13] fix: Generic fixes --- .../seed/seed_verification/seed_verification_step_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart b/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart index b8c1500dcf..fdd88f6627 100644 --- a/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart +++ b/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart @@ -70,6 +70,8 @@ class SeedVerificationStepView extends StatelessWidget { (option) { return GestureDetector( onTap: () async { + if (walletSeedViewModel.wrongEntries > 2) return; + final isCorrectWord = walletSeedViewModel.isChosenWordCorrect(option); final isSecondWrongEntry = walletSeedViewModel.wrongEntries >= 2; if (!isCorrectWord) { From 1335627879b5ded6a12d8e70c18057210e5f8786 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 14 Jan 2025 10:01:38 +0100 Subject: [PATCH 02/13] fix: Modify hasSufficientFundsForRent to work for spl transactions --- cw_solana/lib/solana_client.dart | 36 +++++++++++++++----------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 431f5f7fbe..cc03206f0c 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -151,7 +151,7 @@ class SolanaWalletClient { transactionDetails.addAll(response); // to avoid reaching the node RPS limit - await Future.delayed(Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 500)); } for (final tx in transactionDetails) { @@ -379,18 +379,16 @@ class SolanaWalletClient { required double solBalance, required double fee, }) async { - return true; - // TODO: this is not doing what the name inclines - // final rent = - // await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed); - // - // final rentInSol = (rent / lamportsPerSol).toDouble(); - // - // final remnant = solBalance - (inputAmount + fee); - // - // if (remnant > rentInSol) return true; - // - // return false; + final rent = + await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed); + + final rentInSol = (rent / lamportsPerSol).toDouble(); + + final remnant = solBalance - (inputAmount + fee); + + if (remnant > rentInSol) return true; + + return false; } Future _signNativeTokenTransaction({ @@ -542,7 +540,7 @@ class SolanaWalletClient { ), ); - await Future.delayed(Duration(seconds: 5)); + await Future.delayed(const Duration(seconds: 5)); } } catch (e) { throw SolanaCreateAssociatedTokenAccountException(e.toString()); @@ -569,7 +567,7 @@ class SolanaWalletClient { ); bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent( - inputAmount: inputAmount, + inputAmount: 0, fee: fee, solBalance: solBalance, ); @@ -586,12 +584,12 @@ class SolanaWalletClient { ); sendTx() async { - await Future.delayed(Duration(seconds: 3)); + await Future.delayed(const Duration(seconds: 3)); return await sendTransaction( - signedTransaction: signedTx, - commitment: commitment, - ); + signedTransaction: signedTx, + commitment: commitment, + ); } final pendingTransaction = PendingSolanaTransaction( From ecc0156a64a78c757eef4b272ddf2d4f30213ffc Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 13:16:00 +0100 Subject: [PATCH 03/13] feat: Integrate seed verification into integration tests --- .../components/common_test_flows.dart | 22 ++++++++-- .../robots/seed_verification_page_robot.dart | 41 +++++++++++++++++++ .../robots/wallet_seed_page_robot.dart | 18 +++++--- .../seed_verification_page.dart | 2 + .../seed_verification_step_view.dart | 1 + lib/utils/feature_flag.dart | 4 +- .../hardware_wallet/ledger_view_model.dart | 4 +- 7 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 integration_test/robots/seed_verification_page_robot.dart diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index 8350b58590..01cb94c312 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -15,6 +15,7 @@ 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'; @@ -23,8 +24,6 @@ import '../robots/welcome_page_robot.dart'; import 'common_test_cases.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; -import 'common_test_constants.dart'; - class CommonTestFlows { CommonTestFlows(this._tester) : _commonTestCases = CommonTestCases(_tester), @@ -38,6 +37,7 @@ class CommonTestFlows { _walletListPageRobot = WalletListPageRobot(_tester), _newWalletTypePageRobot = NewWalletTypePageRobot(_tester), _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester), + _seedVerificationPageRobot = SeedVerificationPageRobot(_tester), _createPinWelcomePageRobot = CreatePinWelcomePageRobot(_tester), _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester), _walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester); @@ -56,6 +56,7 @@ class CommonTestFlows { final NewWalletTypePageRobot _newWalletTypePageRobot; final RestoreOptionsPageRobot _restoreOptionsPageRobot; final CreatePinWelcomePageRobot _createPinWelcomePageRobot; + final SeedVerificationPageRobot _seedVerificationPageRobot; final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot; final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot; @@ -85,6 +86,8 @@ class CommonTestFlows { await _confirmPreSeedInfo(); await _confirmWalletDetails(); + + await _verifyWalletSeed(); } //* ========== Handles flow from welcome to restoring wallet from seeds =============== @@ -147,6 +150,9 @@ class CommonTestFlows { await _confirmPreSeedInfo(); await _confirmWalletDetails(); + + await _verifyWalletSeed(); + await _commonTestCases.defaultSleepTime(); } @@ -254,9 +260,17 @@ class CommonTestFlows { await _walletSeedPageRobot.onCopySeedsButtonPressed(); - await _walletSeedPageRobot.onNextButtonPressed(); + await _walletSeedPageRobot.onVerifySeedButtonPressed(); + } + + //* ============ Handles Wallet Seed Verification Page ================== + + Future _verifyWalletSeed() async { + await _seedVerificationPageRobot.isSeedVerificationPage(); + + _seedVerificationPageRobot.hasTitle(); - await _walletSeedPageRobot.onConfirmButtonOnSeedAlertDialogPressed(); + await _seedVerificationPageRobot.verifyWalletSeeds(); } //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action diff --git a/integration_test/robots/seed_verification_page_robot.dart b/integration_test/robots/seed_verification_page_robot.dart new file mode 100644 index 0000000000..2d64606574 --- /dev/null +++ b/integration_test/robots/seed_verification_page_robot.dart @@ -0,0 +1,41 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class SeedVerificationPageRobot { + SeedVerificationPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + final CommonTestCases commonTestCases; + + Future isSeedVerificationPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + commonTestCases.hasText(S.current.verify_seed); + } + + Future verifyWalletSeeds() async { + final seedVerificationPage = + tester.widget(find.byType(SeedVerificationPage)); + + final walletSeedViewModel = seedVerificationPage.walletSeedViewModel; + + while (!walletSeedViewModel.isVerificationComplete) { + final currentCorrectWord = walletSeedViewModel.currentCorrectWord; + + await commonTestCases.tapItemByKey( + 'seed_verification_option_${currentCorrectWord}_button_key', + ); + + await commonTestCases.defaultSleepTime(seconds: 1); + } + + await commonTestCases.tapItemByKey('wallet_seed_page_open_wallet_button_key'); + + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/robots/wallet_seed_page_robot.dart b/integration_test/robots/wallet_seed_page_robot.dart index d52f3b1ec9..34ce3f1912 100644 --- a/integration_test/robots/wallet_seed_page_robot.dart +++ b/integration_test/robots/wallet_seed_page_robot.dart @@ -14,8 +14,13 @@ class WalletSeedPageRobot { await commonTestCases.isSpecificPage(); } - Future onNextButtonPressed() async { - await commonTestCases.tapItemByKey('wallet_seed_page_next_button_key'); + Future onVerifySeedButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_verify_seed_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onSaveSeedButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_save_seeds_button_key'); await commonTestCases.defaultSleepTime(); } @@ -35,14 +40,15 @@ class WalletSeedPageRobot { final walletSeedViewModel = walletSeedPage.walletSeedViewModel; final walletName = walletSeedViewModel.name; - final walletSeeds = walletSeedViewModel.seed; - + final walletSeeds = walletSeedViewModel.seedSplit; commonTestCases.hasText(walletName); - commonTestCases.hasText(walletSeeds); + for (var seed in walletSeeds) { + commonTestCases.hasText(seed); + } } void confirmWalletSeedReminderDisplays() { - commonTestCases.hasText(S.current.seed_reminder); + commonTestCases.hasText( S.current.cake_seeds_save_disclaimer); } Future onSaveSeedsButtonPressed() async { diff --git a/lib/src/screens/seed/seed_verification/seed_verification_page.dart b/lib/src/screens/seed/seed_verification/seed_verification_page.dart index ac03768cad..89c3248566 100644 --- a/lib/src/screens/seed/seed_verification/seed_verification_page.dart +++ b/lib/src/screens/seed/seed_verification/seed_verification_page.dart @@ -23,9 +23,11 @@ class SeedVerificationPage extends BasePage { child: walletSeedViewModel.isVerificationComplete || walletSeedViewModel.verificationIndices.isEmpty ? SeedVerificationSuccessView( + key: ValueKey('seed_verification_success_view_page'), imageColor: titleColor(context), ) : SeedVerificationStepView( + key: ValueKey('seed_verification_step_view_page'), walletSeedViewModel: walletSeedViewModel, questionTextColor: titleColor(context), ), diff --git a/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart b/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart index 9fd70be054..213b9e15cf 100644 --- a/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart +++ b/lib/src/screens/seed/seed_verification/seed_verification_step_view.dart @@ -69,6 +69,7 @@ class SeedVerificationStepView extends StatelessWidget { children: walletSeedViewModel.currentOptions.map( (option) { return GestureDetector( + key: ValueKey('seed_verification_option_${option}_button_key'), onTap: () async { if (walletSeedViewModel.wrongEntries > 2) return; diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index 593e0f216d..2b931d1485 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -5,5 +5,5 @@ class FeatureFlag { static const bool isExolixEnabled = true; static const bool isInAppTorEnabled = false; static const bool isBackgroundSyncEnabled = false; - static const int verificationWordsCount = kDebugMode ? 0 : 2; -} \ No newline at end of file + static const int verificationWordsCount = 2; +} diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 4c084c778b..5210137e39 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -112,8 +112,8 @@ abstract class LedgerViewModelBase with Store { : ledgerPlusUSB; if (_connectionChangeSubscription == null) { - _connectionChangeSubscription = ledger.deviceStateChanges - .listen(_connectionChangeListener); + // _connectionChangeSubscription = ledger.deviceStateChanges + // .listen(_connectionChangeListener); } _connection = await ledger.connect(device); From 50c82e0177a73dfd43050a919b8faf718d54105c Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 13:50:00 +0100 Subject: [PATCH 04/13] fix: Adjust workflow file --- .../workflows/automated_integration_test.yml | 588 +++++++++--------- 1 file changed, 310 insertions(+), 278 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index b299c93407..c2e6ae1145 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -1,299 +1,331 @@ name: Automated Integration Tests -on: - # pull_request: - # branches: [main, CW-659-Transaction-History-Automated-Tests] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" +on: [push] +defaults: + run: + shell: bash jobs: - Automated_integration_test: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - api-level: [29] - # arch: [x86, x86_64] - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} + Automated_integration_test: + runs-on: linux-amd64 + container: + image: ghcr.io/cake-tech/cake_wallet:main-linux + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + MONEROC_CACHE_DIR_ROOT: /opt/generic_cache + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + ANDROID_AVD_HOME: /root/.android/avd + volumes: + - /opt/cw_cache_android/root/.cache:/root/.cache + - /opt/cw_cache_android/root/.android/avd/:/root/.android/avd + - /opt/cw_cache_android/root/.ccache:/root/.ccache + - /opt/cw_cache_android/root/.pub-cache/:/root/.pub-cache + - /opt/cw_cache_android/root/.gradle/:/root/.gradle + - /opt/cw_cache_android/root/.android/:/root/.android + - /opt/cw_cache_android/root/go/pkg:/root/go/pkg + - /opt/cw_cache_android/opt/generic_cache:/opt/generic_cache + - /dev/kvm:/dev/kvm + strategy: + matrix: + api-level: [29] - steps: - - name: is pr - if: github.event_name == 'pull_request' - run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV + steps: + - name: Fix github actions messing up $HOME... + run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + - uses: actions/checkout@v4 + - name: configure git + run: | + git config --global user.email "ci@cakewallet.com" + git config --global user.name "CakeWallet CI" + - name: Add secrets + run: | + touch lib/.secrets.g.dart + touch cw_evm/lib/.secrets.g.dart + touch cw_solana/lib/.secrets.g.dart + touch cw_core/lib/.secrets.g.dart + touch cw_nano/lib/.secrets.g.dart + touch cw_tron/lib/.secrets.g.dart + if [[ "x${{ secrets.SALT }}" == "x" ]]; + then + echo "const salt = '954f787f12622067f7e548d9450c3832';" > lib/.secrets.g.dart + else + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const keychainSalt = '2d2beba777dbf7dff7013b7a';" >> lib/.secrets.g.dart + else + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY }}" == "x" ]]; + then + echo "const key = '638e98820ec10a2945e968435c9397a3';" >> lib/.secrets.g.dart + else + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.WALLET_SALT }}" == "x" ]]; + then + echo "const walletSalt = '8f7f1b70';" >> lib/.secrets.g.dart + else + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.SHORT_KEY }}" == "x" ]]; + then + echo "const shortKey = '653f270c2c152bc7ec864afe';" >> lib/.secrets.g.dart + else + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_SALT }}" == "x" ]]; + then + echo "const backupSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const backupKeychainSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi + echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart + echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart + echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart + echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart + echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart + echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart + echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart + echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart + echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart + echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart + echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart + echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart + echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart + # for tests + echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart + - name: prepare monero_c and cache + run: | + export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }') + echo MONEROC_HASH=$MONEROC_HASH >> /etc/environment + mkdir -p "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + pushd scripts + ln -s "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + ./prepare_moneroc.sh + popd + pushd scripts/monero_c + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" || true - - name: is not pr - if: github.event_name != 'pull_request' - run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV + rm -rf "$PWD/contrib/depends/built" "$PWD/monero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + rm -rf "$PWD/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + mkdir -p contrib/depends || true + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" "$PWD/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" "$PWD/monero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" "$PWD/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + popd - - name: Free Disk Space (Ubuntu) - uses: insightsengineering/disk-space-reclaimer@v1 - with: - tools-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - swap-storage: true - docker-images: true + - name: Generate KeyStore + run: | + pushd /opt/generic_cache + if [[ ! -f key.jks ]]; + then + keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + else + echo "$PWD/key.jks exist, not generating" + fi + popd + cp /opt/generic_cache/key.jks android/app - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: "temurin" - java-version: "17" - - name: Configure placeholder git details - run: | - git config --global user.email "CI@cakewallet.com" - git config --global user.name "Cake Github Actions" - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.24.0" - channel: stable + - name: Execute Build and Setup Commands + run: | + pushd scripts/android + source ./app_env.sh cakewallet + ./app_config.sh + popd - - name: Install package dependencies - run: | - sudo apt update - sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + - name: Build monero_c + run: | + pushd scripts/android/ + source ./app_env.sh cakewallet + ./build_monero_all.sh + popd - - name: Execute Build and Setup Commands - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} - cd cake_wallet/scripts/android/ - ./install_ndk.sh - source ./app_env.sh cakewallet - chmod +x pubspec_gen.sh - ./app_config.sh + - name: Install Flutter dependencies + run: | + flutter pub get - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} + - name: Build mwebd + run: | + set -x -e + export MWEBD_HASH=$(cat scripts/android/build_mwebd.sh | grep 'git reset --hard' | xargs | awk '{ print $4 }') + echo MWEBD_HASH=$MWEBD_HASH >> /etc/environment + pushd scripts/android + gomobile init; + ./build_mwebd.sh --dont-install + popd - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh + - name: Build generated code + run: | + ./model_generator.sh async - - name: Install Flutter dependencies - run: | - cd /opt/android/cake_wallet - flutter pub get + - name: Generate key properties + run: | + dart run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + - name: Generate localization + run: | + dart run tool/generate_localization.dart - - name: Install go and gomobile - run: | - # install go > 1.23: - wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - go install golang.org/x/mobile/cmd/gomobile@latest - gomobile init + - name: Rename app + run: | + sanitized_branch_name=${BRANCH_NAME#origin/} # Remove 'origin/' prefix if it exists + sanitized_branch_name=${sanitized_branch_name:0:16} # Take only the first 16 characters + sanitized_branch_name=$(echo "$sanitized_branch_name" | tr '[:upper:]' '[:lower:]') # Convert to lowercase + sanitized_branch_name=$(echo "$sanitized_branch_name" | sed 's/[^a-z0-9]//g') # Remove all special characters - - name: Build mwebd - run: | - # paths are reset after each step, so we need to set them again: - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - cd /opt/android/cake_wallet/scripts/android/ - ./build_mwebd.sh --dont-install + echo -e "id=com.cakewallet.test_${sanitized_branch_name}\nname=${BRANCH_NAME}" > android/app.properties - - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + - name: Build + run: | + flutter build apk --release --split-per-abi - - name: Generate key properties - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + - name: Rename apk file + run: | + cd build/app/outputs/flutter-apk + mkdir test-apk + cp app-arm64-v8a-release.apk test-apk/${BRANCH_NAME}.apk + cp app-x86_64-release.apk test-apk/${BRANCH_NAME}_x86.apk + cd test-apk + cp ${BRANCH_NAME}.apk ${BRANCH_NAME}_slack.apk - - name: Generate localization - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_localization.dart + - name: Find APK file + id: find_apk + run: | + set -x + apk_file=$(ls build/app/outputs/flutter-apk/test-apk/*_slack.apk || exit 1) + echo "APK_FILE=$apk_file" >> $GITHUB_ENV + + - name: Upload artifact to slack + if: ${{ !contains(github.event.head_commit.message, 'skip slack') }} + continue-on-error: true + uses: adrey/slack-file-upload-action@1.0.5 + with: + token: ${{ secrets.SLACK_APP_TOKEN }} + path: ${{ env.APK_FILE }} + channel: ${{ secrets.SLACK_APK_CHANNEL }} + initial_comment: ${{ github.event.head_commit.message }} - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh + - name: cleanup + run: rm -rf build/app/outputs/flutter-apk/test-apk/ + + - name: Upload Artifact to github + uses: actions/upload-artifact@v4 + with: + path: ${{ github.workspace }}/build/app/outputs/flutter-apk + name: "android apk" - - name: Add secrets - run: | - cd /opt/android/cake_wallet - touch lib/.secrets.g.dart - touch cw_evm/lib/.secrets.g.dart - touch cw_solana/lib/.secrets.g.dart - touch cw_core/lib/.secrets.g.dart - touch cw_nano/lib/.secrets.g.dart - touch cw_tron/lib/.secrets.g.dart - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart - echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart - echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart - echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart - echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart - echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart - echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart - echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart - echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart - echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart - echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart - echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart - echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart - - - name: Rename app - run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties - - - name: Build - run: | - cd /opt/android/cake_wallet - flutter build apk --release --split-per-abi - - # - name: Rename apk file - # run: | - # cd /opt/android/cake_wallet/build/app/outputs/flutter-apk - # mkdir test-apk - # cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk - # cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk - - # - name: Upload Artifact - # uses: kittaakos/upload-artifact-as-is@v0 - # with: - # path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/ - - # - name: Send Test APK - # continue-on-error: true - # uses: adrey/slack-file-upload-action@1.0.5 - # with: - # token: ${{ secrets.SLACK_APP_TOKEN }} - # path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/${{env.BRANCH_NAME}}.apk - # channel: ${{ secrets.SLACK_APK_CHANNEL }} - # title: "${{ env.BRANCH_NAME }}.apk" - # filename: ${{ env.BRANCH_NAME }}.apk - # initial_comment: ${{ github.event.head_commit.message }} - - - name: 🦾 Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - # arch: ${{ matrix.arch }} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - working-directory: /opt/android/cake_wallet - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: 🚀 Integration tests on Android Emulator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - working-directory: /opt/android/cake_wallet - script: | - chmod a+rx integration_test_runner.sh - ./integration_test_runner.sh + - name: 🦾 Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + # arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + working-directory: /opt/android/cake_wallet + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: 🚀 Integration tests on Android Emulator + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + working-directory: /opt/android/cake_wallet + script: | + chmod a+rx integration_test_runner.sh + ./integration_test_runner.sh \ No newline at end of file From ff3606a15205c77e512206b31ac8c7e71e12ef21 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 14:04:27 +0100 Subject: [PATCH 05/13] chore: Remove previous working directory --- .github/workflows/automated_integration_test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index c2e6ae1145..2c0c220ce1 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -314,7 +314,6 @@ jobs: force-avd-creation: false # arch: ${{ matrix.arch }} emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - working-directory: /opt/android/cake_wallet disable-animations: false script: echo "Generated AVD snapshot for caching." @@ -325,7 +324,6 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - working-directory: /opt/android/cake_wallet script: | chmod a+rx integration_test_runner.sh ./integration_test_runner.sh \ No newline at end of file From 4707dc8f42741c7e7c21248b4d2dd60b7c71d57a Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 14:07:01 +0100 Subject: [PATCH 06/13] fix: Revert change --- lib/view_model/hardware_wallet/ledger_view_model.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 5210137e39..4c084c778b 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -112,8 +112,8 @@ abstract class LedgerViewModelBase with Store { : ledgerPlusUSB; if (_connectionChangeSubscription == null) { - // _connectionChangeSubscription = ledger.deviceStateChanges - // .listen(_connectionChangeListener); + _connectionChangeSubscription = ledger.deviceStateChanges + .listen(_connectionChangeListener); } _connection = await ledger.connect(device); From 164ae7e3500b09c31056b2712c9a1fd3edb2a34b Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 14:16:47 +0100 Subject: [PATCH 07/13] chore: Remove step to setup kvm, already existing in new setup --- .github/workflows/automated_integration_test.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 2c0c220ce1..b97cfcb43d 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -287,12 +287,6 @@ jobs: with: path: ${{ github.workspace }}/build/app/outputs/flutter-apk name: "android apk" - - - name: 🦾 Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - name: 🦾 Cache gradle uses: gradle/actions/setup-gradle@v3 From 45aed187b019d07a37767752983392ccd14605ce Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 14:27:08 +0100 Subject: [PATCH 08/13] chore: Remove a couple of steps from workflow --- .../workflows/automated_integration_test.yml | 44 ++----------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index b97cfcb43d..e0601b38f2 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -1,7 +1,8 @@ name: Automated Integration Tests -on: [push] - +on: + pull_request: + branches: [main, Integrate-Seed-Verification-Flow-To-Integration-Tests] defaults: run: shell: bash @@ -268,49 +269,10 @@ jobs: set -x apk_file=$(ls build/app/outputs/flutter-apk/test-apk/*_slack.apk || exit 1) echo "APK_FILE=$apk_file" >> $GITHUB_ENV - - - name: Upload artifact to slack - if: ${{ !contains(github.event.head_commit.message, 'skip slack') }} - continue-on-error: true - uses: adrey/slack-file-upload-action@1.0.5 - with: - token: ${{ secrets.SLACK_APP_TOKEN }} - path: ${{ env.APK_FILE }} - channel: ${{ secrets.SLACK_APK_CHANNEL }} - initial_comment: ${{ github.event.head_commit.message }} - name: cleanup run: rm -rf build/app/outputs/flutter-apk/test-apk/ - - name: Upload Artifact to github - uses: actions/upload-artifact@v4 - with: - path: ${{ github.workspace }}/build/app/outputs/flutter-apk - name: "android apk" - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - # arch: ${{ matrix.arch }} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - name: 🚀 Integration tests on Android Emulator uses: reactivecircus/android-emulator-runner@v2 with: From 3ca0334c0fe9488b0e616f5e0c351253da63b1e4 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 14:38:05 +0100 Subject: [PATCH 09/13] chore: Try switching test commands --- integration_test_runner.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/integration_test_runner.sh b/integration_test_runner.sh index 86e28f0b85..3a512ca57c 100755 --- a/integration_test_runner.sh +++ b/integration_test_runner.sh @@ -18,9 +18,7 @@ do rm -rf ~/.local/share/com.example.cake_wallet ~/Documents/cake_wallet fi echo "Running test: $target" - if flutter drive \ - --driver=test_driver/integration_test.dart \ - --target="$target"; then + if flutter test $target; then echo "✅ Test passed: $target" passed_tests+=("$target") else From 5060cbe55e5bc069d5fd6bfca9989366b1e871e5 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 14:55:31 +0100 Subject: [PATCH 10/13] fix: Switch back to flutter drive --- integration_test_runner.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration_test_runner.sh b/integration_test_runner.sh index 3a512ca57c..86e28f0b85 100755 --- a/integration_test_runner.sh +++ b/integration_test_runner.sh @@ -18,7 +18,9 @@ do rm -rf ~/.local/share/com.example.cake_wallet ~/Documents/cake_wallet fi echo "Running test: $target" - if flutter test $target; then + if flutter drive \ + --driver=test_driver/integration_test.dart \ + --target="$target"; then echo "✅ Test passed: $target" passed_tests+=("$target") else From 85f8ebbfef8875a1bc8aaa9453c12b9173fa2965 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 16 Jan 2025 15:51:30 +0100 Subject: [PATCH 11/13] fix: Flutter error when running tests with Flutter drive --- lib/main.dart | 13 ++++++++++++- lib/test_asset_bundles.dart | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 lib/test_asset_bundles.dart diff --git a/lib/main.dart b/lib/main.dart index fd25a1e9ca..aa218e1d5b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,6 +23,7 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/root/root.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/authentication_store.dart'; +import 'package:cake_wallet/test_asset_bundles.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; @@ -80,8 +81,18 @@ Future runAppWithZone({Key? topLevelKey}) async { ledgerFile.writeAsStringSync("$content\n${event.message}"); }); } + // Basically when we're running a test + if (topLevelKey != null) { + runApp( + DefaultAssetBundle( + bundle: TestAssetBundle(), + child: App(key: topLevelKey), + ), + ); + } else { + runApp(App(key: topLevelKey)); + } - runApp(App(key: topLevelKey)); isAppRunning = true; }, (error, stackTrace) async { if (!isAppRunning) { diff --git a/lib/test_asset_bundles.dart b/lib/test_asset_bundles.dart new file mode 100644 index 0000000000..16993a07f0 --- /dev/null +++ b/lib/test_asset_bundles.dart @@ -0,0 +1,15 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; + +class TestAssetBundle extends CachingAssetBundle { + @override + Future loadString(String key, {bool cache = true}) async { + final ByteData data = await load(key); + + return utf8.decode(data.buffer.asUint8List()); + } + + @override + Future load(String key) async => rootBundle.load(key); +} From b0602f86c16fdd71196d16974c025ca8e0b326a0 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 17 Jan 2025 14:19:04 +0100 Subject: [PATCH 12/13] feat: Add screenshots to individual test pages and modify integration test script to add a watch dog to prevent infinite running on CI --- .../workflows/automated_integration_test.yml | 14 +++-- .../components/common_test_cases.dart | 5 ++ integration_test/robots/auth_page_robot.dart | 1 + .../robots/create_pin_welcome_page_robot.dart | 1 + .../robots/dashboard_menu_widget_robot.dart | 1 + .../robots/dashboard_page_robot.dart | 1 + .../robots/disclaimer_page_robot.dart | 1 + .../robots/exchange_confirm_page_robot.dart | 1 + .../robots/exchange_page_robot.dart | 1 + .../robots/exchange_trade_page_robot.dart | 2 + .../robots/new_wallet_page_robot.dart | 1 + .../robots/new_wallet_type_page_robot.dart | 1 + .../robots/pin_code_widget_robot.dart | 1 + .../robots/pre_seed_page_robot.dart | 1 + .../restore_from_seed_or_key_robot.dart | 3 +- .../robots/restore_options_page_robot.dart | 1 + .../security_and_backup_page_robot.dart | 1 + .../robots/seed_verification_page_robot.dart | 1 + integration_test/robots/send_page_robot.dart | 1 + .../robots/setup_pin_code_robot.dart | 1 + .../robots/transactions_page_robot.dart | 1 + .../wallet_group_description_page_robot.dart | 1 + .../robots/wallet_keys_robot.dart | 1 + .../robots/wallet_list_page_robot.dart | 1 + .../robots/wallet_seed_page_robot.dart | 1 + .../robots/welcome_page_robot.dart | 1 + .../test_suites/confirm_seeds_flow_test.dart | 2 - integration_test_runner.sh | 53 ++++++++++++++++--- 28 files changed, 87 insertions(+), 14 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index e0601b38f2..10fa81b473 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -1,8 +1,8 @@ name: Automated Integration Tests -on: - pull_request: - branches: [main, Integrate-Seed-Verification-Flow-To-Integration-Tests] +on: [push] + # pull_request: + # branches: [main, Integrate-Seed-Verification-Flow-To-Integration-Tests] defaults: run: shell: bash @@ -282,4 +282,10 @@ jobs: disable-animations: true script: | chmod a+rx integration_test_runner.sh - ./integration_test_runner.sh \ No newline at end of file + ./integration_test_runner.sh + + - name: Upload test results + uses: actions/upload-artifact@v3 + with: + name: integration-test-results + path: test_outputs/ \ No newline at end of file diff --git a/integration_test/components/common_test_cases.dart b/integration_test/components/common_test_cases.dart index cc1e6d6d71..c7b2f7ab09 100644 --- a/integration_test/components/common_test_cases.dart +++ b/integration_test/components/common_test_cases.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; class CommonTestCases { WidgetTester tester; @@ -171,4 +172,8 @@ class CommonTestCases { Future defaultSleepTime({int seconds = 2}) async => await Future.delayed(Duration(seconds: seconds)); + + Future takeScreenshots(String screenshotName) async { + await (tester.binding as IntegrationTestWidgetsFlutterBinding).takeScreenshot(screenshotName); + } } diff --git a/integration_test/robots/auth_page_robot.dart b/integration_test/robots/auth_page_robot.dart index 2f5c436273..eac028e4bd 100644 --- a/integration_test/robots/auth_page_robot.dart +++ b/integration_test/robots/auth_page_robot.dart @@ -27,6 +27,7 @@ class AuthPageRobot extends PinCodeWidgetRobot { Future isAuthPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('auth_page'); } void hasTitle() { diff --git a/integration_test/robots/create_pin_welcome_page_robot.dart b/integration_test/robots/create_pin_welcome_page_robot.dart index ca136cb382..1178028529 100644 --- a/integration_test/robots/create_pin_welcome_page_robot.dart +++ b/integration_test/robots/create_pin_welcome_page_robot.dart @@ -13,6 +13,7 @@ class CreatePinWelcomePageRobot { Future isCreatePinWelcomePage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('create_pin_welcome_page'); } void hasTitle() { diff --git a/integration_test/robots/dashboard_menu_widget_robot.dart b/integration_test/robots/dashboard_menu_widget_robot.dart index f48033dda7..34c76aa7d8 100644 --- a/integration_test/robots/dashboard_menu_widget_robot.dart +++ b/integration_test/robots/dashboard_menu_widget_robot.dart @@ -10,6 +10,7 @@ class DashboardMenuWidgetRobot { late CommonTestCases commonTestCases; Future hasMenuWidget() async { + await commonTestCases.takeScreenshots('menu_widget_page'); commonTestCases.hasType(); } diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart index 8e058d9b22..0ba5da5025 100644 --- a/integration_test/robots/dashboard_page_robot.dart +++ b/integration_test/robots/dashboard_page_robot.dart @@ -18,6 +18,7 @@ class DashboardPageRobot { Future isDashboardPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('dashboard_page'); } Future confirmWalletTypeIsDisplayedCorrectly( diff --git a/integration_test/robots/disclaimer_page_robot.dart b/integration_test/robots/disclaimer_page_robot.dart index 18861fc294..4b6ca85ced 100644 --- a/integration_test/robots/disclaimer_page_robot.dart +++ b/integration_test/robots/disclaimer_page_robot.dart @@ -12,6 +12,7 @@ class DisclaimerPageRobot { Future isDisclaimerPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('disclaimer_page'); } void hasCheckIcon(bool hasBeenTapped) { diff --git a/integration_test/robots/exchange_confirm_page_robot.dart b/integration_test/robots/exchange_confirm_page_robot.dart index 160fd9dfb6..e719793936 100644 --- a/integration_test/robots/exchange_confirm_page_robot.dart +++ b/integration_test/robots/exchange_confirm_page_robot.dart @@ -13,6 +13,7 @@ class ExchangeConfirmPageRobot { Future isExchangeConfirmPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('exchange_confirm_page'); } void confirmComponentsOfTradeDisplayProperly() { diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index a3378e2934..b919ba7c15 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -15,6 +15,7 @@ class ExchangePageRobot { Future isExchangePage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('exchange_page'); await commonTestCases.defaultSleepTime(); } diff --git a/integration_test/robots/exchange_trade_page_robot.dart b/integration_test/robots/exchange_trade_page_robot.dart index 5708b6faee..669d4eaf75 100644 --- a/integration_test/robots/exchange_trade_page_robot.dart +++ b/integration_test/robots/exchange_trade_page_robot.dart @@ -16,6 +16,8 @@ class ExchangeTradePageRobot { Future isExchangeTradePage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('exchange_trade_page'); + } void hasInformationDialog() { diff --git a/integration_test/robots/new_wallet_page_robot.dart b/integration_test/robots/new_wallet_page_robot.dart index f8deb00ae8..dda4683983 100644 --- a/integration_test/robots/new_wallet_page_robot.dart +++ b/integration_test/robots/new_wallet_page_robot.dart @@ -11,6 +11,7 @@ class NewWalletPageRobot { Future isNewWalletPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('new_wallet_page'); } Future enterWalletName(String walletName) async { diff --git a/integration_test/robots/new_wallet_type_page_robot.dart b/integration_test/robots/new_wallet_type_page_robot.dart index 89fc8d3901..a0a8636568 100644 --- a/integration_test/robots/new_wallet_type_page_robot.dart +++ b/integration_test/robots/new_wallet_type_page_robot.dart @@ -15,6 +15,7 @@ class NewWalletTypePageRobot { Future isNewWalletTypePage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('new_wallet_type_page'); } void displaysCorrectTitle(bool isCreate) { diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart index 62e606703d..35b9e200e3 100644 --- a/integration_test/robots/pin_code_widget_robot.dart +++ b/integration_test/robots/pin_code_widget_robot.dart @@ -46,6 +46,7 @@ class PinCodeWidgetRobot { ); } + await commonTestCases.takeScreenshots('pin_code_widget'); await commonTestCases.defaultSleepTime(); } } diff --git a/integration_test/robots/pre_seed_page_robot.dart b/integration_test/robots/pre_seed_page_robot.dart index 01be1249cb..6f46e3ac14 100644 --- a/integration_test/robots/pre_seed_page_robot.dart +++ b/integration_test/robots/pre_seed_page_robot.dart @@ -11,6 +11,7 @@ class PreSeedPageRobot { Future isPreSeedPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('pre_seed_page'); } Future onConfirmButtonPressed() async { diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 015a9e46ff..17197116a4 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -1,9 +1,7 @@ import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; -import 'package:cake_wallet/src/widgets/seed_widget.dart'; import 'package:cake_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'; @@ -16,6 +14,7 @@ class RestoreFromSeedOrKeysPageRobot { Future isRestoreFromSeedKeyPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('wallet_restore_page'); } Future confirmViewComponentsDisplayProperlyPerPageView() async { diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart index cd19196091..c269d73431 100644 --- a/integration_test/robots/restore_options_page_robot.dart +++ b/integration_test/robots/restore_options_page_robot.dart @@ -11,6 +11,7 @@ class RestoreOptionsPageRobot { Future isRestoreOptionsPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('restore_options'); } void hasRestoreOptionsButton() { diff --git a/integration_test/robots/security_and_backup_page_robot.dart b/integration_test/robots/security_and_backup_page_robot.dart index eb7c1bc876..f96603a991 100644 --- a/integration_test/robots/security_and_backup_page_robot.dart +++ b/integration_test/robots/security_and_backup_page_robot.dart @@ -12,6 +12,7 @@ class SecurityAndBackupPageRobot { Future isSecurityAndBackupPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('security_backup_page'); } void hasTitle() { diff --git a/integration_test/robots/seed_verification_page_robot.dart b/integration_test/robots/seed_verification_page_robot.dart index 2d64606574..890611e663 100644 --- a/integration_test/robots/seed_verification_page_robot.dart +++ b/integration_test/robots/seed_verification_page_robot.dart @@ -12,6 +12,7 @@ class SeedVerificationPageRobot { Future isSeedVerificationPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('seed_verification_page'); } void hasTitle() { diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index b705c803f0..58535a6884 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -24,6 +24,7 @@ class SendPageRobot { Future isSendPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('send_page'); } void hasTitle() { diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart index 0888aac306..aa6dde0708 100644 --- a/integration_test/robots/setup_pin_code_robot.dart +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -15,6 +15,7 @@ class SetupPinCodeRobot extends PinCodeWidgetRobot { Future isSetupPinCodePage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('setup_pin_code_page'); } void hasTitle() { diff --git a/integration_test/robots/transactions_page_robot.dart b/integration_test/robots/transactions_page_robot.dart index 40a49928ff..1856135cbc 100644 --- a/integration_test/robots/transactions_page_robot.dart +++ b/integration_test/robots/transactions_page_robot.dart @@ -27,6 +27,7 @@ class TransactionsPageRobot { Future isTransactionsPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('transactions_page'); } Future confirmTransactionsPageConstantsDisplayProperly() async { diff --git a/integration_test/robots/wallet_group_description_page_robot.dart b/integration_test/robots/wallet_group_description_page_robot.dart index 57500dc3c3..364397f2a2 100644 --- a/integration_test/robots/wallet_group_description_page_robot.dart +++ b/integration_test/robots/wallet_group_description_page_robot.dart @@ -12,6 +12,7 @@ class WalletGroupDescriptionPageRobot { Future isWalletGroupDescriptionPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('wallet_group_description_page'); } void hasTitle() { diff --git a/integration_test/robots/wallet_keys_robot.dart b/integration_test/robots/wallet_keys_robot.dart index 189929737e..85982b5c39 100644 --- a/integration_test/robots/wallet_keys_robot.dart +++ b/integration_test/robots/wallet_keys_robot.dart @@ -19,6 +19,7 @@ class WalletKeysAndSeedPageRobot { Future isWalletKeysAndSeedPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('wallet_keys_page'); } void hasTitle() { diff --git a/integration_test/robots/wallet_list_page_robot.dart b/integration_test/robots/wallet_list_page_robot.dart index b46d4ca954..b84ae49ccd 100644 --- a/integration_test/robots/wallet_list_page_robot.dart +++ b/integration_test/robots/wallet_list_page_robot.dart @@ -11,6 +11,7 @@ class WalletListPageRobot { Future isWalletListPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('wallet_list_page'); } void displaysCorrectTitle() { diff --git a/integration_test/robots/wallet_seed_page_robot.dart b/integration_test/robots/wallet_seed_page_robot.dart index 8fb4262d54..7bcbe6afe7 100644 --- a/integration_test/robots/wallet_seed_page_robot.dart +++ b/integration_test/robots/wallet_seed_page_robot.dart @@ -12,6 +12,7 @@ class WalletSeedPageRobot { Future isWalletSeedPage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('wallet_seed_page'); } Future onVerifySeedButtonPressed() async { diff --git a/integration_test/robots/welcome_page_robot.dart b/integration_test/robots/welcome_page_robot.dart index 510f63556e..337ab56b33 100644 --- a/integration_test/robots/welcome_page_robot.dart +++ b/integration_test/robots/welcome_page_robot.dart @@ -12,6 +12,7 @@ class WelcomePageRobot { Future isWelcomePage() async { await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('welcome_page'); } void confirmActionButtonsDisplay() { diff --git a/integration_test/test_suites/confirm_seeds_flow_test.dart b/integration_test/test_suites/confirm_seeds_flow_test.dart index a62ce3f604..6716c8055d 100644 --- a/integration_test/test_suites/confirm_seeds_flow_test.dart +++ b/integration_test/test_suites/confirm_seeds_flow_test.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/wallet_type.dart'; @@ -7,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import '../components/common_test_cases.dart'; import '../components/common_test_constants.dart'; import '../components/common_test_flows.dart'; import '../robots/auth_page_robot.dart'; diff --git a/integration_test_runner.sh b/integration_test_runner.sh index 86e28f0b85..480b38938c 100755 --- a/integration_test_runner.sh +++ b/integration_test_runner.sh @@ -5,6 +5,36 @@ declare -a targets declare -a passed_tests declare -a failed_tests +# Max inactivity duration in seconds before marking the test as failed +MAX_INACTIVITY=180 # Adjust as needed (e.g., 300 seconds = 5 minutes) + +# Function to monitor test output and kill the process if inactive +monitor_test() { + local test_pid=$1 + local log_file=$2 + local start_time=$(date +%s) + + while true; do + sleep 10 + + # Check if the process is still running + if ! kill -0 $test_pid 2>/dev/null; then + break + fi + + # Check for log activity + local last_modified=$(stat -c %Y "$log_file") + local current_time=$(date +%s) + if (( current_time - last_modified > MAX_INACTIVITY )); then + echo "❌ Test hung due to inactivity, terminating..." + kill -9 $test_pid + return 1 + fi + done + + return 0 +} + # Collect all Dart test files in the integration_test directory while IFS= read -r -d $'\0' file; do targets+=("$file") @@ -13,20 +43,31 @@ done < <(find integration_test/test_suites -name "*.dart" -type f -print0) # Run each test and collect results for target in "${targets[@]}" do - if [[ "x$REMOVE_DATA_DIRECTORY" == "xY" ]]; - then + if [[ "x$REMOVE_DATA_DIRECTORY" == "xY" ]]; then rm -rf ~/.local/share/com.example.cake_wallet ~/Documents/cake_wallet fi echo "Running test: $target" - if flutter drive \ - --driver=test_driver/integration_test.dart \ - --target="$target"; then + + # Temporary log file to track activity + log_file=$(mktemp) + + # Run the test in the background and log output + flutter drive \ + --driver=test_driver/integration_test.dart \ + --target="$target" >"$log_file" 2>&1 & + test_pid=$! + + # Monitor the test for inactivity + if monitor_test $test_pid "$log_file"; then echo "✅ Test passed: $target" passed_tests+=("$target") else - echo "❌ Test failed: $target" + echo "❌ Test failed or hung: $target" failed_tests+=("$target") fi + + # Clean up log file + rm -f "$log_file" done # Provide a summary of test results From 17285bdc6618eede030e76271d8eb2b9dd0c26a6 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 17 Jan 2025 16:38:19 +0100 Subject: [PATCH 13/13] feat: Implement transaction success info robot and fix issue with send flow test --- .../components/common_test_constants.dart | 4 +- integration_test/robots/send_page_robot.dart | 55 ++++++++++++++++--- .../transaction_success_info_robot.dart | 21 +++++++ .../test_suites/send_flow_test.dart | 10 +++- 4 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 integration_test/robots/transaction_success_info_robot.dart diff --git a/integration_test/components/common_test_constants.dart b/integration_test/components/common_test_constants.dart index 6ace69b45c..0609ecba94 100644 --- a/integration_test/components/common_test_constants.dart +++ b/integration_test/components/common_test_constants.dart @@ -7,7 +7,7 @@ class CommonTestConstants { static final String exchangeTestAmount = '0.01'; static final WalletType testWalletType = WalletType.solana; static final String testWalletName = 'Integrated Testing Wallet'; - static final CryptoCurrency testReceiveCurrency = CryptoCurrency.usdtSol; - static final CryptoCurrency testDepositCurrency = CryptoCurrency.sol; + static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; + static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm'; } diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index 58535a6884..c1fda19e85 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -128,6 +128,21 @@ class SendPageRobot { Future onSendButtonPressed() async { tester.printToConsole('Pressing send'); + await tester.pumpAndSettle(); + final sendPage = tester.widget(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, @@ -150,6 +165,8 @@ class SendPageRobot { await _handleAuthPage(); + await commonTestCases.defaultSleepTime(); + tester.printToConsole('After _handleAuth'); await tester.pump(); @@ -184,15 +201,39 @@ class SendPageRobot { } Future _handleAuthPage() async { - final onAuthPage = authPageRobot.onAuthPage(); - if (onAuthPage) { - await authPageRobot.enterPinCode(CommonTestConstants.pin); - } + tester.printToConsole('Inside _handleAuth'); - final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); - if (onAuthPageDesktop) { + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); + 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.pumpAndSettle(); + + tester.printToConsole('Auth pump done'); + } catch (e) { + tester.printToConsole('Auth failed, retrying'); + await tester.pump(); + _handleAuthPage(); + } } + await tester.pump(); } Future handleSendResult() async { @@ -329,7 +370,7 @@ class SendPageRobot { } //* ---- Add Contact Dialog On Send Successful Dialog ----- - Future onSentDialogPopUp() async { + Future onAddContactDialogPopUp() async { SendPage sendPage = tester.widget(find.byType(SendPage)); final sendViewModel = sendPage.sendViewModel; diff --git a/integration_test/robots/transaction_success_info_robot.dart b/integration_test/robots/transaction_success_info_robot.dart new file mode 100644 index 0000000000..4be484ac55 --- /dev/null +++ b/integration_test/robots/transaction_success_info_robot.dart @@ -0,0 +1,21 @@ +import 'package:cake_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 isTransactionSuccessInfoPage() async { + await commonTestCases.isSpecificPage(); + await commonTestCases.takeScreenshots('transaction_success_info_page'); + } + + Future onConfirmButtonPressed() async { + await commonTestCases.tapItemByKey('transaction_success_info_page_button_key'); + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/test_suites/send_flow_test.dart b/integration_test/test_suites/send_flow_test.dart index 7a46435b85..43a2dcd510 100644 --- a/integration_test/test_suites/send_flow_test.dart +++ b/integration_test/test_suites/send_flow_test.dart @@ -8,17 +8,21 @@ import '../robots/dashboard_page_robot.dart'; import '../robots/send_page_robot.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; +import '../robots/transaction_success_info_robot.dart'; + void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); SendPageRobot sendPageRobot; CommonTestFlows commonTestFlows; DashboardPageRobot dashboardPageRobot; + TransactionSuccessInfoRobot transactionSuccessInfoRobot; testWidgets('Send flow', (tester) async { commonTestFlows = CommonTestFlows(tester); sendPageRobot = SendPageRobot(tester: tester); dashboardPageRobot = DashboardPageRobot(tester); + transactionSuccessInfoRobot = TransactionSuccessInfoRobot(tester); await commonTestFlows.startAppFlow(ValueKey('send_test_app_key')); await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( @@ -39,6 +43,10 @@ void main() { await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed(); - await sendPageRobot.onSentDialogPopUp(); + await transactionSuccessInfoRobot.isTransactionSuccessInfoPage(); + + await transactionSuccessInfoRobot.onConfirmButtonPressed(); + + await sendPageRobot.onAddContactDialogPopUp(); }); }