diff --git a/.maestro/16-wallet-connect-cancel.yaml b/.maestro/16-wallet-connect-cancel.yaml new file mode 100644 index 0000000..94951af --- /dev/null +++ b/.maestro/16-wallet-connect-cancel.yaml @@ -0,0 +1,69 @@ +# 16-wallet-connect-cancel.yaml +# +# Maestro E2E flow: Wallet connection — user cancels in the wallet app +# +# Scenario: +# The user starts a wallet connection, is deep-linked out to the (mocked) +# wallet app, and cancels the connection request. The app should return to +# the connection screen in a non-connected state and surface a clear, +# dismissible "connection cancelled" message. No session/address should be +# persisted. +# +# Deterministic local runs: +# Real wallet apps cannot be driven reliably in CI, so this flow assumes the +# app is launched with the wallet interaction stubbed. Set the launch +# argument `MOCK_WALLET=cancel` (see .maestro/README.md) so the mock wallet +# provider immediately resolves the deep-link callback with a user-rejected +# result instead of opening an external app. +# +# End state asserted (distinct to this flow): +# - "Connection cancelled" message is visible +# - The primary CTA reads "Connect Wallet" again (i.e. still disconnected) +# - No connected address / guild content is shown + +appId: ${APP_ID} +env: + APP_ID: com.example.app + MOCK_WALLET: cancel +--- +- launchApp: + appId: ${APP_ID} + clearState: true + arguments: + MOCK_WALLET: ${MOCK_WALLET} + +# Reach the wallet connection entry point. +- assertVisible: + id: "connect-wallet-button" +- tapOn: + id: "connect-wallet-button" + +# The mock wallet provider stands in for the external wallet app. When +# MOCK_WALLET=cancel it resolves the callback as a user rejection. +- assertVisible: + id: "wallet-provider-list" +- tapOn: + id: "wallet-provider-metamask" + +# Simulate the user cancelling inside the (mocked) wallet app. +- assertVisible: + id: "mock-wallet-cancel-button" +- tapOn: + id: "mock-wallet-cancel-button" + +# Distinct end state: cancellation surfaced, still disconnected. +- assertVisible: + id: "connection-cancelled-message" + text: ".*[Cc]ancel.*" +- assertVisible: + id: "connect-wallet-button" +- assertNotVisible: + id: "connected-address" +- assertNotVisible: + id: "guild-list" + +# The cancellation message can be dismissed and the flow retried. +- tapOn: + id: "connection-cancelled-dismiss" +- assertNotVisible: + id: "connection-cancelled-message" diff --git a/.maestro/17-wallet-connect-timeout.yaml b/.maestro/17-wallet-connect-timeout.yaml new file mode 100644 index 0000000..e507a42 --- /dev/null +++ b/.maestro/17-wallet-connect-timeout.yaml @@ -0,0 +1,82 @@ +# 17-wallet-connect-timeout.yaml +# +# E2E flow: Wallet connection times out. +# +# Scenario: +# The user initiates a wallet connection, gets deep-linked out to the wallet +# app (mocked/stubbed for deterministic local runs — see .maestro/README.md), +# but the wallet never returns an approval within the allotted window. The app +# must surface a timeout error and offer a retry affordance, leaving the user +# in a disconnected-but-recoverable state. +# +# Determinism note: +# Relies on the wallet-mock harness documented in .maestro/README.md. Launch +# with WALLET_MOCK_MODE=timeout so the stubbed wallet bridge never resolves +# the connection request, forcing the app-side timeout to fire. +# +appId: ${APP_ID} +env: + WALLET_MOCK_MODE: "timeout" + # Keep this comfortably above the app's connection timeout so the UI has + # settled into its error state before we assert on it. + TIMEOUT_ASSERT_MS: "45000" +tags: + - wallet + - error-path + - regression +--- +- runFlow: subflows/reset-app-state.yaml + +- launchApp: + clearState: true + arguments: + walletMockMode: ${WALLET_MOCK_MODE} + +# --- Arrive at the wallet connection entry point ---------------------------- +- assertVisible: + id: "onboarding-connect-wallet-cta" +- tapOn: + id: "onboarding-connect-wallet-cta" + +- assertVisible: + id: "wallet-select-list" +- tapOn: + id: "wallet-option-mock" + +# --- Deep link out to the (stubbed) wallet app ----------------------------- +# The mock harness intercepts the outbound deep link and, in "timeout" mode, +# deliberately does NOT dispatch a return deep link. Control returns to the app +# with the connection request left pending. +- assertVisible: + id: "wallet-connect-pending-indicator" +- assertVisible: + text: "Waiting for wallet.*" + +# --- Wait for the app-side connection timeout to fire ----------------------- +- extendedWaitUntil: + visible: + id: "wallet-connect-timeout-error" + timeout: ${TIMEOUT_ASSERT_MS} + +# --- Assert the specific, distinct end state -------------------------------- +- assertVisible: + id: "wallet-connect-timeout-error" +- assertVisible: + text: "Connection timed out.*" +- assertVisible: + id: "wallet-connect-retry-button" + +# The pending spinner must be gone: we are in a settled error state, not limbo. +- assertNotVisible: + id: "wallet-connect-pending-indicator" + +# User is NOT connected — no wallet address chip should be shown. +- assertNotVisible: + id: "connected-wallet-address" + +# Retry must return the user to a re-attemptable connection entry point rather +# than a dead end. +- tapOn: + id: "wallet-connect-retry-button" +- assertVisible: + id: "wallet-select-list" diff --git a/.maestro/18-wallet-connect-unsupported-chain.yaml b/.maestro/18-wallet-connect-unsupported-chain.yaml new file mode 100644 index 0000000..0db6a55 --- /dev/null +++ b/.maestro/18-wallet-connect-unsupported-chain.yaml @@ -0,0 +1,61 @@ +# 18-wallet-connect-unsupported-chain.yaml +# +# E2E flow: Wallet connection succeeds but returns an UNSUPPORTED CHAIN. +# +# Scenario: +# The user initiates a wallet connection, approves it in the (mocked) wallet +# app, but the returned account is on a chain the app does not support. +# The app must reject the session and surface an "unsupported network" error +# while remaining in a disconnected state. +# +# Deterministic mocking: +# Wallet deep-link round-trips are non-deterministic in CI, so this flow +# relies on the app's E2E wallet mock (enabled via the launch arguments +# below). The mock intercepts the WalletConnect/deep-link handshake and +# returns a canned response. +# - MAESTRO_E2E=true -> enables the mock wallet layer +# - MOCK_WALLET_RESULT=unsupported_chain -> mock returns a connected +# account on an unsupported chain +# (chainId 999 by default) +# See .maestro/README.md for how to build a local dev build with the mock +# wallet layer compiled in. +# +# Distinct end state asserted by this flow: +# - An "unsupported network" / "unsupported chain" error is visible. +# - The wallet remains DISCONNECTED (a "Connect Wallet" affordance is still +# present and no connected address is shown). + +appId: ${MAESTRO_APP_ID} +env: + MAESTRO_E2E: "true" + MOCK_WALLET_RESULT: "unsupported_chain" + MOCK_WALLET_CHAIN_ID: "999" +tags: + - wallet + - wallet-error-paths +--- +- launchApp: + clearState: true + arguments: + e2e: ${MAESTRO_E2E} + mockWalletResult: ${MOCK_WALLET_RESULT} + mockWalletChainId: ${MOCK_WALLET_CHAIN_ID} + +# Reach the wallet connection entry point. +- assertVisible: "Connect Wallet" +- tapOn: "Connect Wallet" + +# The mock wallet auto-approves and returns to the app with an +# account on an unsupported chain. Wait for the app to process the callback. +- extendedWaitUntil: + visible: + text: "Unsupported network|Unsupported chain|Network not supported" + timeout: 20000 + +# End state: an explicit, distinct unsupported-network error is shown. +- assertVisible: + text: "Unsupported network|Unsupported chain|Network not supported" + +# End state: the app did NOT establish a session - it stays disconnected. +- assertVisible: "Connect Wallet" +- assertNotVisible: "Disconnect" diff --git a/.maestro/config.yaml b/.maestro/config.yaml index d06dd38..153784e 100644 --- a/.maestro/config.yaml +++ b/.maestro/config.yaml @@ -15,6 +15,10 @@ flows: - 12-access-qr-malformed-json.yaml - 13-deep-link-access-check-missing-resource-id.yaml - 14-deep-link-guild-detail-invalid-guild-id.yaml + - 15-wallet-connect-cancel.yaml + - 16-wallet-connect-timeout.yaml + - 17-wallet-connect-unsupported-chain.yaml + - 18-wallet-connect-reconnect-after-disconnect.yaml env: # Default test environment variables