diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b99145..6e0df37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @tobelabs/chainwright +## 0.11.0 + +### Minor Changes + +- [Core] - Replace the 'glob' package with Node.js internal method + ## 0.10.17 ### Patch Changes diff --git a/package.json b/package.json index 3f0d893..12ff27c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chainwright", - "version": "0.10.17", + "version": "0.11.0", "description": "Playwright Web3 wallet testing framework for end-to-end dApp automation with MetaMask, Phantom, Solflare, Petra, Meteor, and Keplr", "type": "module", "license": "MIT", @@ -33,6 +33,10 @@ "keplr" ], "exports": { + ".": { + "types": "./dist/core/index.d.ts", + "import": "./dist/core/index.js" + }, "./keplr": { "types": "./dist/wallets/keplr/index.d.ts", "import": "./dist/wallets/keplr/index.js" @@ -72,7 +76,7 @@ "access": "public" }, "engines": { - "node": ">=22" + "node": ">=22.18" }, "scripts": { "build": "tsup", @@ -101,6 +105,7 @@ "@types/cli-progress": "^3.11.6", "@types/node": "^26.2.0", "tsup": "^8.5.1", + "tsx": "^4.23.12", "typescript": "^5.9.3", "vitest": "^4.1.10" }, @@ -109,9 +114,6 @@ "adm-zip": "^0.6.0", "cli-progress": "^3.12.0", "commander": "^15.0.0", - "glob": "^13.0.6", - "prool": "^0.2.14", - "tsx": "^4.23.12", - "zod": "^4.4.3" + "prool": "^0.2.14" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 201b1fc..5624e90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,18 +23,9 @@ importers: commander: specifier: ^15.0.0 version: 15.0.0 - glob: - specifier: ^13.0.6 - version: 13.0.6 prool: specifier: ^0.2.14 version: 0.2.14(debug@4.4.3) - tsx: - specifier: ^4.23.12 - version: 4.23.12 - zod: - specifier: ^4.4.3 - version: 4.4.3 devDependencies: '@biomejs/biome': specifier: 2.5.7 @@ -57,6 +48,9 @@ importers: tsup: specifier: ^8.5.1 version: 8.5.1(postcss@8.5.22)(tsx@4.23.12)(typescript@5.9.3) + tsx: + specifier: ^4.23.12 + version: 4.23.12 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -932,18 +926,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -1151,10 +1137,6 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1328,10 +1310,6 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - lru-cache@11.4.0: - resolution: {integrity: sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==} - engines: {node: 20 || >=22} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1343,10 +1321,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -1430,10 +1404,6 @@ packages: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1822,9 +1792,6 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - snapshots: '@babel/runtime@7.29.2': {} @@ -2497,16 +2464,10 @@ snapshots: assertion-error@2.0.1: {} - balanced-match@4.0.4: {} - better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 - brace-expansion@5.0.6: - dependencies: - balanced-match: 4.0.4 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -2742,12 +2703,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@13.0.6: - dependencies: - minimatch: 10.2.5 - minipass: 7.1.3 - path-scurry: 2.0.2 - globby@11.1.0: dependencies: array-union: 2.1.0 @@ -2877,8 +2832,6 @@ snapshots: lodash.startcase@4.4.0: {} - lru-cache@11.4.0: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2890,10 +2843,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.6 - minipass@7.1.3: {} minizlib@3.1.0: @@ -2960,11 +2909,6 @@ snapshots: path-key@4.0.0: {} - path-scurry@2.0.2: - dependencies: - lru-cache: 11.4.0 - minipass: 7.1.3 - path-type@4.0.0: {} pathe@2.0.3: {} @@ -3298,5 +3242,3 @@ snapshots: yallist@5.0.0: {} yoctocolors@2.1.2: {} - - zod@4.4.3: {} diff --git a/src/cli/index.ts b/src/cli/index.ts index 95236e7..5f4d66a 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,3 +1,3 @@ -#!/usr/bin/env -S node --import tsx +#!/usr/bin/env node export * from "./entry-point"; diff --git a/src/core/get-setup-function.ts b/src/core/get-setup-function.ts index ee463ab..642da3a 100644 --- a/src/core/get-setup-function.ts +++ b/src/core/get-setup-function.ts @@ -1,7 +1,7 @@ +import { globSync as glob } from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { styleText } from "node:util"; -import { glob } from "glob"; import type { CLIOptions, GetSetupFunctionFileList, SupportedWallets } from "@/types"; import extractWalletNameFromPath from "@/utils/wallets/extract-wallet-name-from-path"; import type { defineWalletSetup } from "./define-wallet-setup"; @@ -17,7 +17,7 @@ const toPosix = (path: string) => path.replace(/\\/g, "/"); const createGlobPattern = (walletSetupDir: string) => { const base = toPosix(path.resolve(walletSetupDir)); - return `${base}/**/*.setup.{ts,js,}`; + return `${base}/**/*.setup.{ts,js,mjs}`; }; const importSetupFile = (filePath: string) => { @@ -27,14 +27,12 @@ const importSetupFile = (filePath: string) => { export default async function getSetupFunction({ walletSetupDir, selectedWallets }: SetupFunctionHash) { const globPattern = createGlobPattern(walletSetupDir); - const fileList = ( - await glob(globPattern, { - dot: true, - absolute: true, - nodir: true, - windowsPathsNoEscape: true, - }) - ).sort(); + const fileList = glob(globPattern, { + withFileTypes: true, + }) + .filter((dirent) => dirent.isFile()) + .map((dirent) => path.join(dirent.parentPath, dirent.name)) + .sort(); // biome-ignore lint/style/noNonNullAssertion: We will always have a selected wallet const _selectedWallets = selectedWallets.length === 1 ? selectedWallets[0]! : selectedWallets; diff --git a/src/core/test/get-setup-function.test.ts b/src/core/test/get-setup-function.test.ts index 47c7260..080a579 100644 --- a/src/core/test/get-setup-function.test.ts +++ b/src/core/test/get-setup-function.test.ts @@ -1,25 +1,21 @@ import fs from "node:fs"; import path from "node:path"; -import { glob } from "glob"; -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { afterAll, describe, expect, it } from "vitest"; import type { CLIOptions } from "@/types"; import getSetupFunction from "../get-setup-function"; -// Mock glob -vi.mock("glob", () => ({ - glob: vi.fn(), -})); - describe("getSetupFunction", () => { - const WALLET_SETUP_DIR = path.resolve(process.cwd(), "src/core/test/wallet-setup-test-files"); + const TEST_ROOT = path.resolve(process.cwd(), "src/core/test/wallet-setup-test-files"); + + let dirCounter = 0; - beforeAll(() => { - const setupFiles = ["metamask.setup.ts", "metamask-two.setup.ts", "phantom.setup.ts", "solflare.setup.ts"]; - fs.mkdirSync(WALLET_SETUP_DIR, { recursive: true }); + function createSetupDir(setupFiles: string[]): string { + const walletSetupDir = path.resolve(TEST_ROOT, `case-${++dirCounter}`); + fs.mkdirSync(walletSetupDir, { recursive: true }); setupFiles.forEach((filename) => { fs.writeFileSync( - path.resolve(WALLET_SETUP_DIR, filename), + path.resolve(walletSetupDir, filename), ` import { defineWalletSetup } from "@/core/define-wallet-setup"; @@ -27,40 +23,31 @@ describe("getSetupFunction", () => { console.info("Setting up ${filename}....."); return void 0; }, ${filename === "metamask-two.setup.ts" ? '{ profileName: "profile-two" }' : undefined}); - + `, ); }); - }); + + return walletSetupDir; + } afterAll(() => { - fs.rmSync(WALLET_SETUP_DIR, { force: true, recursive: true }); + fs.rmSync(TEST_ROOT, { force: true, recursive: true }); }); - async function handleMock( - mockFilePaths: string[], - selectedWallet: Array = ["all"], - walletSetupDir?: string, - ) { - vi.mocked(glob).mockResolvedValue(mockFilePaths); - - const result = await getSetupFunction({ - walletSetupDir: walletSetupDir ?? WALLET_SETUP_DIR, - selectedWallets: selectedWallet, - }); - - return result; + async function runSetup(walletSetupDir: string, selectedWallets: Array = ["all"]) { + return getSetupFunction({ walletSetupDir, selectedWallets }); } it("should return all setup functions when selectedWallet is 'all'", async () => { - const mockFilePaths = [ - path.resolve(WALLET_SETUP_DIR, "metamask.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "metamask-two.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "phantom.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "solflare.setup.ts"), - ]; + const walletSetupDir = createSetupDir([ + "metamask.setup.ts", + "metamask-two.setup.ts", + "phantom.setup.ts", + "solflare.setup.ts", + ]); - const result = await handleMock(mockFilePaths); + const result = await runSetup(walletSetupDir); expect(result).toHaveLength(4); expect(result[0]).toHaveProperty("walletName"); @@ -75,14 +62,22 @@ describe("getSetupFunction", () => { }); }); + it("should ignore directories whose names match the setup file pattern", async () => { + const walletSetupDir = createSetupDir(["metamask.setup.ts", "phantom.setup.ts"]); + fs.mkdirSync(path.resolve(walletSetupDir, "solflare.setup.ts")); + + const result = await runSetup(walletSetupDir); + + expect(result).toHaveLength(2); + const walletNames = result.map((r) => r.walletName); + expect(walletNames).not.toContain("solflare"); + }); + it("should filter setup functions when selectedWallet is specific", async () => { - const mockFilePaths = [ - path.resolve(WALLET_SETUP_DIR, "metamask.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "metamask-two.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "phantom.setup.ts"), - ]; - vi.mocked(glob).mockResolvedValue(mockFilePaths); - const result = await handleMock(mockFilePaths, ["metamask"]); + const walletSetupDir = createSetupDir(["metamask.setup.ts", "metamask-two.setup.ts", "phantom.setup.ts"]); + + const result = await runSetup(walletSetupDir, ["metamask"]); + expect(result).toHaveLength(2); result.forEach((item) => { expect(item).toHaveProperty("walletName", "metamask"); @@ -92,11 +87,11 @@ describe("getSetupFunction", () => { }); it("should throw an error when no setup files are found", async () => { - vi.mocked(glob).mockResolvedValue([]); + const walletSetupDir = createSetupDir([]); - await expect(handleMock([], ["all"])).rejects.toThrowError( + await expect(runSetup(walletSetupDir, ["all"])).rejects.toThrowError( [ - `No wallet setup file found at ${WALLET_SETUP_DIR} for wallet: "all".`, + `No wallet setup file found at ${walletSetupDir} for wallet: "all".`, `Setup files must use a ".setup.{ts,js,mjs}" extension and include a valid wallet name.`, `Examples: "metamask.setup.ts", "solflare.setup.ts", "phantom.setup.ts", "metamask-connected.setup.ts"`, ].join("\n "), @@ -104,17 +99,11 @@ describe("getSetupFunction", () => { }); it("should throw an error when filtered file list is empty", async () => { - const walletSetupDir = "/test/wallet-setup"; - const mockFilePaths = [ - path.resolve("/test/wallet-setup/phantom.setup.ts"), - path.resolve("/test/wallet-setup/solflare.setup.ts"), - ]; - - vi.mocked(glob).mockResolvedValue(mockFilePaths); + const walletSetupDir = createSetupDir(["phantom.setup.ts", "solflare.setup.ts"]); - await expect(handleMock(mockFilePaths, ["metamask"], walletSetupDir)).rejects.toThrow( + await expect(runSetup(walletSetupDir, ["metamask"])).rejects.toThrow( [ - `No wallet setup file found at /test/wallet-setup for wallet: "metamask".`, + `No wallet setup file found at ${walletSetupDir} for wallet: "metamask".`, `Setup files must use a ".setup.{ts,js,mjs}" extension and include a valid wallet name.`, `Examples: "metamask.setup.ts", "solflare.setup.ts", "phantom.setup.ts", "metamask-connected.setup.ts"`, ].join("\n "), @@ -122,11 +111,9 @@ describe("getSetupFunction", () => { }); it("should handle wallet profiles correctly", async () => { - const mockFilePaths = [path.resolve(WALLET_SETUP_DIR, "metamask-two.setup.ts")]; + const walletSetupDir = createSetupDir(["metamask-two.setup.ts"]); - vi.mocked(glob).mockResolvedValue(mockFilePaths); - - const result = await handleMock(mockFilePaths, ["metamask"]); + const result = await runSetup(walletSetupDir, ["metamask"]); expect(result).toHaveLength(1); expect(result[0]).toHaveProperty("walletName", "metamask"); @@ -134,52 +121,37 @@ describe("getSetupFunction", () => { expect(result[0]).toHaveProperty("setupFunction"); }); - it("should use absolute paths in glob pattern", async () => { - const walletSetupDir = "src/core/test/wallet-setup-test-files"; - const resolvedDir = path.resolve(process.cwd(), walletSetupDir); - const mockFilePaths = [path.resolve(resolvedDir, "metamask.setup.ts")]; - - vi.mocked(glob).mockResolvedValue(mockFilePaths); + it("should resolve a relative walletSetupDir to absolute file paths", async () => { + const walletSetupDir = createSetupDir(["metamask.setup.ts"]); + const relativeDir = path.relative(process.cwd(), walletSetupDir); - await handleMock(mockFilePaths, ["metamask"], walletSetupDir); + const result = await runSetup(relativeDir, ["metamask"]); - const globCall = vi.mocked(glob).mock.calls[0]; - if (globCall?.[0]) { - expect(globCall[0]).toMatch(/\.setup\.\{ts,js,\}/); - } + expect(result).toHaveLength(1); + result[0]?.fileList.forEach(({ filePath }) => { + expect(path.isAbsolute(filePath)).toBe(true); + }); }); it("should sort file list alphabetically", async () => { - const mockFilePaths = [ - path.resolve(WALLET_SETUP_DIR, "solflare.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "metamask.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "phantom.setup.ts"), - ]; - - vi.mocked(glob).mockResolvedValue(mockFilePaths); + const walletSetupDir = createSetupDir(["solflare.setup.ts", "metamask.setup.ts", "phantom.setup.ts"]); - const result = await handleMock(mockFilePaths, ["all"]); + const result = await runSetup(walletSetupDir, ["all"]); - expect(vi.mocked(glob)).toHaveBeenCalled(); - // Verify the file list is sorted (glob should sort, but we verify the result) - const sortedPaths = [...mockFilePaths].sort(); + const expectedPaths = ["metamask.setup.ts", "phantom.setup.ts", "solflare.setup.ts"].map((filename) => + path.resolve(walletSetupDir, filename), + ); const firstResult = result[0]; if (firstResult) { - // The fileList should match the sorted order const fileListPaths = firstResult.fileList.map((f) => f.filePath); - expect(fileListPaths).toEqual(sortedPaths); + expect(fileListPaths).toEqual(expectedPaths); } }); it("should include fileList in all returned objects", async () => { - const mockFilePaths = [ - path.resolve(WALLET_SETUP_DIR, "metamask.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "phantom.setup.ts"), - ]; - - vi.mocked(glob).mockResolvedValue(mockFilePaths); + const walletSetupDir = createSetupDir(["metamask.setup.ts", "phantom.setup.ts"]); - const result = await handleMock(mockFilePaths, ["all"]); + const result = await runSetup(walletSetupDir, ["all"]); expect(result).toHaveLength(2); result.forEach((item) => { @@ -187,23 +159,17 @@ describe("getSetupFunction", () => { expect(item.fileList).toHaveLength(2); expect(item.fileList).toEqual( expect.arrayContaining([ - expect.objectContaining({ filePath: mockFilePaths[0] }), - expect.objectContaining({ filePath: mockFilePaths[1] }), + expect.objectContaining({ filePath: path.resolve(walletSetupDir, "metamask.setup.ts") }), + expect.objectContaining({ filePath: path.resolve(walletSetupDir, "phantom.setup.ts") }), ]), ); }); }); it("should correctly extract wallet names from file paths", async () => { - const mockFilePaths = [ - path.resolve(WALLET_SETUP_DIR, "metamask.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "metamask-two.setup.ts"), - path.resolve(WALLET_SETUP_DIR, "phantom.setup.ts"), - ]; - - vi.mocked(glob).mockResolvedValue(mockFilePaths); + const walletSetupDir = createSetupDir(["metamask.setup.ts", "metamask-two.setup.ts", "phantom.setup.ts"]); - const result = await handleMock(mockFilePaths, ["all"]); + const result = await runSetup(walletSetupDir, ["all"]); expect(result).toHaveLength(3); // Verify wallet names are correctly extracted diff --git a/src/utils/wallets/get-wallet-extension-id-from-browser.ts b/src/utils/wallets/get-wallet-extension-id-from-browser.ts index e101256..909a3c7 100644 --- a/src/utils/wallets/get-wallet-extension-id-from-browser.ts +++ b/src/utils/wallets/get-wallet-extension-id-from-browser.ts @@ -1,13 +1,7 @@ import type { BrowserContext } from "@playwright/test"; -import { z } from "zod"; import type { ExtensionName } from "@/types"; -const Extension = z.object({ - id: z.string(), - name: z.string(), -}); - -const Extensions = z.array(Extension); +type Extensions = Array<{ id: string; name: string }>; /** * Returns the extension ID for the given extension name. The ID is fetched from the `chrome://extensions` page. @@ -21,9 +15,8 @@ export async function getWalletExtensionIdFromBrowser(context: BrowserContext, e const page = await context.newPage(); await page.goto("chrome://extensions"); - const unparsedExtensions = await page.evaluate("chrome.management.getAll()"); + const allExtensions = (await page.evaluate("chrome.management.getAll()")) as Extensions; - const allExtensions = Extensions.parse(unparsedExtensions); const targetExtension = allExtensions.find( (extension) => extension.name.toLowerCase() === extensionName.toLowerCase(), ); diff --git a/src/wallets/keplr/actions/get-account-address.keplr.ts b/src/wallets/keplr/actions/get-account-address.keplr.ts index a9df450..e8aaad6 100644 --- a/src/wallets/keplr/actions/get-account-address.keplr.ts +++ b/src/wallets/keplr/actions/get-account-address.keplr.ts @@ -1,20 +1,18 @@ import { expect, type Locator, type Page } from "@playwright/test"; -import { type GetAccountAddressArgs, getAccountAddressSchema } from "../types"; +import type { GetAccountAddressArgs } from "../types"; type GetAccountAddress = GetAccountAddressArgs & { page: Page }; export async function getAccountAddress({ page, ...args }: GetAccountAddress) { - const parsedData = getAccountAddressSchema.parse({ ...args }); - // Make sure that the assets are visible before copying the account address const spendableAssetSearchbar = page.getByRole("textbox", { name: "Search for asset or chain (i.e. ATOM, Cosmos)", exact: true, }); - await spendableAssetSearchbar.fill(parsedData.chain); + await spendableAssetSearchbar.fill(args.chain); const spendableAssetList = page - .locator(`div:has-text("${parsedData.chain}")`) + .locator(`div:has-text("${args.chain}")`) .nth(-2) .filter({ hasNot: page.locator("span") }); @@ -22,22 +20,22 @@ export async function getAccountAddress({ page, ...args }: GetAccountAddress) { const isSpendableAssetVisible = await spendableAssetList.isVisible().catch(() => false); if (!isSpendableAssetVisible) { - throw Error(`Make sure "${parsedData.chain}" is activated.`); + throw Error(`Make sure "${args.chain}" is activated.`); } const allSpendableAssets = await spendableAssetList.locator("div").all(); expect(allSpendableAssets.length).toBeGreaterThan(0); - const copyWalletAddressContainer = page.locator(`div:has(div:has-text('${parsedData.walletName}'))`).nth(-3); + const copyWalletAddressContainer = page.locator(`div:has(div:has-text('${args.walletName}'))`).nth(-3); const copyWalletAddressPopover = copyWalletAddressContainer.locator("div:has(> div > svg)"); await copyWalletAddressPopover.click(); const popoverContainer = page.locator("div:has(> div[data-simplebar='init'])").last(); const popoverSearchContainer = popoverContainer.locator("div:has(> div > input)"); const popoverSearchInput = popoverSearchContainer.locator("input"); - await popoverSearchInput.fill(parsedData.chain); + await popoverSearchInput.fill(args.chain); - const _chains = popoverContainer.locator("div[cursor='pointer']", { hasText: parsedData.chain }); + const _chains = popoverContainer.locator("div[cursor='pointer']", { hasText: args.chain }); await expect(_chains.first()).toBeVisible({ timeout: 60_000 }); const chains = await _chains.all(); @@ -47,7 +45,7 @@ export async function getAccountAddress({ page, ...args }: GetAccountAddress) { let chainTag: string | undefined; if ("chainTag" in args) chainTag = args.chainTag; - const chainElement = chain.locator(`div`, { hasText: parsedData.chain }).last(); + const chainElement = chain.locator(`div`, { hasText: args.chain }).last(); const chainTagElement = chainTag ? chain.locator("div", { hasText: chainTag }).last() : null; const isChainTagElementVisible = chainTagElement @@ -57,7 +55,7 @@ export async function getAccountAddress({ page, ...args }: GetAccountAddress) { const chainTextContent = await chainElement.textContent(); const combinedChainText = chainTagTextContent ? `${chainTextContent} ${chainTagTextContent}` : chainTextContent; - const combinedParsedData = chainTag ? `${parsedData.chain} ${chainTag}` : parsedData.chain; + const combinedParsedData = chainTag ? `${args.chain} ${chainTag}` : args.chain; if (combinedChainText === combinedParsedData) { // Traversing up the DOM to find the address parent element @@ -68,7 +66,7 @@ export async function getAccountAddress({ page, ...args }: GetAccountAddress) { } if (!addressElement) { - throw Error(`Address for ${parsedData.walletName} account on "${parsedData.chain}" chain not found.`); + throw Error(`Address for ${args.walletName} account on "${args.chain}" chain not found.`); } // To get the address, we need to hover over the chain element and click on it. diff --git a/src/wallets/keplr/actions/rename-account.keplr.ts b/src/wallets/keplr/actions/rename-account.keplr.ts index e28dd60..2800355 100644 --- a/src/wallets/keplr/actions/rename-account.keplr.ts +++ b/src/wallets/keplr/actions/rename-account.keplr.ts @@ -1,21 +1,19 @@ import type { Page } from "@playwright/test"; -import { type RenameAccountArgs, renameAccountSchema } from "../types"; +import type { RenameAccountArgs } from "../types"; type RenameAccount = RenameAccountArgs & { page: Page }; export async function renameAccount({ page, currentAccountName, newAccountName }: RenameAccount) { - const parsedData = renameAccountSchema.parse({ currentAccountName, newAccountName }); - const settingsButton = page.getByRole("link", { name: "Settings", exact: true }); await settingsButton.click(); const activeAccount = page.locator("div[cursor='pointer']").first(); await activeAccount.click(); - const accountList = page.locator(`div`, { hasText: parsedData.currentAccountName }).nth(-4); + const accountList = page.locator(`div`, { hasText: currentAccountName }).nth(-4); const isAccountListVisible = await accountList.isVisible().catch(() => false); if (!isAccountListVisible) { - throw Error(`Account with name "${parsedData.currentAccountName}" not found`); + throw Error(`Account with name "${currentAccountName}" not found`); } const popoverTrigger = accountList.locator("div[cursor='pointer'] svg"); @@ -27,7 +25,7 @@ export async function renameAccount({ page, currentAccountName, newAccountName } await changeAccountNameButton.click(); const newWalletNameInput = page.locator("input[name='name']"); - await newWalletNameInput.fill(parsedData.newAccountName); + await newWalletNameInput.fill(newAccountName); const saveButton = page.locator("button:has-text('Save')"); await saveButton.click(); diff --git a/src/wallets/keplr/types.ts b/src/wallets/keplr/types.ts index 67ce9b7..9b60ac6 100644 --- a/src/wallets/keplr/types.ts +++ b/src/wallets/keplr/types.ts @@ -1,5 +1,4 @@ import type { Page } from "@playwright/test"; -import z from "zod"; import type { Keplr } from "./keplr"; const keplrChains = [ @@ -42,26 +41,21 @@ export interface AddAccountViaSeedPhrase extends AddAccount { seedPhrase: string; } -export const getAccountAddressSchema = z.discriminatedUnion("chain", [ - z.object({ - chain: z.literal(["Injective", "Injective (Testnet)", "Polygon"]), - walletName: z.string().min(1, "Wallet name cannot be an empty string"), - }), - z.object({ - chain: z.literal(["Bitcoin", "Bitcoin Signet", "Bitcoin Testnet"]), - chainTag: z.literal(["Taproot", "Native Segwit"]), - walletName: z.string().min(1, "Wallet name cannot be an empty string"), - }), -]); - -export type GetAccountAddressArgs = z.infer; - -export const renameAccountSchema = z.object({ - currentAccountName: z.string().min(1, "Current account name cannot be an empty string"), - newAccountName: z.string().min(1, "New account name cannot be an empty string"), -}); +export type GetAccountAddressArgs = + | { + chain: "Injective" | "Injective (Testnet)" | "Polygon"; + walletName: string; + } + | { + chain: "Bitcoin" | "Bitcoin Signet" | "Bitcoin Testnet"; + chainTag: "Taproot" | "Native Segwit"; + walletName: string; + }; -export type RenameAccountArgs = z.infer; +export type RenameAccountArgs = { + currentAccountName: string; + newAccountName: string; +}; export type KeplrFixture = { contextPath: string; diff --git a/src/wallets/metamask/actions/add-account.metamask.ts b/src/wallets/metamask/actions/add-account.metamask.ts index a080970..22e9d23 100644 --- a/src/wallets/metamask/actions/add-account.metamask.ts +++ b/src/wallets/metamask/actions/add-account.metamask.ts @@ -1,5 +1,4 @@ import { expect, type Locator, type Page } from "@playwright/test"; -import z from "zod"; import { skip } from "@/tests/utils/skip"; import { accountSelectors, homepageSelectors } from "../selectors/homepage-selectors.metamask"; import { onboardSelectors } from "../selectors/onboard-selectors.metamask"; @@ -10,9 +9,6 @@ type AddAccount = AddAccountArgs & { }; export async function addAccount({ page, privateKey, accountName }: AddAccount) { - const parsedAccountName = z.string().min(1, "Account name cannot be an empty string").trim().parse(accountName); - const parsedPrivateKey = z.string().min(1, "Private key cannot be an empty string").trim().parse(privateKey); - const accountMenuButton = page.getByTestId(homepageSelectors.accountMenuButton); await expect(accountMenuButton).toBeVisible({ timeout: 30_000 }); @@ -38,7 +34,7 @@ export async function addAccount({ page, privateKey, accountName }: AddAccount) await importAccountButton.click(); const inputField = page.locator("input[id='private-key-box']"); - await inputField.fill(parsedPrivateKey); + await inputField.fill(privateKey); const confirmButton = page.getByTestId(onboardSelectors.importAccountConfirmButton); await expect(confirmButton).toBeEnabled(); @@ -63,7 +59,7 @@ export async function addAccount({ page, privateKey, accountName }: AddAccount) if (activeAccountName) { await renameImportedAccount({ page, - accountName: parsedAccountName, + accountName, activeAccountLocator: activeAccount, activeAccountName, }); diff --git a/src/wallets/metamask/actions/add-custom-network.metamask.ts b/src/wallets/metamask/actions/add-custom-network.metamask.ts index 2cce1c6..e7d1b2d 100644 --- a/src/wallets/metamask/actions/add-custom-network.metamask.ts +++ b/src/wallets/metamask/actions/add-custom-network.metamask.ts @@ -1,20 +1,13 @@ import { expect, type Page } from "@playwright/test"; import { settingsSelectors } from "../selectors/homepage-selectors.metamask"; -import { type AddCustomNetwork, addCustomNetworkSchema } from "../types"; +import type { AddCustomNetwork } from "../types"; import { openSettings } from "./open-settings.metamask"; interface AddCustomNetworkArgs extends AddCustomNetwork { page: Page; } -export async function addCustomNetwork({ page, ...args }: AddCustomNetworkArgs) { - const { chainId, currencySymbol, networkName, rpcUrl } = addCustomNetworkSchema.parse( - { ...args }, - { - reportInput: true, - }, - ); - +export async function addCustomNetwork({ page, chainId, currencySymbol, networkName, rpcUrl }: AddCustomNetworkArgs) { await openSettings(page); const networksButton = page.getByTestId(settingsSelectors.networksButton); await networksButton.click(); diff --git a/src/wallets/metamask/types.ts b/src/wallets/metamask/types.ts index 652d099..9eeb835 100644 --- a/src/wallets/metamask/types.ts +++ b/src/wallets/metamask/types.ts @@ -1,5 +1,4 @@ import type { Page } from "@playwright/test"; -import z from "zod"; import type { Metamask } from "./metamask"; export type OnboardingArgs = @@ -18,14 +17,12 @@ export type AddAccountArgs = { accountName: string; }; -export const addCustomNetworkSchema = z.object({ - networkName: z.string().min(1, "Network name cannot be an empty string"), - rpcUrl: z.url(), - chainId: z.number().or(z.string().includes("0x")), - currencySymbol: z.string().toUpperCase().min(1, "Currency symbol cannot be an empty string"), -}); - -export type AddCustomNetwork = z.infer; +export type AddCustomNetwork = { + networkName: string; + rpcUrl: string; + chainId: number | string; + currencySymbol: string; +}; export type SwitchNetwork = | { diff --git a/src/wallets/petra/actions/rename-account.petra.ts b/src/wallets/petra/actions/rename-account.petra.ts index c67cca4..6b72dab 100644 --- a/src/wallets/petra/actions/rename-account.petra.ts +++ b/src/wallets/petra/actions/rename-account.petra.ts @@ -1,5 +1,4 @@ import { expect, type Page } from "@playwright/test"; -import z from "zod"; import { accountSelectors, homepageSelectors } from "../selectors/homepage-selectors.petra"; export type RenameAccount = { @@ -8,8 +7,6 @@ export type RenameAccount = { }; export async function renameAccount({ page, newAccountName }: RenameAccount) { - const parsedNewAccountName = z.string().min(1, "Account name cannot be an empty string").parse(newAccountName); - const settingsMenuButton = page.locator(homepageSelectors.settingsMenu); await settingsMenuButton.click(); @@ -23,17 +20,17 @@ export async function renameAccount({ page, newAccountName }: RenameAccount) { const currentAccountName = await renameInput.getAttribute("value"); - if (currentAccountName === parsedNewAccountName) { - throw Error(`The account to be renamed "${parsedNewAccountName}" already exists.`); + if (currentAccountName === newAccountName) { + throw Error(`The account to be renamed "${newAccountName}" already exists.`); } - await renameInput.fill(parsedNewAccountName); + await renameInput.fill(newAccountName); const saveButton = page.locator(accountSelectors.saveButton); await expect(saveButton).toBeEnabled(); await saveButton.click(); - await expect(page.getByText(parsedNewAccountName).first()).toBeVisible(); + await expect(page.getByText(newAccountName).first()).toBeVisible(); const backButton = page.locator(homepageSelectors.backButton); await backButton.click(); diff --git a/src/wallets/phantom/actions/get-account-address.phantom.ts b/src/wallets/phantom/actions/get-account-address.phantom.ts index f964b72..bb6ec1f 100644 --- a/src/wallets/phantom/actions/get-account-address.phantom.ts +++ b/src/wallets/phantom/actions/get-account-address.phantom.ts @@ -1,5 +1,4 @@ import type { Page } from "@playwright/test"; -import z from "zod"; import { menuSelectors, settingsSelectors } from "../selectors/homepage-selectors.phantom"; import type { GetAccountAddress } from "../types"; @@ -8,14 +7,13 @@ type GetAccountAddressArgs = GetAccountAddress & { page: Page }; const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); export async function getAccountAddress({ page, accountName, chain }: GetAccountAddressArgs) { - const parsedAccountName = z.string().min(1, "Account name cannot be an empty string").parse(accountName); const openMenuButton = page.getByTestId(menuSelectors.openMenuButton); await openMenuButton.click(); const manageAccountsButton = page.getByTestId(menuSelectors.manageAccountsButton); await manageAccountsButton.click(); - const accountProfileToSelect = page.getByTestId(`manage-accounts-sortable-${parsedAccountName}`); + const accountProfileToSelect = page.getByTestId(`manage-accounts-sortable-${accountName}`); await accountProfileToSelect.click(); const accountAddressesButton = page.getByRole("button", { name: /Account Address(?:es)?/i }); diff --git a/src/wallets/phantom/actions/switch-account.phantom.ts b/src/wallets/phantom/actions/switch-account.phantom.ts index 39cba26..af0054c 100644 --- a/src/wallets/phantom/actions/switch-account.phantom.ts +++ b/src/wallets/phantom/actions/switch-account.phantom.ts @@ -1,10 +1,7 @@ import type { Locator, Page } from "@playwright/test"; -import z from "zod"; import { menuSelectors } from "../selectors/homepage-selectors.phantom"; export async function switchAccount(page: Page, accountName: string) { - const parsedAccountName = z.string().min(1, "Account name cannot be an empty string").parse(accountName); - const openMenuButton = page.getByTestId(menuSelectors.openMenuButton); await openMenuButton.click(); @@ -15,14 +12,14 @@ export async function switchAccount(page: Page, accountName: string) { for (const account of accountButton) { const textContent = await account.textContent(); - if (textContent?.includes(parsedAccountName)) { + if (textContent?.includes(accountName)) { accountListButton = account; break; } } if (!accountListButton) { - throw new Error(`Account with name "${parsedAccountName}" not found in the account list.`); + throw new Error(`Account with name "${accountName}" not found in the account list.`); } await accountListButton.click(); diff --git a/src/wallets/solflare/actions/add-account.solflare.ts b/src/wallets/solflare/actions/add-account.solflare.ts index a29fce9..f174ed5 100644 --- a/src/wallets/solflare/actions/add-account.solflare.ts +++ b/src/wallets/solflare/actions/add-account.solflare.ts @@ -1,12 +1,10 @@ import type { Page } from "@playwright/test"; import { navigationMenuSelectors } from "../selectors/homepage-selectors.solflare"; -import { type AddAccountArgs, addAccountSchema } from "../types"; +import type { AddAccountArgs } from "../types"; type AddAccount = AddAccountArgs & { page: Page }; export async function addAccount({ page, privateKey, walletName }: AddAccount) { - const parsedArgs = addAccountSchema.parse({ privateKey, walletName }); - const openWalletSelectorMenu = page.getByTestId(navigationMenuSelectors.walletSelectorButton); await openWalletSelectorMenu.click(); @@ -18,8 +16,8 @@ export async function addAccount({ page, privateKey, walletName }: AddAccount) { const walletNameInput = page.getByTestId("input-name"); const privateKeyInput = page.getByTestId("input-private-key"); - await walletNameInput.fill(parsedArgs.walletName); - await privateKeyInput.fill(`${parsedArgs.privateKey}`); + await walletNameInput.fill(walletName); + await privateKeyInput.fill(`${privateKey}`); const importButton = page.getByTestId("btn-import"); await importButton.click(); diff --git a/src/wallets/solflare/types.ts b/src/wallets/solflare/types.ts index 0469931..a61d88e 100644 --- a/src/wallets/solflare/types.ts +++ b/src/wallets/solflare/types.ts @@ -1,5 +1,4 @@ import type { Page } from "@playwright/test"; -import z from "zod"; import type { Solflare } from "./solflare"; export type OnboardingArgs = { @@ -11,12 +10,10 @@ export type OnboardingArgs = { export type SwitchNetwork = Omit, "recoveryPhrase">["network"]; -export const addAccountSchema = z.object({ - walletName: z.string().min(1, "Wallet name cannot be an empty string"), - privateKey: z.string().min(1, "Private key cannot be an empty string"), -}); - -export type AddAccountArgs = z.infer; +export type AddAccountArgs = { + walletName: string; + privateKey: string; +}; export type RenameAccountArgs = { currentAccountName: string; diff --git a/tsup.config.ts b/tsup.config.ts index 9cad3d2..7af28bc 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ "src/wallets/phantom/index.ts", "src/wallets/solflare/index.ts", ], - external: ["@inquirer/checkbox", "@playwright/test", "adm-zip", "cli-progress", "commander", "glob", "tsx", "zod"], + external: ["@inquirer/checkbox", "@playwright/test", "adm-zip", "cli-progress", "commander"], outDir: "dist", format: "esm", platform: "node",