From 3ad04227a48f7d1d52970e2d20eb0be651fa8c34 Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Fri, 13 Dec 2024 20:45:41 +0100 Subject: [PATCH] test: Attempting automation for testing (#1734) * 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 * test: Restore wallets integration automated tests * Fix: Add keys back to currency amount textfield widget * fix: Switch variable name * fix: remove automation for now * tests: Automated tests for Create wallets flow * tests: Further optimize common flows * tests: Add missing await for call * tests: Confirm Seeds Display Properly WIP * tests: Confirm Seeds Display Correctly Automated Tests * fix: Add missing pubspec params for bitcoin and bitcoin_cash * feat: Automated Tests for Transaction History Flow * fix: Add missing pubspec parameter * feat: Automated Integration Tests for Transaction History flow * test: Updating send page robot and also syncing branch with main * test: Modifying tests to flow with wallet grouping implementation * fix: Issue with transaction history test * fix: Modifications to the PR and add automated confirmation for checking that all wallet types are restored or created correctly * test: Attempting automation for testing * test: Attempting automation for testing * test: Print out working directory * test: See if I can cut down time by removing the build step * test: More logs * test: Pubspec was not generated, checking if this fixes it * test: Pubspec was not generated, checking if this fixes it * test: Pubspec was not generated, checking if this fixes it * test: Pubspec was not generated, checking if this fixes it * test: Pubspec was not generated, checking if this fixes it * test: Pubspec was not generated, checking if this fixes it * test: Another trial * test: Another trial * test: Another trial * test: Another trial * test: Another trial * test: Another trial * fix: Adjust config file * test: Add commands to generate files and set codebase up as new * test: try another route * test: try another route - 2 * test: try another route * test: try another route - 2 * test: Uncomment KVM and optimizations-a * test: Try with sudo permissions-a * test: Try again * test: Pause build and rename steps, see how faster it resolves * test: Try using working directory * test: Check details of current working directory * test: Switch test run command from flutter drive to flutter test * test: Adding secrets to CI workflow * fix: add working directory to emulator and reactivate build step * test: Add verbosity * test: Check tat emulator is present and ready to connect * test: Try a direct test to see if it'll trigger properly * test: Try the flutter drive command * test: Try uninstalling before running * test: Create an aggregator test file as the entry point for all tests * test: Try without awaiting each test * test: Another trial at getting combined tests running * test: Use a test runner script that'll be responsible for running all available integration tests * test: Add command to make integration test runner file an executable * test: Fix failing exchange flow test * test: fix failing exchange flow test * test: Fix issue with send flow test * test: Fix issue with confirm seeds flow test * test: Modify create and restore flows to reflect modified onboarding flow * chore: Remove package declaration in AndroidManifestBase file to fix issue of it being deprecated * test: Bump up flutter version * fix: Add meld keys * chore: Remove package name declarations from AndroidManifests * better write close function definition comment integration tests workflow for now --------- Co-authored-by: OmarHatem --- .../workflows/automated_integration_test.yml | 298 ++++++++++++++++++ .github/workflows/pr_test_build_android.yml | 2 +- cw_bitcoin/lib/electrum_wallet.dart | 2 +- cw_bitcoin/lib/litecoin_wallet.dart | 2 +- cw_core/lib/wallet_base.dart | 2 +- cw_evm/lib/evm_chain_wallet.dart | 2 +- cw_haven/lib/haven_wallet.dart | 2 +- cw_monero/lib/monero_wallet.dart | 2 +- cw_nano/lib/nano_wallet.dart | 2 +- cw_solana/lib/solana_wallet.dart | 2 +- cw_tron/lib/tron_wallet.dart | 2 +- cw_wownero/lib/wownero_wallet.dart | 2 +- .../components/common_test_constants.dart | 6 +- .../components/common_test_flows.dart | 13 +- .../robots/create_pin_welcome_page_robot.dart | 53 ++++ .../robots/exchange_page_robot.dart | 14 +- integration_test/robots/send_page_robot.dart | 11 +- .../robots/wallet_keys_robot.dart | 18 +- .../test_suites/confirm_seeds_flow_test.dart | 2 +- integration_test_runner.sh | 45 +++ .../screens/wallet_keys/wallet_keys_page.dart | 1 + lib/src/widgets/search_bar_widget.dart | 1 + 22 files changed, 446 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/automated_integration_test.yml create mode 100644 integration_test/robots/create_pin_welcome_page_robot.dart create mode 100755 integration_test_runner.sh diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml new file mode 100644 index 000000000..588bc1821 --- /dev/null +++ b/.github/workflows/automated_integration_test.yml @@ -0,0 +1,298 @@ +#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" +# +#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 }} +# +# steps: +# - name: is pr +# if: github.event_name == 'pull_request' +# run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV +# +# - name: is not pr +# if: github.event_name != 'pull_request' +# run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV +# +# - 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 +# +# - 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: 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: 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: 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') }} +# +# - 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: Install Flutter dependencies +# run: | +# cd /opt/android/cake_wallet +# flutter pub get +# +# +# - 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: 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 +# +# - 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: 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: Generate localization +# run: | +# cd /opt/android/cake_wallet +# flutter packages pub run tool/generate_localization.dart +# +# - name: Build generated code +# run: | +# cd /opt/android/cake_wallet +# ./model_generator.sh +# +# - 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 payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const ankrApiKey = '${{ secrets.ANKR_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 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 \ No newline at end of file diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index ab198dfb2..d98c0b77b 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -207,7 +207,7 @@ jobs: 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.dar + 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 diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index d4f0e4adc..a5bb9c655 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1346,7 +1346,7 @@ abstract class ElectrumWalletBase } @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { try { await _receiveStream?.cancel(); await electrumClient.close(); diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index e55516e9a..f7cc20bcd 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -1191,7 +1191,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { _utxoStream?.cancel(); _feeRatesTimer?.cancel(); _syncTimer?.cancel(); diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 112a20852..16c794a25 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -83,7 +83,7 @@ abstract class WalletBase rescan({required int height}); - Future close({required bool shouldCleanup}); + Future close({bool shouldCleanup = false}); Future changePassword(String password); diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index eb93bd94f..dca16539c 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -270,7 +270,7 @@ abstract class EVMChainWalletBase } @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { _client.stop(); _transactionsUpdateTimer?.cancel(); _updateFeesTimer?.cancel(); diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart index 734a6da9c..6c372d344 100644 --- a/cw_haven/lib/haven_wallet.dart +++ b/cw_haven/lib/haven_wallet.dart @@ -108,7 +108,7 @@ abstract class HavenWalletBase Future? updateBalance() => null; @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { _listener?.stop(); _onAccountChangeReaction?.reaction.dispose(); _autoSaveTimer?.cancel(); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 83d9504ad..21d5b6d4b 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -175,7 +175,7 @@ abstract class MoneroWalletBase extends WalletBase? updateBalance() => null; @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { _listener?.stop(); _onAccountChangeReaction?.reaction.dispose(); _onTxHistoryChangeReaction?.reaction.dispose(); diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index f1d66a4a8..b48335857 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -150,7 +150,7 @@ abstract class NanoWalletBase Future changePassword(String password) => throw UnimplementedError("changePassword"); @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { _client.stop(); _receiveTimer?.cancel(); } diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 3a2d281e3..c884d8e82 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -180,7 +180,7 @@ abstract class SolanaWalletBase Future changePassword(String password) => throw UnimplementedError("changePassword"); @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { _client.stop(); _transactionsUpdateTimer?.cancel(); } diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index 3cd8bfc99..cfa80f0d3 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -217,7 +217,7 @@ abstract class TronWalletBase Future changePassword(String password) => throw UnimplementedError("changePassword"); @override - Future close({required bool shouldCleanup}) async => _transactionsUpdateTimer?.cancel(); + Future close({bool shouldCleanup = false}) async => _transactionsUpdateTimer?.cancel(); @action @override diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index db0fe7bd8..c4c79af11 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -162,7 +162,7 @@ abstract class WowneroWalletBase Future? updateBalance() => null; @override - Future close({required bool shouldCleanup}) async { + Future close({bool shouldCleanup = false}) async { _listener?.stop(); _onAccountChangeReaction?.reaction.dispose(); _onTxHistoryChangeReaction?.reaction.dispose(); diff --git a/integration_test/components/common_test_constants.dart b/integration_test/components/common_test_constants.dart index 302d52189..6ace69b45 100644 --- a/integration_test/components/common_test_constants.dart +++ b/integration_test/components/common_test_constants.dart @@ -4,10 +4,10 @@ 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 String exchangeTestAmount = '0.01'; 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 CryptoCurrency testReceiveCurrency = CryptoCurrency.usdtSol; + static final CryptoCurrency testDepositCurrency = CryptoCurrency.sol; static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm'; } diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index 82f714da0..8350b5859 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:cake_wallet/main.dart' as app; +import '../robots/create_pin_welcome_page_robot.dart'; import '../robots/dashboard_page_robot.dart'; import '../robots/disclaimer_page_robot.dart'; import '../robots/new_wallet_page_robot.dart'; @@ -37,6 +38,7 @@ class CommonTestFlows { _walletListPageRobot = WalletListPageRobot(_tester), _newWalletTypePageRobot = NewWalletTypePageRobot(_tester), _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester), + _createPinWelcomePageRobot = CreatePinWelcomePageRobot(_tester), _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester), _walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester); @@ -53,6 +55,7 @@ class CommonTestFlows { final WalletListPageRobot _walletListPageRobot; final NewWalletTypePageRobot _newWalletTypePageRobot; final RestoreOptionsPageRobot _restoreOptionsPageRobot; + final CreatePinWelcomePageRobot _createPinWelcomePageRobot; final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot; final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot; @@ -190,10 +193,12 @@ class CommonTestFlows { WalletType walletTypeToCreate, List pin, ) async { - await _welcomePageRobot.navigateToCreateNewWalletPage(); + await _createPinWelcomePageRobot.tapSetAPinButton(); await setupPinCodeForWallet(pin); + await _welcomePageRobot.navigateToCreateNewWalletPage(); + await _selectWalletTypeForWallet(walletTypeToCreate); } @@ -201,12 +206,14 @@ class CommonTestFlows { WalletType walletTypeToRestore, List pin, ) async { + await _createPinWelcomePageRobot.tapSetAPinButton(); + + await setupPinCodeForWallet(pin); + await _welcomePageRobot.navigateToRestoreWalletPage(); await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); - await setupPinCodeForWallet(pin); - await _selectWalletTypeForWallet(walletTypeToRestore); } diff --git a/integration_test/robots/create_pin_welcome_page_robot.dart b/integration_test/robots/create_pin_welcome_page_robot.dart new file mode 100644 index 000000000..ca136cb38 --- /dev/null +++ b/integration_test/robots/create_pin_welcome_page_robot.dart @@ -0,0 +1,53 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/welcome/create_pin_welcome_page.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class CreatePinWelcomePageRobot { + CreatePinWelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isCreatePinWelcomePage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + String title; + if (isMoneroOnly) { + title = S.current.monero_com; + } + + if (isHaven) { + title = S.current.haven_app; + } + + title = S.current.cake_wallet; + + commonTestCases.hasText(title); + } + + void hasDescription() { + String description; + if (isMoneroOnly) { + description = S.current.monero_com_wallet_text; + } + + if (isHaven) { + description = S.current.haven_app_wallet_text; + } + + description = S.current.new_first_wallet_text; + + commonTestCases.hasText(description); + } + + Future tapSetAPinButton() async { + await commonTestCases.tapItemByKey('create_pin_welcome_page_create_a_pin_button_key'); + + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index e01b2df9c..a3378e293 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -123,10 +123,8 @@ class ExchangePageRobot { return; } - await commonTestCases.dragUntilVisible( - 'picker_items_index_${depositCurrency.name}_button_key', - 'picker_scrollbar_key', - ); + await commonTestCases.enterText(depositCurrency.name, 'search_bar_widget_key'); + await commonTestCases.defaultSleepTime(); await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key'); @@ -149,10 +147,8 @@ class ExchangePageRobot { return; } - await commonTestCases.dragUntilVisible( - 'picker_items_index_${receiveCurrency.name}_button_key', - 'picker_scrollbar_key', - ); + await commonTestCases.enterText(receiveCurrency.name, 'search_bar_widget_key'); + await commonTestCases.defaultSleepTime(); await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key'); @@ -318,7 +314,7 @@ class ExchangePageRobot { Future handleErrors(String initialAmount) async { await tester.pumpAndSettle(); - + await _handleMinLimitError(initialAmount); await _handleMaxLimitError(initialAmount); diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index 20cef948d..f8e1a49ad 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -84,13 +84,11 @@ class SendPageRobot { return; } - await commonTestCases.dragUntilVisible( - 'picker_items_index_${receiveCurrency.name}_button_key', - 'picker_scrollbar_key', - ); + await commonTestCases.enterText(receiveCurrency.title, 'search_bar_widget_key'); + await commonTestCases.defaultSleepTime(); - await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key'); + await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.fullName}_button_key'); } Future enterReceiveAddress(String receiveAddress) async { @@ -210,6 +208,7 @@ class SendPageRobot { _handleAuthPage(); } } + await tester.pump(); } Future handleSendResult() async { @@ -366,4 +365,4 @@ class SendPageRobot { Future _onIgnoreButtonOnSentDialogPressed() async { await commonTestCases.tapItemByKey('send_page_sent_dialog_ignore_button_key'); } -} \ No newline at end of file +} diff --git a/integration_test/robots/wallet_keys_robot.dart b/integration_test/robots/wallet_keys_robot.dart index f6aeb3a66..189929737 100644 --- a/integration_test/robots/wallet_keys_robot.dart +++ b/integration_test/robots/wallet_keys_robot.dart @@ -42,11 +42,11 @@ class WalletKeysAndSeedPageRobot { bool hasPrivateKey = appStore.wallet!.privateKey != null; if (walletType == WalletType.monero) { - final moneroWallet = appStore.wallet as MoneroWallet; + final moneroWallet = appStore.wallet as MoneroWalletBase; final lang = PolyseedLang.getByPhrase(moneroWallet.seed); final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish); - _confirmMoneroWalletCredentials( + await _confirmMoneroWalletCredentials( appStore, walletName, moneroWallet.seed, @@ -59,7 +59,7 @@ class WalletKeysAndSeedPageRobot { final lang = PolyseedLang.getByPhrase(wowneroWallet.seed); final legacySeed = wowneroWallet.seedLegacy(lang.nameEnglish); - _confirmMoneroWalletCredentials( + await _confirmMoneroWalletCredentials( appStore, walletName, wowneroWallet.seed, @@ -105,12 +105,12 @@ class WalletKeysAndSeedPageRobot { await commonTestCases.defaultSleepTime(seconds: 5); } - void _confirmMoneroWalletCredentials( + Future _confirmMoneroWalletCredentials( AppStore appStore, String walletName, String seed, String legacySeed, - ) { + ) async { final keys = appStore.wallet!.keys as MoneroWalletKeys; final hasPublicSpendKey = commonTestCases.isKeyPresent( @@ -145,10 +145,18 @@ class WalletKeysAndSeedPageRobot { tester.printToConsole('$walletName wallet has private view key properly displayed'); } if (hasSeeds) { + await commonTestCases.dragUntilVisible( + '${walletName}_wallet_seed_item_key', + 'wallet_keys_page_credentials_list_view_key', + ); commonTestCases.hasText(seed); tester.printToConsole('$walletName wallet has seeds properly displayed'); } if (hasSeedLegacy) { + await commonTestCases.dragUntilVisible( + '${walletName}_wallet_seed_legacy_item_key', + 'wallet_keys_page_credentials_list_view_key', + ); commonTestCases.hasText(legacySeed); tester.printToConsole('$walletName wallet has legacy seeds properly displayed'); } diff --git a/integration_test/test_suites/confirm_seeds_flow_test.dart b/integration_test/test_suites/confirm_seeds_flow_test.dart index bf6fd5a5f..2d11a2cc4 100644 --- a/integration_test/test_suites/confirm_seeds_flow_test.dart +++ b/integration_test/test_suites/confirm_seeds_flow_test.dart @@ -101,7 +101,7 @@ Future _confirmSeedsFlowForWalletType( walletKeysAndSeedPageRobot.hasTitle(); walletKeysAndSeedPageRobot.hasShareWarning(); - walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType); + await walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType); await walletKeysAndSeedPageRobot.backToDashboard(); } diff --git a/integration_test_runner.sh b/integration_test_runner.sh new file mode 100755 index 000000000..34c9227c0 --- /dev/null +++ b/integration_test_runner.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +declare -a targets +declare -a passed_tests +declare -a failed_tests + +# Collect all Dart test files in the integration_test directory +while IFS= read -r -d $'\0' file; do + targets+=("$file") +done < <(find integration_test/test_suites -name "*.dart" -type f -print0) + +# Run each test and collect results +for target in "${targets[@]}" +do + echo "Running test: $target" + if flutter drive \ + --driver=test_driver/integration_test.dart \ + --target="$target"; then + echo "āœ… Test passed: $target" + passed_tests+=("$target") + else + echo "āŒ Test failed: $target" + failed_tests+=("$target") + fi +done + +# Provide a summary of test results +echo -e "\n===== Test Summary =====" +if [ ${#passed_tests[@]} -gt 0 ]; then + echo "āœ… Passed Tests:" + for test in "${passed_tests[@]}"; do + echo " - $test" + done +fi + +if [ ${#failed_tests[@]} -gt 0 ]; then + echo -e "\nāŒ Failed Tests:" + for test in "${failed_tests[@]}"; do + echo " - $test" + done + # Exit with a non-zero status to indicate failure + exit 1 +else + echo -e "\nšŸŽ‰ All tests passed successfully!" +fi diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index fac760516..ac00bb161 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -84,6 +84,7 @@ class WalletKeysPage extends BasePage { child: Observer( builder: (_) { return ListView.separated( + key: ValueKey('wallet_keys_page_credentials_list_view_key'), separatorBuilder: (context, index) => Container( height: 1, padding: EdgeInsets.only(left: 24), diff --git a/lib/src/widgets/search_bar_widget.dart b/lib/src/widgets/search_bar_widget.dart index e67c793cc..34ebe8560 100644 --- a/lib/src/widgets/search_bar_widget.dart +++ b/lib/src/widgets/search_bar_widget.dart @@ -17,6 +17,7 @@ class SearchBarWidget extends StatelessWidget { @override Widget build(BuildContext context) { return TextFormField( + key: ValueKey('search_bar_widget_key'), controller: searchController, style: TextStyle(color: Theme.of(context).extension()!.searchHintColor), decoration: InputDecoration(