Blog

MetaMask Automated Testing For Wallet Flows

MetaMask automated testing should inspect account access, chain changes, signature prompts, rejection recovery, and transaction state with browser evidence.

Drew Stone
browser-agentmetamaskwallet-testing
An editorial still life about an agent operating a web browser

A user clicks “Sign,” reads the wallet prompt, and sees an account or network that the application never mentioned. An automated test that checks only the page can report success while the actual approval flow is wrong.

MetaMask automated testing has to inspect the boundary between the dapp and the wallet extension. MetaMask is a browser and mobile wallet documented in the official developer documentation. A dapp is a web application that asks a wallet to expose account state, sign messages, switch networks, or submit transactions. DeFi means finance applications that use blockchain accounts and smart contracts, or programs deployed on a blockchain, for actions such as swaps, lending, borrowing, and liquidity provision. An allowance is a token-spending limit that a user grants to a contract. A spender is the contract that receives that allowance. A provider is the page-facing interface through which the dapp requests wallet and chain actions. The page requests the action, the wallet presents the security decision, and the chain records the result.

Tangle Browser Agent is Tangle’s browser driver for goal-based browser sessions. Its bad command is the command-line interface (CLI) for starting those sessions from a terminal. The public driver repository documents a wallet and DeFi testing path built around the same browser automation surface used for ordinary pages. Each case records the page screenshot, wallet prompt screenshot, provider result, and final application state so the user’s approval remains visible. For the wider state model, read DeFi Wallet Testing With Browser Agents. For the evidence record, read AI Browser Testing With Evidence Traces.

Test the wallet boundary a user sees

The word “connect” hides several separate promises. The application must request the right account access. The wallet must show the request. The page must reflect the selected account. The account can change later. The chain can change later.

User-visible actionApplication conditionWallet evidence
connectthe page shows the selected test account or a clear connected staterequested account access and selected account
reject connectionthe page stays usable and explains the rejectionwallet rejection and provider error
switch networkthe page and wallet agree on the chainrequested and selected chain
sign a messagethe page describes the message purposeexact message or typed-data prompt
approve an allowancethe page names the token and spenderallowance amount, token, and spender
submit a transactionpending, success, and failure states are distincttransaction details and outcome
change accountaccount-specific data refreshes or the session closesaccountsChanged event and new page state

The test should save the page screenshot immediately before the prompt and the wallet screenshot at the decision. The first shows the context the user had. The second shows the context the wallet rendered. The final page shows whether the application reported the result honestly.

Use the public provider interface

An EIP, or Ethereum Improvement Proposal, is a public specification for an Ethereum interface or protocol change. EIP-1193 defines a common JavaScript provider API, or application programming interface, for Ethereum applications and wallets. The provider exposes a request method for wallet and chain calls. RPC means remote procedure call, the request-and-response interface used to ask the wallet or chain client for state and actions. EIP-1193 also defines events for connection, disconnection, chain changes, and account changes.

Those events are part of the test surface. An app that never handles accountsChanged can keep showing balances for the previous account. An app that ignores chainChanged can display a quote for one chain while the wallet signs on another. An app that treats a rejected request as success can leave a false “Connected” or “Submitted” state behind.

EIP-1193 recommends provider error code 4001 for user rejection. It recommends 4100 for unauthorized requests, 4200 for unsupported methods, 4900 for a disconnected provider, and 4901 when the requested chain is unavailable while another chain remains connected. Record the code when the app’s recovery behavior matters.

An illustrative browser-side observer looks like this:

type ProviderError = Error & { code?: number; data?: unknown }

const provider = window.ethereum

if (!provider) {
  throw new Error('MetaMask or another injected provider is unavailable')
}

provider.on('accountsChanged', (accounts: string[]) => {
  console.log({ event: 'accountsChanged', accounts })
})

provider.on('chainChanged', (chainId: string) => {
  console.log({ event: 'chainChanged', chainId })
})

try {
  const accounts = await provider.request({ method: 'eth_accounts' })
  const chainId = await provider.request({ method: 'eth_chainId' })
  console.log({ accounts, chainId })
} catch (error) {
  const providerError = error as ProviderError
  console.error({
    message: providerError.message,
    code: providerError.code,
    data: providerError.data,
  })
}

This is an illustrative EIP-1193 example. MetaMask Connect is MetaMask’s current connection software development kit, or SDK, for connecting supported wallet environments to applications. An application that uses MetaMask Connect may use that SDK rather than reading an injected provider directly. The MetaMask Connect documentation says the product can select a desktop extension, QR-code, or mobile-app connection method based on the user’s environment. The test should assert the connection behavior that the application supports instead of assuming the extension is always present.

Make signing a data test

A signing test should inspect the exact message, typed-data domain, spender, value, destination, and chain shown in the prompt. Personal signing presents a message. Typed signing presents structured fields. Transaction signing presents a destination, value, data, and gas-related context according to the wallet and network.

EIP-712 defines a standard for hashing and signing typed structured data. It includes typed fields and a domain separator so a wallet can present more meaningful context than an opaque byte string. The standard does not provide replay protection. In this context, a nonce is a number used once to prevent a signed operation from being reused or applied out of order. An application still needs a nonce, expiry, chain binding, or another policy appropriate to its operation.

An illustrative typed-data fixture can make the intended prompt reviewable:

{
  "domain": {
    "name": "Example Exchange",
    "version": "1",
    "chainId": 11155111,
    "verifyingContract": "0x0000000000000000000000000000000000000001"
  },
  "types": {
    "EIP712Domain": [
      { "name": "name", "type": "string" },
      { "name": "version", "type": "string" },
      { "name": "chainId", "type": "uint256" },
      { "name": "verifyingContract", "type": "address" }
    ],
    "Permit": [
      { "name": "owner", "type": "address" },
      { "name": "spender", "type": "address" },
      { "name": "value", "type": "uint256" },
      { "name": "nonce", "type": "uint256" },
      { "name": "deadline", "type": "uint256" }
    ]
  },
  "primaryType": "Permit",
  "message": {
    "owner": "0x0000000000000000000000000000000000000002",
    "spender": "0x0000000000000000000000000000000000000003",
    "value": "1000000",
    "nonce": "0",
    "deadline": "2000000000"
  }
}

The addresses and numbers above are illustrative placeholders. The test should compare the prompt to the expected fixture before a controlled test wallet approves it. It should run the rejection path as well. The application must not record a session, allowance, or completed action when the user rejects the request.

Build a repeatable MetaMask fixture

The fixture is the state prepared before the browser opens. Record wallet and browser versions, permissions, balances, nonce state, and application data so a rerun can isolate fixture drift.

FixtureRequired control
extensionknown MetaMask version installed and enabled before the run
accountdisposable address with test funds and no production authority
networkexpected chain configured before the dapp opens
permissionsclean connection permissions between cases where needed
balancesreset token and native-currency balances
noncedeterministic or isolated chain state
app datapredictable account, quote, and session records
browserrecorded browser version, viewport, locale, and extension set

Local chains are useful for repeatable transaction results. A local Ethereum development chain can provide repeatable transaction results, but the exact chain tooling belongs to the application’s test setup. Live-network checks can reveal integration issues that a local chain hides. Keep those checks few, explicit, and funded only with disposable value.

The test should record the extension version with the browser version. A wallet UI can change independently of the dapp. When prompt identification fails, the team needs to know whether the page changed, the extension changed, or the fixture did not reach the expected state.

Stop at the signing prompt

The public Browser Agent manifest names the scoped package, bad binary, and safe discovery commands. The public driver README documents the wallet and DeFi section as well as the general CLI and SDK surfaces.

npm install -g @tangle-network/browser-agent-driver
npx playwright install chromium
bad --help
bad run \
  --url https://app.example.com \
  --goal "Connect the disposable MetaMask account, verify the configured test chain, open the swap preview, capture the wallet prompt, and stop before signing"

The example deliberately stops before an irreversible action. A controlled transaction case needs a separate goal, a test wallet, a test chain, and an explicit approval policy. Do not place seed phrases or private keys in the goal text.

The DOM, or Document Object Model, is the page’s structured tree of elements and attributes. The driver’s README documents the run’s observation modes, including DOM, vision, and hybrid approaches. Vision means taking rendered screenshots as model input. Hybrid combines the two. Wallet prompts and visual chain details often need screenshots even when page controls are easy to find in the DOM.

Give rejection its own test case

A suite that covers only successful approvals misses an important product path: the user’s rejection.

CaseExpected resultEvidence
reject account accesspage remains disconnected and offers retryprompt rejection, provider error, page state
reject chain switchpage explains the required chain and does not submitrequested chain, selected chain, error state
reject personal messageno session or authorization is recordedmessage prompt, 4001 error, app state
reject typed datano allowance or permission is claimedtyped-data prompt, rejection, app state
reject transactionpending state is cleared or marked rejectedtransaction prompt, error, retry state
close promptpage does not assume approvalprompt closed, timeout, stop reason

A false pass often comes from stale state. The application displays a connected account from its own memory after the wallet has disconnected. The UI shows “Submitted” after a request was sent but before a receipt or chain confirmation exists. The test should preserve the boundary between requested, signed, broadcast, pending, confirmed, and failed.

Split fast wallet smoke checks from deep runs

Wallet suites are expensive to debug because they depend on browser, extension, account, network, and data state. Use a small smoke case for every change that touches connection code. Reserve the deeper matrix for releases or changes that affect signing, allowances, transaction construction, or chain handling.

SuiteExample casesRequired reset
connection smokeconnect, reject, account changeclean permissions and disposable account
network smokewrong chain, switch, chain changeknown chain configuration
signing suitepersonal message, typed data, rejectfresh nonce and expected domain
transaction suiteapproval, submit, pending, revertisolated balances and local or test chain
release replayfull dapp journey with prompt screenshotsversioned browser, extension, and fixture

Keep the smoke case short enough to diagnose from one trace. Run the deeper suite when a release changes the parts of the flow that the smoke case cannot reach. Run the connection smoke case on each relevant change, and reserve the deeper matrix for changes to signing, allowances, or transaction submission.

Diagnose the first wrong boundary

A trace is the ordered record of the browser, wallet, provider, and chain observations for one run. Compare account, chain, prompt fields, provider errors, transaction hash, and receipt in timestamp order.

First divergenceLikely ownerEvidence to inspect
app account differs from wallet accountdapp state or connection layeraccountsChanged event and both screenshots
requested chain differs from selected chainnetwork configurationchainChanged event and wallet network
prompt spender differs from expected contracttransaction builder or routetyped data or transaction request
rejection leaves success stateerror handlingprovider code and app transition
pending never resolvesRPC, indexer, or receipt handlingrequest error, transaction hash (identifier), and timing
prompt cannot be reachedextension fixture or browser versionextension state, permissions, and screenshot

The evaluation compares the expected account, chain, action, and outcome with that trace. Keep a failed run failed when the evidence contradicts the goal. Keep it inconclusive when the prompt or final chain result was never observable.

Keep the fixture separate from the verdict

Browser Agent drives the page and records the wallet boundary. MetaMask remains the wallet that presents account, chain, message, allowance, and transaction details. The driver can make those states easier to inspect, but it cannot decide whether a contract or an economic outcome is safe.

If a team packages the check as a service, return the page evidence, wallet evidence, and chain result separately. Report browser state, wallet evidence, transaction hash, and receipt as separate verdict fields.

The limit of MetaMask automated testing

MetaMask automated testing can provide evidence that a dapp and a particular extension fixture presented a defined wallet flow. It does not prove the smart contract is safe. It does not prove the quoted exchange rate will remain available. It does not prove a transaction’s economic outcome is correct. It does not cover every MetaMask platform, mobile path, browser version, network, account, or wallet setting. It does not make a production wallet appropriate for unattended automation.

Use simulation to inspect calldata, a receipt to establish settlement, contract tests for invariants, and manual review for high-value signing. Treat a wallet prompt screenshot as evidence of what the user was asked to approve. Treat a chain receipt or application-owned confirmation as evidence of what happened afterward.

What is MetaMask automated testing?

It is browser automation that drives a dapp and a controlled MetaMask flow to check account access, network changes, signing, rejection, and transaction states.

Can MetaMask tests run on a local chain?

Yes. Local chains make account, balance, nonce, and transaction state easier to reset. Keep live-network smoke checks separate because they have different failure and cost boundaries.

What should a failed MetaMask run include?

It should include the page screenshot, wallet prompt or blocked state, actions, account and chain context, provider error, final page state, extension and browser versions, and stop reason.

Should the test accept a transaction automatically?

Only with a disposable wallet and test chain under an explicit policy. The default for exploratory or production-adjacent checks should be to stop at the signing prompt.

Does Tangle Browser Agent replace MetaMask’s SDK?

No. MetaMask Connect defines MetaMask’s supported connection product and integration paths. Browser Agent drives a browser workflow and records evidence around the application’s chosen wallet integration.

Make the prompt reviewable

Automate MetaMask flows when connection, signing, or transaction approval can change onboarding, revenue, or user trust. Keep the account, chain, prompt contents, rejection behavior, and final state visible in the trace. Do not accept a pass that proves only that a button was clicked. For screenshots, action events, stop reasons, and bounded recovery around those wallet states, read browser automation and the AI evidence loop.