diff --git a/maestro/pay-tests/.maestro/flows/pay_confirm_and_verify.yaml b/maestro/pay-tests/.maestro/flows/pay_confirm_and_verify.yaml new file mode 100644 index 0000000..92e9adf --- /dev/null +++ b/maestro/pay-tests/.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-success" diff --git a/maestro/pay-tests/.maestro/flows/pay_open_and_paste_url.yaml b/maestro/pay-tests/.maestro/flows/pay_open_and_paste_url.yaml new file mode 100644 index 0000000..8d1871e --- /dev/null +++ b/maestro/pay-tests/.maestro/flows/pay_open_and_paste_url.yaml @@ -0,0 +1,43 @@ +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 + +# Wait for the app to fully load before interacting +- extendedWaitUntil: + visible: + id: "button-scan" + timeout: 15000 + +# 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 + +- extendedWaitUntil: + visible: + id: "button-submit-url" + timeout: 10000 + +- tapOn: + id: "button-submit-url" diff --git a/maestro/pay-tests/.maestro/flows/pay_open_via_deeplink.yaml b/maestro/pay-tests/.maestro/flows/pay_open_via_deeplink.yaml new file mode 100644 index 0000000..93ded1b --- /dev/null +++ b/maestro/pay-tests/.maestro/flows/pay_open_via_deeplink.yaml @@ -0,0 +1,9 @@ +appId: ${APP_ID} +--- +# Shared flow: Open payment URL via deep link +# Requires: ${output.gateway_url} to be set by a prior runScript step + +# Stop app then open via deeplink so openLink is the launch intent. +- stopApp: + appId: ${APP_ID} +- openLink: ${output.gateway_url} diff --git a/maestro/pay-tests/.maestro/pay_cancel_from_kyc.yaml b/maestro/pay-tests/.maestro/pay_cancel_from_kyc.yaml new file mode 100644 index 0000000..5e57391 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_cancel_from_kyc.yaml @@ -0,0 +1,85 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Cancel from KYC Webview +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 Cancel from KYC" + +# Open wallet, paste payment URL +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# Verify first option is pre-selected +- assertVisible: + id: "pay-option-0-selected" + +# Tap Continue to go to collectData webview +- tapOn: + id: "pay-button-continue" + +# Wait for KYC webview to load +- extendedWaitUntil: + visible: "Add your personal details" + timeout: 30000 + +# Cancel the payment server-side while in KYC webview +- runScript: + file: scripts/cancel-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_MULTI_KYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_MULTI_KYC} + PAYMENT_ID: ${output.payment_id} + +# Complete KYC form (data is autocompleted, just tap Add) +- tapOn: "Add" + +# Retry tap on "Add" if "Confirm your details" doesn't appear (webview can be slow) +- runFlow: + when: + notVisible: "Confirm your details" + commands: + - tapOn: "Add" + +# Confirm your details popup +- 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" + +# Wait for result screen +- extendedWaitUntil: + visible: + id: "pay-result-container" + timeout: 30000 + +# Verify cancelled icon +- assertVisible: + id: "pay-result-cancelled-icon" + +# Verify action button +- assertVisible: + id: "pay-button-result-action-cancelled" + +# Dismiss +- tapOn: + id: "pay-button-result-action-cancelled" + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_cancel_from_review.yaml b/maestro/pay-tests/.maestro/pay_cancel_from_review.yaml new file mode 100644 index 0000000..970a852 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_cancel_from_review.yaml @@ -0,0 +1,61 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Cancel from Review Screen +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 Cancel from Review" + +# Open wallet, paste payment URL +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# Single option auto-selects — verify review screen +- extendedWaitUntil: + visible: + id: "pay-button-pay" + timeout: 10000 + +# Cancel the payment server-side while on review screen +- runScript: + file: scripts/cancel-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_SINGLE_NOKYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_SINGLE_NOKYC} + PAYMENT_ID: ${output.payment_id} + +# Tap Pay — payment was cancelled, should show cancelled result +- tapOn: + id: "pay-button-pay" + +# Wait for result screen +- extendedWaitUntil: + visible: + id: "pay-result-container" + timeout: 30000 + +# Verify cancelled icon +- assertVisible: + id: "pay-result-cancelled-icon" + +# Verify action button +- assertVisible: + id: "pay-button-result-action-cancelled" + +# Dismiss +- tapOn: + id: "pay-button-result-action-cancelled" + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_cancelled.yaml b/maestro/pay-tests/.maestro/pay_cancelled.yaml new file mode 100644 index 0000000..b2144e3 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_cancelled.yaml @@ -0,0 +1,44 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Cancelled Payment +tags: + - pay +--- +# Create a fresh payment, then cancel it immediately via API +- runScript: + file: scripts/create-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_SINGLE_NOKYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_SINGLE_NOKYC} + +- runScript: + file: scripts/cancel-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_SINGLE_NOKYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_SINGLE_NOKYC} + PAYMENT_ID: ${output.payment_id} + +- startRecording: "WalletConnect Pay Cancelled Payment" + +# Open wallet, paste payment URL +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Payment is cancelled — app shows error result screen directly +- extendedWaitUntil: + visible: + id: "pay-result-container" + timeout: 15000 + +# Verify cancelled icon is shown +- assertVisible: + id: "pay-result-cancelled-icon" + +# Verify result action button is visible +- assertVisible: + id: "pay-button-result-action-cancelled" + +# Dismiss the error dialog +- tapOn: + id: "pay-button-result-action-cancelled" + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_double_scan.yaml b/maestro/pay-tests/.maestro/pay_double_scan.yaml new file mode 100644 index 0000000..c25efb6 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_double_scan.yaml @@ -0,0 +1,81 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Double Scan Same Payment +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 Double Scan" + +# === First scan: complete the payment normally === + +# Open wallet, paste payment URL +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# Single option auto-selects — verify pay button +- extendedWaitUntil: + visible: + id: "pay-button-pay" + timeout: 10000 +- copyTextFrom: + id: "pay-button-pay" +- assertTrue: + condition: "${maestro.copiedText == 'Pay $0.01'}" + +# Tap Pay, verify success, tap "Got it!" +- runFlow: + file: flows/pay_confirm_and_verify.yaml + +# === Second scan: re-open the same gateway URL === + +# Tap scan button to open scanner +- tapOn: + id: "button-scan" + +# Type the same payment URL +- 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 error result screen (payment already completed) +- extendedWaitUntil: + visible: + id: "pay-result-container" + timeout: 20000 + +# Verify generic error icon is shown (not success) +- assertVisible: + id: "pay-result-error-icon" + +# Verify result action button is visible +- assertVisible: + id: "pay-button-result-action-generic" + +# Dismiss the error dialog +- tapOn: + id: "pay-button-result-action-generic" + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_expired_link.yaml b/maestro/pay-tests/.maestro/pay_expired_link.yaml new file mode 100644 index 0000000..719475e --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_expired_link.yaml @@ -0,0 +1,33 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Expired Link +tags: + - pay +--- +# Use a hardcoded expired payment URL (no API call needed) +- evalScript: ${output.gateway_url = 'https://pay.walletconnect.com/?pid=pay_b8a2ecc101KNHRNWXD2VF8SGZDS7WK19ZA'} + +- startRecording: "WalletConnect Pay Expired Link" + +# Open wallet, paste payment URL +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Payment is expired — app shows error result screen directly +- extendedWaitUntil: + visible: + id: "pay-result-container" + timeout: 15000 + +# Verify expired icon is shown +- assertVisible: + id: "pay-result-expired-icon" + +# Verify result action button is visible +- assertVisible: + id: "pay-button-result-action-expired" + +# Dismiss the error dialog +- tapOn: + id: "pay-button-result-action-expired" + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_insufficient_funds.yaml b/maestro/pay-tests/.maestro/pay_insufficient_funds.yaml new file mode 100644 index 0000000..f71a25c --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_insufficient_funds.yaml @@ -0,0 +1,38 @@ +appId: ${APP_ID} +name: WalletConnect Pay - Insufficient Funds +tags: + - pay +--- +# Create payment with amount exceeding wallet balance ($9.99) +- runScript: + file: scripts/create-payment.js + env: + WPAY_CUSTOMER_KEY: ${WPAY_CUSTOMER_KEY_SINGLE_NOKYC} + WPAY_MERCHANT_ID: ${WPAY_MERCHANT_ID_SINGLE_NOKYC} + WPAY_AMOUNT: "999" + +- startRecording: "WalletConnect Pay Insufficient Funds" + +# Open wallet, paste payment URL +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# No options available — app jumps directly to error result screen +- extendedWaitUntil: + visible: + id: "pay-result-container" + timeout: 15000 + +# Verify insufficient funds icon is shown +- assertVisible: + id: "pay-result-insufficient-funds-icon" + +# Verify result action button is visible +- assertVisible: + id: "pay-button-result-action-insufficient_funds" + +# Dismiss the error dialog +- tapOn: + id: "pay-button-result-action-insufficient_funds" + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_kyc_back_navigation.yaml b/maestro/pay-tests/.maestro/pay_kyc_back_navigation.yaml new file mode 100644 index 0000000..7f0b063 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_kyc_back_navigation.yaml @@ -0,0 +1,66 @@ +appId: ${APP_ID} +name: WalletConnect Pay - KYC Header Buttons +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 KYC Header Buttons" + +# Open wallet, paste payment URL +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# Verify first option is pre-selected +- assertVisible: + id: "pay-option-0-selected" + +# Tap Continue to go to collectData webview +- tapOn: + id: "pay-button-continue" + +# Wait for KYC webview to load +- extendedWaitUntil: + visible: "Add your personal details" + timeout: 30000 + +# Verify the close button (X) is visible in the header +- assertVisible: + id: "pay-button-close" + +# Verify the back button is visible +- assertVisible: + id: "pay-button-back" + +# Tap back to return to option selection +- tapOn: + id: "pay-button-back" + +# Verify we're back at the selectOption view +- extendedWaitUntil: + visible: + id: "pay-option-0-selected" + timeout: 10000 + +# Dismiss the modal +- tapOn: + id: "pay-button-close" + +# Verify modal is dismissed and we're back at the main wallet screen +- extendedWaitUntil: + visible: + id: "button-scan" + timeout: 10000 + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_multiple_options_kyc.yaml b/maestro/pay-tests/.maestro/pay_multiple_options_kyc.yaml new file mode 100644 index 0000000..2e9a69b --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_multiple_options_kyc.yaml @@ -0,0 +1,101 @@ +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 +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# 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: 30000 + +# Data is autocompleted, just tap Add +- tapOn: "Add" + +# Retry tap on "Add" if "Confirm your details" doesn't appear (webview can be slow) +- runFlow: + when: + notVisible: "Confirm your details" + commands: + - tapOn: "Add" + +# Confirm your details popup +- 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-tests/.maestro/pay_multiple_options_nokyc.yaml b/maestro/pay-tests/.maestro/pay_multiple_options_nokyc.yaml new file mode 100644 index 0000000..1e73d85 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_multiple_options_nokyc.yaml @@ -0,0 +1,76 @@ +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 +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# 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-tests/.maestro/pay_single_option_nokyc.yaml b/maestro/pay-tests/.maestro/pay_single_option_nokyc.yaml new file mode 100644 index 0000000..63cd064 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_single_option_nokyc.yaml @@ -0,0 +1,47 @@ +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 +- runFlow: + file: flows/pay_open_and_paste_url.yaml + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# 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 + +# Verify payment dialog is dismissed and we're back at the main wallet screen +- extendedWaitUntil: + visible: + id: "button-scan" + timeout: 10000 + +- stopRecording diff --git a/maestro/pay-tests/.maestro/pay_single_option_nokyc_deeplink.yaml b/maestro/pay-tests/.maestro/pay_single_option_nokyc_deeplink.yaml new file mode 100644 index 0000000..d7e2953 --- /dev/null +++ b/maestro/pay-tests/.maestro/pay_single_option_nokyc_deeplink.yaml @@ -0,0 +1,41 @@ +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 + +# Wait for payment options to load +- extendedWaitUntil: + visible: + id: "pay-merchant-info" + timeout: 15000 + +# 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-tests/.maestro/scripts/cancel-payment.js b/maestro/pay-tests/.maestro/scripts/cancel-payment.js new file mode 100644 index 0000000..dcd548b --- /dev/null +++ b/maestro/pay-tests/.maestro/scripts/cancel-payment.js @@ -0,0 +1,21 @@ +// Cancels a WalletConnect Pay payment via the API. +// Expects WPAY_CUSTOMER_KEY, WPAY_MERCHANT_ID, and PAYMENT_ID env vars from Maestro. + +if (typeof WPAY_CUSTOMER_KEY === 'undefined') throw new Error('Missing env var: WPAY_CUSTOMER_KEY'); +if (typeof WPAY_MERCHANT_ID === 'undefined') throw new Error('Missing env var: WPAY_MERCHANT_ID'); +if (typeof PAYMENT_ID === 'undefined') throw new Error('Missing env var: PAYMENT_ID'); + +var response = http.post('https://api.pay.walletconnect.com/v1/payments/' + PAYMENT_ID + '/cancel', { + headers: { + 'Content-Type': 'application/json', + 'Api-Key': WPAY_CUSTOMER_KEY, + 'Merchant-Id': WPAY_MERCHANT_ID, + }, + body: JSON.stringify({}), +}); + +if (response.status < 200 || response.status >= 300) { + throw new Error('Cancel API returned HTTP ' + response.status + ': ' + response.body); +} + +console.log('Payment cancelled: ' + PAYMENT_ID); diff --git a/maestro/pay-tests/.maestro/scripts/create-payment.js b/maestro/pay-tests/.maestro/scripts/create-payment.js new file mode 100644 index 0000000..1fc11ae --- /dev/null +++ b/maestro/pay-tests/.maestro/scripts/create-payment.js @@ -0,0 +1,32 @@ +// Creates a WalletConnect Pay payment via the API. +// Expects WPAY_CUSTOMER_KEY and WPAY_MERCHANT_ID env vars from Maestro. +// Sets output.gateway_url and output.payment_id for use in subsequent flow steps. + +if (typeof WPAY_CUSTOMER_KEY === 'undefined') throw new Error('Missing env var: WPAY_CUSTOMER_KEY'); +if (typeof WPAY_MERCHANT_ID === 'undefined') throw new Error('Missing env var: WPAY_MERCHANT_ID'); + +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: typeof WPAY_AMOUNT !== 'undefined' ? WPAY_AMOUNT : '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; +output.payment_id = data.paymentId; diff --git a/maestro/pay-tests/README.md b/maestro/pay-tests/README.md new file mode 100644 index 0000000..c68c397 --- /dev/null +++ b/maestro/pay-tests/README.md @@ -0,0 +1,274 @@ +# Maestro Pay Tests + +Shared [Maestro](https://maestro.mobile.dev/) E2E test flows for **WalletConnect Pay**. These tests verify the full payment lifecycle — from scanning a payment link to confirming on-chain — and are designed to run on any wallet that integrates WalletConnect Pay (React Native, Kotlin, Swift, Flutter). + +## How It Works + +This action copies shared Maestro test flows and helper scripts into your workspace. Your CI workflow then runs `maestro test` against your built app. The flows use accessibility IDs (testIDs) to interact with UI elements, so every wallet platform must implement the same set of IDs on the corresponding components. + +## Prerequisites + +- **Maestro CLI** installed — use [`WalletConnect/actions/maestro/setup`](../setup) action +- **App built and installed** on simulator/emulator +- **Test mode enabled** in the wallet app (to expose the URL input field) + +## Required TestIDs + +Every wallet platform must add these accessibility identifiers to the corresponding UI elements. The tests will fail if any are missing. + +### Scanner / URL Entry + +| TestID | Element | Description | +|---|---|---| +| `button-scan` | Scan button on home screen | Opens the scanner/QR modal | +| `input-paste-url` | Text input in scan modal | For pasting payment URLs (**test mode only** — see below) | +| `button-submit-url` | Submit button in scan modal | Submits the pasted URL (**test mode only**) | + +### Payment Modal — Header + +| TestID | Element | Description | +|---|---|---| +| `pay-button-back` | Back arrow button | Returns to previous step | +| `pay-button-close` | Close (X) button | Dismisses the payment modal | + +### Payment Modal — Merchant Info & Loading + +| TestID | Element | Description | +|---|---|---| +| `pay-merchant-info` | Merchant display | Shows merchant name and payment amount | +| `pay-loading-message` | Loading text | Shown during payment processing | + +### Payment Modal — Option Selection + +| TestID | Element | Description | +|---|---|---| +| `pay-option-{index}` | Payment option (unselected) | 0-based index from the payment options array | +| `pay-option-{index}-selected` | Payment option (selected) | Same element when selected | +| `pay-info-required-badge` | "Info required" badge | Shown on options that require KYC | +| `pay-button-info` | Info (?) button in header | Explains KYC requirement | +| `pay-button-continue` | Continue button | Proceeds after selecting a payment option | + +### Payment Modal — Review Screen + +| TestID | Element | Description | +|---|---|---| +| `pay-review-token-{networkName}` | Token/network display | Dynamic — lowercase network name (e.g. `pay-review-token-base`) | +| `pay-button-pay` | Pay button | Confirms and submits the payment | + +### Payment Modal — Result Screen + +| TestID | Element | Description | +|---|---|---| +| `pay-result-container` | Result screen wrapper | Container for the result view | +| `pay-result-success-icon` | Success icon | Checkmark shown on successful payment | +| `pay-result-insufficient-funds-icon` | Insufficient funds icon | Shown when wallet balance is too low | +| `pay-result-expired-icon` | Expired icon | Shown for expired payment links | +| `pay-result-cancelled-icon` | Cancelled icon | Shown for cancelled payments | +| `pay-result-error-icon` | Generic error icon | Shown for other errors (e.g. already completed) | +| `pay-button-result-action-success` | "Got it!" button (success) | Dismisses the success result | +| `pay-button-result-action-insufficient_funds` | Action button (insufficient funds) | Dismisses the insufficient funds error | +| `pay-button-result-action-expired` | Action button (expired) | Dismisses the expired error | +| `pay-button-result-action-cancelled` | Action button (cancelled) | Dismisses the cancelled error | +| `pay-button-result-action-generic` | Action button (generic error) | Dismisses the generic error | + +### Dynamic TestID Patterns + +Some testIDs include dynamic values: + +- **`pay-option-{index}`** / **`pay-option-{index}-selected`** — `index` is 0-based from the payment options array. Example: `pay-option-0`, `pay-option-1-selected` +- **`pay-review-token-{networkName}`** — lowercase network name. Example: `pay-review-token-base`, `pay-review-token-ethereum` +- **`pay-button-result-action-{type}`** — one of: `success`, `insufficient_funds`, `expired`, `cancelled`, `generic` + +## Test Input Field Requirement + +Each wallet must add a **text input field** and **submit button** inside the scan/QR modal. This is required for Maestro to bypass camera/QR scanning and submit payment URLs directly. + +**Important:** This input should only be visible when a test mode flag is enabled (e.g. `ENV_TEST_MODE=true`). It should never appear in production builds. + +### Reference Implementation (React Native) + +From `ScannerOptionsModal.tsx` in the React Native wallet sample: + +```tsx +import Config from 'react-native-config'; + +const showTestInput = Config.ENV_TEST_MODE === 'true'; + +// Inside the modal component's render: +{showTestInput && ( + + + + +)} +``` + +The key points for any platform: +1. Gate visibility behind a test/debug build flag +2. Use `input-paste-url` as the accessibility ID for the text input +3. Use `button-submit-url` as the accessibility ID for the submit button +4. On submit, pass the URL to the same handler that processes scanned QR codes or deep links + +## Required Secrets + +These secrets must be configured in your repository for the tests to create and manipulate payments: + +| Secret | Description | +|---|---| +| `WPAY_CUSTOMER_KEY_SINGLE_NOKYC` | API key for single-option, no-KYC merchant | +| `WPAY_MERCHANT_ID_SINGLE_NOKYC` | Merchant ID for single-option, no-KYC merchant | +| `WPAY_CUSTOMER_KEY_MULTI_NOKYC` | API key for multi-option, no-KYC merchant | +| `WPAY_MERCHANT_ID_MULTI_NOKYC` | Merchant ID for multi-option, no-KYC merchant | +| `WPAY_CUSTOMER_KEY_MULTI_KYC` | API key for multi-option, KYC-required merchant | +| `WPAY_MERCHANT_ID_MULTI_KYC` | Merchant ID for multi-option, KYC-required merchant | + +Each merchant pair represents a different test configuration. The tests use these to create payments with specific option counts and KYC requirements. + +## Test Catalog + +| Flow | Description | Merchant Config | +|---|---|---| +| `pay_single_option_nokyc` | Happy path: single payment option, no KYC | SINGLE_NOKYC | +| `pay_single_option_nokyc_deeplink` | Same as above but opened via deep link | SINGLE_NOKYC | +| `pay_multiple_options_nokyc` | Select between multiple options, no KYC | MULTI_NOKYC | +| `pay_multiple_options_kyc` | Multiple options with KYC webview | MULTI_KYC | +| `pay_cancel_from_review` | Server-side cancellation on review screen | SINGLE_NOKYC | +| `pay_cancel_from_kyc` | Server-side cancellation during KYC | MULTI_KYC | +| `pay_kyc_back_navigation` | Back/close button navigation in KYC | MULTI_KYC | +| `pay_insufficient_funds` | Payment amount exceeds wallet balance | SINGLE_NOKYC | +| `pay_double_scan` | Re-scan same QR after completion | SINGLE_NOKYC | +| `pay_expired_link` | Hardcoded expired payment URL | None (hardcoded) | +| `pay_cancelled` | Hardcoded cancelled payment URL | None (hardcoded) | + +All flows are tagged with `pay` for filtering via `--include-tags`. + +## Deep Link Support + +The `pay_single_option_nokyc_deeplink` test uses Maestro's `openLink` command to open a `https://pay.walletconnect.com` URL. Your wallet must be configured to handle these URLs as deep links / universal links for this test to work. + +## Local Development + +For running tests locally during development. Requires [Maestro CLI](https://maestro.mobile.dev/) installed on your machine. + +### Setup + +1. **Create your secrets file** (one-time): + ```bash + cp .env.maestro.example .env.maestro + # Fill in the WPAY_* values (get them from your team or the WalletConnect Pay dashboard) + ``` + +2. **Run tests** (auto-downloads flows if not present): + ```bash + ./scripts/run-maestro-pay-tests.sh + ``` + +That's it. The script will automatically download the shared test flows from this repo if they're not already present, load secrets from `.env.maestro`, and run all pay-tagged tests. + +### Other local commands + +```bash +# Run a single test +./scripts/run-maestro-pay-tests.sh .maestro/pay_cancelled.yaml + +# Re-download flows (e.g. after an update) +./scripts/setup-maestro-pay-tests.sh + +# Download flows from a specific branch +./scripts/setup-maestro-pay-tests.sh feat/my-branch +``` + +## CI Usage + +### iOS (composite action) + +```yaml +steps: + - uses: actions/checkout@v4 + + # ... your platform-specific build steps ... + + - name: Copy shared Pay test flows + uses: WalletConnect/actions/maestro/pay-tests@main + + - name: Install Maestro + uses: WalletConnect/actions/maestro/setup@main + + # ... boot simulator, install app ... + + - name: Run Pay E2E tests + uses: WalletConnect/actions/maestro/run@main + with: + app-id: com.example.wallet.internal + wpay-customer-key-single-nokyc: ${{ secrets.WPAY_CUSTOMER_KEY_SINGLE_NOKYC }} + wpay-merchant-id-single-nokyc: ${{ secrets.WPAY_MERCHANT_ID_SINGLE_NOKYC }} + wpay-customer-key-multi-nokyc: ${{ secrets.WPAY_CUSTOMER_KEY_MULTI_NOKYC }} + wpay-merchant-id-multi-nokyc: ${{ secrets.WPAY_MERCHANT_ID_MULTI_NOKYC }} + wpay-customer-key-multi-kyc: ${{ secrets.WPAY_CUSTOMER_KEY_MULTI_KYC }} + wpay-merchant-id-multi-kyc: ${{ secrets.WPAY_MERCHANT_ID_MULTI_KYC }} +``` + +### Android (emulator runner) + +The `reactivecircus/android-emulator-runner` action runs everything inside a `script:` block, which cannot call composite actions. Use `maestro/pay-tests` and `maestro/setup` *before* the emulator step, then run `maestro test` inline: + +```yaml +steps: + - uses: actions/checkout@v4 + + # ... your platform-specific build steps ... + + - name: Copy shared Pay test flows + uses: WalletConnect/actions/maestro/pay-tests@main + + - name: Install Maestro + uses: WalletConnect/actions/maestro/setup@main + + - name: Run E2E tests on Android Emulator + id: maestro + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 34 + arch: x86_64 + script: | + adb install path/to/app.apk + $HOME/.maestro/bin/maestro test \ + --env APP_ID="com.example.wallet.internal" \ + --env WPAY_CUSTOMER_KEY_SINGLE_NOKYC="${{ secrets.WPAY_CUSTOMER_KEY_SINGLE_NOKYC }}" \ + --env WPAY_MERCHANT_ID_SINGLE_NOKYC="${{ secrets.WPAY_MERCHANT_ID_SINGLE_NOKYC }}" \ + --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_MULTI_KYC="${{ secrets.WPAY_CUSTOMER_KEY_MULTI_KYC }}" \ + --env WPAY_MERCHANT_ID_MULTI_KYC="${{ secrets.WPAY_MERCHANT_ID_MULTI_KYC }}" \ + --include-tags pay \ + --test-output-dir maestro-artifacts \ + --debug-output maestro-artifacts \ + .maestro/ + + - name: Upload Maestro artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: maestro-android-artifacts + path: | + maestro-artifacts/ + retention-days: 14 +``` diff --git a/maestro/pay-tests/action.yml b/maestro/pay-tests/action.yml new file mode 100644 index 0000000..1bb4a08 --- /dev/null +++ b/maestro/pay-tests/action.yml @@ -0,0 +1,40 @@ +name: Maestro Pay Tests +description: Copy shared WalletConnect Pay E2E test flows into the consumer workspace + +inputs: + target-dir: + description: 'Directory to copy test flows into (relative to workspace root)' + required: false + default: '.maestro' + +outputs: + maestro-dir: + description: 'Absolute path to the directory containing the test flows' + value: ${{ steps.copy.outputs.maestro-dir }} + +runs: + using: composite + steps: + - name: Copy shared Maestro Pay test flows + id: copy + shell: bash + run: | + set -euo pipefail + ACTION_DIR="${{ github.action_path }}" + TARGET_DIR="${{ github.workspace }}/${{ inputs.target-dir }}" + + mkdir -p "$TARGET_DIR/flows" "$TARGET_DIR/scripts" + + # Copy root-level pay test flows + cp "$ACTION_DIR"/.maestro/pay_*.yaml "$TARGET_DIR/" + + # Copy shared sub-flows + cp "$ACTION_DIR"/.maestro/flows/pay_*.yaml "$TARGET_DIR/flows/" + + # Copy scripts + cp "$ACTION_DIR"/.maestro/scripts/*.js "$TARGET_DIR/scripts/" + + echo "Copied Maestro Pay test flows to $TARGET_DIR" + ls -la "$TARGET_DIR" + + echo "maestro-dir=$TARGET_DIR" >> "$GITHUB_OUTPUT" diff --git a/maestro/run/action.yml b/maestro/run/action.yml new file mode 100644 index 0000000..a9ea474 --- /dev/null +++ b/maestro/run/action.yml @@ -0,0 +1,69 @@ +name: Maestro Pay Test Run +description: Execute Maestro Pay E2E tests with WalletConnect Pay secrets and upload artifacts + +inputs: + app-id: + description: 'Application bundle ID (iOS) or package name (Android)' + required: true + maestro-dir: + description: 'Directory containing Maestro test flows' + required: false + default: '.maestro' + tags: + description: 'Maestro --include-tags value (comma-separated)' + required: false + default: 'pay' + wpay-customer-key-single-nokyc: + description: 'WalletConnect Pay Customer API key (single-offramp-option, no-KYC)' + required: true + wpay-merchant-id-single-nokyc: + description: 'WalletConnect Pay merchant ID (single-offramp-option, no-KYC)' + required: true + wpay-customer-key-multi-nokyc: + description: 'WalletConnect Pay Customer API key (multiple-offramp-options, no-KYC)' + required: true + wpay-merchant-id-multi-nokyc: + description: 'WalletConnect Pay merchant ID (multiple-offramp-options, no-KYC))' + required: true + wpay-customer-key-multi-kyc: + description: 'WalletConnect Pay Customer API key (multiple-offramp-options, KYC-required)' + required: true + wpay-merchant-id-multi-kyc: + description: 'WalletConnect Pay merchant ID (multiple-offramp-options, KYC-required)' + required: true + artifact-name: + description: 'Name for the uploaded artifact' + required: false + default: 'maestro-artifacts' + +runs: + using: composite + steps: + - name: Run Maestro tests + id: maestro + shell: bash + run: | + set -o pipefail + maestro test \ + --env APP_ID="${{ inputs.app-id }}" \ + --env WPAY_CUSTOMER_KEY_SINGLE_NOKYC="${{ inputs.wpay-customer-key-single-nokyc }}" \ + --env WPAY_MERCHANT_ID_SINGLE_NOKYC="${{ inputs.wpay-merchant-id-single-nokyc }}" \ + --env WPAY_CUSTOMER_KEY_MULTI_NOKYC="${{ inputs.wpay-customer-key-multi-nokyc }}" \ + --env WPAY_MERCHANT_ID_MULTI_NOKYC="${{ inputs.wpay-merchant-id-multi-nokyc }}" \ + --env WPAY_CUSTOMER_KEY_MULTI_KYC="${{ inputs.wpay-customer-key-multi-kyc }}" \ + --env WPAY_MERCHANT_ID_MULTI_KYC="${{ inputs.wpay-merchant-id-multi-kyc }}" \ + --include-tags "${{ inputs.tags }}" \ + --test-output-dir maestro-artifacts \ + --debug-output maestro-artifacts \ + "${{ inputs.maestro-dir }}" 2>&1 | tee maestro-output.log + + - name: Upload Maestro artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: | + maestro-artifacts/ + maestro-output.log + if-no-files-found: warn + retention-days: 14 diff --git a/maestro/setup/action.yml b/maestro/setup/action.yml new file mode 100644 index 0000000..ac65f91 --- /dev/null +++ b/maestro/setup/action.yml @@ -0,0 +1,27 @@ +name: Maestro Setup +description: Install the Maestro CLI for mobile E2E testing + +inputs: + version: + description: 'Maestro version to install (e.g. 1.39.15). Leave empty for latest.' + required: false + default: '' + +runs: + using: composite + steps: + - name: Install Maestro CLI + shell: bash + run: | + set -euo pipefail + if command -v maestro &>/dev/null; then + echo "Maestro already installed: $(maestro --version)" + exit 0 + fi + + if [ -n "${{ inputs.version }}" ]; then + export MAESTRO_VERSION="${{ inputs.version }}" + fi + + curl -fsSL "https://get.maestro.mobile.dev" | bash + echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"