Skip to content

Maestro E2E Pay Tests #180

Maestro E2E Pay Tests

Maestro E2E Pay Tests #180

name: Maestro E2E Pay Tests
# "(skip-metrics)" tells the E2E KPI job (WalletConnect/actions) to drop this run.
run-name: ${{ ((github.event_name == 'pull_request' && github.event.pull_request.draft) || (github.event_name == 'workflow_dispatch' && github.ref_name != 'develop')) && format('{0} (skip-metrics)', github.event.pull_request.title || github.ref_name) || '' }}
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
branches:
- develop
- main
schedule:
# Run daily at 04:00 UTC (only on the default branch)
- cron: '0 4 * * *'
workflow_dispatch: {}
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}
cancel-in-progress: true
jobs:
check-balance:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
uses: ./.github/workflows/e2e_balance_check.yml
secrets: inherit
e2e-pay-tests:
needs: check-balance
if: always() && (github.event_name != 'pull_request' || github.event.pull_request.draft == false)
runs-on: macos-latest-xlarge
timeout-minutes: 45
permissions:
checks: write
actions: read
steps:
- uses: actions/checkout@v3
- name: Select Xcode 26 (iOS 26 SDK)
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26'
# Boot the simulator BEFORE the build (not right before `maestro test`).
# A never-booted device posts a one-time "Ready for Apple Intelligence"
# follow-up notification a few minutes after its first boot; when the sim
# was booted seconds before the tests, that banner landed ~8s into the
# first flow, covering the button Maestro was about to tap. Booting early
# lets it fire and auto-dismiss during the ~10 min build instead.
- name: Boot iOS simulator
run: |
# Pick whatever iPhone simulator the runner actually has, so this
# doesn't break each time the Xcode/runner image bumps the model.
# Single awk pass: pull the UDID from the first available iPhone row.
# awk exits 0 even when nothing matches (so the check below stays
# reachable under `set -eo pipefail`), `exit` avoids a head/SIGPIPE
# footgun, and it tolerates parens in the device name (e.g. SE).
SIMULATOR_UDID=$(xcrun simctl list devices available \
| awk '/iPhone/ { if (match($0, /[0-9A-Fa-f-]{36}/)) { print substr($0, RSTART, RLENGTH); exit } }')
if [ -z "$SIMULATOR_UDID" ]; then
echo "ERROR: no available iPhone simulator found"
xcrun simctl list devices available
exit 1
fi
echo "Using simulator: $SIMULATOR_UDID"
xcrun simctl boot "$SIMULATOR_UDID" 2>/dev/null || true
xcrun simctl bootstatus "$SIMULATOR_UDID"
# Belt and suspenders for the same banner: iOS stamps this key when it
# posts the follow-up (verified against a sim's CoreFollowUp items.db —
# the item's creationDate matches this key to the second). Pre-seeding
# it marks the notification as already shown, covering runs where the
# build finishes (warm SPM cache) before the banner would have fired.
# Best-effort: booting early is the primary defense, so a runner image
# where this domain isn't writable must not fail the whole run.
xcrun simctl spawn "$SIMULATOR_UDID" defaults write \
com.apple.generativeexperiences.corefollowup \
DateOfLastAppleIntelligenceReadinessCFU -date "$(date -u '+%Y-%m-%d %H:%M:%S +0000')" \
|| echo "WARNING: could not pre-seed Apple Intelligence CFU timestamp (non-fatal)"
- uses: actions/cache@v3
with:
path: |
**/SourcePackagesCache
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
- name: Build WalletApp for simulator (test mode)
id: build
run: |
set -o pipefail && env NSUnbufferedIO=YES \
xcodebuild \
-project "Example/ExampleApp.xcodeproj" \
-scheme "WalletApp" \
-destination "generic/platform=iOS Simulator" \
-derivedDataPath DerivedDataCache \
-clonedSourcePackagesDirPath ../SourcePackagesCache \
RELAY_HOST='relay.walletconnect.com' \
PROJECT_ID='${{ secrets.PROJECT_ID }}' \
TEST_WALLET_PRIVATE_KEY='${{ secrets.TEST_WALLET_PRIVATE_KEY }}' \
SWIFT_ACTIVE_COMPILATION_CONDITIONS='$(inherited) ENABLE_TEST_MODE' \
build \
| xcbeautify
- name: Copy shared Pay test flows
uses: WalletConnect/actions/maestro/pay-tests@4be7c0112f9651fc63b18c137650b75d2fc9b200
- name: Install Maestro
uses: WalletConnect/actions/maestro/setup@4be7c0112f9651fc63b18c137650b75d2fc9b200
with:
version: '2.4.0'
- name: Install app on simulator
run: |
APP_PATH=$(find DerivedDataCache -name "WalletApp.app" -path "*/Debug-iphonesimulator/*" | head -1)
if [ -z "$APP_PATH" ]; then
echo "ERROR: WalletApp.app not found in DerivedDataCache"
find DerivedDataCache -name "*.app" -path "*/Debug-iphonesimulator/*"
exit 1
fi
echo "Installing: $APP_PATH"
xcrun simctl install booted "$APP_PATH"
- name: Run Maestro Pay E2E tests
id: maestro
run: |
maestro test \
--env APP_ID=com.walletconnect.walletapp \
--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 debug-artifacts/maestro-output \
.maestro/
# Reset the USDT Permit2 allowance back to 0 so the next pay_usdt_polygon run
# re-exercises the on-chain `approve` step. Runs even when the suite failed
# (if: always()) so a mid-flow failure can't leave the allowance set.
# continue-on-error so a reset hiccup can't fail an otherwise-green run.
# It signs a transaction, so it's a Node step (not a Maestro runScript); the
# private key goes via env, never the CLI.
- name: Reset USDT Permit2 allowance (Polygon)
if: always()
continue-on-error: true
uses: WalletConnect/actions/maestro/permit2-reset@4be7c0112f9651fc63b18c137650b75d2fc9b200
with:
chain-id: eip155:137
# polygon-rpc.com gates requests (HTTP 401 "tenant disabled") in CI; use publicnode.
rpc-url: https://polygon-bor-rpc.publicnode.com
token-address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F'
private-key: ${{ secrets.TEST_WALLET_PRIVATE_KEY }}
- name: Capture simulator logs
if: always()
run: |
xcrun simctl spawn booted log collect --output debug-artifacts/sim-logs.logarchive 2>/dev/null || true
- name: Upload debug artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: maestro-pay-test-artifacts
path: |
debug-artifacts/**/*.png
debug-artifacts/**/*.jpg
debug-artifacts/**/*.jpeg
debug-artifacts/**/*.mp4
debug-artifacts/**/*.mov
debug-artifacts/**/*.webm
debug-artifacts/**/*.gif
debug-artifacts/sim-logs.logarchive
retention-days: 14
if-no-files-found: ignore
- name: Send Slack notification
if: failure()
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "E2E Test Report - iOS - Pay",
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "🧪 E2E Test Report - iOS (pay)" }
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Project:*\n`Reown Swift`" },
{ "type": "mrkdwn", "text": "*Platform:*\n`iOS`" },
{ "type": "mrkdwn", "text": "*Branch:*\n`${{ github.ref_name }}`" },
{ "type": "mrkdwn", "text": "*Trigger:*\n`${{ github.event_name }}`" }
]
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Build:*\n`${{ steps.build.outcome == 'success' && '✅ Success' || '❌ Failed' }}`" },
{ "type": "mrkdwn", "text": "*Maestro Tests:*\n`${{ steps.maestro.outcome == 'success' && '✅ Passed' || '❌ Failed' }}`" },
{ "type": "mrkdwn", "text": "*Overall Status:*\n`${{ job.status == 'success' && '✅ Success' || '❌ Failed' }}`" }
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "View Workflow Run" },
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
},
{
"type": "button",
"text": { "type": "plain_text", "text": "Download Artifacts" },
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
}
]
}
]
}