diff --git a/.env.test b/.env.test index 81f7b325f5c..958ac81ad8d 100644 --- a/.env.test +++ b/.env.test @@ -1,4 +1,4 @@ -DEFAULT_TESTNET=alfajores +DEFAULT_TESTNET=mainnet SMS_RETRIEVER_APP_SIGNATURE=TODO DEV_SETTINGS_ACTIVE_INITIALLY=true # Disable firebase b.c. google-services.json files are missing in CI diff --git a/.github/workflows/e2e-android.yml b/.github/workflows/e2e-android.yml index ab9b0d72408..9357e7961c8 100644 --- a/.github/workflows/e2e-android.yml +++ b/.github/workflows/e2e-android.yml @@ -8,6 +8,11 @@ on: jobs: android: + env: + # `if` conditions can't directly access secrets, so we use a workaround + # See https://docs.github.com/en/actions/security-guides/encrypted-secrets#using-encrypted-secrets-in-a-workflow + SECRETS_AVAILABLE: ${{ secrets.SECRETS_AVAILABLE }} + BASH_ENV: ~/.profile name: Android (SDK ${{ inputs.android-api-level }}) runs-on: - nscloud-ubuntu-22.04-amd64-16x64-with-cache @@ -17,6 +22,25 @@ jobs: # If it takes longer it usually fails, so no need to pay for more timeout-minutes: 25 steps: + - uses: google-github-actions/auth@v2 + if: ${{ env.SECRETS_AVAILABLE }} + with: + project_id: celo-mobile-mainnet + credentials_json: ${{ secrets.MAINNET_SERVICE_ACCOUNT_KEY }} + - name: Google Secrets + if: ${{ env.SECRETS_AVAILABLE }} + id: google-secrets + uses: google-github-actions/get-secretmanager-secrets@v2.1.4 + with: + secrets: |- + E2E_WALLET_CONNECT_PROJECT_ID:projects/1027349420744/secrets/E2E_WALLET_CONNECT_PROJECT_ID + E2E_DEFAULT_RECIPIENT_MNEMONIC:projects/1027349420744/secrets/E2E_DEFAULT_RECIPIENT_MNEMONIC + E2E_DEFAULT_RECIPIENT_PRIVATE_KEY:projects/1027349420744/secrets/E2E_DEFAULT_RECIPIENT_PRIVATE_KEY + E2E_WALLET_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_MNEMONIC + E2E_WALLET_PRIVATE_KEY:projects/1027349420744/secrets/E2E_WALLET_PRIVATE_KEY + E2E_WALLET_SINGLE_VERIFIED_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_SINGLE_VERIFIED_MNEMONIC + E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC + E2E_WALLET_12_WORDS_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_12_WORDS_MNEMONIC - uses: actions/checkout@v4 - name: Set env run: | @@ -74,9 +98,6 @@ jobs: run: yarn build:ts - name: Check E2E wallet balance run: NODE_OPTIONS='--unhandled-rejections=strict' yarn ts-node ./e2e/scripts/check-e2e-wallet-balance.ts - - name: Create Android E2E .env File - working-directory: e2e - run: echo WALLET_CONNECT_PROJECT_ID_E2E=${{ secrets.WALLET_CONNECT_PROJECT_ID_E2E }} >> .env - name: Create Detox Build run: CELO_TEST_CONFIG=e2e yarn detox build -c android.release - name: Run Detox @@ -94,6 +115,15 @@ jobs: --headless --retries 3 --device-boot-args="-snapshot ci_boot" + env: + E2E_WALLET_CONNECT_PROJECT_ID: ${{ steps.google-secrets.outputs.E2E_WALLET_CONNECT_PROJECT_ID }} + E2E_DEFAULT_RECIPIENT_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_DEFAULT_RECIPIENT_MNEMONIC }} + E2E_DEFAULT_RECIPIENT_PRIVATE_KEY: ${{ steps.google-secrets.outputs.E2E_DEFAULT_RECIPIENT_PRIVATE_KEY }} + E2E_WALLET_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_MNEMONIC }} + E2E_WALLET_PRIVATE_KEY: ${{ steps.google-secrets.outputs.E2E_WALLET_PRIVATE_KEY }} + E2E_WALLET_SINGLE_VERIFIED_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_SINGLE_VERIFIED_MNEMONIC }} + E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC }} + E2E_WALLET_12_WORDS_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_12_WORDS_MNEMONIC }} - name: Publish Android JUnit Report if: always() uses: mikepenz/action-junit-report@v4 diff --git a/.github/workflows/e2e-faucet-balance.yml b/.github/workflows/e2e-faucet-balance.yml index 2a9d463fd27..31c04599950 100644 --- a/.github/workflows/e2e-faucet-balance.yml +++ b/.github/workflows/e2e-faucet-balance.yml @@ -16,13 +16,27 @@ jobs: balance-and-fund: name: balance-and-fund runs-on: ubuntu-latest + env: + # `if` conditions can't directly access secrets, so we use a workaround + # See https://docs.github.com/en/actions/security-guides/encrypted-secrets#using-encrypted-secrets-in-a-workflow + SECRETS_AVAILABLE: ${{ secrets.SECRETS_AVAILABLE }} + BASH_ENV: ~/.profile steps: + - uses: google-github-actions/auth@v2 + if: ${{ env.SECRETS_AVAILABLE }} + with: + project_id: celo-mobile-mainnet + credentials_json: ${{ secrets.MAINNET_SERVICE_ACCOUNT_KEY }} + - name: Google Secrets + if: ${{ env.SECRETS_AVAILABLE }} + id: google-secrets + uses: google-github-actions/get-secretmanager-secrets@v2.1.4 + with: + secrets: |- + E2E_TEST_FAUCET_SECRET:projects/1027349420744/secrets/E2E_TEST_FAUCET_SECRET - uses: actions/checkout@v4 - uses: ./.github/actions/yarn-install - - name: Create E2E Fund .env File - env: - TEST_FAUCET_SECRET: ${{ secrets.TEST_FAUCET_SECRET }} - working-directory: e2e - run: echo TEST_FAUCET_SECRET=$TEST_FAUCET_SECRET >> .env - name: Run Balance and Fund run: NODE_OPTIONS='--unhandled-rejections=strict' yarn ts-node ./e2e/scripts/fund-e2e-accounts.ts + env: + E2E_TEST_FAUCET_SECRET: ${{ steps.google-secrets.outputs.E2E_TEST_FAUCET_SECRET }} diff --git a/.github/workflows/e2e-ios.yml b/.github/workflows/e2e-ios.yml index f15cc785927..ffad4192a05 100644 --- a/.github/workflows/e2e-ios.yml +++ b/.github/workflows/e2e-ios.yml @@ -28,6 +28,14 @@ jobs: with: secrets: |- EMERGE_API_TOKEN:projects/1027349420744/secrets/EMERGE_API_TOKEN + E2E_WALLET_CONNECT_PROJECT_ID:projects/1027349420744/secrets/E2E_WALLET_CONNECT_PROJECT_ID + E2E_DEFAULT_RECIPIENT_MNEMONIC:projects/1027349420744/secrets/E2E_DEFAULT_RECIPIENT_MNEMONIC + E2E_DEFAULT_RECIPIENT_PRIVATE_KEY:projects/1027349420744/secrets/E2E_DEFAULT_RECIPIENT_PRIVATE_KEY + E2E_WALLET_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_MNEMONIC + E2E_WALLET_PRIVATE_KEY:projects/1027349420744/secrets/E2E_WALLET_PRIVATE_KEY + E2E_WALLET_SINGLE_VERIFIED_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_SINGLE_VERIFIED_MNEMONIC + E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC + E2E_WALLET_12_WORDS_MNEMONIC:projects/1027349420744/secrets/E2E_WALLET_12_WORDS_MNEMONIC - uses: actions/checkout@v4 - uses: ./.github/actions/yarn-install # Since the e2e runners have access to the Valora branding, @@ -46,9 +54,6 @@ jobs: run: yarn ts-node ./.github/scripts/checkPodfileAndUpdateRenovatePr.ts - name: Check E2E wallet balance run: NODE_OPTIONS='--unhandled-rejections=strict' yarn ts-node ./e2e/scripts/check-e2e-wallet-balance.ts - - name: Create iOS E2E .env File - working-directory: e2e - run: echo MOCK_PROVIDER_BASE_URL=${{ secrets.MOCK_PROVIDER_BASE_URL }} >> .env && echo MOCK_PROVIDER_API_KEY=${{ secrets.MOCK_PROVIDER_API_KEY }} >> .env && echo WALLET_CONNECT_PROJECT_ID_E2E=${{ secrets.WALLET_CONNECT_PROJECT_ID_E2E }} >> .env - name: Create Detox Build run: | export CELO_TEST_CONFIG=e2e @@ -76,6 +81,15 @@ jobs: --maxWorkers 6 --retries 3 timeout-minutes: 45 + env: + E2E_WALLET_CONNECT_PROJECT_ID: ${{ steps.google-secrets.outputs.E2E_WALLET_CONNECT_PROJECT_ID }} + E2E_DEFAULT_RECIPIENT_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_DEFAULT_RECIPIENT_MNEMONIC }} + E2E_DEFAULT_RECIPIENT_PRIVATE_KEY: ${{ steps.google-secrets.outputs.E2E_DEFAULT_RECIPIENT_PRIVATE_KEY }} + E2E_WALLET_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_MNEMONIC }} + E2E_WALLET_PRIVATE_KEY: ${{ steps.google-secrets.outputs.E2E_WALLET_PRIVATE_KEY }} + E2E_WALLET_SINGLE_VERIFIED_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_SINGLE_VERIFIED_MNEMONIC }} + E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC }} + E2E_WALLET_12_WORDS_MNEMONIC: ${{ steps.google-secrets.outputs.E2E_WALLET_12_WORDS_MNEMONIC }} - name: Publish iOS JUnit Report if: always() uses: mikepenz/action-junit-report@v4 diff --git a/e2e/.env.enc b/e2e/.env.enc index cd858b91cda..1369c12f268 100644 Binary files a/e2e/.env.enc and b/e2e/.env.enc differ diff --git a/e2e/README.md b/e2e/README.md index d0667b48b8a..a7b8991f507 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -20,6 +20,10 @@ Ensure you have Xcode installed. Install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils#installing) which is used in e2e scripts to launch the iOS simulator. +### Setting up secrets for local runs + +An `.env` example is present at `e2e/env.example` rename this file to `.env` and populate the secrets from Google Cloud Platform. Do not commit the newly created `e2e/.env` file. + ## Running the tests ```sh diff --git a/e2e/env.example b/e2e/env.example new file mode 100644 index 00000000000..5ccbb7bc531 --- /dev/null +++ b/e2e/env.example @@ -0,0 +1,9 @@ +E2E_TEST_FAUCET_SECRET="" +E2E_WALLET_CONNECT_PROJECT_ID="" +E2E_DEFAULT_RECIPIENT_MNEMONIC="" +E2E_DEFAULT_RECIPIENT_PRIVATE_KEY="" +E2E_WALLET_MNEMONIC="" +E2E_WALLET_PRIVATE_KEY="" +E2E_WALLET_SINGLE_VERIFIED_MNEMONIC="" +E2E_WALLET_MULTIPLE_VERIFIED_MNEMONIC="" +E2E_WALLET_12_WORDS_MNEMONIC="" diff --git a/e2e/init.js b/e2e/init.js index 0aa89798c18..f524cd39f5c 100644 --- a/e2e/init.js +++ b/e2e/init.js @@ -5,6 +5,5 @@ beforeAll(async () => { await device.installApp() await launchApp({ newInstance: false, - permissions: { notifications: 'YES', contacts: 'YES', camera: 'YES' }, }) }) diff --git a/e2e/scripts/check-e2e-wallet-balance.ts b/e2e/scripts/check-e2e-wallet-balance.ts index e78bb4dd3c6..ac1a6005280 100644 --- a/e2e/scripts/check-e2e-wallet-balance.ts +++ b/e2e/scripts/check-e2e-wallet-balance.ts @@ -1,4 +1,4 @@ -import { E2E_TEST_WALLET, E2E_TEST_WALLET_SECURE_SEND } from './consts' +import { E2E_TEST_FAUCET, E2E_TEST_WALLET, E2E_TEST_WALLET_SECURE_SEND } from './consts' import { checkBalance, getCeloTokensBalance } from './utils' ;(async () => { console.log(`E2E_TEST_WALLET: ${E2E_TEST_WALLET}`) @@ -8,4 +8,7 @@ import { checkBalance, getCeloTokensBalance } from './utils' console.log(`E2E_TEST_WALLET_SECURE_SEND: ${E2E_TEST_WALLET_SECURE_SEND}`) console.table(await getCeloTokensBalance(E2E_TEST_WALLET_SECURE_SEND)) await checkBalance(E2E_TEST_WALLET_SECURE_SEND) + + console.log(`E2E_TEST_FACUET: ${E2E_TEST_FAUCET}`) + console.table(await getCeloTokensBalance(E2E_TEST_FAUCET)) })() diff --git a/e2e/scripts/consts.ts b/e2e/scripts/consts.ts index 7fb65ff556f..591abf4c785 100644 --- a/e2e/scripts/consts.ts +++ b/e2e/scripts/consts.ts @@ -1,4 +1,4 @@ -export const E2E_TEST_WALLET = '0x6131a6d616a4be3737b38988847270a64bc10caa' -export const E2E_TEST_WALLET_SECURE_SEND = '0x86b8f44386cb2d457db79c3dab8cf42f9d8a3fc0' -export const E2E_TEST_FAUCET = '0xe5F5363e31351C38ac82DBAdeaD91Fd5a7B08846' +export const E2E_TEST_WALLET = '0xebf95355cc5ea643179a02337f3f943fd8dd2bcb' +export const E2E_TEST_WALLET_SECURE_SEND = '0x06f4b680c6cb1aeec4a3ce12c63ea18acb136aa3' +export const E2E_TEST_FAUCET = '0xa694b396cd6f73e4003da8f97f4b12e498b3f2ec' export const REFILL_TOKENS = ['CELO', 'cUSD', 'cEUR'] diff --git a/e2e/scripts/fund-e2e-accounts.ts b/e2e/scripts/fund-e2e-accounts.ts index c987376286a..44917ed3aca 100644 --- a/e2e/scripts/fund-e2e-accounts.ts +++ b/e2e/scripts/fund-e2e-accounts.ts @@ -11,11 +11,11 @@ import { } from './consts' import { checkBalance, getCeloTokensBalance } from './utils' -const provider = new providers.JsonRpcProvider('https://alfajores-forno.celo-testnet.org') +const provider = new providers.JsonRpcProvider('https://forno.celo.org/') dotenv.config({ path: `${__dirname}/../.env` }) -const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']! +const valoraTestFaucetSecret = process.env['E2E_TEST_FAUCET_SECRET']! interface Token { symbol: string @@ -25,17 +25,17 @@ interface Token { const CELO: Token = { symbol: 'CELO', - address: utils.getAddress('0xf194afdf50b03e69bd7d057c1aa9e10c9954e4c9'), + address: utils.getAddress('0x471ece3750da237f93b8e339c536989b8978a438'), decimals: 18, } const CUSD: Token = { symbol: 'cUSD', - address: utils.getAddress('0x874069fa1eb16d44d622f2e0ca25eea172369bc1'), + address: utils.getAddress('0x765de816845861e75a25fca122bb6898b8b1282a'), decimals: 18, } const CEUR: Token = { symbol: 'cEUR', - address: utils.getAddress('0x10c892a6ec43a53e45d0b916b4b7d383b1b78c0f'), + address: utils.getAddress('0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73'), decimals: 18, } const TOKENS_BY_SYMBOL: Record = { @@ -53,7 +53,7 @@ const TOKENS_BY_SYMBOL: Record = { } const faucetTokenBalances = (await getCeloTokensBalance(E2E_TEST_FAUCET)) ?? {} - console.log('Initial faucet balance:') + console.log(`Initial balance for faucet at: ${E2E_TEST_FAUCET}:`) console.table(faucetTokenBalances) // Connect Valora E2E Test Faucet - Private Key Stored in GitHub Secrets @@ -216,14 +216,14 @@ const TOKENS_BY_SYMBOL: Record = { } // Set Amount To Send - const amountToSend = '100' + const amountToSend = '10' for (let i = 0; i < walletsToBeFunded.length; i++) { const walletAddress = walletsToBeFunded[i] const walletBalance = walletBalances[i] for (const tokenSymbol of REFILL_TOKENS) { // @ts-ignore - if (walletBalance && walletBalance[tokenSymbol] < 200) { + if (walletBalance && walletBalance[tokenSymbol] < 20) { console.log(`Sending ${amountToSend} ${tokenSymbol} to ${walletAddress}`) await transferToken(TOKENS_BY_SYMBOL[tokenSymbol], amountToSend, walletAddress) } diff --git a/e2e/scripts/utils.ts b/e2e/scripts/utils.ts index e293ec105b6..dec983599b2 100644 --- a/e2e/scripts/utils.ts +++ b/e2e/scripts/utils.ts @@ -1,5 +1,5 @@ import { Address, createPublicClient, erc20Abi, http } from 'viem' -import { celoAlfajores } from 'viem/chains' +import { celo } from 'viem/chains' import { REFILL_TOKENS } from './consts' export async function checkBalance( @@ -11,7 +11,7 @@ export async function checkBalance( for (const [tokenSymbol, tokenBalance] of Object.entries(balance)) { if (tokenSymbols.includes(tokenSymbol) && tokenBalance < minBalance) { throw new Error( - `${balance} balance of ${address} is below ${minBalance}. Please refill from the faucet https://celo.org/developers/faucet or run ./fund-e2e-accounts.ts if a Valora Dev.` + `${balance} balance of ${address} is below ${minBalance}. Please refill from the faucet or run ./fund-e2e-accounts.ts if a Valora Dev.` ) } } @@ -20,15 +20,15 @@ export async function checkBalance( export async function getCeloTokensBalance(walletAddress: Address) { try { const supportedTokenAddresses: Address[] = [ - '0x874069fa1eb16d44d622f2e0ca25eea172369bc1', - '0x10c892a6ec43a53e45d0b916b4b7d383b1b78c0f', - '0xf194afdf50b03e69bd7d057c1aa9e10c9954e4c9', - '0xe4d517785d091d3c54818832db6094bcc2744545', + '0x765de816845861e75a25fca122bb6898b8b1282a', + '0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73', + '0x471ece3750da237f93b8e339c536989b8978a438', + '0xe8537a3d056da446677b9e9d6c5db704eaab4787', ] // cUSD, cEUR, CELO, cREAL const supportedTokenSymbols: string[] = ['cUSD', 'cEUR', 'CELO', 'cREAL'] const celoClient = createPublicClient({ - chain: celoAlfajores, + chain: celo, transport: http(), }) diff --git a/e2e/src/AccountSetup.spec.js b/e2e/src/AccountSetup.spec.js index 9d6d2533c66..808981d61b1 100644 --- a/e2e/src/AccountSetup.spec.js +++ b/e2e/src/AccountSetup.spec.js @@ -1,9 +1,7 @@ -import ChooseYourAdventure from './usecases/ChooseYourAdventure' import NewAccountOnboarding from './usecases/NewAccountOnboarding' import RestoreAccountOnboarding from './usecases/RestoreAccountOnboarding' describe('Account Setup', () => { describe('New Account', NewAccountOnboarding) describe('Restore', RestoreAccountOnboarding) - describe('Choose Your Adventure', ChooseYourAdventure) }) diff --git a/e2e/src/CeloPage.spec.js b/e2e/src/CeloPage.spec.js index d41b5669010..bb8d26f3847 100644 --- a/e2e/src/CeloPage.spec.js +++ b/e2e/src/CeloPage.spec.js @@ -1,8 +1,7 @@ import CeloEducation from './usecases/CeloEducation' -import PriceChart from './usecases/PriceChart' import CeloNews from './usecases/CeloNews' +import PriceChart from './usecases/PriceChart' import { quickOnboarding } from './utils/utils' -import { launchApp } from './utils/retries' describe('Celo page', () => { beforeAll(async () => { diff --git a/e2e/src/ChooseYourAdventure.spec.js b/e2e/src/ChooseYourAdventure.spec.js new file mode 100644 index 00000000000..57e6f973f10 --- /dev/null +++ b/e2e/src/ChooseYourAdventure.spec.js @@ -0,0 +1,3 @@ +import ChooseYourAdventure from './usecases/ChooseYourAdventure' + +describe('Choose your adventure', ChooseYourAdventure) diff --git a/e2e/src/Discover.spec.js b/e2e/src/Discover.spec.js index f44ff516e97..59d2f94c7ac 100644 --- a/e2e/src/Discover.spec.js +++ b/e2e/src/Discover.spec.js @@ -1,16 +1,13 @@ -import { quickOnboarding, waitForElementByIdAndTap, scrollIntoView } from './utils/utils' -import { launchApp } from './utils/retries' import DappListDisplay from './usecases/DappListDisplay' +import { launchApp } from './utils/retries' +import { quickOnboarding, scrollIntoView, waitForElementByIdAndTap } from './utils/utils' describe('Discover tab', () => { beforeAll(async () => { await quickOnboarding() // Relaunch app to ensure dapp list loads // Needed for e2e tests otherwise dapp list is not loaded on first pass - await launchApp({ - newInstance: true, - permissions: { notifications: 'YES', contacts: 'YES', camera: 'YES' }, - }) + await launchApp() await waitForElementByIdAndTap('Tab/Discover') await scrollIntoView('View All', 'DiscoverScrollView') diff --git a/e2e/src/FiatConnectTransferOut.spec.js b/e2e/src/FiatConnectTransferOut.spec.js deleted file mode 100644 index 8159967fd76..00000000000 --- a/e2e/src/FiatConnectTransferOut.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import { - fiatConnectNonKycTransferOut, - fiatConnectKycTransferOut, -} from './usecases/FiatConnectTransferOut' -import { MOCK_PROVIDER_BASE_URL, MOCK_PROVIDER_API_KEY } from 'react-native-dotenv' -import { launchApp } from './utils/retries' - -describe.skip(':ios: FiatConnect Transfer Out', () => { - // deliberately not doing onboarding in beforeEach, since we'll want to re-use accounts, use fresh accounts, etc for these tests - beforeEach(async () => { - // uninstall and reinstall to obtain a fresh account - await device.uninstallApp() - await device.installApp() - await launchApp({ - newInstance: false, - permissions: { notifications: 'YES', contacts: 'YES', camera: 'YES' }, - }) - }) - - // disable test for android until we fix the bottom sheet issue - describe('Non KYC', fiatConnectNonKycTransferOut) - - // KYC test needs to be on iOS and needs Mock Provider info - if (MOCK_PROVIDER_BASE_URL && MOCK_PROVIDER_API_KEY) { - describe('KYC', fiatConnectKycTransferOut) - } -}) diff --git a/e2e/src/HomeFeed.spec.js b/e2e/src/HomeFeed.spec.js index 92bd22c3234..0e3d8dc4cb5 100644 --- a/e2e/src/HomeFeed.spec.js +++ b/e2e/src/HomeFeed.spec.js @@ -1,45 +1,10 @@ -import { quickOnboarding, waitForElementId } from './utils/utils' -import { sleep } from '../../src/utils/sleep' -import jestExpect from 'expect' +import HomeFeed from './usecases/HomeFeed' +import { quickOnboarding } from './utils/utils' -beforeAll(async () => { - await quickOnboarding() -}) - -describe('Home Feed', () => { - it('should show correct information on tap of feed item', async () => { - // Load Wallet Home - await waitForElementId('WalletHome') - const items = await element(by.id('TransferFeedItem')).getAttributes() - - // Tap top TransferFeedItem - await element(by.id('TransferFeedItem')).atIndex(0).tap() - - // Assert on text based on elements returned earlier - const address = items.elements[0].label.split(' ')[0] - const amount = items.elements[0].label.match(/(\d+\.\d+)/)[1] - await expect(element(by.text(address)).atIndex(0)).toBeVisible() - await expect(element(by.text(`$${amount}`)).atIndex(0)).toBeVisible() +describe('Home feed', () => { + beforeAll(async () => { + await quickOnboarding() }) - it('should load more items on scroll', async () => { - // Tap back button if present form previous test - try { - await element(by.id('BackChevron')).tap() - } catch {} - - // Load Wallet Home - await waitForElementId('WalletHome') - const startingItems = await element(by.id('TransferFeedItem')).getAttributes() - - // Scroll to bottom - Android will scroll forever so we set a static value - device.getPlatform() === 'ios' - ? await element(by.id('WalletHome/FlatList')).scrollTo('bottom') - : await element(by.id('WalletHome/FlatList')).scroll(2000, 'down') - await sleep(5000) - - // Compare initial number of items to new number of items after scroll - const endingItems = await element(by.id('TransferFeedItem')).getAttributes() - jestExpect(endingItems.elements.length).toBeGreaterThan(startingItems.elements.length) - }) + describe('when loaded', HomeFeed) }) diff --git a/e2e/src/Pin.spec.js b/e2e/src/Pin.spec.js index a68730f956a..0bfe89b00eb 100644 --- a/e2e/src/Pin.spec.js +++ b/e2e/src/Pin.spec.js @@ -5,10 +5,8 @@ import { quickOnboarding } from './utils/utils' describe('Given PIN', () => { beforeEach(async () => { - await device.uninstallApp() - await device.installApp() await launchApp({ - newInstance: true, + delete: true, permissions: { notifications: 'YES', contacts: 'YES' }, }) await quickOnboarding() diff --git a/e2e/src/QRScanner.spec.js b/e2e/src/QRScanner.spec.js index d0a47e6d576..bae0a69b825 100644 --- a/e2e/src/QRScanner.spec.js +++ b/e2e/src/QRScanner.spec.js @@ -1,4 +1,4 @@ -import { reloadReactNative, launchApp } from './utils/retries' +import { reloadReactNative } from './utils/retries' import { quickOnboarding, waitForElementId } from './utils/utils' describe('Given QR Scanner', () => { diff --git a/e2e/src/Verify.spec.js b/e2e/src/Verify.spec.js deleted file mode 100644 index cfe2fef0662..00000000000 --- a/e2e/src/Verify.spec.js +++ /dev/null @@ -1,5 +0,0 @@ -import NewAccountPhoneVerification from './usecases/NewAccountPhoneVerification' - -describe('Given Phone Verification', () => { - describe('When New Account', NewAccountPhoneVerification) -}) diff --git a/e2e/src/usecases/Assets.js b/e2e/src/usecases/Assets.js index d210a2be025..609f2a439fa 100644 --- a/e2e/src/usecases/Assets.js +++ b/e2e/src/usecases/Assets.js @@ -1,20 +1,23 @@ +import { E2E_WALLET_MNEMONIC } from 'react-native-dotenv' import { english, generateMnemonic } from 'viem/accounts' -import { DEFAULT_RECIPIENT_ADDRESS, SAMPLE_BACKUP_KEY } from '../utils/consts' +import { DEFAULT_RECIPIENT_ADDRESS } from '../utils/consts' import { launchApp } from '../utils/retries' import { + getDisplayAddress, quickOnboarding, + scrollIntoViewByTestId, waitForElementByIdAndTap, waitForElementId, - scrollIntoViewByTestId, } from '../utils/utils' async function validateSendFlow(tokenSymbol) { + const recipientAddressDisplay = getDisplayAddress(DEFAULT_RECIPIENT_ADDRESS) // navigate to send amount screen to ensure the expected token symbol is pre-selected await waitForElementByIdAndTap('SendSelectRecipientSearchInput') await element(by.id('SendSelectRecipientSearchInput')).replaceText(DEFAULT_RECIPIENT_ADDRESS) await element(by.id('SendSelectRecipientSearchInput')).tapReturnKey() - await expect(element(by.text('0xe5f5...8846')).atIndex(0)).toBeVisible() - await element(by.text('0xe5f5...8846')).atIndex(0).tap() + await expect(element(by.text(recipientAddressDisplay)).atIndex(0)).toBeVisible() + await element(by.text(recipientAddressDisplay)).atIndex(0).tap() await waitForElementByIdAndTap('SendOrInviteButton') await expect( element(by.text(tokenSymbol).withAncestor(by.id('SendEnterAmount/TokenSelect'))) @@ -37,15 +40,15 @@ export default Assets = () => { balance: 'non zero', tokens: [ { - tokenId: 'celo-alfajores:native', + tokenId: 'celo-mainnet:native', symbol: 'CELO', actions: ['Send', 'Add'], moreActions: ['Send', 'Add', 'Withdraw'], learnMore: true, }, { - tokenId: 'celo-alfajores:0x048f47d358ec521a6cf384461d674750a3cb58c8', - symbol: 'TT', + tokenId: 'celo-mainnet:0x32a9fe697a32135bfd313a6ac28792dae4d9979d', + symbol: 'cMCO2', actions: ['Send'], moreActions: [], learnMore: false, @@ -56,14 +59,14 @@ export default Assets = () => { balance: 'zero', tokens: [ { - tokenId: 'celo-alfajores:native', + tokenId: 'celo-mainnet:native', symbol: 'CELO', actions: ['Add'], moreActions: [], learnMore: true, }, { - tokenId: 'celo-alfajores:0x874069fa1eb16d44d622f2e0ca25eea172369bc1', + tokenId: 'celo-mainnet:0x765de816845861e75a25fca122bb6898b8b1282a', symbol: 'cUSD', actions: ['Add'], moreActions: [], @@ -73,15 +76,12 @@ export default Assets = () => { }, ])('For wallet with $balance balance', ({ balance, tokens }) => { beforeAll(async () => { - // uninstall and reinstall to start with either a new account or the usual - // e2e account - await device.uninstallApp() - await device.installApp() + // Start with either a new account or the usual e2e account await launchApp({ - newInstance: true, + delete: true, permissions: { notifications: 'YES', contacts: 'YES', camera: 'YES' }, }) - let mnemonic = SAMPLE_BACKUP_KEY + let mnemonic = E2E_WALLET_MNEMONIC if (balance === 'zero') { mnemonic = generateMnemonic(english) } diff --git a/e2e/src/usecases/CeloEducation.js b/e2e/src/usecases/CeloEducation.js index c3e0c818110..612b85c74a2 100644 --- a/e2e/src/usecases/CeloEducation.js +++ b/e2e/src/usecases/CeloEducation.js @@ -1,5 +1,5 @@ -import { waitForElementByIdAndTap, waitForElementId } from '../utils/utils' import { celoEducation } from '../utils/celoEducation' +import { waitForElementByIdAndTap, waitForElementId } from '../utils/utils' const swipeThrough = async (direction = 'left', swipes = 3) => { for (let i = 0; i < swipes; i++) { diff --git a/e2e/src/usecases/ChooseYourAdventure.js b/e2e/src/usecases/ChooseYourAdventure.js index 4c86757830d..db0c4b9cb59 100644 --- a/e2e/src/usecases/ChooseYourAdventure.js +++ b/e2e/src/usecases/ChooseYourAdventure.js @@ -1,12 +1,10 @@ import { launchApp } from '../utils/retries' -import { quickOnboarding, waitForElementId } from '../utils/utils' +import { quickOnboarding, waitForElementByText, waitForElementId } from '../utils/utils' export default ChooseYourAdventure = () => { beforeEach(async () => { - await device.uninstallApp() - await device.installApp() await launchApp({ - newInstance: true, + delete: true, launchArgs: { onboardingOverrides: 'EnableBiometry,ProtectWallet,PhoneVerification,CloudBackup', }, @@ -15,10 +13,10 @@ export default ChooseYourAdventure = () => { }) it('learn about points navigates to points journey page', async () => { - await element(by.text('Learn about Valora Points')).tap() + await waitForElementByText({ text: `Learn about Valora Points`, tap: true }) // Check that we are on the Points journey page - await expect(element(by.text('Earn points effortlessly')).atIndex(0)).toBeVisible() + await waitForElementByText({ text: 'Earn points effortlessly', index: 0 }) // Back should go to the home screen await element(by.id('BackChevron')).tap() @@ -26,7 +24,7 @@ export default ChooseYourAdventure = () => { }) it('build your profile navigates to profile page', async () => { - await element(by.text('Build your profile')).tap() + await waitForElementByText({ text: 'Build your profile', tap: true }) // Check that we are on the profile page await waitForElementId('ProfileEditName') @@ -37,7 +35,7 @@ export default ChooseYourAdventure = () => { }) it('add funds to your wallet navigates to home and opens the token bottom sheet', async () => { - await element(by.text('Add funds to your wallet')).tap() + await waitForElementByText({ text: 'Add funds to your wallet', tap: true }) // Check that we are on the bottom sheet await waitForElementId('TokenBottomSheet') @@ -48,10 +46,10 @@ export default ChooseYourAdventure = () => { }) it('explore earning opportunities navigates to stablecoins info page', async () => { - await element(by.text('Explore earning opportunities')).tap() + await waitForElementByText({ text: 'Explore earning opportunities', tap: true }) - await waitForElementId('EarnInfoScreen/Title') // Check that we are on the Earn On Your Stablecoins page - await expect(element(by.text('Earn on your\ncrypto')).atIndex(0)).toBeVisible() + await waitForElementId('EarnInfoScreen/Title') + await waitForElementByText({ text: 'Earn on your\ncrypto' }) }) } diff --git a/e2e/src/usecases/DappListDisplay.js b/e2e/src/usecases/DappListDisplay.js index cc8b618e509..2a34e622d6d 100644 --- a/e2e/src/usecases/DappListDisplay.js +++ b/e2e/src/usecases/DappListDisplay.js @@ -1,6 +1,6 @@ +import jestExpect from 'expect' import { fetchDappList } from '../utils/dappList' import { getElementTextList, scrollIntoView } from '../utils/utils' -import jestExpect from 'expect' export default DappListDisplay = () => { let dappList = null diff --git a/e2e/src/usecases/FiatConnectTransferOut.js b/e2e/src/usecases/FiatConnectTransferOut.js deleted file mode 100644 index 3c8ae6e7e99..00000000000 --- a/e2e/src/usecases/FiatConnectTransferOut.js +++ /dev/null @@ -1,309 +0,0 @@ -import { english, generateMnemonic, mnemonicToAccount } from 'viem/accounts' -import { KycStatus } from '@fiatconnect/fiatconnect-types' -import fetch from 'node-fetch' -import { MOCK_PROVIDER_API_KEY, MOCK_PROVIDER_BASE_URL } from 'react-native-dotenv' -import { SAMPLE_PRIVATE_KEY } from '../utils/consts' -import { - enterPinUiIfNecessary, - fundWallet, - quickOnboarding, - waitForElementId, -} from '../utils/utils' -import { sleep } from '../../../src/utils/sleep' - -/** - * From the home screen, navigate to the FiatExchange screen (add/withdraw) - * - * @return {{result: Error}} - */ -async function navigateToFiatExchangeScreen() { - await waitForElementId('HomeActionsCarousel') - await element(by.id('HomeActionsCarousel')).scrollTo('right') - await waitForElementId('HomeAction-Withdraw') - await element(by.id('HomeAction-Withdraw')).tap() -} - -/** - * Select the currency and amount for a transfer. - * - * Must begin on FiatExchangeCurrency screen. Ends on SelectProviderScreen or ReviewScreen, - * depending on whether the user has transferred out with a FiatConnect provider before. - * - * @return {{result: Error}} - */ -async function selectCurrencyAndAmount(token, amount) { - await waitForElementId(`${token}Symbol`) - await element(by.id(`${token}Symbol`)).tap() - - // FiatExchangeAmount - await waitForElementId('FiatExchangeInput') - await element(by.id('FiatExchangeInput')).replaceText(`${amount}`) - await element(by.id('FiatExchangeNextButton')).tap() -} - -/** - * Submit a transfer from the review screen. - * - * Must begin at FiatConnect ReviewScreen. Expects success status screen, - * continues past it and ends at home screen. - * - * @return {{result: Error}} - */ -async function submitTransfer(expectZeroBalance = false) { - // ReviewScreen - await waitForElementId('submitButton') - await element(by.id('submitButton')).tap() - - // TransferStatusScreen - await waitFor(element(by.id('loadingTransferStatus'))).not.toBeVisible() - await waitFor(element(by.text('Your funds are on their way!'))).toBeVisible() - await waitForElementId('Continue') - await element(by.id('Continue')).tap() - - // WalletHome - await expect(element(by.id('HomeAction-Send'))).toBeVisible() // proxy for reaching home screen, imitating NewAccountOnboarding e2e test -} - -/** - * Set the kycStatus for a wallet address on the mock provider. - * - * @param kycStatus: the kycStatus string - * @param walletAddress: the address string for which the kycStatus should be set - */ -async function setWalletKycStatus(kycStatus, walletAddress) { - const requestOptions = { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${MOCK_PROVIDER_API_KEY}`, - }, - body: JSON.stringify({ kycStatus, walletAddress }), - } - const url = new URL('/kyc/PersonalDataAndDocuments/status', MOCK_PROVIDER_BASE_URL) - const resp = await fetch(url, requestOptions) - if (!resp.ok) { - throw Error(`Unable to update kyc status of wallet ${walletAddress} to status ${kycStatus}`) - } -} - -/** - * From a newly installed app, setup and start a transfer out. - * - * Onboards a new wallet and funds it before beginning a transfer out - * with the mock FiatConnect provider. - * - * @param token: string of the token that gets funded to the wallet - * @param fundingAmount: token amount to be deposited - * @param cashOutAmount: token amount to be transfered out (doesn't include gas) - * @returns the address of the now-funded wallet - */ -async function onboardAndBeginTransferOut(token, fundingAmount, cashOutAmount) { - const mnemonic = generateMnemonic(english) - const { address: walletAddress } = mnemonicToAccount(mnemonic) - await quickOnboarding({ mnemonic }) // ends on home screen - await fundWallet(SAMPLE_PRIVATE_KEY, walletAddress, token, fundingAmount) - // For now the balance only updates when the home screen is visible - await waitFor(element(by.text(`${fundingAmount} cUSD`))) // need a balance to withdraw - .toBeVisible() - .withTimeout(120000) // in case funding tx is still pending. balance must be updated before amount can be selected. - - await navigateToFiatExchangeScreen() - - // FiatExchange - await waitForElementId('cashOut') - await element(by.id('cashOut')).tap() - - await selectCurrencyAndAmount(token, cashOutAmount) - - // SelectProviderScreen - await expect(element(by.text('Select Withdraw Method'))).toBeVisible() - await waitForElementId('Exchanges') // once visible, the FC providers should have also loaded - try { - // expand dropdown for "Bank Account" providers section - await element(by.id('Bank/section')).tap() - await element(by.id('image-Test Provider').withAncestor(by.id('Bank/providerList'))).tap() - } catch (error) { - // expected when only one provider exists for "Bank" fiat account type - await expect(element(by.id('Bank/singleprovider'))) - await element(by.id('image-Test Provider').withAncestor(by.id('Bank/singleProviderInfo'))).tap() - } - await enterPinUiIfNecessary() - - return walletAddress -} - -/** - * From the FiatDetailsScreen, enter bank information and submit - * - * Currently assumes that the AccountNumber schema is used - * - * @return {{result: Error}} - */ -async function enterAccountInformation() { - await expect(element(by.text('Enter Bank Information'))).toBeVisible() - await element(by.id('input-institutionName')).replaceText('My Bank Name') - await element(by.id('input-accountNumber')).replaceText('1234567890') - await element(by.id('submitButton')).tap() -} - -/** - * From the home screen, navigate to FiatExchange and transfer out. - * - * @param token: string of the token that will be transferred out - * @param cashOutAmount: token amount to withdraw - */ -async function returnUserTransferOut(token, cashOutAmount) { - await navigateToFiatExchangeScreen() - await waitForElementId('cashOut') - await element(by.id('cashOut')).tap() - - await selectCurrencyAndAmount(token, cashOutAmount) - - await submitTransfer(true) -} - -export const fiatConnectNonKycTransferOut = () => { - it('FiatConnect cash out', async () => { - // ******** First time experience ************ - const cashOutAmount = 0.02 - const gasAmount = 0.015 - const fundingAmount = `${2 * cashOutAmount + gasAmount}` - const token = 'cUSD' - await onboardAndBeginTransferOut(token, fundingAmount, cashOutAmount) - - // LinkAccountScreen - await expect(element(by.text('Set Up Bank Account'))).toBeVisible() - await expect(element(by.id('descriptionText'))).toBeVisible() - await element(by.id('continueButton')).tap() - - // FiatDetailsScreen - await enterAccountInformation() - - await submitTransfer() - - // ******** Returning user experience ************ - await returnUserTransferOut(token, cashOutAmount) - }) -} - -export const fiatConnectKycTransferOut = () => { - it('FiatConnect cash out', async () => { - // ******** First time experience ************ - const cashOutAmount = 0.01 - const gasAmount = 0.015 - const fundingAmount = `${2 * cashOutAmount + gasAmount}` - const token = 'cUSD' - const walletAddress = await onboardAndBeginTransferOut(token, fundingAmount, cashOutAmount) - - // KycLanding - - // Step 1 - await expect(element(by.text('Verify your Identity'))).toBeVisible() - await expect(element(by.id('step-one-grey'))).not.toBeVisible() - await expect(element(by.id('step-two-grey'))).toBeVisible(25) // should be at least 25% visible default is 75% - await expect(element(by.id('checkbox/unchecked'))).toBeVisible() - await element(by.id('checkbox/unchecked')).tap() - await element(by.id('PersonaButton')).tap() - - // Persona - await waitFor(element(by.text('Begin verifying'))) - .toBeVisible() - .withTimeout(5000) - await element(by.text('Begin verifying')).tap() - - // Country of govt id screen - await waitFor(element(by.text('Select'))) - .toBeVisible() - .withTimeout(5 * 1000) - await element(by.text('Select')).tap() - - // Id select screen - await waitFor(element(by.text('Driver License'))) - .toBeVisible() - .withTimeout(5 * 1000) - await element(by.text('Driver License')).tap() - await waitFor(element(by.text('Enable camera'))) - .toBeVisible() - .withTimeout(5 * 1000) - await element(by.text('Enable camera')).tap() - - // Manually wait for Take Photo button to appear, withTimeout didn't work - await sleep(10000) - - // License photo front - await element(by.label('Take photo')).tap() - await element(by.text('Use this photo')).tap() - - // Manually wait for Take Photo button to appear, withTimeout didn't work - await sleep(10000) - - // License photo back -- personal stopped requesting back photo, commenting - // for now to unblock CI - // await element(by.label('Take photo')).tap() - // await element(by.text('Use this photo')).tap() - - // Manually wait for Take Photo button to appear, withTimeout didn't work - // await sleep(10000) - - // License photo barcode - await element(by.label('Take photo')).tap() - await element(by.text('Use this photo')).tap() - - // Selfie section - await waitFor(element(by.text('Get started'))) - .toBeVisible() - .withTimeout(15 * 1000) - await element(by.text('Get started')).tap() - await element(by.label('shutter button')).tap() - await element(by.label('shutter button')).tap() - await element(by.label('shutter button')).tap() - - // Name, number, email, address form - await waitFor(element(by.text('Phone Number'))) - .toBeVisible() - .withTimeout(10000) - - await element(by.type('PersonaPhoneNumberKit2.PhoneNumberTextField')).typeText('0123456789') - await element(by.text('Phone Number')).tap() // Tap away to unfocus from input to dismiss keyboard - await element(by.type('UITextField').withDescendant(by.label('Email Address'))).typeText( - 'test@example.com' - ) - await element(by.text('Phone Number')).tap() // Tap away to unfocus from input - - // Scroll down to continue button - all params needed to scroll down on persona template - // Last parameter is the start point of the scroll, 0.5 is the middle of the element - await element(by.type('UIScrollView').withDescendant(by.text('Phone Number'))).scroll( - 400, - 'down', - NaN, - 0 - ) - - await element(by.text('Continue')).tap() - - // Somehow this sleep is needed to make the test pass - // waitFor doesn't work here !? - await sleep(1000) - await element(by.text('Done')).tap() // End of Persona flow - - // Check that Mock Provider info is defined - await setWalletKycStatus(KycStatus.KycApproved, walletAddress) - - // Step 2 - await waitFor(element(by.text('Set Up Bank Account'))) - .toBeVisible() - .withTimeout(15000) - await expect(element(by.id('step-one-grey'))).toBeVisible() - await expect(element(by.id('step-two-grey'))).not.toBeVisible() - await element(by.type('UIScrollView')).atIndex(0).scroll(400, 'down', NaN, 0.5) - await element(by.id('continueButton')).tap() - - // FiatDetailsScreen - await enterAccountInformation() - - await submitTransfer() - - // ******** Returning user experience ************ - await returnUserTransferOut(token, cashOutAmount) - }) -} diff --git a/e2e/src/usecases/HandleDeepLinkSend.js b/e2e/src/usecases/HandleDeepLinkSend.js index 6671adb6b1f..976c8376380 100644 --- a/e2e/src/usecases/HandleDeepLinkSend.js +++ b/e2e/src/usecases/HandleDeepLinkSend.js @@ -1,11 +1,7 @@ +import jestExpect from 'expect' import { E2E_TEST_FAUCET } from '../../scripts/consts' import { launchApp, reloadReactNative } from '../utils/retries' -import { - enterPinUiIfNecessary, - quote, - waitForElementByIdAndTap, - waitForElementId, -} from '../utils/utils' +import { enterPinUiIfNecessary, waitForElementByIdAndTap, waitForElementId } from '../utils/utils' const deepLinks = { withoutAddress: @@ -20,6 +16,14 @@ const launchDeepLink = async ({ url, newInstance = true }) => { newInstance, }) } +/** + * Returns the crypto symbol from the SendAmount element + * @returns {Promise} + */ +const getCryptoSymbol = async () => { + const sendAmountCryptoElement = await element(by.id('SendAmount')).getAttributes() + return sendAmountCryptoElement.label.split(' ').at(-1) +} const openDeepLink = async (payUrl) => { await reloadReactNative() @@ -32,12 +36,15 @@ export default HandleDeepLinkSend = () => { await launchDeepLink({ url: `celo://wallet/pay?address=${E2E_TEST_FAUCET}&amount=0.01¤cyCode=USD&token=cUSD&displayName=TestFaucet`, }) - await waitFor(element(by.id('SendAmount'))) - .toHaveText('0.0067 cUSD') // alfajores uses 1.5 as price for all tokens - .withTimeout(10 * 1000) + + const cryptoSymbol = await getCryptoSymbol() + jestExpect(cryptoSymbol).toBe('cUSD') + + // Fiat amount should match value passed in deeplink await waitFor(element(by.id('SendAmountFiat'))) .toHaveText('$0.01') .withTimeout(10 * 1000) + await waitFor(element(by.id('DisplayName'))) .toHaveText('TestFaucet') .withTimeout(10 * 1000) @@ -90,12 +97,15 @@ export default HandleDeepLinkSend = () => { it('Then should handle deeplink with all attributes', async () => { const deepLinksWithAll = `celo://wallet/pay?address=${E2E_TEST_FAUCET}&amount=0.01¤cyCode=USD&token=cUSD&displayName=TestFaucet` await launchDeepLink({ url: deepLinksWithAll, newInstance: false }) - await waitFor(element(by.id('SendAmount'))) - .toHaveText('0.0067 cUSD') // alfajores uses 1.5 as price for all tokens - .withTimeout(10 * 1000) + + const cryptoSymbol = await getCryptoSymbol() + jestExpect(cryptoSymbol).toBe('cUSD') + + // Fiat amount should match value passed in deeplink await waitFor(element(by.id('SendAmountFiat'))) .toHaveText('$0.01') .withTimeout(10 * 1000) + await waitFor(element(by.id('DisplayName'))) .toHaveText('TestFaucet') .withTimeout(10 * 1000) @@ -120,12 +130,15 @@ export default HandleDeepLinkSend = () => { it('Then should handle deeplink with all attributes', async () => { const deepLinksWithAll = `celo://wallet/pay?address=${E2E_TEST_FAUCET}&amount=0.01¤cyCode=USD&token=cUSD&displayName=TestFaucet` await openDeepLink(deepLinksWithAll) - await waitFor(element(by.id('SendAmount'))) - .toHaveText('0.0067 cUSD') // alfajores uses 1.5 as price for all tokens - .withTimeout(10 * 1000) + + const cryptoSymbol = await getCryptoSymbol() + jestExpect(cryptoSymbol).toBe('cUSD') + + // Fiat amount should match value passed in deeplink await waitFor(element(by.id('SendAmountFiat'))) .toHaveText('$0.01') .withTimeout(10 * 1000) + await waitFor(element(by.id('DisplayName'))) .toHaveText('TestFaucet') .withTimeout(10 * 1000) diff --git a/e2e/src/usecases/HandleNotification.js b/e2e/src/usecases/HandleNotification.js index f2626bd889d..9982ae05a75 100644 --- a/e2e/src/usecases/HandleNotification.js +++ b/e2e/src/usecases/HandleNotification.js @@ -20,12 +20,11 @@ export default HandleNotification = () => { 'action-identifier': 'default', } - await launchApp({ newInstance: true, userNotification }) + await launchApp({ userNotification }) }) it(':ios: Launch app and deeplink to another screen', async () => { await launchApp({ - newInstance: true, userNotification: { trigger: { type: DetoxConstants.userNotificationTriggers.push, diff --git a/e2e/src/usecases/HomeFeed.js b/e2e/src/usecases/HomeFeed.js new file mode 100644 index 00000000000..33a7592ac72 --- /dev/null +++ b/e2e/src/usecases/HomeFeed.js @@ -0,0 +1,41 @@ +import jestExpect from 'expect' +import { waitForElementId } from '../utils/utils' +import { sleep } from '../../../src/utils/sleep' + +export default HomeFeed = () => { + it('should show correct information on tap of feed item', async () => { + // Load Wallet Home + await waitForElementId('WalletHome') + const items = await element(by.id('TransferFeedItem')).getAttributes() + + // Tap top TransferFeedItem + await element(by.id('TransferFeedItem')).atIndex(0).tap() + + // Assert on text based on elements returned earlier + const address = items.elements[0].label.split(' ')[0] + await expect(element(by.text(address)).atIndex(0)).toBeVisible() + + // TODO(ENG-165): enable after display is standardized between feed and transaction details + // const amount = items.elements[0].label.match(/(\d+\.\d+)/)[1] + // await expect(element(by.text(`$${amount}`)).atIndex(0)).toBeVisible() + }) + + it('should load more items on scroll', async () => { + // Tap back button if present form previous test + try { + await element(by.id('BackChevron')).tap() + } catch {} + + // Load Wallet Home + await waitForElementId('WalletHome') + const startingItems = await element(by.id('TransferFeedItem')).getAttributes() + + // Scroll to bottom - Android will scroll forever so we set a static value + await element(by.id('TransactionList')).scroll(5000, 'down') + await sleep(5000) + + // Compare initial number of items to new number of items after scroll + const endingItems = await element(by.id('TransferFeedItem')).getAttributes() + jestExpect(endingItems.elements.length).toBeGreaterThan(startingItems.elements.length) + }) +} diff --git a/e2e/src/usecases/NewAccountOnboarding.js b/e2e/src/usecases/NewAccountOnboarding.js index 3b3f2e3e185..354fe5d61e7 100644 --- a/e2e/src/usecases/NewAccountOnboarding.js +++ b/e2e/src/usecases/NewAccountOnboarding.js @@ -1,15 +1,13 @@ -import { getAddressChunks } from '../../../src/utils/address' +import { sleep } from '../../../src/utils/sleep' import { launchApp } from '../utils/retries' import { completeProtectWalletScreen, enterPinUi, + navigateToSecurity, quickOnboarding, - scrollIntoView, - waitForElementId, waitForElementByIdAndTap, - navigateToSecurity, + waitForElementId, } from '../utils/utils' -import { sleep } from '../../../src/utils/sleep' import jestExpect from 'expect' @@ -41,7 +39,6 @@ export default NewAccountOnboarding = () => { await sleep(5000) await launchApp({ delete: true, - permissions: { notifications: 'YES', contacts: 'YES' }, launchArgs: { onboardingOverrides: 'EnableBiometry,ProtectWallet,PhoneVerification,CloudBackup', }, @@ -154,10 +151,8 @@ export default NewAccountOnboarding = () => { }) it('Should be able to restore newly created account', async () => { - await device.uninstallApp() - await device.installApp() await launchApp({ - newInstance: true, + delete: true, launchArgs: { onboardingOverrides: 'EnableBiometry,ProtectWallet,PhoneVerification,CloudBackup', }, diff --git a/e2e/src/usecases/NewAccountPhoneVerification.js b/e2e/src/usecases/NewAccountPhoneVerification.js deleted file mode 100644 index c9dde667b84..00000000000 --- a/e2e/src/usecases/NewAccountPhoneVerification.js +++ /dev/null @@ -1,197 +0,0 @@ -import { - TWILIO_ACCOUNT_SID, - TWILIO_AUTH_TOKEN, - VERIFICATION_PHONE_NUMBER, -} from 'react-native-dotenv' -import { EXAMPLE_PHONE_NUMBER } from '../utils/consts' -import { launchApp } from '../utils/retries' -import { checkBalance, receiveSms } from '../utils/twilio' -import { - completeProtectWalletScreen, - enterPinUi, - navigateToProfile, - scrollIntoView, - waitForElementId, - waitForElementByIdAndTap, -} from '../utils/utils' -import { sleep } from '../../../src/utils/sleep' - -import jestExpect from 'expect' -const examplePhoneNumber = VERIFICATION_PHONE_NUMBER || EXAMPLE_PHONE_NUMBER - -export default NewAccountPhoneVerification = () => { - beforeEach(async () => { - await launchApp({ - delete: true, - permissions: { notifications: 'YES', contacts: 'YES' }, - }) - - // Create new account - await element(by.id('CreateAccountButton')).tap() - - // Accept terms - await element(by.id('scrollView')).scrollTo('bottom') - await expect(element(by.id('AcceptTermsButton'))).toBeVisible() - await element(by.id('AcceptTermsButton')).tap() - - // Set and verify pin - await enterPinUi() - await enterPinUi() - - // Protect Wallet screen - await completeProtectWalletScreen() - - // Set phone number - await expect(element(by.id('PhoneNumberField'))).toBeVisible() - await element(by.id('PhoneNumberField')).replaceText(examplePhoneNumber) - await element(by.id('PhoneNumberField')).tapReturnKey() - }) - - // Uninstall after to remove verification - afterAll(async () => { - device.uninstallApp() - }) - - // Check that Twilio SID, Auth Token and Verification Phone Number are defined - if (TWILIO_ACCOUNT_SID && TWILIO_AUTH_TOKEN && VERIFICATION_PHONE_NUMBER) { - // Log Twilio balance at start - beforeAll(async () => { - try { - await checkBalance() - } catch {} - }) - - // Conditionally skipping jest tests with an async request is currently not possible - // https://github.com/facebook/jest/issues/7245 - // https://github.com/facebook/jest/issues/11489 - // Either fix or move to nightly tests when present - // Also needs to be updated to work against tab navigation instead of drawer - // jest.retryTimes(1) - it.skip('Then should be able to verify phone number', async () => { - // Get Date at start - let date = new Date() - // Start verification - await element(by.text('Start')).tap() - - // Retrieve the verification codes from Twilio - const codes = await receiveSms(date) - - // Check that we've received 3 codes - jestExpect(codes).toHaveLength(3) - - // Enter 3 codes - for (let i = 0; i < 3; i++) { - await sleep(1000) - await waitFor(element(by.id(`VerificationCode${i}`))) - .toBeVisible() - .withTimeout(30 * 1000) - await element(by.id(`VerificationCode${i}`)).typeText(codes[i]) - } - - // Choose your own adventure (CYA screen) - await waitForElementByIdAndTap('ChooseYourAdventure/Later') - - // Assert we've arrived at the home screen - await waitFor(element(by.id('HomeAction-Send'))) - .toBeVisible() - .withTimeout(45 * 1000) - - // Assert that correct phone number is present in sidebar - await waitForElementId('Hamburger') - await element(by.id('Hamburger')).tap() - await expect(element(by.text(`${examplePhoneNumber}`))).toBeVisible() - - // Assert that 'Connect phone number' is not present in settings - await scrollIntoView('Settings', 'SettingsScrollView') - await waitFor(element(by.id('Settings'))) - .toBeVisible() - .withTimeout(30000) - await element(by.id('Settings')).tap() - await expect(element(by.text('Connect Phone Number'))).not.toBeVisible() - }) - - // Note: (Tom) Skip this test until we have a nightly suite vs pull request suite as it takes a long time - // jest.retryTimes(1) - it.skip('Then should be able to resend last 2 messages', async () => { - // Get Date at start - let date = new Date() - // Start verification - await element(by.text('Start')).tap() - - // Request codes, but wait for all 3 to verify resend codes work - const codes = await receiveSms(date) - await waitFor(element(by.id('VerificationCode0'))) - .toExist() - .withTimeout(45 * 1000) - - // Assert that we've received 3 codes - jestExpect(codes).toHaveLength(3) - - // Input first code - await element(by.id(`VerificationCode0`)).replaceText(codes[0]) - - // Wait one minute before resending - await sleep(60 * 1000) - await element(by.text('Resend 2 messages')).tap() - - // Set date and enter pin to start resend - date = new Date() - await enterPinUi() - let secondCodeSet = await receiveSms(date, 2, codes) - - // Assert that we've received at least 2 codes - jestExpect(secondCodeSet.length).toBeGreaterThanOrEqual(2) - - // Input codes two codes - for (let i = 0; i < 2; i++) { - await waitFor(element(by.id(`VerificationCode${i + 1}`))) - .toBeVisible() - .withTimeout(10 * 1000) - await element(by.id(`VerificationCode${i + 1}`)).replaceText(secondCodeSet[i]) - } - - // Choose your own adventure (CYA screen) - await waitForElementByIdAndTap('ChooseYourAdventure/Later') - - // Assert we've arrived at the home screen - await waitFor(element(by.id('HomeAction-Send'))) - .toBeVisible() - .withTimeout(30 * 1000) - - // Assert that correct phone number is present in sidebar - await waitForElementId('Hamburger') - await element(by.id('Hamburger')).tap() - await expect(element(by.text(`${examplePhoneNumber}`))).toBeVisible() - - // Assert that 'Connect phone number' is not present in settings - await scrollIntoView('Settings', 'SettingsScrollView') - await waitFor(element(by.id('Settings'))) - .toBeVisible() - .withTimeout(30 * 1000) - await element(by.id('Settings')).tap() - await expect(element(by.text('Connect Phone Number'))).not.toBeVisible() - }) - } - - // Assert correct content is visible on the phone verification screen - it('Then should have correct phone verification screen', async () => { - await expect(element(by.id('PhoneVerificationHeader'))).toBeVisible() - let skipAttributes = await element(by.text('Skip')).getAttributes() - jestExpect(skipAttributes.enabled).toBe(true) - - // Tap 'Skip' - await element(by.text('Skip')).tap() - - // Choose your own adventure (CYA screen) - await waitForElementByIdAndTap('ChooseYourAdventure/Later') - - // Assert we've arrived at the home screen - await waitForElementId('HomeAction-Send') - - // Assert that 'Connect phone number' is present in settings - await navigateToProfile() - await waitFor(element(by.text('Connect Phone Number'))) - .toBeVisible() - .withTimeout(10 * 1000) - }) -} diff --git a/e2e/src/usecases/OffRamps.js b/e2e/src/usecases/OffRamps.js index e190a018a67..b1d998a07c7 100644 --- a/e2e/src/usecases/OffRamps.js +++ b/e2e/src/usecases/OffRamps.js @@ -3,9 +3,7 @@ import { waitForElementId } from '../utils/utils' export default offRamps = () => { beforeAll(async () => { - await launchApp({ - newInstance: true, - }) + await launchApp() }) beforeEach(async () => { await reloadReactNative() diff --git a/e2e/src/usecases/OnRamps.js b/e2e/src/usecases/OnRamps.js index 62ce658618b..bdb9a1a9203 100644 --- a/e2e/src/usecases/OnRamps.js +++ b/e2e/src/usecases/OnRamps.js @@ -3,9 +3,7 @@ import { isElementVisible, waitForElementId } from '../utils/utils' export default onRamps = () => { beforeAll(async () => { - await launchApp({ - newInstance: true, - }) + await launchApp() }) beforeEach(async () => { await reloadReactNative() diff --git a/e2e/src/usecases/PINChange.js b/e2e/src/usecases/PINChange.js index 19ae5a86ebb..aa73bdb6b23 100644 --- a/e2e/src/usecases/PINChange.js +++ b/e2e/src/usecases/PINChange.js @@ -1,7 +1,7 @@ +import { sleep } from '../../../src/utils/sleep' import { ALTERNATIVE_PIN, DEFAULT_PIN } from '../utils/consts' import { reloadReactNative } from '../utils/retries' import { enterPinUi, navigateToSecurity, waitForElementId } from '../utils/utils' -import { sleep } from '../../../src/utils/sleep' export default ChangePIN = () => { it('Then should be retain changed PIN', async () => { diff --git a/e2e/src/usecases/PINRequire.js b/e2e/src/usecases/PINRequire.js index 4e0afa0fb86..39c48ccc021 100644 --- a/e2e/src/usecases/PINRequire.js +++ b/e2e/src/usecases/PINRequire.js @@ -1,5 +1,5 @@ -import { navigateToSecurity } from '../utils/utils' import { reloadReactNative } from '../utils/retries' +import { navigateToSecurity } from '../utils/utils' export default RequirePIN = () => { it('Then should be require PIN on app open', async () => { diff --git a/e2e/src/usecases/ResetAccount.js b/e2e/src/usecases/ResetAccount.js index 3234daba0d7..b4256e3ad01 100644 --- a/e2e/src/usecases/ResetAccount.js +++ b/e2e/src/usecases/ResetAccount.js @@ -1,4 +1,4 @@ -import { SAMPLE_BACKUP_KEY } from '../utils/consts' +import { E2E_WALLET_MNEMONIC } from 'react-native-dotenv' import { reloadReactNative } from '../utils/retries' import { enterPinUiIfNecessary, navigateToSecurity, waitForElementId } from '../utils/utils' @@ -31,7 +31,7 @@ export default ResetAccount = () => { // Go through the quiz. await element(by.id('backupKeySavedSwitch')).longPress() await element(by.id('backupKeyContinue')).tap() - const mnemonic = SAMPLE_BACKUP_KEY.split(' ') + const mnemonic = E2E_WALLET_MNEMONIC.split(' ') await waitForElementId(`backupQuiz/${mnemonic[0]}`) for (const word of mnemonic) { await element(by.id(`backupQuiz/${word}`)).tap() diff --git a/e2e/src/usecases/RestoreAccountOnboarding.js b/e2e/src/usecases/RestoreAccountOnboarding.js index 3977a83e061..9efc9ac3560 100644 --- a/e2e/src/usecases/RestoreAccountOnboarding.js +++ b/e2e/src/usecases/RestoreAccountOnboarding.js @@ -1,34 +1,20 @@ -import { getAddressChunks } from '../../../src/utils/address' -import { - SAMPLE_BACKUP_KEY, - SAMPLE_BACKUP_KEY_12_WORDS, - SAMPLE_WALLET_ADDRESS, - SAMPLE_WALLET_ADDRESS_12_WORDS, -} from '../utils/consts' +import { E2E_WALLET_12_WORDS_MNEMONIC, E2E_WALLET_MNEMONIC } from 'react-native-dotenv' +import { WALLET_12_WORDS_ADDRESS, WALLET_ADDRESS } from '../utils/consts' import { launchApp } from '../utils/retries' -import { - enterPinUi, - scrollIntoView, - waitForElementByIdAndTap, - waitForElementId, -} from '../utils/utils' +import { enterPinUi, scrollIntoView, waitForElementByIdAndTap } from '../utils/utils' export default RestoreAccountOnboarding = () => { beforeEach(async () => { - await device.uninstallApp() - await device.installApp() - await launchApp({ - permissions: { notifications: 'YES', contacts: 'YES' }, - }) + await launchApp({ delete: true }) }) it.each` - wordCount | phrase | walletAddress - ${'12'} | ${SAMPLE_BACKUP_KEY_12_WORDS} | ${SAMPLE_WALLET_ADDRESS_12_WORDS} - ${'24'} | ${SAMPLE_BACKUP_KEY} | ${SAMPLE_WALLET_ADDRESS} + wordCount | phrase | walletAddress | walletFunded | verifiedPhoneNumber + ${'12'} | ${E2E_WALLET_12_WORDS_MNEMONIC} | ${WALLET_12_WORDS_ADDRESS} | ${false} | ${false} + ${'24'} | ${E2E_WALLET_MNEMONIC} | ${WALLET_ADDRESS} | ${true} | ${true} `( 'restores an existing wallet using a $wordCount word recovery phrase', - async ({ phrase, walletAddress }) => { + async ({ phrase, walletAddress, walletFunded, verifiedPhoneNumber }) => { // choose restore flow await element(by.id('RestoreAccountButton')).tap() @@ -63,9 +49,15 @@ export default RestoreAccountOnboarding = () => { await scrollIntoView('Restore', 'ImportWalletKeyboardAwareScrollView') await element(by.id('ImportWalletButton')).tap() - // verification step comes after restoring wallet, skip this step - await waitForElementId('PhoneVerificationSkipHeader') - await element(by.id('PhoneVerificationSkipHeader')).tap() + if (!walletFunded) { + // case where account not funded yet. dismiss zero balance modal to continue with onboarding. + await waitForElementByIdAndTap('ConfirmUseAccountDialog/PrimaryAction') + } + + if (!verifiedPhoneNumber) { + // case where phone verification is required. skip it. + await waitForElementByIdAndTap('PhoneVerificationSkipHeader') + } // Choose your own adventure (CYA screen) await waitForElementByIdAndTap('ChooseYourAdventure/Later') diff --git a/e2e/src/usecases/SecureSend.js b/e2e/src/usecases/SecureSend.js index 57bff1561c4..4fd7207b7b9 100644 --- a/e2e/src/usecases/SecureSend.js +++ b/e2e/src/usecases/SecureSend.js @@ -1,19 +1,17 @@ +import { E2E_WALLET_PRIVATE_KEY, E2E_WALLET_SINGLE_VERIFIED_MNEMONIC } from 'react-native-dotenv' import { - SAMPLE_BACKUP_KEY_SINGLE_ADDRESS_VERIFIED, - SAMPLE_PRIVATE_KEY, - VERIFIED_PHONE_NUMBER, - SAMPLE_WALLET_ADDRESS_SINGLE_ADDRESS_VERIFIED, - SAMPLE_WALLET_ADDRESS_VERIFIED_2, + WALLET_MULTIPLE_VERIFIED_ADDRESS, + WALLET_MULTIPLE_VERIFIED_PHONE_NUMBER, + WALLET_SINGLE_VERIFIED_ADDRESS, } from '../utils/consts' import { launchApp } from '../utils/retries' import { enterPinUiIfNecessary, fundWallet, - inputNumberKeypad, - scrollIntoView, quickOnboarding, - waitForElementId, + scrollIntoView, waitForElementByIdAndTap, + waitForElementId, } from '../utils/utils' const AMOUNT_TO_SEND = '0.01' @@ -22,27 +20,23 @@ const WALLET_FUNDING_MULTIPLIER = 2.2 export default SecureSend = () => { describe('Secure send flow with phone number lookup', () => { beforeAll(async () => { - // uninstall the app to remove secure send mapping - await device.uninstallApp() - await device.installApp() // fund wallet for send await fundWallet( - SAMPLE_PRIVATE_KEY, - SAMPLE_WALLET_ADDRESS_SINGLE_ADDRESS_VERIFIED, + E2E_WALLET_PRIVATE_KEY, + WALLET_SINGLE_VERIFIED_ADDRESS, 'cUSD', `${AMOUNT_TO_SEND * WALLET_FUNDING_MULTIPLIER}` ) - await launchApp({ - newInstance: true, - permissions: { notifications: 'YES', contacts: 'YES' }, - }) - await quickOnboarding({ mnemonic: SAMPLE_BACKUP_KEY_SINGLE_ADDRESS_VERIFIED }) + await launchApp({ delete: true }) + await quickOnboarding({ mnemonic: E2E_WALLET_SINGLE_VERIFIED_MNEMONIC }) }) it('Send cUSD to phone number with multiple mappings', async () => { await waitForElementByIdAndTap('HomeAction-Send', 30_000) await waitForElementByIdAndTap('SendSelectRecipientSearchInput', 3000) - await element(by.id('SendSelectRecipientSearchInput')).replaceText(VERIFIED_PHONE_NUMBER) + await element(by.id('SendSelectRecipientSearchInput')).replaceText( + WALLET_MULTIPLE_VERIFIED_PHONE_NUMBER + ) await element(by.id('RecipientItem')).tap() await waitForElementByIdAndTap('SendOrInviteButton', 30_000) @@ -50,8 +44,8 @@ export default SecureSend = () => { // Use the last digits of the account to confirm the sender. await waitForElementByIdAndTap('confirmAccountButton', 30_000) for (let index = 0; index < 4; index++) { - const character = SAMPLE_WALLET_ADDRESS_VERIFIED_2.charAt( - SAMPLE_WALLET_ADDRESS_VERIFIED_2.length - (4 - index) + const character = WALLET_MULTIPLE_VERIFIED_ADDRESS.charAt( + WALLET_MULTIPLE_VERIFIED_ADDRESS.length - (4 - index) ) await element(by.id(`SingleDigitInput/digit${index}`)).replaceText(character) } diff --git a/e2e/src/usecases/Send.js b/e2e/src/usecases/Send.js index 88c868272cd..c3799e649dd 100644 --- a/e2e/src/usecases/Send.js +++ b/e2e/src/usecases/Send.js @@ -1,13 +1,13 @@ import jestExpect from 'expect' import { DEFAULT_RECIPIENT_ADDRESS, - SAMPLE_BACKUP_KEY_VERIFIED, - SINGLE_ADDRESS_VERIFIED_PHONE_NUMBER, - SINGLE_ADDRESS_VERIFIED_PHONE_NUMBER_DISPLAY, + WALLET_SINGLE_VERIFIED_PHONE_NUMBER, + WALLET_SINGLE_VERIFIED_PHONE_NUMBER_DISPLAY, } from '../utils/consts' import { launchApp } from '../utils/retries' import { enterPinUiIfNecessary, + getDisplayAddress, isElementVisible, quickOnboarding, waitForElementByIdAndTap, @@ -15,13 +15,14 @@ import { } from '../utils/utils' export default Send = () => { + const recipientAddressDisplay = getDisplayAddress(DEFAULT_RECIPIENT_ADDRESS) beforeAll(async () => { await quickOnboarding() }) describe('When multi-token send flow to address', () => { beforeAll(async () => { - await launchApp({ newInstance: true }) + await launchApp() }) it('Then should navigate to send search input from home action', async () => { @@ -33,11 +34,11 @@ export default Send = () => { await waitForElementByIdAndTap('SendSelectRecipientSearchInput', 30_000) await element(by.id('SendSelectRecipientSearchInput')).replaceText(DEFAULT_RECIPIENT_ADDRESS) await element(by.id('SendSelectRecipientSearchInput')).tapReturnKey() - await expect(element(by.text('0xe5f5...8846')).atIndex(0)).toBeVisible() + await expect(element(by.text(recipientAddressDisplay)).atIndex(0)).toBeVisible() }) it('Then tapping a recipient should show send button', async () => { - await element(by.text('0xe5f5...8846')).atIndex(0).tap() + await element(by.text(recipientAddressDisplay)).atIndex(0).tap() await waitForElementId('SendOrInviteButton', 30_000) }) @@ -60,14 +61,14 @@ export default Send = () => { it('Then should be able to enter amount and navigate to review screen', async () => { await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000) - await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01') + await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.02') await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey() await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000) await isElementVisible('ConfirmButton') }) it('Then should display correct recipient', async () => { - await expect(element(by.text('0xe5f5...8846'))).toBeVisible() + await expect(element(by.text(recipientAddressDisplay))).toBeVisible() }) it('Then should be able to edit amount', async () => { @@ -92,18 +93,18 @@ export default Send = () => { describe('When multi-token send flow to recent recipient', () => { beforeAll(async () => { - await launchApp({ newInstance: true }) + await launchApp() }) it('Then should navigate to send search input from home action', async () => { await waitForElementByIdAndTap('HomeAction-Send', 30_000) - await waitFor(element(by.text('0xe5f5...8846'))) + await waitFor(element(by.text(recipientAddressDisplay))) .toBeVisible() .withTimeout(10_000) }) it('Then should be able to click on recent recipient', async () => { - await element(by.text('0xe5f5...8846')).atIndex(0).tap() + await element(by.text(recipientAddressDisplay)).atIndex(0).tap() await waitForElementId('SendEnterAmount/TokenAmountInput', 30_000) }) @@ -122,7 +123,7 @@ export default Send = () => { }) it('Then should display correct recipient', async () => { - await expect(element(by.text('0xe5f5...8846'))).toBeVisible() + await expect(element(by.text(recipientAddressDisplay))).toBeVisible() }) it('Then should be able to send', async () => { @@ -135,10 +136,8 @@ export default Send = () => { describe('When multi-token send flow to phone number with one address', () => { beforeAll(async () => { - await device.uninstallApp() - await device.installApp() - await launchApp({ newInstance: true }) - await quickOnboarding({ mnemonic: SAMPLE_BACKUP_KEY_VERIFIED }) + await launchApp({ delete: true }) + await quickOnboarding() }) it('Then should navigate to send search input from home action', async () => { @@ -149,7 +148,7 @@ export default Send = () => { it('Then should be able to enter a phone number', async () => { await waitForElementByIdAndTap('SendSelectRecipientSearchInput', 30_000) await element(by.id('SendSelectRecipientSearchInput')).typeText( - SINGLE_ADDRESS_VERIFIED_PHONE_NUMBER + WALLET_SINGLE_VERIFIED_PHONE_NUMBER ) await element(by.id('SendSelectRecipientSearchInput')).tapReturnKey() await isElementVisible('RecipientItem', 0) @@ -180,7 +179,7 @@ export default Send = () => { }) it('Then should display correct recipient', async () => { - await expect(element(by.text(SINGLE_ADDRESS_VERIFIED_PHONE_NUMBER_DISPLAY))).toBeVisible() + await expect(element(by.text(WALLET_SINGLE_VERIFIED_PHONE_NUMBER_DISPLAY))).toBeVisible() }) it('Then should be able to send', async () => { diff --git a/e2e/src/usecases/Settings.js b/e2e/src/usecases/Settings.js index 4d3c37a2486..ade5313a4e3 100644 --- a/e2e/src/usecases/Settings.js +++ b/e2e/src/usecases/Settings.js @@ -1,12 +1,12 @@ +import { sleep } from '../../../src/utils/sleep' import { dismissBanners } from '../utils/banners' -import { reloadReactNative, launchApp } from '../utils/retries' +import { reloadReactNative } from '../utils/retries' import { + navigateToPreferences, navigateToProfile, scrollIntoView, waitForElementByIdAndTap, - navigateToPreferences, } from '../utils/utils' -import { sleep } from '../../../src/utils/sleep' const faker = require('@faker-js/faker') diff --git a/e2e/src/usecases/Support.js b/e2e/src/usecases/Support.js index fc0351e5087..dd8ce1dfbd0 100644 --- a/e2e/src/usecases/Support.js +++ b/e2e/src/usecases/Support.js @@ -1,5 +1,5 @@ -import { reloadReactNative, launchApp } from '../utils/retries' -import { scrollIntoView, waitForElementId, waitForElementByIdAndTap } from '../utils/utils' +import { reloadReactNative } from '../utils/retries' +import { waitForElementByIdAndTap } from '../utils/utils' export default Support = () => { beforeEach(async () => { diff --git a/e2e/src/usecases/WalletConnectV2.js b/e2e/src/usecases/WalletConnectV2.js index 7e6623366dc..82049e852a2 100644 --- a/e2e/src/usecases/WalletConnectV2.js +++ b/e2e/src/usecases/WalletConnectV2.js @@ -1,30 +1,29 @@ import { Core } from '@walletconnect/core' import Client from '@walletconnect/sign-client' -import { WALLET_CONNECT_PROJECT_ID_E2E } from 'react-native-dotenv' +import { E2E_WALLET_CONNECT_PROJECT_ID } from 'react-native-dotenv' import { + createPublicClient, hashMessage, hexToNumber, + http, verifyMessage, verifyTypedData, - createPublicClient, - http, } from 'viem' -import { celoAlfajores } from 'viem/chains' import { parseTransaction } from 'viem/celo' +import { celo } from 'viem/chains' +import { sleep } from '../../../src/utils/sleep' +import WALLET_ADDRESS from '../utils/consts' import { formatUri, utf8ToHex } from '../utils/encoding' import { launchApp } from '../utils/retries' import { enterPinUiIfNecessary, waitForElementByIdAndTap } from '../utils/utils' -import { sleep } from '../../../src/utils/sleep' import jestExpect from 'expect' const dappName = 'WalletConnectV2 E2E' -const walletAddress = ( - process.env.E2E_WALLET_ADDRESS || '0x6131a6d616a4be3737b38988847270a64bc10caa' -).toLowerCase() +const walletAddress = (WALLET_ADDRESS || '0xebf95355cc5ea643179a02337f3f943fd8dd2bcb').toLowerCase() const client = createPublicClient({ - chain: celoAlfajores, + chain: celo, transport: http(), }) @@ -101,7 +100,7 @@ export default WalletConnect = () => { })) core = await Core.init({ - projectId: WALLET_CONNECT_PROJECT_ID_E2E, + projectId: E2E_WALLET_CONNECT_PROJECT_ID, relayUrl: 'wss://relay.walletconnect.org', }) @@ -129,7 +128,7 @@ export default WalletConnect = () => { 'personal_sign', 'eth_signTypedData', ], - chains: ['eip155:44787'], + chains: ['eip155:42220'], events: ['chainChanged', 'accountsChanged'], }, }, @@ -151,7 +150,7 @@ export default WalletConnect = () => { it('Then is able to establish a session', async () => { if (device.getPlatform() === 'android') { - await launchApp({ url: pairingUrl, newInstance: true }) + await launchApp({ url: pairingUrl }) } else { await device.openURL({ url: pairingUrl }) } @@ -171,7 +170,7 @@ export default WalletConnect = () => { const [session] = walletConnectClient.session.map.values() const requestPromise = walletConnectClient.request({ topic: session.topic, - chainId: 'eip155:44787', + chainId: 'eip155:42220', request: { method: 'eth_sendTransaction', params: [tx], @@ -204,7 +203,7 @@ export default WalletConnect = () => { const [session] = walletConnectClient.session.map.values() const requestPromise = walletConnectClient.request({ topic: session.topic, - chainId: 'eip155:44787', + chainId: 'eip155:42220', request: { method: 'eth_signTransaction', params: [tx], @@ -214,6 +213,7 @@ export default WalletConnect = () => { await waitFor(element(by.text(new RegExp(`^${dappName} would like to sign a transaction.*`)))) .toBeVisible() .withTimeout(15 * 1000) + await verifySuccessfulTransaction('Sign transaction', tx) const signedTx = await requestPromise @@ -232,7 +232,7 @@ export default WalletConnect = () => { const [session] = walletConnectClient.session.map.values() const requestPromise = walletConnectClient.request({ topic: session.topic, - chainId: 'eip155:44787', + chainId: 'eip155:42220', request: { method: 'eth_sign', params, @@ -260,7 +260,7 @@ export default WalletConnect = () => { const [session] = walletConnectClient.session.map.values() const requetPromise = walletConnectClient.request({ topic: session.topic, - chainId: 'eip155:44787', + chainId: 'eip155:42220', request: { method: 'personal_sign', params, @@ -336,7 +336,7 @@ export default WalletConnect = () => { const [session] = walletConnectClient.session.map.values() const requestPromise = walletConnectClient.request({ topic: session.topic, - chainId: 'eip155:44787', + chainId: 'eip155:42220', request: { method: 'eth_signTypedData', params, diff --git a/e2e/src/utils/celoEducation.js b/e2e/src/utils/celoEducation.js index 97d76599130..66440f5d68c 100644 --- a/e2e/src/utils/celoEducation.js +++ b/e2e/src/utils/celoEducation.js @@ -1,5 +1,5 @@ -import { isElementVisible } from './utils' import { sleep } from '../../../src/utils/sleep' +import { isElementVisible } from './utils' export const celoEducation = async () => { // Not ideal, but needed to help with flakiness. diff --git a/e2e/src/utils/consts.js b/e2e/src/utils/consts.js index 0ed6e417b45..65b3b58e834 100644 --- a/e2e/src/utils/consts.js +++ b/e2e/src/utils/consts.js @@ -1,26 +1,18 @@ -export const DEFAULT_RECIPIENT_ADDRESS = '0xe5F5363e31351C38ac82DBAdeaD91Fd5a7B08846' -export const SAMPLE_BACKUP_KEY = - 'general debate dial flock want basket local machine effort monitor stomach purity attend brand extend salon obscure soul open floor useful like cause exhaust' -export const SAMPLE_PRIVATE_KEY = // corresponds to backup key above - '0x34c78f42ec153668070bd55c0d237adcdfee0359f1ea473b8e16c8d5f99be14a' -export const SAMPLE_WALLET_ADDRESS = '0x6131a6d616a4be3737b38988847270a64bc10caa' // corresponds to the backup key above -export const SAMPLE_BACKUP_KEY_VERIFIED = - 'embody siege middle glory soda solar nasty report swap now never any' -export const SAMPLE_WALLET_ADDRESS_VERIFIED = '0x86b8f44386cb2d457db79c3dab8cf42f9d8a3fc0' // corresponds to the backup key above -export const SAMPLE_BACKUP_KEY_VERIFIED_2 = - 'bench album relax truth pond orchard diet unaware cloud tackle twin tongue' -export const SAMPLE_WALLET_ADDRESS_VERIFIED_2 = '0x5fe1407f47b1310ff232a8d368b36099eff61604' // corresponds to the backup key above -export const VERIFIED_PHONE_NUMBER = '+15203140983' // all verified addresses are using this number -export const SAMPLE_BACKUP_KEY_SINGLE_ADDRESS_VERIFIED = - 'brain host balcony anger rail album spice lawn reject soldier sunny sell' -export const SAMPLE_WALLET_ADDRESS_SINGLE_ADDRESS_VERIFIED = - '0x06502700eac7123676a7332ba2015dffba021af6' // corresponds to the backup key above -export const SINGLE_ADDRESS_VERIFIED_PHONE_NUMBER = '+14154980301' // is linked to the single address above -export const SINGLE_ADDRESS_VERIFIED_PHONE_NUMBER_DISPLAY = '(415) 498-0301' -export const SAMPLE_BACKUP_KEY_12_WORDS = - 'cheese size muscle either false spend price lunar undo share kite whisper' -export const SAMPLE_WALLET_ADDRESS_12_WORDS = '0x19cf37810fd5933bd63e02388e37203841c703de' // corresponds to the backup key above +export const DEFAULT_RECIPIENT_ADDRESS = '0xa694b396cd6f73e4003da8f97f4b12e498b3f2ec' +export const WALLET_ADDRESS = '0xebf95355cc5ea643179a02337f3f943fd8dd2bcb' +export const WALLET_PHONE_NUMBER = '+1 702-706-4712' +export const WALLET_NUMBER_DISPLAY = '(702) 706-4712' + +export const WALLET_SINGLE_VERIFIED_ADDRESS = '0xdfc6be410421fcf1842aea7139d101eee549ab1b' +export const WALLET_SINGLE_VERIFIED_PHONE_NUMBER = '+1 347-674-6098' +export const WALLET_SINGLE_VERIFIED_PHONE_NUMBER_DISPLAY = '(347) 674-6098' + +export const WALLET_MULTIPLE_VERIFIED_ADDRESS = '0x06f4b680c6cb1aeec4a3ce12c63ea18acb136aa3' +export const WALLET_MULTIPLE_VERIFIED_PHONE_NUMBER = '+1 404-590-7154' +export const WALLET_MULTIPLE_VERIFIED_PHONE_NUMBER_DISPLAY = '(404) 590-7154' + +export const WALLET_12_WORDS_ADDRESS = '0x8095a02317b490c9277de7f7fa2e4a723b83703d' + export const DEFAULT_PIN = '193705' export const ALTERNATIVE_PIN = '507391' export const EXAMPLE_PHONE_NUMBER = '+1 306-555-5555' -export const ALFAJORES_FORNO_URL = 'https://alfajores-forno.celo-testnet.org' diff --git a/e2e/src/utils/dappList.js b/e2e/src/utils/dappList.js index f71e96c1e30..69e047e133b 100644 --- a/e2e/src/utils/dappList.js +++ b/e2e/src/utils/dappList.js @@ -1,5 +1,5 @@ -import { waitForElementId } from './utils' import fetch from 'node-fetch' +import { waitForElementId } from './utils' /** * From the home screen, navigate to the dapp explorer screen diff --git a/e2e/src/utils/retries.js b/e2e/src/utils/retries.js index ca685435c6f..2fbd3e7f9d1 100644 --- a/e2e/src/utils/retries.js +++ b/e2e/src/utils/retries.js @@ -1,14 +1,20 @@ +import { merge } from 'lodash' import { retry } from 'ts-retry-promise' -export const launchApp = async ( - launchArgs = { - newInstance: true, - permissions: { notifications: 'YES', contacts: 'YES', camera: 'YES' }, - launchArgs: { - detoxPrintBusyIdleResources: 'YES', - }, - } -) => { +const defaultLaunchArgs = { + newInstance: true, + permissions: { notifications: 'YES', contacts: 'YES', camera: 'YES' }, + launchArgs: { + detoxPrintBusyIdleResources: 'YES', + // Use new tx feed from Zerion by default + statsigGateOverrides: 'show_zerion_transaction_feed=true', + }, +} + +export const launchApp = async (customArgs = {}) => { + // Deep merge customArgs into defaultLaunchArgs + const launchArgs = merge({}, defaultLaunchArgs, customArgs) + await retry( async () => { try { @@ -19,9 +25,7 @@ export const launchApp = async ( } }, { retries: 5, delay: 10 * 1000, timeout: 30 * 10000 } - ).then(async () => { - await device.setURLBlacklist(['.*blockchain-api-dot-celo-mobile-alfajores.*']) - }) + ) } export const reloadReactNative = async () => { @@ -32,7 +36,7 @@ export const reloadReactNative = async () => { } catch (error) { // eslint-disable-next-line no-console console.error('Failed to reload react native with error', error) - await launchApp() + await launchApp(defaultLaunchArgs) } }, { retries: 5, delay: 10 * 1000, timeout: 30 * 10000 } diff --git a/e2e/src/utils/utils.js b/e2e/src/utils/utils.js index efbea32ecd6..2a573711ae4 100644 --- a/e2e/src/utils/utils.js +++ b/e2e/src/utils/utils.js @@ -1,9 +1,9 @@ +import { E2E_WALLET_MNEMONIC } from 'react-native-dotenv' import { createWalletClient, encodeFunctionData, erc20Abi, http, publicActions } from 'viem' -import { celoAlfajores } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' -import jestExpect from 'expect' -import { DEFAULT_PIN, SAMPLE_BACKUP_KEY } from '../utils/consts' +import { celo } from 'viem/chains' import { sleep } from '../../../src/utils/sleep' +import { DEFAULT_PIN } from '../utils/consts' const childProcess = require('child_process') const fs = require('fs') @@ -149,7 +149,7 @@ export async function waitForElementByIdAndTap(elementId, timeout = 10 * 1000, i } export async function quickOnboarding({ - mnemonic = SAMPLE_BACKUP_KEY, + mnemonic = E2E_WALLET_MNEMONIC, cloudBackupEnabled = false, stopOnCYA = false, } = {}) { @@ -177,8 +177,7 @@ export async function quickOnboarding({ .withTimeout(20000) // Input Wallet Backup Key - await sleep(3000) - await element(by.id('ImportWalletBackupKeyInputField')).tap() + await waitForElementByIdAndTap('ImportWalletBackupKeyInputField') await element(by.id('ImportWalletBackupKeyInputField')).replaceText(mnemonic) if (device.getPlatform() === 'ios') { // On iOS, type one more space to workaround onChangeText not being triggered with replaceText above @@ -190,7 +189,9 @@ export async function quickOnboarding({ } await scrollIntoView('Restore', 'ImportWalletKeyboardAwareScrollView') - await element(by.id('ImportWalletButton')).tap() + await waitForElementByIdAndTap('ImportWalletButton') + // Wait for the wallet to restored + await sleep(5 * 1000) try { // case where account not funded yet. continue with onboarding. @@ -314,13 +315,14 @@ export function padTrailingZeros(num, size = 5) { return s } -export async function waitForElementByText(text, timeout = 30_000, index = 0) { +export async function waitForElementByText({ text, index, tap = false, timeout = 30_000 }) { try { - index === 0 - ? await waitFor(element(by.text(text))) - : await waitFor(element(by.text(text)).atIndex(index)) - .toBeVisible() - .withTimeout(timeout) + const elementMatcher = + index !== undefined ? element(by.text(text)).atIndex(index) : element(by.text(text)) + + await waitFor(elementMatcher).toBeVisible().withTimeout(timeout) + + if (tap) await elementMatcher.tap() } catch { throw new Error(`Element with text '${text}' not found`) } @@ -378,20 +380,19 @@ export async function completeProtectWalletScreen() { */ export async function fundWallet(senderPrivateKey, recipientAddress, stableToken, amountEther) { const stableTokenSymbolToAddress = { - cUSD: '0x874069fa1eb16d44d622f2e0ca25eea172369bc1', + cUSD: '0x765de816845861e75a25fca122bb6898b8b1282a', } const tokenAddress = stableTokenSymbolToAddress[stableToken] if (!tokenAddress) { throw new Error(`Unsupported token symbol passed to fundWallet: ${stableToken}`) } - console.log(`Sending ${amountEther} ${stableToken} from ${senderAddress} to ${recipientAddress}`) - const account = privateKeyToAccount(senderPrivateKey) const senderAddress = account.address + console.log(`Sending ${amountEther} ${stableToken} from ${senderAddress} to ${recipientAddress}`) const client = createWalletClient({ account, - chain: celoAlfajores, + chain: celo, transport: http(), }).extend(publicActions) @@ -424,3 +425,7 @@ export async function navigateToPreferences() { await waitForElementByIdAndTap('WalletHome/SettingsGearButton') await waitForElementByIdAndTap('SettingsMenu/Preferences') } + +export const getDisplayAddress = (address) => { + return `${address.slice(0, 6)}...${address.slice(-4)}` +} diff --git a/src/analytics/AppAnalytics.ts b/src/analytics/AppAnalytics.ts index b8b8788c580..3697dcc1df1 100644 --- a/src/analytics/AppAnalytics.ts +++ b/src/analytics/AppAnalytics.ts @@ -160,7 +160,7 @@ class AppAnalytics { isEnabled() { // Remove __DEV__ here to test analytics in dev builds - return !__DEV__ && store.getState().app.analyticsEnabled + return !__DEV__ && !isE2EEnv && store.getState().app.analyticsEnabled } startSession(