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

chainwright

v0.9.12

Published

Playwright Web3 wallet testing framework for end-to-end dApp automation with MetaMask, Phantom, Solflare, Petra, Meteor, and Keplr

Downloads

2,907

Readme

Chainwright is an end-to-end testing toolkit for Web3 dapps built on Playwright. It helps you build and cache browser wallet extension state, then reuse it in your end-to-end tests through ready-made fixtures.

Features

  • Wallet setup CLI to build reusable extension cache
  • Playwright fixtures for wallet + Dapp testing
  • Support for multiple wallet profiles per wallet
  • Wallet action APIs for onboarding, account switching, transaction confirmation, and more

Supported Wallets

  • Keplr
  • MetaMask
  • Meteor
  • Petra
  • Phantom
  • Solflare

Requirements

Operating Systems

Supports the following operating systems:

  • MacOS
  • Linux
  • Windows

Installation

Before installing Chainwright, ensure to install Playwright's browser using the command below.

npx playwright install chromium
bunx playwright install chromium

After Installing Playwright's browser, install Chainwright and @playwright/test

pnpm add -D chainwright @playwright/test
bun add -D chainwright @playwright/test
npm install --save-dev chainwright @playwright/test
yarn add -D chainwright @playwright/test

Quick Start

1. Create wallet setup files

Create a setup directory (default: tests/wallet-setup) and add *.setup.ts files with a wallet name in the filename, for example:

  • base.setup.ts
  • base-two.setup.ts
  • petra.setup.ts
  • phantom-team-a.setup.ts

Each file must export default defineWalletSetup(...).

// tests/wallet-setup/metamask.setup.ts
import { defineWalletSetup } from "chainwright/core";
import { Metamask } from "chainwright/metamask";

const PASSWORD = "test1234"; // For Petra wallet, you have to use a strong password. e.g. PlayerPetra45!!
const SEED_PHRASE = "test test test test test test test test test test test test test";

export default defineWalletSetup(
  PASSWORD,
  async ({ walletPage }) => {
    const metamask = new Metamask(walletPage);

    await metamask.onboard({
      mode: "import",
      secretRecoveryPhrase: SEED_PHRASE,
      mainAccountName: "Main",
    });
  },
  {
    ...//Optional prarmeters here
  },
);

For Wallets with additional accounts

// tests/wallet-setup/metamask.setup.ts
import { defineWalletSetup } from "chainwright/core";
import { Petra } from "chainwright/petra";

const PASSWORD = "PlayerPetra45!!";

export default defineWalletSetup(
  PASSWORD,
  async ({ walletPage }) => {
    const petra = new Petra(walletPage);

    await petra.onboard({
      mode: "importMnemonic",
      accountName: "default",
      network: "Testnet",
      secretRecoveryPhrase: "test test test...", // Seed phrase for the main account
      additionalAccounts: [
        {
          accountName: "nw-account",
          mode: "mnemonic",
          mnemonicPhrase: "test test test..." // Seed Phrase for this account
        },
      ]
    });
  },
  {
    ...//Optional prarmeters here
  }
);

To support multiple profiles in a single wallet (for example, MetaMask), only setup files from the second profile onward need an explicit, distinct profile name.

main.setup.ts can use the default profile, while main-two.setup.ts (and any additional setup files) should declare a unique profile name. Then, in any fixture that should use that profile, pass the exact same profileName value.

Example:

  • main.setup.ts: uses the default profile
  • main-two.setup.ts: defines profileName: "profile two"
  • Fixture usage: metamaskFixture({ profileName: "profile two" })
// tests/wallet-setup/main-two.setup.ts
import { defineWalletSetup } from "chainwright/core";
import { Metamask } from "chainwright/metamask";

const PASSWORD = "test1234"; // For Petra wallet, you have to use a strong password. e.g. PlayerPetra45!!
const SEED_PHRASE = "test test test test test test test test test test test test test";

export default defineWalletSetup(
  PASSWORD,
  async ({ walletPage }) => {
    const metamask = new Metamask(walletPage);

    await metamask.onboard({
      mode: "import",
      secretRecoveryPhrase: SEED_PHRASE,
      mainAccountName: "Main",
    });
  },
  {
    profileName: "profile two"
  },
);

2. Build wallet cache

Run setup with the CLI (Supports npx, bun, pnpm, and yarn):

[!NOTE] By default, Chainwright looks for tests/wallet-setup in your base directory. However, you can specify the directory you want Chainwright to get your setup files from.

bun chainwright --wallets <Wallets you want to support>

To specify a directory:

bun chainwright <directory path> <wallet> -f #Optional flag

Useful flags:

  • -h, --help shows you all the commands
  • -f, --force overwrite existing cache
  • --wallets <wallets...> select wallets (metamask, solflare, petra, phantom, meteor, keplr). Setup multiple wallets at the same time.
  • -a, --all setup all wallets
  • --kp, --keplr setup keplr wallet
  • -m, --metamask setup metamask wallet
  • --mt, --meteor setup the meteor wallet
  • --pt, --petra setup petra wallet
  • --ph, --phantom setup phantom wallet
  • -s, --solflare setup solflare wallet

Wallet profile cache is stored under:

  • .wallet-cache/<wallet>/wallet-data (default profile)
  • .wallet-cache/<wallet>/<profileName> (custom profile)

3. Use wallet fixtures in Playwright tests

import { expect, type Page } from "@playwright/test";
import { testWithChainwright } from "chainwright/core";
import { metamaskFixture } from "chainwright/metamask";

// Chainwright's Fixture
export const testWithMetamask = testWithChainwright(metamaskFixture());

// Extend Chainwright's metamaskFixture to suit your need
export const testDappFixture = testWithMetamask.extend<{dAppPage: Page}>({
    dappPage: async ({ page, baseURL }, use) => {
        await page.goto(`https://your-dapp.example`);
        await use(page);
    },
});

// Then in your tests do:
const test = testDappFixture;
test.describe("Example tests", () => {
  test("connect wallet to dapp", async ({ dappPage, metamask }) => {
    const connectButton = dappPage.getByRole("button", { name: /Connect/i})
    await connectButton.click();
    await metamask.connectToApp("Account 1");
    await expect(dappPage.getByText("Connected")).toBeVisible();
  });
})

[!NOTE] The wallet fixture will make use of the default wallet profile. If you specified a profile-name at the point of setting up, make sure to include it in the fixture.

// No profile name is specified at setup time
const testWithFixture = testWithChainwright(fixture())

// If a profile name is specified at setup time.
const testWithFixture = testWithChainwright(fixture({ profileName: "profile name" }))

Wallet fixture parameters:

  • profileName?: string,
  • slowMo?: number

Worker-Scoped Fixture

Use worker-scoped fixtures when tests in a test.describe() block can safely share a wallet context. Setup and teardown run once per worker instead of per test, which speeds up CI runs and reduces flakiness caused by repeated wallet initialization.

import { type Page } from "@playwright/test";
import { testWithChainwright } from "chainwright/core";
import { metamaskWorkerScopeFixture } from "chainwright/metamask";

// Chainwright's worker scoped fixture
export const testWithFixture = testWithChainwright(metamaskWorkerScopeFixture());

// Your worker scoped fixture that extends Chainwright's worker scoped fixture
export const workerScopedFixture = testWithFixture.extend<{dAppPage: Page}>({
  dappPage: [
    async ({ workerScopeContents }, use) => {
        const { context, wallet, walletPage } = workerScopeContents;
        /** N.B:
         * wallet represents -> metamask, phantom, keplr, etc...
         * walletPage represents -> metamask wallet page, phantom wallet page, keplr wallet page, etc...
        */
        const _dappPage = await context.newPage();
        await _dappPage.goto(`http://example-site.com`);
        await use(_dappPage);
    },
    { scope: "worker" },
  ],
})

// Then in your tests do:
const test = workerScopedFixture;

test.describe("Example test", () => {
  test("Should confirm transaction", ({ dappPage, workerScopeContents}) => {
    const { wallet: metamask } = workerScopeContents
    await dappPage.getByRole("button", { name: "Send Tx" }).click();
    await metamask.confirmTransaction();
  });

  test("Should reject transaction", async ({ dappPage, workerScopeContents })=> {
    const { wallet: metamask } = workerScopeContents
    await dappPage.getByRole("button", { name: "Send Tx" }).click();
    await metamask.rejectTransaction();
  });
})

Worker scoped fixture parameters:

  • profileName?: string
  • slowMo?: number

4. Running in CI (GitHub Actions)

Running Chainwright in CI is very similar to running Playwright in CI. The only additional requirement is a cache-build step before executing tests, as shown below:

Why we make use of xvfb:

[!IMPORTANT] Browser extensions don't load in headless Chromium, so the tests have to run in headed mode. CI machines have no display, so launching a headed browser fails. xvfb provides a fake virtual display, letting Chromium run headed in CI as if a screen were attached.

name: CI

on:
  workflow_dispatch:
  pull_request:
    branches: ["main"]

jobs:
  test:
    runs-on: ubuntu-22.04
    timeout-minutes: 60

    steps:
      - name: Checkout code
        uses: actions/checkout@v5
        with:
          submodules: "recursive"
          fetch-depth: 0

      - name: Install pnpm
        uses: pnpm/action-setup@v5
        with:
          version: 10

      - name: Use Node LTS
        uses: actions/setup-node@v6
        with:
          node-version: 24.x
          cache: "pnpm"

      - name: Install dependencies
        run: pnpm install

      - name: Install XVFB
        run: sudo apt-get install -y xvfb

      - name: Install Playwright browsers
        run: pnpx playwright install chromium

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1

      - name: Build cache
        run: xvfb-run pnpm run setup-wallets

      - name: Run end-to-end tests (Headful)
        run: xvfb-run pnpm playwright test --config=tests/playwright.config.ts

Wallets By Module

Each wallet module exports:

  • <wallet>Fixture(...)
  • <wallet>WorkerScopeFixture(...)
  • <WalletClass>

Examples:

  • metamaskFixture, metamaskWorkerScopeFixture, Metamask
  • phantomFixture, phantomWorkerScopeFixture, Phantom
  • petraFixture, petraWorkerScopeFixture, Petra
  • solflareFixture, solflareWorkerScopeFixture, Solflare
  • meteorFixture, meteorWorkerScopeFixture, Meteor
  • keplrFixture, keplrWorkerScopeFixture, Keplr

Extra MetaMask fixtures:

  • createAnvilNode(options?)
  • connectToAnvil()

Extra Phantom/Solflare fixtures:

  • autoCloseNotification (auto fixture)

Core APIs

defineWalletSetup

defineWalletSetup(password, setupFn, config?)
  • password: string - wallet unlock password saved in cache metadata
  • setupFn: ({ context, walletPage }) => Promise<void> - runs onboarding/import flow
  • config?: { profileName?: string; slowMo?: number } - useful for setting up multiple wallet profiles and running the setup in slow motion slowMo.

testWithChainwright

testWithChainwright(customFixtures)

Merges Playwright test with your Chainwright fixture extension.

Common Wallet Actions

Depending on wallet module, wallet class methods include:

  • onboard(...)
  • unlock()
  • lock()
  • switchAccount(...)
  • renameAccount(...)
  • getAccountAddress(...)
  • addAccount(...)
  • connectToApp(...)
  • confirmTransaction()
  • rejectTransaction()

Additional wallet-specific actions are available, for example:

  • MetaMask: switchNetwork, toggleShowTestnetNetwork, addCustomNetwork
  • Phantom: switchNetwork, toggleOptionalChains
  • Petra/Solflare/Meteor: switchNetwork
  • Meteor: openSettings

License

MIT


Built by Tobechukwu. (github) Contributions are welcome: see CONTRIBUTING.md to get involved.