@walletwright/core 0.3.1 → 0.4.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @walletwright/core
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2113ac7: `connectToDapp` now accepts `{ optional: true }` and waits for a required popup by default. A site the wallet already trusts connects without one; passing the flag keeps the short wait and resolves quietly, while the default flags a missing popup instead of silently succeeding.
8
+
3
9
  ## 0.3.1
4
10
 
5
11
  ### Patch Changes
package/dist/cli.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as WalletSetup } from "./types-CFQpWZhv.mjs";
1
+ import { a as WalletSetup } from "./types-BXDYOEaq.mjs";
2
2
  //#region src/cli.d.ts
3
3
  type Flags = {
4
4
  "cache-dir"?: string;
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as WalletSetup, i as WalletKind, n as Wallet, r as WalletDefinition, t as Ecosystem } from "./types-CFQpWZhv.mjs";
1
+ import { a as WalletSetup, i as WalletKind, n as Wallet, r as WalletDefinition, t as Ecosystem } from "./types-BXDYOEaq.mjs";
2
2
  import { BrowserContext, chromium } from "@playwright/test";
3
3
  //#region src/internal/cache.d.ts
4
4
  declare const buildCache: (setup: WalletSetup, options?: {
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ import os from "node:os";
8
8
  const POPUP_CLOSE_TIMEOUT_MS = 15e3;
9
9
  /** Optional approvals keep a short wait: "no popup" is a normal outcome, and the wait is latency. */
10
10
  const OPTIONAL_POPUP_TIMEOUT_MS = 1e4;
11
- /** The MV3 service worker spawns a popup slowly once the wallet's own UI has been driven. */
11
+ /** A popup that has to appear gets the full wait: the MV3 service worker can be slow to spawn it. */
12
12
  const REQUIRED_POPUP_TIMEOUT_MS = 3e4;
13
13
  /**
14
14
  * Drive `popup` with `settle` and return the page that actually took the approval. A popup that
@@ -85,7 +85,7 @@ const createWallet = ({ context, definition, extensionId, home, password }) => {
85
85
  approve,
86
86
  confirmSignature: () => approve(),
87
87
  confirmTransaction: () => approve(),
88
- connectToDapp: () => approve({ optional: true }),
88
+ connectToDapp: (options = {}) => approve(options),
89
89
  extensionId,
90
90
  home,
91
91
  network: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["base"],"sources":["../src/internal/controller.ts","../src/internal/launch.ts","../src/fixtures.ts"],"sourcesContent":["import type { BrowserContext, Page } from \"@playwright/test\";\n\nimport type { Wallet, WalletActionContext, WalletDefinition } from \"../types\";\n\nimport { DEFAULT_NOTIFICATION_MATCH, findNotificationPopup, placeApprovalWindow } from \"./utils\";\nimport { formatTimeout, waitUntil } from \"./wait\";\n\nconst POPUP_CLOSE_TIMEOUT_MS = 15_000;\n/** Optional approvals keep a short wait: \"no popup\" is a normal outcome, and the wait is latency. */\nconst OPTIONAL_POPUP_TIMEOUT_MS = 10_000;\n/** The MV3 service worker spawns a popup slowly once the wallet's own UI has been driven. */\nconst REQUIRED_POPUP_TIMEOUT_MS = 30_000;\ntype ResolveOptions = { optional?: boolean };\n\n/**\n * Drive `popup` with `settle` and return the page that actually took the approval. A popup that\n * closes mid-settle is the wallet respawning the request, so re-acquire once and drive the new one.\n */\nconst settleApproval = async (\n settle: (popup: Page) => Promise<void>,\n popup: Page,\n acquire: () => Promise<Page | undefined>,\n): Promise<Page> => {\n try {\n await settle(popup);\n return popup;\n } catch (error) {\n if (!popup.isClosed()) {\n throw error;\n }\n const fresh = await acquire();\n if (!fresh) {\n throw error;\n }\n await settle(fresh);\n return fresh;\n }\n};\n\ntype CreateWalletOptions = {\n context: BrowserContext;\n definition: WalletDefinition;\n extensionId: string;\n /** The wallet's own page, left open after unlock, that the actions drive. */\n home: Page;\n password: string;\n};\n\n/** Build the runtime controller that drives an unlocked wallet against the dapp under test. */\nexport const createWallet = ({\n context,\n definition,\n extensionId,\n home,\n password,\n}: CreateWalletOptions): Wallet => {\n const match = definition.notificationMatch ?? DEFAULT_NOTIFICATION_MATCH;\n const ctx: WalletActionContext = { context, extensionId, home, password };\n\n /** MetaMask opens requests inline while its extension page owns focus. */\n const frontDapp = async (): Promise<void> => {\n const dapp = context.pages().find((page) => /^https?:/v.test(page.url()) && !page.isClosed());\n await dapp?.bringToFront().catch(() => {});\n };\n\n /** Find and place the approval window before its controls are driven. */\n const acquireApproval = async (optional: boolean): Promise<Page | undefined> => {\n const popup = await findNotificationPopup({\n approvalControls: definition.approvalControls,\n context,\n extensionId,\n match,\n timeoutMs: optional ? OPTIONAL_POPUP_TIMEOUT_MS : REQUIRED_POPUP_TIMEOUT_MS,\n });\n if (popup) {\n await placeApprovalWindow(popup);\n }\n return popup;\n };\n\n /** Drive the pending approval popup with `settle`, then wait for that popup to close. */\n const resolvePopup = async (\n settle: (popup: Page) => Promise<void>,\n { optional = false }: ResolveOptions,\n ): Promise<void> => {\n await frontDapp();\n const acquire = () => acquireApproval(optional);\n const popup = await acquire();\n if (!popup) {\n if (optional) {\n return; // e.g. Phantom auto-approves an already-trusted site (no popup)\n }\n const open = context.pages().map((page) => page.url());\n throw new Error(\n `[walletwright] approval popup did not appear. Open pages: ${open.join(\" | \") || \"none\"}`,\n );\n }\n\n const driven = await settleApproval(settle, popup, acquire);\n\n /** Wait for the exact popup we drove; chained popups may reuse the same URL. */\n const closed = await waitUntil(() => driven.isClosed() || undefined, {\n timeoutMs: POPUP_CLOSE_TIMEOUT_MS,\n });\n if (closed === undefined) {\n throw new Error(\n `[walletwright] approval popup did not close after ${formatTimeout(POPUP_CLOSE_TIMEOUT_MS)} (the approval may not have registered)`,\n );\n }\n };\n\n const unsupported = (name: string): Error =>\n new Error(`[walletwright] ${definition.extensionName} does not support ${name}()`);\n\n const approve = (options: ResolveOptions = {}) =>\n resolvePopup((popup) => definition.approve(popup, password), options);\n\n const reject = (options: ResolveOptions = {}) =>\n resolvePopup((popup) => definition.reject(popup), options);\n\n /** Bind a verified wallet action and restore focus to the dapp when it finishes. */\n const action =\n <A extends ReadonlyArray<unknown>>(\n fn: ((ctx: WalletActionContext, ...args: A) => Promise<void>) | undefined,\n name: string,\n ) =>\n async (...args: A): Promise<void> => {\n if (!fn) {\n throw unsupported(name);\n }\n if (home.isClosed()) {\n throw new Error(`[walletwright] wallet home page is closed; cannot run ${name}()`);\n }\n await home.bringToFront().catch(() => {});\n await fn(ctx, ...args);\n await frontDapp();\n };\n\n return {\n accounts: {\n add: action(definition.actions?.accounts?.add, \"accounts.add\"),\n importPrivateKey: action(\n definition.actions?.accounts?.importPrivateKey,\n \"accounts.importPrivateKey\",\n ),\n rename: action(definition.actions?.accounts?.rename, \"accounts.rename\"),\n switch: action(definition.actions?.accounts?.switch, \"accounts.switch\"),\n },\n approve,\n confirmSignature: () => approve(),\n confirmTransaction: () => approve(),\n connectToDapp: () => approve({ optional: true }),\n extensionId,\n home,\n network: {\n add: action(definition.actions?.network?.add, \"network.add\"),\n switch: action(definition.actions?.network?.switch, \"network.switch\"),\n },\n reject,\n rejectConnection: () => reject(),\n rejectSignature: () => reject(),\n rejectTransaction: () => reject(),\n settings: {\n lock: action(definition.actions?.settings?.lock, \"settings.lock\"),\n unlock: action(definition.actions?.settings?.unlock, \"settings.unlock\"),\n },\n };\n};\n","import { cp, mkdtemp, rm, stat } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { type BrowserContext, chromium, type Page } from \"@playwright/test\";\n\nimport type { Wallet, WalletSetup } from \"../types\";\nimport { wallets } from \"../wallets/index\";\n\nimport { restorePreviousProfile } from \"./cache\";\nimport { extensionContextOptions } from \"./chromium\";\nimport { createWallet } from \"./controller\";\nimport { DEFAULT_CACHE_DIR, extensionIdFromPath, profileKey } from \"./utils\";\n\nexport type LaunchedWallet = {\n /**\n * Close the browser and remove the throwaway profile copy, in that order. Prefer this over\n * `context.close()`: the copy is a full onboarded profile (tens of MB), and awaiting its removal is\n * the only way to know it is gone before the process exits.\n */\n close: () => Promise<void>;\n context: BrowserContext;\n wallet: Wallet;\n};\n\nconst closeLaunch = async (context: BrowserContext, runDir: string): Promise<void> => {\n const closed = await Promise.allSettled([context.close()]);\n const removed = await Promise.allSettled([rm(runDir, { force: true, recursive: true })]);\n const closeResult = closed[0];\n const removeResult = removed[0];\n\n if (closeResult?.status === \"rejected\" && removeResult?.status === \"rejected\") {\n throw new AggregateError(\n [closeResult.reason, removeResult.reason],\n `[walletwright] failed to close the browser and remove ${runDir}`,\n );\n }\n if (closeResult?.status === \"rejected\") {\n throw closeResult.reason;\n }\n if (removeResult?.status === \"rejected\") {\n throw removeResult.reason;\n }\n};\n\n/** Close Chromium's blank tab and the extension's auto-opened tab. */\nconst closeStrayPages = async (context: BrowserContext, home: Page): Promise<void> => {\n const isStray = (page: Page): boolean =>\n page !== home &&\n !page.isClosed() &&\n (page.url() === \"about:blank\" || page.url().startsWith(\"chrome-extension://\"));\n\n const closing: Array<Promise<void>> = [];\n for (const page of context.pages()) {\n if (isStray(page)) {\n closing.push(page.close());\n }\n }\n await Promise.allSettled(closing);\n};\n\n/** Launch and unlock a disposable profile copy. Headless mode requires verified wallet support. */\nexport const launchWallet = async (\n setup: WalletSetup,\n { headless = false }: { headless?: boolean } = {},\n): Promise<LaunchedWallet> => {\n const definition = wallets[setup.wallet];\n if (headless && definition.headlessApprovals !== true) {\n throw new Error(\n `[walletwright] ${definition.extensionName} has no verified headless approval flow; run this suite headed (\\`use: { headless: false }\\`, or \\`--headed\\`).`,\n );\n }\n const cacheDir = path.resolve(setup.cacheDir ?? DEFAULT_CACHE_DIR);\n const profileDir = path.join(cacheDir, profileKey(setup));\n await restorePreviousProfile(profileDir);\n try {\n await stat(profileDir);\n } catch (error) {\n throw new Error(\n `[walletwright] no cache for this setup at ${profileDir}. Build it first with buildCache() or \\`walletwright cache\\`.`,\n { cause: error },\n );\n }\n\n const extensionPath = await definition.prepareExtension(cacheDir, setup.version);\n\n const extensionId = await extensionIdFromPath(extensionPath);\n\n /** Cleanup must cover copy and launch failures that occur before a context exists. */\n const runDir = await mkdtemp(path.join(os.tmpdir(), \"walletwright-\"));\n const removeRunDir = (): Promise<void> => rm(runDir, { force: true, recursive: true });\n const handleContextClose = async (): Promise<void> => {\n try {\n await removeRunDir();\n } catch {\n // Playwright does not await close-event listeners; `close()` owns strict cleanup.\n }\n };\n\n let context: BrowserContext | undefined;\n try {\n await cp(profileDir, runDir, { recursive: true });\n\n context = await chromium.launchPersistentContext(\n runDir,\n extensionContextOptions(extensionPath, headless),\n );\n const launched = context;\n launched.on(\"close\", handleContextClose);\n\n await definition.prepareContext?.(launched);\n const home = await definition.reachUnlockScreen(launched, extensionId);\n await definition.unlock(home, setup.password);\n await closeStrayPages(launched, home);\n\n return {\n close: async () => {\n launched.off(\"close\", handleContextClose);\n await closeLaunch(launched, runDir);\n },\n context: launched,\n wallet: createWallet({\n context: launched,\n definition,\n extensionId,\n home,\n password: setup.password,\n }),\n };\n } catch (error) {\n context?.off(\"close\", handleContextClose);\n await context?.close().catch(() => {});\n await removeRunDir().catch(() => {});\n throw error;\n }\n};\n\nexport { closeLaunch };\n","import { test as base } from \"@playwright/test\";\n\nimport { type LaunchedWallet, launchWallet } from \"./internal/launch\";\nimport type { Wallet, WalletSetup } from \"./types\";\n\nexport type WalletFixtures = {\n wallet: Wallet;\n};\n\n/** One launch per test, which the `context` and `wallet` fixtures then read from. */\ntype LaunchFixture = {\n walletLaunch: LaunchedWallet;\n};\n\n/** Create Playwright fixtures backed by one unlocked wallet launch per test. */\nexport const createWalletFixtures = (setup: WalletSetup) =>\n base.extend<LaunchFixture & WalletFixtures>({\n context: async ({ walletLaunch }, use) => {\n await use(walletLaunch.context);\n },\n wallet: async ({ walletLaunch }, use) => {\n await use(walletLaunch.wallet);\n },\n walletLaunch: async ({ headless }, use) => {\n const launched = await launchWallet(setup, { headless });\n await use(launched);\n await launched.close();\n },\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["base"],"sources":["../src/internal/controller.ts","../src/internal/launch.ts","../src/fixtures.ts"],"sourcesContent":["import type { BrowserContext, Page } from \"@playwright/test\";\n\nimport type { Wallet, WalletActionContext, WalletDefinition } from \"../types\";\n\nimport { DEFAULT_NOTIFICATION_MATCH, findNotificationPopup, placeApprovalWindow } from \"./utils\";\nimport { formatTimeout, waitUntil } from \"./wait\";\n\nconst POPUP_CLOSE_TIMEOUT_MS = 15_000;\n/** Optional approvals keep a short wait: \"no popup\" is a normal outcome, and the wait is latency. */\nconst OPTIONAL_POPUP_TIMEOUT_MS = 10_000;\n/** A popup that has to appear gets the full wait: the MV3 service worker can be slow to spawn it. */\nconst REQUIRED_POPUP_TIMEOUT_MS = 30_000;\ntype ResolveOptions = { optional?: boolean };\n\n/**\n * Drive `popup` with `settle` and return the page that actually took the approval. A popup that\n * closes mid-settle is the wallet respawning the request, so re-acquire once and drive the new one.\n */\nconst settleApproval = async (\n settle: (popup: Page) => Promise<void>,\n popup: Page,\n acquire: () => Promise<Page | undefined>,\n): Promise<Page> => {\n try {\n await settle(popup);\n return popup;\n } catch (error) {\n if (!popup.isClosed()) {\n throw error;\n }\n const fresh = await acquire();\n if (!fresh) {\n throw error;\n }\n await settle(fresh);\n return fresh;\n }\n};\n\ntype CreateWalletOptions = {\n context: BrowserContext;\n definition: WalletDefinition;\n extensionId: string;\n /** The wallet's own page, left open after unlock, that the actions drive. */\n home: Page;\n password: string;\n};\n\n/** Build the runtime controller that drives an unlocked wallet against the dapp under test. */\nexport const createWallet = ({\n context,\n definition,\n extensionId,\n home,\n password,\n}: CreateWalletOptions): Wallet => {\n const match = definition.notificationMatch ?? DEFAULT_NOTIFICATION_MATCH;\n const ctx: WalletActionContext = { context, extensionId, home, password };\n\n /** MetaMask opens requests inline while its extension page owns focus. */\n const frontDapp = async (): Promise<void> => {\n const dapp = context.pages().find((page) => /^https?:/v.test(page.url()) && !page.isClosed());\n await dapp?.bringToFront().catch(() => {});\n };\n\n /** Find and place the approval window before its controls are driven. */\n const acquireApproval = async (optional: boolean): Promise<Page | undefined> => {\n const popup = await findNotificationPopup({\n approvalControls: definition.approvalControls,\n context,\n extensionId,\n match,\n timeoutMs: optional ? OPTIONAL_POPUP_TIMEOUT_MS : REQUIRED_POPUP_TIMEOUT_MS,\n });\n if (popup) {\n await placeApprovalWindow(popup);\n }\n return popup;\n };\n\n /** Drive the pending approval popup with `settle`, then wait for that popup to close. */\n const resolvePopup = async (\n settle: (popup: Page) => Promise<void>,\n { optional = false }: ResolveOptions,\n ): Promise<void> => {\n await frontDapp();\n const acquire = () => acquireApproval(optional);\n const popup = await acquire();\n if (!popup) {\n if (optional) {\n return; // e.g. Phantom auto-approves an already-trusted site (no popup)\n }\n const open = context.pages().map((page) => page.url());\n throw new Error(\n `[walletwright] approval popup did not appear. Open pages: ${open.join(\" | \") || \"none\"}`,\n );\n }\n\n const driven = await settleApproval(settle, popup, acquire);\n\n /** Wait for the exact popup we drove; chained popups may reuse the same URL. */\n const closed = await waitUntil(() => driven.isClosed() || undefined, {\n timeoutMs: POPUP_CLOSE_TIMEOUT_MS,\n });\n if (closed === undefined) {\n throw new Error(\n `[walletwright] approval popup did not close after ${formatTimeout(POPUP_CLOSE_TIMEOUT_MS)} (the approval may not have registered)`,\n );\n }\n };\n\n const unsupported = (name: string): Error =>\n new Error(`[walletwright] ${definition.extensionName} does not support ${name}()`);\n\n const approve = (options: ResolveOptions = {}) =>\n resolvePopup((popup) => definition.approve(popup, password), options);\n\n const reject = (options: ResolveOptions = {}) =>\n resolvePopup((popup) => definition.reject(popup), options);\n\n /** Bind a verified wallet action and restore focus to the dapp when it finishes. */\n const action =\n <A extends ReadonlyArray<unknown>>(\n fn: ((ctx: WalletActionContext, ...args: A) => Promise<void>) | undefined,\n name: string,\n ) =>\n async (...args: A): Promise<void> => {\n if (!fn) {\n throw unsupported(name);\n }\n if (home.isClosed()) {\n throw new Error(`[walletwright] wallet home page is closed; cannot run ${name}()`);\n }\n await home.bringToFront().catch(() => {});\n await fn(ctx, ...args);\n await frontDapp();\n };\n\n return {\n accounts: {\n add: action(definition.actions?.accounts?.add, \"accounts.add\"),\n importPrivateKey: action(\n definition.actions?.accounts?.importPrivateKey,\n \"accounts.importPrivateKey\",\n ),\n rename: action(definition.actions?.accounts?.rename, \"accounts.rename\"),\n switch: action(definition.actions?.accounts?.switch, \"accounts.switch\"),\n },\n approve,\n confirmSignature: () => approve(),\n confirmTransaction: () => approve(),\n connectToDapp: (options: ResolveOptions = {}) => approve(options),\n extensionId,\n home,\n network: {\n add: action(definition.actions?.network?.add, \"network.add\"),\n switch: action(definition.actions?.network?.switch, \"network.switch\"),\n },\n reject,\n rejectConnection: () => reject(),\n rejectSignature: () => reject(),\n rejectTransaction: () => reject(),\n settings: {\n lock: action(definition.actions?.settings?.lock, \"settings.lock\"),\n unlock: action(definition.actions?.settings?.unlock, \"settings.unlock\"),\n },\n };\n};\n","import { cp, mkdtemp, rm, stat } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport { type BrowserContext, chromium, type Page } from \"@playwright/test\";\n\nimport type { Wallet, WalletSetup } from \"../types\";\nimport { wallets } from \"../wallets/index\";\n\nimport { restorePreviousProfile } from \"./cache\";\nimport { extensionContextOptions } from \"./chromium\";\nimport { createWallet } from \"./controller\";\nimport { DEFAULT_CACHE_DIR, extensionIdFromPath, profileKey } from \"./utils\";\n\nexport type LaunchedWallet = {\n /**\n * Close the browser and remove the throwaway profile copy, in that order. Prefer this over\n * `context.close()`: the copy is a full onboarded profile (tens of MB), and awaiting its removal is\n * the only way to know it is gone before the process exits.\n */\n close: () => Promise<void>;\n context: BrowserContext;\n wallet: Wallet;\n};\n\nconst closeLaunch = async (context: BrowserContext, runDir: string): Promise<void> => {\n const closed = await Promise.allSettled([context.close()]);\n const removed = await Promise.allSettled([rm(runDir, { force: true, recursive: true })]);\n const closeResult = closed[0];\n const removeResult = removed[0];\n\n if (closeResult?.status === \"rejected\" && removeResult?.status === \"rejected\") {\n throw new AggregateError(\n [closeResult.reason, removeResult.reason],\n `[walletwright] failed to close the browser and remove ${runDir}`,\n );\n }\n if (closeResult?.status === \"rejected\") {\n throw closeResult.reason;\n }\n if (removeResult?.status === \"rejected\") {\n throw removeResult.reason;\n }\n};\n\n/** Close Chromium's blank tab and the extension's auto-opened tab. */\nconst closeStrayPages = async (context: BrowserContext, home: Page): Promise<void> => {\n const isStray = (page: Page): boolean =>\n page !== home &&\n !page.isClosed() &&\n (page.url() === \"about:blank\" || page.url().startsWith(\"chrome-extension://\"));\n\n const closing: Array<Promise<void>> = [];\n for (const page of context.pages()) {\n if (isStray(page)) {\n closing.push(page.close());\n }\n }\n await Promise.allSettled(closing);\n};\n\n/** Launch and unlock a disposable profile copy. Headless mode requires verified wallet support. */\nexport const launchWallet = async (\n setup: WalletSetup,\n { headless = false }: { headless?: boolean } = {},\n): Promise<LaunchedWallet> => {\n const definition = wallets[setup.wallet];\n if (headless && definition.headlessApprovals !== true) {\n throw new Error(\n `[walletwright] ${definition.extensionName} has no verified headless approval flow; run this suite headed (\\`use: { headless: false }\\`, or \\`--headed\\`).`,\n );\n }\n const cacheDir = path.resolve(setup.cacheDir ?? DEFAULT_CACHE_DIR);\n const profileDir = path.join(cacheDir, profileKey(setup));\n await restorePreviousProfile(profileDir);\n try {\n await stat(profileDir);\n } catch (error) {\n throw new Error(\n `[walletwright] no cache for this setup at ${profileDir}. Build it first with buildCache() or \\`walletwright cache\\`.`,\n { cause: error },\n );\n }\n\n const extensionPath = await definition.prepareExtension(cacheDir, setup.version);\n\n const extensionId = await extensionIdFromPath(extensionPath);\n\n /** Cleanup must cover copy and launch failures that occur before a context exists. */\n const runDir = await mkdtemp(path.join(os.tmpdir(), \"walletwright-\"));\n const removeRunDir = (): Promise<void> => rm(runDir, { force: true, recursive: true });\n const handleContextClose = async (): Promise<void> => {\n try {\n await removeRunDir();\n } catch {\n // Playwright does not await close-event listeners; `close()` owns strict cleanup.\n }\n };\n\n let context: BrowserContext | undefined;\n try {\n await cp(profileDir, runDir, { recursive: true });\n\n context = await chromium.launchPersistentContext(\n runDir,\n extensionContextOptions(extensionPath, headless),\n );\n const launched = context;\n launched.on(\"close\", handleContextClose);\n\n await definition.prepareContext?.(launched);\n const home = await definition.reachUnlockScreen(launched, extensionId);\n await definition.unlock(home, setup.password);\n await closeStrayPages(launched, home);\n\n return {\n close: async () => {\n launched.off(\"close\", handleContextClose);\n await closeLaunch(launched, runDir);\n },\n context: launched,\n wallet: createWallet({\n context: launched,\n definition,\n extensionId,\n home,\n password: setup.password,\n }),\n };\n } catch (error) {\n context?.off(\"close\", handleContextClose);\n await context?.close().catch(() => {});\n await removeRunDir().catch(() => {});\n throw error;\n }\n};\n\nexport { closeLaunch };\n","import { test as base } from \"@playwright/test\";\n\nimport { type LaunchedWallet, launchWallet } from \"./internal/launch\";\nimport type { Wallet, WalletSetup } from \"./types\";\n\nexport type WalletFixtures = {\n wallet: Wallet;\n};\n\n/** One launch per test, which the `context` and `wallet` fixtures then read from. */\ntype LaunchFixture = {\n walletLaunch: LaunchedWallet;\n};\n\n/** Create Playwright fixtures backed by one unlocked wallet launch per test. */\nexport const createWalletFixtures = (setup: WalletSetup) =>\n base.extend<LaunchFixture & WalletFixtures>({\n context: async ({ walletLaunch }, use) => {\n await use(walletLaunch.context);\n },\n wallet: async ({ walletLaunch }, use) => {\n await use(walletLaunch.wallet);\n },\n walletLaunch: async ({ headless }, use) => {\n const launched = await launchWallet(setup, { headless });\n await use(launched);\n await launched.close();\n },\n });\n"],"mappings":"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"}
@@ -146,8 +146,13 @@ type Wallet = {
146
146
  confirmSignature: () => Promise<void>;
147
147
  /** Approve a pending transaction request popup. */
148
148
  confirmTransaction: () => Promise<void>;
149
- /** Approve a pending connection request popup. Resolves quietly if the wallet auto-approved. */
150
- connectToDapp: () => Promise<void>;
149
+ /**
150
+ * Approve a pending connection request popup. Pass `{ optional: true }` for a site the wallet may
151
+ * already trust, where connecting produces no popup at all.
152
+ */
153
+ connectToDapp: (options?: {
154
+ optional?: boolean;
155
+ }) => Promise<void>;
151
156
  /** The loaded extension id. */
152
157
  readonly extensionId: string;
153
158
  /**
@@ -170,4 +175,4 @@ type Wallet = {
170
175
  };
171
176
  //#endregion
172
177
  export { WalletSetup as a, WalletKind as i, Wallet as n, WalletDefinition as r, Ecosystem as t };
173
- //# sourceMappingURL=types-CFQpWZhv.d.mts.map
178
+ //# sourceMappingURL=types-BXDYOEaq.d.mts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walletwright/core",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Playwright wallet automation for MetaMask, Phantom, Rabby, Solflare, and Slush across EVM, Solana, and Sui. Connect and sign in real browser extensions.",
5
5
  "keywords": [
6
6
  "e2e",
@@ -57,16 +57,16 @@
57
57
  "dependencies": {
58
58
  "adm-zip": "^0.6.0",
59
59
  "classic-level": "^3.0.0",
60
- "zod": "^4.4.3"
60
+ "zod": "^4.5.4"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@playwright/test": "1.62.1",
64
64
  "@types/adm-zip": "^0.5.7",
65
- "@types/node": "^26.2.0",
65
+ "@types/node": "^26.4.0",
66
66
  "prool": "^0.2.14",
67
67
  "typescript": "^7.0.2",
68
- "viem": "^2.55.11",
69
- "vitest": "^4.1.10",
68
+ "viem": "^2.56.0",
69
+ "vitest": "^4.1.11",
70
70
  "@repo/config-vitest": "0.0.0",
71
71
  "@repo/typescript-config": "0.0.0"
72
72
  },