Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,10 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts

# playwright + e2e test reports
e2e/playwright-report
test-results
e2e/.cache

#env
.env
Binary file modified bun.lockb
Binary file not shown.
94 changes: 94 additions & 0 deletions e2e/E2E.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# End-to-End Test Guide

This project includes a full Playwright / OnchainTestKit setup that automatically:

1. Spins up an **Anvil** fork of Base Mainnet (via OnchainTestKit)
2. Downloads & loads the **MetaMask** browser extension
3. Starts the local **Next.js** app (`bun run dev`)
4. Connects MetaMask to the app and clicks the **Fund** button

Follow the steps below to run the tests on your machine or in CI.

---

## 1 Prerequisites

| Tool | Version | Notes |
|------|---------|-------|
| **Bun** | ≥ 1.2 | <https://bun.sh> |
| **Node ≥ 18** | for Next.js/Playwright |
| **Git** | clone the repo |
| **Mac/Windows/Linux** | Chrome is bundled by Playwright |

Install project deps:

```bash
bun i
```

---

## 2 One-time MetaMask download (local & CI)

```bash
bunx prepare-metamask # provided by @coinbase/onchaintestkit
```

This downloads `metamask-12.8.1` into `e2e/.cache/` so every future test run re-uses the same extension.

---

## 3 Environment variables

Create **`.env.local`** in the project root and add:

```dotenv
# OnchainKit (public) – get these from the CDP dashboard
NEXT_PUBLIC_CDP_API_KEY=your_client_api_key
NEXT_PUBLIC_CDP_PROJECT_ID=your_project_id
NEXT_PUBLIC_WC_PROJECT_ID=your_walletconnect_id

# Only needed if you enable Secure-Init (session-token flow)
# CDP_SECRET_KEY="-----BEGIN PRIVATE KEY-----…"
# CDP_SECRET_KEY_ID=key_id
# CDP_ORG_ID=org_id
```

Feel free to add other env variables here related to E2E_TEST_FORK_URL, or E2E_TEST_FORK_BLOCK_NUMBER, or anything else otherwise they will use the default values.

---

## 4 Running the tests

Headless (CI-style)
```bash
bun run e2e # alias for: bunx playwright test
```

Interactive debugging
```bash
bunx playwright test --headed --debug
```

Playwright will:
* start `next dev` on port 3000
* launch Chromium with the MetaMask extension
* execute the spec at `e2e/fund-flow.spec.ts`

---

## 5 What the template currently does

Currently, it sets up a metamask wallet, uses that as the wallet to login to, and then the fund button becomes available.

Feel free to extend the spec file or duplicate it for additional user journeys.

---

## 6 Handy scripts

| Script | Description |
|--------|-------------|
| `bun run e2e` | Run Playwright tests (headless) |
| `bun run e2e:headed` | Run Playwright tests in headed mode |
| `bunx prepare-metamask` | Download MetaMask extension |
15 changes: 15 additions & 0 deletions e2e/appSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BaseActionType, MetaMask } from "@coinbase/onchaintestkit";
import { Page } from "@playwright/test";

/**
* Connects MetaMask wallet to the app and accepts Terms of Service
* This represents the standard onboarding flow for first-time users
*
* @param page - The Playwright page object
* @param metamask - The MetaMask wallet instance
*/
export async function connectWallet(page: Page, metamask: MetaMask): Promise<void> {
await page.getByTestId('ockConnectButton').nth(1).click();
await page.getByText(/MetaMask/i).first().click();
await metamask.handleAction(BaseActionType.CONNECT_TO_DAPP);
}
27 changes: 27 additions & 0 deletions e2e/fund-flow.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { connectWallet } from './appSession';
import { test, expect } from './onchainTest';

// Adjust selectors to match your UI; this is a starter example.
test('user funds wallet via FundButton', async ({ page, metamask }) => {
// ensure wallet automation fixture is present
if (!metamask) throw new Error('MetaMask fixture is required');

await page.goto('/');

await connectWallet(page, metamask);

// Now click the Fund button - looking for button containing "Fund" text
const fundButton = page.getByRole('button', { name: /Fund/i });
await expect(fundButton).toBeEnabled();

// Handle fund flow trigger – wait for popup/tab after clicking fund
const [popup] = await Promise.all([
page.waitForEvent('popup'),
fundButton.click(),
]);

await expect(popup).not.toBeNull();

// Depending on your success criteria, expand on this test case
// You will need to get things like session tokens, etc....
});
5 changes: 5 additions & 0 deletions e2e/onchainTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createOnchainTest } from '@coinbase/onchaintestkit';
import { metamaskWalletConfig } from './walletConfig/metamaskWalletConfig';

export const test = createOnchainTest(metamaskWalletConfig);
export const { expect } = test;
32 changes: 32 additions & 0 deletions e2e/walletConfig/metamaskWalletConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { base } from 'viem/chains';
import { configure } from '@coinbase/onchaintestkit';

export const DEFAULT_PASSWORD = 'PASSWORD';
export const DEFAULT_SEED_PHRASE = process.env.E2E_TEST_SEED_PHRASE;

// Configure the test with MetaMask setup without adding network yet
const baseConfig = configure()
.withLocalNode({
chainId: base.id,
forkUrl: process.env.E2E_TEST_FORK_URL ?? 'https://mainnet.base.org',
forkBlockNumber: BigInt(process.env.E2E_TEST_FORK_BLOCK_NUMBER ?? '31397553'),
hardfork: 'cancun',
})
.withMetaMask()
.withSeedPhrase({
seedPhrase: DEFAULT_SEED_PHRASE ?? 'test test test test test test test test test test test junk',
password: DEFAULT_PASSWORD,
})
// Add the network with the actual port in a custom setup
.withNetwork({
name: 'Base Mainnet',
chainId: base.id,
symbol: 'ETH',
// placeholder for the actual rpcUrl, which is auto injected by the node fixture
rpcUrl: 'http://localhost:8545',
});

// Build the config
const config = baseConfig.build();

export const metamaskWalletConfig = config;
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
"lint:unsafe": "biome lint --write --unsafe .",
"start": "next start",
"test": "vitest run",
"test:coverage": "vitest run --coverage"
"test:coverage": "vitest run --coverage",
"e2e": "bunx playwright test",
"e2e:headed": "bunx playwright test --headed --debug",
"e2e:ci": "bunx playwright install --with-deps && bunx playwright test --reporter dot"
},
"dependencies": {
"@coinbase/onchainkit": "^0.33.4",
Expand All @@ -30,6 +33,8 @@
},
"devDependencies": {
"@biomejs/biome": "^1.8.0",
"@coinbase/onchaintestkit": "^1.1.0",
"@playwright/test": "^1.53.1",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^14.2.0",
"@types/node": "^20.11.8",
Expand All @@ -49,6 +54,7 @@
"tailwindcss": "^3.4.0",
"typescript": "5.6.2",
"utf-8-validate": "^6.0.3",
"viem": "^2.31.4",
"vitest": "^2.0.1"
}
}
51 changes: 51 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { defineConfig, devices } from '@playwright/test';
import * as dotenv from 'dotenv';

// Load environment variables from .env (if present)
// Load multiple environment files in order of precedence
dotenv.config({ path: './.env.local' });
dotenv.config({ path: './.env' });

// Default port for the Next.js dev server
const PORT = Number(process.env.PORT ?? 3000);
const baseURL = `http://localhost:${PORT}`;

/**
* Playwright configuration
* See https://playwright.dev/docs/test-configuration for all available options.
*/
export default defineConfig({
testDir: './e2e',
/* Global timeouts */
timeout: 5 * 60 * 1000, // 5 minutes per test
expect: {
timeout: 120_000, // 2 minutes for expect assertions
},
/* CI behaviour */
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,

/* Shared settings for all the projects below */
use: {
baseURL,
trace: 'on-first-retry',
video: 'retain-on-failure',
},

/* Projects */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],

/* Automatically start the Next.js server before running the tests */
webServer: {
command: process.env.CI ? 'bun run start' : 'bun run dev',
url: baseURL,
timeout: 120 * 1000, // wait up to 2 minutes for the server
reuseExistingServer: !process.env.CI, // local dev: don\'t restart if already running
},
});
2 changes: 1 addition & 1 deletion src/components/OnchainProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function OnchainProviders({ children }: Props) {
return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<OnchainKitProvider apiKey={NEXT_PUBLIC_CDP_API_KEY} projectId={process.env.NEXT_PUBLIC_CDP_PROJECT_ID}chain={base}>
<OnchainKitProvider apiKey={NEXT_PUBLIC_CDP_API_KEY} projectId={process.env.NEXT_PUBLIC_CDP_PROJECT_ID}chain={base as any}>
<RainbowKitProvider modalSize="compact">
{children}
</RainbowKitProvider>
Expand Down