@kiwa-test/dapp
Full kiwa overview (127s) — this package covers the dApp e2e and manual-write paths. Full-quality MP4 (2.9 MB).
Headless E2E test fixture for dApps on anvil forks (Playwright + viem).
@kiwa-test/dapp is the runtime fixture used by kiwa's Playwright-based dApp E2E layer. It injects window.ethereum into the test page, starts an anvil-backed wallet fixture, handles the core EIP-1193 flows directly, and forwards the rest of the JSON-RPC surface to anvil.
You can use this package in two ways: (a) let Claude generate Playwright tests via /kiwa-play, or (b) import the fixture directly (import { dappE2eTest as test } from "@kiwa-test/dapp") and write the tests by hand. See kiwa README for the full 3-path picture (contract test via @kiwa-test/forge + dApp e2e via @kiwa-test/dapp + manual write).
Installation
pnpm add -D @kiwa-test/dapp @playwright/test viemBonus — Claude Code plugin
If you use Claude Code, install the kiwa skill chain in one command to get /kiwa:kiwa-play (Playwright e2e generation), /kiwa:kiwa-design, /kiwa:kiwa-forge, /kiwa:kiwa-hardhat, /kiwa:kiwa-vitest, /kiwa:kiwa-api, /kiwa:kiwa-review across every dApp project. (The /kiwa:kiwa-test one-shot orchestrator requires examples/ and is kiwa-monorepo-only.)
# In Claude Code:
/plugin marketplace add cardene777/kiwa
/plugin install kiwa@kiwa-marketplace
/reload-pluginsPlugin skills are namespaced by plugin name (
/kiwa:kiwa-design, not/kiwa-design). See kiwa README — Option A for the full skill list.
Quickstart
import { expect } from "@playwright/test";
import { dappE2eTest as test } from "@kiwa-test/dapp";
test("window.ethereum is injected", async ({ page, dappE2e }) => {
await page.goto("/");
const chainId = await page.evaluate(() => {
return (window as any).ethereum.request({ method: "eth_chainId" });
});
await dappE2e.waitForRpcIdle();
expect(chainId).toBe("0x7a69");
});Features
- Playwright fixture preset (
dappE2eTest) that injectswindow.ethereum - anvil lifecycle integration for isolated test runs
- 9 EIP-1193 JSON-RPC methods handled directly, with all other methods forwarded to anvil
- 4 EIP-1193 events triggerable from tests
- transaction broadcast helper for
eth_sendTransaction - approval-mode helpers for deterministic reject-flow testing
- EIP-6963 multi-wallet injection and smart-account support
- vitest helper (
setupTestEnv/withAnvil) that boots a real anvil with--load-statefor unit / integration tests - anvil pool (
createAnvilPool) withanvil_reset-backed 0 ms borrow / release for parallel test suites - tunable transport timeout / retry on
sendTransactionfor fail-fast error-path tests
Vitest helper (mock + real anvil with state load)
@kiwa-test/dapp ships a vitest helper that lets the same test file run in either mock mode (no anvil process, default) or real anvil mode with a pre-built state file. The state file is produced once via kiwa anvil seed, then every test boots anvil with --load-state — equivalent to pasting the entire chain in one shot.
import { setupTestEnv } from "@kiwa-test/dapp";
// mock mode (default): no anvil process, viem mock transport
const env = await setupTestEnv();
// clean anvil: spawns anvil on a free port, chainId 31337
const env = await setupTestEnv({ anvil: true });
// anvil with pre-built state: instant setup, no per-test deploy
const env = await setupTestEnv({
anvil: { loadState: "tests/fixtures/state.json", chainId: 31337 },
});
await env.stop(); // call in afterAllGenerate the state file once with the CLI seed command:
# tests/seed.ts uses process.env.ANVIL_RPC_URL to deploy + setup
pnpm exec kiwa anvil seed tests/seed.ts --out tests/fixtures/state.jsonThe seed script runs against a one-shot anvil; on exit, anvil's --dump-state writes the full chain state to the output path. Subsequent test runs read it back via loadState and reach a ready chain in ~300 ms without replaying any transactions.
Anvil pool (v0.2.0+)
For test suites that need real anvil across many cases, createAnvilPool pre-spawns N instances and lets each test borrow/release one. Releasing runs anvil_reset so the next borrower sees a clean chain — borrow + release is ~0 ms after the pool warms up.
import { createAnvilPool, setupTestEnv, type AnvilPool } from "@kiwa-test/dapp";
let pool: AnvilPool;
beforeAll(async () => {
pool = await createAnvilPool({ size: 4 });
});
afterAll(async () => {
await pool.stopAll();
});
it("borrows from pool", async () => {
const env = await setupTestEnv({ pool }); // borrow + lease
// ...
await env.stop(); // releases back, anvil_reset is invoked
});setupTestEnv cannot accept anvil and pool together — the call throws if both are passed. Polling/spawn was tuned in v0.2.0 so a fresh setupTestEnv({ anvil: true }) returns in ~32 ms (down from 107 ms).
Transport tuning for sendTransaction (v0.2.0+)
TxBroadcastCtx accepts optional transportTimeoutMs (default 5000) and transportRetryCount (default 0). Tests that exercise transport-error paths (e.g. invalid port → ECONNREFUSED) can shrink the wait from viem's 10s default to 200 ms:
await sendTransaction(
{ privateKey, chainId: 31337, anvilPort: brokenPort, transportTimeoutMs: 200, transportRetryCount: 0 },
params,
); // rejects with code -32603 within ~200msDirect RPC Methods
@kiwa-test/dapp handles these methods directly:
eth_requestAccountseth_accountseth_chainIdnet_versionpersonal_signeth_signTypedData_v4wallet_switchEthereumChainwallet_addEthereumChaineth_sendTransaction
Common read methods such as eth_call, eth_getBalance, eth_blockNumber, and eth_estimateGas are forwarded to anvil.
EIP-1193 Events
The injected provider supports these events:
accountsChangedchainChangedconnectdisconnect
From tests, you can drive them with dappE2e.triggerEvent() or use higher-level helpers such as connect(), disconnect(), switchChain(), and waitForRpcIdle().
Related
Author
License
MIT