npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

walletqa

v1.0.0-beta.56

Published

Playwright-based multi-chain wallet E2E testing library

Downloads

3,427

Readme

walletqa

Playwright-based E2E testing library for web apps that integrate with browser extension wallets.

Test your dApp against real wallet extensions (Trust Wallet, MetaMask, Phantom, Rabby, OKX, Rainbow, TONkeeper) across EVM, Solana, and TON chains.

Why walletqa?

  • Real extension testing — headed browser with actual wallet extensions, not mocks or stubs
  • 7 wallets — Trust Wallet, Phantom, Rabby, OKX, Rainbow, TONkeeper, MetaMask
  • Cached profiles — onboard once (~35s), reuse the cached profile in every test (~7s)
  • Config-driven engine — declarative wallet definitions replace hundreds of lines of imperative code
  • A11y-first element discovery — finds approval buttons via accessibility tree scoring, not brittle CSS selectors
  • CDP popup detection — deterministic popup detection via Chrome DevTools Protocol
  • MCP server — AI agents (Claude, Cursor) can drive wallet extensions via MCP tools
  • Playwright-native — uses test.extend() fixtures, works with your existing Playwright setup

Quick Start

npm install -D walletqa @playwright/test
npx walletqa init --wallet trust-wallet
npx walletqa cache tests/wallet-setup
npx playwright test --project=extension

How It Works

walletqa loads real Chrome extensions into a Playwright browser context, restores a cached profile (so the wallet is already set up), and provides fixtures to approve/reject wallet popups from your test code.

1. defineWalletSetup() → onboard wallet once, snapshot browser profile
2. walletqa cache       → build cached profiles for all wallets
3. Test runs            → restore profile → auto-unlock → run dApp test
4. approve/reject       → detect popup → click confirm/cancel → close popup

Setup Guide

1. Define a wallet setup (one-time onboarding)

Create tests/wallet-setup/trust-wallet.setup.ts:

import { defineWalletSetup } from 'walletqa/cache';
import { TrustWalletPageObject } from 'walletqa/wallets';

const SEED = 'test test test test test test test test test test test junk';
const PASSWORD = 'TestPassword123!';

export default defineWalletSetup('trust-wallet', async (_context, walletPage) => {
  const tw = walletPage as TrustWalletPageObject;
  await tw.importWallet(SEED, PASSWORD);
});

2. Build the cache

npx walletqa cache tests/wallet-setup

This launches a headed browser, runs the onboarding flow, and snapshots the browser profile. You only need to do this once — or when the setup function changes.

3. Write a test

Create tests/connect.extension.test.ts:

import { expect } from '@playwright/test';
import { createWalletFixture } from 'walletqa/fixtures';
import trustWalletSetup from './wallet-setup/trust-wallet.setup.js';

const test = createWalletFixture({
  wallet: 'trust-wallet',
  mode: 'extension',
  password: 'TestPassword123!',
  cacheHash: trustWalletSetup.hash,
});

test('connect wallet to dApp', async ({
  walletPage,
  approveConnection,
}) => {
  await walletPage.goto('http://localhost:3000');
  await walletPage.click('[data-testid="connect-btn"]');

  // Trust Wallet popup appears — approve it
  await approveConnection();

  // Assert your dApp received the wallet address
  const address = walletPage.locator('[data-testid="wallet-address"]');
  await expect(address).toBeVisible();
  await expect(address).toHaveText(/^0x[a-fA-F0-9]{40}$/);
});

4. Configure Playwright

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 60_000,
  projects: [
    {
      name: 'extension',
      use: { ...devices['Desktop Chrome'], headless: false },
      testMatch: '**/*.extension.test.ts',
      fullyParallel: false,
      workers: 1,  // Extension tests must run sequentially
    },
  ],
});

5. Run

npx playwright test --project=extension

Config-Driven Engine

walletqa includes a declarative engine that replaces imperative page objects with data-driven wallet definitions. Instead of writing 300+ lines of TypeScript per wallet, you write ~100 lines of config.

Using the engine directly

import { ApprovalEngine, getWalletDefinition } from 'walletqa/engine';

const def = getWalletDefinition('trust-wallet');
const engine = new ApprovalEngine({
  context: walletContext,
  definition: def,
  extensionId,
  walletPassword: 'TestPassword123!',
  screenshotMode: 'always',  // capture all pages before + after every action
});

// Approve a connection popup
await engine.approve('connect');

// Reject a transaction popup
await engine.reject('transaction');

// Attach screenshots to Playwright HTML report
import { attachCaptures } from 'walletqa/ci';
await attachCaptures(testInfo, engine.getScreenshots());

Screenshot modes: 'off' (zero overhead), 'on-failure' (default — only on timeout), 'always' (before + after every action). When enabled, captures every open page — dApp, extension popup, and extension home — for visual debugging and AI-assisted selector updates.

Wallet definitions

A WalletDefinition fully describes how to interact with a wallet as pure data:

export const trustWalletDef: WalletDefinition = {
  brand: 'trust-wallet',
  extensionStoreId: 'egjidjbpglichdcondbcbdnbeeppgdph',
  chains: ['evm', 'ton', 'solana'],
  protocol: 'eip-6963',
  pages: {
    home: 'home.html',
    onboarding: 'home.html',
    notification: 'notification.html',
  },
  mnemonic: {
    wordCount: 12,
    type: 'bip39',
    inputStyle: 'textarea',
    inputSelector: 'textarea',
  },
  onboarding: {
    steps: [
      { label: 'Accept terms', action: 'check', selector: 'input[type="checkbox"]' },
      { label: 'I already have a wallet', action: 'click',
        selector: 'button:has-text("I already have a wallet")' },
      // ... more steps
    ],
    readySelector: '[class*="Balance"], [class*="balance"]',
  },
  approval: {
    connect:     { confirm: 'button:has-text("Connect")', reject: 'button:has-text("Cancel")' },
    transaction: { confirm: 'button:has-text("Confirm")', reject: 'button:has-text("Cancel")' },
    signature:   { confirm: 'button:has-text("Confirm")', reject: 'button:has-text("Cancel")' },
  },
  quirks: {
    popupStyle: 'new-page',
    autoOpensOnboarding: true,
  },
};

Six wallet definitions are included: Tonkeeper, Trust Wallet, OKX, Phantom, Rabby, and Rainbow. See src/engine/wallet-defs/ for the full configs.

Resilience features

  • A11y-first element discovery — finds approval buttons by scoring all visible elements against the intended action vocabulary using Jaro-Winkler string similarity. A single accessibility tree pass replaces 28 sequential selector attempts, completing in ~200ms vs ~8s worst case
  • CDP popup detection — hooks Chrome DevTools Protocol Target.targetCreated events for deterministic popup detection, eliminating timing-dependent flakiness from service worker delays. Falls back to the heuristic 3-phase detection when CDP is unavailable
  • SES iframe supportframe property on ApprovalAction targets buttons inside sandboxed iframes (OKX SES). The ses-pointer-dispatch click strategy dispatches the full pointer event sequence React 18+ needs inside iframes
  • Post-click verificationquirks.postClickVerification checks the button actually disappeared after clicking, and retries with escalating strategies (evaluate-clickses-pointer-dispatch) if it didn't
  • Custom click strategiesregisterClickStrategy(name, fn) lets you define your own click handler for wallets with unique sandbox architectures
  • Smart fallbacks — when all configured selectors fail, the engine tries common button text patterns ("Confirm", "Approve", "Connect", "Sign") as a recovery layer. Enabled per wallet via quirks.enableSmartFallbacks
  • Auto-screenshot on failure — when approval detection fails, screenshots of all open pages are saved to test-results/approval-failures/ for debugging
  • Structured error diagnosticsApprovalTimeoutError includes which detection phases were tried, which selectors failed, and which pages were open
  • isClosed() guards — all approval operations check page state before interacting, preventing crashes when popups close mid-interaction
  • Extension version pinning--pin-version accepts exact versions (5.0.0) and semver ranges (^4.0.0, ~3.2.0, >=5.0.0, 4.x)
  • Post-action hooksregisterPostActionHook(name, fn) runs custom logic (logging, metrics, screenshots) after every approve/reject action
  • dApp iframe widget supportDappIframeContext helper for testing wallet interactions triggered from embedded iframes (payment widgets, bridges, DEX aggregators)
  • Nightly selector validation — CI workflow runs walletqa validate-selectors nightly and auto-opens GitHub issues when selectors break
  • Pre-flight health checkrunPreflightChecks() validates cache, extension, and version compatibility before browser launch. Fails fast with actionable messages instead of cryptic timeouts
  • Retry-aware flake detection — tests that pass on retry are automatically annotated as walletqa:flake in the HTML report, making it easy to identify unreliable tests
  • Video recording on failurevideo: 'retain-on-failure' records video of every test but only keeps it for failures, providing visual debugging without retry overhead
  • LLM fallback — when all selectors and smart fallbacks fail, optionally queries an LLM (OpenAI, Google Generative AI, or Ollama) to find the button via accessibility tree analysis. Entirely opt-in via llmFallback config on ApprovalEngine. Logs a warning when used so you know to update your selectors
  • Failure root cause classificationclassifyFailure() auto-categorizes test errors into actionable classes: selector-miss, popup-timeout, cache-stale, network-error, extension-crash, or unknown. Used by FlakeReporter and walletqa doctor --flakes for automated triage
  • Flakiness trackingFlakeReporter (a Playwright Reporter) persists pass/fail/retry results to .walletqa/history.json and computes per-test flake scores with trend analysis. Tests below 70% pass rate are flagged for quarantine. See Flake Tracking below
  • Visual change detectioncompareScreenshot() diffs wallet popup screenshots against stored baselines using pixelmatch. Detects UI regressions from extension updates. Requires pixelmatch and pngjs as optional peer deps

For a deep dive into the engine architecture, see docs/wallet-e2e-engine.md.

Flake Tracking

Add the FlakeReporter to your playwright.config.ts to automatically record test results and track flakiness over time:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['html'],
    ['walletqa/ci/flake-reporter'],  // Records results to .walletqa/history.json
  ],
  // ...
});

The reporter extracts wallet and action from test tags (@trust-wallet, @connect, etc.), classifies failures by root cause, and persists results to .walletqa/history.json. View flake scores with:

npx walletqa doctor --flakes    # Top flaky tests with pass rates, trends, and quarantine suggestions

Each test gets a flake score based on pass rate, retry-pass frequency, and trend direction (improving/stable/degrading). Tests below 70% pass rate are flagged for quarantine.

Fixture API

createWalletFixture(options) returns a Playwright test object with these fixtures:

| Fixture | Type | Description | |---------|------|-------------| | walletPage | Page | The dApp page (not the extension) | | walletContext | BrowserContext | Browser context with the extension loaded | | extensionId | string | The Chrome extension ID | | walletPageObject | WalletPageObject | Direct access to wallet page object methods | | approveConnection(opts?) | (OperationOptions?) => Promise<void> | Approve a wallet connection popup | | rejectConnection(opts?) | (OperationOptions?) => Promise<void> | Reject a wallet connection popup | | approveTransaction(opts?) | (OperationOptions?) => Promise<void> | Approve a transaction popup | | rejectTransaction(opts?) | (OperationOptions?) => Promise<void> | Reject a transaction popup | | approveSignature(opts?) | (OperationOptions?) => Promise<void> | Approve a signature popup | | rejectSignature(opts?) | (OperationOptions?) => Promise<void> | Reject a signature popup | | approveNext(opts?) | (OperationOptions?) => Promise<void> | Approve whatever popup appears next (auto-detects type) | | captureState() | (label) => Promise | Capture screenshots of all open pages | | walletEvents | WalletEventLogger | Structured event log for debugging | | networkMonitor | NetworkMonitor | Auto-captures network requests — getErrors(), assertAllUnder(ms) | | dappFrame | FrameLocator \| null | FrameLocator for dApp iframe (set dappIframe option) | | preflightResult | PreflightResult | Pre-flight health check results (cache, extension, version) |

Per-Operation Timeout

All approval/reject fixtures accept an optional OperationOptions parameter so you can override timeouts per call instead of changing the global walletTimeouts:

await approveConnection({ timeout: 15_000 });        // 15s for connect
await approveTransaction({ timeout: 60_000 });       // 60s for slow chain
await approveSignature({ popupTimeout: 10_000 });    // 10s popup detection
await rejectConnection({ forceOpen: false });         // disable force-open fallback

OperationOptions fields (all optional):

| Field | Type | Default | Description | |-------|------|---------|-------------| | timeout | number | walletTimeouts.popupTimeout | Overall timeout for the operation (ms) | | popupTimeout | number | 3000 | How long to wait before falling back to force-open (ms) | | forceOpen | boolean | true | Force-open the notification popup if it doesn't appear |

Options

| Option | Type | Description | |--------|------|-------------| | mode | 'extension' | Testing mode (only 'extension' is supported) | | wallet | WalletBrand | Which wallet to use | | password | string | Wallet password for auto-unlock | | mnemonic | string | Seed phrase for onboarding | | cacheHash | string | Cache key from defineWalletSetup() | | extensionVersion | string | Specific extension version to download | | extensionPath | string | Path to local extension directory | | pinnedVersion | string | Pin extension to a version — fails on mismatch | | launchArgs | string[] | Additional Chrome launch arguments | | dappIframe | string | CSS selector for a dApp iframe — enables the dappFrame fixture | | windowPosition | { x: number; y: number } | Browser window position (e.g., { x: 0, y: 0 } for primary display) |

Multi-Wallet Parametric Tests

Run the same test across multiple wallets with one definition:

import { createMultiWalletTest } from 'walletqa/fixtures';

const test = createMultiWalletTest(['trust-wallet', 'okx', 'phantom'], {
  walletPassword: 'TestPassword123!',
});

test('connect wallet', async ({ walletPage, walletBrand }) => {
  await walletPage.goto('http://localhost:3000');
  await walletPage.click('[data-testid="connect-btn"]');
  console.log(`Testing with ${walletBrand}`);
});

This generates three test variants: [trust-wallet] connect wallet, [okx] connect wallet, [phantom] connect wallet.

Supported Wallets

| Wallet | Chains | dApp E2E | Engine Def | |--------|--------|----------|------------| | Trust Wallet | EVM, TON, Solana | connect + sign + reject + tx (TON) | Yes | | TONkeeper | TON | connect + sign + reject + tx | Yes | | OKX | EVM, TON, Solana | connect + sign + reject + tx (TON) | Yes | | MetaMask | EVM | — (use Synpress) | — | | Phantom | Solana, EVM | connect + sign + reject | Yes | | Rabby | EVM | connect + sign + reject | Yes | | Rainbow | EVM | onboarding only | Yes |

dApp E2E = tests that connect to a real test dApp, approve/reject in the wallet popup, and verify the dApp receives the result.

Engine Def = declarative WalletDefinition config available in src/engine/wallet-defs/.

CLI

npx walletqa init                          # Generate project scaffolding
npx walletqa cache [setupDir]              # Build wallet setup caches
npx walletqa cache [setupDir] --only trust-wallet --force  # Rebuild single wallet (21s vs 5min)
npx walletqa cache [setupDir] --pin-version 4.2.0  # Pin to specific extension version
npx walletqa cache:list                    # List cached setups with hash and age
npx walletqa cache:clean                   # Remove all caches
npx walletqa doctor                        # Check environment: Node, Playwright, extensions
npx walletqa doctor --json                 # Machine-readable diagnostic output
npx walletqa test [testGlob]               # Build cache + run extension tests
npx walletqa test --wallet trust-wallet    # Filter by wallet
npx walletqa test --chain ton              # Filter by chain
npx walletqa test --action sign            # Filter by action
npx walletqa test --wallet okx --action connect  # Combined filters
npx walletqa test --affected               # Auto-select from git diff
npx walletqa test --list                   # Show available test matrix
npx walletqa test --repeat 5               # Run the suite 5 times sequentially (#25)
npx walletqa test --repeat 20 --repeat-on flake-budget=2  # Flake hunt: tolerate 2 failed runs
npx walletqa test --repeat 5 --aggregate stability.json   # Cross-run JSON summary
npx walletqa validate-selectors            # Validate engine selectors against live extensions
npx walletqa validate-selectors --wallet tonkeeper --json  # Validate one wallet, JSON output
npx walletqa check-updates                 # Check all wallets for new extension versions
npx walletqa check-updates --wallet okx    # Check a single wallet
npx walletqa check-updates --json          # Machine-readable output
npx walletqa generate --wallet trust-wallet --actions connect,sign,reject  # Scaffold test + setup files
npx walletqa generate --wallet okx --actions connect --output src/tests   # Custom output directory
npx walletqa generate --wallet tonkeeper --actions send-tx --dapp-url http://localhost:5173  # Custom dApp URL
npx walletqa doctor --flakes               # Show flaky test report with pass rates and trends
npx walletqa open --wallet metamask        # Open a browser with MetaMask (latest version)
npx walletqa open --wallet phantom --ext-version 25.0.0  # Open with a specific version
npx walletqa open --wallet trust-wallet --url http://localhost:3000  # Open and navigate to a dApp
npx walletqa open --wallet metamask --list-versions  # List downloadable versions
npx walletqa open --wallet trust-wallet --seed-phrase "your twelve word seed phrase here"  # Open with wallet ready to use
npx walletqa open --wallet metamask --seed-phrase "..." --password "MyPass123!"  # Custom password
npx walletqa open --wallet okx --seed-phrase "..." --url http://localhost:3000  # Configured + navigate to dApp
npx walletqa cache:verify                  # Verify all cached profiles are still functional
npx walletqa cache:verify --wallet tonkeeper --json  # Verify one wallet, JSON output
npx walletqa verify --wallet trust-wallet  # Quick smoke test: connect + sign + reject
npx walletqa balance --address tonkeeper:ton:UQ...                # Native balance preflight (#22)
npx walletqa balance --address metamask:erc20:0xWALLET:0xUSDC     # ERC-20 / SPL / TON jetton balances
npx walletqa balance --aggregate ./tests --address tonkeeper:ton:UQ...  # Aggregate `requiredBalances` from tests
npx walletqa balance --address tonkeeper:ton:UQ... --usd          # Surface USD-equivalents via CoinGecko (#26)
npx walletqa reports                       # List saved test reports
npx walletqa test --save-report            # Run tests and save HTML report to timestamped dir
npx walletqa diagnose                      # Docker networking diagnostics (DNS, TonConnect proxy, ports)
npx walletqa diagnose --port 3000 --port 3001  # Check host dev server ports from Docker
npx walletqa diagnose --url https://example.com/manifest.json  # Check URL reachability
npx walletqa mcp                           # Start MCP server (stdio transport for Claude/Cursor)

Semantic Test Filtering

All example tests use Playwright tags for semantic filtering:

# "Test all TON wallet connections"
npx walletqa test --chain ton --action connect

# "Run sign tests for Trust Wallet"
npx walletqa test --wallet trust-wallet --action sign

# "Test what my PR changed"
npx walletqa test --affected

Tags: @trust-wallet, @tonkeeper, @okx, @metamask, @phantom, @rabby, @rainbow | @evm, @ton, @solana | @connect, @sign, @send-tx, @reject, @onboard | @engine

The --affected flag reads git diff and maps changed wallet files to the relevant test tags automatically.

Interactive Debugging with walletqa open

Open a real browser with any wallet extension loaded — useful for manual dApp testing, debugging selectors, or exploring wallet UI changes.

# Just the extension (configure manually)
npx walletqa open --wallet metamask

# Fully configured — imports seed phrase, sets password, wallet ready to use
npx walletqa open --wallet trust-wallet --seed-phrase "your twelve word seed phrase"

# Configured + navigate to your dApp
npx walletqa open --wallet okx --seed-phrase "..." --url http://localhost:3000

# Custom password (default: Walletqa1!)
npx walletqa open --wallet phantom --seed-phrase "..." --password "MyPass123!"

# Specific extension version
npx walletqa open --wallet metamask --ext-version 11.16.15

# List all downloadable versions
npx walletqa open --wallet metamask --list-versions

Note: Tonkeeper requires a 24-word TON mnemonic (not standard 12-word BIP39). Use @ton/crypto's mnemonicNew() to generate valid test mnemonics.

MCP Server

walletqa includes an MCP (Model Context Protocol) server that lets AI agents drive real wallet extensions. This is the only MCP server in the ecosystem that can interact with chrome-extension:// pages.

# Start the MCP server (stdio transport)
npx walletqa mcp

Configure in Claude Desktop or Cursor:

{
  "mcpServers": {
    "walletqa": {
      "command": "npx",
      "args": ["walletqa", "mcp"]
    }
  }
}

Available tools (11):

| Tool | Description | |------|-------------| | walletqa_list_wallets | List supported wallets and capabilities | | walletqa_launch | Launch browser with wallet extension | | walletqa_connect | Approve a dApp connection | | walletqa_sign | Approve a signature request | | walletqa_send_tx | Approve a transaction | | walletqa_reject | Reject any pending approval | | walletqa_screenshot | Screenshot the extension popup | | walletqa_navigate | Navigate the dApp page | | walletqa_click | Click an element on the dApp | | walletqa_snapshot | Get a11y tree of the dApp page | | walletqa_close | Close browser and clean up |

Requires @modelcontextprotocol/sdk as an optional peer dependency.

How Caching Works

  1. defineWalletSetup() hashes the setup function body (via esbuild minification) to create a stable cache key
  2. walletqa cache launches a real browser, runs the onboarding flow, waits for Chrome to flush writes, then snapshots the browser profile
  3. At test time, the fixture restores the cached profile, patches Chrome's internal extension paths, and auto-unlocks the wallet
  4. The extension ID stays stable across machines via key injection into the extension's manifest.json

Cache handles: LevelDB LOCK files, Secure Preferences HMAC (via --use-mock-keychain), extension path rewriting, and Extension State cleanup.

Integration with Existing Projects

walletqa is a Playwright fixture — it works alongside your existing tests with zero migration.

Add to an existing Playwright project

npm install -D walletqa

Add a wallet-e2e project to your playwright.config.ts:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 60_000,
  projects: [
    // Your existing tests — unchanged
    {
      name: 'web',
      use: { ...devices['Desktop Chrome'] },
      testMatch: '**/*.test.ts',
      testIgnore: '**/*.extension.test.ts',  // Exclude wallet tests
    },

    // walletqa wallet tests
    {
      name: 'wallet-e2e',
      use: {
        ...devices['Desktop Chrome'],
        headless: false,  // Required — Chrome extensions don't work headless
      },
      testMatch: '**/*.extension.test.ts',
      workers: 1,         // Extension tests run serially
    },
  ],
});

Your existing tests run unchanged. Wallet tests live in separate *.extension.test.ts files:

your-project/
├── playwright.config.ts             # Two projects: "web" + "wallet-e2e"
├── tests/
│   ├── homepage.test.ts             # Existing test (unchanged)
│   ├── wallet-setup/
│   │   └── trust-wallet.setup.ts    # walletqa setup file
│   └── connect.extension.test.ts    # walletqa E2E test
└── .walletqa-cache/                 # Auto-created by walletqa cache

Copy-paste starter: See examples/getting-started/ for a minimal working example.

Using alongside Synpress

walletqa complements Synpress — keep Synpress for MetaMask, use walletqa for everything else. Both use Playwright test.extend() fixtures, so they coexist without conflicts.

| | Synpress | walletqa | |--|---------|---------| | MetaMask | Mature, production-ready | Basic (deprioritized) | | Trust Wallet | Not supported | Full dApp E2E | | OKX | Not supported | Full dApp E2E | | TONkeeper | Not supported | Full dApp E2E + TonConnect | | Phantom | Not supported | Full dApp E2E | | Rabby | Not supported | Full dApp E2E | | Rainbow | Not supported | Onboarding verified |

// playwright.config.ts — three projects coexisting
projects: [
  { name: 'web',        testMatch: '**/*.test.ts',
    testIgnore: ['**/*.extension.test.ts', '**/metamask/**'] },
  { name: 'metamask',   testMatch: '**/metamask/**/*.test.ts',  // Synpress
    use: { headless: false } },
  { name: 'wallet-e2e', testMatch: '**/*.extension.test.ts',    // walletqa
    use: { headless: false }, workers: 1 },
]

Full example: See examples/synpress-coexistence/ for a complete config with shared test helpers.

For detailed step-by-step instructions, see the Integration Guide.

CI Setup

Extension tests require a display server and a cache build step:

# GitHub Actions
- run: xvfb-run npx walletqa cache tests/wallet-setup     # Build cache
- run: xvfb-run npx playwright test --project=wallet-e2e   # Run tests

See docs/ci-setup.md for full CI configuration, cache artifacts, and secrets management.

Docker

Run tests without visible browser windows using the included Docker + Xvfb setup:

docker build -t walletqa .
docker compose run --rm test npx walletqa cache tests/wallet-setup   # build caches inside container
docker compose run --rm test                                         # run tests

# Watch tests live via VNC
WALLETQA_VNC=1 docker compose run --rm --service-ports test
open vnc://localhost:5900   # connect from host

# Network diagnostics (before running against external services)
docker compose run --rm test npx walletqa diagnose --port 3000

Caches must be built inside the container (macOS caches don't work in Linux). See docs/docker.md for VNC, diagnostics, and troubleshooting.

Extending walletqa

Add a custom wallet (no fork required)

import { registerWallet, createWalletFixture, BaseWalletPageObject } from 'walletqa';

// 1. Create a page object
class MyWalletPageObject extends BaseWalletPageObject {
  readonly brand = 'my-wallet';

  async importWallet(mnemonic: string, password: string) {
    const page = await this.getExtensionPage('popup.html');
    // ... your onboarding logic
  }

  async approveConnection() {
    const popup = await this.waitForPopup();  // 3-phase detection built-in
    await popup.locator('button:has-text("Connect")').click();
    await this.closePopup(popup);
  }

  async rejectConnection() { /* ... */ }
  async approveTransaction() { /* ... */ }
  async rejectTransaction() { /* ... */ }
  async approveSignature() { /* ... */ }
  async rejectSignature() { /* ... */ }
}

// 2. Register it
registerWallet('my-wallet', MyWalletPageObject, {
  readySelector: '#root',     // CSS selector for "extension is loaded"
  entryPage: 'popup.html',    // extension entry page path
});

// 3. Use it like any built-in wallet
const test = createWalletFixture({
  wallet: 'my-wallet',
  password: 'TestPassword123!',
});

test('connect', async ({ approveConnection }) => {
  // works exactly like built-in wallets
});

Add a custom engine definition

For declarative wallet configs (~50 lines instead of a full page object class):

import { registerWalletDefinition, ApprovalEngine } from 'walletqa';

registerWalletDefinition('my-wallet', {
  brand: 'my-wallet',
  extensionStoreId: 'abc123',
  chains: ['evm'],
  protocol: 'eip-6963',
  pages: { home: 'popup.html', onboarding: 'popup.html', notification: 'popup.html' },
  mnemonic: { wordCount: 12, type: 'bip39', inputStyle: 'textarea', inputSelector: 'textarea' },
  onboarding: {
    steps: [
      { label: 'Import', action: 'click', selector: 'button:has-text("Import")' },
      { label: 'Fill seed', action: 'fill-mnemonic', selector: '' },
      { label: 'Set password', action: 'fill', selector: '#password', value: '{{password}}' },
      { label: 'Submit', action: 'click', selector: 'button[type="submit"]' },
    ],
    readySelector: '[data-testid="balance"]',
  },
  approval: {
    connect:     { confirm: 'button:has-text("Connect")', reject: 'button:has-text("Cancel")' },
    transaction: { confirm: 'button:has-text("Confirm")', reject: 'button:has-text("Reject")' },
    signature:   { confirm: 'button:has-text("Sign")',    reject: 'button:has-text("Cancel")' },
  },
  unlock: { passwordInput: 'input[type="password"]', submitButton: 'button[type="submit"]' },
  quirks: { popupStyle: 'new-page', enableSmartFallbacks: true },
  meta: { lastVerified: '2026-03-08' },
});

Override selectors without forking

import { patchDefinition, getWalletDefinition } from 'walletqa/engine';

const base = getWalletDefinition('trust-wallet');
const patched = patchDefinition(base, {
  approval: {
    connect: { confirm: '.my-custom-connect-button' },
  },
  quirks: { approvalExtraTimeout: 20_000 },
});

Version-specific selector overrides

When an extension updates and selectors change, add version overrides without breaking older versions:

const def = getWalletDefinition('tonkeeper', '5.1.0');
// Any versions[] entries matching '5.1.0' are automatically deep-merged

Documentation

  • Integration Guide — adding walletqa to existing projects and Synpress coexistence
  • Architecture — module structure, data flow, popup detection, caching internals
  • CI Setup — GitHub Actions, secrets, cache artifacts, xvfb, nightly selector validation
  • Troubleshooting — common issues, error diagnostics, selector validation
  • Engine Design — config-driven wallet definitions, smart fallbacks, version pinning
  • Wallet References — UI selectors, architecture, and debugging for all 7 wallets
  • Parallel Execution — per-wallet parallel safety, workers config, CI guidance
  • Docker — Docker + Xvfb for headless CI/extension testing
  • Backlog — planned features and roadmap

Requirements

  • Node.js >= 18
  • @playwright/test >= 1.39 (peer dependency)
  • Extension tests require headed modeheadless: true is not supported by Chrome extensions
  • For CI: use xvfb-run on Linux or a hosted runner with a display

License

MIT