From 0c0d97747590fc34d8737323345361ee78496779 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:36:14 -0300 Subject: [PATCH 1/8] feat(rn_cli_wallet): refactor Maestro Pay E2E tests with inline payment creation - Replace shell-based payment creation with JS scripts run inside Maestro flows - Add separate test flows for single/multi option, KYC/no-KYC, and deeplink scenarios - Pass WalletConnect Pay API keys as Maestro env vars instead of pre-creating payments - Add testIDs to PaymentOptionsModal and Scanner components for E2E targeting - Extend balance-check workflow to monitor OP token on Optimism - Set Internal scheme to Release build configuration Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci_e2e_walletkit.yaml | 31 ++---- .github/workflows/e2e-balance-check.yml | 65 +++++++++++-- .maestro/flows/pay_confirm_and_verify.yaml | 30 ++++++ .maestro/flows/pay_open_and_paste_url.yaml | 36 +++++++ .maestro/flows/pay_open_via_deeplink.yaml | 13 +++ .maestro/pay_confirm.yaml | 97 ------------------- .maestro/pay_multiple_options_kyc.yaml | 88 +++++++++++++++++ .maestro/pay_multiple_options_nokyc.yaml | 70 +++++++++++++ .maestro/pay_single_option_nokyc.yaml | 35 +++++++ .../pay_single_option_nokyc_deeplink.yaml | 35 +++++++ .maestro/scripts/create-payment.js | 28 ++++++ scripts/create-payment.sh | 45 --------- wallets/rn_cli_wallet/AGENTS.md | 14 ++- wallets/rn_cli_wallet/README.md | 6 +- .../xcschemes/RNWallet-Internal.xcscheme | 2 +- .../src/components/ActionButton.tsx | 3 + .../rn_cli_wallet/src/components/Button.tsx | 3 + .../PaymentOptionsModal/ReviewPaymentView.tsx | 3 + .../PaymentOptionsModal/SelectOptionView.tsx | 14 ++- .../src/modals/PaymentOptionsModal/index.tsx | 4 +- .../src/modals/ScannerOptionsModal.tsx | 5 +- 21 files changed, 438 insertions(+), 189 deletions(-) create mode 100644 .maestro/flows/pay_confirm_and_verify.yaml create mode 100644 .maestro/flows/pay_open_and_paste_url.yaml create mode 100644 .maestro/flows/pay_open_via_deeplink.yaml delete mode 100644 .maestro/pay_confirm.yaml create mode 100644 .maestro/pay_multiple_options_kyc.yaml create mode 100644 .maestro/pay_multiple_options_nokyc.yaml create mode 100644 .maestro/pay_single_option_nokyc.yaml create mode 100644 .maestro/pay_single_option_nokyc_deeplink.yaml create mode 100644 .maestro/scripts/create-payment.js delete mode 100755 scripts/create-payment.sh diff --git a/.github/workflows/ci_e2e_walletkit.yaml b/.github/workflows/ci_e2e_walletkit.yaml index 4025e2057..f9121d53e 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: @@ -76,6 +71,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 @@ -136,22 +132,16 @@ 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_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 \ @@ -287,13 +277,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 @@ -310,7 +293,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_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 6aeb162da..9b28e8a45 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_TOKEN: '0x4200000000000000000000000000000000000042' + 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 }} @@ -32,8 +34,8 @@ jobs: 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 OP balance on Optimism + id: op_balance + run: | + BALANCE=$(cast call --rpc-url "$OP_RPC" "$OP_TOKEN" \ + "balanceOf(address)(uint256)" \ + "${{ vars.TEST_WALLET_ADDRESS }}" | awk '{print $1}') + echo "balance=$BALANCE" >> $GITHUB_OUTPUT + BALANCE_HUMAN=$(echo "scale=6; $BALANCE / 1000000000000000000" | bc) + echo "OP on Optimism: $BALANCE_HUMAN ($BALANCE raw)" + + - name: Prepare OP alert + id: op_alert + run: | + BALANCE="${{ steps.op_balance.outputs.balance }}" + THRESHOLD="${{ vars.OP_THRESHOLD_UNITS || '500000000000000000' }}" + if [ "$BALANCE" -lt "$THRESHOLD" ]; then + BALANCE_HUMAN=$(echo "scale=6; $BALANCE / 1000000000000000000" | bc) + THRESHOLD_HUMAN=$(echo "scale=6; $THRESHOLD / 1000000000000000000" | bc) + ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} OP 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 (OP) + if: | + steps.op_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_alert.outputs.text }}" + } + + - name: Skip Slack alert (OP - no webhook) + if: | + steps.op_alert.outputs.should_alert == 'true' && + env.SLACK_FAUCETBOT_WEBHOOK_URL == '' + run: | + echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping OP alert" diff --git a/.maestro/flows/pay_confirm_and_verify.yaml b/.maestro/flows/pay_confirm_and_verify.yaml new file mode 100644 index 000000000..209a8a28b --- /dev/null +++ b/.maestro/flows/pay_confirm_and_verify.yaml @@ -0,0 +1,30 @@ +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" + +# Verify confirming screen (optional — may flash by quickly) +- runFlow: + when: + visible: + id: "pay-loading-message" + commands: + - 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" 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 000000000..b2e5b118d --- /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 000000000..ef82a2220 --- /dev/null +++ b/.maestro/flows/pay_open_via_deeplink.yaml @@ -0,0 +1,13 @@ +appId: ${APP_ID} +--- +# Shared flow: Launch wallet, open payment URL via deep link, 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 8ad7d6110..000000000 --- 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 000000000..ba6ef6e16 --- /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 000000000..f8de6c56b --- /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 000000000..622e00283 --- /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 000000000..5953f0976 --- /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 000000000..4a033c17d --- /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/scripts/create-payment.sh b/scripts/create-payment.sh deleted file mode 100755 index 1cf4c939e..000000000 --- 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 0becde901..1e56ad7f8 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 7223efd6c..9861ccd24 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 f367b0e4b..a5f18e2a7 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 a8a60a8d4..6c977f07d 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 5819ade79..45be2a9af 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 a838ad972..aebcd8d6f 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 ( -