@marigoldlabs/web3-tester 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Web3 Tester
2
2
 
3
- `@marigoldlabs/web3-tester` is a Playwright, Anvil, and Viem harness for deterministic Web3 end-to-end tests without a browser wallet extension.
3
+ `@marigoldlabs/web3-tester` is a Playwright, Anvil, Viem, and MetaMask harness for Web3 end-to-end tests.
4
4
 
5
- The core rule is simple: test the dApp, not MetaMask's popup UI. The harness injects a standards-shaped EIP-1193 provider before application scripts run, bridges wallet RPC calls back to the Playwright process, and sends chain operations directly to Anvil or to an explicit live-chain signer.
5
+ The injected fixtures test dApp behavior with a programmable EIP-1193 provider. The real-wallet adapter launches a persistent Chromium profile with MetaMask, imports or unlocks the profile when configured, and exposes wallet-side actions so consumer apps do not carry extension automation code.
6
6
 
7
7
  ## What It Provides
8
8
 
@@ -12,6 +12,7 @@ The core rule is simple: test the dApp, not MetaMask's popup UI. The harness inj
12
12
  - EIP-6963 provider announcements for wallet selector testing.
13
13
  - Viem-backed chain helpers for impersonation, balance setup, time travel, and block mining.
14
14
  - Optional live-chain fixtures for controlled Sepolia QA with a runtime-only private key.
15
+ - Real MetaMask launch, profile resolution, unlock/import, dapp connection, signature confirmation, transaction confirmation, and token approval helpers.
15
16
  - Fjord v4 QA specs and reports that document the current state of `https://v4.fjordfoundry.com`.
16
17
 
17
18
  ## Install In A Consumer App
@@ -19,7 +20,7 @@ The core rule is simple: test the dApp, not MetaMask's popup UI. The harness inj
19
20
  From the Fjord v4 package, install this repo as a dev dependency:
20
21
 
21
22
  ```bash
22
- npm install --save-dev github:AndyMarigoldLabs/web3-tester
23
+ npm install --save-dev @marigoldlabs/web3-tester
23
24
  ```
24
25
 
25
26
  Then import the local deterministic fixture:
@@ -52,6 +53,30 @@ test('signs in through SIWE on Sepolia', async ({ page, wallet }) => {
52
53
  });
53
54
  ```
54
55
 
56
+ For fully in-UI real wallet tests, launch MetaMask through the package:
57
+
58
+ ```ts
59
+ import { launchRealWallet } from '@marigoldlabs/web3-tester/real-wallet';
60
+
61
+ const wallet = await launchRealWallet({
62
+ baseURL: 'https://v4.fjordfoundry.com',
63
+ expectedAddress: process.env.FJORD_REAL_WALLET_ADDRESS,
64
+ extensionPath: process.env.FJORD_REAL_WALLET_EXTENSION_PATH as string,
65
+ profileDir: process.env.FJORD_REAL_WALLET_PROFILE_DIR as string,
66
+ setup: process.env.FJORD_REAL_WALLET_PASSWORD
67
+ ? {
68
+ password: process.env.FJORD_REAL_WALLET_PASSWORD,
69
+ seedPhrase: process.env.FJORD_REAL_WALLET_SECRET_RECOVERY_PHRASE,
70
+ }
71
+ : undefined,
72
+ });
73
+
74
+ await wallet.connectToDapp();
75
+ await wallet.confirmSignature();
76
+ await wallet.confirmTransaction();
77
+ await wallet.close();
78
+ ```
79
+
55
80
  ## Local Development
56
81
 
57
82
  ```bash
@@ -107,6 +132,11 @@ Copy `.env.example` for local reference. Do not commit real private keys.
107
132
  | `FJORD_MUTATE_STATE` | unset | Must be `true` to deploy QA tokens or create sale drafts. |
108
133
  | `FJORD_PUBLISH_SALES` | unset | Must be `true` to attempt live sale publishing. |
109
134
  | `FJORD_ADMIN_MUTATE` | unset | Must be `true` to attempt admin mutation tests. |
135
+ | `FJORD_REAL_WALLET_EXTENSION_PATH` | unset | Path to the unpacked MetaMask extension for real-wallet tests. |
136
+ | `FJORD_REAL_WALLET_PROFILE_DIR` | unset | Persistent Chromium user-data directory, or a Chrome profile directory such as `Profile 1`. |
137
+ | `FJORD_REAL_WALLET_ADDRESS` | unset | Optional expected account address checked after unlock/import. |
138
+ | `FJORD_REAL_WALLET_PASSWORD` | unset | Optional MetaMask password used to unlock or import the profile. |
139
+ | `FJORD_REAL_WALLET_SECRET_RECOVERY_PHRASE` | unset | Optional seed phrase used only when MetaMask opens on onboarding. |
110
140
 
111
141
  ## Package Surface
112
142
 
@@ -115,6 +145,7 @@ The installable package exports:
115
145
  - `@marigoldlabs/web3-tester`
116
146
  - `@marigoldlabs/web3-tester/fixtures`
117
147
  - `@marigoldlabs/web3-tester/live-fixtures`
148
+ - `@marigoldlabs/web3-tester/real-wallet`
118
149
  - `@marigoldlabs/web3-tester/anvil`
119
150
  - `@marigoldlabs/web3-tester/mock-wallet-controller`
120
151
  - `@marigoldlabs/web3-tester/private-key-rpc-client`
@@ -169,6 +200,7 @@ test.use({
169
200
 
170
201
  - Local tests use deterministic Anvil accounts only.
171
202
  - Live tests require explicit environment variables and never store private keys in source.
203
+ - Real-wallet tests use a persistent browser profile and keep extension-side automation inside this package.
172
204
  - Mutation tests are skipped unless their opt-in flag is set.
173
205
  - Published reports redact secrets and record transaction hashes only when useful for auditability.
174
206
 
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE5E,eAAO,MAAM,aAAa,SAClB,MAAM,WACH,MAAM,SACR,OAAO,KACb,oBAOF,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAW,OAAO,KAAG,mBAiBlD,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE5E,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,SAAS,MAAM,EACf,OAAO,OAAO,KACb,oBAOF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,mBAiBlD,CAAC"}
package/dist/index.d.ts CHANGED
@@ -2,8 +2,10 @@ export { AnvilInstance, ChainController } from './anvil.js';
2
2
  export { test, expect } from './fixtures.js';
3
3
  export { MockWalletController } from './mock-wallet-controller.js';
4
4
  export { PrivateKeyRpcClient } from './private-key-rpc-client.js';
5
+ export { launchRealWallet, resolveRealWalletProfile } from './real-wallet.js';
5
6
  export type { AnvilOptions, AnvilSnapshotId, AnvilViemClient, ChainControllerOptions, } from './anvil.js';
6
7
  export type { MockWalletControllerOptions, RejectionRule, } from './mock-wallet-controller.js';
7
8
  export type { JsonRpcRequest, MockWalletConfig, WalletProviderInfo, } from './types.js';
8
9
  export type { PrivateKeyRpcClientOptions } from './private-key-rpc-client.js';
10
+ export type { RealWalletController, RealWalletGasSettings, RealWalletLaunchOptions, RealWalletProfile, RealWalletSession, RealWalletSetup, } from './real-wallet.js';
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EACV,YAAY,EACZ,eAAe,EACf,eAAe,EACf,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,2BAA2B,EAC3B,aAAa,GACd,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,YAAY,EACV,YAAY,EACZ,eAAe,EACf,eAAe,EACf,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,2BAA2B,EAC3B,aAAa,GACd,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -2,4 +2,5 @@ export { AnvilInstance, ChainController } from './anvil.js';
2
2
  export { test, expect } from './fixtures.js';
3
3
  export { MockWalletController } from './mock-wallet-controller.js';
4
4
  export { PrivateKeyRpcClient } from './private-key-rpc-client.js';
5
+ export { launchRealWallet, resolveRealWalletProfile } from './real-wallet.js';
5
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"injected-provider.d.ts","sourceRoot":"","sources":["../src/injected-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKnD,eAAO,MAAM,aAAa,+BAAc,CAAC;AACzC,eAAO,MAAM,WAAW,0BAAe,CAAC;AAExC,eAAO,MAAM,2BAA2B,WAAY,gBAAgB,KAAG,MAuItE,CAAC"}
1
+ {"version":3,"file":"injected-provider.d.ts","sourceRoot":"","sources":["../src/injected-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKnD,eAAO,MAAM,aAAa,+BAAc,CAAC;AACzC,eAAO,MAAM,WAAW,0BAAe,CAAC;AAExC,eAAO,MAAM,2BAA2B,GAAI,QAAQ,gBAAgB,KAAG,MAuItE,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { type BrowserContext } from '@playwright/test';
2
+ export type RealWalletProfile = {
3
+ profileDirectory?: string;
4
+ userDataDir: string;
5
+ };
6
+ export type RealWalletSetup = {
7
+ password: string;
8
+ seedPhrase?: string;
9
+ };
10
+ export type RealWalletGasSettings = 'site' | 'low' | 'market' | 'aggressive' | {
11
+ gasLimit?: number;
12
+ maxBaseFee: number;
13
+ priorityFee: number;
14
+ };
15
+ export type RealWalletLaunchOptions = {
16
+ baseURL?: string;
17
+ expectedAddress?: string;
18
+ extensionName?: string;
19
+ extensionPath: string;
20
+ headless?: boolean;
21
+ profileDir: string;
22
+ setup?: RealWalletSetup;
23
+ slowMo?: number;
24
+ };
25
+ export type RealWalletController = {
26
+ approveTokenPermission(options?: {
27
+ gasSetting?: RealWalletGasSettings;
28
+ spendLimit?: 'max' | number;
29
+ }): Promise<void>;
30
+ confirmSignature(): Promise<void>;
31
+ confirmTransaction(options?: {
32
+ gasSetting?: RealWalletGasSettings;
33
+ }): Promise<void>;
34
+ connectToDapp(accounts?: string[]): Promise<void>;
35
+ getAccountAddress(): Promise<string>;
36
+ rejectSignature(): Promise<void>;
37
+ rejectTransaction(): Promise<void>;
38
+ };
39
+ export type RealWalletSession = {
40
+ approveTokenPermission(options?: {
41
+ gasSetting?: RealWalletGasSettings;
42
+ spendLimit?: 'max' | number;
43
+ }): Promise<void>;
44
+ close(): Promise<void>;
45
+ confirmSignature(): Promise<void>;
46
+ confirmTransaction(options?: {
47
+ gasSetting?: RealWalletGasSettings;
48
+ }): Promise<void>;
49
+ connectToDapp(accounts?: string[]): Promise<void>;
50
+ context: BrowserContext;
51
+ extensionId: string;
52
+ getAccountAddress(): Promise<string>;
53
+ rejectSignature(): Promise<void>;
54
+ rejectTransaction(): Promise<void>;
55
+ wallet: RealWalletController;
56
+ };
57
+ export declare function resolveRealWalletProfile(profileDir: string): RealWalletProfile;
58
+ export declare function launchRealWallet(options: RealWalletLaunchOptions): Promise<RealWalletSession>;
59
+ //# sourceMappingURL=real-wallet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-wallet.d.ts","sourceRoot":"","sources":["../src/real-wallet.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,cAAc,EAA2B,MAAM,kBAAkB,CAAC;AAE1F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAC7B,MAAM,GACN,KAAK,GACL,QAAQ,GACR,YAAY,GACZ;IACE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEN,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,sBAAsB,CAAC,OAAO,CAAC,EAAE;QAC/B,UAAU,CAAC,EAAE,qBAAqB,CAAC;QACnC,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,kBAAkB,CAAC,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,qBAAqB,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,sBAAsB,CAAC,OAAO,CAAC,EAAE;QAC/B,UAAU,CAAC,EAAE,qBAAqB,CAAC;QACnC,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,kBAAkB,CAAC,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,qBAAqB,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;CAC9B,CAAC;AAeF,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAW9E;AAqkBD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0CnG"}
@@ -0,0 +1,494 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { chromium } from '@playwright/test';
4
+ const DEFAULT_EXTENSION_NAME = 'MetaMask';
5
+ const DEFAULT_TIMEOUT_MS = 30_000;
6
+ const SHORT_TIMEOUT_MS = 2_000;
7
+ const testId = (id) => `[data-testid="${id}"]`;
8
+ function extensionUrl(extensionId, page = 'home.html') {
9
+ return `chrome-extension://${extensionId}/${page}`;
10
+ }
11
+ function extensionIdFromUrl(url) {
12
+ return /^chrome-extension:\/\/([^/]+)\//.exec(url)?.[1];
13
+ }
14
+ export function resolveRealWalletProfile(profileDir) {
15
+ const resolved = path.resolve(profileDir);
16
+ const profileDirectory = path.basename(resolved);
17
+ const userDataDir = path.dirname(resolved);
18
+ const looksLikeChromeProfile = /^(?:Default|Profile \d+)$/.test(profileDirectory);
19
+ if (looksLikeChromeProfile && fs.existsSync(path.join(userDataDir, 'Local State'))) {
20
+ return { profileDirectory, userDataDir };
21
+ }
22
+ return { userDataDir: resolved };
23
+ }
24
+ async function isVisible(locator, timeout = SHORT_TIMEOUT_MS) {
25
+ await locator.first().waitFor({ state: 'visible', timeout });
26
+ return true;
27
+ }
28
+ async function isHidden(locator, timeout = SHORT_TIMEOUT_MS) {
29
+ await locator.first().waitFor({ state: 'hidden', timeout });
30
+ return true;
31
+ }
32
+ async function clickFirstVisible(locators, timeout = SHORT_TIMEOUT_MS) {
33
+ for (const locator of locators) {
34
+ const target = locator.first();
35
+ if (!(await isVisible(target, timeout).catch(() => false)))
36
+ continue;
37
+ await target.click();
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+ async function fillFirstVisible(locators, value, timeout = DEFAULT_TIMEOUT_MS) {
43
+ for (const locator of locators) {
44
+ const target = locator.first();
45
+ if (!(await isVisible(target, timeout).catch(() => false)))
46
+ continue;
47
+ await target.fill(value);
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ async function waitForMetaMaskReady(page) {
53
+ await page.waitForLoadState('domcontentloaded', { timeout: DEFAULT_TIMEOUT_MS }).catch(() => undefined);
54
+ await isHidden(page.locator('.spinner, .loading-overlay, [data-testid="loading-overlay"]'), DEFAULT_TIMEOUT_MS).catch(() => undefined);
55
+ }
56
+ async function closeMetaMaskOverlay(page) {
57
+ await clickFirstVisible([
58
+ page.locator(testId('popover-close')),
59
+ page.locator('.mm-modal-content .mm-modal-header button').first(),
60
+ page.getByRole('button', { name: 'Close' }),
61
+ ], SHORT_TIMEOUT_MS);
62
+ }
63
+ async function discoverExtensionIdFromRuntime(context) {
64
+ const workerId = context.serviceWorkers().map((worker) => extensionIdFromUrl(worker.url())).find(Boolean);
65
+ if (workerId)
66
+ return workerId;
67
+ const pageId = context.pages().map((page) => extensionIdFromUrl(page.url())).find(Boolean);
68
+ if (pageId)
69
+ return pageId;
70
+ const worker = await context.waitForEvent('serviceworker', { timeout: 10_000 }).catch(() => undefined);
71
+ if (worker) {
72
+ const extensionId = extensionIdFromUrl(worker.url());
73
+ if (extensionId)
74
+ return extensionId;
75
+ }
76
+ return undefined;
77
+ }
78
+ async function discoverExtensionIdFromManagementApi(context, extensionName) {
79
+ const page = await context.newPage();
80
+ try {
81
+ await page.goto('chrome://extensions', { waitUntil: 'domcontentloaded' });
82
+ const extensions = await page.evaluate(() => {
83
+ const chromeApi = globalThis;
84
+ return new Promise((resolve, reject) => {
85
+ if (!chromeApi.chrome?.management?.getAll) {
86
+ reject(new Error('chrome.management.getAll is not available on chrome://extensions.'));
87
+ return;
88
+ }
89
+ chromeApi.chrome.management.getAll((items) => {
90
+ const error = chromeApi.chrome?.runtime?.lastError;
91
+ if (error)
92
+ reject(new Error(error.message ?? 'Unable to enumerate Chrome extensions.'));
93
+ else
94
+ resolve(items);
95
+ });
96
+ });
97
+ });
98
+ const exact = extensions.find((extension) => extension.name.toLowerCase() === extensionName.toLowerCase());
99
+ if (exact)
100
+ return exact.id;
101
+ const available = extensions.map((extension) => extension.name).sort().join(', ');
102
+ throw new Error(`Unable to find extension "${extensionName}". Installed extensions: ${available || 'none'}.`);
103
+ }
104
+ finally {
105
+ await page.close().catch(() => undefined);
106
+ }
107
+ }
108
+ async function getExtensionId(context, extensionName) {
109
+ return ((await discoverExtensionIdFromRuntime(context)) ??
110
+ (await discoverExtensionIdFromManagementApi(context, extensionName)));
111
+ }
112
+ async function openExtensionHome(context, extensionId) {
113
+ const homeUrl = extensionUrl(extensionId);
114
+ const existing = context.pages().find((page) => page.url().startsWith(homeUrl));
115
+ const page = existing ?? (await context.newPage());
116
+ if (!page.url().startsWith(homeUrl))
117
+ await page.goto(homeUrl);
118
+ await waitForMetaMaskReady(page);
119
+ return page;
120
+ }
121
+ function notificationUrlPrefix(extensionId) {
122
+ return extensionUrl(extensionId, 'notification.html');
123
+ }
124
+ async function getNotificationPage(context, extensionId, timeout = DEFAULT_TIMEOUT_MS) {
125
+ const prefix = notificationUrlPrefix(extensionId);
126
+ const startedAt = Date.now();
127
+ let page = context.pages().find((candidate) => candidate.url().startsWith(prefix));
128
+ while (!page && Date.now() - startedAt < timeout) {
129
+ const pendingPages = context.pages().filter((candidate) => !candidate.isClosed());
130
+ for (const pendingPage of pendingPages) {
131
+ await pendingPage
132
+ .waitForURL((url) => url.href.startsWith(prefix), { timeout: 250 })
133
+ .catch(() => undefined);
134
+ if (pendingPage.url().startsWith(prefix)) {
135
+ page = pendingPage;
136
+ break;
137
+ }
138
+ }
139
+ if (page)
140
+ break;
141
+ const remaining = Math.max(timeout - (Date.now() - startedAt), 1_000);
142
+ const candidate = await context.waitForEvent('page', { timeout: Math.min(remaining, 1_000) }).catch(() => undefined);
143
+ if (!candidate) {
144
+ page = context.pages().find((openPage) => openPage.url().startsWith(prefix));
145
+ continue;
146
+ }
147
+ await candidate
148
+ .waitForURL((url) => url.href.startsWith(prefix), { timeout: Math.min(remaining, 5_000) })
149
+ .catch(() => undefined);
150
+ if (candidate.url().startsWith(prefix))
151
+ page = candidate;
152
+ }
153
+ if (!page)
154
+ throw new Error('Timed out waiting for MetaMask notification window.');
155
+ await waitForMetaMaskReady(page);
156
+ await page.bringToFront().catch(() => undefined);
157
+ return page;
158
+ }
159
+ function shortAddress(address) {
160
+ return `${address.slice(0, 6)}...${address.slice(-4)}`.toLowerCase();
161
+ }
162
+ async function fillSeedPhrase(page, seedPhrase) {
163
+ const words = seedPhrase.trim().split(/\s+/);
164
+ if (words.length < 12) {
165
+ throw new Error('setup.seedPhrase must contain at least 12 words.');
166
+ }
167
+ const singleInputFilled = await fillFirstVisible([
168
+ page.locator(testId('srp-input-import__srp-note')),
169
+ page.locator('textarea[name="seedPhrase"]'),
170
+ page.locator('textarea').first(),
171
+ ], words.join(' '), SHORT_TIMEOUT_MS);
172
+ if (singleInputFilled)
173
+ return;
174
+ const wordInputs = page.locator('input[id^="import-srp__srp-word-"], input[data-testid^="import-srp__srp-word-"]');
175
+ if ((await wordInputs.count()) >= words.length) {
176
+ for (const [index, word] of words.entries()) {
177
+ await wordInputs.nth(index).fill(word);
178
+ }
179
+ return;
180
+ }
181
+ throw new Error('Unable to find MetaMask seed phrase input fields.');
182
+ }
183
+ async function importMetaMaskWallet(page, setup) {
184
+ if (!setup.password || !setup.seedPhrase) {
185
+ throw new Error('MetaMask is on onboarding. Provide setup.password and setup.seedPhrase to import a wallet through web3-tester, or use a preconfigured persistent profile.');
186
+ }
187
+ await page.bringToFront().catch(() => undefined);
188
+ const terms = page.locator(testId('onboarding-terms-checkbox'));
189
+ if (await isVisible(terms, SHORT_TIMEOUT_MS).catch(() => false)) {
190
+ await terms.check();
191
+ }
192
+ const importStarted = await clickFirstVisible([
193
+ page.locator(testId('onboarding-import-wallet')),
194
+ page.locator(testId('onboarding-import-with-srp-button')),
195
+ page.getByRole('button', { name: 'Import an existing wallet' }),
196
+ page.getByRole('button', { name: 'Import wallet' }),
197
+ ], DEFAULT_TIMEOUT_MS);
198
+ if (!importStarted)
199
+ throw new Error('Unable to start MetaMask wallet import flow.');
200
+ await clickFirstVisible([
201
+ page.locator(testId('metametrics-no-thanks')),
202
+ page.locator(testId('metametrics-i-agree')),
203
+ page.getByRole('button', { name: 'No thanks' }),
204
+ page.getByRole('button', { name: 'I agree' }),
205
+ ], SHORT_TIMEOUT_MS);
206
+ await fillSeedPhrase(page, setup.seedPhrase);
207
+ const seedConfirmed = await clickFirstVisible([
208
+ page.locator(testId('import-srp-confirm')),
209
+ page.getByRole('button', { name: 'Confirm Secret Recovery Phrase' }),
210
+ page.getByRole('button', { name: 'Confirm' }),
211
+ ], DEFAULT_TIMEOUT_MS);
212
+ if (!seedConfirmed)
213
+ throw new Error('Unable to confirm MetaMask seed phrase import.');
214
+ const passwordFilled = await fillFirstVisible([page.locator(testId('create-password-new-input')), page.locator('input[type="password"]').nth(0)], setup.password);
215
+ if (!passwordFilled)
216
+ throw new Error('Unable to find MetaMask password input.');
217
+ const confirmationPasswordFilled = await fillFirstVisible([page.locator(testId('create-password-confirm-input')), page.locator('input[type="password"]').nth(1)], setup.password);
218
+ if (!confirmationPasswordFilled)
219
+ throw new Error('Unable to find MetaMask password confirmation input.');
220
+ const passwordTerms = page.locator(testId('create-password-terms'));
221
+ if (await isVisible(passwordTerms, SHORT_TIMEOUT_MS).catch(() => false)) {
222
+ await passwordTerms.check();
223
+ }
224
+ const passwordSubmitted = await clickFirstVisible([page.locator(testId('create-password-submit')), page.getByRole('button', { name: 'Import my wallet' })], DEFAULT_TIMEOUT_MS);
225
+ if (!passwordSubmitted)
226
+ throw new Error('Unable to submit MetaMask import password.');
227
+ await clickFirstVisible([
228
+ page.locator(testId('onboarding-complete-done')),
229
+ page.getByRole('button', { name: 'Done' }),
230
+ page.getByRole('button', { name: 'Skip' }),
231
+ ], DEFAULT_TIMEOUT_MS);
232
+ await waitForMetaMaskReady(page);
233
+ }
234
+ async function unlockMetaMask(page, password) {
235
+ const passwordFilled = await fillFirstVisible([page.locator(testId('unlock-password')), page.locator('input[type="password"]').first()], password, DEFAULT_TIMEOUT_MS);
236
+ if (!passwordFilled)
237
+ throw new Error('Unable to find MetaMask unlock password input.');
238
+ const unlocked = await clickFirstVisible([page.locator(testId('unlock-submit')), page.getByRole('button', { name: 'Unlock' })], DEFAULT_TIMEOUT_MS);
239
+ if (!unlocked)
240
+ throw new Error('Unable to submit MetaMask unlock form.');
241
+ await waitForMetaMaskReady(page);
242
+ }
243
+ class MetaMaskRealWallet {
244
+ context;
245
+ homePage;
246
+ extensionId;
247
+ constructor(context, homePage, extensionId) {
248
+ this.context = context;
249
+ this.homePage = homePage;
250
+ this.extensionId = extensionId;
251
+ }
252
+ async approveTokenPermission(options) {
253
+ const page = await this.notificationPage();
254
+ if (options?.spendLimit === 'max') {
255
+ await clickFirstVisible([page.locator(testId('custom-spending-cap-max-button'))], SHORT_TIMEOUT_MS);
256
+ }
257
+ else if (typeof options?.spendLimit === 'number') {
258
+ await fillFirstVisible([page.locator(testId('custom-spending-cap-input'))], String(options.spendLimit), SHORT_TIMEOUT_MS);
259
+ }
260
+ await this.confirmFooterAction(page);
261
+ if (!page.isClosed()) {
262
+ await waitForMetaMaskReady(page);
263
+ await this.applyGasSetting(page, options?.gasSetting);
264
+ await this.confirmFooterAction(page, SHORT_TIMEOUT_MS).catch(() => undefined);
265
+ }
266
+ }
267
+ async confirmSignature() {
268
+ const page = await this.notificationPage();
269
+ const structuredSignButton = page.locator(testId('signature-sign-button')).first();
270
+ const scrollButton = page.locator(testId('signature-request-scroll-button')).first();
271
+ for (let attempts = 0; attempts < 8; attempts += 1) {
272
+ if (!(await isVisible(scrollButton, SHORT_TIMEOUT_MS).catch(() => false)))
273
+ break;
274
+ if (await structuredSignButton.isEnabled().catch(() => false))
275
+ break;
276
+ await scrollButton.click();
277
+ }
278
+ const signed = await clickFirstVisible([
279
+ page.locator(testId('request-signature__sign')),
280
+ structuredSignButton,
281
+ page.locator(testId('page-container-footer-next')),
282
+ page.getByRole('button', { name: 'Sign' }),
283
+ ], DEFAULT_TIMEOUT_MS);
284
+ if (!signed)
285
+ throw new Error('Unable to confirm MetaMask signature request.');
286
+ await clickFirstVisible([page.locator(testId('signature-warning-sign-button'))], SHORT_TIMEOUT_MS);
287
+ }
288
+ async confirmTransaction(options) {
289
+ const page = await this.notificationPage();
290
+ await this.applyGasSetting(page, options?.gasSetting);
291
+ await clickFirstVisible([page.locator('.set-approval-for-all-warning__footer__approve-button')], SHORT_TIMEOUT_MS);
292
+ await this.confirmFooterAction(page);
293
+ }
294
+ async connectToDapp(accounts) {
295
+ const page = await this.notificationPage();
296
+ await this.selectAccounts(page, accounts);
297
+ await this.confirmFooterAction(page);
298
+ if (!page.isClosed()) {
299
+ await waitForMetaMaskReady(page);
300
+ await this.confirmFooterAction(page, SHORT_TIMEOUT_MS).catch(() => undefined);
301
+ }
302
+ }
303
+ async getAccountAddress() {
304
+ const page = await this.home();
305
+ await closeMetaMaskOverlay(page);
306
+ const openedMenu = await clickFirstVisible([page.locator(testId('account-options-menu-button'))], DEFAULT_TIMEOUT_MS);
307
+ if (!openedMenu)
308
+ throw new Error('Unable to open MetaMask account options menu.');
309
+ const openedDetails = await clickFirstVisible([page.locator(testId('account-list-menu-details'))], DEFAULT_TIMEOUT_MS);
310
+ if (!openedDetails)
311
+ throw new Error('Unable to open MetaMask account details.');
312
+ const addressText = page.locator(testId('address-copy-button-text')).first();
313
+ await addressText.waitFor({ state: 'visible', timeout: DEFAULT_TIMEOUT_MS });
314
+ const address = (await addressText.textContent())?.trim();
315
+ await closeMetaMaskOverlay(page);
316
+ if (!address?.startsWith('0x'))
317
+ throw new Error('Unable to read selected MetaMask account address.');
318
+ return address;
319
+ }
320
+ async rejectSignature() {
321
+ const page = await this.notificationPage();
322
+ await this.rejectFooterAction(page, [
323
+ page.locator(testId('signature-cancel-button')),
324
+ page.locator('.request-signature__footer button.btn-secondary'),
325
+ ]);
326
+ }
327
+ async rejectTransaction() {
328
+ const page = await this.notificationPage();
329
+ await this.rejectFooterAction(page);
330
+ }
331
+ async home() {
332
+ if (!this.homePage.isClosed()) {
333
+ await this.homePage.bringToFront().catch(() => undefined);
334
+ await waitForMetaMaskReady(this.homePage);
335
+ return this.homePage;
336
+ }
337
+ return openExtensionHome(this.context, this.extensionId);
338
+ }
339
+ async notificationPage() {
340
+ return getNotificationPage(this.context, this.extensionId);
341
+ }
342
+ async confirmFooterAction(page, timeout = DEFAULT_TIMEOUT_MS) {
343
+ const confirmed = await clickFirstVisible([
344
+ page.locator(testId('confirm-footer-button')),
345
+ page.locator(testId('confirmation-submit-button')),
346
+ page.locator(testId('page-container-footer-next')),
347
+ page.getByRole('button', { name: 'Confirm' }),
348
+ page.getByRole('button', { name: 'Connect' }),
349
+ page.getByRole('button', { name: 'Next' }),
350
+ page.getByRole('button', { name: 'Approve' }),
351
+ ], timeout);
352
+ if (!confirmed)
353
+ throw new Error('Unable to confirm MetaMask notification.');
354
+ }
355
+ async rejectFooterAction(page, extraLocators = []) {
356
+ const rejected = await clickFirstVisible([
357
+ ...extraLocators,
358
+ page.locator(testId('confirm-footer-cancel-button')),
359
+ page.locator(testId('page-container-footer-cancel')),
360
+ page.getByRole('button', { name: 'Reject' }),
361
+ page.getByRole('button', { name: 'Cancel' }),
362
+ ], DEFAULT_TIMEOUT_MS);
363
+ if (!rejected)
364
+ throw new Error('Unable to reject MetaMask notification.');
365
+ }
366
+ async selectAccounts(page, accounts) {
367
+ if (!accounts?.length)
368
+ return;
369
+ const rows = page.locator('.choose-account-list .choose-account-list__account');
370
+ const rowCount = await rows.count();
371
+ if (!rowCount)
372
+ return;
373
+ const expected = accounts.map((account) => account.toLowerCase());
374
+ const expectedShort = accounts.map(shortAddress);
375
+ for (let index = 0; index < rowCount; index += 1) {
376
+ const row = rows.nth(index);
377
+ const text = ((await row.textContent()) ?? '').toLowerCase();
378
+ const matches = expected.some((account) => text.includes(account)) || expectedShort.some((account) => text.includes(account));
379
+ if (!matches)
380
+ continue;
381
+ const checkbox = row.locator('input[type="checkbox"]').first();
382
+ if (await isVisible(checkbox, SHORT_TIMEOUT_MS).catch(() => false))
383
+ await checkbox.check();
384
+ else
385
+ await row.click();
386
+ return;
387
+ }
388
+ if (rowCount > 1) {
389
+ throw new Error(`Unable to find requested MetaMask account in connect prompt: ${accounts.join(', ')}.`);
390
+ }
391
+ }
392
+ async applyGasSetting(page, gasSetting) {
393
+ if (!gasSetting || gasSetting === 'site')
394
+ return;
395
+ const editOpened = await clickFirstVisible([page.locator(testId('edit-gas-fee-icon')), page.getByRole('button', { name: 'Edit gas fee' })], SHORT_TIMEOUT_MS);
396
+ if (!editOpened)
397
+ throw new Error('Unable to open MetaMask gas fee editor.');
398
+ if (typeof gasSetting === 'string') {
399
+ const testIdBySetting = {
400
+ aggressive: 'edit-gas-fee-item-high',
401
+ low: 'edit-gas-fee-item-low',
402
+ market: 'edit-gas-fee-item-medium',
403
+ };
404
+ const selected = await clickFirstVisible([page.locator(testId(testIdBySetting[gasSetting]))], DEFAULT_TIMEOUT_MS);
405
+ if (!selected)
406
+ throw new Error(`Unable to select MetaMask ${gasSetting} gas setting.`);
407
+ }
408
+ else {
409
+ const customOpened = await clickFirstVisible([page.locator(testId('edit-gas-fee-item-custom'))], DEFAULT_TIMEOUT_MS);
410
+ if (!customOpened)
411
+ throw new Error('Unable to open MetaMask custom gas editor.');
412
+ const baseFeeFilled = await fillFirstVisible([page.locator(testId('base-fee-input'))], String(gasSetting.maxBaseFee));
413
+ if (!baseFeeFilled)
414
+ throw new Error('Unable to fill MetaMask custom base fee.');
415
+ const priorityFeeFilled = await fillFirstVisible([page.locator(testId('priority-fee-input'))], String(gasSetting.priorityFee));
416
+ if (!priorityFeeFilled)
417
+ throw new Error('Unable to fill MetaMask custom priority fee.');
418
+ if (gasSetting.gasLimit !== undefined) {
419
+ const gasLimitFilled = await fillFirstVisible([page.locator(testId('gas-limit-input'))], String(gasSetting.gasLimit));
420
+ if (!gasLimitFilled)
421
+ throw new Error('Unable to fill MetaMask custom gas limit.');
422
+ }
423
+ }
424
+ const saved = await clickFirstVisible([page.locator('.popover-footer button.btn-primary'), page.getByRole('button', { name: 'Save' })], DEFAULT_TIMEOUT_MS);
425
+ if (!saved)
426
+ throw new Error('Unable to save MetaMask gas setting.');
427
+ }
428
+ }
429
+ async function prepareMetaMask({ expectedAddress, page, setup, wallet, }) {
430
+ const onboardingImport = page.locator(testId('onboarding-import-wallet'));
431
+ const onboardingCreate = page.locator(testId('onboarding-create-wallet'));
432
+ const onboardingTerms = page.locator(testId('onboarding-terms-checkbox'));
433
+ if ((await isVisible(onboardingImport, SHORT_TIMEOUT_MS).catch(() => false)) ||
434
+ (await isVisible(onboardingCreate, SHORT_TIMEOUT_MS).catch(() => false)) ||
435
+ (await isVisible(onboardingTerms, SHORT_TIMEOUT_MS).catch(() => false))) {
436
+ if (!setup?.password || !setup.seedPhrase) {
437
+ throw new Error('MetaMask is on onboarding. Provide setup.password and setup.seedPhrase to import a wallet through web3-tester, or use a preconfigured persistent profile.');
438
+ }
439
+ await importMetaMaskWallet(page, setup);
440
+ }
441
+ const unlockPassword = page.locator(testId('unlock-password'));
442
+ if (await isVisible(unlockPassword, SHORT_TIMEOUT_MS).catch(() => false)) {
443
+ if (!setup?.password) {
444
+ throw new Error('MetaMask profile is locked. Provide setup.password to unlock through web3-tester, or unlock the persistent profile before running.');
445
+ }
446
+ await unlockMetaMask(page, setup.password);
447
+ }
448
+ if (!expectedAddress)
449
+ return;
450
+ const address = await wallet.getAccountAddress();
451
+ if (address.toLowerCase() !== expectedAddress.toLowerCase()) {
452
+ throw new Error(`MetaMask account ${address} does not match expected address ${expectedAddress}.`);
453
+ }
454
+ }
455
+ export async function launchRealWallet(options) {
456
+ const extensionName = options.extensionName ?? DEFAULT_EXTENSION_NAME;
457
+ if (!fs.existsSync(options.extensionPath)) {
458
+ throw new Error(`MetaMask extension path does not exist: ${options.extensionPath}`);
459
+ }
460
+ const profile = resolveRealWalletProfile(options.profileDir);
461
+ const context = await chromium.launchPersistentContext(profile.userDataDir, {
462
+ args: [
463
+ ...(profile.profileDirectory ? [`--profile-directory=${profile.profileDirectory}`] : []),
464
+ `--disable-extensions-except=${options.extensionPath}`,
465
+ `--load-extension=${options.extensionPath}`,
466
+ ],
467
+ baseURL: options.baseURL,
468
+ headless: options.headless ?? false,
469
+ slowMo: options.slowMo,
470
+ });
471
+ const extensionId = await getExtensionId(context, extensionName);
472
+ const page = await openExtensionHome(context, extensionId);
473
+ const wallet = new MetaMaskRealWallet(context, page, extensionId);
474
+ await prepareMetaMask({
475
+ expectedAddress: options.expectedAddress,
476
+ page,
477
+ setup: options.setup,
478
+ wallet,
479
+ });
480
+ return {
481
+ approveTokenPermission: (approvalOptions) => wallet.approveTokenPermission(approvalOptions),
482
+ close: () => context.close(),
483
+ confirmSignature: () => wallet.confirmSignature(),
484
+ confirmTransaction: (confirmationOptions) => wallet.confirmTransaction(confirmationOptions),
485
+ connectToDapp: (accounts) => wallet.connectToDapp(accounts),
486
+ context,
487
+ extensionId,
488
+ getAccountAddress: () => wallet.getAccountAddress(),
489
+ rejectSignature: () => wallet.rejectSignature(),
490
+ rejectTransaction: () => wallet.rejectTransaction(),
491
+ wallet,
492
+ };
493
+ }
494
+ //# sourceMappingURL=real-wallet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-wallet.js","sourceRoot":"","sources":["../src/real-wallet.ts"],"names":[],"mappings":"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"}
package/docs/API.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # API Reference
2
2
 
3
- This package exposes two Playwright fixture families:
3
+ This package exposes two Playwright fixture families and one real-wallet adapter:
4
4
 
5
5
  - `fixtures`: local deterministic Anvil tests.
6
6
  - `live-fixtures`: live Sepolia tests that sign with a runtime-only private key.
7
+ - `real-wallet`: persistent Chromium and MetaMask automation for fully in-UI Web3 tests.
7
8
 
8
9
  ## Local Fixtures
9
10
 
@@ -48,6 +49,53 @@ Optional:
48
49
  SEPOLIA_RPC_URL=https://...
49
50
  ```
50
51
 
52
+ ## Real Wallet
53
+
54
+ ```ts
55
+ import { launchRealWallet } from '@marigoldlabs/web3-tester/real-wallet';
56
+
57
+ const session = await launchRealWallet({
58
+ extensionPath: process.env.FJORD_REAL_WALLET_EXTENSION_PATH as string,
59
+ profileDir: process.env.FJORD_REAL_WALLET_PROFILE_DIR as string,
60
+ expectedAddress: process.env.FJORD_REAL_WALLET_ADDRESS,
61
+ setup: process.env.FJORD_REAL_WALLET_PASSWORD
62
+ ? {
63
+ password: process.env.FJORD_REAL_WALLET_PASSWORD,
64
+ seedPhrase: process.env.FJORD_REAL_WALLET_SECRET_RECOVERY_PHRASE,
65
+ }
66
+ : undefined,
67
+ });
68
+ ```
69
+
70
+ `launchRealWallet` starts a persistent Chromium context with the configured unpacked MetaMask extension. `profileDir` can be either a dedicated Playwright user data directory or a Chrome profile directory such as `Default` or `Profile 1`; Chrome profile paths are mapped back to their user data root and launched with `--profile-directory`.
71
+
72
+ Options:
73
+
74
+ | Option | Description |
75
+ | --- | --- |
76
+ | `extensionPath` | Required path to the unpacked MetaMask extension. |
77
+ | `profileDir` | Required persistent Chromium user data directory, or a Chrome profile directory. |
78
+ | `baseURL` | Optional Playwright base URL for pages opened from the returned context. |
79
+ | `expectedAddress` | Optional account address assertion after unlock/import. |
80
+ | `extensionName` | Extension name used to resolve the extension ID. Defaults to `MetaMask`. |
81
+ | `headless` | Chromium headless setting. Defaults to `false` because extensions require headed Chromium in normal use. |
82
+ | `setup.password` | Password used to unlock MetaMask, or to import a seed phrase when onboarding is visible. |
83
+ | `setup.seedPhrase` | Seed phrase used only if MetaMask opens on onboarding. |
84
+ | `slowMo` | Optional Playwright slow-motion delay. |
85
+
86
+ Returned session methods:
87
+
88
+ | Method | Description |
89
+ | --- | --- |
90
+ | `connectToDapp(accounts?)` | Approves a dapp connection request in MetaMask. |
91
+ | `confirmSignature()` | Confirms a pending signature request. |
92
+ | `confirmTransaction(options?)` | Confirms a pending transaction request. |
93
+ | `approveTokenPermission(options?)` | Approves a pending ERC-20 spending permission request. |
94
+ | `rejectSignature()` | Rejects a pending signature request. |
95
+ | `rejectTransaction()` | Rejects a pending transaction request. |
96
+ | `getAccountAddress()` | Returns the selected MetaMask account. |
97
+ | `close()` | Closes the persistent browser context. |
98
+
51
99
  ## MockWalletController
52
100
 
53
101
  ```ts
@@ -1,17 +1,17 @@
1
1
  # Consuming Web3 Tester From Fjord V4
2
2
 
3
- This guide covers adding this repo to Fjord v4 as a dependency.
3
+ This guide covers adding the published package to Fjord v4 as a dependency.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install --save-dev github:AndyMarigoldLabs/web3-tester
8
+ npm install --save-dev @marigoldlabs/web3-tester
9
9
  ```
10
10
 
11
- For a pinned dependency, use a commit SHA:
11
+ For a pinned dependency, use an exact package version:
12
12
 
13
13
  ```bash
14
- npm install --save-dev github:AndyMarigoldLabs/web3-tester#<commit-sha>
14
+ npm install --save-dev @marigoldlabs/web3-tester@<version>
15
15
  ```
16
16
 
17
17
  ## Playwright Config
@@ -120,4 +120,4 @@ Keep destructive or expensive tests behind separate flags so normal CI remains r
120
120
  - Run live Sepolia read-only tests on a scheduled job or protected branch.
121
121
  - Run live mutation tests manually with approval.
122
122
  - Store `FJORD_PRIVATE_KEY` and `SEPOLIA_RPC_URL` only as CI secrets.
123
- - Pin this repo by commit SHA once Fjord depends on it.
123
+ - Pin the npm dependency to an exact version once Fjord depends on it.
@@ -1,6 +1,6 @@
1
1
  # Release Checklist
2
2
 
3
- Use this before pushing a new version or pinning it inside Fjord v4.
3
+ Use this before publishing a new version or pinning it inside Fjord v4.
4
4
 
5
5
  ## Required
6
6
 
@@ -16,7 +16,7 @@ Use this before pushing a new version or pinning it inside Fjord v4.
16
16
  From a clean consumer repo:
17
17
 
18
18
  ```bash
19
- npm install --save-dev github:AndyMarigoldLabs/web3-tester#<commit-sha>
19
+ npm install --save-dev @marigoldlabs/web3-tester@<version>
20
20
  ```
21
21
 
22
22
  Then import:
@@ -47,3 +47,9 @@ git add .
47
47
  git commit -m "Document and package Web3 tester"
48
48
  git push -u origin main
49
49
  ```
50
+
51
+ ## Publish
52
+
53
+ ```bash
54
+ npm publish --access public
55
+ ```
@@ -1,4 +1,4 @@
1
- import { expect, test } from '@marigoldlabs/web3-tester/live-fixtures';
1
+ import { expect, test } from '@andy-marigold-labs/web3-tester/live-fixtures';
2
2
 
3
3
  test.skip(!process.env.FJORD_PRIVATE_KEY, 'FJORD_PRIVATE_KEY is required for live Sepolia tests.');
4
4
 
@@ -1,4 +1,4 @@
1
- import { expect, test } from '@marigoldlabs/web3-tester/fixtures';
1
+ import { expect, test } from '@andy-marigold-labs/web3-tester/fixtures';
2
2
 
3
3
  type EthereumProvider = {
4
4
  request: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marigoldlabs/web3-tester",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "A Playwright, Anvil, and Viem based Web3 E2E harness with an injected programmable wallet provider.",
@@ -39,6 +39,10 @@
39
39
  "./private-key-rpc-client": {
40
40
  "types": "./dist/private-key-rpc-client.d.ts",
41
41
  "import": "./dist/private-key-rpc-client.js"
42
+ },
43
+ "./real-wallet": {
44
+ "types": "./dist/real-wallet.d.ts",
45
+ "import": "./dist/real-wallet.js"
42
46
  }
43
47
  },
44
48
  "files": [
@@ -56,15 +60,14 @@
56
60
  "typecheck": "tsc --noEmit",
57
61
  "verify": "npm run typecheck && npm run build && npm test"
58
62
  },
59
- "dependencies": {
60
- "viem": "^2.39.3"
61
- },
62
63
  "peerDependencies": {
63
- "@playwright/test": ">=1.56.0"
64
+ "@playwright/test": ">=1.56.0",
65
+ "viem": ">=2.39.3 <3"
64
66
  },
65
67
  "devDependencies": {
66
68
  "@playwright/test": "^1.56.1",
67
69
  "@types/node": "^24.10.1",
68
- "typescript": "^5.9.3"
70
+ "typescript": "^5.9.3",
71
+ "viem": "^2.52.0"
69
72
  }
70
73
  }