diff --git a/.github/workflows/ci_e2e_walletkit.yaml b/.github/workflows/ci_e2e_walletkit.yaml index 4025e205..742e62e4 100644 --- a/.github/workflows/ci_e2e_walletkit.yaml +++ b/.github/workflows/ci_e2e_walletkit.yaml @@ -22,11 +22,6 @@ on: required: false default: 'pay' type: string - app-id: - description: 'App bundle ID / package name for Maestro' - required: false - default: 'com.walletconnect.web3wallet.rnsample.internal' - type: string platform: description: 'Platform to run tests on' required: false @@ -39,7 +34,7 @@ on: env: MAESTRO_TAGS: ${{ inputs.maestro-tags || 'pay' }} - MAESTRO_APP_ID: ${{ inputs.app-id || 'com.walletconnect.web3wallet.rnsample.internal' }} + MAESTRO_APP_ID: com.walletconnect.web3wallet.rnsample.internal ROOT_PATH: wallets/rn_cli_wallet jobs: @@ -63,6 +58,11 @@ jobs: sudo xcode-select -s "$XCODE_PATH" xcodebuild -version + - name: Setup SSH for Match + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.MATCH_SSH_KEY }} + - name: Setup uses: ./.github/actions/ci-setup with: @@ -76,6 +76,7 @@ jobs: fi echo "${{ secrets.WALLETKIT_ENV_FILE }}" > ${{ env.ROOT_PATH }}/.env echo "ENV_TEST_PRIVATE_KEY=${{ secrets.TEST_WALLET_PRIVATE_KEY }}" >> ${{ env.ROOT_PATH }}/.env + echo "ENV_TEST_MODE=true" >> ${{ env.ROOT_PATH }}/.env - name: Install Ruby uses: ruby/setup-ruby@v1 @@ -114,6 +115,11 @@ jobs: BUILD_FOR_SIMULATOR: true PODS_CACHE_HIT: ${{ steps.pods-cache.outputs.cache-hit }} SENTRY_DISABLE_AUTO_UPLOAD: true + APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} + APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }} + APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + MATCH_PASSWORD: ${{ secrets.MATCH_KEYCHAIN_PASSWORD }} - name: Find .app path run: | @@ -136,22 +142,18 @@ jobs: curl -fsSL "https://get.maestro.mobile.dev" | bash echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - - name: Create payment via API - id: payment - run: bash scripts/create-payment.sh - env: - WPAY_CUSTOMER_KEY: ${{ secrets.WPAY_CUSTOMER_KEY }} - WPAY_MERCHANT_ID: ${{ secrets.WPAY_MERCHANT_ID }} - - - name: Set payment URL in simulator clipboard - run: echo "${{ steps.payment.outputs.gateway_url }}" | xcrun simctl pbcopy "$DEVICE_ID" - - name: Run Maestro tests id: maestro run: | set -o pipefail maestro test \ --env APP_ID="${{ env.MAESTRO_APP_ID }}" \ + --env WPAY_CUSTOMER_KEY_MULTI_KYC="${{ secrets.WPAY_CUSTOMER_KEY_MULTI_KYC }}" \ + --env WPAY_MERCHANT_ID_MULTI_KYC="${{ secrets.WPAY_MERCHANT_ID_MULTI_KYC }}" \ + --env WPAY_CUSTOMER_KEY_MULTI_NOKYC="${{ secrets.WPAY_CUSTOMER_KEY_MULTI_NOKYC }}" \ + --env WPAY_MERCHANT_ID_MULTI_NOKYC="${{ secrets.WPAY_MERCHANT_ID_MULTI_NOKYC }}" \ + --env WPAY_CUSTOMER_KEY_SINGLE_NOKYC="${{ secrets.WPAY_CUSTOMER_KEY_SINGLE_NOKYC }}" \ + --env WPAY_MERCHANT_ID_SINGLE_NOKYC="${{ secrets.WPAY_MERCHANT_ID_SINGLE_NOKYC }}" \ --include-tags "${{ env.MAESTRO_TAGS }}" \ --test-output-dir maestro-artifacts \ --debug-output maestro-artifacts \ @@ -251,6 +253,12 @@ jobs: - name: Accept Android Licenses uses: SimonMarquis/android-accept-licenses@8d8deab5b7ab2aaea9ed69f0b802e53173f21fd1 # v1 + - name: Add Secrets file + run: echo "${{ secrets.ANDROID_SECRETS_FILE }}" > ${{ env.ROOT_PATH }}/android/secrets.properties + + - name: Add Keystore file + run: echo ${{ secrets.WC_INTERNAL_KEYSTORE }} | base64 --decode >> ${{ env.ROOT_PATH }}/android/app/${{ vars.WC_INTERNAL_KEYSTORE_NAME }}.keystore + - name: Optimize Gradle for CI run: | echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m" >> ${{ env.ROOT_PATH }}/android/gradle.properties @@ -276,6 +284,12 @@ jobs: id: build run: cd ${{ env.ROOT_PATH }} && export SENTRY_DISABLE_AUTO_UPLOAD=true && yarn run android:build:internal + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android/sdk/ndk /opt/ghc /usr/local/share/boost + sudo apt-get clean + df -h + - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules @@ -287,13 +301,6 @@ jobs: curl -fsSL "https://get.maestro.mobile.dev" | bash echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - - name: Create payment via API - id: payment - run: bash scripts/create-payment.sh - env: - WPAY_CUSTOMER_KEY: ${{ secrets.WPAY_CUSTOMER_KEY }} - WPAY_MERCHANT_ID: ${{ secrets.WPAY_MERCHANT_ID }} - - name: Run E2E tests on Android Emulator id: maestro uses: reactivecircus/android-emulator-runner@70f4dee990796918b78d040e3278474bdbd348a7 # v2 @@ -303,6 +310,7 @@ jobs: target: default avd-name: test_device ram-size: 4096M + disk-size: 4096M heap-size: 576M emulator-boot-timeout: 900 force-avd-creation: false @@ -310,7 +318,7 @@ jobs: disable-animations: true script: | adb install ${{ env.ROOT_PATH }}/android/app/build/outputs/apk/internal/app-internal.apk - $HOME/.maestro/bin/maestro test --env APP_ID="${{ env.MAESTRO_APP_ID }}" --env GATEWAY_URL="${{ steps.payment.outputs.gateway_url }}" --include-tags "${{ env.MAESTRO_TAGS }}" --test-output-dir maestro-artifacts --debug-output maestro-artifacts .maestro/ >maestro-output.log 2>&1; echo $? > maestro-exit-code + $HOME/.maestro/bin/maestro test --env APP_ID="${{ env.MAESTRO_APP_ID }}" --env WPAY_CUSTOMER_KEY_MULTI_KYC="${{ secrets.WPAY_CUSTOMER_KEY_MULTI_KYC }}" --env WPAY_MERCHANT_ID_MULTI_KYC="${{ secrets.WPAY_MERCHANT_ID_MULTI_KYC }}" --env WPAY_CUSTOMER_KEY_MULTI_NOKYC="${{ secrets.WPAY_CUSTOMER_KEY_MULTI_NOKYC }}" --env WPAY_MERCHANT_ID_MULTI_NOKYC="${{ secrets.WPAY_MERCHANT_ID_MULTI_NOKYC }}" --env WPAY_CUSTOMER_KEY_SINGLE_NOKYC="${{ secrets.WPAY_CUSTOMER_KEY_SINGLE_NOKYC }}" --env WPAY_MERCHANT_ID_SINGLE_NOKYC="${{ secrets.WPAY_MERCHANT_ID_SINGLE_NOKYC }}" --include-tags "${{ env.MAESTRO_TAGS }}" --test-output-dir maestro-artifacts --debug-output maestro-artifacts .maestro/ >maestro-output.log 2>&1; echo $? > maestro-exit-code cat maestro-output.log exit $(cat maestro-exit-code) diff --git a/.github/workflows/e2e-balance-check.yml b/.github/workflows/e2e-balance-check.yml index 6aeb162d..89b1b5cc 100644 --- a/.github/workflows/e2e-balance-check.yml +++ b/.github/workflows/e2e-balance-check.yml @@ -11,10 +11,12 @@ permissions: env: BASE_USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' BASE_RPC: 'https://mainnet.base.org' + OP_USDC: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' + OP_RPC: 'https://mainnet.optimism.io' jobs: check-balance: - name: Check E2E Wallet USDC Balance + name: Check E2E Wallet Balances runs-on: ubuntu-latest env: SLACK_FAUCETBOT_WEBHOOK_URL: ${{ secrets.SLACK_FAUCETBOT_WEBHOOK_URL }} @@ -27,13 +29,13 @@ jobs: run: | BALANCE=$(cast call --rpc-url "$BASE_RPC" "$BASE_USDC" \ "balanceOf(address)(uint256)" \ - "${{ vars.TEST_WALLET_ADDRESS }}" | awk '{print $1}') + "${{ vars.TEST_WALLET_ADDRESS }}" | tr -d '[:space:]') echo "balance=$BALANCE" >> $GITHUB_OUTPUT BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) echo "USDC on Base: $BALANCE_HUMAN ($BALANCE raw)" - - name: Prepare alert - id: alert + - name: Prepare USDC alert + id: usdc_alert run: | BALANCE="${{ steps.usdc_balance.outputs.balance }}" THRESHOLD="${{ vars.USDC_THRESHOLD_UNITS || '500000' }}" @@ -47,9 +49,9 @@ jobs: echo "::warning::$ALERT_TEXT" fi - - name: Send Slack alert + - name: Send Slack alert (USDC) if: | - steps.alert.outputs.should_alert == 'true' && + steps.usdc_alert.outputs.should_alert == 'true' && env.SLACK_FAUCETBOT_WEBHOOK_URL != '' uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: @@ -57,12 +59,57 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ steps.alert.outputs.text }}" + "text": "${{ steps.usdc_alert.outputs.text }}" } - - name: Skip Slack alert (no webhook) + - name: Skip Slack alert (USDC - no webhook) if: | - steps.alert.outputs.should_alert == 'true' && + steps.usdc_alert.outputs.should_alert == 'true' && env.SLACK_FAUCETBOT_WEBHOOK_URL == '' run: | - echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping alert" + echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping USDC alert" + + - name: Check USDC balance on Optimism + id: op_usdc_balance + run: | + BALANCE=$(cast call --rpc-url "$OP_RPC" "$OP_USDC" \ + "balanceOf(address)(uint256)" \ + "${{ vars.TEST_WALLET_ADDRESS }}" | tr -d '[:space:]') + echo "balance=$BALANCE" >> $GITHUB_OUTPUT + BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) + echo "USDC on Optimism: $BALANCE_HUMAN ($BALANCE raw)" + + - name: Prepare USDC on Optimism alert + id: op_usdc_alert + run: | + BALANCE="${{ steps.op_usdc_balance.outputs.balance }}" + THRESHOLD="${{ vars.USDC_THRESHOLD_UNITS || '500000' }}" + if [ "$BALANCE" -lt "$THRESHOLD" ]; then + BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) + THRESHOLD_HUMAN=$(echo "scale=2; $THRESHOLD / 1000000" | bc) + ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} USDC on Optimism in wallet: \ + ${{ vars.TEST_WALLET_ADDRESS }}" + echo "should_alert=true" >> "$GITHUB_OUTPUT" + echo "text=$ALERT_TEXT" >> "$GITHUB_OUTPUT" + echo "::warning::$ALERT_TEXT" + fi + + - name: Send Slack alert (USDC on Optimism) + if: | + steps.op_usdc_alert.outputs.should_alert == 'true' && + env.SLACK_FAUCETBOT_WEBHOOK_URL != '' + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 + with: + webhook: ${{ env.SLACK_FAUCETBOT_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.op_usdc_alert.outputs.text }}" + } + + - name: Skip Slack alert (USDC on Optimism - no webhook) + if: | + steps.op_usdc_alert.outputs.should_alert == 'true' && + env.SLACK_FAUCETBOT_WEBHOOK_URL == '' + run: | + echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Optimism USDC alert" diff --git a/.maestro/flows/pay_confirm_and_verify.yaml b/.maestro/flows/pay_confirm_and_verify.yaml new file mode 100644 index 00000000..d033ee1b --- /dev/null +++ b/.maestro/flows/pay_confirm_and_verify.yaml @@ -0,0 +1,21 @@ +appId: ${APP_ID} +--- +# Shared flow: Tap Pay, verify loading, wait for success, tap "Got it!" + +# On review screen, tap Pay button +- extendedWaitUntil: + visible: + id: "pay-button-pay" + timeout: 10000 +- tapOn: + id: "pay-button-pay" + +# Wait for success screen (generous timeout for on-chain confirmation) +- extendedWaitUntil: + visible: + id: "pay-result-success-icon" + timeout: 30000 + +# Tap "Got it!" button +- tapOn: + id: "pay-button-result-action" diff --git a/.maestro/flows/pay_open_and_paste_url.yaml b/.maestro/flows/pay_open_and_paste_url.yaml new file mode 100644 index 00000000..b2e5b118 --- /dev/null +++ b/.maestro/flows/pay_open_and_paste_url.yaml @@ -0,0 +1,36 @@ +appId: ${APP_ID} +--- +# Shared flow: Launch wallet, open scanner, paste payment URL, wait for merchant info +# Requires: ${output.gateway_url} to be set by a prior runScript step + +# Launch wallet app +- launchApp: + appId: ${APP_ID} + permissions: + all: allow + +# Tap scan button to open scanner options modal +- tapOn: + id: "button-scan" + +# Type the payment URL into the test input field +- tapOn: + id: "input-paste-url" + +# Dismiss iOS keyboard language prompt if it appears +- runFlow: + when: + visible: "Continue" + commands: + - tapOn: "Continue" + +- inputText: ${output.gateway_url} +- pressKey: Enter +- tapOn: + id: "button-submit-url" + +# Wait for payment options to load with merchant info +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 diff --git a/.maestro/flows/pay_open_via_deeplink.yaml b/.maestro/flows/pay_open_via_deeplink.yaml new file mode 100644 index 00000000..56d8a1e5 --- /dev/null +++ b/.maestro/flows/pay_open_via_deeplink.yaml @@ -0,0 +1,13 @@ +appId: ${APP_ID} +--- +# Shared flow: Open payment URL via deep link and wait for merchant info +# Requires: ${output.gateway_url} to be set by a prior runScript step + +# Open the payment URL as a deep link +- openLink: ${output.gateway_url} + +# Wait for payment options to load with merchant info +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 diff --git a/.maestro/pay_confirm.yaml b/.maestro/pay_confirm.yaml deleted file mode 100644 index 8ad7d611..00000000 --- a/.maestro/pay_confirm.yaml +++ /dev/null @@ -1,97 +0,0 @@ -appId: ${APP_ID} -name: WalletConnect Pay - Wallet Payment Confirmed -tags: - - pay ---- -# Launch wallet app (GATEWAY_URL is already in the device clipboard, set by CI) -- launchApp: - appId: ${APP_ID} - permissions: - all: allow - -- startRecording: "WalletConnect Pay Confirmed" - -# Tap scan button to open scanner options modal -- tapOn: - id: "button-scan" - -# Android: type the payment URL into the test input field -- runFlow: - when: - platform: Android - commands: - - tapOn: - id: "input-paste-url" - - inputText: ${GATEWAY_URL} - - pressKey: Enter - - tapOn: - id: "button-submit-url" - -# iOS: paste from clipboard -- runFlow: - when: - platform: iOS - commands: - - tapOn: - id: "button-paste-url" - - runFlow: - when: - visible: "Allow Paste" - commands: - - tapOn: "Allow Paste" - -# Wait for payment options to load with merchant info -- extendedWaitUntil: - visible: - id: "pay-merchant-info" - timeout: 15000 - -# Handle single vs. multi option: if Continue button visible, tap it -- runFlow: - when: - visible: - id: "pay-button-continue" - commands: - - tapOn: - id: "pay-button-continue" - -# Handle personal details webview (if user needs KYC) -- runFlow: - when: - visible: "Add your personal details" - commands: - # Data is autocompleted, just tap Add - - tapOn: "Add" - # Confirm your details popup: tap the Terms checkbox then Confirm - - extendedWaitUntil: - visible: "Confirm your details" - timeout: 10000 - # Tap the checkbox / label for terms agreement - - tapOn: - text: "I agree to the Terms and Conditions and Privacy Policy" - retryTapIfNoChange: true - - tapOn: "Confirm" - -# On review screen, tap Pay button -- extendedWaitUntil: - visible: - id: "pay-button-pay" - timeout: 10000 -- tapOn: - id: "pay-button-pay" - -# Verify confirming screen -- assertVisible: - id: "pay-loading-message" - -# Wait for success screen (generous timeout for on-chain confirmation) -- extendedWaitUntil: - visible: - id: "pay-result-success-icon" - timeout: 30000 - -# Tap "Got it!" button -- tapOn: - id: "pay-button-result-action" - -- stopRecording diff --git a/.maestro/pay_multiple_options_kyc.yaml b/.maestro/pay_multiple_options_kyc.yaml new file mode 100644 index 00000000..ba6ef6e1 --- /dev/null +++ b/.maestro/pay_multiple_options_kyc.yaml @@ -0,0 +1,88 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Multiple Options with KYC +tags: + - pay +--- +# Create payment via API (multi-option, KYC merchant) +- runScript: + file: scripts/create-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_MULTI_KYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_MULTI_KYC} + +- startRecording: "WalletConnect Pay Multiple Options KYC" + +# Open wallet, paste payment URL, wait for merchant info +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Verify first option is pre-selected (index 0) +- assertVisible: + id: "pay-option-0-selected" + +# Verify at least a second option exists (multiple options) +- assertVisible: + id: "pay-option-1" + +# Verify "Info required" badge is visible on KYC options +- assertVisible: + id: "pay-info-required-badge" + +# Verify "?" info button is visible in the header +- assertVisible: + id: "pay-button-info" + +# Select the second option (index 1) +- tapOn: + id: "pay-option-1" + +# Verify second option is now selected +- assertVisible: + id: "pay-option-1-selected" + +# Verify first option is now deselected +- assertVisible: + id: "pay-option-0" + +# Copy the network name from the selected option's accessibilityLabel +- copyTextFrom: + id: "pay-option-1-selected" + +# Tap Continue to proceed +- tapOn: + id: "pay-button-continue" + +# Handle personal details webview (KYC) +- extendedWaitUntil: + visible: "Add your personal details" + timeout: 10000 + +# Data is autocompleted, just tap Add +- tapOn: "Add" + +# Confirm your details popup: tap the Terms checkbox then Confirm +- extendedWaitUntil: + visible: "Confirm your details" + timeout: 10000 + +# Tap the checkbox / label for terms agreement +- tapOn: + text: "I agree to the Terms and Conditions and Privacy Policy" + retryTapIfNoChange: true +- tapOn: "Confirm" + +# Verify review screen shows the same token we selected +- assertVisible: + id: "pay-review-token-${maestro.copiedText}" + +# Verify pay button shows the correct amount +- copyTextFrom: + id: "pay-button-pay" +- assertTrue: + condition: "${maestro.copiedText == 'Pay $0.01'}" + +# Tap Pay, verify success +- runFlow: + file: flows/pay_confirm_and_verify.yaml + +- stopRecording diff --git a/.maestro/pay_multiple_options_nokyc.yaml b/.maestro/pay_multiple_options_nokyc.yaml new file mode 100644 index 00000000..f8de6c56 --- /dev/null +++ b/.maestro/pay_multiple_options_nokyc.yaml @@ -0,0 +1,70 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Multiple Options No KYC +tags: + - pay +--- +# Create payment via API (multi-option, no-KYC merchant) +- runScript: + file: scripts/create-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_MULTI_NOKYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_MULTI_NOKYC} + +- startRecording: "WalletConnect Pay Multiple Options No KYC" + +# Open wallet, paste payment URL, wait for merchant info +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Verify first option is pre-selected (index 0) +- assertVisible: + id: "pay-option-0-selected" + +# Verify at least a second option exists (multiple options) +- assertVisible: + id: "pay-option-1" + +# Verify "?" info button is NOT visible (no KYC merchant) +- assertNotVisible: + id: "pay-button-info" + +# Verify "info required" badge is NOT visible (no KYC) +- assertNotVisible: + id: "pay-info-required-badge" + +# Select the second option (index 1) +- tapOn: + id: "pay-option-1" + +# Verify second option is now selected +- assertVisible: + id: "pay-option-1-selected" + +# Verify first option is now deselected +- assertVisible: + id: "pay-option-0" + +# Copy the network name from the selected option's accessibilityLabel +- copyTextFrom: + id: "pay-option-1-selected" + +# Tap Continue to proceed +- tapOn: + id: "pay-button-continue" + +# No KYC — goes straight to review screen +# Verify review screen shows the same token we selected +- assertVisible: + id: "pay-review-token-${maestro.copiedText}" + +# Verify pay button shows the correct amount +- copyTextFrom: + id: "pay-button-pay" +- assertTrue: + condition: "${maestro.copiedText == 'Pay $0.01'}" + +# Tap Pay, verify success +- runFlow: + file: flows/pay_confirm_and_verify.yaml + +- stopRecording diff --git a/.maestro/pay_single_option_nokyc.yaml b/.maestro/pay_single_option_nokyc.yaml new file mode 100644 index 00000000..622e0028 --- /dev/null +++ b/.maestro/pay_single_option_nokyc.yaml @@ -0,0 +1,35 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Single Option No KYC +tags: + - pay +--- +# Create payment via API (single-option, no-KYC merchant) +- runScript: + file: scripts/create-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_SINGLE_NOKYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_SINGLE_NOKYC} + +- startRecording: "WalletConnect Pay Single Option No KYC" + +# Open wallet, paste payment URL, wait for merchant info +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Single option auto-selects — go straight to review screen + +# Verify pay button shows the correct amount +- extendedWaitUntil: + visible: + id: "pay-button-pay" + timeout: 10000 +- copyTextFrom: + id: "pay-button-pay" +- assertTrue: + condition: "${maestro.copiedText == 'Pay $0.01'}" + +# Tap Pay, verify success +- runFlow: + file: flows/pay_confirm_and_verify.yaml + +- stopRecording diff --git a/.maestro/pay_single_option_nokyc_deeplink.yaml b/.maestro/pay_single_option_nokyc_deeplink.yaml new file mode 100644 index 00000000..5953f097 --- /dev/null +++ b/.maestro/pay_single_option_nokyc_deeplink.yaml @@ -0,0 +1,35 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Single Option No KYC (Deep Link) +tags: + - pay +--- +# Create payment via API (single-option, no-KYC merchant) +- runScript: + file: scripts/create-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_SINGLE_NOKYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_SINGLE_NOKYC} + +- startRecording: "WalletConnect Pay Single Option No KYC Deep Link" + +# Open wallet via deep link with payment URL +- runFlow: + file: flows/pay_open_via_deeplink.yaml + +# Single option auto-selects — go straight to review screen + +# Verify pay button shows the correct amount +- extendedWaitUntil: + visible: + id: "pay-button-pay" + timeout: 10000 +- copyTextFrom: + id: "pay-button-pay" +- assertTrue: + condition: "${maestro.copiedText == 'Pay $0.01'}" + +# Tap Pay, verify success +- runFlow: + file: flows/pay_confirm_and_verify.yaml + +- stopRecording diff --git a/.maestro/scripts/create-payment.js b/.maestro/scripts/create-payment.js new file mode 100644 index 00000000..4a033c17 --- /dev/null +++ b/.maestro/scripts/create-payment.js @@ -0,0 +1,28 @@ +// Creates a WalletConnect Pay payment via the API. +// Expects WPAY_CUSTOMER_KEY and WPAY_MERCHANT_ID env vars from Maestro. +// Sets output.gateway_url for use in subsequent flow steps. + +var response = http.post('https://api.pay.walletconnect.com/v1/payments', { + headers: { + 'Content-Type': 'application/json', + 'Api-Key': WPAY_CUSTOMER_KEY, + 'Merchant-Id': WPAY_MERCHANT_ID, + }, + body: JSON.stringify({ + referenceId: '' + Date.now() + Math.random().toString(36).substring(2, 10), + amount: { value: '1', unit: 'iso4217/USD' }, + }), +}); + +if (response.status < 200 || response.status >= 300) { + throw new Error('API returned HTTP ' + response.status + ': ' + response.body); +} + +var data = json(response.body); + +if (!data.gatewayUrl) { + throw new Error('No gatewayUrl in response: ' + response.body); +} + +console.log('Payment created: ' + data.paymentId); +output.gateway_url = data.gatewayUrl; diff --git a/fastlane/Fastfile b/fastlane/Fastfile index e195b966..666c18be 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -107,6 +107,36 @@ platform :ios do end lane :build_for_simulator do |options| + sign_build = ENV['MATCH_GIT_URL'] && !ENV['MATCH_GIT_URL'].empty? + + if sign_build + UI.message("🔑 Signing enabled — setting up Match development profile") + setup_ci() + + api_key = app_store_connect_api_key( + key_id: ENV["APPLE_KEY_ID"], + issuer_id: ENV["APPLE_ISSUER_ID"], + key_content: ENV["APPLE_KEY_CONTENT"], + duration: 1200, + in_house: false, + ) + + match( + readonly: true, + type: "development", + app_identifier: ENV['BUNDLE_ID'], + git_url: ENV['MATCH_GIT_URL'], + api_key: api_key, + ) + + update_code_signing_settings( + use_automatic_signing: false, + path: ENV['XCODE_PROJECT_PATH'], + bundle_identifier: ENV['BUNDLE_ID'], + code_sign_identity: "Apple Development", + profile_name: "match Development #{ENV['BUNDLE_ID']}" + ) + end # Install pods (skip repo_update if cache hit) cocoapods( @@ -120,16 +150,22 @@ platform :ios do build_dir = File.expand_path("../build") archive_path = "#{build_dir}/wallet-simulator.xcarchive" - # Archive for simulator without code signing - gym( + gym_params = { workspace: ENV['XCWORKSPACE_PATH'], scheme: ENV['SCHEME'], configuration: "Release", destination: "generic/platform=iOS Simulator", archive_path: archive_path, - skip_codesigning: true, skip_package_ipa: true, - ) + } + + if sign_build + gym_params[:xcargs] = "CODE_SIGNING_ALLOWED=YES" + else + gym_params[:skip_codesigning] = true + end + + gym(gym_params) # Find the .app directly inside the xcarchive — no IPA needed for simulator install app_path = Dir.glob("#{archive_path}/Products/Applications/*.app").first diff --git a/scripts/create-payment.sh b/scripts/create-payment.sh deleted file mode 100755 index 1cf4c939..00000000 --- a/scripts/create-payment.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Creates a WalletConnect Pay payment via the API and outputs the gateway URL. -# Required env vars: WPAY_CUSTOMER_KEY, WPAY_MERCHANT_ID - -API_URL="https://api.pay.walletconnect.com/v1/payments" - -: "${WPAY_CUSTOMER_KEY:?WPAY_CUSTOMER_KEY is required}" -: "${WPAY_MERCHANT_ID:?WPAY_MERCHANT_ID is required}" - -REFERENCE_ID=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid) -REFERENCE_ID=${REFERENCE_ID//-/} - -HTTP_CODE=$(curl -s -o /tmp/payment_response.json -w "%{http_code}" -X POST "$API_URL" \ - -H "Content-Type: application/json" \ - -H "Api-Key: ${WPAY_CUSTOMER_KEY}" \ - -H "Merchant-Id: ${WPAY_MERCHANT_ID}" \ - -d "{\"referenceId\":\"${REFERENCE_ID}\",\"amount\":{\"value\":\"1\",\"unit\":\"iso4217/USD\"}}") - -RESPONSE=$(cat /tmp/payment_response.json) - -if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then - echo "Error: API returned HTTP $HTTP_CODE" >&2 - echo "Response: $RESPONSE" >&2 - exit 1 -fi - -GATEWAY_URL=$(echo "$RESPONSE" | jq -r '.gatewayUrl') -PAYMENT_ID=$(echo "$RESPONSE" | jq -r '.paymentId') - -if [ -z "$GATEWAY_URL" ] || [ "$GATEWAY_URL" = "null" ]; then - echo "Error: Failed to get gatewayUrl from API response" >&2 - echo "Response: $RESPONSE" >&2 - exit 1 -fi - -echo "gateway_url=$GATEWAY_URL" -echo "payment_id=$PAYMENT_ID" - -# Output for GitHub Actions -if [ -n "${GITHUB_OUTPUT:-}" ]; then - echo "gateway_url=${GATEWAY_URL}" >> "$GITHUB_OUTPUT" - echo "payment_id=${PAYMENT_ID}" >> "$GITHUB_OUTPUT" -fi diff --git a/wallets/rn_cli_wallet/AGENTS.md b/wallets/rn_cli_wallet/AGENTS.md index 0becde90..1e56ad7f 100644 --- a/wallets/rn_cli_wallet/AGENTS.md +++ b/wallets/rn_cli_wallet/AGENTS.md @@ -183,9 +183,17 @@ The app uses standardized `testID` props for Maestro E2E testing. These IDs are - `pay-*`: WalletConnect Pay flow elements (e.g., `pay-merchant-info`, `pay-button-pay`, `pay-result-success-icon`) ### Test Files -- `.maestro/web/pay_confirm.yaml`: WalletConnect Pay end-to-end flow (POS → Wallet → POS) -- `.maestro/native/`: Native dapp-to-wallet flows (connect, sign, reject) -- `.maestro/web/`: Web-to-wallet flows (connect, sign, reject) +- `.maestro/pay_single_option_nokyc.yaml`: Single payment option, no KYC — goes straight to review screen +- `.maestro/pay_multiple_options_nokyc.yaml`: Multiple payment options, no KYC — option selection then review +- `.maestro/pay_multiple_options_kyc.yaml`: Multiple payment options with KYC — option selection, webview KYC flow, then review +- `.maestro/flows/pay_open_and_paste_url.yaml`: Shared sub-flow — opens wallet, pastes payment URL, waits for merchant info +- `.maestro/flows/pay_confirm_and_verify.yaml`: Shared sub-flow — taps Pay, verifies success screen +- `.maestro/scripts/create-payment.js`: Creates a payment via the WalletConnect Pay API (called via `runScript`) + +### Running Pay Tests Locally +```bash +maestro test --env APP_ID=com.walletconnect.web3wallet.rnsample.internal --env WPAY_CUSTOMER_KEY_SINGLE_NOKYC="" --env WPAY_MERCHANT_ID_SINGLE_NOKYC="" --env WPAY_CUSTOMER_KEY_MULTI_KYC="" --env WPAY_MERCHANT_ID_MULTI_KYC="" --env WPAY_CUSTOMER_KEY_MULTI_NOKYC="" --env WPAY_MERCHANT_ID_MULTI_NOKYC="" --include-tags pay .maestro/ +``` ### Dynamic App ID Maestro tests use `${APP_ID}` env variable instead of hardcoded bundle IDs, enabling reuse across wallet platforms. Pass via `--env APP_ID=` when running tests. diff --git a/wallets/rn_cli_wallet/README.md b/wallets/rn_cli_wallet/README.md index 7223efd6..9861ccd2 100644 --- a/wallets/rn_cli_wallet/README.md +++ b/wallets/rn_cli_wallet/README.md @@ -91,14 +91,14 @@ curl -fsSL "https://get.maestro.mobile.dev" | bash ### Running Tests Locally -Run all tests with the `pay` tag: +Run all pay tests: ```bash -maestro test --env APP_ID=com.walletconnect.web3wallet.rnsample.internal --tags pay .maestro/ +maestro test --env APP_ID=com.walletconnect.web3wallet.rnsample.internal --env WPAY_CUSTOMER_KEY_SINGLE_NOKYC="" --env WPAY_MERCHANT_ID_SINGLE_NOKYC="" --env WPAY_CUSTOMER_KEY_MULTI_KYC="" --env WPAY_MERCHANT_ID_MULTI_KYC="" --env WPAY_CUSTOMER_KEY_MULTI_NOKYC="" --env WPAY_MERCHANT_ID_MULTI_NOKYC="" --include-tags pay .maestro/ ``` Run a specific test: ```bash -maestro test --env APP_ID=com.walletconnect.web3wallet.rnsample.internal .maestro/web/pay_confirm.yaml +maestro test --env APP_ID=com.walletconnect.web3wallet.rnsample.internal --env WPAY_CUSTOMER_KEY_SINGLE_NOKYC="" --env WPAY_MERCHANT_ID_SINGLE_NOKYC="" .maestro/pay_single_option_nokyc.yaml ``` Run all tests: diff --git a/wallets/rn_cli_wallet/ios/RNWeb3Wallet.xcodeproj/xcshareddata/xcschemes/RNWallet-Internal.xcscheme b/wallets/rn_cli_wallet/ios/RNWeb3Wallet.xcodeproj/xcshareddata/xcschemes/RNWallet-Internal.xcscheme index f367b0e4..a5f18e2a 100644 --- a/wallets/rn_cli_wallet/ios/RNWeb3Wallet.xcodeproj/xcshareddata/xcschemes/RNWallet-Internal.xcscheme +++ b/wallets/rn_cli_wallet/ios/RNWeb3Wallet.xcodeproj/xcshareddata/xcschemes/RNWallet-Internal.xcscheme @@ -30,7 +30,7 @@ shouldAutocreateTestPlan = "YES"> ; textStyle?: StyleProp; testID?: string; + accessibilityLabel?: string; } export function ActionButton({ @@ -38,6 +39,7 @@ export function ActionButton({ style, textStyle, testID, + accessibilityLabel, }: ActionButtonProps) { const Theme = useTheme(); @@ -71,6 +73,7 @@ export function ActionButton({ onPress={onPress} disabled={disabled || loading || silentDisabled} testID={testID} + accessibilityLabel={accessibilityLabel} style={[ styles.container, { backgroundColor, borderColor }, diff --git a/wallets/rn_cli_wallet/src/components/Button.tsx b/wallets/rn_cli_wallet/src/components/Button.tsx index a8a60a8d..6c977f07 100644 --- a/wallets/rn_cli_wallet/src/components/Button.tsx +++ b/wallets/rn_cli_wallet/src/components/Button.tsx @@ -9,6 +9,7 @@ interface ButtonProps { disabled?: boolean; hitSlop?: number | Insets; testID?: string; + accessibilityLabel?: string; } export const Button: React.FC = ({ @@ -18,6 +19,7 @@ export const Button: React.FC = ({ disabled = false, hitSlop, testID, + accessibilityLabel, }) => { return ( = ({ enabled={!disabled} hitSlop={hitSlop} testID={testID} + accessibilityLabel={accessibilityLabel} > {children} diff --git a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ReviewPaymentView.tsx b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ReviewPaymentView.tsx index 5819ade7..45be2a9a 100644 --- a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ReviewPaymentView.tsx +++ b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ReviewPaymentView.tsx @@ -53,6 +53,8 @@ export function ReviewPaymentView({ styles.item, { backgroundColor: Theme['foreground-primary'] }, ]} + testID={`pay-review-token-${selectedOption.amount.display?.networkName?.toLowerCase() || ''}`} + accessibilityLabel={selectedOption.amount.display?.networkName?.toLowerCase() || ''} > Pay with @@ -87,6 +89,7 @@ export function ReviewPaymentView({ disabled={isSigningPayment || isLoadingActions} fullWidth testID="pay-button-pay" + accessibilityLabel={`Pay ${currencySymbol}${payAmount}`} > {`Pay ${currencySymbol}${payAmount}`} diff --git a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx index a838ad97..aebcd8d6 100644 --- a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx +++ b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx @@ -27,6 +27,7 @@ const OPTION_HEIGHT = 64; interface OptionItemProps { option: PaymentOption; + index: number; isSelected: boolean; hasCollectData: boolean; onSelect: (option: PaymentOption) => void; @@ -36,6 +37,7 @@ const ANIMATION_DURATION = 250; function OptionItem({ option, + index, isSelected, hasCollectData, onSelect, @@ -95,7 +97,12 @@ function OptionItem({ ); return ( -