diff --git a/.github/actions/ci-setup-android-files/action.yml b/.github/actions/ci-setup-android-files/action.yml new file mode 100644 index 000000000..1ddcdf980 --- /dev/null +++ b/.github/actions/ci-setup-android-files/action.yml @@ -0,0 +1,58 @@ +name: 'Setup Android Files' +description: 'Setup required files for building an Android project' + +inputs: + root-path: + description: 'Path of the react-native project' + required: true + google-services-file: + description: 'Google Services Json file content, base64 encoded' + required: false + secrets-file: + description: 'Secrets properties file content' + required: true + keystore: + description: 'Keystore file content, base64 encoded' + required: true + keystore-name: + description: 'Keystore file name' + required: true + sentry-file: + description: 'Sentry file content' + required: true + project-id: + description: 'Reown Cloud ID' + required: true + relay-url: + description: 'Relay URL' + required: true + sentry-dsn: + description: 'Sentry DSN' + required: true + release-type: + description: 'Release type of the project (debug/internal/production)' + default: 'debug' + +runs: + using: "composite" + steps: + - name: Create Google Services Json File + if: ${{ inputs.google-services-file != '' }} + shell: bash + run: echo "${{ inputs.google-services-file }}" | base64 --decode > ${{ inputs.root-path }}/android/app/google-services.json + + - name: Create secrets.properties File + shell: bash + run: echo "${{ inputs.secrets-file }}" > ${{ inputs.root-path }}/android/secrets.properties + + - name: Decode Keystore + shell: bash + run: echo "${{ inputs.keystore }}" | base64 --decode > ${{ inputs.root-path }}/android/app/${{ inputs.keystore-name }}.keystore + + - name: Create Sentry File + shell: bash + run: echo "${{ inputs.sentry-file }}" > ${{ inputs.root-path }}/android/sentry.properties + + - name: Create Env File + shell: bash + run: echo -e "ENV_PROJECT_ID=${{ inputs.project-id }}\nENV_RELAY_URL=${{ inputs.relay-url }}\nENV_SENTRY_DSN=${{ inputs.sentry-dsn }}\nENV_SENTRY_TAG=${{ inputs.release-type }}" >> ${{ inputs.root-path }}/.env.${{ inputs.release-type }} \ No newline at end of file diff --git a/.github/actions/ci-setup-ios-files/action.yml b/.github/actions/ci-setup-ios-files/action.yml new file mode 100644 index 000000000..c3007b92e --- /dev/null +++ b/.github/actions/ci-setup-ios-files/action.yml @@ -0,0 +1,41 @@ +name: 'Setup iOS Files' +description: 'Setup required files for building an iOS project' + +inputs: + root-path: + description: 'Path of the react-native project' + required: true + google-services-file: + description: 'Google Services Plist file content, base64 encoded' + required: false + sentry-file: + description: 'Sentry file content' + required: true + project-id: + description: 'WalletConnect Cloud ID' + required: true + relay-url: + description: 'WalletConnect Relay URL' + required: false + sentry-dsn: + description: 'Sentry DSN' + required: true + release-type: + description: 'Release type of the project (debug/internal/production)' + default: 'debug' + +runs: + using: "composite" + steps: + - name: Create .env file + shell: bash + run: echo -e "ENV_PROJECT_ID=${{ inputs.project-id }}\nENV_RELAY_URL=${{ inputs.relay-url }}\nENV_SENTRY_DSN=${{ inputs.sentry-dsn }}\nENV_SENTRY_TAG=${{ inputs.release-type }}" >> ${{ inputs.root-path }}/.env.${{ inputs.release-type }} + + - name: Create sentry.properties file + shell: bash + run: echo "${{ inputs.sentry-file }}" > ${{ inputs.root-path }}/ios/sentry.properties + + - name: Create GoogleService-Info.plist file + if: ${{ inputs.google-services-file != '' }} + shell: bash + run: echo "${{ inputs.google-services-file }}" | base64 --decode > ${{ inputs.root-path }}/ios/GoogleService-Info.plist \ No newline at end of file diff --git a/.github/workflows/ci_e2e_relay.yaml b/.github/workflows/ci_e2e_relay.yaml new file mode 100644 index 000000000..0ac0a88cc --- /dev/null +++ b/.github/workflows/ci_e2e_relay.yaml @@ -0,0 +1,604 @@ +name: CI E2E Relay +# Cross-app WalletConnect *relay* E2E (connect / sign / switch-chain / disconnect / +# restore), named by the SDK under test: +# relay-walletkit — native rn_cli_wallet (under test) vs deployed appkit web dapp +# relay-appkit — native appkit-wagmi (under test) vs deployed rn_cli_wallet web +# In each, Maestro drives the native app + its web counterparty in the device +# browser (one device, one flow). Separate from the WC Pay E2E +# (ci_e2e_walletkit.yaml). See .context/RELAY_E2E_PLAN.md. +# +# Flows live in .maestro/relay/ (tags: relay-walletkit / relay-appkit). Build +# steps mirror the proven walletkit-build-and-maestro composite; kept +# self-contained here so the relay path carries no pay-core / merchant / permit2 +# baggage. + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + schedule: + - cron: '0 9 * * *' # Daily canary: wallet (main) native vs deployed appkit web. + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + paths: + - 'wallets/rn_cli_wallet/**' + - 'dapps/appkit-wagmi/**' + - '.maestro/relay/**' + - '.github/workflows/ci_e2e_relay.yaml' + workflow_dispatch: + inputs: + platform: + description: 'Platform to run' + required: false + default: both + type: choice + options: [both, android, ios] + dapp-url: + description: 'appkit-wagmi web dapp base URL (defaults to vars.RELAY_DAPP_URL)' + required: false + default: '' + type: string + +env: + # Deployed web counterparties (tracking main). Must be https URLs reachable from + # the emulator/simulator AND allowlisted on the reown projectId. + # DAPP_URL — appkit-wagmi web dapp (counterparty for the relay-walletkit suite) + # WALLET_URL — rn_cli_wallet web (counterparty for the relay-appkit suite) + # DAPP_URL override precedence: dispatch input > RELAY_DAPP_URL repo var > default. + DAPP_URL: ${{ inputs.dapp-url != '' && inputs.dapp-url || vars.RELAY_DAPP_URL || 'https://react-native-appkit.vercel.app' }} + WALLET_URL: ${{ vars.RELAY_WALLET_URL || 'https://react-native-walletkit.vercel.app' }} + APP_ID: com.walletconnect.web3wallet.rnsample.internal + WALLET_ROOT: wallets/rn_cli_wallet + +jobs: + e2e-relay-walletkit-android: + if: >- + github.event_name != 'workflow_dispatch' || inputs.platform == 'both' || inputs.platform == 'android' + name: E2E Relay WalletKit - Android + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + cache-dependency-path: ${{ env.WALLET_ROOT }}/yarn.lock + + - name: Install dependencies + working-directory: ${{ env.WALLET_ROOT }} + run: yarn install --immutable + + - name: Write wallet .env + working-directory: ${{ env.WALLET_ROOT }} + env: + WALLET_ENV_FILE: ${{ secrets.WALLETKIT_ENV_FILE }} + WALLET_PRIVATE_KEY: ${{ secrets.TEST_WALLET_PRIVATE_KEY }} + run: | + set -euo pipefail + printf '%s\n' "$WALLET_ENV_FILE" > .env + { + echo "EXPO_PUBLIC_TEST_PRIVATE_KEY=$WALLET_PRIVATE_KEY" + echo "EXPO_PUBLIC_TEST_MODE=true" + echo "SENTRY_DISABLE_AUTO_UPLOAD=true" + } >> .env + + - name: Install Java 17 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + + - name: Accept Android Licenses + uses: SimonMarquis/android-accept-licenses@8d8deab5b7ab2aaea9ed69f0b802e53173f21fd1 # v1 + + - name: Install Android NDK 27.0.12077973 + run: | + set +o pipefail + yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;27.0.12077973" + + - name: Expo Prebuild (Android) + working-directory: ${{ env.WALLET_ROOT }} + run: npx expo prebuild --platform android --no-install + + - name: Add Secrets file + working-directory: ${{ env.WALLET_ROOT }} + env: + ANDROID_SECRETS_FILE: ${{ secrets.ANDROID_SECRETS_FILE }} + run: printf '%s\n' "$ANDROID_SECRETS_FILE" > android/secrets.properties + + - name: Add Keystore file + working-directory: ${{ env.WALLET_ROOT }} + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.WC_INTERNAL_KEYSTORE }} + ANDROID_KEYSTORE_NAME: ${{ vars.WC_INTERNAL_KEYSTORE_NAME }} + run: printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode >> "android/app/${ANDROID_KEYSTORE_NAME}.keystore" + + - name: Optimize Gradle for CI + working-directory: ${{ env.WALLET_ROOT }} + run: | + { + echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m" + echo "org.gradle.parallel=true" + echo "org.gradle.caching=true" + echo "reactNativeArchitectures=x86_64" + } >> android/gradle.properties + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ${{ env.WALLET_ROOT }}/android/.gradle + ${{ env.WALLET_ROOT }}/android/app/.cxx + ${{ env.WALLET_ROOT }}/android/app/build/generated + key: ${{ runner.os }}-gradle-relay-${{ hashFiles(format('{0}/android/**/*.gradle*', env.WALLET_ROOT), format('{0}/android/**/gradle-wrapper.properties', env.WALLET_ROOT)) }} + restore-keys: | + ${{ runner.os }}-gradle-relay- + + - name: Build APK + working-directory: ${{ env.WALLET_ROOT }} + run: | + 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 + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Install Maestro + uses: WalletConnect/actions/maestro/setup@4be7c0112f9651fc63b18c137650b75d2fc9b200 + + - name: Run Maestro relay tests (Android) + id: maestro + uses: reactivecircus/android-emulator-runner@70f4dee990796918b78d040e3278474bdbd348a7 # v2 + env: + APK_PATH: ${{ env.WALLET_ROOT }}/android/app/build/outputs/apk/internal/app-internal.apk + with: + api-level: 34 + arch: x86_64 + target: default + avd-name: test_device + ram-size: 4096M + disk-size: 4096M + heap-size: 576M + emulator-boot-timeout: 900 + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -no-snapshot-load + disable-animations: true + # Single line: the android-emulator-runner `script` does not honor + # backslash line-continuations (they leak a literal "\" as an arg → + # "Flow path does not exist"). Mirrors the walletkit composite. + script: | + adb install "$APK_PATH"; mkdir -p maestro-artifacts; "$HOME/.maestro/bin/maestro" test --env APP_ID="$APP_ID" --env DAPP_URL="$DAPP_URL" --include-tags relay-walletkit --test-output-dir maestro-artifacts --debug-output "${RUNNER_TEMP}/maestro-debug" .maestro/relay + + - name: Upload Maestro artifacts (Android) + if: ${{ !cancelled() }} + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: maestro-relay-walletkit-android-artifacts + path: | + maestro-artifacts/** + if-no-files-found: warn + retention-days: 14 + + e2e-relay-walletkit-ios: + # macOS is expensive: only the daily cron and explicit dispatch run iOS. + if: >- + (github.event_name == 'schedule') + || (github.event_name == 'workflow_dispatch' && (inputs.platform == 'both' || inputs.platform == 'ios')) + || (github.event_name == 'pull_request' && github.event.pull_request.draft == false) + name: E2E Relay WalletKit - iOS + runs-on: macos-latest-xlarge + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + cache-dependency-path: ${{ env.WALLET_ROOT }}/yarn.lock + + - name: Install dependencies + working-directory: ${{ env.WALLET_ROOT }} + run: yarn install --immutable + + - name: Write wallet .env + working-directory: ${{ env.WALLET_ROOT }} + env: + WALLET_ENV_FILE: ${{ secrets.WALLETKIT_ENV_FILE }} + WALLET_PRIVATE_KEY: ${{ secrets.TEST_WALLET_PRIVATE_KEY }} + run: | + set -euo pipefail + printf '%s\n' "$WALLET_ENV_FILE" > .env + { + echo "EXPO_PUBLIC_TEST_PRIVATE_KEY=$WALLET_PRIVATE_KEY" + echo "EXPO_PUBLIC_TEST_MODE=true" + echo "SENTRY_DISABLE_AUTO_UPLOAD=true" + } >> .env + + - name: Select Xcode 26.2 + run: | + XCODE_PATH=/Applications/Xcode_26.2.app/Contents/Developer + sudo xcode-select -s "$XCODE_PATH" + xcodebuild -version + + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + + - name: Expo Prebuild (iOS) + working-directory: ${{ env.WALLET_ROOT }} + run: APP_VARIANT=internal npx expo prebuild --platform ios --no-install + + - name: Set Xcode paths + run: | + XCODEPROJ=$(find "$WALLET_ROOT/ios" -maxdepth 1 -name "*.xcodeproj" -print -quit) + echo "XCODE_PROJECT_PATH=$XCODEPROJ" >> "$GITHUB_ENV" + echo "XCWORKSPACE_PATH=$WALLET_ROOT/ios/ReactNWallet.xcworkspace" >> "$GITHUB_ENV" + + - name: Cache Pods + id: pods-cache + uses: actions/cache@v4 + with: + path: ${{ env.WALLET_ROOT }}/ios/Pods + key: ${{ runner.os }}-pods-relay-${{ hashFiles(format('{0}/ios/Podfile.lock', env.WALLET_ROOT)) }} + + - name: Build for Simulator + uses: maierj/fastlane-action@v3.0.0 + with: + lane: build_for_simulator + env: + SCHEME: ReactNWallet + BUNDLE_ID: com.walletconnect.web3wallet.rnsample.internal + PODFILE_PATH: ${{ env.WALLET_ROOT }}/ios/Podfile + XCWORKSPACE_PATH: ${{ env.XCWORKSPACE_PATH }} + XCODE_PROJECT_PATH: ${{ env.XCODE_PROJECT_PATH }} + PROJECT_TYPE: wallet + RELEASE_TYPE: internal + BUILD_FOR_SIMULATOR: true + PODS_CACHE_HIT: ${{ steps.pods-cache.outputs.cache-hit }} + SENTRY_DISABLE_AUTO_UPLOAD: true + + - name: Find .app path + run: echo "APP_PATH=$(cat build/app_path.txt)" >> "$GITHUB_ENV" + + - name: Boot iOS Simulator + run: | + DEVICE_TYPE=$(xcrun simctl list devicetypes -j | \ + jq -r '.devicetypes[] | select(.productFamily == "iPhone") | .identifier' | tail -1) + DEVICE_ID=$(xcrun simctl create "E2E-iPhone" "$DEVICE_TYPE" 2>/dev/null || \ + xcrun simctl list devices available -j | \ + jq -r '.devices | to_entries[] | select(.key | contains("iOS")) | .value[] | select(.name | startswith("iPhone")) | .udid' | head -1) + xcrun simctl boot "$DEVICE_ID" || true + xcrun simctl bootstatus "$DEVICE_ID" -b + xcrun simctl spawn "$DEVICE_ID" defaults write com.apple.UIKit UIAnimationDragCoefficient 0 || true + echo "DEVICE_ID=$DEVICE_ID" >> "$GITHUB_ENV" + + - name: Install app on Simulator + run: xcrun simctl install "$DEVICE_ID" "$APP_PATH" + + - name: Install Maestro + uses: WalletConnect/actions/maestro/setup@4be7c0112f9651fc63b18c137650b75d2fc9b200 + + - name: Run Maestro relay tests (iOS) + id: maestro + run: | + xcrun simctl launch "$DEVICE_ID" "$APP_ID" || true + mkdir -p maestro-artifacts + "$HOME/.maestro/bin/maestro" test \ + --env APP_ID="$APP_ID" \ + --env DAPP_URL="$DAPP_URL" \ + --include-tags relay-walletkit \ + --test-output-dir maestro-artifacts \ + --debug-output "${RUNNER_TEMP}/maestro-debug" \ + .maestro/relay + + - name: Upload Maestro artifacts (iOS) + if: ${{ !cancelled() }} + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: maestro-relay-walletkit-ios-artifacts + path: | + maestro-artifacts/** + if-no-files-found: warn + retention-days: 14 + + # --------------------------------------------------------------------------- + # MIRROR suite — native appkit-wagmi (app under test) vs deployed web wallet. + # Secondary to the wallet-under-test suite; reuses the same shared signing + # secrets as the appkit release (ANDROID_SECRETS_FILE / WC_INTERNAL_KEYSTORE) + # plus APPKIT_ENV_FILE. Runs `--include-tags relay-appkit`. + # --------------------------------------------------------------------------- + e2e-relay-appkit-android: + if: >- + github.event_name != 'workflow_dispatch' || inputs.platform == 'both' || inputs.platform == 'android' + name: E2E Relay AppKit - Android + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + APP_ID: com.walletconnect.web3modal.rnsample.internal + APP_ROOT: dapps/appkit-wagmi + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + cache-dependency-path: ${{ env.APP_ROOT }}/yarn.lock + + - name: Install dependencies + working-directory: ${{ env.APP_ROOT }} + run: yarn install --immutable + + - name: Write dapp .env + working-directory: ${{ env.APP_ROOT }} + env: + APPKIT_ENV_FILE: ${{ secrets.APPKIT_ENV_FILE }} + run: | + set -euo pipefail + printf '%s\n' "$APPKIT_ENV_FILE" > .env + echo "SENTRY_DISABLE_AUTO_UPLOAD=true" >> .env + + - name: Install Java 17 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + architecture: x86_64 + + - name: Accept Android Licenses + uses: SimonMarquis/android-accept-licenses@8d8deab5b7ab2aaea9ed69f0b802e53173f21fd1 # v1 + + - name: Install Android NDK 27.0.12077973 + run: | + set +o pipefail + yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;27.0.12077973" + + - name: Expo Prebuild (Android) + working-directory: ${{ env.APP_ROOT }} + run: npx expo prebuild --platform android --no-install + + - name: Add Secrets file + working-directory: ${{ env.APP_ROOT }} + env: + ANDROID_SECRETS_FILE: ${{ secrets.ANDROID_SECRETS_FILE }} + run: printf '%s\n' "$ANDROID_SECRETS_FILE" > android/secrets.properties + + - name: Add Keystore file + working-directory: ${{ env.APP_ROOT }} + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.WC_INTERNAL_KEYSTORE }} + ANDROID_KEYSTORE_NAME: ${{ vars.WC_INTERNAL_KEYSTORE_NAME }} + run: printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode >> "android/app/${ANDROID_KEYSTORE_NAME}.keystore" + + - name: Optimize Gradle for CI + working-directory: ${{ env.APP_ROOT }} + run: | + { + echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m" + echo "org.gradle.parallel=true" + echo "org.gradle.caching=true" + echo "reactNativeArchitectures=x86_64" + } >> android/gradle.properties + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ${{ env.APP_ROOT }}/android/.gradle + ${{ env.APP_ROOT }}/android/app/.cxx + ${{ env.APP_ROOT }}/android/app/build/generated + key: ${{ runner.os }}-gradle-relay-appkit-${{ hashFiles(format('{0}/android/**/*.gradle*', env.APP_ROOT), format('{0}/android/**/gradle-wrapper.properties', env.APP_ROOT)) }} + restore-keys: | + ${{ runner.os }}-gradle-relay-appkit- + + - name: Build APK + working-directory: ${{ env.APP_ROOT }}/android + # --stacktrace to surface the failing config-time `node` exec (the + # default lifecycle log swallows the subprocess stderr). + run: | + export SENTRY_DISABLE_AUTO_UPLOAD=true + ./gradlew app:assembleInternal --stacktrace + + - 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 + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Install Maestro + uses: WalletConnect/actions/maestro/setup@4be7c0112f9651fc63b18c137650b75d2fc9b200 + + - name: Run Maestro relay-appkit tests (Android) + id: maestro + uses: reactivecircus/android-emulator-runner@70f4dee990796918b78d040e3278474bdbd348a7 # v2 + env: + APK_PATH: ${{ env.APP_ROOT }}/android/app/build/outputs/apk/internal/app-internal.apk + with: + api-level: 34 + arch: x86_64 + target: default + avd-name: test_device + ram-size: 4096M + disk-size: 4096M + heap-size: 576M + emulator-boot-timeout: 900 + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -no-snapshot-load + disable-animations: true + # Single line (see walletkit note): the android-emulator-runner script + # does not honor backslash line-continuations. + script: | + adb install "$APK_PATH"; mkdir -p maestro-artifacts; "$HOME/.maestro/bin/maestro" test --env APP_ID="$APP_ID" --env WALLET_URL="$WALLET_URL" --include-tags relay-appkit --test-output-dir maestro-artifacts --debug-output "${RUNNER_TEMP}/maestro-debug" .maestro/relay + + - name: Upload Maestro artifacts (Android) + if: ${{ !cancelled() }} + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: maestro-relay-appkit-android-artifacts + path: | + maestro-artifacts/** + if-no-files-found: warn + retention-days: 14 + + e2e-relay-appkit-ios: + # UNVERIFIED: the dapp's fastlane simulator build (scheme RNAppKit, workspace + # path, PROJECT_TYPE, app_path.txt location) hasn't been run yet. Gated to + # explicit dispatch so it can't destabilize PR/scheduled signal until proven. + if: github.event_name == 'workflow_dispatch' && inputs.platform == 'ios' + name: E2E Relay AppKit - iOS + runs-on: macos-latest-xlarge + timeout-minutes: 60 + env: + APP_ID: com.walletconnect.web3modal.rnsample.internal + APP_ROOT: dapps/appkit-wagmi + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: yarn + cache-dependency-path: ${{ env.APP_ROOT }}/yarn.lock + + - name: Install dependencies + working-directory: ${{ env.APP_ROOT }} + run: yarn install --immutable + + - name: Write dapp .env + working-directory: ${{ env.APP_ROOT }} + env: + APPKIT_ENV_FILE: ${{ secrets.APPKIT_ENV_FILE }} + run: | + set -euo pipefail + printf '%s\n' "$APPKIT_ENV_FILE" > .env + echo "SENTRY_DISABLE_AUTO_UPLOAD=true" >> .env + + - name: Select Xcode 26.2 + run: | + sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer + xcodebuild -version + + - name: Install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + + - name: Expo Prebuild (iOS) + working-directory: ${{ env.APP_ROOT }} + run: APP_VARIANT=internal npx expo prebuild --platform ios --no-install + + - name: Set Xcode paths + run: | + XCODEPROJ=$(find "$APP_ROOT/ios" -maxdepth 1 -name "*.xcodeproj" -print -quit) + echo "XCODE_PROJECT_PATH=$XCODEPROJ" >> "$GITHUB_ENV" + echo "XCWORKSPACE_PATH=$APP_ROOT/ios/RNAppKit.xcworkspace" >> "$GITHUB_ENV" + + - name: Cache Pods + id: pods-cache + uses: actions/cache@v4 + with: + path: ${{ env.APP_ROOT }}/ios/Pods + key: ${{ runner.os }}-pods-relay-appkit-${{ hashFiles(format('{0}/ios/Podfile.lock', env.APP_ROOT)) }} + + - name: Build for Simulator + uses: maierj/fastlane-action@v3.0.0 + with: + lane: build_for_simulator + env: + SCHEME: RNAppKit + BUNDLE_ID: com.walletconnect.web3modal.rnsample.internal + PODFILE_PATH: ${{ env.APP_ROOT }}/ios/Podfile + XCWORKSPACE_PATH: ${{ env.XCWORKSPACE_PATH }} + XCODE_PROJECT_PATH: ${{ env.XCODE_PROJECT_PATH }} + PROJECT_TYPE: dapp + RELEASE_TYPE: internal + BUILD_FOR_SIMULATOR: true + PODS_CACHE_HIT: ${{ steps.pods-cache.outputs.cache-hit }} + SENTRY_DISABLE_AUTO_UPLOAD: true + + - name: Find .app path + run: echo "APP_PATH=$(cat build/app_path.txt)" >> "$GITHUB_ENV" + + - name: Boot iOS Simulator + run: | + DEVICE_TYPE=$(xcrun simctl list devicetypes -j | \ + jq -r '.devicetypes[] | select(.productFamily == "iPhone") | .identifier' | tail -1) + DEVICE_ID=$(xcrun simctl create "E2E-iPhone" "$DEVICE_TYPE" 2>/dev/null || \ + xcrun simctl list devices available -j | \ + jq -r '.devices | to_entries[] | select(.key | contains("iOS")) | .value[] | select(.name | startswith("iPhone")) | .udid' | head -1) + xcrun simctl boot "$DEVICE_ID" || true + xcrun simctl bootstatus "$DEVICE_ID" -b + xcrun simctl spawn "$DEVICE_ID" defaults write com.apple.UIKit UIAnimationDragCoefficient 0 || true + echo "DEVICE_ID=$DEVICE_ID" >> "$GITHUB_ENV" + + - name: Install app on Simulator + run: xcrun simctl install "$DEVICE_ID" "$APP_PATH" + + - name: Install Maestro + uses: WalletConnect/actions/maestro/setup@4be7c0112f9651fc63b18c137650b75d2fc9b200 + + - name: Run Maestro relay-appkit tests (iOS) + id: maestro + run: | + xcrun simctl launch "$DEVICE_ID" "$APP_ID" || true + mkdir -p maestro-artifacts + "$HOME/.maestro/bin/maestro" test \ + --env APP_ID="$APP_ID" \ + --env WALLET_URL="$WALLET_URL" \ + --include-tags relay-appkit \ + --test-output-dir maestro-artifacts \ + --debug-output "${RUNNER_TEMP}/maestro-debug" \ + .maestro/relay + + - name: Upload Maestro artifacts (iOS) + if: ${{ !cancelled() }} + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: maestro-relay-appkit-ios-artifacts + path: | + maestro-artifacts/** + if-no-files-found: warn + retention-days: 14 diff --git a/.github/workflows/ci_e2e_tests_android.yaml b/.github/workflows/ci_e2e_tests_android.yaml index 80473c7ef..48f03911f 100644 --- a/.github/workflows/ci_e2e_tests_android.yaml +++ b/.github/workflows/ci_e2e_tests_android.yaml @@ -12,4 +12,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Do nothing - run: echo "This is a placeholder workflow to enable manual triggers from feature branches." \ No newline at end of file + run: echo "This is a placeholder workflow to enable manual triggers from feature branches." diff --git a/.github/workflows/ci_e2e_tests_ios.yaml b/.github/workflows/ci_e2e_tests_ios.yaml index 92d105670..cd75b8ed1 100644 --- a/.github/workflows/ci_e2e_tests_ios.yaml +++ b/.github/workflows/ci_e2e_tests_ios.yaml @@ -12,4 +12,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Do nothing - run: echo "This is a placeholder workflow to enable manual triggers from feature branches." \ No newline at end of file + run: echo "This is a placeholder workflow to enable manual triggers from feature branches." diff --git a/.maestro/native/connect_confirm.yaml b/.maestro/native/connect_confirm.yaml index 5bf45da40..a3d3b0286 100644 --- a/.maestro/native/connect_confirm.yaml +++ b/.maestro/native/connect_confirm.yaml @@ -6,8 +6,24 @@ name: React Native Dapp to React Native Wallet Connection Confirmed permissions: all: allow - startRecording: "Native Connection Confirmed" -- tapOn: "Connect" -- tapOn: "React Native Wallet" -- tapOn: "Approve" +- tapOn: + id: "connect-button" +- tapOn: + id: "all-wallets" +- waitForAnimationToEnd +- tapOn: "React" +- runFlow: + when: + visible: "Open" + platform: iOS + commands: + - tapOn: "Open" +- extendedWaitUntil: + visible: + id: "approve-button" + timeout: 10000 +- tapOn: + id: "approve-button" +- waitForAnimationToEnd - assertVisible: "Sign message" - stopRecording \ No newline at end of file diff --git a/.maestro/native/connect_reject.yaml b/.maestro/native/connect_reject.yaml index 2015895ff..2e3e921ff 100644 --- a/.maestro/native/connect_reject.yaml +++ b/.maestro/native/connect_reject.yaml @@ -1,11 +1,29 @@ appId: com.walletconnect.web3modal.rnsample.internal -name: React Native Dapp to React Native Wallet Connection Confirmed +name: React Native Dapp to React Native Wallet Connection Rejected --- - clearState - launchApp: permissions: all: allow -- tapOn: "Connect" -- tapOn: "React Native Wallet" -- tapOn: "Reject" -- assertVisible: "Connection declined" \ No newline at end of file +- startRecording: "Native to Native Connection Rejected" +- tapOn: + id: "connect-button" +- tapOn: + id: "all-wallets" +- waitForAnimationToEnd +- tapOn: "React" +- runFlow: + when: + visible: "Open" + platform: iOS + commands: + - tapOn: "Open" +- extendedWaitUntil: + visible: + id: "reject-button" + timeout: 10000 +- tapOn: + id: "reject-button" +- waitForAnimationToEnd +- assertVisible: "Connection declined" +- stopRecording \ No newline at end of file diff --git a/.maestro/native/personal_sign_confirm.yaml b/.maestro/native/personal_sign_confirm.yaml index 31ec415eb..48ca8dd36 100644 --- a/.maestro/native/personal_sign_confirm.yaml +++ b/.maestro/native/personal_sign_confirm.yaml @@ -5,7 +5,21 @@ name: React Native Dapp to React Native Wallet Personal Sign Confirmed permissions: all: allow - startRecording: "Native personal_sign Confirmed" +- extendedWaitUntil: + visible: "Sign message" + timeout: 10000 - tapOn: "Sign message" -- tapOn: "Approve" +- runFlow: + when: + visible: "Open" + platform: iOS + commands: + - tapOn: "Open" +- extendedWaitUntil: + visible: + id: "approve-button" + timeout: 10000 +- tapOn: + id: "approve-button" - assertVisible: "Request response" - stopRecording \ No newline at end of file diff --git a/.maestro/native/personal_sign_reject.yaml b/.maestro/native/personal_sign_reject.yaml index c102a41f5..5ccd5d517 100644 --- a/.maestro/native/personal_sign_reject.yaml +++ b/.maestro/native/personal_sign_reject.yaml @@ -5,7 +5,21 @@ name: React Native Dapp to React Native Wallet Personal Sign Rejected permissions: all: allow - startRecording: "Native personal_sign Rejected" +- extendedWaitUntil: + visible: "Sign message" + timeout: 10000 - tapOn: "Sign message" -- tapOn: "Reject" +- runFlow: + when: + visible: "Open" + platform: iOS + commands: + - tapOn: "Open" +- extendedWaitUntil: + visible: + id: "reject-button" + timeout: 10000 +- tapOn: + id: "reject-button" - assertVisible: "Request failure" - stopRecording \ No newline at end of file diff --git a/.maestro/relay/_connect_appkit.yaml b/.maestro/relay/_connect_appkit.yaml new file mode 100644 index 000000000..3fc73fc1b --- /dev/null +++ b/.maestro/relay/_connect_appkit.yaml @@ -0,0 +1,50 @@ +# Mirror-suite connect handshake — native appkit-wagmi dapp (app under test) <-> +# web wallet (counterparty). Untagged; pulled in via runFlow by the mirror flows. +# +# There is NO deep link from a native dapp to a web wallet, so we bridge via the +# OS clipboard (shared between the app and the device browser on a simulator / +# emulator): AppKit's QR view has a "copy to clipboard" button for the wc: URI — +# we copy it, open the web wallet, and paste it into the paste-URI screen. No +# dapp code change needed (the URI never has to be surfaced as readable text). +# +# Env: +# APP_ID native appkit-wagmi bundle id (app under test) +# WALLET_URL deployed web wallet base URL (counterparty) +# +# The wallet reads the OS clipboard itself via its "Paste a URL" button +# (ScannerOptionsModal -> button-paste-url -> getClipboardString -> +# handleUriOrPaymentLink, which pairs on a wc: URI). We deliberately do NOT use +# Maestro's `pasteText`: that only replays what a prior `copyTextFrom` captured +# into Maestro's own buffer — it does not read the OS clipboard, so it would be +# empty here (the URI was copied by AppKit's button, not by Maestro). +# +# LIVE-TUNING (needs a device run to confirm): +# - AppKit "Connect Wallet" label + the QR-view copy-to-clipboard button label +# (guessed "Copy link" below). +# - Web clipboard READ permission: expo-clipboard's getStringAsync -> +# navigator.clipboard.readText() needs a secure context + clipboard-read +# permission; the managed Chromium may prompt/deny. May need a browser flag. +appId: ${APP_ID} +--- +# Open the AppKit modal (QR view) and copy the wc: URI to the OS clipboard. +- tapOn: 'Connect Wallet' +- tapOn: 'Copy link' +# Open the web wallet in the device browser; its paste button reads the clipboard. +- openLink: + link: ${WALLET_URL} + browser: true +- extendedWaitUntil: + visible: + id: 'button-scan' + timeout: 30000 +- tapOn: + id: 'button-scan' +- tapOn: + id: 'button-paste-url' +# Approve the proposal in the web wallet. +- extendedWaitUntil: + visible: + id: 'approve-button' + timeout: 30000 +- tapOn: + id: 'approve-button' diff --git a/.maestro/relay/_connect_walletkit.yaml b/.maestro/relay/_connect_walletkit.yaml new file mode 100644 index 000000000..469fef61f --- /dev/null +++ b/.maestro/relay/_connect_walletkit.yaml @@ -0,0 +1,39 @@ +# Reusable connect handshake — NOT a standalone test. +# It is untagged on purpose: the CI runs the suite with `--include-tags relay-walletkit`, +# so tag filtering never picks this up directly; the tagged flows below pull it +# in via `runFlow`. It opens the AppKit web dapp in the device browser, starts a +# WalletConnect connection, approves the proposal in the native wallet, and +# returns to the browser — leaving a live session with the dapp foregrounded. +# +# Env: +# APP_ID native wallet bundle id (the app under test) +# DAPP_URL appkit-wagmi web dapp base URL (deployed counterparty) +# +# LIVE-TUNING NOTES (need a device run to confirm): +# - AppKitButton renders "Connect Wallet" when disconnected. +# - The dapp must surface "React Native Sample Wallet" in the AppKit wallet +# list so tapping it deep-links to the native wallet. If it doesn't, switch +# to the QR/copy-URI path instead. +# - Post-approve foregrounding differs per platform; the iOS `back` returns to +# Safari. Android usually redirects back to Chrome automatically. +appId: ${APP_ID} +--- +- openLink: + link: ${DAPP_URL} + browser: true +- extendedWaitUntil: + visible: 'AppKit + Multichain' + timeout: 30000 +- tapOn: 'Connect Wallet' +- tapOn: 'React Native Sample Wallet' +- extendedWaitUntil: + visible: + id: 'approve-button' + timeout: 30000 +- tapOn: + id: 'approve-button' +- runFlow: + when: + platform: iOS + commands: + - back diff --git a/.maestro/relay/config.yaml b/.maestro/relay/config.yaml new file mode 100644 index 000000000..94842cde1 --- /dev/null +++ b/.maestro/relay/config.yaml @@ -0,0 +1,14 @@ +# Maestro config for the relay (dapp <-> wallet) E2E suite. +# +# Named by the SDK under test: +# maestro test --include-tags relay-walletkit .maestro/relay # native wallet vs web dapp +# maestro test --include-tags relay-appkit .maestro/relay # native appkit vs web wallet +# Tag filtering keeps the untagged `_connect_*.yaml` subflows from running as +# standalone tests; they are only pulled in via `runFlow`. +# +# Suite is intentionally separate from the WC Pay suite (shared flows copied in +# by ci_e2e_walletkit.yaml) and from the legacy .maestro/native + .maestro/web +# flows. +baselineBranch: main +flows: + - '*.yaml' diff --git a/.maestro/relay/connect_appkit.yaml b/.maestro/relay/connect_appkit.yaml new file mode 100644 index 000000000..b0367b029 --- /dev/null +++ b/.maestro/relay/connect_appkit.yaml @@ -0,0 +1,18 @@ +# Mirror Flow 1 — connect: native appkit dapp initiates, web wallet approves. +# App under test: native appkit-wagmi. Counterparty: deployed web wallet. +appId: ${APP_ID} +tags: + - relay-appkit + - connect +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_appkit.yaml +# Foreground the native dapp and confirm it reflects the connected account. +- launchApp +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 diff --git a/.maestro/relay/connect_walletkit.yaml b/.maestro/relay/connect_walletkit.yaml new file mode 100644 index 000000000..0f10f6d4b --- /dev/null +++ b/.maestro/relay/connect_walletkit.yaml @@ -0,0 +1,16 @@ +# Flow 1 — connect: dapp initiates, wallet approves, session established. +appId: ${APP_ID} +tags: + - relay-walletkit + - connect +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_walletkit.yaml +# Dapp reflects the connected account (WalletInfoView). +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 diff --git a/.maestro/relay/disconnect_dapp_appkit.yaml b/.maestro/relay/disconnect_dapp_appkit.yaml new file mode 100644 index 000000000..02b630665 --- /dev/null +++ b/.maestro/relay/disconnect_dapp_appkit.yaml @@ -0,0 +1,31 @@ +# Mirror Flow 5 — disconnect from the dapp side: the native appkit dapp tears +# down the session; the web wallet receives session_delete. +appId: ${APP_ID} +tags: + - relay-appkit + - disconnect-dapp +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_appkit.yaml +- launchApp +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 +# Open the AppKit account view and disconnect (see disconnect_dapp.yaml notes). +- tapOn: + id: 'dapp-connected' +- tapOn: 'Disconnect' +- extendedWaitUntil: + notVisible: + id: 'dapp-connected' + timeout: 30000 +# Web wallet dropped the session too. +- openLink: + link: ${WALLET_URL} + browser: true +- tapOn: 'Connected apps' +- assertVisible: 'No connected apps yet' diff --git a/.maestro/relay/disconnect_dapp_walletkit.yaml b/.maestro/relay/disconnect_dapp_walletkit.yaml new file mode 100644 index 000000000..8cf64f124 --- /dev/null +++ b/.maestro/relay/disconnect_dapp_walletkit.yaml @@ -0,0 +1,30 @@ +# Flow 5 — disconnect from the dapp side: dapp tears down the session; the +# wallet receives session_delete and drops it. +appId: ${APP_ID} +tags: + - relay-walletkit + - disconnect-dapp +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_walletkit.yaml +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 +# Open the AppKit account view (AppKitButton now shows the account) and disconnect. +# LIVE-TUNING: confirm the account-modal open target and the "Disconnect" label +# come from the AppKit account view. +- tapOn: + id: 'dapp-connected' +- tapOn: 'Disconnect' +- extendedWaitUntil: + notVisible: + id: 'dapp-connected' + timeout: 30000 +# Wallet side dropped the session too. +- launchApp +- tapOn: 'Connected apps' +- assertVisible: 'No connected apps yet' diff --git a/.maestro/relay/disconnect_wallet_appkit.yaml b/.maestro/relay/disconnect_wallet_appkit.yaml new file mode 100644 index 000000000..2d3f7fd16 --- /dev/null +++ b/.maestro/relay/disconnect_wallet_appkit.yaml @@ -0,0 +1,29 @@ +# Mirror Flow 6 — disconnect from the wallet side: the web wallet tears down the +# session; the native appkit dapp receives session_delete. +appId: ${APP_ID} +tags: + - relay-appkit + - disconnect-wallet +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_appkit.yaml +# Web wallet is foreground after connect: open the connected app's detail. +- tapOn: 'Connected apps' +- tapOn: + id: 'session-item' +- extendedWaitUntil: + visible: + id: 'session-disconnect-button' + timeout: 30000 +- tapOn: + id: 'session-disconnect-button' +- assertVisible: 'No connected apps yet' +# Dapp side reflects the disconnect. +- launchApp +- extendedWaitUntil: + notVisible: + id: 'dapp-connected' + timeout: 30000 diff --git a/.maestro/relay/disconnect_wallet_walletkit.yaml b/.maestro/relay/disconnect_wallet_walletkit.yaml new file mode 100644 index 000000000..4782cfa89 --- /dev/null +++ b/.maestro/relay/disconnect_wallet_walletkit.yaml @@ -0,0 +1,25 @@ +# Flow 6 — disconnect from the wallet side: wallet tears down the session via +# the session detail modal; the dapp receives session_delete. +appId: ${APP_ID} +tags: + - relay-walletkit + - disconnect-wallet +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_walletkit.yaml +# Foreground the wallet and open the connected app's detail. +- launchApp +- tapOn: 'Connected apps' +- tapOn: + id: 'session-item' +- extendedWaitUntil: + visible: + id: 'session-disconnect-button' + timeout: 30000 +- tapOn: + id: 'session-disconnect-button' +# Wallet's session list is now empty. +- assertVisible: 'No connected apps yet' diff --git a/.maestro/relay/restore_appkit.yaml b/.maestro/relay/restore_appkit.yaml new file mode 100644 index 000000000..4d24cb1f5 --- /dev/null +++ b/.maestro/relay/restore_appkit.yaml @@ -0,0 +1,25 @@ +# Mirror Flow 7 — restore sessions: after a full restart (no clearState) the +# native appkit dapp rehydrates its active session from storage. +appId: ${APP_ID} +tags: + - relay-appkit + - restore +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_appkit.yaml +- launchApp +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 +# Full stop + relaunch WITHOUT clearing state -> exercises session restore. +- stopApp +- launchApp: + clearState: false +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 diff --git a/.maestro/relay/restore_walletkit.yaml b/.maestro/relay/restore_walletkit.yaml new file mode 100644 index 000000000..bd243549f --- /dev/null +++ b/.maestro/relay/restore_walletkit.yaml @@ -0,0 +1,21 @@ +# Flow 7 — restore sessions: after a full app restart (no clearState), the +# wallet rehydrates its active sessions from storage. +appId: ${APP_ID} +tags: + - relay-walletkit + - restore +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_walletkit.yaml +# Full stop + relaunch WITHOUT clearing state -> exercises session restore. +- stopApp +- launchApp: + clearState: false +- tapOn: 'Connected apps' +- extendedWaitUntil: + visible: + id: 'session-item' + timeout: 30000 diff --git a/.maestro/relay/sign_appkit.yaml b/.maestro/relay/sign_appkit.yaml new file mode 100644 index 000000000..8bc113b52 --- /dev/null +++ b/.maestro/relay/sign_appkit.yaml @@ -0,0 +1,38 @@ +# Mirror Flow 2 — sign a message: native appkit requests personal_sign, the web +# wallet approves. +# +# App switching: after connect the browser wallet is foreground. We foreground +# the native dapp to trigger the sign, then re-open the wallet URL to bring the +# browser back (the relay re-delivers the queued session_request after the page +# reconnects), approve there, then return to the dapp to assert the result. +appId: ${APP_ID} +tags: + - relay-appkit + - sign +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_appkit.yaml +# Trigger personal_sign on the native dapp. +- launchApp +- tapOn: + id: 'sign-message-button' +# Bring the web wallet forward and approve the request. +- openLink: + link: ${WALLET_URL} + browser: true +- extendedWaitUntil: + visible: + id: 'approve-button' + timeout: 30000 +- tapOn: + id: 'approve-button' +# Back to the dapp: persistent, testID'd result. +- launchApp +- extendedWaitUntil: + visible: + id: 'sign-result' + timeout: 30000 +- assertVisible: 'Sign success' diff --git a/.maestro/relay/sign_other_chain_appkit.yaml b/.maestro/relay/sign_other_chain_appkit.yaml new file mode 100644 index 000000000..b94c7084c --- /dev/null +++ b/.maestro/relay/sign_other_chain_appkit.yaml @@ -0,0 +1,37 @@ +# Mirror Flow 4 — sign a message on another chain: switch chain on the native +# dapp, then personal_sign approved in the web wallet. +appId: ${APP_ID} +tags: + - relay-appkit + - sign-other-chain +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_appkit.yaml +- launchApp +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 +- tapOn: 'NetworkButton' +- tapOn: 'Polygon' +- assertVisible: 'Chain: Polygon' +- tapOn: + id: 'sign-message-button' +- openLink: + link: ${WALLET_URL} + browser: true +- extendedWaitUntil: + visible: + id: 'approve-button' + timeout: 30000 +- tapOn: + id: 'approve-button' +- launchApp +- extendedWaitUntil: + visible: + id: 'sign-result' + timeout: 30000 +- assertVisible: 'Sign success' diff --git a/.maestro/relay/sign_other_chain_walletkit.yaml b/.maestro/relay/sign_other_chain_walletkit.yaml new file mode 100644 index 000000000..2f7f57670 --- /dev/null +++ b/.maestro/relay/sign_other_chain_walletkit.yaml @@ -0,0 +1,39 @@ +# Flow 4 — sign a message on another chain: switch chain, then personal_sign. +appId: ${APP_ID} +tags: + - relay-walletkit + - sign-other-chain +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_walletkit.yaml +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 +# Switch to Polygon (see switch_chain.yaml notes). +- tapOn: 'NetworkButton' +- tapOn: 'Polygon' +- assertVisible: 'Chain: Polygon' +# Sign on the new chain. +- tapOn: + id: 'sign-message-button' +- launchApp +- extendedWaitUntil: + visible: + id: 'approve-button' + timeout: 30000 +- tapOn: + id: 'approve-button' +- runFlow: + when: + platform: iOS + commands: + - back +- extendedWaitUntil: + visible: + id: 'sign-result' + timeout: 30000 +- assertVisible: 'Sign success' diff --git a/.maestro/relay/sign_walletkit.yaml b/.maestro/relay/sign_walletkit.yaml new file mode 100644 index 000000000..ffbec7977 --- /dev/null +++ b/.maestro/relay/sign_walletkit.yaml @@ -0,0 +1,33 @@ +# Flow 2 — sign a message: dapp requests personal_sign, wallet approves. +appId: ${APP_ID} +tags: + - relay-walletkit + - sign +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_walletkit.yaml +# In the dapp (browser), trigger personal_sign ("Hello AppKit!"). +- tapOn: + id: 'sign-message-button' +# The request arrives in the wallet over the relay; foreground it to approve. +- launchApp +- extendedWaitUntil: + visible: + id: 'approve-button' + timeout: 30000 +- tapOn: + id: 'approve-button' +- runFlow: + when: + platform: iOS + commands: + - back +# Dapp shows the persistent, testID'd result. +- extendedWaitUntil: + visible: + id: 'sign-result' + timeout: 30000 +- assertVisible: 'Sign success' diff --git a/.maestro/relay/switch_chain_appkit.yaml b/.maestro/relay/switch_chain_appkit.yaml new file mode 100644 index 000000000..b41a46730 --- /dev/null +++ b/.maestro/relay/switch_chain_appkit.yaml @@ -0,0 +1,25 @@ +# Mirror Flow 3 — switch chain: native appkit switches network (client-side; all +# EVM chains are in the approved namespaces, so no wallet interaction). +appId: ${APP_ID} +tags: + - relay-appkit + - switch-chain +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_appkit.yaml +- launchApp +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 +# Open the AppKit network selector and pick Polygon (see switch_chain.yaml notes). +- tapOn: 'NetworkButton' +- tapOn: 'Polygon' +- extendedWaitUntil: + visible: + id: 'connected-chain' + timeout: 30000 +- assertVisible: 'Chain: Polygon' diff --git a/.maestro/relay/switch_chain_walletkit.yaml b/.maestro/relay/switch_chain_walletkit.yaml new file mode 100644 index 000000000..20ef9bc7c --- /dev/null +++ b/.maestro/relay/switch_chain_walletkit.yaml @@ -0,0 +1,28 @@ +# Flow 3 — switch chain: dapp switches to a second EVM network. +# All 5 EVM chains (mainnet/polygon/arbitrum/optimism/base) are in the approved +# namespaces, so switching is client-side on the dapp — no wallet re-approval. +appId: ${APP_ID} +tags: + - relay-walletkit + - switch-chain +--- +- launchApp: + clearState: true + permissions: + all: allow +- runFlow: _connect_walletkit.yaml +- extendedWaitUntil: + visible: + id: 'dapp-connected' + timeout: 30000 +# Open the AppKit network selector (NetworkButton) and pick Polygon. +# LIVE-TUNING: NetworkButton renders the *current* network name; confirm the tap +# target and the destination chain label on a device run. +- tapOn: 'NetworkButton' +- tapOn: 'Polygon' +# WalletInfoView reflects the active chain. +- extendedWaitUntil: + visible: + id: 'connected-chain' + timeout: 30000 +- assertVisible: 'Chain: Polygon' diff --git a/.maestro/web/connect_confirm.yaml b/.maestro/web/connect_confirm.yaml index 3f5a0c428..5bfbe37ea 100644 --- a/.maestro/web/connect_confirm.yaml +++ b/.maestro/web/connect_confirm.yaml @@ -12,12 +12,26 @@ name: AppKit Web to React Native Wallet Connection Confirmed - tapOn: "Connect Wallet" - scroll - scroll -- tapOn: "React Native Sample Wallet React Native Sample Wallet" +- runFlow: + when: + platform: iOS + commands: + - tapOn: "React Native Sample Wallet" +- runFlow: + when: + platform: Android + commands: + - tapOn: "React Native Sample Wallet React Native Sample Wallet" - runFlow: when: platform: iOS commands: - back -- tapOn: "Approve" +- extendedWaitUntil: + visible: + id: "approve-button" + timeout: 10000 +- tapOn: + id: "approve-button" - assertVisible: "AppKit Interactions" - stopRecording \ No newline at end of file diff --git a/.maestro/web/connect_reject.yaml b/.maestro/web/connect_reject.yaml index 83f900ab9..032830b1d 100644 --- a/.maestro/web/connect_reject.yaml +++ b/.maestro/web/connect_reject.yaml @@ -12,8 +12,22 @@ name: AppKit Web to React Native Wallet Connection Rejected - tapOn: "Connect Wallet" - scroll - scroll -- tapOn: "React Native Sample Wallet React Native Sample Wallet" -- tapOn: "Reject" +- runFlow: + when: + platform: iOS + commands: + - tapOn: "React Native Sample Wallet" +- runFlow: + when: + platform: Android + commands: + - tapOn: "React Native Sample Wallet React Native Sample Wallet" +- extendedWaitUntil: + visible: + id: "reject-button" + timeout: 10000 +- tapOn: + id: "reject-button" - runFlow: when: platform: iOS diff --git a/.maestro/web/personal_sign_confirm.yaml b/.maestro/web/personal_sign_confirm.yaml index 61e754783..e2395b15f 100644 --- a/.maestro/web/personal_sign_confirm.yaml +++ b/.maestro/web/personal_sign_confirm.yaml @@ -11,11 +11,17 @@ name: AppKit Web to React Native Wallet Sign Request Confirmed - scroll - scroll - tapOn: "Sign Message" -- tapOn: "Approve" +- extendedWaitUntil: + visible: + id: "approve-button" + timeout: 10000 +- tapOn: + id: "approve-button" - runFlow: when: platform: iOS commands: - back +- waitForAnimationToEnd - assertVisible: "Signing Succeeded" - stopRecording \ No newline at end of file diff --git a/.maestro/web/personal_sign_reject.yaml b/.maestro/web/personal_sign_reject.yaml index c69275f9e..ddb6cd487 100644 --- a/.maestro/web/personal_sign_reject.yaml +++ b/.maestro/web/personal_sign_reject.yaml @@ -11,11 +11,17 @@ name: AppKit Web to React Native Wallet Sign Request Rejected - scroll - scroll - tapOn: "Sign Message" -- tapOn: "Reject" +- extendedWaitUntil: + visible: + id: "reject-button" + timeout: 10000 +- tapOn: + id: "reject-button" - runFlow: when: platform: iOS commands: - back +- waitForAnimationToEnd - assertVisible: "Signing Failed" - stopRecording \ No newline at end of file diff --git a/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx b/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx index db2d2ef39..f3918c412 100644 --- a/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx +++ b/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, Text as RNText } from 'react-native'; import { useSignMessage, useAccount, useSendTransaction, useEstimateGas, useSignTypedData } from 'wagmi'; import { Hex, parseEther } from 'viem'; import { SendTransactionData, SignMessageData, SignTypedDataData } from 'wagmi/query'; @@ -10,11 +10,17 @@ import { eip712 } from '@/utils/eip712'; export function WagmiActionsView() { const { isConnected } = useAccount(); + // Persistent, testID'd sign result for Maestro (the toasts below are + // ephemeral and racy to assert on). Cleared on each new sign attempt. + const [signResult, setSignResult] = useState(null); + const onSignSuccess = (data: SignMessageData) => { + setSignResult('Sign success'); ToastUtils.showSuccessToast('Signature successful', data); }; const onSignError = (error: Error) => { + setSignResult('Sign failed'); ToastUtils.showErrorToast('Signature failed', error.message); }; @@ -73,10 +79,14 @@ export function WagmiActionsView() { disabled={isPending} loading={isPending} testID="sign-message-button" - onPress={() => signMessage({ message: 'Hello AppKit!' })} + onPress={() => { + setSignResult(null); + signMessage({ message: 'Hello AppKit!' }); + }} > Sign + {signResult && {signResult}} {isGasError && Error estimating gas}