@walletwright/core 0.3.0 → 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,17 @@
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
+
9
+ ## 0.3.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 28c935f: Fix the Slush onboarding gate, which could never pass. It waited for `dataLayerMigrated`/`dataLayerMigrationStartedAt` on the preferences row, and current Slush (26.18.1) writes neither field, so every cache build failed after 60s regardless of the timeout. The gate now waits for what the cache actually needs: an account in `signaldb-accounts` and a preferences row whose `currentAccountId` selects it.
14
+
3
15
  ## 0.3.0
4
16
 
5
17
  ### Minor Changes
@@ -1,10 +1,10 @@
1
- import { mkdir, mkdtemp, readdir, rename, rm, stat } from "node:fs/promises";
1
+ import { access, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { chromium } from "@playwright/test";
4
4
  import { createHash } from "node:crypto";
5
- import { existsSync, readFileSync, realpathSync } from "node:fs";
6
5
  import AdmZip from "adm-zip";
7
6
  import { ClassicLevel } from "classic-level";
7
+ import { z } from "zod";
8
8
 
9
9
  //#region src/internal/download.ts
10
10
  const ZIP_SIGNATURE = Buffer.from([
@@ -19,13 +19,22 @@ const ZIP_SIGNATURE = Buffer.from([
19
19
  * never collide with a wallet's `name`.
20
20
  */
21
21
  const STAGING_PREFIX = ".staging-";
22
+ const pathExists$1 = async (target) => {
23
+ try {
24
+ await stat(target);
25
+ return true;
26
+ } catch (error) {
27
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
28
+ throw error;
29
+ }
30
+ };
22
31
  /** Download and validate an extension archive, then publish its extraction atomically from staging. */
23
32
  const downloadAndExtractExtension = async (options) => {
24
33
  const { cacheDir, kind, name, sha256, url } = options;
25
34
  const cacheRoot = path.resolve(cacheDir);
26
35
  const outDir = path.resolve(cacheDir, name);
27
36
  if (outDir === cacheRoot || !outDir.startsWith(cacheRoot + path.sep)) throw new Error(`[walletwright] invalid extension name: ${name}`);
28
- if (existsSync(path.join(outDir, "manifest.json"))) return outDir;
37
+ if (await pathExists$1(path.join(outDir, "manifest.json"))) return outDir;
29
38
  await mkdir(cacheDir, { recursive: true });
30
39
  const response = await fetch(url, { redirect: "follow" });
31
40
  if (!response.ok) throw new Error(`[walletwright] failed to download ${url}: ${response.status} ${response.statusText}`);
@@ -48,8 +57,8 @@ const downloadAndExtractExtension = async (options) => {
48
57
  if (target !== staging && !target.startsWith(staging + path.sep)) throw new Error(`[walletwright] refusing to extract ${entry.entryName}: escapes ${outDir}`);
49
58
  }
50
59
  zip.extractAllTo(staging, true);
51
- if (!existsSync(path.join(staging, "manifest.json"))) throw new Error(`[walletwright] extracted ${name} but no manifest.json found in ${outDir}`);
52
- if (existsSync(path.join(outDir, "manifest.json"))) return outDir;
60
+ if (!await pathExists$1(path.join(staging, "manifest.json"))) throw new Error(`[walletwright] extracted ${name} but no manifest.json found in ${outDir}`);
61
+ if (await pathExists$1(path.join(outDir, "manifest.json"))) return outDir;
53
62
  await rm(outDir, {
54
63
  force: true,
55
64
  recursive: true
@@ -356,20 +365,21 @@ const DEFAULT_NOTIFICATION_MATCH = "notification.html";
356
365
  /** Stable per-setup profile directory name, so the same wallet+seed+password reuses one cache. */
357
366
  const profileKey = (setup) => createHash("sha256").update(`${setup.wallet}:${setup.version ?? "default"}:${setup.seedPhrase}:${setup.password}`).digest("hex").slice(0, 20);
358
367
  /** Mirror Chrome's extension-id derivation; resolve symlinks because Chrome hashes the real path. */
359
- const extensionIdFromPath = (extensionPath) => {
368
+ const extensionIdFromPath = async (extensionPath) => {
360
369
  const resolved = path.resolve(extensionPath);
361
- const abs = (() => {
362
- try {
363
- return realpathSync(resolved);
364
- } catch {
365
- return resolved;
366
- }
367
- })();
370
+ let abs = resolved;
371
+ try {
372
+ abs = await realpath(resolved);
373
+ } catch {
374
+ abs = resolved;
375
+ }
368
376
  let key;
369
377
  try {
370
- const manifest = JSON.parse(readFileSync(path.join(abs, "manifest.json"), "utf8"));
371
- if (typeof manifest === "object" && manifest !== null && "key" in manifest) key = typeof manifest.key === "string" ? manifest.key : void 0;
372
- } catch {}
378
+ const text = await readFile(path.join(abs, "manifest.json"), "utf8");
379
+ key = z.object({ key: z.string().optional() }).parse(JSON.parse(text)).key;
380
+ } catch {
381
+ key = void 0;
382
+ }
373
383
  const source = key === void 0 || key === "" ? Buffer.from(abs, "utf8") : Buffer.from(key, "base64");
374
384
  const hex = createHash("sha256").update(source).digest("hex").slice(0, 32);
375
385
  return Array.from(hex, (nibble) => String.fromCodePoint(97 + Number.parseInt(nibble, 16))).join("");
@@ -423,7 +433,12 @@ const extensionStateDir = (profileDir, extensionId) => path.join(profileDir, "De
423
433
 
424
434
  //#endregion
425
435
  //#region src/wallets/metamask/onboarding-patch.ts
426
- const asState = (value) => typeof value === "object" && value !== null ? { ...value } : void 0;
436
+ const onboardingStateSchema = z.record(z.string(), z.json());
437
+ const parseState = (text, label) => {
438
+ const result = onboardingStateSchema.safeParse(JSON.parse(text));
439
+ if (!result.success) throw new Error(`[walletwright] MetaMask's ${label} state is not an object`, { cause: result.error });
440
+ return result.data;
441
+ };
427
442
  const markOnboarded = (onboarding) => ({
428
443
  ...onboarding,
429
444
  completedOnboarding: true,
@@ -433,7 +448,11 @@ const markOnboarded = (onboarding) => ({
433
448
  /** Patch both MetaMask 13.x onboarding layouts while the browser has released LevelDB. */
434
449
  const markMetaMaskOnboarded = async (profileDir, extensionId) => {
435
450
  const dbDir = extensionStateDir(profileDir, extensionId);
436
- if (!existsSync(dbDir)) throw new Error(`[walletwright] MetaMask wrote no extension state to ${dbDir}`);
451
+ try {
452
+ await access(dbDir);
453
+ } catch (error) {
454
+ throw new Error(`[walletwright] MetaMask wrote no extension state to ${dbDir}`, { cause: error });
455
+ }
437
456
  const db = new ClassicLevel(dbDir, {
438
457
  createIfMissing: false,
439
458
  keyEncoding: "utf8",
@@ -443,19 +462,18 @@ const markMetaMaskOnboarded = async (profileDir, extensionId) => {
443
462
  await db.open();
444
463
  const perController = await db.get("OnboardingController");
445
464
  if (perController !== void 0 && perController !== "") {
446
- const onboarding = asState(JSON.parse(perController));
447
- if (onboarding === void 0) throw new Error(`[walletwright] MetaMask's OnboardingController state is not an object`);
465
+ const onboarding = parseState(perController, "OnboardingController");
448
466
  if (onboarding.completedOnboarding !== true) await db.put("OnboardingController", JSON.stringify(markOnboarded(onboarding)));
449
467
  return;
450
468
  }
451
469
  const raw = await db.get("data");
452
470
  if (raw === void 0 || raw === "") throw new Error(`[walletwright] MetaMask persisted no onboarding state in ${dbDir} (neither an OnboardingController key nor a data key)`);
453
- const state = asState(JSON.parse(raw));
454
- if (state === void 0) throw new Error(`[walletwright] MetaMask's "data" state is not an object`);
455
- const nested = asState(state.data);
456
- const controller = asState((nested ?? state).OnboardingController);
457
- if (controller === void 0) throw new Error(`[walletwright] MetaMask's "data" state holds no OnboardingController`);
458
- const patched = markOnboarded(controller);
471
+ const state = parseState(raw, "\"data\"");
472
+ const nestedResult = onboardingStateSchema.safeParse(state.data);
473
+ const nested = nestedResult.success ? nestedResult.data : void 0;
474
+ const controllerResult = onboardingStateSchema.safeParse((nested ?? state).OnboardingController);
475
+ if (!controllerResult.success) throw new Error(`[walletwright] MetaMask's "data" state holds no OnboardingController`);
476
+ const patched = markOnboarded(controllerResult.data);
459
477
  await db.put("data", JSON.stringify(nested === void 0 ? {
460
478
  ...state,
461
479
  OnboardingController: patched
@@ -699,7 +717,53 @@ const rabby = {
699
717
  const SLUSH_EXTENSION_ID = "opcgpfmipidbgpenhmajoajpbobppdil";
700
718
  const HOME_ROUTE = "#/tokens";
701
719
  const IMPORT_HOME_TIMEOUT_MS = 3e4;
720
+ const PERSIST_TIMEOUT_MS = 6e4;
721
+ const ACCOUNTS_DATABASE = "signaldb-accounts";
722
+ const PREFERENCES_DATABASE = "signaldb-preferences";
723
+ const ITEMS_STORE = "items";
702
724
  const atHome = async (page) => await page.evaluate(() => globalThis.location.hash).catch(() => "") === HOME_ROUTE;
725
+ /**
726
+ * Slush writes the imported account, its provider and the preferences row that selects it in
727
+ * separate transactions, so reaching the home route does not mean the profile is durable yet.
728
+ * Caching between those writes yields a profile that unlocks to an account-less wallet.
729
+ */
730
+ const walletPersisted = (page) => page.evaluate(async ({ accountsDatabase, preferencesDatabase, storeName }) => {
731
+ const readItems = async (databaseName) => {
732
+ if (!(await indexedDB.databases()).some((database) => database.name === databaseName)) return [];
733
+ const database = await new Promise((resolve, reject) => {
734
+ const request = indexedDB.open(databaseName);
735
+ request.addEventListener("success", () => {
736
+ resolve(request.result);
737
+ });
738
+ request.addEventListener("error", () => {
739
+ reject(request.error ?? /* @__PURE__ */ new Error(`Unable to open ${databaseName}`));
740
+ });
741
+ });
742
+ try {
743
+ if (!database.objectStoreNames.contains(storeName)) return [];
744
+ const request = database.transaction(storeName, "readonly").objectStore(storeName).getAll();
745
+ return await new Promise((resolve, reject) => {
746
+ request.addEventListener("success", () => {
747
+ resolve(request.result.filter((item) => typeof item === "object" && item !== null));
748
+ });
749
+ request.addEventListener("error", () => {
750
+ reject(request.error ?? /* @__PURE__ */ new Error(`Unable to read ${storeName}`));
751
+ });
752
+ });
753
+ } finally {
754
+ database.close();
755
+ }
756
+ };
757
+ const accounts = await readItems(accountsDatabase);
758
+ const accountIds = /* @__PURE__ */ new Set();
759
+ for (const account of accounts) if ("id" in account && typeof account.id === "string") accountIds.add(account.id);
760
+ if (accountIds.size === 0) return false;
761
+ return (await readItems(preferencesDatabase)).some((preference) => "currentAccountId" in preference && typeof preference.currentAccountId === "string" && accountIds.has(preference.currentAccountId));
762
+ }, {
763
+ accountsDatabase: ACCOUNTS_DATABASE,
764
+ preferencesDatabase: PREFERENCES_DATABASE,
765
+ storeName: ITEMS_STORE
766
+ });
703
767
  const fclick = async (page, text, timeoutMs = 15e3) => {
704
768
  const target = page.getByText(text, { exact: true }).first();
705
769
  if (!await target.waitFor({
@@ -753,6 +817,11 @@ const slush = {
753
817
  message: `Slush import never reached the wallet home (${HOME_ROUTE})`,
754
818
  timeoutMs: IMPORT_HOME_TIMEOUT_MS
755
819
  });
820
+ await waitUntilOrThrow(() => walletPersisted(page), {
821
+ intervalMs: 1e3,
822
+ message: "Slush import never persisted an account and its selected preference",
823
+ timeoutMs: PERSIST_TIMEOUT_MS
824
+ });
756
825
  },
757
826
  notificationMatch: "isPopup=1",
758
827
  onboardingPage: "index.html",
@@ -868,13 +937,12 @@ const hasPersistedState = async (profileDir, extensionId) => {
868
937
  */
869
938
  const BUILD_PREFIX = ".building-";
870
939
  const PREVIOUS_SUFFIX = ".previous";
871
- const isErrorCode = (error, code) => error instanceof Error && "code" in error && error.code === code;
872
940
  const pathExists = async (target) => {
873
941
  try {
874
942
  await stat(target);
875
943
  return true;
876
944
  } catch (error) {
877
- if (isErrorCode(error, "ENOENT")) return false;
945
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
878
946
  throw error;
879
947
  }
880
948
  };
@@ -891,7 +959,7 @@ const restorePreviousProfile = async (profileDir) => {
891
959
  try {
892
960
  await rename(previous, profileDir);
893
961
  } catch (error) {
894
- if (!isErrorCode(error, "ENOENT")) throw error;
962
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
895
963
  }
896
964
  };
897
965
  const publishProfile = async (staging, profileDir) => {
@@ -902,7 +970,7 @@ const publishProfile = async (staging, profileDir) => {
902
970
  await rename(profileDir, previous);
903
971
  movedPrevious = true;
904
972
  } catch (error) {
905
- if (!isErrorCode(error, "ENOENT")) throw error;
973
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
906
974
  }
907
975
  try {
908
976
  await rename(staging, profileDir);
@@ -921,20 +989,23 @@ const publishProfile = async (staging, profileDir) => {
921
989
  recursive: true
922
990
  });
923
991
  };
924
- /** Build in staging and retain the prior cache until the new profile publishes successfully. */
925
- const buildCache = async (setup, options = {}) => {
992
+ const defaultBuildCacheDependencies = {
993
+ launchPersistentContext: chromium.launchPersistentContext.bind(chromium),
994
+ prepareExtension: (definition, cacheDir, version) => definition.prepareExtension(cacheDir, version)
995
+ };
996
+ const buildCacheWithDependencies = async (setup, options = {}, dependencies = defaultBuildCacheDependencies) => {
926
997
  const definition = wallets[setup.wallet];
927
998
  const cacheDir = path.resolve(setup.cacheDir ?? ".walletwright");
928
- const extensionPath = await definition.prepareExtension(cacheDir, setup.version);
999
+ const extensionPath = await dependencies.prepareExtension(definition, cacheDir, setup.version);
929
1000
  const profileDir = path.join(cacheDir, profileKey(setup));
930
1001
  await restorePreviousProfile(profileDir);
931
1002
  await mkdir(cacheDir, { recursive: true });
932
1003
  const staging = await mkdtemp(path.join(cacheDir, BUILD_PREFIX));
933
1004
  let context;
934
1005
  try {
935
- context = await chromium.launchPersistentContext(staging, extensionContextOptions(extensionPath, options.headless === true));
1006
+ context = await dependencies.launchPersistentContext(staging, extensionContextOptions(extensionPath, options.headless === true));
936
1007
  await definition.prepareContext?.(context);
937
- const extensionId = extensionIdFromPath(extensionPath);
1008
+ const extensionId = await extensionIdFromPath(extensionPath);
938
1009
  const page = context.pages().find((candidate) => candidate.url() === "about:blank") ?? await context.newPage();
939
1010
  await gotoWithRetry(page, `chrome-extension://${extensionId}/${definition.onboardingPage}`, {
940
1011
  label: `${definition.extensionName} onboarding page`,
@@ -962,7 +1033,8 @@ const buildCache = async (setup, options = {}) => {
962
1033
  });
963
1034
  }
964
1035
  };
1036
+ const buildCache = (setup, options) => buildCacheWithDependencies(setup, options);
965
1037
 
966
1038
  //#endregion
967
1039
  export { walletKindsByEcosystem as a, DEFAULT_NOTIFICATION_MATCH as c, placeApprovalWindow as d, profileKey as f, isWalletKind as i, extensionIdFromPath as l, waitUntil as m, restorePreviousProfile as n, wallets as o, formatTimeout as p, extensionContextOptions as r, DEFAULT_CACHE_DIR as s, buildCache as t, findNotificationPopup as u };
968
- //# sourceMappingURL=cache-CHP-6eU0.mjs.map
1040
+ //# sourceMappingURL=cache-BaWu5vyM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-BaWu5vyM.mjs","names":["pathExists","add","rename","NAVIGATION_TIMEOUT_MS","importWallet","unlockScreen","importWallet"],"sources":["../src/internal/download.ts","../src/wallets/metamask/actions/accounts.ts","../src/wallets/metamask/actions/network.ts","../src/internal/wait.ts","../src/internal/unlock-screen.ts","../src/wallets/metamask/onboarding.ts","../src/wallets/metamask/actions/settings.ts","../src/wallets/metamask/approve.ts","../src/internal/utils.ts","../src/wallets/metamask/onboarding-patch.ts","../src/wallets/metamask.ts","../src/wallets/phantom.ts","../src/wallets/rabby.ts","../src/wallets/slush.ts","../src/wallets/solflare.ts","../src/wallets/index.ts","../src/internal/chromium.ts","../src/internal/cache.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, mkdtemp, rename, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport AdmZip from \"adm-zip\";\n\nconst ZIP_SIGNATURE = Buffer.from([80, 75, 3, 4]);\n\n/**\n * Prefix for in-progress extractions. It has to live inside `cacheDir` so publishing is a `rename`\n * on one filesystem (a cross-device rename fails with EXDEV), and it starts with a dot so it can\n * never collide with a wallet's `name`.\n */\nconst STAGING_PREFIX = \".staging-\";\n\nconst pathExists = async (target: string): Promise<boolean> => {\n try {\n await stat(target);\n return true;\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return false;\n }\n throw error;\n }\n};\n\n/** Download and validate an extension archive, then publish its extraction atomically from staging. */\nexport const downloadAndExtractExtension = async (options: {\n cacheDir: string;\n kind: \"zip\" | \"crx\";\n name: string;\n /**\n * Expected sha256 of the downloaded bytes. Required, and `undefined` only where the bytes genuinely\n * cannot be pinned, so adding a download site forces a decision instead of quietly trusting it.\n */\n sha256: string | undefined;\n url: string;\n}): Promise<string> => {\n const { cacheDir, kind, name, sha256, url } = options;\n const cacheRoot = path.resolve(cacheDir);\n const outDir = path.resolve(cacheDir, name);\n if (outDir === cacheRoot || !outDir.startsWith(cacheRoot + path.sep)) {\n throw new Error(`[walletwright] invalid extension name: ${name}`);\n }\n if (await pathExists(path.join(outDir, \"manifest.json\"))) {\n return outDir;\n }\n\n await mkdir(cacheDir, { recursive: true });\n\n const response = await fetch(url, { redirect: \"follow\" });\n if (!response.ok) {\n throw new Error(\n `[walletwright] failed to download ${url}: ${response.status} ${response.statusText}`,\n );\n }\n const bytes = Buffer.from(await response.arrayBuffer());\n\n if (sha256 !== undefined) {\n const actual = createHash(\"sha256\").update(bytes).digest(\"hex\");\n if (actual !== sha256.toLowerCase()) {\n throw new Error(\n `[walletwright] ${name} failed integrity check: expected ${sha256}, got ${actual}`,\n );\n }\n }\n\n let zipBytes = bytes;\n if (kind === \"crx\") {\n const start = bytes.indexOf(ZIP_SIGNATURE);\n if (start === -1) {\n throw new Error(`[walletwright] ${url} is not a valid CRX (no ZIP header found)`);\n }\n zipBytes = bytes.subarray(start);\n }\n\n const staging = await mkdtemp(path.join(cacheRoot, STAGING_PREFIX));\n try {\n const zip = new AdmZip(zipBytes);\n for (const entry of zip.getEntries()) {\n const target = path.resolve(staging, entry.entryName);\n if (target !== staging && !target.startsWith(staging + path.sep)) {\n throw new Error(`[walletwright] refusing to extract ${entry.entryName}: escapes ${outDir}`);\n }\n }\n zip.extractAllTo(staging, /* overwrite */ true);\n\n if (!(await pathExists(path.join(staging, \"manifest.json\")))) {\n throw new Error(`[walletwright] extracted ${name} but no manifest.json found in ${outDir}`);\n }\n\n if (await pathExists(path.join(outDir, \"manifest.json\"))) {\n return outDir; // another worker published while we were downloading\n }\n await rm(outDir, { force: true, recursive: true });\n await rename(staging, outDir);\n return outDir;\n } finally {\n await rm(staging, { force: true, recursive: true }).catch(() => {});\n }\n};\n\n/** Build the Chrome Web Store CRX download URL for an extension id. */\nexport const chromeWebStoreCrxUrl = (extensionId: string): string =>\n `https://clients2.google.com/service/update2/crx?response=redirect&prodversion=130.0&acceptformat=crx2,crx3&x=id%3D${extensionId}%26uc`;\n\n/** Download and extract a Chrome Web Store extension (latest) into `<cacheDir>/<name>`. */\nexport const prepareWebStoreExtension = (options: {\n cacheDir: string;\n extensionId: string;\n name: string;\n}): Promise<string> =>\n downloadAndExtractExtension({\n cacheDir: options.cacheDir,\n kind: \"crx\",\n name: options.name,\n sha256: undefined,\n url: chromeWebStoreCrxUrl(options.extensionId),\n });\n","import type { Page } from \"@playwright/test\";\n\nimport type { AccountActions, WalletActionContext } from \"../../../types\";\n\nconst openAccountMenu = async (home: Page): Promise<void> => {\n await home.getByTestId(\"account-menu-icon\").click();\n await home.getByTestId(\"add-multichain-account-button\").waitFor({ state: \"visible\" });\n};\n\n/** The menu is a popover with no close affordance; going home is the reliable way out. */\nconst closeAccountMenu = async (home: Page): Promise<void> => {\n await home.goto(`${home.url().split(\"#\")[0]}#/`);\n await home.getByTestId(\"account-menu-icon\").waitFor({ state: \"visible\" });\n};\n\nconst add = async ({ home }: WalletActionContext): Promise<void> => {\n await openAccountMenu(home);\n const cells = home.getByTestId(\"account-cell-avatar\");\n const before = await cells.count();\n await home.getByTestId(\"add-multichain-account-button\").click();\n await cells.nth(before).waitFor({ state: \"visible\", timeout: 15_000 });\n await closeAccountMenu(home);\n};\n\nconst importPrivateKey = async (\n { home }: WalletActionContext,\n privateKey: string,\n): Promise<void> => {\n await openAccountMenu(home);\n await home.getByTestId(\"account-list-add-wallet-button\").click();\n const chooseImport = home.getByTestId(\"choose-wallet-type-import-account\");\n await chooseImport.waitFor({ state: \"visible\" });\n await chooseImport.click();\n const confirm = home.getByTestId(\"import-account-confirm-button\");\n await confirm.waitFor({ state: \"visible\" });\n await home.locator(\"input\").first().fill(privateKey);\n await confirm.click();\n await confirm.waitFor({ state: \"hidden\", timeout: 15_000 });\n await closeAccountMenu(home);\n};\n\nconst rename = async (\n { home }: WalletActionContext,\n options: { index: number; name: string },\n): Promise<void> => {\n await openAccountMenu(home);\n await home.getByTestId(\"multichain-account-cell-end-accessory\").nth(options.index).click();\n await home.getByTestId(\"multichain-account-menu-item-rename\").click();\n await home.getByTestId(\"account-name-input\").locator(\"input\").fill(options.name);\n await home.getByRole(\"button\", { name: \"Confirm\" }).click();\n await closeAccountMenu(home);\n};\n\nconst switchTo = async ({ home }: WalletActionContext, index: number): Promise<void> => {\n await openAccountMenu(home);\n await home.getByTestId(\"account-cell-avatar\").nth(index).click();\n const menuClosed = await home\n .getByTestId(\"add-multichain-account-button\")\n .waitFor({ state: \"hidden\", timeout: 10_000 })\n .then(() => true)\n .catch(() => false);\n if (!menuClosed) {\n throw new Error(`[walletwright] accounts.switch(${index}) did not switch (menu stayed open)`);\n }\n};\n\nexport const accounts: AccountActions = {\n add,\n importPrivateKey,\n rename,\n switch: switchTo,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport type { NetworkActions, NetworkConfig, WalletActionContext } from \"../../../types\";\n\nconst closeButton = (home: Page) =>\n home.getByTestId(\"modal-header-close-button\").filter({ visible: true }).first();\n\nconst openNetworkManager = async (home: Page): Promise<void> => {\n await home.getByTestId(\"sort-by-networks\").click();\n await closeButton(home).waitFor({ state: \"visible\" });\n};\n\nconst add = async ({ home }: WalletActionContext, config: NetworkConfig): Promise<void> => {\n await openNetworkManager(home);\n\n await home.getByText(\"Custom\", { exact: true }).first().click();\n await home.getByRole(\"button\", { name: \"Add custom network\" }).first().click();\n\n await home.getByTestId(\"network-form-network-name\").fill(config.name);\n await home.getByTestId(\"network-form-chain-id\").fill(String(config.chainId));\n await home.getByTestId(\"network-form-ticker-input\").fill(config.symbol);\n\n await home.getByTestId(\"test-add-rpc-drop-down\").click();\n await home.getByText(\"Add RPC URL\", { exact: true }).first().click();\n await home.getByTestId(\"rpc-url-input-test\").fill(config.rpcUrl);\n await home.getByTestId(\"page-container-footer-next\").click();\n\n await home.getByTestId(\"network-form-network-name\").waitFor();\n await home.getByTestId(\"page-container-footer-next\").click();\n\n await closeButton(home).click();\n await closeButton(home).waitFor({ state: \"hidden\" });\n};\n\nexport const network: NetworkActions = {\n add,\n};\n","import type { Page } from \"@playwright/test\";\n\nconst DEFAULT_INTERVAL_MS = 200;\nconst DEFAULT_NAVIGATION_INTERVAL_MS = 1000;\n\nexport type WaitOptions = {\n /** Gap between checks. */\n intervalMs?: number;\n /** Total budget. `check` runs at least once, and never again once the budget has elapsed. */\n timeoutMs: number;\n};\n\nexport const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n\n/** Render a timeout for an error message, so what a failure claims matches what it waited. */\nexport const formatTimeout = (ms: number): string => `${Math.round(ms / 100) / 10}s`;\n\n/** Poll until `check` returns a value, or `undefined` when the timeout expires. */\nexport const waitUntil = async <T>(\n check: () => Promise<T | false | undefined> | T | false | undefined,\n { intervalMs = DEFAULT_INTERVAL_MS, timeoutMs }: WaitOptions,\n): Promise<T | undefined> => {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const result = await check();\n if (result !== undefined && result !== false) {\n return result;\n }\n if (Date.now() >= deadline) {\n return undefined;\n }\n await sleep(intervalMs);\n }\n};\n\nexport const waitUntilOrThrow = async <T>(\n check: () => Promise<T | false | undefined> | T | false | undefined,\n options: WaitOptions & { message: string },\n): Promise<T> => {\n const result = await waitUntil(check, options);\n if (result === undefined) {\n throw new Error(\n `[walletwright] ${options.message} (gave up after ${formatTimeout(options.timeoutMs)})`,\n );\n }\n return result;\n};\n\n/**\n * A `chrome-extension://` URL fails with `ERR_BLOCKED_BY_CLIENT` until the extension registers,\n * which takes seconds after launch, so reaching an extension page is a poll rather than a one-shot.\n */\nexport const gotoWithRetry = async (\n page: Page,\n url: string,\n {\n intervalMs = DEFAULT_NAVIGATION_INTERVAL_MS,\n label,\n timeoutMs,\n }: WaitOptions & { label: string },\n): Promise<void> => {\n await waitUntilOrThrow(\n () =>\n page\n .goto(url, { waitUntil: \"domcontentloaded\" })\n .then(() => true)\n .catch(() => undefined),\n { intervalMs, message: `${label} never loaded (${url})`, timeoutMs },\n );\n};\n","import type { BrowserContext, Locator, Page } from \"@playwright/test\";\n\nimport { formatTimeout, gotoWithRetry, waitUntil } from \"./wait\";\n\nconst PASSWORD_FIELD = 'input[type=\"password\"]';\n\nconst NAVIGATION_TIMEOUT_MS = 15_000;\nconst FIRST_SETTLE_TIMEOUT_MS = 20_000;\nconst RELOAD_ATTEMPTS = 5;\nconst RELOAD_SETTLE_TIMEOUT_MS = 5000;\nconst PROBE_INTERVAL_MS = 500;\nconst PASSWORD_CLEARED_TIMEOUT_MS = 15_000;\n/**\n * Short on purpose. `unlock` is normally handed a page `reachUnlockScreen` already settled, so this\n * budget only matters when it is called on its own (the `settings.unlock` path), where a wallet with a\n * late-mounting UI needs a poll rather than a single read but a dead page should still fail fast.\n */\nconst UNLOCK_SETTLE_TIMEOUT_MS = 5000;\n\ntype UnlockScreenOptions = {\n /** Extension-relative page that renders the unlock screen (e.g. `home.html`). */\n entry: string;\n /** Detect a warm launch that is already unlocked. */\n isUnlocked?: (page: Page) => Promise<boolean>;\n /** Submit the filled password. Defaults to pressing Enter in the field. */\n submit?: (page: Page, field: Locator) => Promise<void>;\n wallet: string;\n};\n\ntype UnlockScreen = {\n reachUnlockScreen: (context: BrowserContext, extensionId: string) => Promise<Page>;\n unlock: (page: Page, password: string) => Promise<void>;\n};\n\n/**\n * The shared unlock flow: open the wallet's own page, wait for it to settle into a state we can\n * name, and type the password. Every wallet differs only in its entry page, how it submits, and\n * whether it can come back already unlocked.\n */\nexport const createUnlockScreen = ({\n entry,\n isUnlocked,\n submit,\n wallet,\n}: UnlockScreenOptions): UnlockScreen => {\n const settled = async (page: Page): Promise<\"locked\" | \"unlocked\" | undefined> => {\n const locked = await page\n .locator(PASSWORD_FIELD)\n .isVisible()\n .catch(() => false);\n if (locked) {\n return \"locked\";\n }\n const unlocked = await isUnlocked?.(page).catch(() => false);\n return unlocked === true ? \"unlocked\" : undefined;\n };\n\n const settleWithin = (page: Page, timeoutMs: number) =>\n waitUntil(() => settled(page), { intervalMs: PROBE_INTERVAL_MS, timeoutMs });\n\n const reachUnlockScreen = async (context: BrowserContext, extensionId: string): Promise<Page> => {\n const page = await context.newPage();\n await gotoWithRetry(page, `chrome-extension://${extensionId}/${entry}`, {\n label: `${wallet} ${entry}`,\n timeoutMs: NAVIGATION_TIMEOUT_MS,\n });\n\n let state = await settleWithin(page, FIRST_SETTLE_TIMEOUT_MS);\n for (let attempt = 0; attempt < RELOAD_ATTEMPTS && state === undefined; attempt++) {\n await page.reload().catch(() => {});\n state = await settleWithin(page, RELOAD_SETTLE_TIMEOUT_MS);\n }\n if (state === undefined) {\n throw new Error(\n `[walletwright] ${wallet} never reached its unlock screen (gave up after ${formatTimeout(\n FIRST_SETTLE_TIMEOUT_MS + RELOAD_ATTEMPTS * RELOAD_SETTLE_TIMEOUT_MS,\n )})`,\n );\n }\n return page;\n };\n\n const unlock = async (page: Page, password: string): Promise<void> => {\n // Through `settleWithin`, so there is one answer to \"what state is this page in\" and it waits.\n const state = await settleWithin(page, UNLOCK_SETTLE_TIMEOUT_MS);\n if (state === \"unlocked\") {\n return;\n }\n if (state === undefined) {\n throw new Error(\n `[walletwright] ${wallet} showed neither an unlock screen nor an unlocked wallet`,\n );\n }\n\n const field = page.locator(PASSWORD_FIELD);\n await field.fill(password);\n await (submit === undefined ? field.press(\"Enter\") : submit(page, field));\n const cleared = await field\n .waitFor({ state: \"hidden\", timeout: PASSWORD_CLEARED_TIMEOUT_MS })\n .then(() => true)\n .catch(() => false);\n if (!cleared) {\n throw new Error(\n `[walletwright] ${wallet} unlock failed (password screen still visible after ${formatTimeout(\n PASSWORD_CLEARED_TIMEOUT_MS,\n )})`,\n );\n }\n };\n\n return { reachUnlockScreen, unlock };\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { createUnlockScreen } from \"../../internal/unlock-screen\";\nimport { waitUntil } from \"../../internal/wait\";\n\nconst SRP_RETYPE_TIMEOUT_MS = 40_000;\nconst SRP_CONFIRM_TIMEOUT_MS = 5000;\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"home.html\",\n wallet: \"MetaMask\",\n});\n\nexport const importWallet = async (\n page: Page,\n seedPhrase: string,\n password: string,\n): Promise<void> => {\n await page.getByTestId(\"onboarding-import-wallet\").click();\n await page.getByTestId(\"onboarding-import-with-srp-button\").click();\n\n const srpField = page.getByTestId(\"srp-input-import__srp-note\");\n const confirmEnabled = page.locator('[data-testid=\"import-srp-confirm\"]:not([disabled])');\n const accepted = await waitUntil(\n async () => {\n await srpField.click();\n await srpField.fill(\"\");\n await srpField.pressSequentially(seedPhrase, { delay: 50 });\n return confirmEnabled\n .waitFor({ state: \"visible\", timeout: SRP_CONFIRM_TIMEOUT_MS })\n .then(() => true)\n .catch(() => undefined);\n },\n { intervalMs: 0, timeoutMs: SRP_RETYPE_TIMEOUT_MS },\n );\n if (accepted === undefined) {\n throw new Error(\"[walletwright] MetaMask rejected the seed phrase (confirm stayed disabled)\");\n }\n await confirmEnabled.click();\n\n await page.getByTestId(\"create-password-new-input\").fill(password);\n await page.getByTestId(\"create-password-confirm-input\").fill(password);\n await page.getByTestId(\"create-password-terms\").click();\n await page.locator('[data-testid=\"create-password-submit\"]:not([disabled])').click();\n\n await page\n .getByTestId(\"passkey-maybe-later-button\")\n .click({ timeout: 10_000 })\n .catch(() => {});\n await page\n .getByTestId(\"metametrics-i-agree\")\n .click({ timeout: 10_000 })\n .catch(() => {});\n};\n\nexport { reachUnlockScreen, unlock };\n","import type { SettingsActions } from \"../../../types\";\nimport { unlock as unlockScreen } from \"../onboarding\";\n\nexport const settings: SettingsActions = {\n lock: async ({ home }) => {\n await home.getByTestId(\"account-options-menu-button\").click();\n await home.getByTestId(\"global-menu-lock\").filter({ visible: true }).first().click();\n await home.locator('input[type=\"password\"]').waitFor({ state: \"visible\", timeout: 15_000 });\n },\n\n unlock: async ({ home, password }) => {\n await unlockScreen(home, password);\n },\n};\n","import type { Locator, Page } from \"@playwright/test\";\n\n/**\n * How long a confirm or cancel button gets to become clickable, once the page is already showing\n * one. Generous because a loaded CI runner renders MetaMask's footer slowly.\n */\nconst BUTTON_TIMEOUT_MS = 45_000;\n/** The retry only covers a notice that rendered late, so it does not need the full budget again. */\nconst RETRY_TIMEOUT_MS = 10_000;\n\n/** Snap notices intercept footer clicks and disable Accept until their content is scrolled. */\nconst acceptThirdPartyNotice = async (popup: Page): Promise<void> => {\n const modal = popup.locator(\".snap-install-warning\");\n if ((await modal.count().catch(() => 0)) === 0) {\n return;\n }\n await modal\n .locator(\"p\")\n .last()\n .scrollIntoViewIfNeeded()\n .catch(() => {});\n await modal\n .getByRole(\"button\", { name: \"Accept\" })\n .click({ timeout: 3000 })\n .catch(() => {});\n};\n\n/** Resolve the confirm test id used by standard, legacy, and Snap request footers. */\nconst confirmButton = (popup: Page): Locator =>\n popup\n .getByTestId(\"confirm-btn\")\n .or(popup.getByTestId(\"confirm-footer-button\"))\n .or(popup.locator('[data-testid=\"page-container-footer-next\"]'))\n .or(popup.locator('[data-testid$=\"-confirm-snap-footer-button\"]'))\n .first();\n\nconst cancelButton = (popup: Page): Locator =>\n popup\n .getByTestId(\"cancel-btn\")\n .or(popup.getByTestId(\"confirm-footer-cancel-button\"))\n .or(popup.locator('[data-testid=\"page-container-footer-cancel\"]'))\n .or(popup.locator('[data-testid$=\"-cancel-snap-footer-button\"]'))\n .first();\n\n/** Distinguishes a pending request from MetaMask's requestless notification home screen. */\nexport const approvalControls = (popup: Page): Locator =>\n confirmButton(popup).or(cancelButton(popup));\n\n/**\n * Click one of the popup's footer buttons. The third-party notice covers that whole footer, so both\n * confirm and cancel are intercepted while it is up, and both need the dismiss-then-retry: without it\n * a click times out against a button that is rendered and enabled, which reads as a stale selector.\n */\nconst clickFooterButton = async (popup: Page, button: Locator): Promise<void> => {\n await acceptThirdPartyNotice(popup);\n try {\n await button.click({ timeout: BUTTON_TIMEOUT_MS });\n } catch (error) {\n await acceptThirdPartyNotice(popup);\n await button.click({ timeout: RETRY_TIMEOUT_MS }).catch(() => {\n throw error;\n });\n }\n};\n\nexport const approve = (popup: Page): Promise<void> =>\n clickFooterButton(popup, confirmButton(popup));\n\n/** The cancel counterpart of `approve`; same union-by-DOM-order resolution, not written order. */\nexport const reject = (popup: Page): Promise<void> => clickFooterButton(popup, cancelButton(popup));\n","import { createHash } from \"node:crypto\";\nimport { readFile, realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { BrowserContext, Locator, Page } from \"@playwright/test\";\nimport { z } from \"zod\";\n\nimport type { WalletSetup } from \"../types\";\n\nimport { waitUntil } from \"./wait\";\n\nexport const DEFAULT_CACHE_DIR = \".walletwright\";\n\n/** MetaMask/Phantom approval-popup URL token; Slush overrides it with `notificationMatch: \"isPopup=1\"`. */\nexport const DEFAULT_NOTIFICATION_MATCH = \"notification.html\";\n\n/** Stable per-setup profile directory name, so the same wallet+seed+password reuses one cache. */\nexport const profileKey = (setup: WalletSetup): string =>\n createHash(\"sha256\")\n .update(`${setup.wallet}:${setup.version ?? \"default\"}:${setup.seedPhrase}:${setup.password}`)\n .digest(\"hex\")\n .slice(0, 20);\n\n/** Mirror Chrome's extension-id derivation; resolve symlinks because Chrome hashes the real path. */\nexport const extensionIdFromPath = async (extensionPath: string): Promise<string> => {\n const resolved = path.resolve(extensionPath);\n let abs = resolved;\n try {\n abs = await realpath(resolved);\n } catch {\n abs = resolved;\n }\n let key: string | undefined;\n try {\n const text = await readFile(path.join(abs, \"manifest.json\"), \"utf8\");\n const manifest = z.object({ key: z.string().optional() }).parse(JSON.parse(text));\n key = manifest.key;\n } catch {\n key = undefined;\n }\n const source =\n key === undefined || key === \"\" ? Buffer.from(abs, \"utf8\") : Buffer.from(key, \"base64\");\n const hex = createHash(\"sha256\").update(source).digest(\"hex\").slice(0, 32);\n return Array.from(hex, (nibble) => String.fromCodePoint(97 + Number.parseInt(nibble, 16))).join(\n \"\",\n );\n};\n\n/**\n * Approval popups open as `about:blank` and then navigate, so `waitForEvent('page', { predicate })`\n * misses them, poll the open pages instead. `match` is the URL token identifying the popup\n * (`notification.html` for MetaMask/Phantom; `isPopup=1` for Slush's single-page `index.html`).\n */\nexport const isApprovalPopup = (page: Page, extensionId: string, match: string): boolean =>\n page.url().startsWith(`chrome-extension://${extensionId}`) &&\n page.url().includes(match) &&\n !page.isClosed();\n\nconst isVisible = async (locator: Locator): Promise<boolean> => {\n try {\n return await locator.isVisible();\n } catch {\n return false; // the page can go while we ask\n }\n};\n\nconst hasVisibleButton = (page: Page): Promise<boolean> =>\n isVisible(page.locator(\"button\").first());\n\nexport const findNotificationPopup = ({\n approvalControls,\n context,\n extensionId,\n match = DEFAULT_NOTIFICATION_MATCH,\n timeoutMs = 10_000,\n}: {\n /** From `WalletDefinition.approvalControls`, for a wallet that can render its popup requestless. */\n approvalControls?: (page: Page) => Locator;\n context: BrowserContext;\n extensionId: string;\n match?: string;\n timeoutMs?: number;\n}): Promise<Page | undefined> =>\n waitUntil(\n async () => {\n const popup = context.pages().find((page) => isApprovalPopup(page, extensionId, match));\n if (!popup) {\n return undefined;\n }\n await popup.waitForLoadState(\"domcontentloaded\").catch(() => {});\n const ready =\n approvalControls === undefined\n ? await hasVisibleButton(popup)\n : await isVisible(approvalControls(popup).first());\n return ready ? popup : undefined;\n },\n { timeoutMs },\n );\n\n/** MetaMask's own popup dimensions, which its layout is built for. */\nconst APPROVAL_WINDOW = { height: 592, width: 360 };\n/** Far enough from the edge that a popup this size lands fully on any usable display. */\nconst APPROVAL_WINDOW_OFFSET = 50;\n\n/** Move approval popups on-screen; headless and unsupported CDP targets are ignored. */\nexport const placeApprovalWindow = async (page: Page): Promise<void> => {\n await page.setViewportSize(APPROVAL_WINDOW).catch(() => {});\n try {\n const session = await page.context().newCDPSession(page);\n const { targetInfo } = await session.send(\"Target.getTargetInfo\");\n const { windowId } = await session.send(\"Browser.getWindowForTarget\", {\n targetId: targetInfo.targetId,\n });\n await session.send(\"Browser.setWindowBounds\", {\n bounds: { left: APPROVAL_WINDOW_OFFSET, top: APPROVAL_WINDOW_OFFSET },\n windowId,\n });\n await session.detach();\n } catch {\n // no window to place (headless), or the page refused a CDP session\n }\n};\n\n/** Where Chrome persists an extension's `chrome.storage.local` inside a browser profile. */\nexport const extensionStateDir = (profileDir: string, extensionId: string): string =>\n path.join(profileDir, \"Default\", \"Local Extension Settings\", extensionId);\n","import { access } from \"node:fs/promises\";\n\nimport { ClassicLevel } from \"classic-level\";\nimport { z } from \"zod\";\n\nimport { extensionStateDir } from \"../../internal/utils\";\n\nconst onboardingStateSchema = z.record(z.string(), z.json());\n\ntype OnboardingState = z.infer<typeof onboardingStateSchema>;\n\nconst parseState = (text: string, label: string): OnboardingState => {\n const result = onboardingStateSchema.safeParse(JSON.parse(text));\n if (!result.success) {\n throw new Error(`[walletwright] MetaMask's ${label} state is not an object`, {\n cause: result.error,\n });\n }\n return result.data;\n};\n\nconst markOnboarded = (onboarding: OnboardingState) => ({\n ...onboarding,\n completedOnboarding: true,\n firstTimeFlowType:\n typeof onboarding.firstTimeFlowType === \"string\" ? onboarding.firstTimeFlowType : \"import\",\n onboardingTabs: {},\n});\n\n/** Patch both MetaMask 13.x onboarding layouts while the browser has released LevelDB. */\nexport const markMetaMaskOnboarded = async (\n profileDir: string,\n extensionId: string,\n): Promise<void> => {\n const dbDir = extensionStateDir(profileDir, extensionId);\n try {\n await access(dbDir);\n } catch (error) {\n throw new Error(`[walletwright] MetaMask wrote no extension state to ${dbDir}`, {\n cause: error,\n });\n }\n\n const db = new ClassicLevel(dbDir, {\n createIfMissing: false,\n keyEncoding: \"utf8\",\n valueEncoding: \"utf8\",\n });\n try {\n await db.open();\n\n const perController = await db.get(\"OnboardingController\");\n if (perController !== undefined && perController !== \"\") {\n const onboarding = parseState(perController, \"OnboardingController\");\n if (onboarding.completedOnboarding !== true) {\n await db.put(\"OnboardingController\", JSON.stringify(markOnboarded(onboarding)));\n }\n return;\n }\n\n const raw = await db.get(\"data\");\n if (raw === undefined || raw === \"\") {\n throw new Error(\n `[walletwright] MetaMask persisted no onboarding state in ${dbDir} (neither an OnboardingController key nor a data key)`,\n );\n }\n const state = parseState(raw, '\"data\"');\n const nestedResult = onboardingStateSchema.safeParse(state.data);\n const nested = nestedResult.success ? nestedResult.data : undefined;\n const controllerResult = onboardingStateSchema.safeParse(\n (nested ?? state).OnboardingController,\n );\n if (!controllerResult.success) {\n throw new Error(`[walletwright] MetaMask's \"data\" state holds no OnboardingController`);\n }\n const patched = markOnboarded(controllerResult.data);\n await db.put(\n \"data\",\n JSON.stringify(\n nested === undefined\n ? { ...state, OnboardingController: patched }\n : { ...state, data: { ...nested, OnboardingController: patched } },\n ),\n );\n } finally {\n await db.close();\n }\n};\n","import { downloadAndExtractExtension } from \"../internal/download\";\nimport type { WalletDefinition } from \"../types\";\n\nimport { accounts } from \"./metamask/actions/accounts\";\nimport { network } from \"./metamask/actions/network\";\nimport { settings } from \"./metamask/actions/settings\";\nimport { approvalControls, approve, reject } from \"./metamask/approve\";\nimport { importWallet, reachUnlockScreen, unlock } from \"./metamask/onboarding\";\nimport { markMetaMaskOnboarded } from \"./metamask/onboarding-patch\";\n\n/** Release hashes for the pinned default. Add an entry before changing `DEFAULT_VERSION`. */\nconst RELEASE_SHA256 = {\n \"13.35.1\": \"4e0f8626df0ae9fb15f5f3ad6784a0b518f3ede067b2b0d4f539f9f457c5049c\",\n} as const satisfies Readonly<Record<string, string>>;\n\n/**\n * Typed as a key of `RELEASE_SHA256`, so bumping the default without adding its hash above is a\n * compile error. `download.ts` skips verification for an unpinned `sha256`, so the same bump would\n * otherwise silently downgrade every default MetaMask download to unverified.\n */\nexport const DEFAULT_VERSION: keyof typeof RELEASE_SHA256 = \"13.35.1\";\n\nconst releaseSha256 = (version: string): string | undefined =>\n Object.entries(RELEASE_SHA256).find(([pinned]) => pinned === version)?.[1];\n\n/** Download inputs for a MetaMask release, split out so the integrity wiring is unit-testable. */\nexport const metamaskDownload = (cacheDir: string, version: string) => ({\n cacheDir,\n kind: \"zip\" as const,\n name: `metamask-chrome-${version}`,\n sha256: releaseSha256(version),\n url: `https://github.com/MetaMask/metamask-extension/releases/download/v${version}/metamask-chrome-${version}.zip`,\n});\n\n/** MetaMask's account-name sync mutates shared test-seed profiles, so its own e2e suite blocks this host. */\nconst ACCOUNT_SYNC_HOST = \"user-storage.api.cx.metamask.io\";\n\nexport const metamask: WalletDefinition = {\n actions: { accounts, network, settings },\n approvalControls,\n approve,\n ecosystems: [\"evm\", \"svm\"],\n extensionName: \"MetaMask\",\n\n finalizeCache: markMetaMaskOnboarded,\n\n importWallet,\n\n onboardingPage: \"home.html\",\n\n prepareContext: async (context) => {\n await context.route(`**://${ACCOUNT_SYNC_HOST}/**`, (route) => route.abort());\n },\n prepareExtension: (cacheDir, version = DEFAULT_VERSION) =>\n downloadAndExtractExtension(metamaskDownload(cacheDir, version)),\n reachUnlockScreen,\n reject,\n unlock,\n};\n","import { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport { sleep } from \"../internal/wait\";\nimport type { WalletDefinition } from \"../types\";\n\nconst PHANTOM_EXTENSION_ID = \"bfnaelmomeimhlpmgjnjophhpkkoljpa\";\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"popup.html\",\n isUnlocked: async (page) => {\n const rendered = await page\n .locator(\"button\")\n .first()\n .isVisible()\n .catch(() => false);\n return rendered;\n },\n submit: async (page, field) => {\n const unlockButton = page.getByRole(\"button\", { name: /unlock/iv });\n await ((await unlockButton.isVisible().catch(() => false))\n ? unlockButton.click()\n : field.press(\"Enter\"));\n },\n wallet: \"Phantom\",\n});\n\nexport const phantom: WalletDefinition = {\n approve: async (popup) => {\n await popup.getByTestId(\"primary-button\").click({ timeout: 15_000 });\n },\n ecosystems: [\"evm\", \"svm\"],\n\n extensionName: \"Phantom\",\n\n headlessApprovals: true,\n\n importWallet: async (page, seedPhrase, password) => {\n const testId = (id: string) => page.locator(`[data-testid=\"${id}\"]`);\n\n await page.locator(\"text=I already have a wallet\").click();\n await sleep(1000);\n await page.locator(\"text=Import Recovery Phrase\").click();\n await sleep(1500);\n\n const words = seedPhrase.trim().split(/\\s+/v);\n for (let i = 0; i < words.length; i++) {\n await testId(`secret-recovery-phrase-word-input-${i}`).fill(words[i] ?? \"\");\n }\n await testId(\"onboarding-form-submit-button\").click();\n\n await testId(\"onboarding-form-secondary-button\").waitFor({ state: \"visible\", timeout: 60_000 });\n await testId(\"onboarding-form-submit-button\").click();\n\n await testId(\"onboarding-form-password-input\").fill(password);\n await testId(\"onboarding-form-confirm-password-input\").fill(password);\n await testId(\"onboarding-form-terms-of-service-checkbox\").click();\n await testId(\"onboarding-form-submit-button\").click();\n await sleep(2500);\n\n const getStarted = page.locator(\"text=Get Started\");\n if (await getStarted.isVisible().catch(() => false)) {\n await getStarted.click();\n }\n await sleep(1500);\n },\n\n onboardingPage: \"onboarding.html\",\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: PHANTOM_EXTENSION_ID,\n name: \"phantom-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n await popup.getByTestId(\"secondary-button\").click({ timeout: 15_000 });\n },\n\n unlock,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport { sleep, waitUntil, waitUntilOrThrow } from \"../internal/wait\";\nimport type { WalletDefinition } from \"../types\";\n\nconst ROUTE_TIMEOUT_MS = 30_000;\nconst SEED_PASTE_TIMEOUT_MS = 3000;\nconst APPROVAL_TIMEOUT_MS = 30_000;\n\nconst RABBY_EXTENSION_ID = \"acmacodkjbdgmoleebolmdjonilkdbch\";\n\nconst ONBOARDING_ROUTE = \"index.html#/new-user/guide\";\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({ entry: \"index.html\", wallet: \"Rabby\" });\n\nconst clickText = async (page: Page, text: string, timeoutMs = 30_000): Promise<void> => {\n const target = page.getByText(text, { exact: true }).first();\n await target.waitFor({ state: \"visible\", timeout: timeoutMs });\n await target.click();\n};\n\n/**\n * Rabby routes with a hash router, so `waitForURL` never settles (a hash change fires no navigation\n * event). Poll the URL instead. Anchoring each onboarding step on its route matters because several\n * screens share the same `input[type=\"password\"]` selector.\n */\nconst waitForRoute = async (\n page: Page,\n fragment: string,\n timeoutMs = ROUTE_TIMEOUT_MS,\n): Promise<void> => {\n await waitUntilOrThrow(() => page.url().includes(fragment), {\n message: `Rabby never reached ${fragment}`,\n timeoutMs,\n });\n};\n\nconst importWallet = async (page: Page, seedPhrase: string, password: string): Promise<void> => {\n await clickText(page, \"I already have an address\");\n await clickText(page, \"Seed Phrase or Private Key\");\n await waitForRoute(page, \"seed-or-key\");\n\n const words = page.locator('input[type=\"password\"]');\n await words.first().waitFor({ state: \"visible\", timeout: 30_000 });\n await page\n .context()\n .grantPermissions([\"clipboard-read\", \"clipboard-write\"])\n .catch(() => {});\n const list = seedPhrase.trim().split(/\\s+/v);\n const lastFilled = async (): Promise<boolean> => {\n const value = await words\n .nth(list.length - 1)\n .inputValue()\n .catch(() => \"\");\n return value.trim().length > 0;\n };\n\n await words.first().click();\n await page.evaluate((phrase) => navigator.clipboard.writeText(phrase), seedPhrase.trim());\n await page.keyboard.press(\"ControlOrMeta+v\");\n const pasted = await waitUntil(lastFilled, { timeoutMs: SEED_PASTE_TIMEOUT_MS });\n if (pasted === undefined) {\n for (let index = 0; index < list.length; index++) {\n const box = words.nth(index);\n await box.click();\n await box.fill(list[index] ?? \"\");\n }\n }\n await page.getByRole(\"button\", { exact: true, name: \"Next\" }).first().click();\n\n await waitForRoute(page, \"set-password\");\n const passwords = page.locator('input[type=\"password\"]');\n await passwords.nth(1).waitFor({ state: \"visible\", timeout: 30_000 });\n await passwords.nth(0).fill(password);\n await passwords.nth(1).fill(password);\n await page.getByRole(\"button\", { exact: true, name: \"Confirm\" }).first().click();\n\n await page\n .getByText(\"Address Imported\", { exact: false })\n .first()\n .waitFor({ state: \"visible\", timeout: 30_000 });\n await sleep(1000);\n};\n\nconst CONFIRM_LABELS = [\"Connect\", \"Sign\", \"Confirm\"];\nconst CANCEL_LABELS = [\"Cancel\", \"Reject\"];\n\n/** Rabby's focus-fragile popup requires in-page clicks; signing then requires a second Confirm click. */\nconst clickApprovalButton = async (\n popup: Page,\n labels: ReadonlyArray<string>,\n): Promise<boolean> => {\n // Every label already clicked, not just the most recent one: after Sign then Confirm, a one-slot\n // memory makes \"Sign\" eligible again, and re-clicking it re-issues the request.\n const clickedLabels = new Set<string>();\n const answered = await waitUntil(\n async () => {\n if (popup.isClosed()) {\n return true;\n }\n const clicked = await popup\n .evaluate(\n (arg) => {\n const target = [...document.querySelectorAll(\"button\")].find((button) => {\n const text = (button.textContent ?? \"\").trim();\n return arg.names.includes(text) && !button.disabled && !arg.skip.includes(text);\n });\n target?.click();\n return target ? (target.textContent ?? \"\").trim() : \"\";\n },\n { names: [...labels], skip: [...clickedLabels] },\n )\n .catch(() => \"\");\n if (clicked) {\n clickedLabels.add(clicked);\n }\n return undefined;\n },\n { intervalMs: 300, timeoutMs: APPROVAL_TIMEOUT_MS },\n );\n return answered === true || popup.isClosed();\n};\n\nexport const rabby: WalletDefinition = {\n approve: async (popup) => {\n if (!(await clickApprovalButton(popup, CONFIRM_LABELS))) {\n throw new Error(\"[walletwright] Rabby approval: no confirm button became actionable\");\n }\n },\n\n ecosystems: [\"evm\"],\n\n extensionName: \"Rabby\",\n\n headlessApprovals: true,\n\n importWallet,\n\n onboardingPage: ONBOARDING_ROUTE,\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: RABBY_EXTENSION_ID,\n name: \"rabby-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n if (!(await clickApprovalButton(popup, CANCEL_LABELS))) {\n throw new Error(\"[walletwright] Rabby reject: no cancel button became actionable\");\n }\n },\n\n unlock,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport { sleep, waitUntilOrThrow } from \"../internal/wait\";\nimport type { WalletDefinition } from \"../types\";\n\nconst SLUSH_EXTENSION_ID = \"opcgpfmipidbgpenhmajoajpbobppdil\";\n\nconst HOME_ROUTE = \"#/tokens\";\n\nconst IMPORT_HOME_TIMEOUT_MS = 30_000;\nconst PERSIST_TIMEOUT_MS = 60_000;\n\nconst ACCOUNTS_DATABASE = \"signaldb-accounts\";\nconst PREFERENCES_DATABASE = \"signaldb-preferences\";\nconst ITEMS_STORE = \"items\";\n\nconst atHome = async (page: Page): Promise<boolean> =>\n (await page.evaluate(() => globalThis.location.hash).catch(() => \"\")) === HOME_ROUTE;\n\n/**\n * Slush writes the imported account, its provider and the preferences row that selects it in\n * separate transactions, so reaching the home route does not mean the profile is durable yet.\n * Caching between those writes yields a profile that unlocks to an account-less wallet.\n */\nconst walletPersisted = (page: Page): Promise<boolean> =>\n page.evaluate(\n async ({ accountsDatabase, preferencesDatabase, storeName }) => {\n const readItems = async (databaseName: string): Promise<Array<object>> => {\n const databases = await indexedDB.databases();\n if (!databases.some((database) => database.name === databaseName)) {\n return [];\n }\n\n const database = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(databaseName);\n request.addEventListener(\"success\", () => {\n resolve(request.result);\n });\n request.addEventListener(\"error\", () => {\n reject(request.error ?? new Error(`Unable to open ${databaseName}`));\n });\n });\n\n try {\n if (!database.objectStoreNames.contains(storeName)) {\n return [];\n }\n const store = database.transaction(storeName, \"readonly\").objectStore(storeName);\n const request = store.getAll();\n return await new Promise<Array<object>>((resolve, reject) => {\n request.addEventListener(\"success\", () => {\n resolve(request.result.filter((item) => typeof item === \"object\" && item !== null));\n });\n request.addEventListener(\"error\", () => {\n reject(request.error ?? new Error(`Unable to read ${storeName}`));\n });\n });\n } finally {\n database.close();\n }\n };\n\n const accounts = await readItems(accountsDatabase);\n const accountIds = new Set<string>();\n for (const account of accounts) {\n if (\"id\" in account && typeof account.id === \"string\") {\n accountIds.add(account.id);\n }\n }\n if (accountIds.size === 0) {\n return false;\n }\n\n const preferences = await readItems(preferencesDatabase);\n return preferences.some(\n (preference) =>\n \"currentAccountId\" in preference &&\n typeof preference.currentAccountId === \"string\" &&\n accountIds.has(preference.currentAccountId),\n );\n },\n {\n accountsDatabase: ACCOUNTS_DATABASE,\n preferencesDatabase: PREFERENCES_DATABASE,\n storeName: ITEMS_STORE,\n },\n );\n\nconst fclick = async (page: Page, text: string, timeoutMs = 15_000): Promise<boolean> => {\n const target = page.getByText(text, { exact: true }).first();\n const visible = await target\n .waitFor({ state: \"visible\", timeout: timeoutMs })\n .then(() => true)\n .catch(() => false);\n if (!visible) {\n return false;\n }\n await target.scrollIntoViewIfNeeded().catch(() => {});\n await target.click({ force: true, timeout: timeoutMs });\n return true;\n};\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"index.html\",\n isUnlocked: atHome,\n submit: async (page) => {\n await fclick(page, \"Unlock\");\n },\n wallet: \"Slush\",\n});\n\nexport const slush: WalletDefinition = {\n approve: async (popup, password) => {\n await sleep(2000);\n const confirmed = (await fclick(popup, \"Approve\", 4000)) || (await fclick(popup, \"Sign\"));\n if (!confirmed) {\n throw new Error(\"[walletwright] Slush approval: neither Approve nor Sign was actionable\");\n }\n await sleep(1500);\n const input = popup.locator('input[type=\"password\"]');\n if (await input.isVisible().catch(() => false)) {\n await input.fill(password);\n await fclick(popup, \"Unlock\");\n }\n },\n\n ecosystems: [\"sui\"],\n\n extensionName: \"Slush\",\n\n importWallet: async (page, seedPhrase, password) => {\n await fclick(page, \"More options\");\n await sleep(1000);\n await fclick(page, \"Import existing from passphrase\");\n await sleep(1500);\n\n const words = seedPhrase.trim().split(/\\s+/v);\n for (let i = 0; i < words.length; i++) {\n await page.getByLabel(`Word ${i + 1}`, { exact: true }).fill(words[i] ?? \"\");\n }\n await page.getByRole(\"button\", { name: \"Next\" }).click();\n await sleep(2000);\n\n await page.locator('input[placeholder=\"Password\"]').fill(password);\n await page.locator('input[placeholder=\"Confirm Password\"]').fill(password);\n await page.getByRole(\"button\", { name: \"Next\" }).click();\n await sleep(2000);\n\n await page\n .getByRole(\"button\", { name: \"Next\" })\n .click()\n .catch(() => {});\n await waitUntilOrThrow(() => atHome(page), {\n intervalMs: 1000,\n message: `Slush import never reached the wallet home (${HOME_ROUTE})`,\n timeoutMs: IMPORT_HOME_TIMEOUT_MS,\n });\n await waitUntilOrThrow(() => walletPersisted(page), {\n intervalMs: 1000,\n message: \"Slush import never persisted an account and its selected preference\",\n timeoutMs: PERSIST_TIMEOUT_MS,\n });\n },\n\n notificationMatch: \"isPopup=1\",\n\n onboardingPage: \"index.html\",\n\n prepareContext: async (context) => {\n await context.route(\"**://*.slush.app/**\", (route) =>\n route.fulfill({\n body: JSON.stringify({ data: {} }),\n contentType: \"application/json\",\n status: 200,\n }),\n );\n },\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: SLUSH_EXTENSION_ID,\n name: \"slush-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n await sleep(2000);\n if (!(await fclick(popup, \"Reject\"))) {\n throw new Error(\"[walletwright] Slush reject: no Reject control was actionable\");\n }\n },\n\n unlock,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport type { WalletDefinition } from \"../types\";\n\nconst SOLFLARE_EXTENSION_ID = \"bhhhlbepdkbapadjdnnojkbgioiodbic\";\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"wallet.html\",\n wallet: \"Solflare\",\n});\n\nconst importWallet = async (page: Page, seedPhrase: string, password: string): Promise<void> => {\n await page.getByTestId(\"btn-import-existing-wallet\").click({ timeout: 30_000 });\n await page.getByText(\"Recovery phrase\", { exact: true }).first().click({ timeout: 30_000 });\n\n const words = seedPhrase.trim().split(/\\s+/v);\n await page.getByTestId(\"input-recovery-phrase-1\").waitFor({ state: \"visible\", timeout: 30_000 });\n for (let index = 0; index < words.length; index++) {\n await page.getByTestId(`input-recovery-phrase-${index + 1}`).fill(words[index] ?? \"\");\n }\n await page.getByTestId(\"btn-continue\").click({ timeout: 30_000 });\n\n await page.getByTestId(\"input-new-password\").waitFor({ state: \"visible\", timeout: 30_000 });\n await page.getByTestId(\"input-new-password\").fill(password);\n await page.getByTestId(\"input-repeat-password\").fill(password);\n await page.getByTestId(\"btn-continue\").click({ timeout: 30_000 });\n\n await page.getByTestId(\"btn-quick-setup\").click({ timeout: 60_000 });\n\n await page.getByTestId(\"btn-explore\").click({ timeout: 60_000 });\n};\n\nexport const solflare: WalletDefinition = {\n approve: async (popup) => {\n await popup\n .getByTestId(\"btn-connect\")\n .or(popup.getByTestId(\"btn-approve\"))\n .first()\n .click({ timeout: 30_000 });\n },\n\n ecosystems: [\"svm\"],\n\n extensionName: \"Solflare\",\n\n importWallet,\n\n notificationMatch: \"confirm_popup.html\",\n\n onboardingPage: \"wallet.html\",\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: SOLFLARE_EXTENSION_ID,\n name: \"solflare-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n await popup\n .getByTestId(\"btn-cancel\")\n .or(popup.getByTestId(\"btn-reject\"))\n .first()\n .click({ timeout: 30_000 });\n },\n\n unlock,\n};\n","import type { Ecosystem, WalletDefinition, WalletKind } from \"../types\";\n\nimport { metamask } from \"./metamask\";\nimport { phantom } from \"./phantom\";\nimport { rabby } from \"./rabby\";\nimport { slush } from \"./slush\";\nimport { solflare } from \"./solflare\";\n\nexport const wallets = {\n metamask,\n phantom,\n rabby,\n slush,\n solflare,\n} satisfies Record<WalletKind, WalletDefinition>;\n\n/** Narrow an arbitrary string (CLI flag, setup file field) to a supported wallet. */\nexport const isWalletKind = (value: string): value is WalletKind => Object.hasOwn(wallets, value);\n\nconst walletKinds = Object.keys(wallets).filter(isWalletKind);\n\n/** Wallet kinds that can drive the given ecosystem (e.g. `\"evm\"` → MetaMask, Phantom). */\nexport const walletKindsByEcosystem = (ecosystem: Ecosystem): Array<WalletKind> =>\n walletKinds.filter((kind) => wallets[kind].ecosystems.includes(ecosystem));\n","/**\n * How walletwright launches Chromium with a wallet extension loaded. Shared by the cache build and\n * the test run so the two cannot drift: an extension that loads for onboarding but not for the\n * tests (or the reverse) fails far from its cause.\n */\nexport const extensionContextOptions = (extensionPath: string, headless: boolean) => ({\n args: [`--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`],\n channel: \"chromium\",\n headless,\n});\n","import { mkdir, mkdtemp, readdir, rename, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { chromium } from \"@playwright/test\";\n\nimport type { WalletDefinition, WalletSetup } from \"../types\";\nimport { wallets } from \"../wallets/index\";\n\nimport { extensionContextOptions } from \"./chromium\";\nimport { DEFAULT_CACHE_DIR, extensionIdFromPath, extensionStateDir, profileKey } from \"./utils\";\nimport { gotoWithRetry, sleep, waitUntilOrThrow } from \"./wait\";\n\nconst NAVIGATION_TIMEOUT_MS = 15_000;\nconst FLUSH_SETTLE_MS = 3000;\nconst STATE_WRITE_TIMEOUT_MS = 10_000;\n\n/** Wallet state may live in `chrome.storage.local` or extension IndexedDB. */\nconst hasPersistedState = async (profileDir: string, extensionId: string): Promise<boolean> => {\n const stores = [\n extensionStateDir(profileDir, extensionId),\n path.join(\n profileDir,\n \"Default\",\n \"IndexedDB\",\n `chrome-extension_${extensionId}_0.indexeddb.leveldb`,\n ),\n ];\n const listings = await Promise.allSettled(stores.map((dir) => readdir(dir)));\n return listings.some((listing) => listing.status === \"fulfilled\" && listing.value.length > 0);\n};\n\n/**\n * Prefix for an in-progress build. Inside `cacheDir` so publishing is a `rename` on one filesystem,\n * dot-prefixed so it can never be mistaken for a 20-hex-character `profileKey`.\n */\nconst BUILD_PREFIX = \".building-\";\nconst PREVIOUS_SUFFIX = \".previous\";\n\nconst pathExists = async (target: string): Promise<boolean> => {\n try {\n await stat(target);\n return true;\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return false;\n }\n throw error;\n }\n};\n\nconst previousProfileDir = (profileDir: string): string => `${profileDir}${PREVIOUS_SUFFIX}`;\n\nconst restorePreviousProfile = async (profileDir: string): Promise<void> => {\n const previous = previousProfileDir(profileDir);\n if (await pathExists(profileDir)) {\n await rm(previous, { force: true, recursive: true });\n return;\n }\n try {\n await rename(previous, profileDir);\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) {\n throw error;\n }\n }\n};\n\nconst publishProfile = async (staging: string, profileDir: string): Promise<void> => {\n await restorePreviousProfile(profileDir);\n const previous = previousProfileDir(profileDir);\n let movedPrevious = false;\n try {\n await rename(profileDir, previous);\n movedPrevious = true;\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) {\n throw error;\n }\n }\n\n try {\n await rename(staging, profileDir);\n } catch (error) {\n if (movedPrevious) {\n try {\n await rename(previous, profileDir);\n } catch (rollbackError) {\n const publishMessage = error instanceof Error ? error.message : String(error);\n const rollbackMessage =\n rollbackError instanceof Error ? rollbackError.message : String(rollbackError);\n throw new Error(\n `[walletwright] failed to publish cache at ${profileDir}: ${publishMessage}; rollback failed: ${rollbackMessage}`,\n { cause: rollbackError },\n );\n }\n }\n throw error;\n }\n\n if (movedPrevious) {\n await rm(previous, { force: true, recursive: true });\n }\n};\n\n/** Build in staging and retain the prior cache until the new profile publishes successfully. */\ntype BuildCacheDependencies = {\n launchPersistentContext: typeof chromium.launchPersistentContext;\n prepareExtension: (\n definition: WalletDefinition,\n cacheDir: string,\n version?: string,\n ) => Promise<string>;\n};\n\nconst defaultBuildCacheDependencies: BuildCacheDependencies = {\n launchPersistentContext: chromium.launchPersistentContext.bind(chromium),\n prepareExtension: (definition, cacheDir, version) =>\n definition.prepareExtension(cacheDir, version),\n};\n\nconst buildCacheWithDependencies = async (\n setup: WalletSetup,\n options: { headless?: boolean } = {},\n dependencies: BuildCacheDependencies = defaultBuildCacheDependencies,\n): Promise<string> => {\n const definition = wallets[setup.wallet];\n const cacheDir = path.resolve(setup.cacheDir ?? DEFAULT_CACHE_DIR);\n const extensionPath = await dependencies.prepareExtension(definition, cacheDir, setup.version);\n\n const profileDir = path.join(cacheDir, profileKey(setup));\n await restorePreviousProfile(profileDir);\n await mkdir(cacheDir, { recursive: true });\n const staging = await mkdtemp(path.join(cacheDir, BUILD_PREFIX));\n\n let context;\n try {\n context = await dependencies.launchPersistentContext(\n staging,\n extensionContextOptions(extensionPath, options.headless === true),\n );\n await definition.prepareContext?.(context);\n const extensionId = await extensionIdFromPath(extensionPath);\n\n const page =\n context.pages().find((candidate) => candidate.url() === \"about:blank\") ??\n (await context.newPage());\n await gotoWithRetry(page, `chrome-extension://${extensionId}/${definition.onboardingPage}`, {\n label: `${definition.extensionName} onboarding page`,\n timeoutMs: NAVIGATION_TIMEOUT_MS,\n });\n await sleep(2000);\n\n await definition.importWallet(page, setup.seedPhrase, setup.password);\n await sleep(FLUSH_SETTLE_MS); // let the wallet flush state to disk before we close\n await waitUntilOrThrow(() => hasPersistedState(staging, extensionId), {\n intervalMs: 500,\n message: `${definition.extensionName} onboarding wrote no wallet state into ${staging}`,\n timeoutMs: STATE_WRITE_TIMEOUT_MS,\n });\n await context.close();\n await definition.finalizeCache?.(staging, extensionId);\n\n await publishProfile(staging, profileDir);\n return profileDir;\n } catch (error) {\n await context?.close().catch(() => {});\n throw error;\n } finally {\n await rm(staging, { force: true, recursive: true });\n }\n};\n\nconst buildCache = (setup: WalletSetup, options?: { headless?: boolean }): Promise<string> =>\n buildCacheWithDependencies(setup, options);\n\nexport { buildCache, buildCacheWithDependencies, publishProfile, restorePreviousProfile };\nexport type { BuildCacheDependencies };\n"],"mappings":"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"}
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;
@@ -20,7 +20,7 @@ type Command = {
20
20
  declare const resolveSetup: (flags: Flags) => Promise<WalletSetup>;
21
21
  declare const parseArgv: (argv: Array<string>) => Promise<Command>;
22
22
  /** Resolve the CLI symlink before comparing `process.argv[1]` with `import.meta.url`. */
23
- declare const isEntryPoint: (moduleUrl: string, entryPath: string | undefined) => boolean;
23
+ declare const isEntryPoint: (moduleUrl: string, entryPath: string | undefined) => Promise<boolean>;
24
24
  //#endregion
25
25
  export { isEntryPoint, parseArgv, resolveSetup };
26
26
  //# sourceMappingURL=cli.d.mts.map
package/dist/cli.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { i as isWalletKind, o as wallets, t as buildCache } from "./cache-CHP-6eU0.mjs";
2
+ import { i as isWalletKind, o as wallets, t as buildCache } from "./cache-BaWu5vyM.mjs";
3
+ import { realpath } from "node:fs/promises";
3
4
  import path from "node:path";
4
- import { realpathSync } from "node:fs";
5
+ import { z } from "zod";
5
6
  import { pathToFileURL } from "node:url";
6
7
  import { parseArgs } from "node:util";
7
8
 
@@ -50,13 +51,27 @@ const SETUP_CONFLICTS = [
50
51
  "version",
51
52
  "wallet"
52
53
  ];
53
- const isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
54
- const isWalletSetup = (value) => typeof value === "object" && value !== null && "wallet" in value && typeof value.wallet === "string" && isWalletKind(value.wallet) && "seedPhrase" in value && isNonEmptyString(value.seedPhrase) && "password" in value && isNonEmptyString(value.password);
54
+ const nonEmptyStringSchema = z.string().min(1);
55
+ const walletSetupSchema = z.object({
56
+ cacheDir: nonEmptyStringSchema.optional(),
57
+ password: nonEmptyStringSchema,
58
+ seedPhrase: nonEmptyStringSchema,
59
+ version: nonEmptyStringSchema.optional(),
60
+ wallet: z.enum([
61
+ "metamask",
62
+ "phantom",
63
+ "rabby",
64
+ "slush",
65
+ "solflare"
66
+ ])
67
+ });
68
+ const setupModuleSchema = z.object({ default: walletSetupSchema });
69
+ const isNonEmptyString = (value) => value !== void 0 && nonEmptyStringSchema.safeParse(value).success;
55
70
  const loadSetup = async (file) => {
56
- const mod = await import(pathToFileURL(path.resolve(file)).href);
57
- const setup = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : void 0;
58
- if (!isWalletSetup(setup)) throw new Error(`[walletwright] ${file} must default-export a WalletSetup`);
59
- return setup;
71
+ const resolved = pathToFileURL(path.resolve(file)).href;
72
+ const result = setupModuleSchema.safeParse(await import(resolved));
73
+ if (!result.success) throw new Error(`[walletwright] ${file} must default-export a WalletSetup`);
74
+ return result.data.default;
60
75
  };
61
76
  const resolveSetup = async (flags) => {
62
77
  const withCacheDir = (setup) => isNonEmptyString(flags["cache-dir"]) ? {
@@ -115,8 +130,8 @@ const main = async () => {
115
130
  process.stdout.write(`[walletwright] cache ready: ${profileDir}\n`);
116
131
  };
117
132
  /** Resolve the CLI symlink before comparing `process.argv[1]` with `import.meta.url`. */
118
- const isEntryPoint = (moduleUrl, entryPath) => entryPath !== void 0 && moduleUrl === pathToFileURL(realpathSync(entryPath)).href;
119
- if (isEntryPoint(import.meta.url, process.argv[1])) try {
133
+ const isEntryPoint = async (moduleUrl, entryPath) => entryPath !== void 0 && moduleUrl === pathToFileURL(await realpath(entryPath)).href;
134
+ if (await isEntryPoint(import.meta.url, process.argv[1])) try {
120
135
  await main();
121
136
  } catch (error) {
122
137
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpathSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\nimport { buildCache } from \"./internal/cache\";\nimport type { WalletSetup } from \"./types\";\nimport { isWalletKind, wallets } from \"./wallets/index\";\n\nconst KINDS = Object.keys(wallets);\n\nconst HELP = `walletwright: build the onboarded wallet cache for Playwright tests\n\nUsage:\n walletwright cache --setup <file> Build cache from a module's default-exported WalletSetup\n walletwright cache --wallet <${KINDS.join(\"|\")}> --seed \"<phrase>\" --password \"<pw>\" [--version <v>]\n\nOptions:\n --setup <file> A module whose default export is a WalletSetup (.ts works on modern Node)\n --wallet <kind> ${KINDS.join(\" | \")}\n --seed <phrase> seed phrase to import\n --password <pw> wallet password\n --version <v> pin an extension version\n --cache-dir <dir> cache directory (default: .walletwright)\n --headless build the cache headless\n -h, --help show this help\n\n--setup carries the whole setup, so it cannot be combined with --wallet/--seed/--password/--version;\n--cache-dir works with either form. A value that begins with \"-\" needs the --flag=value spelling.\n\n--seed and --password are visible in shell history and process lists when passed as flags; use\ntest-only values, or prefer --setup <file> to keep them out of argv.\n`;\n\n/** `parseArgs` schema; strict mode rejects unknown, missing, or misplaced flag values. */\nconst OPTIONS = {\n \"cache-dir\": { type: \"string\" },\n headless: { type: \"boolean\" },\n help: { short: \"h\", type: \"boolean\" },\n password: { type: \"string\" },\n seed: { type: \"string\" },\n setup: { type: \"string\" },\n version: { type: \"string\" },\n wallet: { type: \"string\" },\n} as const;\n\ntype Flags = {\n \"cache-dir\"?: string;\n headless?: boolean;\n help?: boolean;\n password?: string;\n seed?: string;\n setup?: string;\n version?: string;\n wallet?: string;\n};\n\n/** The flags `--setup` makes redundant: accepting both silently discarded one of the two. */\nconst SETUP_CONFLICTS = [\"password\", \"seed\", \"version\", \"wallet\"] as const;\n\ntype Command = { kind: \"help\" } | { headless: boolean; kind: \"cache\"; setup: WalletSetup };\n\nconst isNonEmptyString = (value: unknown): value is string =>\n typeof value === \"string\" && value.length > 0;\n\nconst isWalletSetup = (value: unknown): value is WalletSetup =>\n typeof value === \"object\" &&\n value !== null &&\n \"wallet\" in value &&\n typeof value.wallet === \"string\" &&\n isWalletKind(value.wallet) &&\n \"seedPhrase\" in value &&\n isNonEmptyString(value.seedPhrase) &&\n \"password\" in value &&\n isNonEmptyString(value.password);\n\nconst loadSetup = async (file: string): Promise<WalletSetup> => {\n const resolved = pathToFileURL(path.resolve(file)).href;\n const mod: unknown = await import(resolved);\n const setup =\n typeof mod === \"object\" && mod !== null && \"default\" in mod ? mod.default : undefined;\n if (!isWalletSetup(setup)) {\n throw new Error(`[walletwright] ${file} must default-export a WalletSetup`);\n }\n return setup;\n};\n\nconst resolveSetup = async (flags: Flags): Promise<WalletSetup> => {\n const withCacheDir = (setup: WalletSetup): WalletSetup =>\n isNonEmptyString(flags[\"cache-dir\"]) ? { ...setup, cacheDir: flags[\"cache-dir\"] } : setup;\n\n if (isNonEmptyString(flags.setup)) {\n const redundant = SETUP_CONFLICTS.filter((name) => flags[name] !== undefined);\n if (redundant.length > 0) {\n throw new Error(\n `[walletwright] --setup carries the whole setup, so --${redundant.join(\", --\")} would be ignored. Pass one or the other.`,\n );\n }\n return withCacheDir(await loadSetup(flags.setup));\n }\n\n const { password, seed, wallet } = flags;\n if (!isNonEmptyString(wallet) || !isNonEmptyString(seed) || !isNonEmptyString(password)) {\n throw new Error(\n \"[walletwright] provide --setup <file> or --wallet/--seed/--password. See --help.\",\n );\n }\n if (!isWalletKind(wallet)) {\n throw new Error(\n `[walletwright] unknown --wallet \"${wallet}\". Expected one of: ${KINDS.join(\", \")}.`,\n );\n }\n const base: WalletSetup = { password, seedPhrase: seed, wallet };\n return withCacheDir(isNonEmptyString(flags.version) ? { ...base, version: flags.version } : base);\n};\n\nconst parseArgv = async (argv: Array<string>): Promise<Command> => {\n let parsed;\n try {\n parsed = parseArgs({ allowPositionals: true, args: argv, options: OPTIONS, strict: true });\n } catch (error) {\n // node's ERR_PARSE_ARGS_* messages are good; they just need the prefix everything else here has.\n throw new Error(`[walletwright] ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n }\n\n const flags: Flags = parsed.values;\n const [command, ...extra] = parsed.positionals;\n\n if (flags.help === true || command === undefined || command === \"help\") {\n return { kind: \"help\" };\n }\n if (command !== \"cache\") {\n throw new Error(`[walletwright] unknown command \"${command}\". Run \\`walletwright --help\\`.`);\n }\n if (extra.length > 0) {\n throw new Error(\n `[walletwright] unexpected argument \"${extra[0]}\". Run \\`walletwright --help\\`.`,\n );\n }\n\n return { headless: flags.headless === true, kind: \"cache\", setup: await resolveSetup(flags) };\n};\n\nconst main = async (): Promise<void> => {\n const command = await parseArgv(process.argv.slice(2));\n if (command.kind === \"help\") {\n process.stdout.write(HELP);\n return;\n }\n\n process.stdout.write(`[walletwright] building ${command.setup.wallet} cache…\\n`);\n const profileDir = await buildCache(command.setup, { headless: command.headless });\n process.stdout.write(`[walletwright] cache ready: ${profileDir}\\n`);\n};\n\n/** Resolve the CLI symlink before comparing `process.argv[1]` with `import.meta.url`. */\nconst isEntryPoint = (moduleUrl: string, entryPath: string | undefined): boolean =>\n entryPath !== undefined && moduleUrl === pathToFileURL(realpathSync(entryPath)).href;\n\nif (isEntryPoint(import.meta.url, process.argv[1])) {\n try {\n await main();\n } catch (error: unknown) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n // Not process.exit(1): writes to a piped stderr are async and exit() does not flush them, so\n // `walletwright cache … 2>&1 | tee build.log` could exit 1 with an empty diagnostic. Nothing runs\n // after this block, so the process ends on its own once the write drains.\n process.exitCode = 1;\n }\n}\n\nexport { isEntryPoint, parseArgv, resolveSetup };\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { realpath } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\nimport { z } from \"zod\";\n\nimport { buildCache } from \"./internal/cache\";\nimport type { WalletSetup } from \"./types\";\nimport { isWalletKind, wallets } from \"./wallets/index\";\n\nconst KINDS = Object.keys(wallets);\n\nconst HELP = `walletwright: build the onboarded wallet cache for Playwright tests\n\nUsage:\n walletwright cache --setup <file> Build cache from a module's default-exported WalletSetup\n walletwright cache --wallet <${KINDS.join(\"|\")}> --seed \"<phrase>\" --password \"<pw>\" [--version <v>]\n\nOptions:\n --setup <file> A module whose default export is a WalletSetup (.ts works on modern Node)\n --wallet <kind> ${KINDS.join(\" | \")}\n --seed <phrase> seed phrase to import\n --password <pw> wallet password\n --version <v> pin an extension version\n --cache-dir <dir> cache directory (default: .walletwright)\n --headless build the cache headless\n -h, --help show this help\n\n--setup carries the whole setup, so it cannot be combined with --wallet/--seed/--password/--version;\n--cache-dir works with either form. A value that begins with \"-\" needs the --flag=value spelling.\n\n--seed and --password are visible in shell history and process lists when passed as flags; use\ntest-only values, or prefer --setup <file> to keep them out of argv.\n`;\n\n/** `parseArgs` schema; strict mode rejects unknown, missing, or misplaced flag values. */\nconst OPTIONS = {\n \"cache-dir\": { type: \"string\" },\n headless: { type: \"boolean\" },\n help: { short: \"h\", type: \"boolean\" },\n password: { type: \"string\" },\n seed: { type: \"string\" },\n setup: { type: \"string\" },\n version: { type: \"string\" },\n wallet: { type: \"string\" },\n} as const;\n\ntype Flags = {\n \"cache-dir\"?: string;\n headless?: boolean;\n help?: boolean;\n password?: string;\n seed?: string;\n setup?: string;\n version?: string;\n wallet?: string;\n};\n\n/** The flags `--setup` makes redundant: accepting both silently discarded one of the two. */\nconst SETUP_CONFLICTS = [\"password\", \"seed\", \"version\", \"wallet\"] as const;\n\ntype Command = { kind: \"help\" } | { headless: boolean; kind: \"cache\"; setup: WalletSetup };\n\nconst nonEmptyStringSchema = z.string().min(1);\nconst walletSetupSchema = z.object({\n cacheDir: nonEmptyStringSchema.optional(),\n password: nonEmptyStringSchema,\n seedPhrase: nonEmptyStringSchema,\n version: nonEmptyStringSchema.optional(),\n wallet: z.enum([\"metamask\", \"phantom\", \"rabby\", \"slush\", \"solflare\"]),\n});\nconst setupModuleSchema = z.object({ default: walletSetupSchema });\n\nconst isNonEmptyString = (value: string | undefined): value is string =>\n value !== undefined && nonEmptyStringSchema.safeParse(value).success;\n\nconst loadSetup = async (file: string): Promise<WalletSetup> => {\n const resolved = pathToFileURL(path.resolve(file)).href;\n const result = setupModuleSchema.safeParse(await import(resolved));\n if (!result.success) {\n throw new Error(`[walletwright] ${file} must default-export a WalletSetup`);\n }\n return result.data.default;\n};\n\nconst resolveSetup = async (flags: Flags): Promise<WalletSetup> => {\n const withCacheDir = (setup: WalletSetup): WalletSetup =>\n isNonEmptyString(flags[\"cache-dir\"]) ? { ...setup, cacheDir: flags[\"cache-dir\"] } : setup;\n\n if (isNonEmptyString(flags.setup)) {\n const redundant = SETUP_CONFLICTS.filter((name) => flags[name] !== undefined);\n if (redundant.length > 0) {\n throw new Error(\n `[walletwright] --setup carries the whole setup, so --${redundant.join(\", --\")} would be ignored. Pass one or the other.`,\n );\n }\n return withCacheDir(await loadSetup(flags.setup));\n }\n\n const { password, seed, wallet } = flags;\n if (!isNonEmptyString(wallet) || !isNonEmptyString(seed) || !isNonEmptyString(password)) {\n throw new Error(\n \"[walletwright] provide --setup <file> or --wallet/--seed/--password. See --help.\",\n );\n }\n if (!isWalletKind(wallet)) {\n throw new Error(\n `[walletwright] unknown --wallet \"${wallet}\". Expected one of: ${KINDS.join(\", \")}.`,\n );\n }\n const base: WalletSetup = { password, seedPhrase: seed, wallet };\n return withCacheDir(isNonEmptyString(flags.version) ? { ...base, version: flags.version } : base);\n};\n\nconst parseArgv = async (argv: Array<string>): Promise<Command> => {\n let parsed;\n try {\n parsed = parseArgs({ allowPositionals: true, args: argv, options: OPTIONS, strict: true });\n } catch (error) {\n // node's ERR_PARSE_ARGS_* messages are good; they just need the prefix everything else here has.\n throw new Error(`[walletwright] ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n });\n }\n\n const flags: Flags = parsed.values;\n const [command, ...extra] = parsed.positionals;\n\n if (flags.help === true || command === undefined || command === \"help\") {\n return { kind: \"help\" };\n }\n if (command !== \"cache\") {\n throw new Error(`[walletwright] unknown command \"${command}\". Run \\`walletwright --help\\`.`);\n }\n if (extra.length > 0) {\n throw new Error(\n `[walletwright] unexpected argument \"${extra[0]}\". Run \\`walletwright --help\\`.`,\n );\n }\n\n return { headless: flags.headless === true, kind: \"cache\", setup: await resolveSetup(flags) };\n};\n\nconst main = async (): Promise<void> => {\n const command = await parseArgv(process.argv.slice(2));\n if (command.kind === \"help\") {\n process.stdout.write(HELP);\n return;\n }\n\n process.stdout.write(`[walletwright] building ${command.setup.wallet} cache…\\n`);\n const profileDir = await buildCache(command.setup, { headless: command.headless });\n process.stdout.write(`[walletwright] cache ready: ${profileDir}\\n`);\n};\n\n/** Resolve the CLI symlink before comparing `process.argv[1]` with `import.meta.url`. */\nconst isEntryPoint = async (moduleUrl: string, entryPath: string | undefined): Promise<boolean> =>\n entryPath !== undefined && moduleUrl === pathToFileURL(await realpath(entryPath)).href;\n\nif (await isEntryPoint(import.meta.url, process.argv[1])) {\n try {\n await main();\n } catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n // Not process.exit(1): writes to a piped stderr are async and exit() does not flush them, so\n // `walletwright cache … 2>&1 | tee build.log` could exit 1 with an empty diagnostic. Nothing runs\n // after this block, so the process ends on its own once the write drains.\n process.exitCode = 1;\n }\n}\n\nexport { isEntryPoint, parseArgv, resolveSetup };\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
- import { a as WalletSetup, i as WalletKind, n as Wallet, r as WalletDefinition, t as Ecosystem } from "./types-CFQpWZhv.mjs";
2
- import { BrowserContext } from "@playwright/test";
1
+ import { a as WalletSetup, i as WalletKind, n as Wallet, r as WalletDefinition, t as Ecosystem } from "./types-BXDYOEaq.mjs";
2
+ import { BrowserContext, chromium } from "@playwright/test";
3
3
  //#region src/internal/cache.d.ts
4
- /** Build in staging and retain the prior cache until the new profile publishes successfully. */
5
4
  declare const buildCache: (setup: WalletSetup, options?: {
6
5
  headless?: boolean;
7
6
  }) => Promise<string>;
@@ -34,7 +33,13 @@ type LaunchFixture = {
34
33
  declare const createWalletFixtures: (setup: WalletSetup) => import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & LaunchFixture & WalletFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
35
34
  //#endregion
36
35
  //#region src/wallets/index.d.ts
37
- declare const wallets: Record<WalletKind, WalletDefinition>;
36
+ declare const wallets: {
37
+ metamask: WalletDefinition;
38
+ phantom: WalletDefinition;
39
+ rabby: WalletDefinition;
40
+ slush: WalletDefinition;
41
+ solflare: WalletDefinition;
42
+ };
38
43
  /** Narrow an arbitrary string (CLI flag, setup file field) to a supported wallet. */
39
44
  declare const isWalletKind: (value: string) => value is WalletKind;
40
45
  /** Wallet kinds that can drive the given ecosystem (e.g. `"evm"` → MetaMask, Phantom). */
package/dist/index.mjs CHANGED
@@ -1,15 +1,14 @@
1
- import { a as walletKindsByEcosystem, c as DEFAULT_NOTIFICATION_MATCH, d as placeApprovalWindow, f as profileKey, i as isWalletKind, l as extensionIdFromPath, m as waitUntil, n as restorePreviousProfile, o as wallets, p as formatTimeout, r as extensionContextOptions, s as DEFAULT_CACHE_DIR, t as buildCache, u as findNotificationPopup } from "./cache-CHP-6eU0.mjs";
2
- import { cp, mkdtemp, rm } from "node:fs/promises";
1
+ import { a as walletKindsByEcosystem, c as DEFAULT_NOTIFICATION_MATCH, d as placeApprovalWindow, f as profileKey, i as isWalletKind, l as extensionIdFromPath, m as waitUntil, n as restorePreviousProfile, o as wallets, p as formatTimeout, r as extensionContextOptions, s as DEFAULT_CACHE_DIR, t as buildCache, u as findNotificationPopup } from "./cache-BaWu5vyM.mjs";
2
+ import { cp, mkdtemp, rm, stat } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { chromium, test } from "@playwright/test";
5
- import { existsSync } from "node:fs";
6
5
  import os from "node:os";
7
6
 
8
7
  //#region src/internal/controller.ts
9
8
  const POPUP_CLOSE_TIMEOUT_MS = 15e3;
10
9
  /** Optional approvals keep a short wait: "no popup" is a normal outcome, and the wait is latency. */
11
10
  const OPTIONAL_POPUP_TIMEOUT_MS = 1e4;
12
- /** 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. */
13
12
  const REQUIRED_POPUP_TIMEOUT_MS = 3e4;
14
13
  /**
15
14
  * Drive `popup` with `settle` and return the page that actually took the approval. A popup that
@@ -86,7 +85,7 @@ const createWallet = ({ context, definition, extensionId, home, password }) => {
86
85
  approve,
87
86
  confirmSignature: () => approve(),
88
87
  confirmTransaction: () => approve(),
89
- connectToDapp: () => approve({ optional: true }),
88
+ connectToDapp: (options = {}) => approve(options),
90
89
  extensionId,
91
90
  home,
92
91
  network: {
@@ -132,9 +131,13 @@ const launchWallet = async (setup, { headless = false } = {}) => {
132
131
  const cacheDir = path.resolve(setup.cacheDir ?? ".walletwright");
133
132
  const profileDir = path.join(cacheDir, profileKey(setup));
134
133
  await restorePreviousProfile(profileDir);
135
- if (!existsSync(profileDir)) throw new Error(`[walletwright] no cache for this setup at ${profileDir}. Build it first with buildCache() or \`walletwright cache\`.`);
134
+ try {
135
+ await stat(profileDir);
136
+ } catch (error) {
137
+ throw new Error(`[walletwright] no cache for this setup at ${profileDir}. Build it first with buildCache() or \`walletwright cache\`.`, { cause: error });
138
+ }
136
139
  const extensionPath = await definition.prepareExtension(cacheDir, setup.version);
137
- const extensionId = extensionIdFromPath(extensionPath);
140
+ const extensionId = await extensionIdFromPath(extensionPath);
138
141
  /** Cleanup must cover copy and launch failures that occur before a context exists. */
139
142
  const runDir = await mkdtemp(path.join(os.tmpdir(), "walletwright-"));
140
143
  const removeRunDir = () => rm(runDir, {
@@ -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 { existsSync } from \"node:fs\";\nimport { cp, mkdtemp, rm } 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 if (!existsSync(profileDir)) {\n throw new Error(\n `[walletwright] no cache for this setup at ${profileDir}. Build it first with buildCache() or \\`walletwright cache\\`.`,\n );\n }\n\n const extensionPath = await definition.prepareExtension(cacheDir, setup.version);\n\n const extensionId = 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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"}
@@ -58,7 +58,16 @@ const installMockStandardWallet = async (target, options = {}) => {
58
58
  const handle = createStandardHandler(seed);
59
59
  const bindingName = `__walletwrightMockStandardSign_${installCount++}`;
60
60
  await target.exposeFunction(bindingName, (rpc) => handle(rpc));
61
+ const initArgs = [bindingName, {
62
+ address,
63
+ chains: [...SVM_CHAINS],
64
+ icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
65
+ name,
66
+ publicKeyHex
67
+ }];
61
68
  await target.addInitScript(([binding, info]) => {
69
+ const hasBinding = (value) => typeof Object.getOwnPropertyDescriptor(value, binding)?.value === "function";
70
+ if (!hasBinding(window)) throw new TypeError(`Missing Playwright binding: ${binding}`);
62
71
  const call = window[binding];
63
72
  const publicKeyBytes = Uint8Array.from((info.publicKeyHex.match(new RegExp(".{2}", "gv")) ?? []).map((byte) => Number.parseInt(byte, 16)));
64
73
  const account = {
@@ -104,13 +113,7 @@ const installMockStandardWallet = async (target, options = {}) => {
104
113
  window.addEventListener("wallet-standard:app-ready", (event) => {
105
114
  callback(event.detail);
106
115
  });
107
- }, [bindingName, {
108
- address,
109
- chains: [...SVM_CHAINS],
110
- icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
111
- name,
112
- publicKeyHex
113
- }]);
116
+ }, initArgs);
114
117
  return {
115
118
  address,
116
119
  publicKeyHex
@@ -1 +1 @@
1
- {"version":3,"file":"mock-standard.mjs","names":[],"sources":["../src/mock-standard.ts"],"sourcesContent":["import { createPrivateKey, createPublicKey, sign } from \"node:crypto\";\n\nimport type { BrowserContext, Page } from \"@playwright/test\";\n\n/** Headless Wallet-Standard Solana mock that signs messages with Node's ed25519 implementation. */\ntype MockStandardWalletOptions = {\n /** Wallet name announced over Wallet Standard. */\n name?: string;\n /** 32-byte ed25519 seed as hex. Defaults to a fixed test seed so the address is stable. */\n seedHex?: string;\n};\n\ntype MockStandardAccount = {\n address: string;\n /** Raw 32-byte ed25519 public key, hex-encoded. */\n publicKeyHex: string;\n};\n\nconst DEFAULT_SEED_HEX = \"0101010101010101010101010101010101010101010101010101010101010101\";\n\nconst PKCS8_ED25519_PREFIX = Buffer.from(\"302e020100300506032b657004220420\", \"hex\");\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nconst encodeBase58 = (bytes: Uint8Array): string => {\n let leadingZeros = 0;\n while (leadingZeros < bytes.length && bytes[leadingZeros] === 0) {\n leadingZeros += 1;\n }\n const digits: Array<number> = [];\n for (const byte of bytes) {\n let carry = byte;\n for (let index = 0; index < digits.length; index += 1) {\n carry += digits[index] * 256;\n digits[index] = carry % 58;\n carry = Math.floor(carry / 58);\n }\n while (carry > 0) {\n digits.push(carry % 58);\n carry = Math.floor(carry / 58);\n }\n }\n const prefix = \"1\".repeat(leadingZeros);\n const body = digits\n .toReversed()\n .map((digit) => BASE58_ALPHABET[digit])\n .join(\"\");\n return prefix + body;\n};\n\nconst privateKeyFromSeed = (seed: Buffer) =>\n createPrivateKey({\n format: \"der\",\n key: Buffer.concat([PKCS8_ED25519_PREFIX, seed]),\n type: \"pkcs8\",\n });\n\nconst rawPublicKey = (seed: Buffer): Buffer => {\n const spki = createPublicKey(privateKeyFromSeed(seed)).export({ format: \"der\", type: \"spki\" });\n return spki.subarray(-32);\n};\n\nconst signEd25519 = (seed: Buffer, message: Uint8Array): Array<number> => [\n ...sign(null, Buffer.from(message), privateKeyFromSeed(seed)),\n];\n\nconst SVM_CHAINS = [\"solana:mainnet\", \"solana:devnet\"] as const;\n\n/** `message` is required: defaulting it would sign the empty message instead of rejecting. */\ntype BridgeRequest = { message: Array<number>; method: string };\n\nconst createStandardHandler =\n (seed: Buffer) =>\n ({ message, method }: BridgeRequest): Promise<Array<number>> => {\n switch (method) {\n case \"solana:signMessage\": {\n return Promise.resolve(signEd25519(seed, Uint8Array.from(message)));\n }\n default: {\n return Promise.reject(\n new Error(`[walletwright/mock-standard] unsupported method: ${method}`),\n );\n }\n }\n };\n\n/**\n * Install the headless Wallet-Standard mock for Solana connect + sign on a context (every page) or a\n * single page. Call before `goto`, so the wallet is registered when the dapp discovers wallets.\n * Returns the account it announces so a test can verify signatures against the public key.\n */\n/** Per install, for the same reason as the EVM mock: see the note on `installCount` in `mock.ts`. */\nlet installCount = 0;\n\nconst installMockStandardWallet = async (\n target: BrowserContext | Page,\n options: MockStandardWalletOptions = {},\n): Promise<MockStandardAccount> => {\n const { name = \"Walletwright Mock\", seedHex = DEFAULT_SEED_HEX } = options;\n const seed = Buffer.from(seedHex, \"hex\");\n if (seed.length !== 32) {\n throw new Error(\"[walletwright/mock-standard] seedHex must be 32 bytes\");\n }\n const publicKey = rawPublicKey(seed);\n const address = encodeBase58(publicKey);\n const publicKeyHex = publicKey.toString(\"hex\");\n const handle = createStandardHandler(seed);\n\n const bindingName = `__walletwrightMockStandardSign_${installCount++}`;\n await target.exposeFunction(bindingName, (rpc: BridgeRequest) => handle(rpc));\n\n await target.addInitScript(\n ([binding, info]) => {\n const call =\n // oxlint-disable-next-line anti-slop/no-chained-type-assertions -- Playwright's exposeBinding installs `binding` on the page's window after this script's types are fixed\n (window as unknown as Record<string, (rpc: BridgeRequest) => Promise<Array<number>>>)[\n binding\n ];\n const publicKeyBytes = Uint8Array.from(\n (info.publicKeyHex.match(/.{2}/gv) ?? []).map((byte) => Number.parseInt(byte, 16)),\n );\n const account = {\n address: info.address,\n chains: info.chains,\n features: [\"solana:signMessage\"],\n label: info.name,\n publicKey: publicKeyBytes,\n };\n const wallet = {\n accounts: [account],\n chains: info.chains,\n features: {\n \"solana:signMessage\": {\n signMessage: (...inputs: Array<{ message: Uint8Array }>) =>\n Promise.all(\n inputs.map(async ({ message }) => ({\n signature: Uint8Array.from(\n await call({ message: [...message], method: \"solana:signMessage\" }),\n ),\n signedMessage: message,\n })),\n ),\n version: \"1.0.0\",\n },\n \"standard:connect\": {\n connect: () => Promise.resolve({ accounts: [account] }),\n version: \"1.0.0\",\n },\n \"standard:events\": {\n on: () => () => {},\n version: \"1.0.0\",\n },\n },\n icon: info.icon,\n name: info.name,\n version: \"1.0.0\" as const,\n };\n\n const callback = (api: { register: (w: typeof wallet) => void }) => {\n api.register(wallet);\n };\n try {\n window.dispatchEvent(\n new CustomEvent(\"wallet-standard:register-wallet\", { detail: callback }),\n );\n } catch {\n // The app may not be listening yet; the app-ready handler below covers that case.\n }\n window.addEventListener(\"wallet-standard:app-ready\", (event) => {\n callback((event as CustomEvent<{ register: (w: typeof wallet) => void }>).detail);\n });\n },\n [\n bindingName,\n {\n address,\n chains: [...SVM_CHAINS],\n icon: \"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>\",\n name,\n publicKeyHex,\n },\n ] as const,\n );\n\n return { address, publicKeyHex };\n};\n\nexport { createStandardHandler, encodeBase58, installMockStandardWallet, privateKeyFromSeed };\nexport type { MockStandardAccount, MockStandardWalletOptions };\n"],"mappings":"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"}
1
+ {"version":3,"file":"mock-standard.mjs","names":[],"sources":["../src/mock-standard.ts"],"sourcesContent":["import { createPrivateKey, createPublicKey, sign } from \"node:crypto\";\n\nimport type { BrowserContext, Page } from \"@playwright/test\";\n\n/** Headless Wallet-Standard Solana mock that signs messages with Node's ed25519 implementation. */\ntype MockStandardWalletOptions = {\n /** Wallet name announced over Wallet Standard. */\n name?: string;\n /** 32-byte ed25519 seed as hex. Defaults to a fixed test seed so the address is stable. */\n seedHex?: string;\n};\n\ntype MockStandardAccount = {\n address: string;\n /** Raw 32-byte ed25519 public key, hex-encoded. */\n publicKeyHex: string;\n};\n\nconst DEFAULT_SEED_HEX = \"0101010101010101010101010101010101010101010101010101010101010101\";\n\nconst PKCS8_ED25519_PREFIX = Buffer.from(\"302e020100300506032b657004220420\", \"hex\");\n\nconst BASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nconst encodeBase58 = (bytes: Uint8Array): string => {\n let leadingZeros = 0;\n while (leadingZeros < bytes.length && bytes[leadingZeros] === 0) {\n leadingZeros += 1;\n }\n const digits: Array<number> = [];\n for (const byte of bytes) {\n let carry = byte;\n for (let index = 0; index < digits.length; index += 1) {\n carry += digits[index] * 256;\n digits[index] = carry % 58;\n carry = Math.floor(carry / 58);\n }\n while (carry > 0) {\n digits.push(carry % 58);\n carry = Math.floor(carry / 58);\n }\n }\n const prefix = \"1\".repeat(leadingZeros);\n const body = digits\n .toReversed()\n .map((digit) => BASE58_ALPHABET[digit])\n .join(\"\");\n return prefix + body;\n};\n\nconst privateKeyFromSeed = (seed: Buffer) =>\n createPrivateKey({\n format: \"der\",\n key: Buffer.concat([PKCS8_ED25519_PREFIX, seed]),\n type: \"pkcs8\",\n });\n\nconst rawPublicKey = (seed: Buffer): Buffer => {\n const spki = createPublicKey(privateKeyFromSeed(seed)).export({ format: \"der\", type: \"spki\" });\n return spki.subarray(-32);\n};\n\nconst signEd25519 = (seed: Buffer, message: Uint8Array): Array<number> => [\n ...sign(null, Buffer.from(message), privateKeyFromSeed(seed)),\n];\n\nconst SVM_CHAINS = [\"solana:mainnet\", \"solana:devnet\"] as const;\n\n/** `message` is required: defaulting it would sign the empty message instead of rejecting. */\ntype BridgeRequest = { message: Array<number>; method: string };\ntype StandardInitInfo = {\n address: string;\n chains: ReadonlyArray<string>;\n icon: string;\n name: string;\n publicKeyHex: string;\n};\n\nconst createStandardHandler =\n (seed: Buffer) =>\n ({ message, method }: BridgeRequest): Promise<Array<number>> => {\n switch (method) {\n case \"solana:signMessage\": {\n return Promise.resolve(signEd25519(seed, Uint8Array.from(message)));\n }\n default: {\n return Promise.reject(\n new Error(`[walletwright/mock-standard] unsupported method: ${method}`),\n );\n }\n }\n };\n\n/**\n * Install the headless Wallet-Standard mock for Solana connect + sign on a context (every page) or a\n * single page. Call before `goto`, so the wallet is registered when the dapp discovers wallets.\n * Returns the account it announces so a test can verify signatures against the public key.\n */\n/** Per install, for the same reason as the EVM mock: see the note on `installCount` in `mock.ts`. */\nlet installCount = 0;\n\nconst installMockStandardWallet = async (\n target: BrowserContext | Page,\n options: MockStandardWalletOptions = {},\n): Promise<MockStandardAccount> => {\n const { name = \"Walletwright Mock\", seedHex = DEFAULT_SEED_HEX } = options;\n const seed = Buffer.from(seedHex, \"hex\");\n if (seed.length !== 32) {\n throw new Error(\"[walletwright/mock-standard] seedHex must be 32 bytes\");\n }\n const publicKey = rawPublicKey(seed);\n const address = encodeBase58(publicKey);\n const publicKeyHex = publicKey.toString(\"hex\");\n const handle = createStandardHandler(seed);\n\n const bindingName = `__walletwrightMockStandardSign_${installCount++}`;\n await target.exposeFunction(bindingName, (rpc: BridgeRequest) => handle(rpc));\n const initArgs = [\n bindingName,\n {\n address,\n chains: [...SVM_CHAINS],\n icon: \"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>\",\n name,\n publicKeyHex,\n },\n ] satisfies readonly [string, StandardInitInfo];\n\n await target.addInitScript(([binding, info]) => {\n const hasBinding = <Value extends object>(\n value: Value,\n ): value is Value & Record<string, (rpc: BridgeRequest) => Promise<Array<number>>> =>\n typeof Object.getOwnPropertyDescriptor(value, binding)?.value === \"function\";\n if (!hasBinding(window)) {\n throw new TypeError(`Missing Playwright binding: ${binding}`);\n }\n const call = window[binding];\n const publicKeyBytes = Uint8Array.from(\n (info.publicKeyHex.match(/.{2}/gv) ?? []).map((byte) => Number.parseInt(byte, 16)),\n );\n const account = {\n address: info.address,\n chains: info.chains,\n features: [\"solana:signMessage\"],\n label: info.name,\n publicKey: publicKeyBytes,\n };\n const wallet = {\n accounts: [account],\n chains: info.chains,\n features: {\n \"solana:signMessage\": {\n signMessage: (...inputs: Array<{ message: Uint8Array }>) =>\n Promise.all(\n inputs.map(async ({ message }) => ({\n signature: Uint8Array.from(\n await call({ message: [...message], method: \"solana:signMessage\" }),\n ),\n signedMessage: message,\n })),\n ),\n version: \"1.0.0\",\n },\n \"standard:connect\": {\n connect: () => Promise.resolve({ accounts: [account] }),\n version: \"1.0.0\",\n },\n \"standard:events\": {\n on: () => () => {},\n version: \"1.0.0\",\n },\n },\n icon: info.icon,\n name: info.name,\n version: \"1.0.0\" as const,\n };\n\n const callback = (api: { register: (w: typeof wallet) => void }) => {\n api.register(wallet);\n };\n try {\n window.dispatchEvent(\n new CustomEvent(\"wallet-standard:register-wallet\", { detail: callback }),\n );\n } catch {\n // The app may not be listening yet; the app-ready handler below covers that case.\n }\n window.addEventListener(\"wallet-standard:app-ready\", (event) => {\n // SAFETY: Wallet Standard dispatches this named event with the register callback contract.\n callback((event as CustomEvent<{ register: (w: typeof wallet) => void }>).detail);\n });\n }, initArgs);\n\n return { address, publicKeyHex };\n};\n\nexport { createStandardHandler, encodeBase58, installMockStandardWallet, privateKeyFromSeed };\nexport type { MockStandardAccount, MockStandardWalletOptions };\n"],"mappings":"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"}
package/dist/mock.d.mts CHANGED
@@ -13,8 +13,9 @@ type Rpc = {
13
13
  method: string;
14
14
  params?: ReadonlyArray<unknown>;
15
15
  };
16
+ type RpcResult = ReadonlyArray<string> | string | null;
16
17
  /** The EIP-1193 handler a mock provider answers with. Exported for direct, browser-free testing. */
17
- declare const createRpcHandler: (account: PrivateKeyAccount, chainIdHex: string) => ({ method, params }: Rpc) => Promise<unknown>;
18
+ declare const createRpcHandler: (account: PrivateKeyAccount, chainIdHex: string) => ({ method, params }: Rpc) => Promise<RpcResult>;
18
19
  /**
19
20
  * Install the mock on a context (every page) or a single page. Call before `goto`, so the provider
20
21
  * exists when the dapp looks for it. Returns the account address it announces.
package/dist/mock.mjs CHANGED
@@ -34,7 +34,15 @@ const installMockWallet = async (target, options = {}) => {
34
34
  const install = installCount++;
35
35
  const bindingName = `__walletwrightMockRpc_${install}`;
36
36
  await target.exposeFunction(bindingName, (rpc) => handle(rpc));
37
+ const initArgs = [bindingName, {
38
+ icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
39
+ name,
40
+ rdns: "sh.walletwright.mock",
41
+ uuid: `00000000-0000-0000-0000-${String(install).padStart(12, "0")}`
42
+ }];
37
43
  await target.addInitScript(([binding, info]) => {
44
+ const hasBinding = (value) => typeof Object.getOwnPropertyDescriptor(value, binding)?.value === "function";
45
+ if (!hasBinding(window)) throw new TypeError(`Missing Playwright binding: ${binding}`);
38
46
  const call = window[binding];
39
47
  const provider = {
40
48
  isMetaMask: true,
@@ -51,12 +59,7 @@ const installMockWallet = async (target, options = {}) => {
51
59
  };
52
60
  window.addEventListener("eip6963:requestProvider", announce);
53
61
  announce();
54
- }, [bindingName, {
55
- icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
56
- name,
57
- rdns: "sh.walletwright.mock",
58
- uuid: `00000000-0000-0000-0000-${String(install).padStart(12, "0")}`
59
- }]);
62
+ }, initArgs);
60
63
  return account.address;
61
64
  };
62
65
 
package/dist/mock.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mock.mjs","names":[],"sources":["../src/mock.ts"],"sourcesContent":["import type { BrowserContext, Page } from \"@playwright/test\";\nimport type { PrivateKeyAccount } from \"viem/accounts\";\nimport { privateKeyToAccount } from \"viem/accounts\";\n\n/** Headless EIP-6963 and EIP-1193 mock that signs with a real key. */\nexport type MockWalletOptions = {\n chainId?: number;\n /** Wallet name announced over EIP-6963. */\n name?: string;\n /** The account's private key. Defaults to anvil/hardhat account #0. */\n privateKey?: `0x${string}`;\n};\n\nconst DEFAULT_KEY = \"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\";\n\ntype Rpc = { method: string; params?: ReadonlyArray<unknown> };\n\nconst toHex = (value: number): string => `0x${value.toString(16)}`;\n\nconst isHexMessage = (value: string): value is `0x${string}` => /^0x[\\da-fA-F]*$/v.test(value);\n\n/** The EIP-1193 handler a mock provider answers with. Exported for direct, browser-free testing. */\nconst createRpcHandler =\n (account: PrivateKeyAccount, chainIdHex: string) =>\n ({ method, params = [] }: Rpc): Promise<unknown> => {\n switch (method) {\n case \"eth_requestAccounts\":\n case \"eth_accounts\": {\n return Promise.resolve([account.address]);\n }\n case \"eth_chainId\": {\n return Promise.resolve(chainIdHex);\n }\n case \"personal_sign\": {\n const [message] = params;\n if (typeof message !== \"string\") {\n return Promise.reject(\n new Error(\"[walletwright/mock] personal_sign expects a string message\"),\n );\n }\n return account.signMessage({\n message: isHexMessage(message) ? { raw: message } : message,\n });\n }\n case \"wallet_switchEthereumChain\":\n case \"wallet_addEthereumChain\": {\n return Promise.resolve(null);\n }\n default: {\n return Promise.reject(new Error(`[walletwright/mock] unsupported method: ${method}`));\n }\n }\n };\n\n/** Each install needs its own Playwright binding and EIP-6963 identity. */\nlet installCount = 0;\n\n/**\n * Install the mock on a context (every page) or a single page. Call before `goto`, so the provider\n * exists when the dapp looks for it. Returns the account address it announces.\n */\nconst installMockWallet = async (\n target: BrowserContext | Page,\n options: MockWalletOptions = {},\n): Promise<string> => {\n const { chainId = 31_337, name = \"Walletwright Mock\", privateKey = DEFAULT_KEY } = options;\n const account = privateKeyToAccount(privateKey);\n const chainIdHex = toHex(chainId);\n const handle = createRpcHandler(account, chainIdHex);\n\n const install = installCount++;\n const bindingName = `__walletwrightMockRpc_${install}`;\n await target.exposeFunction(bindingName, (rpc: Rpc) => handle(rpc));\n\n await target.addInitScript(\n ([binding, info]) => {\n // oxlint-disable-next-line anti-slop/no-chained-type-assertions -- Playwright's exposeBinding installs `binding` on the page's window after this script's types are fixed\n const call = (window as unknown as Record<string, (rpc: Rpc) => Promise<unknown>>)[binding];\n const provider = {\n isMetaMask: true,\n on: () => provider,\n removeListener: () => provider,\n request: (rpc: Rpc) => call(rpc),\n };\n // oxlint-disable-next-line anti-slop/no-chained-type-assertions -- injecting the EIP-1193 provider is the point of this script; the DOM lib has no `ethereum` slot\n (window as unknown as { ethereum?: unknown }).ethereum = provider;\n\n const announce = () => {\n window.dispatchEvent(\n new CustomEvent(\"eip6963:announceProvider\", {\n detail: Object.freeze({ info, provider }),\n }),\n );\n };\n window.addEventListener(\"eip6963:requestProvider\", announce);\n announce();\n },\n [\n bindingName,\n {\n icon: \"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>\",\n name,\n rdns: \"sh.walletwright.mock\",\n // Per install, so two providers stay distinguishable in a dapp's uuid-keyed provider map.\n uuid: `00000000-0000-0000-0000-${String(install).padStart(12, \"0\")}`,\n },\n ] as const,\n );\n\n return account.address;\n};\n\nexport { createRpcHandler, installMockWallet };\n"],"mappings":";;;AAaA,MAAM,cAAc;AAIpB,MAAM,SAAS,UAA0B,KAAK,MAAM,SAAS,EAAE;AAE/D,MAAM,gBAAgB,UAA0C,kCAAiB,CAAC,CAAC,KAAK,KAAK;;AAG7F,MAAM,oBACH,SAA4B,gBAC5B,EAAE,QAAQ,SAAS,CAAC,QAA+B;CAClD,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,gBACH,OAAO,QAAQ,QAAQ,CAAC,QAAQ,OAAO,CAAC;EAE1C,KAAK,eACH,OAAO,QAAQ,QAAQ,UAAU;EAEnC,KAAK,iBAAiB;GACpB,MAAM,CAAC,WAAW;GAClB,IAAI,OAAO,YAAY,UACrB,OAAO,QAAQ,uBACb,IAAI,MAAM,4DAA4D,CACxE;GAEF,OAAO,QAAQ,YAAY,EACzB,SAAS,aAAa,OAAO,IAAI,EAAE,KAAK,QAAQ,IAAI,QACtD,CAAC;EACH;EACA,KAAK;EACL,KAAK,2BACH,OAAO,QAAQ,QAAQ,IAAI;EAE7B,SACE,OAAO,QAAQ,uBAAO,IAAI,MAAM,2CAA2C,QAAQ,CAAC;CAExF;AACF;;AAGF,IAAI,eAAe;;;;;AAMnB,MAAM,oBAAoB,OACxB,QACA,UAA6B,CAAC,MACV;CACpB,MAAM,EAAE,UAAU,OAAQ,OAAO,qBAAqB,aAAa,gBAAgB;CACnF,MAAM,UAAU,oBAAoB,UAAU;CAC9C,MAAM,aAAa,MAAM,OAAO;CAChC,MAAM,SAAS,iBAAiB,SAAS,UAAU;CAEnD,MAAM,UAAU;CAChB,MAAM,cAAc,yBAAyB;CAC7C,MAAM,OAAO,eAAe,cAAc,QAAa,OAAO,GAAG,CAAC;CAElE,MAAM,OAAO,eACV,CAAC,SAAS,UAAU;EAEnB,MAAM,OAAQ,OAAqE;EACnF,MAAM,WAAW;GACf,YAAY;GACZ,UAAU;GACV,sBAAsB;GACtB,UAAU,QAAa,KAAK,GAAG;EACjC;EAEA,AAAC,OAA6C,WAAW;EAEzD,MAAM,iBAAiB;GACrB,OAAO,cACL,IAAI,YAAY,4BAA4B,EAC1C,QAAQ,OAAO,OAAO;IAAE;IAAM;GAAS,CAAC,EAC1C,CAAC,CACH;EACF;EACA,OAAO,iBAAiB,2BAA2B,QAAQ;EAC3D,SAAS;CACX,GACA,CACE,aACA;EACE,MAAM;EACN;EACA,MAAM;EAEN,MAAM,2BAA2B,OAAO,OAAO,CAAC,CAAC,SAAS,IAAI,GAAG;CACnE,CACF,CACF;CAEA,OAAO,QAAQ;AACjB"}
1
+ {"version":3,"file":"mock.mjs","names":[],"sources":["../src/mock.ts"],"sourcesContent":["import type { BrowserContext, Page } from \"@playwright/test\";\nimport type { PrivateKeyAccount } from \"viem/accounts\";\nimport { privateKeyToAccount } from \"viem/accounts\";\n\n/** Headless EIP-6963 and EIP-1193 mock that signs with a real key. */\nexport type MockWalletOptions = {\n chainId?: number;\n /** Wallet name announced over EIP-6963. */\n name?: string;\n /** The account's private key. Defaults to anvil/hardhat account #0. */\n privateKey?: `0x${string}`;\n};\n\nconst DEFAULT_KEY = \"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\";\n\ntype Rpc = { method: string; params?: ReadonlyArray<unknown> };\ntype RpcResult = ReadonlyArray<string> | string | null;\n\nconst toHex = (value: number): `0x${string}` => `0x${value.toString(16)}`;\n\nconst isHexMessage = (value: string): value is `0x${string}` => /^0x[\\da-fA-F]*$/v.test(value);\n\n/** The EIP-1193 handler a mock provider answers with. Exported for direct, browser-free testing. */\nconst createRpcHandler =\n (account: PrivateKeyAccount, chainIdHex: string) =>\n ({ method, params = [] }: Rpc): Promise<RpcResult> => {\n switch (method) {\n case \"eth_requestAccounts\":\n case \"eth_accounts\": {\n return Promise.resolve([account.address]);\n }\n case \"eth_chainId\": {\n return Promise.resolve(chainIdHex);\n }\n case \"personal_sign\": {\n const [message] = params;\n if (typeof message !== \"string\") {\n return Promise.reject(\n new Error(\"[walletwright/mock] personal_sign expects a string message\"),\n );\n }\n return account.signMessage({\n message: isHexMessage(message) ? { raw: message } : message,\n });\n }\n case \"wallet_switchEthereumChain\":\n case \"wallet_addEthereumChain\": {\n return Promise.resolve(null);\n }\n default: {\n return Promise.reject(new Error(`[walletwright/mock] unsupported method: ${method}`));\n }\n }\n };\n\n/** Each install needs its own Playwright binding and EIP-6963 identity. */\nlet installCount = 0;\n\n/**\n * Install the mock on a context (every page) or a single page. Call before `goto`, so the provider\n * exists when the dapp looks for it. Returns the account address it announces.\n */\nconst installMockWallet = async (\n target: BrowserContext | Page,\n options: MockWalletOptions = {},\n): Promise<string> => {\n const { chainId = 31_337, name = \"Walletwright Mock\", privateKey = DEFAULT_KEY } = options;\n const account = privateKeyToAccount(privateKey);\n const chainIdHex = toHex(chainId);\n const handle = createRpcHandler(account, chainIdHex);\n\n const install = installCount++;\n const bindingName = `__walletwrightMockRpc_${install}`;\n await target.exposeFunction(bindingName, (rpc: Rpc) => handle(rpc));\n const initArgs = [\n bindingName,\n {\n icon: \"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>\",\n name,\n rdns: \"sh.walletwright.mock\",\n uuid: `00000000-0000-0000-0000-${String(install).padStart(12, \"0\")}`,\n },\n ] satisfies readonly [string, { icon: string; name: string; rdns: string; uuid: string }];\n\n await target.addInitScript(([binding, info]) => {\n const hasBinding = <Value extends object>(\n value: Value,\n ): value is Value & Record<string, (rpc: Rpc) => Promise<RpcResult>> =>\n typeof Object.getOwnPropertyDescriptor(value, binding)?.value === \"function\";\n if (!hasBinding(window)) {\n throw new TypeError(`Missing Playwright binding: ${binding}`);\n }\n const call = window[binding];\n const provider = {\n isMetaMask: true,\n on: () => provider,\n removeListener: () => provider,\n request: (rpc: Rpc) => call(rpc),\n };\n // SAFETY: EIP-1193 providers own window.ethereum, which the DOM library does not declare.\n (window as Window & { ethereum?: typeof provider }).ethereum = provider;\n\n const announce = () => {\n window.dispatchEvent(\n new CustomEvent(\"eip6963:announceProvider\", {\n detail: Object.freeze({ info, provider }),\n }),\n );\n };\n window.addEventListener(\"eip6963:requestProvider\", announce);\n announce();\n }, initArgs);\n\n return account.address;\n};\n\nexport { createRpcHandler, installMockWallet };\n"],"mappings":";;;AAaA,MAAM,cAAc;AAKpB,MAAM,SAAS,UAAiC,KAAK,MAAM,SAAS,EAAE;AAEtE,MAAM,gBAAgB,UAA0C,kCAAiB,CAAC,CAAC,KAAK,KAAK;;AAG7F,MAAM,oBACH,SAA4B,gBAC5B,EAAE,QAAQ,SAAS,CAAC,QAAiC;CACpD,QAAQ,QAAR;EACE,KAAK;EACL,KAAK,gBACH,OAAO,QAAQ,QAAQ,CAAC,QAAQ,OAAO,CAAC;EAE1C,KAAK,eACH,OAAO,QAAQ,QAAQ,UAAU;EAEnC,KAAK,iBAAiB;GACpB,MAAM,CAAC,WAAW;GAClB,IAAI,OAAO,YAAY,UACrB,OAAO,QAAQ,uBACb,IAAI,MAAM,4DAA4D,CACxE;GAEF,OAAO,QAAQ,YAAY,EACzB,SAAS,aAAa,OAAO,IAAI,EAAE,KAAK,QAAQ,IAAI,QACtD,CAAC;EACH;EACA,KAAK;EACL,KAAK,2BACH,OAAO,QAAQ,QAAQ,IAAI;EAE7B,SACE,OAAO,QAAQ,uBAAO,IAAI,MAAM,2CAA2C,QAAQ,CAAC;CAExF;AACF;;AAGF,IAAI,eAAe;;;;;AAMnB,MAAM,oBAAoB,OACxB,QACA,UAA6B,CAAC,MACV;CACpB,MAAM,EAAE,UAAU,OAAQ,OAAO,qBAAqB,aAAa,gBAAgB;CACnF,MAAM,UAAU,oBAAoB,UAAU;CAC9C,MAAM,aAAa,MAAM,OAAO;CAChC,MAAM,SAAS,iBAAiB,SAAS,UAAU;CAEnD,MAAM,UAAU;CAChB,MAAM,cAAc,yBAAyB;CAC7C,MAAM,OAAO,eAAe,cAAc,QAAa,OAAO,GAAG,CAAC;CAClE,MAAM,WAAW,CACf,aACA;EACE,MAAM;EACN;EACA,MAAM;EACN,MAAM,2BAA2B,OAAO,OAAO,CAAC,CAAC,SAAS,IAAI,GAAG;CACnE,CACF;CAEA,MAAM,OAAO,eAAe,CAAC,SAAS,UAAU;EAC9C,MAAM,cACJ,UAEA,OAAO,OAAO,yBAAyB,OAAO,OAAO,CAAC,EAAE,UAAU;EACpE,IAAI,CAAC,WAAW,MAAM,GACpB,MAAM,IAAI,UAAU,+BAA+B,SAAS;EAE9D,MAAM,OAAO,OAAO;EACpB,MAAM,WAAW;GACf,YAAY;GACZ,UAAU;GACV,sBAAsB;GACtB,UAAU,QAAa,KAAK,GAAG;EACjC;EAEA,AAAC,OAAmD,WAAW;EAE/D,MAAM,iBAAiB;GACrB,OAAO,cACL,IAAI,YAAY,4BAA4B,EAC1C,QAAQ,OAAO,OAAO;IAAE;IAAM;GAAS,CAAC,EAC1C,CAAC,CACH;EACF;EACA,OAAO,iBAAiB,2BAA2B,QAAQ;EAC3D,SAAS;CACX,GAAG,QAAQ;CAEX,OAAO,QAAQ;AACjB"}
@@ -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.0",
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",
@@ -56,18 +56,19 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "adm-zip": "^0.6.0",
59
- "classic-level": "^3.0.0"
59
+ "classic-level": "^3.0.0",
60
+ "zod": "^4.5.4"
60
61
  },
61
62
  "devDependencies": {
62
63
  "@playwright/test": "1.62.1",
63
64
  "@types/adm-zip": "^0.5.7",
64
- "@types/node": "^26.2.0",
65
+ "@types/node": "^26.4.0",
65
66
  "prool": "^0.2.14",
66
- "typescript": "^6.0.3",
67
- "viem": "^2.55.11",
68
- "vitest": "^4.1.10",
69
- "@repo/typescript-config": "0.0.0",
70
- "@repo/config-vitest": "0.0.0"
67
+ "typescript": "^7.0.2",
68
+ "viem": "^2.56.0",
69
+ "vitest": "^4.1.11",
70
+ "@repo/config-vitest": "0.0.0",
71
+ "@repo/typescript-config": "0.0.0"
71
72
  },
72
73
  "peerDependencies": {
73
74
  "@playwright/test": ">=1.48 <2",
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache-CHP-6eU0.mjs","names":["add","rename","NAVIGATION_TIMEOUT_MS","importWallet","unlockScreen","importWallet"],"sources":["../src/internal/download.ts","../src/wallets/metamask/actions/accounts.ts","../src/wallets/metamask/actions/network.ts","../src/internal/wait.ts","../src/internal/unlock-screen.ts","../src/wallets/metamask/onboarding.ts","../src/wallets/metamask/actions/settings.ts","../src/wallets/metamask/approve.ts","../src/internal/utils.ts","../src/wallets/metamask/onboarding-patch.ts","../src/wallets/metamask.ts","../src/wallets/phantom.ts","../src/wallets/rabby.ts","../src/wallets/slush.ts","../src/wallets/solflare.ts","../src/wallets/index.ts","../src/internal/chromium.ts","../src/internal/cache.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, mkdtemp, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport AdmZip from \"adm-zip\";\n\nconst ZIP_SIGNATURE = Buffer.from([80, 75, 3, 4]);\n\n/**\n * Prefix for in-progress extractions. It has to live inside `cacheDir` so publishing is a `rename`\n * on one filesystem (a cross-device rename fails with EXDEV), and it starts with a dot so it can\n * never collide with a wallet's `name`.\n */\nconst STAGING_PREFIX = \".staging-\";\n\n/** Download and validate an extension archive, then publish its extraction atomically from staging. */\nexport const downloadAndExtractExtension = async (options: {\n cacheDir: string;\n kind: \"zip\" | \"crx\";\n name: string;\n /**\n * Expected sha256 of the downloaded bytes. Required, and `undefined` only where the bytes genuinely\n * cannot be pinned, so adding a download site forces a decision instead of quietly trusting it.\n */\n sha256: string | undefined;\n url: string;\n}): Promise<string> => {\n const { cacheDir, kind, name, sha256, url } = options;\n const cacheRoot = path.resolve(cacheDir);\n const outDir = path.resolve(cacheDir, name);\n if (outDir === cacheRoot || !outDir.startsWith(cacheRoot + path.sep)) {\n throw new Error(`[walletwright] invalid extension name: ${name}`);\n }\n if (existsSync(path.join(outDir, \"manifest.json\"))) {\n return outDir;\n }\n\n await mkdir(cacheDir, { recursive: true });\n\n const response = await fetch(url, { redirect: \"follow\" });\n if (!response.ok) {\n throw new Error(\n `[walletwright] failed to download ${url}: ${response.status} ${response.statusText}`,\n );\n }\n const bytes = Buffer.from(await response.arrayBuffer());\n\n if (sha256 !== undefined) {\n const actual = createHash(\"sha256\").update(bytes).digest(\"hex\");\n if (actual !== sha256.toLowerCase()) {\n throw new Error(\n `[walletwright] ${name} failed integrity check: expected ${sha256}, got ${actual}`,\n );\n }\n }\n\n let zipBytes = bytes;\n if (kind === \"crx\") {\n const start = bytes.indexOf(ZIP_SIGNATURE);\n if (start === -1) {\n throw new Error(`[walletwright] ${url} is not a valid CRX (no ZIP header found)`);\n }\n zipBytes = bytes.subarray(start);\n }\n\n const staging = await mkdtemp(path.join(cacheRoot, STAGING_PREFIX));\n try {\n const zip = new AdmZip(zipBytes);\n for (const entry of zip.getEntries()) {\n const target = path.resolve(staging, entry.entryName);\n if (target !== staging && !target.startsWith(staging + path.sep)) {\n throw new Error(`[walletwright] refusing to extract ${entry.entryName}: escapes ${outDir}`);\n }\n }\n zip.extractAllTo(staging, /* overwrite */ true);\n\n if (!existsSync(path.join(staging, \"manifest.json\"))) {\n throw new Error(`[walletwright] extracted ${name} but no manifest.json found in ${outDir}`);\n }\n\n if (existsSync(path.join(outDir, \"manifest.json\"))) {\n return outDir; // another worker published while we were downloading\n }\n await rm(outDir, { force: true, recursive: true });\n await rename(staging, outDir);\n return outDir;\n } finally {\n await rm(staging, { force: true, recursive: true }).catch(() => {});\n }\n};\n\n/** Build the Chrome Web Store CRX download URL for an extension id. */\nexport const chromeWebStoreCrxUrl = (extensionId: string): string =>\n `https://clients2.google.com/service/update2/crx?response=redirect&prodversion=130.0&acceptformat=crx2,crx3&x=id%3D${extensionId}%26uc`;\n\n/** Download and extract a Chrome Web Store extension (latest) into `<cacheDir>/<name>`. */\nexport const prepareWebStoreExtension = (options: {\n cacheDir: string;\n extensionId: string;\n name: string;\n}): Promise<string> =>\n downloadAndExtractExtension({\n cacheDir: options.cacheDir,\n kind: \"crx\",\n name: options.name,\n sha256: undefined,\n url: chromeWebStoreCrxUrl(options.extensionId),\n });\n","import type { Page } from \"@playwright/test\";\n\nimport type { AccountActions, WalletActionContext } from \"../../../types\";\n\nconst openAccountMenu = async (home: Page): Promise<void> => {\n await home.getByTestId(\"account-menu-icon\").click();\n await home.getByTestId(\"add-multichain-account-button\").waitFor({ state: \"visible\" });\n};\n\n/** The menu is a popover with no close affordance; going home is the reliable way out. */\nconst closeAccountMenu = async (home: Page): Promise<void> => {\n await home.goto(`${home.url().split(\"#\")[0]}#/`);\n await home.getByTestId(\"account-menu-icon\").waitFor({ state: \"visible\" });\n};\n\nconst add = async ({ home }: WalletActionContext): Promise<void> => {\n await openAccountMenu(home);\n const cells = home.getByTestId(\"account-cell-avatar\");\n const before = await cells.count();\n await home.getByTestId(\"add-multichain-account-button\").click();\n await cells.nth(before).waitFor({ state: \"visible\", timeout: 15_000 });\n await closeAccountMenu(home);\n};\n\nconst importPrivateKey = async (\n { home }: WalletActionContext,\n privateKey: string,\n): Promise<void> => {\n await openAccountMenu(home);\n await home.getByTestId(\"account-list-add-wallet-button\").click();\n const chooseImport = home.getByTestId(\"choose-wallet-type-import-account\");\n await chooseImport.waitFor({ state: \"visible\" });\n await chooseImport.click();\n const confirm = home.getByTestId(\"import-account-confirm-button\");\n await confirm.waitFor({ state: \"visible\" });\n await home.locator(\"input\").first().fill(privateKey);\n await confirm.click();\n await confirm.waitFor({ state: \"hidden\", timeout: 15_000 });\n await closeAccountMenu(home);\n};\n\nconst rename = async (\n { home }: WalletActionContext,\n options: { index: number; name: string },\n): Promise<void> => {\n await openAccountMenu(home);\n await home.getByTestId(\"multichain-account-cell-end-accessory\").nth(options.index).click();\n await home.getByTestId(\"multichain-account-menu-item-rename\").click();\n await home.getByTestId(\"account-name-input\").locator(\"input\").fill(options.name);\n await home.getByRole(\"button\", { name: \"Confirm\" }).click();\n await closeAccountMenu(home);\n};\n\nconst switchTo = async ({ home }: WalletActionContext, index: number): Promise<void> => {\n await openAccountMenu(home);\n await home.getByTestId(\"account-cell-avatar\").nth(index).click();\n const menuClosed = await home\n .getByTestId(\"add-multichain-account-button\")\n .waitFor({ state: \"hidden\", timeout: 10_000 })\n .then(() => true)\n .catch(() => false);\n if (!menuClosed) {\n throw new Error(`[walletwright] accounts.switch(${index}) did not switch (menu stayed open)`);\n }\n};\n\nexport const accounts: AccountActions = {\n add,\n importPrivateKey,\n rename,\n switch: switchTo,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport type { NetworkActions, NetworkConfig, WalletActionContext } from \"../../../types\";\n\nconst closeButton = (home: Page) =>\n home.getByTestId(\"modal-header-close-button\").filter({ visible: true }).first();\n\nconst openNetworkManager = async (home: Page): Promise<void> => {\n await home.getByTestId(\"sort-by-networks\").click();\n await closeButton(home).waitFor({ state: \"visible\" });\n};\n\nconst add = async ({ home }: WalletActionContext, config: NetworkConfig): Promise<void> => {\n await openNetworkManager(home);\n\n await home.getByText(\"Custom\", { exact: true }).first().click();\n await home.getByRole(\"button\", { name: \"Add custom network\" }).first().click();\n\n await home.getByTestId(\"network-form-network-name\").fill(config.name);\n await home.getByTestId(\"network-form-chain-id\").fill(String(config.chainId));\n await home.getByTestId(\"network-form-ticker-input\").fill(config.symbol);\n\n await home.getByTestId(\"test-add-rpc-drop-down\").click();\n await home.getByText(\"Add RPC URL\", { exact: true }).first().click();\n await home.getByTestId(\"rpc-url-input-test\").fill(config.rpcUrl);\n await home.getByTestId(\"page-container-footer-next\").click();\n\n await home.getByTestId(\"network-form-network-name\").waitFor();\n await home.getByTestId(\"page-container-footer-next\").click();\n\n await closeButton(home).click();\n await closeButton(home).waitFor({ state: \"hidden\" });\n};\n\nexport const network: NetworkActions = {\n add,\n};\n","import type { Page } from \"@playwright/test\";\n\nconst DEFAULT_INTERVAL_MS = 200;\nconst DEFAULT_NAVIGATION_INTERVAL_MS = 1000;\n\nexport type WaitOptions = {\n /** Gap between checks. */\n intervalMs?: number;\n /** Total budget. `check` runs at least once, and never again once the budget has elapsed. */\n timeoutMs: number;\n};\n\nexport const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n\n/** Render a timeout for an error message, so what a failure claims matches what it waited. */\nexport const formatTimeout = (ms: number): string => `${Math.round(ms / 100) / 10}s`;\n\n/** Poll until `check` returns a value, or `undefined` when the timeout expires. */\nexport const waitUntil = async <T>(\n check: () => Promise<T | false | undefined> | T | false | undefined,\n { intervalMs = DEFAULT_INTERVAL_MS, timeoutMs }: WaitOptions,\n): Promise<T | undefined> => {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const result = await check();\n if (result !== undefined && result !== false) {\n return result;\n }\n if (Date.now() >= deadline) {\n return undefined;\n }\n await sleep(intervalMs);\n }\n};\n\nexport const waitUntilOrThrow = async <T>(\n check: () => Promise<T | false | undefined> | T | false | undefined,\n options: WaitOptions & { message: string },\n): Promise<T> => {\n const result = await waitUntil(check, options);\n if (result === undefined) {\n throw new Error(\n `[walletwright] ${options.message} (gave up after ${formatTimeout(options.timeoutMs)})`,\n );\n }\n return result;\n};\n\n/**\n * A `chrome-extension://` URL fails with `ERR_BLOCKED_BY_CLIENT` until the extension registers,\n * which takes seconds after launch, so reaching an extension page is a poll rather than a one-shot.\n */\nexport const gotoWithRetry = async (\n page: Page,\n url: string,\n {\n intervalMs = DEFAULT_NAVIGATION_INTERVAL_MS,\n label,\n timeoutMs,\n }: WaitOptions & { label: string },\n): Promise<void> => {\n await waitUntilOrThrow(\n () =>\n page\n .goto(url, { waitUntil: \"domcontentloaded\" })\n .then(() => true)\n .catch(() => undefined),\n { intervalMs, message: `${label} never loaded (${url})`, timeoutMs },\n );\n};\n","import type { BrowserContext, Locator, Page } from \"@playwright/test\";\n\nimport { formatTimeout, gotoWithRetry, waitUntil } from \"./wait\";\n\nconst PASSWORD_FIELD = 'input[type=\"password\"]';\n\nconst NAVIGATION_TIMEOUT_MS = 15_000;\nconst FIRST_SETTLE_TIMEOUT_MS = 20_000;\nconst RELOAD_ATTEMPTS = 5;\nconst RELOAD_SETTLE_TIMEOUT_MS = 5000;\nconst PROBE_INTERVAL_MS = 500;\nconst PASSWORD_CLEARED_TIMEOUT_MS = 15_000;\n/**\n * Short on purpose. `unlock` is normally handed a page `reachUnlockScreen` already settled, so this\n * budget only matters when it is called on its own (the `settings.unlock` path), where a wallet with a\n * late-mounting UI needs a poll rather than a single read but a dead page should still fail fast.\n */\nconst UNLOCK_SETTLE_TIMEOUT_MS = 5000;\n\ntype UnlockScreenOptions = {\n /** Extension-relative page that renders the unlock screen (e.g. `home.html`). */\n entry: string;\n /** Detect a warm launch that is already unlocked. */\n isUnlocked?: (page: Page) => Promise<boolean>;\n /** Submit the filled password. Defaults to pressing Enter in the field. */\n submit?: (page: Page, field: Locator) => Promise<void>;\n wallet: string;\n};\n\ntype UnlockScreen = {\n reachUnlockScreen: (context: BrowserContext, extensionId: string) => Promise<Page>;\n unlock: (page: Page, password: string) => Promise<void>;\n};\n\n/**\n * The shared unlock flow: open the wallet's own page, wait for it to settle into a state we can\n * name, and type the password. Every wallet differs only in its entry page, how it submits, and\n * whether it can come back already unlocked.\n */\nexport const createUnlockScreen = ({\n entry,\n isUnlocked,\n submit,\n wallet,\n}: UnlockScreenOptions): UnlockScreen => {\n const settled = async (page: Page): Promise<\"locked\" | \"unlocked\" | undefined> => {\n const locked = await page\n .locator(PASSWORD_FIELD)\n .isVisible()\n .catch(() => false);\n if (locked) {\n return \"locked\";\n }\n const unlocked = await isUnlocked?.(page).catch(() => false);\n return unlocked === true ? \"unlocked\" : undefined;\n };\n\n const settleWithin = (page: Page, timeoutMs: number) =>\n waitUntil(() => settled(page), { intervalMs: PROBE_INTERVAL_MS, timeoutMs });\n\n const reachUnlockScreen = async (context: BrowserContext, extensionId: string): Promise<Page> => {\n const page = await context.newPage();\n await gotoWithRetry(page, `chrome-extension://${extensionId}/${entry}`, {\n label: `${wallet} ${entry}`,\n timeoutMs: NAVIGATION_TIMEOUT_MS,\n });\n\n let state = await settleWithin(page, FIRST_SETTLE_TIMEOUT_MS);\n for (let attempt = 0; attempt < RELOAD_ATTEMPTS && state === undefined; attempt++) {\n await page.reload().catch(() => {});\n state = await settleWithin(page, RELOAD_SETTLE_TIMEOUT_MS);\n }\n if (state === undefined) {\n throw new Error(\n `[walletwright] ${wallet} never reached its unlock screen (gave up after ${formatTimeout(\n FIRST_SETTLE_TIMEOUT_MS + RELOAD_ATTEMPTS * RELOAD_SETTLE_TIMEOUT_MS,\n )})`,\n );\n }\n return page;\n };\n\n const unlock = async (page: Page, password: string): Promise<void> => {\n // Through `settleWithin`, so there is one answer to \"what state is this page in\" and it waits.\n const state = await settleWithin(page, UNLOCK_SETTLE_TIMEOUT_MS);\n if (state === \"unlocked\") {\n return;\n }\n if (state === undefined) {\n throw new Error(\n `[walletwright] ${wallet} showed neither an unlock screen nor an unlocked wallet`,\n );\n }\n\n const field = page.locator(PASSWORD_FIELD);\n await field.fill(password);\n await (submit === undefined ? field.press(\"Enter\") : submit(page, field));\n const cleared = await field\n .waitFor({ state: \"hidden\", timeout: PASSWORD_CLEARED_TIMEOUT_MS })\n .then(() => true)\n .catch(() => false);\n if (!cleared) {\n throw new Error(\n `[walletwright] ${wallet} unlock failed (password screen still visible after ${formatTimeout(\n PASSWORD_CLEARED_TIMEOUT_MS,\n )})`,\n );\n }\n };\n\n return { reachUnlockScreen, unlock };\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { createUnlockScreen } from \"../../internal/unlock-screen\";\nimport { waitUntil } from \"../../internal/wait\";\n\nconst SRP_RETYPE_TIMEOUT_MS = 40_000;\nconst SRP_CONFIRM_TIMEOUT_MS = 5000;\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"home.html\",\n wallet: \"MetaMask\",\n});\n\nexport const importWallet = async (\n page: Page,\n seedPhrase: string,\n password: string,\n): Promise<void> => {\n await page.getByTestId(\"onboarding-import-wallet\").click();\n await page.getByTestId(\"onboarding-import-with-srp-button\").click();\n\n const srpField = page.getByTestId(\"srp-input-import__srp-note\");\n const confirmEnabled = page.locator('[data-testid=\"import-srp-confirm\"]:not([disabled])');\n const accepted = await waitUntil(\n async () => {\n await srpField.click();\n await srpField.fill(\"\");\n await srpField.pressSequentially(seedPhrase, { delay: 50 });\n return confirmEnabled\n .waitFor({ state: \"visible\", timeout: SRP_CONFIRM_TIMEOUT_MS })\n .then(() => true)\n .catch(() => undefined);\n },\n { intervalMs: 0, timeoutMs: SRP_RETYPE_TIMEOUT_MS },\n );\n if (accepted === undefined) {\n throw new Error(\"[walletwright] MetaMask rejected the seed phrase (confirm stayed disabled)\");\n }\n await confirmEnabled.click();\n\n await page.getByTestId(\"create-password-new-input\").fill(password);\n await page.getByTestId(\"create-password-confirm-input\").fill(password);\n await page.getByTestId(\"create-password-terms\").click();\n await page.locator('[data-testid=\"create-password-submit\"]:not([disabled])').click();\n\n await page\n .getByTestId(\"passkey-maybe-later-button\")\n .click({ timeout: 10_000 })\n .catch(() => {});\n await page\n .getByTestId(\"metametrics-i-agree\")\n .click({ timeout: 10_000 })\n .catch(() => {});\n};\n\nexport { reachUnlockScreen, unlock };\n","import type { SettingsActions } from \"../../../types\";\nimport { unlock as unlockScreen } from \"../onboarding\";\n\nexport const settings: SettingsActions = {\n lock: async ({ home }) => {\n await home.getByTestId(\"account-options-menu-button\").click();\n await home.getByTestId(\"global-menu-lock\").filter({ visible: true }).first().click();\n await home.locator('input[type=\"password\"]').waitFor({ state: \"visible\", timeout: 15_000 });\n },\n\n unlock: async ({ home, password }) => {\n await unlockScreen(home, password);\n },\n};\n","import type { Locator, Page } from \"@playwright/test\";\n\n/**\n * How long a confirm or cancel button gets to become clickable, once the page is already showing\n * one. Generous because a loaded CI runner renders MetaMask's footer slowly.\n */\nconst BUTTON_TIMEOUT_MS = 45_000;\n/** The retry only covers a notice that rendered late, so it does not need the full budget again. */\nconst RETRY_TIMEOUT_MS = 10_000;\n\n/** Snap notices intercept footer clicks and disable Accept until their content is scrolled. */\nconst acceptThirdPartyNotice = async (popup: Page): Promise<void> => {\n const modal = popup.locator(\".snap-install-warning\");\n if ((await modal.count().catch(() => 0)) === 0) {\n return;\n }\n await modal\n .locator(\"p\")\n .last()\n .scrollIntoViewIfNeeded()\n .catch(() => {});\n await modal\n .getByRole(\"button\", { name: \"Accept\" })\n .click({ timeout: 3000 })\n .catch(() => {});\n};\n\n/** Resolve the confirm test id used by standard, legacy, and Snap request footers. */\nconst confirmButton = (popup: Page): Locator =>\n popup\n .getByTestId(\"confirm-btn\")\n .or(popup.getByTestId(\"confirm-footer-button\"))\n .or(popup.locator('[data-testid=\"page-container-footer-next\"]'))\n .or(popup.locator('[data-testid$=\"-confirm-snap-footer-button\"]'))\n .first();\n\nconst cancelButton = (popup: Page): Locator =>\n popup\n .getByTestId(\"cancel-btn\")\n .or(popup.getByTestId(\"confirm-footer-cancel-button\"))\n .or(popup.locator('[data-testid=\"page-container-footer-cancel\"]'))\n .or(popup.locator('[data-testid$=\"-cancel-snap-footer-button\"]'))\n .first();\n\n/** Distinguishes a pending request from MetaMask's requestless notification home screen. */\nexport const approvalControls = (popup: Page): Locator =>\n confirmButton(popup).or(cancelButton(popup));\n\n/**\n * Click one of the popup's footer buttons. The third-party notice covers that whole footer, so both\n * confirm and cancel are intercepted while it is up, and both need the dismiss-then-retry: without it\n * a click times out against a button that is rendered and enabled, which reads as a stale selector.\n */\nconst clickFooterButton = async (popup: Page, button: Locator): Promise<void> => {\n await acceptThirdPartyNotice(popup);\n try {\n await button.click({ timeout: BUTTON_TIMEOUT_MS });\n } catch (error) {\n await acceptThirdPartyNotice(popup);\n await button.click({ timeout: RETRY_TIMEOUT_MS }).catch(() => {\n throw error;\n });\n }\n};\n\nexport const approve = (popup: Page): Promise<void> =>\n clickFooterButton(popup, confirmButton(popup));\n\n/** The cancel counterpart of `approve`; same union-by-DOM-order resolution, not written order. */\nexport const reject = (popup: Page): Promise<void> => clickFooterButton(popup, cancelButton(popup));\n","import { createHash } from \"node:crypto\";\nimport { readFileSync, realpathSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { BrowserContext, Locator, Page } from \"@playwright/test\";\n\nimport type { WalletSetup } from \"../types\";\n\nimport { waitUntil } from \"./wait\";\n\nexport const DEFAULT_CACHE_DIR = \".walletwright\";\n\n/** MetaMask/Phantom approval-popup URL token; Slush overrides it with `notificationMatch: \"isPopup=1\"`. */\nexport const DEFAULT_NOTIFICATION_MATCH = \"notification.html\";\n\n/** Stable per-setup profile directory name, so the same wallet+seed+password reuses one cache. */\nexport const profileKey = (setup: WalletSetup): string =>\n createHash(\"sha256\")\n .update(`${setup.wallet}:${setup.version ?? \"default\"}:${setup.seedPhrase}:${setup.password}`)\n .digest(\"hex\")\n .slice(0, 20);\n\n/** Mirror Chrome's extension-id derivation; resolve symlinks because Chrome hashes the real path. */\nexport const extensionIdFromPath = (extensionPath: string): string => {\n const resolved = path.resolve(extensionPath);\n const abs = (() => {\n try {\n return realpathSync(resolved);\n } catch {\n return resolved; // path doesn't exist yet, fall back to the literal resolved path\n }\n })();\n let key: string | undefined;\n try {\n const manifest: unknown = JSON.parse(readFileSync(path.join(abs, \"manifest.json\"), \"utf8\"));\n if (typeof manifest === \"object\" && manifest !== null && \"key\" in manifest) {\n key = typeof manifest.key === \"string\" ? manifest.key : undefined;\n }\n } catch {\n // manifest not present yet, fall back to the path\n }\n const source =\n key === undefined || key === \"\" ? Buffer.from(abs, \"utf8\") : Buffer.from(key, \"base64\");\n const hex = createHash(\"sha256\").update(source).digest(\"hex\").slice(0, 32);\n return Array.from(hex, (nibble) => String.fromCodePoint(97 + Number.parseInt(nibble, 16))).join(\n \"\",\n );\n};\n\n/**\n * Approval popups open as `about:blank` and then navigate, so `waitForEvent('page', { predicate })`\n * misses them, poll the open pages instead. `match` is the URL token identifying the popup\n * (`notification.html` for MetaMask/Phantom; `isPopup=1` for Slush's single-page `index.html`).\n */\nexport const isApprovalPopup = (page: Page, extensionId: string, match: string): boolean =>\n page.url().startsWith(`chrome-extension://${extensionId}`) &&\n page.url().includes(match) &&\n !page.isClosed();\n\nconst isVisible = async (locator: Locator): Promise<boolean> => {\n try {\n return await locator.isVisible();\n } catch {\n return false; // the page can go while we ask\n }\n};\n\nconst hasVisibleButton = (page: Page): Promise<boolean> =>\n isVisible(page.locator(\"button\").first());\n\nexport const findNotificationPopup = ({\n approvalControls,\n context,\n extensionId,\n match = DEFAULT_NOTIFICATION_MATCH,\n timeoutMs = 10_000,\n}: {\n /** From `WalletDefinition.approvalControls`, for a wallet that can render its popup requestless. */\n approvalControls?: (page: Page) => Locator;\n context: BrowserContext;\n extensionId: string;\n match?: string;\n timeoutMs?: number;\n}): Promise<Page | undefined> =>\n waitUntil(\n async () => {\n const popup = context.pages().find((page) => isApprovalPopup(page, extensionId, match));\n if (!popup) {\n return undefined;\n }\n await popup.waitForLoadState(\"domcontentloaded\").catch(() => {});\n const ready =\n approvalControls === undefined\n ? await hasVisibleButton(popup)\n : await isVisible(approvalControls(popup).first());\n return ready ? popup : undefined;\n },\n { timeoutMs },\n );\n\n/** MetaMask's own popup dimensions, which its layout is built for. */\nconst APPROVAL_WINDOW = { height: 592, width: 360 };\n/** Far enough from the edge that a popup this size lands fully on any usable display. */\nconst APPROVAL_WINDOW_OFFSET = 50;\n\n/** Move approval popups on-screen; headless and unsupported CDP targets are ignored. */\nexport const placeApprovalWindow = async (page: Page): Promise<void> => {\n await page.setViewportSize(APPROVAL_WINDOW).catch(() => {});\n try {\n const session = await page.context().newCDPSession(page);\n const { targetInfo } = await session.send(\"Target.getTargetInfo\");\n const { windowId } = await session.send(\"Browser.getWindowForTarget\", {\n targetId: targetInfo.targetId,\n });\n await session.send(\"Browser.setWindowBounds\", {\n bounds: { left: APPROVAL_WINDOW_OFFSET, top: APPROVAL_WINDOW_OFFSET },\n windowId,\n });\n await session.detach();\n } catch {\n // no window to place (headless), or the page refused a CDP session\n }\n};\n\n/** Where Chrome persists an extension's `chrome.storage.local` inside a browser profile. */\nexport const extensionStateDir = (profileDir: string, extensionId: string): string =>\n path.join(profileDir, \"Default\", \"Local Extension Settings\", extensionId);\n","import { existsSync } from \"node:fs\";\n\nimport { ClassicLevel } from \"classic-level\";\n\nimport { extensionStateDir } from \"../../internal/utils\";\n\ntype OnboardingState = Record<string, unknown>;\n\nconst asState = (value: unknown): OnboardingState | undefined =>\n typeof value === \"object\" && value !== null ? { ...value } : undefined;\n\nconst markOnboarded = (onboarding: OnboardingState): OnboardingState => ({\n ...onboarding,\n completedOnboarding: true,\n firstTimeFlowType:\n typeof onboarding.firstTimeFlowType === \"string\" ? onboarding.firstTimeFlowType : \"import\",\n onboardingTabs: {},\n});\n\n/** Patch both MetaMask 13.x onboarding layouts while the browser has released LevelDB. */\nexport const markMetaMaskOnboarded = async (\n profileDir: string,\n extensionId: string,\n): Promise<void> => {\n const dbDir = extensionStateDir(profileDir, extensionId);\n if (!existsSync(dbDir)) {\n throw new Error(`[walletwright] MetaMask wrote no extension state to ${dbDir}`);\n }\n\n const db = new ClassicLevel(dbDir, {\n createIfMissing: false,\n keyEncoding: \"utf8\",\n valueEncoding: \"utf8\",\n });\n try {\n await db.open();\n\n const perController = await db.get(\"OnboardingController\");\n if (perController !== undefined && perController !== \"\") {\n const onboarding = asState(JSON.parse(perController));\n if (onboarding === undefined) {\n throw new Error(`[walletwright] MetaMask's OnboardingController state is not an object`);\n }\n if (onboarding.completedOnboarding !== true) {\n await db.put(\"OnboardingController\", JSON.stringify(markOnboarded(onboarding)));\n }\n return;\n }\n\n const raw = await db.get(\"data\");\n if (raw === undefined || raw === \"\") {\n throw new Error(\n `[walletwright] MetaMask persisted no onboarding state in ${dbDir} (neither an OnboardingController key nor a data key)`,\n );\n }\n const state = asState(JSON.parse(raw));\n if (state === undefined) {\n throw new Error(`[walletwright] MetaMask's \"data\" state is not an object`);\n }\n const nested = asState(state.data);\n const controller = asState((nested ?? state).OnboardingController);\n if (controller === undefined) {\n throw new Error(`[walletwright] MetaMask's \"data\" state holds no OnboardingController`);\n }\n const patched = markOnboarded(controller);\n await db.put(\n \"data\",\n JSON.stringify(\n nested === undefined\n ? { ...state, OnboardingController: patched }\n : { ...state, data: { ...nested, OnboardingController: patched } },\n ),\n );\n } finally {\n await db.close();\n }\n};\n","import { downloadAndExtractExtension } from \"../internal/download\";\nimport type { WalletDefinition } from \"../types\";\n\nimport { accounts } from \"./metamask/actions/accounts\";\nimport { network } from \"./metamask/actions/network\";\nimport { settings } from \"./metamask/actions/settings\";\nimport { approvalControls, approve, reject } from \"./metamask/approve\";\nimport { importWallet, reachUnlockScreen, unlock } from \"./metamask/onboarding\";\nimport { markMetaMaskOnboarded } from \"./metamask/onboarding-patch\";\n\n/** Release hashes for the pinned default. Add an entry before changing `DEFAULT_VERSION`. */\nconst RELEASE_SHA256 = {\n \"13.35.1\": \"4e0f8626df0ae9fb15f5f3ad6784a0b518f3ede067b2b0d4f539f9f457c5049c\",\n} as const satisfies Readonly<Record<string, string>>;\n\n/**\n * Typed as a key of `RELEASE_SHA256`, so bumping the default without adding its hash above is a\n * compile error. `download.ts` skips verification for an unpinned `sha256`, so the same bump would\n * otherwise silently downgrade every default MetaMask download to unverified.\n */\nexport const DEFAULT_VERSION: keyof typeof RELEASE_SHA256 = \"13.35.1\";\n\nconst releaseSha256 = (version: string): string | undefined =>\n Object.entries(RELEASE_SHA256).find(([pinned]) => pinned === version)?.[1];\n\n/** Download inputs for a MetaMask release, split out so the integrity wiring is unit-testable. */\nexport const metamaskDownload = (cacheDir: string, version: string) => ({\n cacheDir,\n kind: \"zip\" as const,\n name: `metamask-chrome-${version}`,\n sha256: releaseSha256(version),\n url: `https://github.com/MetaMask/metamask-extension/releases/download/v${version}/metamask-chrome-${version}.zip`,\n});\n\n/** MetaMask's account-name sync mutates shared test-seed profiles, so its own e2e suite blocks this host. */\nconst ACCOUNT_SYNC_HOST = \"user-storage.api.cx.metamask.io\";\n\nexport const metamask: WalletDefinition = {\n actions: { accounts, network, settings },\n approvalControls,\n approve,\n ecosystems: [\"evm\", \"svm\"],\n extensionName: \"MetaMask\",\n\n finalizeCache: markMetaMaskOnboarded,\n\n importWallet,\n\n onboardingPage: \"home.html\",\n\n prepareContext: async (context) => {\n await context.route(`**://${ACCOUNT_SYNC_HOST}/**`, (route) => route.abort());\n },\n prepareExtension: (cacheDir, version = DEFAULT_VERSION) =>\n downloadAndExtractExtension(metamaskDownload(cacheDir, version)),\n reachUnlockScreen,\n reject,\n unlock,\n};\n","import { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport { sleep } from \"../internal/wait\";\nimport type { WalletDefinition } from \"../types\";\n\nconst PHANTOM_EXTENSION_ID = \"bfnaelmomeimhlpmgjnjophhpkkoljpa\";\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"popup.html\",\n isUnlocked: async (page) => {\n const rendered = await page\n .locator(\"button\")\n .first()\n .isVisible()\n .catch(() => false);\n return rendered;\n },\n submit: async (page, field) => {\n const unlockButton = page.getByRole(\"button\", { name: /unlock/iv });\n await ((await unlockButton.isVisible().catch(() => false))\n ? unlockButton.click()\n : field.press(\"Enter\"));\n },\n wallet: \"Phantom\",\n});\n\nexport const phantom: WalletDefinition = {\n approve: async (popup) => {\n await popup.getByTestId(\"primary-button\").click({ timeout: 15_000 });\n },\n ecosystems: [\"evm\", \"svm\"],\n\n extensionName: \"Phantom\",\n\n headlessApprovals: true,\n\n importWallet: async (page, seedPhrase, password) => {\n const testId = (id: string) => page.locator(`[data-testid=\"${id}\"]`);\n\n await page.locator(\"text=I already have a wallet\").click();\n await sleep(1000);\n await page.locator(\"text=Import Recovery Phrase\").click();\n await sleep(1500);\n\n const words = seedPhrase.trim().split(/\\s+/v);\n for (let i = 0; i < words.length; i++) {\n await testId(`secret-recovery-phrase-word-input-${i}`).fill(words[i] ?? \"\");\n }\n await testId(\"onboarding-form-submit-button\").click();\n\n await testId(\"onboarding-form-secondary-button\").waitFor({ state: \"visible\", timeout: 60_000 });\n await testId(\"onboarding-form-submit-button\").click();\n\n await testId(\"onboarding-form-password-input\").fill(password);\n await testId(\"onboarding-form-confirm-password-input\").fill(password);\n await testId(\"onboarding-form-terms-of-service-checkbox\").click();\n await testId(\"onboarding-form-submit-button\").click();\n await sleep(2500);\n\n const getStarted = page.locator(\"text=Get Started\");\n if (await getStarted.isVisible().catch(() => false)) {\n await getStarted.click();\n }\n await sleep(1500);\n },\n\n onboardingPage: \"onboarding.html\",\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: PHANTOM_EXTENSION_ID,\n name: \"phantom-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n await popup.getByTestId(\"secondary-button\").click({ timeout: 15_000 });\n },\n\n unlock,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport { sleep, waitUntil, waitUntilOrThrow } from \"../internal/wait\";\nimport type { WalletDefinition } from \"../types\";\n\nconst ROUTE_TIMEOUT_MS = 30_000;\nconst SEED_PASTE_TIMEOUT_MS = 3000;\nconst APPROVAL_TIMEOUT_MS = 30_000;\n\nconst RABBY_EXTENSION_ID = \"acmacodkjbdgmoleebolmdjonilkdbch\";\n\nconst ONBOARDING_ROUTE = \"index.html#/new-user/guide\";\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({ entry: \"index.html\", wallet: \"Rabby\" });\n\nconst clickText = async (page: Page, text: string, timeoutMs = 30_000): Promise<void> => {\n const target = page.getByText(text, { exact: true }).first();\n await target.waitFor({ state: \"visible\", timeout: timeoutMs });\n await target.click();\n};\n\n/**\n * Rabby routes with a hash router, so `waitForURL` never settles (a hash change fires no navigation\n * event). Poll the URL instead. Anchoring each onboarding step on its route matters because several\n * screens share the same `input[type=\"password\"]` selector.\n */\nconst waitForRoute = async (\n page: Page,\n fragment: string,\n timeoutMs = ROUTE_TIMEOUT_MS,\n): Promise<void> => {\n await waitUntilOrThrow(() => page.url().includes(fragment), {\n message: `Rabby never reached ${fragment}`,\n timeoutMs,\n });\n};\n\nconst importWallet = async (page: Page, seedPhrase: string, password: string): Promise<void> => {\n await clickText(page, \"I already have an address\");\n await clickText(page, \"Seed Phrase or Private Key\");\n await waitForRoute(page, \"seed-or-key\");\n\n const words = page.locator('input[type=\"password\"]');\n await words.first().waitFor({ state: \"visible\", timeout: 30_000 });\n await page\n .context()\n .grantPermissions([\"clipboard-read\", \"clipboard-write\"])\n .catch(() => {});\n const list = seedPhrase.trim().split(/\\s+/v);\n const lastFilled = async (): Promise<boolean> => {\n const value = await words\n .nth(list.length - 1)\n .inputValue()\n .catch(() => \"\");\n return value.trim().length > 0;\n };\n\n await words.first().click();\n await page.evaluate((phrase) => navigator.clipboard.writeText(phrase), seedPhrase.trim());\n await page.keyboard.press(\"ControlOrMeta+v\");\n const pasted = await waitUntil(lastFilled, { timeoutMs: SEED_PASTE_TIMEOUT_MS });\n if (pasted === undefined) {\n for (let index = 0; index < list.length; index++) {\n const box = words.nth(index);\n await box.click();\n await box.fill(list[index] ?? \"\");\n }\n }\n await page.getByRole(\"button\", { exact: true, name: \"Next\" }).first().click();\n\n await waitForRoute(page, \"set-password\");\n const passwords = page.locator('input[type=\"password\"]');\n await passwords.nth(1).waitFor({ state: \"visible\", timeout: 30_000 });\n await passwords.nth(0).fill(password);\n await passwords.nth(1).fill(password);\n await page.getByRole(\"button\", { exact: true, name: \"Confirm\" }).first().click();\n\n await page\n .getByText(\"Address Imported\", { exact: false })\n .first()\n .waitFor({ state: \"visible\", timeout: 30_000 });\n await sleep(1000);\n};\n\nconst CONFIRM_LABELS = [\"Connect\", \"Sign\", \"Confirm\"];\nconst CANCEL_LABELS = [\"Cancel\", \"Reject\"];\n\n/** Rabby's focus-fragile popup requires in-page clicks; signing then requires a second Confirm click. */\nconst clickApprovalButton = async (\n popup: Page,\n labels: ReadonlyArray<string>,\n): Promise<boolean> => {\n // Every label already clicked, not just the most recent one: after Sign then Confirm, a one-slot\n // memory makes \"Sign\" eligible again, and re-clicking it re-issues the request.\n const clickedLabels = new Set<string>();\n const answered = await waitUntil(\n async () => {\n if (popup.isClosed()) {\n return true;\n }\n const clicked = await popup\n .evaluate(\n (arg) => {\n const target = [...document.querySelectorAll(\"button\")].find((button) => {\n const text = (button.textContent ?? \"\").trim();\n return arg.names.includes(text) && !button.disabled && !arg.skip.includes(text);\n });\n target?.click();\n return target ? (target.textContent ?? \"\").trim() : \"\";\n },\n { names: [...labels], skip: [...clickedLabels] },\n )\n .catch(() => \"\");\n if (clicked) {\n clickedLabels.add(clicked);\n }\n return undefined;\n },\n { intervalMs: 300, timeoutMs: APPROVAL_TIMEOUT_MS },\n );\n return answered === true || popup.isClosed();\n};\n\nexport const rabby: WalletDefinition = {\n approve: async (popup) => {\n if (!(await clickApprovalButton(popup, CONFIRM_LABELS))) {\n throw new Error(\"[walletwright] Rabby approval: no confirm button became actionable\");\n }\n },\n\n ecosystems: [\"evm\"],\n\n extensionName: \"Rabby\",\n\n headlessApprovals: true,\n\n importWallet,\n\n onboardingPage: ONBOARDING_ROUTE,\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: RABBY_EXTENSION_ID,\n name: \"rabby-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n if (!(await clickApprovalButton(popup, CANCEL_LABELS))) {\n throw new Error(\"[walletwright] Rabby reject: no cancel button became actionable\");\n }\n },\n\n unlock,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport { sleep, waitUntilOrThrow } from \"../internal/wait\";\nimport type { WalletDefinition } from \"../types\";\n\nconst SLUSH_EXTENSION_ID = \"opcgpfmipidbgpenhmajoajpbobppdil\";\n\nconst HOME_ROUTE = \"#/tokens\";\n\nconst IMPORT_HOME_TIMEOUT_MS = 30_000;\n\nconst atHome = async (page: Page): Promise<boolean> =>\n (await page.evaluate(() => globalThis.location.hash).catch(() => \"\")) === HOME_ROUTE;\n\nconst fclick = async (page: Page, text: string, timeoutMs = 15_000): Promise<boolean> => {\n const target = page.getByText(text, { exact: true }).first();\n const visible = await target\n .waitFor({ state: \"visible\", timeout: timeoutMs })\n .then(() => true)\n .catch(() => false);\n if (!visible) {\n return false;\n }\n await target.scrollIntoViewIfNeeded().catch(() => {});\n await target.click({ force: true, timeout: timeoutMs });\n return true;\n};\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"index.html\",\n isUnlocked: atHome,\n submit: async (page) => {\n await fclick(page, \"Unlock\");\n },\n wallet: \"Slush\",\n});\n\nexport const slush: WalletDefinition = {\n approve: async (popup, password) => {\n await sleep(2000);\n const confirmed = (await fclick(popup, \"Approve\", 4000)) || (await fclick(popup, \"Sign\"));\n if (!confirmed) {\n throw new Error(\"[walletwright] Slush approval: neither Approve nor Sign was actionable\");\n }\n await sleep(1500);\n const input = popup.locator('input[type=\"password\"]');\n if (await input.isVisible().catch(() => false)) {\n await input.fill(password);\n await fclick(popup, \"Unlock\");\n }\n },\n\n ecosystems: [\"sui\"],\n\n extensionName: \"Slush\",\n\n importWallet: async (page, seedPhrase, password) => {\n await fclick(page, \"More options\");\n await sleep(1000);\n await fclick(page, \"Import existing from passphrase\");\n await sleep(1500);\n\n const words = seedPhrase.trim().split(/\\s+/v);\n for (let i = 0; i < words.length; i++) {\n await page.getByLabel(`Word ${i + 1}`, { exact: true }).fill(words[i] ?? \"\");\n }\n await page.getByRole(\"button\", { name: \"Next\" }).click();\n await sleep(2000);\n\n await page.locator('input[placeholder=\"Password\"]').fill(password);\n await page.locator('input[placeholder=\"Confirm Password\"]').fill(password);\n await page.getByRole(\"button\", { name: \"Next\" }).click();\n await sleep(2000);\n\n await page\n .getByRole(\"button\", { name: \"Next\" })\n .click()\n .catch(() => {});\n await waitUntilOrThrow(() => atHome(page), {\n intervalMs: 1000,\n message: `Slush import never reached the wallet home (${HOME_ROUTE})`,\n timeoutMs: IMPORT_HOME_TIMEOUT_MS,\n });\n },\n\n notificationMatch: \"isPopup=1\",\n\n onboardingPage: \"index.html\",\n\n prepareContext: async (context) => {\n await context.route(\"**://*.slush.app/**\", (route) =>\n route.fulfill({\n body: JSON.stringify({ data: {} }),\n contentType: \"application/json\",\n status: 200,\n }),\n );\n },\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: SLUSH_EXTENSION_ID,\n name: \"slush-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n await sleep(2000);\n if (!(await fclick(popup, \"Reject\"))) {\n throw new Error(\"[walletwright] Slush reject: no Reject control was actionable\");\n }\n },\n\n unlock,\n};\n","import type { Page } from \"@playwright/test\";\n\nimport { prepareWebStoreExtension } from \"../internal/download\";\nimport { createUnlockScreen } from \"../internal/unlock-screen\";\nimport type { WalletDefinition } from \"../types\";\n\nconst SOLFLARE_EXTENSION_ID = \"bhhhlbepdkbapadjdnnojkbgioiodbic\";\n\nconst { reachUnlockScreen, unlock } = createUnlockScreen({\n entry: \"wallet.html\",\n wallet: \"Solflare\",\n});\n\nconst importWallet = async (page: Page, seedPhrase: string, password: string): Promise<void> => {\n await page.getByTestId(\"btn-import-existing-wallet\").click({ timeout: 30_000 });\n await page.getByText(\"Recovery phrase\", { exact: true }).first().click({ timeout: 30_000 });\n\n const words = seedPhrase.trim().split(/\\s+/v);\n await page.getByTestId(\"input-recovery-phrase-1\").waitFor({ state: \"visible\", timeout: 30_000 });\n for (let index = 0; index < words.length; index++) {\n await page.getByTestId(`input-recovery-phrase-${index + 1}`).fill(words[index] ?? \"\");\n }\n await page.getByTestId(\"btn-continue\").click({ timeout: 30_000 });\n\n await page.getByTestId(\"input-new-password\").waitFor({ state: \"visible\", timeout: 30_000 });\n await page.getByTestId(\"input-new-password\").fill(password);\n await page.getByTestId(\"input-repeat-password\").fill(password);\n await page.getByTestId(\"btn-continue\").click({ timeout: 30_000 });\n\n await page.getByTestId(\"btn-quick-setup\").click({ timeout: 60_000 });\n\n await page.getByTestId(\"btn-explore\").click({ timeout: 60_000 });\n};\n\nexport const solflare: WalletDefinition = {\n approve: async (popup) => {\n await popup\n .getByTestId(\"btn-connect\")\n .or(popup.getByTestId(\"btn-approve\"))\n .first()\n .click({ timeout: 30_000 });\n },\n\n ecosystems: [\"svm\"],\n\n extensionName: \"Solflare\",\n\n importWallet,\n\n notificationMatch: \"confirm_popup.html\",\n\n onboardingPage: \"wallet.html\",\n\n prepareExtension: (cacheDir) =>\n prepareWebStoreExtension({\n cacheDir,\n extensionId: SOLFLARE_EXTENSION_ID,\n name: \"solflare-chrome-latest\",\n }),\n\n reachUnlockScreen,\n\n reject: async (popup) => {\n await popup\n .getByTestId(\"btn-cancel\")\n .or(popup.getByTestId(\"btn-reject\"))\n .first()\n .click({ timeout: 30_000 });\n },\n\n unlock,\n};\n","import type { Ecosystem, WalletDefinition, WalletKind } from \"../types\";\n\nimport { metamask } from \"./metamask\";\nimport { phantom } from \"./phantom\";\nimport { rabby } from \"./rabby\";\nimport { slush } from \"./slush\";\nimport { solflare } from \"./solflare\";\n\nexport const wallets: Record<WalletKind, WalletDefinition> = {\n metamask,\n phantom,\n rabby,\n slush,\n solflare,\n};\n\n/** Narrow an arbitrary string (CLI flag, setup file field) to a supported wallet. */\nexport const isWalletKind = (value: string): value is WalletKind => Object.hasOwn(wallets, value);\n\nconst walletKinds = Object.keys(wallets).filter(isWalletKind);\n\n/** Wallet kinds that can drive the given ecosystem (e.g. `\"evm\"` → MetaMask, Phantom). */\nexport const walletKindsByEcosystem = (ecosystem: Ecosystem): Array<WalletKind> =>\n walletKinds.filter((kind) => wallets[kind].ecosystems.includes(ecosystem));\n","/**\n * How walletwright launches Chromium with a wallet extension loaded. Shared by the cache build and\n * the test run so the two cannot drift: an extension that loads for onboarding but not for the\n * tests (or the reverse) fails far from its cause.\n */\nexport const extensionContextOptions = (\n extensionPath: string,\n headless: boolean,\n): { args: Array<string>; channel: string; headless: boolean } => ({\n args: [`--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`],\n channel: \"chromium\",\n headless,\n});\n","import { mkdir, mkdtemp, readdir, rename, rm, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { chromium } from \"@playwright/test\";\n\nimport type { WalletSetup } from \"../types\";\nimport { wallets } from \"../wallets/index\";\n\nimport { extensionContextOptions } from \"./chromium\";\nimport { DEFAULT_CACHE_DIR, extensionIdFromPath, extensionStateDir, profileKey } from \"./utils\";\nimport { gotoWithRetry, sleep, waitUntilOrThrow } from \"./wait\";\n\nconst NAVIGATION_TIMEOUT_MS = 15_000;\nconst FLUSH_SETTLE_MS = 3000;\nconst STATE_WRITE_TIMEOUT_MS = 10_000;\n\n/** Wallet state may live in `chrome.storage.local` or extension IndexedDB. */\nconst hasPersistedState = async (profileDir: string, extensionId: string): Promise<boolean> => {\n const stores = [\n extensionStateDir(profileDir, extensionId),\n path.join(\n profileDir,\n \"Default\",\n \"IndexedDB\",\n `chrome-extension_${extensionId}_0.indexeddb.leveldb`,\n ),\n ];\n const listings = await Promise.allSettled(stores.map((dir) => readdir(dir)));\n return listings.some((listing) => listing.status === \"fulfilled\" && listing.value.length > 0);\n};\n\n/**\n * Prefix for an in-progress build. Inside `cacheDir` so publishing is a `rename` on one filesystem,\n * dot-prefixed so it can never be mistaken for a 20-hex-character `profileKey`.\n */\nconst BUILD_PREFIX = \".building-\";\nconst PREVIOUS_SUFFIX = \".previous\";\n\nconst isErrorCode = (error: unknown, code: string): boolean =>\n error instanceof Error && \"code\" in error && error.code === code;\n\nconst pathExists = async (target: string): Promise<boolean> => {\n try {\n await stat(target);\n return true;\n } catch (error) {\n if (isErrorCode(error, \"ENOENT\")) {\n return false;\n }\n throw error;\n }\n};\n\nconst previousProfileDir = (profileDir: string): string => `${profileDir}${PREVIOUS_SUFFIX}`;\n\nconst restorePreviousProfile = async (profileDir: string): Promise<void> => {\n const previous = previousProfileDir(profileDir);\n if (await pathExists(profileDir)) {\n await rm(previous, { force: true, recursive: true });\n return;\n }\n try {\n await rename(previous, profileDir);\n } catch (error) {\n if (!isErrorCode(error, \"ENOENT\")) {\n throw error;\n }\n }\n};\n\nconst publishProfile = async (staging: string, profileDir: string): Promise<void> => {\n await restorePreviousProfile(profileDir);\n const previous = previousProfileDir(profileDir);\n let movedPrevious = false;\n try {\n await rename(profileDir, previous);\n movedPrevious = true;\n } catch (error) {\n if (!isErrorCode(error, \"ENOENT\")) {\n throw error;\n }\n }\n\n try {\n await rename(staging, profileDir);\n } catch (error) {\n if (movedPrevious) {\n try {\n await rename(previous, profileDir);\n } catch (rollbackError) {\n const publishMessage = error instanceof Error ? error.message : String(error);\n const rollbackMessage =\n rollbackError instanceof Error ? rollbackError.message : String(rollbackError);\n throw new Error(\n `[walletwright] failed to publish cache at ${profileDir}: ${publishMessage}; rollback failed: ${rollbackMessage}`,\n { cause: rollbackError },\n );\n }\n }\n throw error;\n }\n\n if (movedPrevious) {\n await rm(previous, { force: true, recursive: true });\n }\n};\n\n/** Build in staging and retain the prior cache until the new profile publishes successfully. */\nconst buildCache = async (\n setup: WalletSetup,\n options: { headless?: boolean } = {},\n): Promise<string> => {\n const definition = wallets[setup.wallet];\n const cacheDir = path.resolve(setup.cacheDir ?? DEFAULT_CACHE_DIR);\n const extensionPath = await definition.prepareExtension(cacheDir, setup.version);\n\n const profileDir = path.join(cacheDir, profileKey(setup));\n await restorePreviousProfile(profileDir);\n await mkdir(cacheDir, { recursive: true });\n const staging = await mkdtemp(path.join(cacheDir, BUILD_PREFIX));\n\n let context;\n try {\n context = await chromium.launchPersistentContext(\n staging,\n extensionContextOptions(extensionPath, options.headless === true),\n );\n await definition.prepareContext?.(context);\n const extensionId = extensionIdFromPath(extensionPath);\n\n const page =\n context.pages().find((candidate) => candidate.url() === \"about:blank\") ??\n (await context.newPage());\n await gotoWithRetry(page, `chrome-extension://${extensionId}/${definition.onboardingPage}`, {\n label: `${definition.extensionName} onboarding page`,\n timeoutMs: NAVIGATION_TIMEOUT_MS,\n });\n await sleep(2000);\n\n await definition.importWallet(page, setup.seedPhrase, setup.password);\n await sleep(FLUSH_SETTLE_MS); // let the wallet flush state to disk before we close\n await waitUntilOrThrow(() => hasPersistedState(staging, extensionId), {\n intervalMs: 500,\n message: `${definition.extensionName} onboarding wrote no wallet state into ${staging}`,\n timeoutMs: STATE_WRITE_TIMEOUT_MS,\n });\n await context.close();\n await definition.finalizeCache?.(staging, extensionId);\n\n await publishProfile(staging, profileDir);\n return profileDir;\n } catch (error) {\n await context?.close().catch(() => {});\n throw error;\n } finally {\n await rm(staging, { force: true, recursive: true });\n }\n};\n\nexport { buildCache, publishProfile, restorePreviousProfile };\n"],"mappings":"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"}