diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ff6399..6e0df37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # @tobelabs/chainwright +## 0.11.0 + +### Minor Changes + +- [Core] - Replace the 'glob' package with Node.js internal method + +## 0.10.17 + +### Patch Changes + +- [Release] - Fix failing release build by reverting from Typescript v7.0.2 to v5.9.3 + +## 0.10.16 + +### Patch Changes + +- [Core] - Update Playwright and other core dependencies + ## 0.10.15 ### Patch Changes diff --git a/dist/cli/index.d.ts b/dist/cli/index.d.ts index dccaca6..efe121b 100644 --- a/dist/cli/index.d.ts +++ b/dist/cli/index.d.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env -S node --import tsx +#!/usr/bin/env node declare function clientEntry(): Promise; export { clientEntry }; diff --git a/dist/cli/index.js b/dist/cli/index.js index 890fa1b..b669362 100755 --- a/dist/cli/index.js +++ b/dist/cli/index.js @@ -1,12 +1,12 @@ -#!/usr/bin/env -S node --import tsx -import z from"path";import{styleText as y}from"util";import De from"@inquirer/checkbox";import{Command as Ue}from"commander";import b from"fs";import H from"path";import{fileURLToPath as oe}from"url";var re=oe(import.meta.url),ae=H.dirname(re),P=H.resolve(ae,"..","..","generated-profile-name.types.ts"),ne=e=>e.replace(/(^\w)/g,o=>o.toUpperCase());async function G({walletName:e,profileName:o}){let r=ne(e),a=`export type ${r}Profiles = "${o}";`;if(b.existsSync(P)){let t=b.readFileSync(P,"utf-8"),c=t.match(new RegExp(`export type ${r}Profiles = ("[^"]+"(?:\\s*\\|\\s*"[^"]+")*)`));if(c){let n=c[0];if(!n.includes(`"${o}"`)){let p=n.concat(` | "${o}"`),l=t.replace(n,p);try{b.writeFileSync(P,l)}catch(s){console.error("Error updating existing profile name type: ",s)}}}else try{b.appendFileSync(P,` -${a}`,{encoding:"utf-8"})}catch(n){console.error("Error appending new profile name type: ",n)}}else try{b.writeFileSync(P,a)}catch(t){console.error("Error writing new profile name type: ",t)}}import ie from"path";import{pathToFileURL as le}from"url";import{styleText as ce}from"util";import{glob as pe}from"glob";import se from"path";function D(e){let o=se.basename(e),r=o.match(/^([a-z0-9_]+)(?:-[a-z0-9_]+)*\.setup\.(?:ts|js|mjs)$/i);if(!r)throw new Error(`Invalid wallet setup filename: ${o} (expected "[ -variant].setup.{ts,js,mjs}")`);return r[1]}var me=e=>e.replace(/\\/g,"/"),fe=e=>`${me(ie.resolve(e))}/**/*.setup.{ts,js,}`,de=e=>import(new URL(le(e)).href);async function U({walletSetupDir:e,selectedWallets:o}){let r=fe(e),a=(await pe(r,{dot:!0,absolute:!0,nodir:!0,windowsPathsNoEscape:!0})).sort(),t=o.length===1?o[0]:o,c=["metamask","solflare","petra","meteor","keplr","phantom"];Array.isArray(t)&&t.forEach(s=>{c.includes(s)||console.warn(ce("magenta",`Unsupported wallet: "${s}". Supported wallets are: ${c.join(", ")}`,{validateStream:!1}))});let p=(t==="all"?a:Array.isArray(t)?a.filter(s=>t.some(f=>s.includes(f))):a.filter(s=>s.includes(t))).map(s=>({filePath:s,walletName:D(s)}));if(!p.length||p.length===0)throw new Error([`No wallet setup file found at ${e} for wallet: "${o}".`,'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(` - `));return await Promise.all(p.map(async({filePath:s,walletName:f})=>{let m=await(await de(s)).default,{fn:g,config:d,password:x}=m;return{walletName:f,fileList:p,config:d,walletPassword:x,setupFunction:g}}))}import S from"fs";import O from"path";import{styleText as E}from"util";import{chromium as Ne}from"@playwright/test";import Ee from"path";var V=".wallet-cache";var K="wallet-setup",X="13.33.0",C="https://github.com/amaify/chainwright/releases/download/v0.1.0/",ue=`https://github.com/MetaMask/metamask-extension/releases/download/v${X}/`,we=`${ue}metamask-chrome-${X}.zip`,he=`${C}solflare-wallet-extension-v2.19.1.zip`,ge=`${C}petra-wallet-extension-v2.4.8.zip`,ye=`${C}phantom-wallet-extension-v26.10.0.zip`,xe=`${C}meteor-wallet-extension-v0.7.0.zip`,Se=`${C}keplr-wallet-extension-v0.13.39.zip`,T={metamask:{downloadUrl:we,extensionName:"MetaMask",sha256:"400a71744b0f3a107e5fcb33871c25a50a8da96f9633df1b9bdef58b555441c1"},solflare:{downloadUrl:he,extensionName:"Solflare Wallet",sha256:"08c90d006638f719245250552bb942ddc8ec287c586da8624b771592faffbcf8"},petra:{downloadUrl:ge,extensionName:"Petra Aptos Wallet",sha256:"cb7c876e1b3e6e0f394c8ca446f99bac58a0e9da0411eda8d664b4a21956e53f"},phantom:{downloadUrl:ye,extensionName:"Phantom",sha256:"29d5475f4957d3fc888f9e69766c8ff96865c9572ac3021c9fd7c175cf97e955"},meteor:{downloadUrl:xe,extensionName:"Meteor Wallet",sha256:"133eed4119accd21fbae9dd9ed9e9228a83c3ebd783c6407d708c18c8e20ce05"},keplr:{downloadUrl:Se,extensionName:"Keplr",sha256:"12182982784ac20c6741eaad036f4529d49dc4c222b0e6118e8647606e5a296f"}};function _(e){return Ee.resolve(process.cwd(),V,e)}import{z as I}from"zod";var Ae=I.object({id:I.string(),name:I.string()}),ve=I.array(Ae);async function Y(e,o){let r=await e.newPage();await r.goto("chrome://extensions");let a=await r.evaluate("chrome.management.getAll()"),t=ve.parse(a),c=t.find(n=>n.name.toLowerCase()===o.toLowerCase());if(!c)throw new Error([`[GetExtensionId] Extension with name ${o} not found.`,`Available extensions: ${t.map(n=>n.name).join(", ")}`].join(` -`));return await r.close(),c.id}import A from"fs";import k from"path";import{styleText as L}from"util";import Te from"adm-zip";import{createWriteStream as Le}from"fs";import{Readable as $e}from"stream";import{styleText as q}from"util";import be from"cli-progress";var Pe=12e4;async function Z({url:e,destination:o}){let r=new AbortController,a=setTimeout(()=>r.abort(),Pe);try{let t=await fetch(e,{redirect:"follow",signal:r.signal});if(!t.ok)throw new Error(`HTTP ${t.status} ${t.statusText}`);let c=parseInt(t.headers.get("content-length")||"0",10),n=0,p=$e.fromWeb(t.body),l=new be.SingleBar({format:`Downloading ${q("cyan","{bar}",{validateStream:!1})} {percentage}%`,clearOnComplete:!0,barCompleteChar:"\u2588",barIncompleteChar:"\u2591",hideCursor:!0});l.start(c,0,{speed:"N/A"}),await new Promise((s,f)=>{let i=Le(o);p.pipe(i),p.on("data",m=>{n+=m.length,l.update(n)}),i.on("error",f),i.on("finish",()=>{l.stop(),s(void 0)})})}catch(t){throw console.error(q("redBright",`\u274C Wallet extension download failed: ${t}`,{validateStream:!1})),t}finally{clearTimeout(a)}}import{createHash as Ce}from"crypto";import{createReadStream as _e}from"fs";var Fe=/^[a-f0-9]{64}$/i;async function We(e){let o=Ce("sha256"),r=_e(e);return await new Promise((a,t)=>{r.on("data",c=>o.update(c)),r.on("error",t),r.on("end",()=>a(o.digest("hex")))})}async function J({filePath:e,expectedSha256:o,label:r}){if(!Fe.test(o))throw new Error(`${r} has an invalid SHA-256 checksum.`);let a=await We(e);if(a.toLowerCase()!==o.toLowerCase())throw new Error([`${r} failed integrity verification.`,`Expected SHA-256: ${o}`,`Actual SHA-256: ${a}`,"The file was not extracted."].join(` -`));return a}var $=!1,M="";async function Q({downloadUrl:e,name:o,force:r,extensionSource:a}){let t=_(o),c=T[o],n=c.extensionName,p=k.join(t,`${o}-extension.zip`),l=k.join(t,`${o}-extension`),s=a&&"downloadUrl"in a,f=s?a.downloadUrl:e,i=a&&"localPath"in a?a.localPath:void 0,m=s?a.sha256:c.sha256;if($?M!==n&&$&&($=!1,M=n):M=n,r&&A.existsSync(t)&&!$&&(A.rmSync(t,{recursive:!0}),console.info(L("magenta",`\u{1F9F9} Removed ${n} because of the force flag`,{validateStream:!1}))),A.existsSync(t)||A.mkdir(t,{recursive:!0},d=>{if(d)throw Error("Failed to create cache directory");console.info(`\u2705 ${n} Cache directory created successfully.`)}),A.existsSync(l)?console.info(`\u2705 ${n} Version is downloaded already.`):i?console.info(L("cyanBright",`Using the local ${o} extension zip file`,{validateStream:!1})):(console.info(L("cyanBright",`\u{1F4E5} Downloading ${n} extension...`,{validateStream:!1})),await Z({url:f,destination:p}),console.info(L("green",`\u2705 ${o.toUpperCase()} Extension downloaded successfully.`,{validateStream:!1}))),A.existsSync(l)){if($)return console.info(L("magentaBright",`Using the cached ${n} extension for profile creation.`,{validateStream:!1})),l;console.info(L("yellow",`\u26A0\uFE0F Skipping ${n} cache creation: Cache already exists at ${l}. Use --force to overwrite.`,{validateStream:!1}))}else{let d=i||p;console.info("\u{1F6A8} Verifying file integrity..."),await J({filePath:d,expectedSha256:m,label:`${n} extension`}),console.info("\u{1F4E6} Extracting extension..."),new Te(d).extractAllTo(l,!0),console.info(`\u2705 ${n} Extension extracted successfully.`)}let g=k.join(l,"manifest.json");if(!A.existsSync(g))throw new Error(`\u274C (${n}) Invalid extension: manifest.json not found`);return $=!0,l}import{styleText as Ie}from"util";function B(e){return new Promise(o=>setTimeout(o,e))}var R=20,Re=6e3,ee=4e3;async function Oe(e){return e.pages().find(a=>{try{return a.url().startsWith("chrome-extension://")}catch(t){return console.error("[WaitForExtensionOnLoadPage] Error checking page URL:",t),!1}})}async function te(e,o){let r=0,a=null;if(o==="meteor")return await e.newPage();for(console.info(`Waiting ${ee}ms for browser to initialize...`),await B(ee);r<=R;)try{if(console.info(`Looking for extension page (attempt ${r+1}/${R})...`),r===R)throw new Error("Extension page not found after maximum retries");let t=await Oe(e);if(t){console.info(`Found extension page after ${r+1} polling attempts`),a=t;break}t||(r++,console.info(`Extension page not found, retrying (${r}/${R})...`),await B(Re))}catch(t){throw console.error("Error waiting for extension page:",t instanceof Error?t.message:"Unknown error"),new Error(`Extension failed to load properly after ${r} attempts!`)}return console.info(Ie("greenBright","\u2705 Extension page is properly loaded and ready",{validateStream:!1})),a}async function j({walletName:e,force:o,config:r,fileList:a,walletPassword:t,setupFunction:c}){let{downloadUrl:n,extensionName:p}=T[e],l=_(e),s=r?.profileName,f=s?`${s}`:"wallet-data",i=O.resolve(l,"extension-id.txt"),m=O.resolve(l,"extension-path.txt"),g=O.resolve(l,"password.txt"),d=O.resolve(l,f),x=await Q({downloadUrl:n,name:e,force:o,extensionSource:r?.extensionSource}),v=[`--disable-extensions-except=${x}`,`--load-extension=${x}`];if(S.existsSync(d)&&a.length>1)throw Error([E("yellowBright",[`\u274C ${f} directory already exists for ${p}.`,` +#!/usr/bin/env node +import j from"path";import{styleText as y}from"util";import Re from"@inquirer/checkbox";import{Command as Oe}from"commander";import P from"fs";import H from"path";import{fileURLToPath as oe}from"url";var re=oe(import.meta.url),ae=H.dirname(re),b=H.resolve(ae,"..","..","generated-profile-name.types.ts"),ne=e=>e.replace(/(^\w)/g,o=>o.toUpperCase());async function z({walletName:e,profileName:o}){let r=ne(e),a=`export type ${r}Profiles = "${o}";`;if(P.existsSync(b)){let t=P.readFileSync(b,"utf-8"),c=t.match(new RegExp(`export type ${r}Profiles = ("[^"]+"(?:\\s*\\|\\s*"[^"]+")*)`));if(c){let s=c[0];if(!s.includes(`"${o}"`)){let p=s.concat(` | "${o}"`),l=t.replace(s,p);try{P.writeFileSync(b,l)}catch(n){console.error("Error updating existing profile name type: ",n)}}}else try{P.appendFileSync(b,` +${a}`,{encoding:"utf-8"})}catch(s){console.error("Error appending new profile name type: ",s)}}else try{P.writeFileSync(b,a)}catch(t){console.error("Error writing new profile name type: ",t)}}import{globSync as ie}from"fs";import G from"path";import{pathToFileURL as le}from"url";import{styleText as ce}from"util";import se from"path";function N(e){let o=se.basename(e),r=o.match(/^([a-z0-9_]+)(?:-[a-z0-9_]+)*\.setup\.(?:ts|js|mjs)$/i);if(!r)throw new Error(`Invalid wallet setup filename: ${o} (expected "[ -variant].setup.{ts,js,mjs}")`);return r[1]}var pe=e=>e.replace(/\\/g,"/"),me=e=>`${pe(G.resolve(e))}/**/*.setup.{ts,js,mjs}`,fe=e=>import(new URL(le(e)).href);async function D({walletSetupDir:e,selectedWallets:o}){let r=me(e),a=ie(r,{withFileTypes:!0}).filter(n=>n.isFile()).map(n=>G.join(n.parentPath,n.name)).sort(),t=o.length===1?o[0]:o,c=["metamask","solflare","petra","meteor","keplr","phantom"];Array.isArray(t)&&t.forEach(n=>{c.includes(n)||console.warn(ce("magenta",`Unsupported wallet: "${n}". Supported wallets are: ${c.join(", ")}`,{validateStream:!1}))});let p=(t==="all"?a:Array.isArray(t)?a.filter(n=>t.some(f=>n.includes(f))):a.filter(n=>n.includes(t))).map(n=>({filePath:n,walletName:N(n)}));if(!p.length||p.length===0)throw new Error([`No wallet setup file found at ${e} for wallet: "${o}".`,'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(` + `));return await Promise.all(p.map(async({filePath:n,walletName:f})=>{let m=await(await fe(n)).default,{fn:g,config:d,password:x}=m;return{walletName:f,fileList:p,config:d,walletPassword:x,setupFunction:g}}))}import S from"fs";import R from"path";import{styleText as E}from"util";import{chromium as Ie}from"@playwright/test";import Se from"path";var V=".wallet-cache";var K="wallet-setup",X="13.33.0",C="https://github.com/amaify/chainwright/releases/download/v0.1.0/",de=`https://github.com/MetaMask/metamask-extension/releases/download/v${X}/`,ue=`${de}metamask-chrome-${X}.zip`,we=`${C}solflare-wallet-extension-v2.19.1.zip`,he=`${C}petra-wallet-extension-v2.4.8.zip`,ge=`${C}phantom-wallet-extension-v26.10.0.zip`,ye=`${C}meteor-wallet-extension-v0.7.0.zip`,xe=`${C}keplr-wallet-extension-v0.13.39.zip`,W={metamask:{downloadUrl:ue,extensionName:"MetaMask",sha256:"400a71744b0f3a107e5fcb33871c25a50a8da96f9633df1b9bdef58b555441c1"},solflare:{downloadUrl:we,extensionName:"Solflare Wallet",sha256:"08c90d006638f719245250552bb942ddc8ec287c586da8624b771592faffbcf8"},petra:{downloadUrl:he,extensionName:"Petra Aptos Wallet",sha256:"cb7c876e1b3e6e0f394c8ca446f99bac58a0e9da0411eda8d664b4a21956e53f"},phantom:{downloadUrl:ge,extensionName:"Phantom",sha256:"29d5475f4957d3fc888f9e69766c8ff96865c9572ac3021c9fd7c175cf97e955"},meteor:{downloadUrl:ye,extensionName:"Meteor Wallet",sha256:"133eed4119accd21fbae9dd9ed9e9228a83c3ebd783c6407d708c18c8e20ce05"},keplr:{downloadUrl:xe,extensionName:"Keplr",sha256:"12182982784ac20c6741eaad036f4529d49dc4c222b0e6118e8647606e5a296f"}};function _(e){return Se.resolve(process.cwd(),V,e)}async function Y(e,o){let r=await e.newPage();await r.goto("chrome://extensions");let a=await r.evaluate("chrome.management.getAll()"),t=a.find(c=>c.name.toLowerCase()===o.toLowerCase());if(!t)throw new Error([`[GetExtensionId] Extension with name ${o} not found.`,`Available extensions: ${a.map(c=>c.name).join(", ")}`].join(` +`));return await r.close(),t.id}import A from"fs";import U from"path";import{styleText as L}from"util";import _e from"adm-zip";import{createWriteStream as Ee}from"fs";import{Readable as Ae}from"stream";import{styleText as q}from"util";import ve from"cli-progress";var Le=12e4;async function Z({url:e,destination:o}){let r=new AbortController,a=setTimeout(()=>r.abort(),Le);try{let t=await fetch(e,{redirect:"follow",signal:r.signal});if(!t.ok)throw new Error(`HTTP ${t.status} ${t.statusText}`);let c=parseInt(t.headers.get("content-length")||"0",10),s=0,p=Ae.fromWeb(t.body),l=new ve.SingleBar({format:`Downloading ${q("cyan","{bar}",{validateStream:!1})} {percentage}%`,clearOnComplete:!0,barCompleteChar:"\u2588",barIncompleteChar:"\u2591",hideCursor:!0});l.start(c,0,{speed:"N/A"}),await new Promise((n,f)=>{let i=Ee(o);p.pipe(i),p.on("data",m=>{s+=m.length,l.update(s)}),i.on("error",f),i.on("finish",()=>{l.stop(),n(void 0)})})}catch(t){throw console.error(q("redBright",`\u274C Wallet extension download failed: ${t}`,{validateStream:!1})),t}finally{clearTimeout(a)}}import{createHash as $e}from"crypto";import{createReadStream as Pe}from"fs";var be=/^[a-f0-9]{64}$/i;async function Ce(e){let o=$e("sha256"),r=Pe(e);return await new Promise((a,t)=>{r.on("data",c=>o.update(c)),r.on("error",t),r.on("end",()=>a(o.digest("hex")))})}async function J({filePath:e,expectedSha256:o,label:r}){if(!be.test(o))throw new Error(`${r} has an invalid SHA-256 checksum.`);let a=await Ce(e);if(a.toLowerCase()!==o.toLowerCase())throw new Error([`${r} failed integrity verification.`,`Expected SHA-256: ${o}`,`Actual SHA-256: ${a}`,"The file was not extracted."].join(` +`));return a}var $=!1,k="";async function Q({downloadUrl:e,name:o,force:r,extensionSource:a}){let t=_(o),c=W[o],s=c.extensionName,p=U.join(t,`${o}-extension.zip`),l=U.join(t,`${o}-extension`),n=a&&"downloadUrl"in a,f=n?a.downloadUrl:e,i=a&&"localPath"in a?a.localPath:void 0,m=n?a.sha256:c.sha256;if($?k!==s&&$&&($=!1,k=s):k=s,r&&A.existsSync(t)&&!$&&(A.rmSync(t,{recursive:!0}),console.info(L("magenta",`\u{1F9F9} Removed ${s} because of the force flag`,{validateStream:!1}))),A.existsSync(t)||A.mkdir(t,{recursive:!0},d=>{if(d)throw Error("Failed to create cache directory");console.info(`\u2705 ${s} Cache directory created successfully.`)}),A.existsSync(l)?console.info(`\u2705 ${s} Version is downloaded already.`):i?console.info(L("cyanBright",`Using the local ${o} extension zip file`,{validateStream:!1})):(console.info(L("cyanBright",`\u{1F4E5} Downloading ${s} extension...`,{validateStream:!1})),await Z({url:f,destination:p}),console.info(L("green",`\u2705 ${o.toUpperCase()} Extension downloaded successfully.`,{validateStream:!1}))),A.existsSync(l)){if($)return console.info(L("magentaBright",`Using the cached ${s} extension for profile creation.`,{validateStream:!1})),l;console.info(L("yellow",`\u26A0\uFE0F Skipping ${s} cache creation: Cache already exists at ${l}. Use --force to overwrite.`,{validateStream:!1}))}else{let d=i||p;console.info("\u{1F6A8} Verifying file integrity..."),await J({filePath:d,expectedSha256:m,label:`${s} extension`}),console.info("\u{1F4E6} Extracting extension..."),new _e(d).extractAllTo(l,!0),console.info(`\u2705 ${s} Extension extracted successfully.`)}let g=U.join(l,"manifest.json");if(!A.existsSync(g))throw new Error(`\u274C (${s}) Invalid extension: manifest.json not found`);return $=!0,l}import{styleText as Fe}from"util";function M(e){return new Promise(o=>setTimeout(o,e))}var I=20,Te=6e3,ee=4e3;async function We(e){return e.pages().find(a=>{try{return a.url().startsWith("chrome-extension://")}catch(t){return console.error("[WaitForExtensionOnLoadPage] Error checking page URL:",t),!1}})}async function te(e,o){let r=0,a=null;if(o==="meteor")return await e.newPage();for(console.info(`Waiting ${ee}ms for browser to initialize...`),await M(ee);r<=I;)try{if(console.info(`Looking for extension page (attempt ${r+1}/${I})...`),r===I)throw new Error("Extension page not found after maximum retries");let t=await We(e);if(t){console.info(`Found extension page after ${r+1} polling attempts`),a=t;break}t||(r++,console.info(`Extension page not found, retrying (${r}/${I})...`),await M(Te))}catch(t){throw console.error("Error waiting for extension page:",t instanceof Error?t.message:"Unknown error"),new Error(`Extension failed to load properly after ${r} attempts!`)}return console.info(Fe("greenBright","\u2705 Extension page is properly loaded and ready",{validateStream:!1})),a}async function B({walletName:e,force:o,config:r,fileList:a,walletPassword:t,setupFunction:c}){let{downloadUrl:s,extensionName:p}=W[e],l=_(e),n=r?.profileName,f=n?`${n}`:"wallet-data",i=R.resolve(l,"extension-id.txt"),m=R.resolve(l,"extension-path.txt"),g=R.resolve(l,"password.txt"),d=R.resolve(l,f),x=await Q({downloadUrl:s,name:e,force:o,extensionSource:r?.extensionSource}),v=[`--disable-extensions-except=${x}`,`--load-extension=${x}`];if(S.existsSync(d)&&a.length>1)throw Error([E("yellowBright",[`\u274C ${f} directory already exists for ${p}.`,` To setup another wallet profile, add a profile name to the wallet setup function.`,E(["blueBright","italic"],'Example: defineWalletSetup(async ({ context, walletPage }) => { ... }, { profileName: "profile-name" });'),E("italic","You can also use the --force flag to overwrite the existing cache.")].join(` `),{validateStream:!1})].join(` -`));if(S.existsSync(d))return;let u=await Ne.launchPersistentContext(d,{headless:!1,args:v,slowMo:r?.slowMo??0});console.info(E("magentaBright",`\u{1F9E9}\u{1F680} Starting Chrome extension for ${e.toUpperCase()}`,{validateStream:!1}));let N=await te(u,e);if(!S.existsSync(i)&&!S.existsSync(m)){let W=await Y(u,p);console.info(E("magentaBright",`\u{1F194} ${p} extension ID: ${W}`,{validateStream:!1})),S.writeFileSync(i,W,"utf-8"),console.info(E("cyanBright",`\u{1F4BE} Saved extension ID to: ${i}`,{validateStream:!1})),S.writeFileSync(m,x,"utf-8"),console.info(E("blueBright",`\u{1F4C1} Saved extension Path to: ${m}`,{validateStream:!1})),S.writeFileSync(g,t,"utf-8"),console.info(E("yellowBright",`\u{1F511} Saved ${e} password to: ${g}`,{validateStream:!1}))}try{await c({context:u,walletPage:N})}catch(W){throw await u.close(),S.rmSync(l,{force:!0,recursive:!0}),Error(`Error setting up wallet: ${W.message}`)}await u.close()}var ke=z.resolve(process.cwd(),"tests",K),F=1;async function Me(){let e=new Ue;e.name(y("yellow","Chainwright")).description(y("green","A CLI tool for setting up wallet cache for E2E testing of web3 applications")).version(y("blue","0.0.0")),e.argument("[dir]","Directory containing the wallet setup functions",z.resolve(ke)).option("--headless","Build cache in the headless browser mode. Alternatively, set the `HEADLESS` env variable to `true`",!1).option("-f, --force","Force the creation of cache even if it already exists",!1).option("-a, --all","Setup all wallets","all").option("--kp, --keplr","Setup Keplr","keplr").option("-m, --metamask","Setup MetaMask","metamask").option("--mt, --meteor","Setup Meteor","meteor").option("--pt, --petra","Setup Petra","petra").option("--ph, --phantom","Setup Phantom","phantom").option("-s, --solflare","Setup Solflare","solflare").option("--wls, --wallets ","Specify wallets to setup (e.g., --wallets keplr metamask)").action(async(o,r)=>{let a=["all","metamask","solflare","petra","meteor","keplr","phantom"],t=Object.keys(r).filter(i=>a.includes(i)?r[i]===!0:!1),c=t.length>0,n=r.wallets,p=n||(c?t:await De({message:"Select the wallet you want to setup",choices:[{name:"All",value:"all"},{name:"Keplr",value:"keplr"},{name:"MetaMask",value:"metamask"},{name:"Meteor",value:"meteor"},{name:"Petra",value:"petra"},{name:"Phantom",value:"phantom"},{name:"Solflare",value:"solflare"}],validate:i=>{let m=i.map(g=>g.value);return m.includes("all")&&m.length>1?'Select either "All" or specific wallets, not both.':!0},pageSize:10})),l=z.resolve(process.cwd(),o);r.headless&&(process.env.HEADLESS=!0);let s=await U({walletSetupDir:l,selectedWallets:p}),f=0;for(let{walletName:i,config:m,walletPassword:g,setupFunction:d,fileList:x}of s){try{console.info(y("cyanBright",` - Setting up cache for ${i}...`,{validateStream:!1})),await j({walletName:i,config:m,setupFunction:d,fileList:x,force:r.force,walletPassword:g}),m.profileName&&await G({walletName:i,profileName:m.profileName})}catch(v){if(v.message.includes("directory already exists")&&console.warn(v.message),!v.message.includes("directory already exists")){console.error(y("redBright",` - \u274C Failed to setup cache for ${i}: ${v.message}`,{validateStream:!1}));let u=0;for(;F>u;){console.info(`${y("yellow",`Retry Attempt ${u+1} of ${F} for ${i}...`,{validateStream:!1})}`),console.info(y("yellow","Retrying wallet setup...",{validateStream:!1}));try{await j({walletName:i,config:m,setupFunction:d,fileList:x,force:r.force,walletPassword:g});break}catch(N){u+1console.error(y("redBright",`Failed to run the CLI: ${e.message})`,{validateStream:!1})));export{Me as clientEntry}; +`));if(S.existsSync(d))return;let u=await Ie.launchPersistentContext(d,{headless:!1,args:v,slowMo:r?.slowMo??0});console.info(E("magentaBright",`\u{1F9E9}\u{1F680} Starting Chrome extension for ${e.toUpperCase()}`,{validateStream:!1}));let O=await te(u,e);if(!S.existsSync(i)&&!S.existsSync(m)){let T=await Y(u,p);console.info(E("magentaBright",`\u{1F194} ${p} extension ID: ${T}`,{validateStream:!1})),S.writeFileSync(i,T,"utf-8"),console.info(E("cyanBright",`\u{1F4BE} Saved extension ID to: ${i}`,{validateStream:!1})),S.writeFileSync(m,x,"utf-8"),console.info(E("blueBright",`\u{1F4C1} Saved extension Path to: ${m}`,{validateStream:!1})),S.writeFileSync(g,t,"utf-8"),console.info(E("yellowBright",`\u{1F511} Saved ${e} password to: ${g}`,{validateStream:!1}))}try{await c({context:u,walletPage:O})}catch(T){throw await u.close(),S.rmSync(l,{force:!0,recursive:!0}),Error(`Error setting up wallet: ${T.message}`)}await u.close()}var Ne=j.resolve(process.cwd(),"tests",K),F=1;async function De(){let e=new Oe;e.name(y("yellow","Chainwright")).description(y("green","A CLI tool for setting up wallet cache for E2E testing of web3 applications")).version(y("blue","0.0.0")),e.argument("[dir]","Directory containing the wallet setup functions",j.resolve(Ne)).option("--headless","Build cache in the headless browser mode. Alternatively, set the `HEADLESS` env variable to `true`",!1).option("-f, --force","Force the creation of cache even if it already exists",!1).option("-a, --all","Setup all wallets","all").option("--kp, --keplr","Setup Keplr","keplr").option("-m, --metamask","Setup MetaMask","metamask").option("--mt, --meteor","Setup Meteor","meteor").option("--pt, --petra","Setup Petra","petra").option("--ph, --phantom","Setup Phantom","phantom").option("-s, --solflare","Setup Solflare","solflare").option("--wls, --wallets ","Specify wallets to setup (e.g., --wallets keplr metamask)").action(async(o,r)=>{let a=["all","metamask","solflare","petra","meteor","keplr","phantom"],t=Object.keys(r).filter(i=>a.includes(i)?r[i]===!0:!1),c=t.length>0,s=r.wallets,p=s||(c?t:await Re({message:"Select the wallet you want to setup",choices:[{name:"All",value:"all"},{name:"Keplr",value:"keplr"},{name:"MetaMask",value:"metamask"},{name:"Meteor",value:"meteor"},{name:"Petra",value:"petra"},{name:"Phantom",value:"phantom"},{name:"Solflare",value:"solflare"}],validate:i=>{let m=i.map(g=>g.value);return m.includes("all")&&m.length>1?'Select either "All" or specific wallets, not both.':!0},pageSize:10})),l=j.resolve(process.cwd(),o);r.headless&&(process.env.HEADLESS=!0);let n=await D({walletSetupDir:l,selectedWallets:p}),f=0;for(let{walletName:i,config:m,walletPassword:g,setupFunction:d,fileList:x}of n){try{console.info(y("cyanBright",` + Setting up cache for ${i}...`,{validateStream:!1})),await B({walletName:i,config:m,setupFunction:d,fileList:x,force:r.force,walletPassword:g}),m.profileName&&await z({walletName:i,profileName:m.profileName})}catch(v){if(v.message.includes("directory already exists")&&console.warn(v.message),!v.message.includes("directory already exists")){console.error(y("redBright",` + \u274C Failed to setup cache for ${i}: ${v.message}`,{validateStream:!1}));let u=0;for(;F>u;){console.info(`${y("yellow",`Retry Attempt ${u+1} of ${F} for ${i}...`,{validateStream:!1})}`),console.info(y("yellow","Retrying wallet setup...",{validateStream:!1}));try{await B({walletName:i,config:m,setupFunction:d,fileList:x,force:r.force,walletPassword:g});break}catch(O){u+1console.error(y("redBright",`Failed to run the CLI: ${e.message})`,{validateStream:!1})));export{De as clientEntry}; diff --git a/dist/wallets/keplr/index.d.ts b/dist/wallets/keplr/index.d.ts index baf17df..e7c8a77 100644 --- a/dist/wallets/keplr/index.d.ts +++ b/dist/wallets/keplr/index.d.ts @@ -3,7 +3,6 @@ import { W as WorkerScopeFixture } from '../../worker-scope-context-DSkOcWf-.js' export { w as workerScopeContext } from '../../worker-scope-context-DSkOcWf-.js'; import * as _playwright_test from '@playwright/test'; import { BrowserContext, Page } from '@playwright/test'; -import z from 'zod'; declare class KeplrProfile { readonly name: "keplr"; @@ -41,20 +40,18 @@ interface AddAccountViaPrivateKey extends AddAccount { interface AddAccountViaSeedPhrase extends AddAccount { seedPhrase: string; } -declare const getAccountAddressSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{ - chain: z.ZodLiteral<"Injective" | "Injective (Testnet)" | "Polygon">; - walletName: z.ZodString; -}, z.core.$strip>, z.ZodObject<{ - chain: z.ZodLiteral<"Bitcoin" | "Bitcoin Signet" | "Bitcoin Testnet">; - chainTag: z.ZodLiteral<"Taproot" | "Native Segwit">; - walletName: z.ZodString; -}, z.core.$strip>], "chain">; -type GetAccountAddressArgs = z.infer; -declare const renameAccountSchema: z.ZodObject<{ - currentAccountName: z.ZodString; - newAccountName: z.ZodString; -}, z.core.$strip>; -type RenameAccountArgs = z.infer; +type GetAccountAddressArgs = { + chain: "Injective" | "Injective (Testnet)" | "Polygon"; + walletName: string; +} | { + chain: "Bitcoin" | "Bitcoin Signet" | "Bitcoin Testnet"; + chainTag: "Taproot" | "Native Segwit"; + walletName: string; +}; +type RenameAccountArgs = { + currentAccountName: string; + newAccountName: string; +}; type KeplrFixture = { contextPath: string; keplr: Keplr; diff --git a/dist/wallets/keplr/index.js b/dist/wallets/keplr/index.js index 4346849..9cfbda6 100644 --- a/dist/wallets/keplr/index.js +++ b/dist/wallets/keplr/index.js @@ -1,4 +1,4 @@ -import tt from"fs";import St from"path";import{chromium as Ct}from"@playwright/test";import vt from"path";var Y=".wallet-cache",q=".wallet-context";var Q="13.33.0",W="https://github.com/amaify/chainwright/releases/download/v0.1.0/",bt=`https://github.com/MetaMask/metamask-extension/releases/download/v${Q}/`,jt=`${bt}metamask-chrome-${Q}.zip`,Ht=`${W}solflare-wallet-extension-v2.19.1.zip`,zt=`${W}petra-wallet-extension-v2.4.8.zip`,Gt=`${W}phantom-wallet-extension-v26.10.0.zip`,Xt=`${W}meteor-wallet-extension-v0.7.0.zip`,Jt=`${W}keplr-wallet-extension-v0.13.39.zip`;async function E(t){return vt.resolve(process.cwd(),q,t)}import kt from"path";function y(t){return kt.resolve(process.cwd(),Y,t)}import Z from"fs";import Bt from"path";async function F(t){try{let o=y(t),e=Bt.resolve(o,"extension-path.txt");if(!Z.existsSync(e))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=Z.readFileSync(e,"utf-8").trim();if(!a)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return a}catch(o){throw new Error(`\u274C Failed to get ${t} extension path: ${o.message}`)}}function N(t,o){let e=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(e.push("--headless=new"),o>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),e}async function et({wallet:t,workerInfo:o,profileName:e,slowMo:a}){let r=await E(o.workerIndex.toString()),n=y(t.name),c=St.resolve(n,e??"wallet-data");if(!tt.existsSync(c))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);tt.cpSync(c,r,{recursive:!0,force:!0});let u=await F(t.name),m=N(u,a??0),p=await Ct.launchPersistentContext(r,{headless:!1,args:m,slowMo:process.env.HEADLESS?0:a}),l=await t.indexUrl(),d=p.pages()[0];return d||(d=await p.newPage()),await d.goto(l),{context:p,walletPage:d,contextPath:r}}import{expect as it}from"@playwright/test";function _(t){return new Promise(o=>setTimeout(o,t))}async function b(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}import ot from"fs";import Tt from"path";async function K(t){let o=y(t),e=Tt.resolve(o,"password.txt");try{if(!ot.existsSync(e))throw new Error("\u274C password.txt not found. Run setup script first.");return ot.readFileSync(e,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Wt}from"@playwright/test";async function at({context:t,path:o,locator:e}){let a;try{await Wt.poll(async()=>(a=t.pages().filter(r=>r.url().startsWith("chrome-extension://")).find(r=>r.url().match(o)),!!a),{timeout:9e4}).toBe(!0)}catch{let r=t.pages().filter(n=>n.url().startsWith("chrome-extension://")).map(n=>n.url());throw new Error([`Popup page with path "${o}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(r)}`].join(` -`))}if(!a)throw new Error(`Popup page with path ${o} not found in context.`);return await Et(a,e),await a.setViewportSize({width:360,height:592}),a}async function Et(t,o){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(o).first().waitFor({state:"attached",timeout:45e3})}import rt from"fs";import It from"path";async function nt(t){let o=y(t),e=It.resolve(o,"extension-id.txt");try{if(!rt.existsSync(e))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return rt.readFileSync(e,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var A=class{name="keplr";onboardingPath="register.html";async indexUrl(){return`chrome-extension://${await this.extensionId()}/sidePanel.html`}async onboardingUrl(){return`chrome-extension://${await this.extensionId()}/${this.onboardingPath}`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/popup.html`}async extensionId(){return await nt(this.name)}async promptPage(o){let e=await this.promptUrl();return await at({context:o,path:e,locator:"div[data-simplebar='init']"})}};var x={importExistingWalletButton:"button:has-text('Import an existing wallet')",usePrivateKeyButton:"button:has-text('Use recovery phrase or private key')",privateKeyTabButton:"button:has-text('Private key')",privateKeyInput:"input[type='password']",importButton:"button:has-text('Import')",walletNameInput:"input[name='name']",walletPasswordInput:"input[name='password']",confirmWalletPasswordInput:"input[name='confirmPassword']",nextButton:"button:has-text('Next')",searchNetworkInput:"input[placeholder='Search networks']",saveButton:"button:has-text('Save')",finishButton:"button:has-text('Finish')"};function Ft(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async function k({page:t,privateKey:o,walletName:e,chains:a,mode:r="onboard"}){await t.locator(x.importExistingWalletButton).click(),await t.locator(x.usePrivateKeyButton).click(),await t.getByRole("button",{name:"Private key",exact:!0}).click(),await t.locator(x.privateKeyInput).fill(o),await st({page:t,walletName:e,mode:r,chains:a})}async function B({page:t,seedPhrase:o,walletName:e,chains:a,mode:r="onboard"}){await t.locator(x.importExistingWalletButton).click(),await t.locator(x.usePrivateKeyButton).click(),await t.getByRole("button",{name:"12 words",exact:!0}).click();let m=t.locator("input[type='password']"),p=o.split(" ");await m.first().fill(p[0]),await m.first().press("Tab");for(let l=1;l div").first().locator("div").filter({hasText:new RegExp(`^${Ft(h)}$`,"i")}).nth(2).locator("../../../../..");await f.waitFor({state:"visible",timeout:2e4}),await f.locator("input[type='checkbox']").getAttribute("checked")===null&&await f.click()}let w=t.locator(x.saveButton);if(await w.scrollIntoViewIfNeeded(),await w.click(),await _(2e3),e==="onboard"){await t.goto(await r.indexUrl());return}if(e==="add-account-single"){let h=t.locator(x.finishButton);await h.waitFor({state:"visible",timeout:2e4}),await it(h).toBeEnabled({timeout:2e4}),await h.click()}}async function L(t){let e=await new A().onboardingUrl();await t.getByRole("link",{name:"Settings",exact:!0}).click(),await t.locator("div[cursor='pointer']").first().click(),await t.getByRole("button",{name:"Add Wallet",exact:!0}).click();let c;if(await it.poll(async()=>(c=t.context().pages().find(u=>u.url().match(e)),!!c),{timeout:3e4}).toBe(!0).catch(u=>{console.error(`Failed to find onboarding page with URL matching ${e}. Original error: ${u}`)}),!c)throw new Error(`Onboarding page not found. Expected URL: ${e}`);return await b(c),await c.bringToFront(),c}async function ct({page:t,chains:o,walletName:e,mode:a,...r}){let n=await L(t);"privateKey"in r&&await k({page:n,privateKey:r.privateKey,walletName:e,chains:o,mode:a}),"seedPhrase"in r&&await B({page:n,seedPhrase:r.seedPhrase,walletName:e,chains:o,mode:a}),await t.locator("div:has(div:has-text('Select Wallet'))").nth(-4).locator("div:has(> div > svg)").first().click(),await t.getByRole("link",{name:"Home",exact:!0}).click()}import{expect as Nt}from"@playwright/test";var R={approveButton:"button:has-text('Approve')",rejectButton:"button[color='secondary']"};async function lt(t){let o=t.locator(R.approveButton);await Nt(o).toBeEnabled({timeout:2e4}),await o.click()}async function pt(t){t.getByRole("button",{name:"Approve",exact:!0}).click(),await _(1e3)}import{expect as U}from"@playwright/test";import P from"zod";var dt=P.discriminatedUnion("chain",[P.object({chain:P.literal(["Injective","Injective (Testnet)","Polygon"]),walletName:P.string().min(1,"Wallet name cannot be an empty string")}),P.object({chain:P.literal(["Bitcoin","Bitcoin Signet","Bitcoin Testnet"]),chainTag:P.literal(["Taproot","Native Segwit"]),walletName:P.string().min(1,"Wallet name cannot be an empty string")})]),mt=P.object({currentAccountName:P.string().min(1,"Current account name cannot be an empty string"),newAccountName:P.string().min(1,"New account name cannot be an empty string")});async function ut({page:t,...o}){let e=dt.parse({...o});await t.getByRole("textbox",{name:"Search for asset or chain (i.e. ATOM, Cosmos)",exact:!0}).fill(e.chain);let r=t.locator(`div:has-text("${e.chain}")`).nth(-2).filter({hasNot:t.locator("span")});if(await r.waitFor({state:"attached",timeout:3e4}),!await r.isVisible().catch(()=>!1))throw Error(`Make sure "${e.chain}" is activated.`);let c=await r.locator("div").all();U(c.length).toBeGreaterThan(0),await t.locator(`div:has(div:has-text('${e.walletName}'))`).nth(-3).locator("div:has(> div > svg)").click();let p=t.locator("div:has(> div[data-simplebar='init'])").last();await p.locator("div:has(> div > input)").locator("input").fill(e.chain);let w=p.locator("div[cursor='pointer']",{hasText:e.chain});await U(w.first()).toBeVisible({timeout:6e4});let h=await w.all(),g;for(let v of h){let T;"chainTag"in o&&(T=o.chainTag);let G=v.locator("div",{hasText:e.chain}).last(),V=T?v.locator("div",{hasText:T}).last():null,X=(V?await V?.isVisible().catch(()=>!1):!1)?await V?.textContent():null,J=await G.textContent(),At=X?`${J} ${X}`:J,Pt=T?`${e.chain} ${T}`:e.chain;if(At===Pt){g=G.locator("xpath=../../../..");break}}if(!g)throw Error(`Address for ${e.walletName} account on "${e.chain}" chain not found.`);await g.hover(),await g.scrollIntoViewIfNeeded(),await g.click();let f=await t.evaluate(async()=>await navigator.clipboard.readText());return U(f).not.toBeFalsy(),f}var I={openSidebarMenuButton:"div[cursor='pointer']:has(> div[cursor='pointer'])",menuPopupContent:"div[id='modal-root-3']",lockWalletButton:"div:has(> div:has-text('Lock Wallet'))",settingsButton:"div:has(a[href='#/settings'])"},M={unlockButton:"button[type='submit']:has-text('Unlock')",passwordInput:"input[placeholder='Type Your Password']"};async function wt(t){await t.locator(I.openSidebarMenuButton).click(),await t.locator(I.lockWalletButton).nth(-1).click(),await t.getByText("Welcome Back").waitFor({state:"visible",timeout:3e4})}import{styleText as ht}from"util";import{expect as _t}from"@playwright/test";async function O(t,o){let e=t.locator("div[color]").nth(1);if(await e.textContent()===o){console.info(` - Already on ${o} account. No need to switch.`);return}await e.click();let r=t.locator("div[class='simplebar-content'] > div").locator("> div",{hasText:o});if(!await r.isVisible().catch(()=>!1))throw new Error(`Account "${o}" not found. Make sure the account is onboarded or verify the account name.`);let c=t.locator("div:has-text('Select Wallet')").last();await r.click(),await c.waitFor({state:"detached",timeout:3e4})}async function j({page:t,onboard:o}){if(console.info(ht("yellowBright",` - Keplr onboarding started...`,{validateStream:!1})),o.length===1){let e=o[0];if(e&&e.mode==="privateKey"){let{privateKey:a,walletName:r,chains:n}=e;await k({page:t,privateKey:a,walletName:r,chains:n,mode:"onboard"})}if(e&&e.mode==="seedPhrase"){let{seedPhrase:a,walletName:r,chains:n}=e;await B({page:t,seedPhrase:a,walletName:r,chains:n,mode:"onboard"})}}if(o.length>1){let e=o[0];if(e&&e.mode==="privateKey"){let{privateKey:l,walletName:d,chains:w}=e;await k({page:t,privateKey:l,walletName:d,chains:w,mode:"onboard"})}if(e&&e.mode==="seedPhrase"){let{seedPhrase:l,walletName:d,chains:w}=e;await B({page:t,seedPhrase:l,walletName:d,chains:w,mode:"onboard"})}let a=o.slice(1);for(let[l,d]of a.entries()){let w=await L(t);if(d.mode==="privateKey"){let{privateKey:h,walletName:g,chains:f}=d;await k({page:w,privateKey:h,walletName:g,chains:f,mode:"add-account-single"})}if(d.mode==="seedPhrase"){let{seedPhrase:h,walletName:g,chains:f}=d;await B({page:w,seedPhrase:h,walletName:g,chains:f,mode:"add-account-single"})}l!==a.length-1&&await t.locator("div",{hasText:"Select Wallet"}).last().locator("../../..").locator("div > svg").click()}await t.locator("div",{hasText:"Select Wallet"}).last().locator("../../..").locator("div > svg").click(),await t.getByRole("link",{name:"Home",exact:!0}).click();let u=t.locator(I.openSidebarMenuButton);await _t(u).toBeVisible({timeout:3e4});let m=o.at(-1)?.walletName,p=o[0]?.walletName;m&&p&&await O(t,p)}console.info(ht("greenBright","\u2728 Keplr onboarding completed successfully",{validateStream:!1}))}async function ft(t){let o=t.locator(R.rejectButton);await o.waitFor({state:"visible",timeout:2e4}),await o.click()}async function gt({page:t,currentAccountName:o,newAccountName:e}){let a=mt.parse({currentAccountName:o,newAccountName:e});await t.getByRole("link",{name:"Settings",exact:!0}).click(),await t.locator("div[cursor='pointer']").first().click();let c=t.locator("div",{hasText:a.currentAccountName}).nth(-4);if(!await c.isVisible().catch(()=>!1))throw Error(`Account with name "${a.currentAccountName}" not found`);await c.locator("div[cursor='pointer'] svg").click(),await t.locator("div > div[cursor='pointer'] > div:has-text('Change Wallet Name')").last().click(),await t.locator("input[name='name']").fill(a.newAccountName),await t.locator("button:has-text('Save')").click()}async function D(t){let o=await K("keplr");await t.locator(M.passwordInput).fill(o),await t.locator(M.unlockButton).click(),await t.locator("div:has-text('Deposit')").last().waitFor({state:"visible",timeout:3e4})}var S=class extends A{page;constructor(o){super(),this.page=o}async onboard(o){await j({page:this.page,onboard:o})}async unlock(){await D(this.page)}async lock(){await wt(this.page)}async renameAccount({currentAccountName:o,newAccountName:e}){await gt({page:this.page,currentAccountName:o,newAccountName:e})}async switchAccount(o){await O(this.page,o)}async getAccountAddress({...o}){return await ut({page:this.page,...o})}async addAccount({chains:o,walletName:e,mode:a="add-account-multiple",...r}){await ct({page:this.page,walletName:e,chains:o,mode:a,...r})}async connectToApp(){await pt(await this.promptPage(this.page.context()))}async confirmTransaction(){await lt(await this.promptPage(this.page.context()))}async rejectTransaction(){await ft(await this.promptPage(this.page.context()))}};import yt from"fs";import Ot from"path";import{test as Dt,chromium as $t}from"@playwright/test";import{expect as Kt}from"@playwright/test";async function H(t,o){let e=await t.newPage();return await Kt(async()=>{await e.goto(o),await b(e)}).toPass(),e}async function z(t,o){let e=await o.newPage();for(let{origin:a,localStorage:r}of t){let n=e.mainFrame();await n.goto(a),await n.evaluate(c=>{c.forEach(({name:u,value:m})=>{window.localStorage.setItem(u,m)})},r)}await e.close()}import Lt from"fs/promises";async function xt(t){await Lt.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Rt=35e3;async function $(t,o){try{await Promise.race([t.close(),new Promise((e,a)=>setTimeout(()=>a(new Error("Context close timed out")),Rt))])}catch(e){console.warn(`Browser context close did not complete cleanly: ${e.message}`)}try{await xt(o)}catch(e){console.error(`Failed to remove temporary context directory at ${o}. Error:`,e)}}var C,Ta=({slowMo:t=0,profileName:o}={})=>Dt.extend({contextPath:async({browserName:e},a,r)=>{let n=await E(`${e}-${r.testId}`);await a(n)},context:async({context:e,contextPath:a},r)=>{let n=new A,c=y(n.name),u=await F(n.name),m=Ot.resolve(c,o??"wallet-data");if(!yt.existsSync(m))throw new Error("\u274C Cache for Keplr wallet data not found. Create it first");yt.cpSync(m,a,{recursive:!0,force:!0});let p=N(u,t),l=await $t.launchPersistentContext(a,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:t});await l.grantPermissions(["clipboard-read"]);let{cookies:d,origins:w}=await e.storageState();d&&await l.addCookies(d),w&&w.length>0&&await z(w,l);let h=await n.indexUrl();C=l.pages().find(f=>f.url().startsWith(h))||await H(l,h),await b(C);for(let f of l.pages()){let v=f.url();(v.includes("about:blank")||v.includes(n.onboardingPath))&&await f.close()}await C.bringToFront(),await D(C),await r(l),await $(l,a)},keplrPage:async({context:e},a)=>{await a(C)},keplr:async({context:e},a)=>{let r=new S(C);await a(r)}});import{test as Vt}from"@playwright/test";var La=({slowMo:t,profileName:o}={})=>Vt.extend({workerScopeContents:[async({browser:e},a,r)=>{let n=new A,{context:c,contextPath:u,walletPage:m}=await et({wallet:n,workerInfo:r,profileName:o,slowMo:t});await c.grantPermissions(["clipboard-read"]);for(let l of c.pages())l.url().includes("about:blank")&&await l.close();let p=new S(m);await p.unlock(),await a({wallet:p,walletPage:m,context:c}),await $(c,u)},{scope:"worker"}]});export{S as Keplr,Ta as keplrFixture,La as keplrWorkerScopeFixture,et as workerScopeContext}; +import Q from"fs";import vt from"path";import{chromium as bt}from"@playwright/test";import yt from"path";var X=".wallet-cache",J=".wallet-context";var Y="13.33.0",C="https://github.com/amaify/chainwright/releases/download/v0.1.0/",xt=`https://github.com/MetaMask/metamask-extension/releases/download/v${Y}/`,Ot=`${xt}metamask-chrome-${Y}.zip`,Vt=`${C}solflare-wallet-extension-v2.19.1.zip`,Ut=`${C}petra-wallet-extension-v2.4.8.zip`,Mt=`${C}phantom-wallet-extension-v26.10.0.zip`,Ht=`${C}meteor-wallet-extension-v0.7.0.zip`,Gt=`${C}keplr-wallet-extension-v0.13.39.zip`;async function T(t){return yt.resolve(process.cwd(),J,t)}import Pt from"path";function y(t){return Pt.resolve(process.cwd(),X,t)}import q from"fs";import At from"path";async function E(t){try{let e=y(t),o=At.resolve(e,"extension-path.txt");if(!q.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=q.readFileSync(o,"utf-8").trim();if(!a)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return a}catch(e){throw new Error(`\u274C Failed to get ${t} extension path: ${e.message}`)}}function I(t,e){let o=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(o.push("--headless=new"),e>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),o}async function Z({wallet:t,workerInfo:e,profileName:o,slowMo:a}){let r=await T(e.workerIndex.toString()),n=y(t.name),l=vt.resolve(n,o??"wallet-data");if(!Q.existsSync(l))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);Q.cpSync(l,r,{recursive:!0,force:!0});let w=await E(t.name),d=I(w,a??0),u=await bt.launchPersistentContext(r,{headless:!1,args:d,slowMo:process.env.HEADLESS?0:a}),c=await t.indexUrl(),p=u.pages()[0];return p||(p=await u.newPage()),await p.goto(c),{context:u,walletPage:p,contextPath:r}}import{expect as rt}from"@playwright/test";function F(t){return new Promise(e=>setTimeout(e,t))}async function v(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}import tt from"fs";import kt from"path";async function _(t){let e=y(t),o=kt.resolve(e,"password.txt");try{if(!tt.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return tt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Bt}from"@playwright/test";async function et({context:t,path:e,locator:o}){let a;try{await Bt.poll(async()=>(a=t.pages().filter(r=>r.url().startsWith("chrome-extension://")).find(r=>r.url().match(e)),!!a),{timeout:9e4}).toBe(!0)}catch{let r=t.pages().filter(n=>n.url().startsWith("chrome-extension://")).map(n=>n.url());throw new Error([`Popup page with path "${e}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(r)}`].join(` +`))}if(!a)throw new Error(`Popup page with path ${e} not found in context.`);return await St(a,o),await a.setViewportSize({width:360,height:592}),a}async function St(t,e){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(e).first().waitFor({state:"attached",timeout:45e3})}import ot from"fs";import Ct from"path";async function at(t){let e=y(t),o=Ct.resolve(e,"extension-id.txt");try{if(!ot.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return ot.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var P=class{name="keplr";onboardingPath="register.html";async indexUrl(){return`chrome-extension://${await this.extensionId()}/sidePanel.html`}async onboardingUrl(){return`chrome-extension://${await this.extensionId()}/${this.onboardingPath}`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/popup.html`}async extensionId(){return await at(this.name)}async promptPage(e){let o=await this.promptUrl();return await et({context:e,path:o,locator:"div[data-simplebar='init']"})}};var g={importExistingWalletButton:"button:has-text('Import an existing wallet')",usePrivateKeyButton:"button:has-text('Use recovery phrase or private key')",privateKeyTabButton:"button:has-text('Private key')",privateKeyInput:"input[type='password']",importButton:"button:has-text('Import')",walletNameInput:"input[name='name']",walletPasswordInput:"input[name='password']",confirmWalletPasswordInput:"input[name='confirmPassword']",nextButton:"button:has-text('Next')",searchNetworkInput:"input[placeholder='Search networks']",saveButton:"button:has-text('Save')",finishButton:"button:has-text('Finish')"};function Tt(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async function b({page:t,privateKey:e,walletName:o,chains:a,mode:r="onboard"}){await t.locator(g.importExistingWalletButton).click(),await t.locator(g.usePrivateKeyButton).click(),await t.getByRole("button",{name:"Private key",exact:!0}).click(),await t.locator(g.privateKeyInput).fill(e),await nt({page:t,walletName:o,mode:r,chains:a})}async function k({page:t,seedPhrase:e,walletName:o,chains:a,mode:r="onboard"}){await t.locator(g.importExistingWalletButton).click(),await t.locator(g.usePrivateKeyButton).click(),await t.getByRole("button",{name:"12 words",exact:!0}).click();let d=t.locator("input[type='password']"),u=e.split(" ");await d.first().fill(u[0]),await d.first().press("Tab");for(let c=1;c div").first().locator("div").filter({hasText:new RegExp(`^${Tt(m)}$`,"i")}).nth(2).locator("../../../../..");await h.waitFor({state:"visible",timeout:2e4}),await h.locator("input[type='checkbox']").getAttribute("checked")===null&&await h.click()}let f=t.locator(g.saveButton);if(await f.scrollIntoViewIfNeeded(),await f.click(),await F(2e3),o==="onboard"){await t.goto(await r.indexUrl());return}if(o==="add-account-single"){let m=t.locator(g.finishButton);await m.waitFor({state:"visible",timeout:2e4}),await rt(m).toBeEnabled({timeout:2e4}),await m.click()}}async function N(t){let o=await new P().onboardingUrl();await t.getByRole("link",{name:"Settings",exact:!0}).click(),await t.locator("div[cursor='pointer']").first().click(),await t.getByRole("button",{name:"Add Wallet",exact:!0}).click();let l;if(await rt.poll(async()=>(l=t.context().pages().find(w=>w.url().match(o)),!!l),{timeout:3e4}).toBe(!0).catch(w=>{console.error(`Failed to find onboarding page with URL matching ${o}. Original error: ${w}`)}),!l)throw new Error(`Onboarding page not found. Expected URL: ${o}`);return await v(l),await l.bringToFront(),l}async function it({page:t,chains:e,walletName:o,mode:a,...r}){let n=await N(t);"privateKey"in r&&await b({page:n,privateKey:r.privateKey,walletName:o,chains:e,mode:a}),"seedPhrase"in r&&await k({page:n,seedPhrase:r.seedPhrase,walletName:o,chains:e,mode:a}),await t.locator("div:has(div:has-text('Select Wallet'))").nth(-4).locator("div:has(> div > svg)").first().click(),await t.getByRole("link",{name:"Home",exact:!0}).click()}import{expect as Wt}from"@playwright/test";var K={approveButton:"button:has-text('Approve')",rejectButton:"button[color='secondary']"};async function st(t){let e=t.locator(K.approveButton);await Wt(e).toBeEnabled({timeout:2e4}),await e.click()}async function ct(t){t.getByRole("button",{name:"Approve",exact:!0}).click(),await F(1e3)}import{expect as O}from"@playwright/test";async function lt({page:t,...e}){await t.getByRole("textbox",{name:"Search for asset or chain (i.e. ATOM, Cosmos)",exact:!0}).fill(e.chain);let a=t.locator(`div:has-text("${e.chain}")`).nth(-2).filter({hasNot:t.locator("span")});if(await a.waitFor({state:"attached",timeout:3e4}),!await a.isVisible().catch(()=>!1))throw Error(`Make sure "${e.chain}" is activated.`);let n=await a.locator("div").all();O(n.length).toBeGreaterThan(0),await t.locator(`div:has(div:has-text('${e.walletName}'))`).nth(-3).locator("div:has(> div > svg)").click();let d=t.locator("div:has(> div[data-simplebar='init'])").last();await d.locator("div:has(> div > input)").locator("input").fill(e.chain);let p=d.locator("div[cursor='pointer']",{hasText:e.chain});await O(p.first()).toBeVisible({timeout:6e4});let f=await p.all(),m;for(let h of f){let A;"chainTag"in e&&(A=e.chainTag);let G=h.locator("div",{hasText:e.chain}).last(),D=A?h.locator("div",{hasText:A}).last():null,j=(D?await D?.isVisible().catch(()=>!1):!1)?await D?.textContent():null,z=await G.textContent(),ht=j?`${z} ${j}`:z,gt=A?`${e.chain} ${A}`:e.chain;if(ht===gt){m=G.locator("xpath=../../../..");break}}if(!m)throw Error(`Address for ${e.walletName} account on "${e.chain}" chain not found.`);await m.hover(),await m.scrollIntoViewIfNeeded(),await m.click();let x=await t.evaluate(async()=>await navigator.clipboard.readText());return O(x).not.toBeFalsy(),x}var W={openSidebarMenuButton:"div[cursor='pointer']:has(> div[cursor='pointer'])",menuPopupContent:"div[id='modal-root-3']",lockWalletButton:"div:has(> div:has-text('Lock Wallet'))",settingsButton:"div:has(a[href='#/settings'])"},V={unlockButton:"button[type='submit']:has-text('Unlock')",passwordInput:"input[placeholder='Type Your Password']"};async function pt(t){await t.locator(W.openSidebarMenuButton).click(),await t.locator(W.lockWalletButton).nth(-1).click(),await t.getByText("Welcome Back").waitFor({state:"visible",timeout:3e4})}import{styleText as dt}from"util";import{expect as Et}from"@playwright/test";async function L(t,e){let o=t.locator("div[color]").nth(1);if(await o.textContent()===e){console.info(` + Already on ${e} account. No need to switch.`);return}await o.click();let r=t.locator("div[class='simplebar-content'] > div").locator("> div",{hasText:e});if(!await r.isVisible().catch(()=>!1))throw new Error(`Account "${e}" not found. Make sure the account is onboarded or verify the account name.`);let l=t.locator("div:has-text('Select Wallet')").last();await r.click(),await l.waitFor({state:"detached",timeout:3e4})}async function U({page:t,onboard:e}){if(console.info(dt("yellowBright",` + Keplr onboarding started...`,{validateStream:!1})),e.length===1){let o=e[0];if(o&&o.mode==="privateKey"){let{privateKey:a,walletName:r,chains:n}=o;await b({page:t,privateKey:a,walletName:r,chains:n,mode:"onboard"})}if(o&&o.mode==="seedPhrase"){let{seedPhrase:a,walletName:r,chains:n}=o;await k({page:t,seedPhrase:a,walletName:r,chains:n,mode:"onboard"})}}if(e.length>1){let o=e[0];if(o&&o.mode==="privateKey"){let{privateKey:c,walletName:p,chains:f}=o;await b({page:t,privateKey:c,walletName:p,chains:f,mode:"onboard"})}if(o&&o.mode==="seedPhrase"){let{seedPhrase:c,walletName:p,chains:f}=o;await k({page:t,seedPhrase:c,walletName:p,chains:f,mode:"onboard"})}let a=e.slice(1);for(let[c,p]of a.entries()){let f=await N(t);if(p.mode==="privateKey"){let{privateKey:m,walletName:x,chains:h}=p;await b({page:f,privateKey:m,walletName:x,chains:h,mode:"add-account-single"})}if(p.mode==="seedPhrase"){let{seedPhrase:m,walletName:x,chains:h}=p;await k({page:f,seedPhrase:m,walletName:x,chains:h,mode:"add-account-single"})}c!==a.length-1&&await t.locator("div",{hasText:"Select Wallet"}).last().locator("../../..").locator("div > svg").click()}await t.locator("div",{hasText:"Select Wallet"}).last().locator("../../..").locator("div > svg").click(),await t.getByRole("link",{name:"Home",exact:!0}).click();let w=t.locator(W.openSidebarMenuButton);await Et(w).toBeVisible({timeout:3e4});let d=e.at(-1)?.walletName,u=e[0]?.walletName;d&&u&&await L(t,u)}console.info(dt("greenBright","\u2728 Keplr onboarding completed successfully",{validateStream:!1}))}async function mt(t){let e=t.locator(K.rejectButton);await e.waitFor({state:"visible",timeout:2e4}),await e.click()}async function ut({page:t,currentAccountName:e,newAccountName:o}){await t.getByRole("link",{name:"Settings",exact:!0}).click(),await t.locator("div[cursor='pointer']").first().click();let n=t.locator("div",{hasText:e}).nth(-4);if(!await n.isVisible().catch(()=>!1))throw Error(`Account with name "${e}" not found`);await n.locator("div[cursor='pointer'] svg").click(),await t.locator("div > div[cursor='pointer'] > div:has-text('Change Wallet Name')").last().click(),await t.locator("input[name='name']").fill(o),await t.locator("button:has-text('Save')").click()}async function R(t){let e=await _("keplr");await t.locator(V.passwordInput).fill(e),await t.locator(V.unlockButton).click(),await t.locator("div:has-text('Deposit')").last().waitFor({state:"visible",timeout:3e4})}var B=class extends P{page;constructor(e){super(),this.page=e}async onboard(e){await U({page:this.page,onboard:e})}async unlock(){await R(this.page)}async lock(){await pt(this.page)}async renameAccount({currentAccountName:e,newAccountName:o}){await ut({page:this.page,currentAccountName:e,newAccountName:o})}async switchAccount(e){await L(this.page,e)}async getAccountAddress({...e}){return await lt({page:this.page,...e})}async addAccount({chains:e,walletName:o,mode:a="add-account-multiple",...r}){await it({page:this.page,walletName:o,chains:e,mode:a,...r})}async connectToApp(){await ct(await this.promptPage(this.page.context()))}async confirmTransaction(){await st(await this.promptPage(this.page.context()))}async rejectTransaction(){await mt(await this.promptPage(this.page.context()))}};import ft from"fs";import Nt from"path";import{test as Kt,chromium as Lt}from"@playwright/test";import{expect as It}from"@playwright/test";async function M(t,e){let o=await t.newPage();return await It(async()=>{await o.goto(e),await v(o)}).toPass(),o}async function H(t,e){let o=await e.newPage();for(let{origin:a,localStorage:r}of t){let n=o.mainFrame();await n.goto(a),await n.evaluate(l=>{l.forEach(({name:w,value:d})=>{window.localStorage.setItem(w,d)})},r)}await o.close()}import Ft from"fs/promises";async function wt(t){await Ft.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var _t=35e3;async function $(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),_t))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await wt(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var S,ya=({slowMo:t=0,profileName:e}={})=>Kt.extend({contextPath:async({browserName:o},a,r)=>{let n=await T(`${o}-${r.testId}`);await a(n)},context:async({context:o,contextPath:a},r)=>{let n=new P,l=y(n.name),w=await E(n.name),d=Nt.resolve(l,e??"wallet-data");if(!ft.existsSync(d))throw new Error("\u274C Cache for Keplr wallet data not found. Create it first");ft.cpSync(d,a,{recursive:!0,force:!0});let u=I(w,t),c=await Lt.launchPersistentContext(a,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:t});await c.grantPermissions(["clipboard-read"]);let{cookies:p,origins:f}=await o.storageState();p&&await c.addCookies(p),f&&f.length>0&&await H(f,c);let m=await n.indexUrl();S=c.pages().find(h=>h.url().startsWith(m))||await M(c,m),await v(S);for(let h of c.pages()){let A=h.url();(A.includes("about:blank")||A.includes(n.onboardingPath))&&await h.close()}await S.bringToFront(),await R(S),await r(c),await $(c,a)},keplrPage:async({context:o},a)=>{await a(S)},keplr:async({context:o},a)=>{let r=new B(S);await a(r)}});import{test as Rt}from"@playwright/test";var Ca=({slowMo:t,profileName:e}={})=>Rt.extend({workerScopeContents:[async({browser:o},a,r)=>{let n=new P,{context:l,contextPath:w,walletPage:d}=await Z({wallet:n,workerInfo:r,profileName:e,slowMo:t});await l.grantPermissions(["clipboard-read"]);for(let c of l.pages())c.url().includes("about:blank")&&await c.close();let u=new B(d);await u.unlock(),await a({wallet:u,walletPage:d,context:l}),await $(l,w)},{scope:"worker"}]});export{B as Keplr,ya as keplrFixture,Ca as keplrWorkerScopeFixture,Z as workerScopeContext}; diff --git a/dist/wallets/metamask/index.d.ts b/dist/wallets/metamask/index.d.ts index 1b6b424..d21fb04 100644 --- a/dist/wallets/metamask/index.d.ts +++ b/dist/wallets/metamask/index.d.ts @@ -3,7 +3,6 @@ import { W as WorkerScopeFixture } from '../../worker-scope-context-DSkOcWf-.js' export { w as workerScopeContext } from '../../worker-scope-context-DSkOcWf-.js'; import * as _playwright_test from '@playwright/test'; import { Page, BrowserContext } from '@playwright/test'; -import z from 'zod'; type RenameAccount = { page: Page; @@ -32,13 +31,12 @@ type AddAccountArgs = { privateKey: string; accountName: string; }; -declare const addCustomNetworkSchema: z.ZodObject<{ - networkName: z.ZodString; - rpcUrl: z.ZodURL; - chainId: z.ZodUnion<[z.ZodNumber, z.ZodString]>; - currencySymbol: z.ZodString; -}, z.core.$strip>; -type AddCustomNetwork = z.infer; +type AddCustomNetwork = { + networkName: string; + rpcUrl: string; + chainId: number | string; + currencySymbol: string; +}; type SwitchNetwork = { chainName: "Ethereum" | "Base" | "Linea"; networkType: "mainnet"; @@ -427,4 +425,4 @@ declare const metamaskFixture: ({ slowMo, profileName }?: WalletProfileFixtureAr declare const metamaskWorkerScopeFixture: ({ profileName, slowMo }?: WalletProfileFixtureArgs) => _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & MetamaskFixture, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions & WorkerScopeFixture>; -export { type AddAccountArgs, type AddCustomNetwork, type AnvilNodeInstance, type AnvilNodeOptions, type CreateAnvilNodeResult, type GasFeeSettings, type GetAccountAddressChains, Metamask, type MetamaskFixture, type OnboardingArgs, type SwitchNetwork, WalletProfileFixtureArgs, WorkerScopeFixture, addCustomNetworkSchema, metamaskFixture, metamaskWorkerScopeFixture }; +export { type AddAccountArgs, type AddCustomNetwork, type AnvilNodeInstance, type AnvilNodeOptions, type CreateAnvilNodeResult, type GasFeeSettings, type GetAccountAddressChains, Metamask, type MetamaskFixture, type OnboardingArgs, type SwitchNetwork, WalletProfileFixtureArgs, WorkerScopeFixture, metamaskFixture, metamaskWorkerScopeFixture }; diff --git a/dist/wallets/metamask/index.js b/dist/wallets/metamask/index.js index 255f654..d79c4fe 100644 --- a/dist/wallets/metamask/index.js +++ b/dist/wallets/metamask/index.js @@ -1,5 +1,5 @@ -import dt from"fs";import $t from"path";import{chromium as Ht}from"@playwright/test";import Ut from"path";var it=".wallet-cache",st=".wallet-context";var ct="13.33.0",V="https://github.com/amaify/chainwright/releases/download/v0.1.0/",Dt=`https://github.com/MetaMask/metamask-extension/releases/download/v${ct}/`,xe=`${Dt}metamask-chrome-${ct}.zip`,Be=`${V}solflare-wallet-extension-v2.19.1.zip`,ke=`${V}petra-wallet-extension-v2.4.8.zip`,be=`${V}phantom-wallet-extension-v26.10.0.zip`,Ae=`${V}meteor-wallet-extension-v0.7.0.zip`,Ce=`${V}keplr-wallet-extension-v0.13.39.zip`;async function v(t){return Ut.resolve(process.cwd(),st,t)}import Vt from"path";function B(t){return Vt.resolve(process.cwd(),it,t)}import lt from"fs";import Ot from"path";async function F(t){try{let e=B(t),o=Ot.resolve(e,"extension-path.txt");if(!lt.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let n=lt.readFileSync(o,"utf-8").trim();if(!n)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return n}catch(e){throw new Error(`\u274C Failed to get ${t} extension path: ${e.message}`)}}function M(t,e){let o=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(o.push("--headless=new"),e>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),o}async function Xe({wallet:t,workerInfo:e,profileName:o,slowMo:n}){let r=await v(e.workerIndex.toString()),a=B(t.name),i=$t.resolve(a,o??"wallet-data");if(!dt.existsSync(i))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);dt.cpSync(i,r,{recursive:!0,force:!0});let c=await F(t.name),s=M(c,n??0),m=await Ht.launchPersistentContext(r,{headless:!1,args:s,slowMo:process.env.HEADLESS?0:n}),u=await t.indexUrl(),p=m.pages()[0];return p||(p=await m.newPage()),await p.goto(u),{context:m,walletPage:p,contextPath:r}}import{expect as k}from"@playwright/test";import mt from"zod";import{test as Gt}from"@playwright/test";function ut(t,e){t&&(console.warn(` - \u26A0\uFE0F Skipping test: ${e}`),Gt.skip())}var w={buyButton:"eth-overview-buy",swapButton:"eth-overview-swap",sendButton:"eth-overview-send",receiveButton:"eth-overview-receive",openSettingsButton:"account-options-menu-button",accountMenuButton:"account-menu-icon",accountCell:"multichain-account-cell-entropy",openNetworkSelectorButton:"sort-by-networks",accountAddressesButton:"networks-subtitle-test-id",accountAddressesElements:"multichain-address-row",accountAddressQRCode:"multichain-address-row-qr-button"},I={lockButton:"global-menu-lock",networksButton:"global-menu-networks",networksPageList:"networks-page-list",headerBackButton:"settings-header-back-button"},O={passwordInput:"unlock-password",unlockButton:"unlock-submit"},T={accountOptionsMenuButton:"multichain-account-cell-end-accessory",accountDetailsLabel:"Account details",renameAccountLabel:"Rename",addressesLabel:"Addresses",pinToTopLabel:"Pin to top",hideAccountLabel:"Hide account",backButton:"back",addMultichainAccountButton:"add-multichain-account-button",addWalletButton:"account-list-add-wallet-button",importWalletButton:"choose-wallet-type-import-wallet",importAccountButton:"choose-wallet-type-import-account"};var y={createWalletButton:"onboarding-create-wallet",importWalletButton:"onboarding-import-wallet",useSecretRecoveryPhraseButton:"onboarding-create-with-srp-button",createNewPasswordInput:"create-password-new-input",confirmNewPasswordInput:"create-password-confirm-input",confirmPasswordCheckbox:"create-password-terms",createPasswordButton:"create-password-submit",revealSecretRecoveryPhraseButton:"recovery-phrase-reveal",recoveryPhraseRemindMeLaterButton:"recovery-phrase-remind-later",metamaskMetricsIAgreeButton:"metametrics-i-agree",onboardingDoneButton:"onboarding-complete-done",importUsingSecretRecoveryPhraseButton:"onboarding-import-with-srp-button",secretRecoveryPhraseTextAreaInput:"srp-input-import__srp-note",importWalletConfirmButton:"import-srp-confirm",importAccountConfirmButton:"import-account-confirm-button",importSRPError:"bannerAlert"};async function pt({page:t,privateKey:e,accountName:o}){let n=mt.string().min(1,"Account name cannot be an empty string").trim().parse(o),r=mt.string().min(1,"Private key cannot be an empty string").trim().parse(e),a=t.getByTestId(w.accountMenuButton);await k(a).toBeVisible({timeout:3e4}),await a.click(),await k(t.getByRole("heading",{name:/accounts/i})).toBeVisible();let i=t.getByTestId(T.addWalletButton),c=await i.textContent();c?.includes("Syncing")&&await k.poll(async()=>(await i.textContent())?.trim()??"",{timeout:12e4}).not.toBe(c),await k(i).toBeEnabled({timeout:6e4}),await i.click();let s=t.getByTestId("multichain-page");await k(s).toContainText(/add a wallet/i),await t.getByTestId(T.importAccountButton).click(),await t.locator("input[id='private-key-box']").fill(r);let p=t.getByTestId(y.importAccountConfirmButton);await k(p).toBeEnabled(),await p.click();let f=t.getByTestId(y.importSRPError),g=await f.isVisible().catch(()=>!1);g&&ut(g,`${(await f.textContent())?.split(".")[0]}`),await t.locator("button[aria-label='Back']").first().click();let A=t.locator("div[data-testid^='multichain-account-cell-keyring'][class*='is-selected']"),x=await A.locator("p[class*='multichain-account-cell__account-name']").textContent();x&&await zt({page:t,accountName:n,activeAccountLocator:A,activeAccountName:x}),await t.locator("button[aria-label='Back']").first().click()}async function zt({page:t,accountName:e,activeAccountLocator:o,activeAccountName:n}){let r=o.locator(`div[aria-label='${n} options']`);await k(r).toBeVisible(),await r.click(),await k(t.getByRole("tooltip")).toBeVisible();let a=t.locator(`div[aria-label='${T.renameAccountLabel}']`);await k(a).toBeVisible(),await a.click();let i=t.getByRole("dialog"),c=i.getByRole("heading",{name:/rename/i});await k(c).toBeVisible();let s=i.getByRole("textbox");await k(s).toBeVisible(),await s.fill(e);let m=i.getByRole("button",{name:/confirm/i});await k(m).toBeEnabled(),await m.click(),await i.waitFor({state:"detached",timeout:2e4});let u=t.locator("div[data-testid^='multichain-account-cell-keyring'][class*='is-selected']");await k(u).toContainText(e)}import{expect as ft}from"@playwright/test";import L from"zod";var wt=L.object({networkName:L.string().min(1,"Network name cannot be an empty string"),rpcUrl:L.url(),chainId:L.number().or(L.string().includes("0x")),currencySymbol:L.string().toUpperCase().min(1,"Currency symbol cannot be an empty string")});import{expect as Kt}from"@playwright/test";async function z(t){let e=t.locator("div:has(> p[data-testid='notifications-tag-counter__unread-dot'])"),o=t.getByTestId(w.openSettingsButton);await e.isVisible().catch(()=>!1)?await e.click():(await Kt(o).toBeVisible(),await o.click())}async function gt({page:t,...e}){let{chainId:o,currencySymbol:n,networkName:r,rpcUrl:a}=wt.parse({...e},{reportInput:!0});await z(t),await t.getByTestId(I.networksButton).click();let c=t.getByTestId(I.networksPageList);await ft(c).toContainText(/networks/i),await t.getByRole("button",{name:/add a custom network/i}).click();let m=t.getByTestId("network-form-network-name"),u=t.getByTestId("test-add-rpc-drop-down"),p=t.getByTestId("network-form-chain-id"),f=t.getByTestId("network-form-ticker-input");await m.fill(r),await u.click(),await t.getByRole("tooltip").locator("div:has(> button:has-text('Add RPC URL'))").click();let A=t.getByTestId("rpc-url-input-test"),x=t.getByRole("button",{name:/Add URL/i});await A.fill(a),await x.click();let h=t.getByTestId("network-form-chain-id-error");if(await h.isVisible().catch(()=>!1)){let N=await h.textContent();throw Error(`RPC error: ${N}`)}await p.fill(`${o}`),await f.fill(n);let P=t.getByRole("button",{name:/save/i});await ft(P).toBeEnabled(),await P.click(),await t.getByTestId(I.headerBackButton).click()}import{expect as yt}from"@playwright/test";function K(t){return new Promise(e=>setTimeout(e,t))}var j={confirmButton:"confirm-footer-button",cancelButton:"confirm-footer-cancel-button"};async function ht(t,e){let o=t.getByTestId(j.confirmButton);await K(2e3);let r=(await o.textContent())?.includes("Review alert"),a=await o.isDisabled().catch(()=>!1);if(r&&a){await t.getByTestId("edit-gas-fees-row").locator("> div").first().click();let u=t.getByRole("dialog");await yt(u).toBeVisible();let p=u.locator("h4",{hasText:"Insufficient funds"}),f=await u.getByTestId("alert-modal__selected-alert").textContent();if(await p.isVisible().catch(()=>!1))throw Error(`${f}`)}if(e){let s=t.getByTestId("edit-gas-fee-icon");if(await s.scrollIntoViewIfNeeded(),await s.click(),e.feeType!=="advanced"&&await t.getByTestId(`gas-option-${e.feeType}`).click(),e.feeType==="advanced"){await t.getByTestId("gas-option-advanced").click();let u=t.getByRole("textbox",{name:"Max base fee"}),p=t.getByRole("textbox",{name:"Priority fee"}),f=t.getByRole("button",{name:"Save",exact:!0});await u.fill(e.maxBaseFee),await p.fill(e.priorityFee),await f.click()}}await yt(o).toBeEnabled(),await o.click();let i=t.getByRole("dialog");await i.isVisible().catch(()=>!1)&&await i.locator("h4",{hasText:"Your assets may be at risk"}).isVisible().catch(()=>!1)&&(await i.getByTestId("alert-modal-acknowledge-checkbox").click(),await i.getByTestId("confirm-alert-modal-submit-button").click()),await t.waitForEvent("close",{timeout:15e3})}import{expect as jt}from"@playwright/test";async function xt(t){let e=t.getByRole("button",{name:"Connect",exact:!0});await e.waitFor({state:"visible",timeout:25e3}),await t.getByRole("button",{name:"Edit accounts"}).click();let n=t.getByTestId("modal-page");await n.waitFor({state:"visible",timeout:25e3});let r=await t.locator("[data-testid^='multichain-account-cell-entropy:']").all();for(let m of r)await m.locator("input[type='checkbox']").isChecked().catch(()=>!1)||await m.click();await n.getByTestId("connect-more-accounts-button").click(),await e.click(),await t.getByRole("heading",{name:"Connecting",exact:!0}).waitFor({state:"detached",timeout:3e4});let c;await jt.poll(async()=>(c=await t.locator("div[class='permissions-connect']").isVisible().catch(()=>!1),c),{timeout:25e3}).toBe(!0).catch(()=>console.error("Notice dialog did not appear within the timeout period."));let s=t.getByTestId("page-container-footer-next");await s.waitFor({state:"visible",timeout:6e4}),await s.click(),await t.waitForEvent("close",{predicate:()=>!0,timeout:25e3}).catch(()=>console.error("Extension popup did not close within the timeout period when connecting to the DApp."))}async function Bt(t,e){await t.getByTestId(w.accountAddressesButton).hover(),await t.getByTestId("multichain-address-rows-list").getByRole("button",{name:/view all/i}).click(),await t.getByRole("searchbox",{name:/search networks/i}).fill(e),await t.locator(`div[data-testid='${w.accountAddressesElements}']:has-text('${e}')`).getByTestId(w.accountAddressQRCode).click();let u=await t.getByRole("dialog").locator("div > p[data-testid='account-address']").textContent();if(!u)throw new Error("Account address not found");return await t.getByLabel("Close",{exact:!0}).click(),await t.getByTestId("multichain-account-address-list-page-back-button").click(),u}import{expect as Yt}from"@playwright/test";import{errors as qt,expect as Xt}from"@playwright/test";async function R(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}var kt={loadingOverlay:"loading-overlay",loadingSpinner:"spinner loading-overlay__spinner"};var Qt=6e4,Jt=async(t,e,o)=>{await R(e);try{await e.locator(`div[class="${t}"]`).waitFor({state:"detached",timeout:o})}catch(n){if(n instanceof qt.TimeoutError)console.info(`Loading indicator '${t}' not found - continuing.`);else throw console.error(`Error while waiting for loading indicator '${t}' to disappear`),n}},q=async t=>{try{await Jt(kt.loadingSpinner,t,Qt)}catch(e){console.warn("Warning during MetaMask load:",e)}return await K(300),t};async function bt(t){let e=new URL(t.url()),o=`${e.protocol}//${e.host}`,n=t.context(),r=n.serviceWorkers().find(a=>a.url().startsWith(o))??await n.waitForEvent("serviceworker",{predicate:a=>a.url().startsWith(o),timeout:6e4});await Xt.poll(async()=>{try{return await r.evaluate(`(async () => { +import lt from"fs";import Vt from"path";import{chromium as Ut}from"@playwright/test";import Lt from"path";var rt=".wallet-cache",it=".wallet-context";var st="13.33.0",D="https://github.com/amaify/chainwright/releases/download/v0.1.0/",Wt=`https://github.com/MetaMask/metamask-extension/releases/download/v${st}/`,ge=`${Wt}metamask-chrome-${st}.zip`,ye=`${D}solflare-wallet-extension-v2.19.1.zip`,he=`${D}petra-wallet-extension-v2.4.8.zip`,Be=`${D}phantom-wallet-extension-v26.10.0.zip`,xe=`${D}meteor-wallet-extension-v0.7.0.zip`,ke=`${D}keplr-wallet-extension-v0.13.39.zip`;async function E(t){return Lt.resolve(process.cwd(),it,t)}import Nt from"path";function h(t){return Nt.resolve(process.cwd(),rt,t)}import ct from"fs";import Dt from"path";async function F(t){try{let e=h(t),o=Dt.resolve(e,"extension-path.txt");if(!ct.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=ct.readFileSync(o,"utf-8").trim();if(!a)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return a}catch(e){throw new Error(`\u274C Failed to get ${t} extension path: ${e.message}`)}}function M(t,e){let o=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(o.push("--headless=new"),e>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),o}async function ze({wallet:t,workerInfo:e,profileName:o,slowMo:a}){let n=await E(e.workerIndex.toString()),r=h(t.name),i=Vt.resolve(r,o??"wallet-data");if(!lt.existsSync(i))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);lt.cpSync(i,n,{recursive:!0,force:!0});let c=await F(t.name),s=M(c,a??0),d=await Ut.launchPersistentContext(n,{headless:!1,args:s,slowMo:process.env.HEADLESS?0:a}),l=await t.indexUrl(),w=d.pages()[0];return w||(w=await d.newPage()),await w.goto(l),{context:d,walletPage:w,contextPath:n}}import{expect as B}from"@playwright/test";import{test as Ot}from"@playwright/test";function dt(t,e){t&&(console.warn(` + \u26A0\uFE0F Skipping test: ${e}`),Ot.skip())}var p={buyButton:"eth-overview-buy",swapButton:"eth-overview-swap",sendButton:"eth-overview-send",receiveButton:"eth-overview-receive",openSettingsButton:"account-options-menu-button",accountMenuButton:"account-menu-icon",accountCell:"multichain-account-cell-entropy",openNetworkSelectorButton:"sort-by-networks",accountAddressesButton:"networks-subtitle-test-id",accountAddressesElements:"multichain-address-row",accountAddressQRCode:"multichain-address-row-qr-button"},v={lockButton:"global-menu-lock",networksButton:"global-menu-networks",networksPageList:"networks-page-list",headerBackButton:"settings-header-back-button"},V={passwordInput:"unlock-password",unlockButton:"unlock-submit"},I={accountOptionsMenuButton:"multichain-account-cell-end-accessory",accountDetailsLabel:"Account details",renameAccountLabel:"Rename",addressesLabel:"Addresses",pinToTopLabel:"Pin to top",hideAccountLabel:"Hide account",backButton:"back",addMultichainAccountButton:"add-multichain-account-button",addWalletButton:"account-list-add-wallet-button",importWalletButton:"choose-wallet-type-import-wallet",importAccountButton:"choose-wallet-type-import-account"};var y={createWalletButton:"onboarding-create-wallet",importWalletButton:"onboarding-import-wallet",useSecretRecoveryPhraseButton:"onboarding-create-with-srp-button",createNewPasswordInput:"create-password-new-input",confirmNewPasswordInput:"create-password-confirm-input",confirmPasswordCheckbox:"create-password-terms",createPasswordButton:"create-password-submit",revealSecretRecoveryPhraseButton:"recovery-phrase-reveal",recoveryPhraseRemindMeLaterButton:"recovery-phrase-remind-later",metamaskMetricsIAgreeButton:"metametrics-i-agree",onboardingDoneButton:"onboarding-complete-done",importUsingSecretRecoveryPhraseButton:"onboarding-import-with-srp-button",secretRecoveryPhraseTextAreaInput:"srp-input-import__srp-note",importWalletConfirmButton:"import-srp-confirm",importAccountConfirmButton:"import-account-confirm-button",importSRPError:"bannerAlert"};async function ut({page:t,privateKey:e,accountName:o}){let a=t.getByTestId(p.accountMenuButton);await B(a).toBeVisible({timeout:3e4}),await a.click(),await B(t.getByRole("heading",{name:/accounts/i})).toBeVisible();let n=t.getByTestId(I.addWalletButton),r=await n.textContent();r?.includes("Syncing")&&await B.poll(async()=>(await n.textContent())?.trim()??"",{timeout:12e4}).not.toBe(r),await B(n).toBeEnabled({timeout:6e4}),await n.click();let i=t.getByTestId("multichain-page");await B(i).toContainText(/add a wallet/i),await t.getByTestId(I.importAccountButton).click(),await t.locator("input[id='private-key-box']").fill(e);let d=t.getByTestId(y.importAccountConfirmButton);await B(d).toBeEnabled(),await d.click();let l=t.getByTestId(y.importSRPError),w=await l.isVisible().catch(()=>!1);w&&dt(w,`${(await l.textContent())?.split(".")[0]}`),await t.locator("button[aria-label='Back']").first().click();let g=t.locator("div[data-testid^='multichain-account-cell-keyring'][class*='is-selected']"),T=await g.locator("p[class*='multichain-account-cell__account-name']").textContent();T&&await $t({page:t,accountName:o,activeAccountLocator:g,activeAccountName:T}),await t.locator("button[aria-label='Back']").first().click()}async function $t({page:t,accountName:e,activeAccountLocator:o,activeAccountName:a}){let n=o.locator(`div[aria-label='${a} options']`);await B(n).toBeVisible(),await n.click(),await B(t.getByRole("tooltip")).toBeVisible();let r=t.locator(`div[aria-label='${I.renameAccountLabel}']`);await B(r).toBeVisible(),await r.click();let i=t.getByRole("dialog"),c=i.getByRole("heading",{name:/rename/i});await B(c).toBeVisible();let s=i.getByRole("textbox");await B(s).toBeVisible(),await s.fill(e);let d=i.getByRole("button",{name:/confirm/i});await B(d).toBeEnabled(),await d.click(),await i.waitFor({state:"detached",timeout:2e4});let l=t.locator("div[data-testid^='multichain-account-cell-keyring'][class*='is-selected']");await B(l).toContainText(e)}import{expect as mt}from"@playwright/test";import{expect as Ht}from"@playwright/test";async function G(t){let e=t.locator("div:has(> p[data-testid='notifications-tag-counter__unread-dot'])"),o=t.getByTestId(p.openSettingsButton);await e.isVisible().catch(()=>!1)?await e.click():(await Ht(o).toBeVisible(),await o.click())}async function wt({page:t,chainId:e,currencySymbol:o,networkName:a,rpcUrl:n}){await G(t),await t.getByTestId(v.networksButton).click();let i=t.getByTestId(v.networksPageList);await mt(i).toContainText(/networks/i),await t.getByRole("button",{name:/add a custom network/i}).click();let s=t.getByTestId("network-form-network-name"),d=t.getByTestId("test-add-rpc-drop-down"),l=t.getByTestId("network-form-chain-id"),w=t.getByTestId("network-form-ticker-input");await s.fill(a),await d.click(),await t.getByRole("tooltip").locator("div:has(> button:has-text('Add RPC URL'))").click();let T=t.getByTestId("rpc-url-input-test"),P=t.getByRole("button",{name:/Add URL/i});await T.fill(n),await P.click();let x=t.getByTestId("network-form-chain-id-error");if(await x.isVisible().catch(()=>!1)){let A=await x.textContent();throw Error(`RPC error: ${A}`)}await l.fill(`${e}`),await w.fill(o);let S=t.getByRole("button",{name:/save/i});await mt(S).toBeEnabled(),await S.click(),await t.getByTestId(v.headerBackButton).click()}import{expect as pt}from"@playwright/test";function K(t){return new Promise(e=>setTimeout(e,t))}var z={confirmButton:"confirm-footer-button",cancelButton:"confirm-footer-cancel-button"};async function ft(t,e){let o=t.getByTestId(z.confirmButton);await K(2e3);let n=(await o.textContent())?.includes("Review alert"),r=await o.isDisabled().catch(()=>!1);if(n&&r){await t.getByTestId("edit-gas-fees-row").locator("> div").first().click();let l=t.getByRole("dialog");await pt(l).toBeVisible();let w=l.locator("h4",{hasText:"Insufficient funds"}),f=await l.getByTestId("alert-modal__selected-alert").textContent();if(await w.isVisible().catch(()=>!1))throw Error(`${f}`)}if(e){let s=t.getByTestId("edit-gas-fee-icon");if(await s.scrollIntoViewIfNeeded(),await s.click(),e.feeType!=="advanced"&&await t.getByTestId(`gas-option-${e.feeType}`).click(),e.feeType==="advanced"){await t.getByTestId("gas-option-advanced").click();let l=t.getByRole("textbox",{name:"Max base fee"}),w=t.getByRole("textbox",{name:"Priority fee"}),f=t.getByRole("button",{name:"Save",exact:!0});await l.fill(e.maxBaseFee),await w.fill(e.priorityFee),await f.click()}}await pt(o).toBeEnabled(),await o.click();let i=t.getByRole("dialog");await i.isVisible().catch(()=>!1)&&await i.locator("h4",{hasText:"Your assets may be at risk"}).isVisible().catch(()=>!1)&&(await i.getByTestId("alert-modal-acknowledge-checkbox").click(),await i.getByTestId("confirm-alert-modal-submit-button").click()),await t.waitForEvent("close",{timeout:15e3})}import{expect as Gt}from"@playwright/test";async function gt(t){let e=t.getByRole("button",{name:"Connect",exact:!0});await e.waitFor({state:"visible",timeout:25e3}),await t.getByRole("button",{name:"Edit accounts"}).click();let a=t.getByTestId("modal-page");await a.waitFor({state:"visible",timeout:25e3});let n=await t.locator("[data-testid^='multichain-account-cell-entropy:']").all();for(let d of n)await d.locator("input[type='checkbox']").isChecked().catch(()=>!1)||await d.click();await a.getByTestId("connect-more-accounts-button").click(),await e.click(),await t.getByRole("heading",{name:"Connecting",exact:!0}).waitFor({state:"detached",timeout:3e4});let c;await Gt.poll(async()=>(c=await t.locator("div[class='permissions-connect']").isVisible().catch(()=>!1),c),{timeout:25e3}).toBe(!0).catch(()=>console.error("Notice dialog did not appear within the timeout period."));let s=t.getByTestId("page-container-footer-next");await s.waitFor({state:"visible",timeout:6e4}),await s.click(),await t.waitForEvent("close",{predicate:()=>!0,timeout:25e3}).catch(()=>console.error("Extension popup did not close within the timeout period when connecting to the DApp."))}async function yt(t,e){await t.getByTestId(p.accountAddressesButton).hover(),await t.getByTestId("multichain-address-rows-list").getByRole("button",{name:/view all/i}).click(),await t.getByRole("searchbox",{name:/search networks/i}).fill(e),await t.locator(`div[data-testid='${p.accountAddressesElements}']:has-text('${e}')`).getByTestId(p.accountAddressQRCode).click();let l=await t.getByRole("dialog").locator("div > p[data-testid='account-address']").textContent();if(!l)throw new Error("Account address not found");return await t.getByLabel("Close",{exact:!0}).click(),await t.getByTestId("multichain-account-address-list-page-back-button").click(),l}import{expect as Xt}from"@playwright/test";import{errors as Kt,expect as zt}from"@playwright/test";async function R(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}var ht={loadingOverlay:"loading-overlay",loadingSpinner:"spinner loading-overlay__spinner"};var jt=6e4,qt=async(t,e,o)=>{await R(e);try{await e.locator(`div[class="${t}"]`).waitFor({state:"detached",timeout:o})}catch(a){if(a instanceof Kt.TimeoutError)console.info(`Loading indicator '${t}' not found - continuing.`);else throw console.error(`Error while waiting for loading indicator '${t}' to disappear`),a}},j=async t=>{try{await qt(ht.loadingSpinner,t,jt)}catch(e){console.warn("Warning during MetaMask load:",e)}return await K(300),t};async function Bt(t){let e=new URL(t.url()),o=`${e.protocol}//${e.host}`,a=t.context(),n=a.serviceWorkers().find(r=>r.url().startsWith(o))??await a.waitForEvent("serviceworker",{predicate:r=>r.url().startsWith(o),timeout:6e4});await zt.poll(async()=>{try{return await n.evaluate(`(async () => { const state = await chrome.storage.local.get([ "OnboardingController", "KeyringController", @@ -18,6 +18,6 @@ import dt from"fs";import $t from"path";import{chromium as Ht}from"@playwright/t keyring.vault.length > 0 && preferences.showTestNetworks === true ); - })()`)}catch{return!1}},{message:"MetaMask onboarding state was not persisted to extension storage",timeout:6e4}).toBe(!0)}async function At(t){if(await t.getByTestId(O.unlockButton).isVisible().catch(()=>!1)){console.info("\u{1F4A1} Wallet is already locked");return}await z(t);let n=t.getByTestId(I.lockButton);await Yt(n).toBeVisible(),await n.click(),await q(t)}import{styleText as It}from"util";import{expect as D}from"@playwright/test";import Ct from"fs";import Zt from"path";async function X(t){let e=B(t),o=Zt.resolve(e,"password.txt");try{if(!Ct.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return Ct.readFileSync(o,"utf-8")}catch(n){throw new Error(`\u274C Failed to get ${t} password from cache: ${n.message}`)}}import{expect as te}from"@playwright/test";async function Tt({context:t,path:e,locator:o}){let n;try{await te.poll(async()=>(n=t.pages().filter(r=>r.url().startsWith("chrome-extension://")).find(r=>r.url().match(e)),!!n),{timeout:9e4}).toBe(!0)}catch{let r=t.pages().filter(a=>a.url().startsWith("chrome-extension://")).map(a=>a.url());throw new Error([`Popup page with path "${e}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(r)}`].join(` -`))}if(!n)throw new Error(`Popup page with path ${e} not found in context.`);return await ee(n,o),await n.setViewportSize({width:360,height:592}),n}async function ee(t,e){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(e).first().waitFor({state:"attached",timeout:45e3})}import Pt from"fs";import oe from"path";async function St(t){let e=B(t),o=oe.resolve(e,"extension-id.txt");try{if(!Pt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return Pt.readFileSync(o,"utf-8")}catch(n){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${n.message}`)}}var C=class{name="metamask";onboardingPath="/home.html#onboarding";async indexUrl(){return`chrome-extension://${await this.extensionId()}/home.html`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/notification.html`}async extensionId(){return await St(this.name)}async promptPage(e){let o=await this.promptUrl();return await Tt({context:e,path:o,locator:"div[data-testid='multichain-page']"})}};import{expect as Y}from"@playwright/test";async function Q(t,e){let o=t.getByTestId(w.accountMenuButton);if(await o.textContent()===e){console.info(`Can't switch account to "${e}", it is already selected.`);return}await Y(o).toBeVisible({timeout:15e3}),await o.click(),await Y(t.getByRole("heading",{name:/accounts/i})).toBeVisible();let r=t.getByTestId(T.addMultichainAccountButton),a=await r.textContent();a?.includes("Syncing")&&await Y.poll(async()=>(await r.textContent())?.trim()??"",{timeout:6e4}).not.toBe(a);let i=await t.getByTestId(/^multichain-account-cell-(?:entropy|keyring):/).all(),c=null;for(let m of i)if(await m.scrollIntoViewIfNeeded(),(await m.textContent())?.includes(e)){c=m;break}if(!c)throw Error(`Account with name "${e}" not found.`);await c.click(),await t.locator("div[class='spinner loading-overlay__spinner']").waitFor({state:"detached"})}import{expect as Z}from"@playwright/test";async function W({page:t}){await t.locator(`div:has(> button[data-testid='${w.openSettingsButton}'])`).click(),await t.getByTestId(I.networksButton).click();let n=t.getByTestId("networks-page-list");await Z(n).toBeVisible(),await Z(n).toContainText(/networks/i);let r="div:has(> p:has-text('Show test networks'))";await n.locator(r).scrollIntoViewIfNeeded();let a=n.locator(r),i=await a.locator("label[class='toggle-button toggle-button--off']").isVisible().catch(()=>!1),c=t.getByTestId("settings-header-back-button"),s=t.getByTestId("drawer-close-button");if(!i){await c.click(),await s.click(),console.info("Testnet networks are already visible.");return}await a.locator("label[class='toggle-button toggle-button--off']").click(),await t.getByTestId("Sepolia").scrollIntoViewIfNeeded(),await Z(t.getByTestId("Sepolia")).toBeVisible(),await c.click(),await s.click()}async function tt({page:t,mainAccountName:e,...o}){console.info(It("yellowBright",` - \u{1F98A} MetaMask onboarding started...`,{validateStream:!1}));let n=await X("metamask"),r=new C,a=t.locator("img[class='loading-spinner']"),i=t.getByTestId(y.createWalletButton),c=t.getByTestId(y.importWalletButton),s=t.getByTestId(y.createNewPasswordInput),m=t.getByTestId(y.confirmNewPasswordInput),u=t.getByTestId(y.confirmPasswordCheckbox),p=t.getByTestId(y.createPasswordButton),f=t.getByTestId(y.metamaskMetricsIAgreeButton),g=t.getByTestId(y.onboardingDoneButton);if(await a.waitFor({state:"detached",timeout:3e4}),o.mode==="create"){let P=t.getByTestId(y.useSecretRecoveryPhraseButton);await i.click(),await P.click(),await s.fill(n),await m.fill(n),await u.click(),await p.click(),await t.getByTestId(y.revealSecretRecoveryPhraseButton).click(),await t.getByTestId(y.recoveryPhraseRemindMeLaterButton).click(),await f.click()}if(o.mode==="import"){let{secretRecoveryPhrase:P}=o,S=P.split(" "),N=t.getByTestId(y.importUsingSecretRecoveryPhraseButton);await c.click(),await N.click();let at=t.getByTestId(y.secretRecoveryPhraseTextAreaInput);await at.fill(S[0]),await at.press("Space");for(let G=1;G{if(h){let{targetInfos:P}=await h.send("Target.getTargets"),S=P.find(N=>N.url===A);return _=S,!!S}},{timeout:15e3}).toBe(!0),_&&await h?.send("Target.closeTarget",{targetId:_.targetId}),await t.goto(await r.indexUrl()),await a.waitFor({state:"detached",timeout:3e4}),await D(t.getByTestId(w.buyButton)).toBeVisible(),await D(t.getByTestId(w.swapButton)).toBeVisible(),await D(t.getByTestId(w.sendButton)).toBeVisible(),await D(t.getByTestId(w.receiveButton)).toBeVisible(),await W({page:t}),e&&await Q(t,e),await bt(t),console.info(It("greenBright","\u2728 MetaMask onboarding completed successfully",{validateStream:!1}))}import{expect as ne}from"@playwright/test";async function vt(t){let e=t.getByTestId(j.cancelButton);await ne(e).toBeEnabled(),await e.click()}import{expect as b}from"@playwright/test";async function Rt({page:t,currentAccountName:e,newAccountName:o}){let n=t.getByTestId(w.accountMenuButton);if(await n.textContent()===o)throw Error(`The account to be renamed "${o}" already exists.`);await b(n).toBeVisible({timeout:15e3}),await n.click(),await b(t.getByRole("heading",{name:/accounts/i})).toBeVisible();let a=t.getByTestId(T.addMultichainAccountButton),i=await a.textContent();i?.includes("Syncing")&&await b.poll(async()=>(await a.textContent())?.trim()??"",{timeout:6e4}).not.toBe(i);let c=await t.getByTestId(/^multichain-account-cell/).all(),s=null;for(let h of c)if((await h.textContent())?.includes(e)){s=h;break}if(!s)throw Error(`Account with name "${e}" not found.`);if((await s.textContent())?.split("$")[0]===o)throw Error(`The new account name "${o}" is the same as the old account name "${e}".`);let u=t.locator(`div[aria-label='${e} options']`);await b(u).toBeVisible(),await u.click(),await b(t.getByRole("tooltip")).toBeVisible();let p=t.locator(`div[aria-label='${T.renameAccountLabel}']`);await b(p).toBeVisible(),await p.click();let f=t.getByRole("dialog"),g=f.getByRole("heading",{name:/rename/i});await b(g).toBeVisible();let E=f.getByRole("textbox");await b(E).toBeVisible(),await E.fill(o);let A=f.getByRole("button",{name:/confirm/i});await b(A).toBeEnabled(),await A.click();for(let h of c)if((await h.textContent())?.includes(o)){await b(h).toBeVisible(),await b(h).toContainText(o);break}await t.locator("button[aria-label='Back']").first().click()}import{expect as et}from"@playwright/test";async function Et({page:t,networkType:e,chainName:o}){let n=t.getByTestId(w.openNetworkSelectorButton);await n.click();let r=t.getByTestId("modal-header-close-button");if(e==="testnet"||e==="custom"){let s=t.getByRole("tab",{name:"Custom"});await s.click(),await t.locator("p:has-text('Testnets')").isVisible().catch(()=>!1)||(await r.click(),await W({page:t}),await n.click(),await s.click());let p=t.locator(`div div[data-testid='${o}']`),f=await p.textContent();et(f).toBe(o),await p.click();return}await t.getByRole("tab",{name:"Popular"}).click();let i=t.locator(`div div[data-testid='${o}']`),c=await i.textContent();et(c).toBe(o),await i.click(),await et(n).toContainText(o,{timeout:3e4})}import{expect as _t}from"@playwright/test";async function $(t){let e=await X("metamask"),o=t.getByTestId(O.passwordInput);if(await t.getByTestId(w.openNetworkSelectorButton).isVisible().catch(()=>!1)){console.info("\u{1F4A1} Wallet is already unlocked");return}await o.fill(e);let a=t.getByTestId(O.unlockButton);await _t(a).toBeVisible(),await a.click(),await q(t),await _t(t.getByTestId(w.buyButton)).toBeVisible({timeout:3e4})}var U=class extends C{page;constructor(e){super(),this.page=e}async onboard(e){await tt({page:this.page,...e})}async unlock(){await $(this.page)}async lock(){await At(this.page)}async renameAccount({newAccountName:e,currentAccountName:o}){await Rt({page:this.page,newAccountName:e,currentAccountName:o})}async addAccount({privateKey:e,accountName:o}){await pt({page:this.page,privateKey:e,accountName:o})}async switchAccount(e){await Q(this.page,e)}async switchNetwork({...e}){await Et({page:this.page,...e})}async getAccountAddress(e){return await Bt(this.page,e)}async toggleShowTestnetNetwork(){await W({page:this.page})}async addCustomNetwork({chainId:e,currencySymbol:o,networkName:n,rpcUrl:r}){await gt({page:this.page,chainId:e,currencySymbol:o,networkName:n,rpcUrl:r})}async connectToApp(){await xt(await this.promptPage(this.page.context()))}async confirmTransaction(e){await ht(await this.promptPage(this.page.context()),e)}async rejectTransaction(){await vt(await this.promptPage(this.page.context()))}};import Ft from"fs";import se from"path";import{test as ce,chromium as le}from"@playwright/test";import{Instance as de,Pool as ue}from"prool";import{expect as ae}from"@playwright/test";async function ot(t,e){let o=await t.newPage();return await ae(async()=>{await o.goto(e),await R(o)}).toPass(),o}async function nt(t,e){let o=await e.newPage();for(let{origin:n,localStorage:r}of t){let a=o.mainFrame();await a.goto(n),await a.evaluate(i=>{i.forEach(({name:c,value:s})=>{window.localStorage.setItem(c,s)})},r)}await o.close()}import re from"fs/promises";async function Nt(t){await re.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var ie=35e3;async function J(t,e){try{await Promise.race([t.close(),new Promise((o,n)=>setTimeout(()=>n(new Error("Context close timed out")),ie))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await Nt(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var H,ja=({slowMo:t=0,profileName:e}={})=>ce.extend({contextPath:async({browserName:o},n,r)=>{let a=await v(`${o}-${r.testId}`);await n(a)},context:async({context:o,contextPath:n},r)=>{let a=new C,i=B(a.name),c=await F(a.name),s=se.resolve(i,e??"wallet-data");if(!Ft.existsSync(s))throw new Error("\u274C Cache for MetaMask wallet data not found. Create it first");Ft.cpSync(s,n,{recursive:!0,force:!0});let m=M(c,t),u=await le.launchPersistentContext(n,{headless:!1,args:m,slowMo:process.env.HEADLESS?0:t}),{cookies:p,origins:f}=await o.storageState();p&&await u.addCookies(p),f&&f.length>0&&await nt(f,u);let g=await a.indexUrl();await u.waitForEvent("page",{predicate:x=>x.url().startsWith(g),timeout:3e4}),H=u.pages().find(x=>x.url().startsWith(g))??await ot(u,g);for(let x of u.pages())x.url().includes("about:blank")&&await x.close();await H.locator("img[class='loading-spinner']").waitFor({state:"detached",timeout:45e3}),await $(H),await r(u),await J(u,n)},metamaskPage:async({context:o},n)=>{await n(H)},metamask:async({context:o},n)=>{let r=new U(H);await n(r)},createAnvilNode:async({context:o},n,r)=>{let a=r.workerIndex,i;await n(async c=>{i=ue.define({instance:de.anvil(c)});let s=await i.start(a),m=`http://${s.host}:${s.port}`,p=c?.chainId??31337;return{rpcUrl:m,anvil:s,chainId:p}}),i&&await i.stop(a)},connectToAnvil:async({context:o,metamask:n,createAnvilNode:r},a)=>{await a(async()=>{let{chainId:i,rpcUrl:c}=await r({chainId:2251});await n.addCustomNetwork({chainId:i,currencySymbol:"ETH",networkName:"Anvil Localnet",rpcUrl:c})})}});import{test as we}from"@playwright/test";import{Instance as fe,Pool as ge}from"prool";import Mt from"fs";import me from"path";import{chromium as pe}from"@playwright/test";async function Lt({workerInfo:t,profileName:e,slowMo:o}){let n=new C,r=await v(t.workerIndex.toString()),a=B(n.name),i=me.resolve(a,e??"wallet-data");if(!Mt.existsSync(i))throw new Error(`Cache for ${n.name} does not exist. Create it first!`);Mt.cpSync(i,r,{recursive:!0,force:!0});let c=await F(n.name),s=M(c,o??0),m=await pe.launchPersistentContext(r,{headless:!1,args:s,slowMo:process.env.HEADLESS?0:o}),u=await n.indexUrl();await m.waitForEvent("page",{predicate:g=>g.url().startsWith(u),timeout:4e4});let p=m.pages().find(g=>g.url().startsWith(u));p||(p=await m.newPage(),await p.goto(u),await R(p));for(let g of m.pages())g.url().includes("about:blank")&&await g.close();return await p.locator("img[class='loading-spinner']").waitFor({state:"detached"}),{context:m,walletPage:p,contextPath:r}}var mr=({profileName:t,slowMo:e}={})=>we.extend({workerScopeContents:[async({browser:o},n,r)=>{let{context:a,contextPath:i,walletPage:c}=await Lt({workerInfo:r,profileName:t,slowMo:e}),s=new U(c);await s.unlock(),await n({wallet:s,walletPage:c,context:a}),await J(a,i)},{scope:"worker"}],createAnvilNode:async({context:o},n,r)=>{let a=r.workerIndex,i;await n(async c=>{i=ge.define({instance:fe.anvil(c)});let s=await i.start(a),m=`http://${s.host}:${s.port}`,p=c?.chainId??31337;return{rpcUrl:m,anvil:s,chainId:p}}),i&&await i.stop(a)},connectToAnvil:async({context:o,createAnvilNode:n,workerScopeContents:r},a)=>{await a(async()=>{let{wallet:i}=r,{chainId:c,rpcUrl:s}=await n({chainId:2251});await i.addCustomNetwork({chainId:c,currencySymbol:"ETH",networkName:"Anvil Localnet",rpcUrl:s})})}});export{U as Metamask,ja as metamaskFixture,mr as metamaskWorkerScopeFixture,Xe as workerScopeContext}; + })()`)}catch{return!1}},{message:"MetaMask onboarding state was not persisted to extension storage",timeout:6e4}).toBe(!0)}async function xt(t){if(await t.getByTestId(V.unlockButton).isVisible().catch(()=>!1)){console.info("\u{1F4A1} Wallet is already locked");return}await G(t);let a=t.getByTestId(v.lockButton);await Xt(a).toBeVisible(),await a.click(),await j(t)}import{styleText as At}from"util";import{expect as L}from"@playwright/test";import kt from"fs";import Qt from"path";async function q(t){let e=h(t),o=Qt.resolve(e,"password.txt");try{if(!kt.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return kt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Jt}from"@playwright/test";async function bt({context:t,path:e,locator:o}){let a;try{await Jt.poll(async()=>(a=t.pages().filter(n=>n.url().startsWith("chrome-extension://")).find(n=>n.url().match(e)),!!a),{timeout:9e4}).toBe(!0)}catch{let n=t.pages().filter(r=>r.url().startsWith("chrome-extension://")).map(r=>r.url());throw new Error([`Popup page with path "${e}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(n)}`].join(` +`))}if(!a)throw new Error(`Popup page with path ${e} not found in context.`);return await Yt(a,o),await a.setViewportSize({width:360,height:592}),a}async function Yt(t,e){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(e).first().waitFor({state:"attached",timeout:45e3})}import Ct from"fs";import Zt from"path";async function Tt(t){let e=h(t),o=Zt.resolve(e,"extension-id.txt");try{if(!Ct.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return Ct.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var C=class{name="metamask";onboardingPath="/home.html#onboarding";async indexUrl(){return`chrome-extension://${await this.extensionId()}/home.html`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/notification.html`}async extensionId(){return await Tt(this.name)}async promptPage(e){let o=await this.promptUrl();return await bt({context:e,path:o,locator:"div[data-testid='multichain-page']"})}};import{expect as J}from"@playwright/test";async function X(t,e){let o=t.getByTestId(p.accountMenuButton);if(await o.textContent()===e){console.info(`Can't switch account to "${e}", it is already selected.`);return}await J(o).toBeVisible({timeout:15e3}),await o.click(),await J(t.getByRole("heading",{name:/accounts/i})).toBeVisible();let n=t.getByTestId(I.addMultichainAccountButton),r=await n.textContent();r?.includes("Syncing")&&await J.poll(async()=>(await n.textContent())?.trim()??"",{timeout:6e4}).not.toBe(r);let i=await t.getByTestId(/^multichain-account-cell-(?:entropy|keyring):/).all(),c=null;for(let d of i)if(await d.scrollIntoViewIfNeeded(),(await d.textContent())?.includes(e)){c=d;break}if(!c)throw Error(`Account with name "${e}" not found.`);await c.click(),await t.locator("div[class='spinner loading-overlay__spinner']").waitFor({state:"detached"})}import{expect as Y}from"@playwright/test";async function W({page:t}){await t.locator(`div:has(> button[data-testid='${p.openSettingsButton}'])`).click(),await t.getByTestId(v.networksButton).click();let a=t.getByTestId("networks-page-list");await Y(a).toBeVisible(),await Y(a).toContainText(/networks/i);let n="div:has(> p:has-text('Show test networks'))";await a.locator(n).scrollIntoViewIfNeeded();let r=a.locator(n),i=await r.locator("label[class='toggle-button toggle-button--off']").isVisible().catch(()=>!1),c=t.getByTestId("settings-header-back-button"),s=t.getByTestId("drawer-close-button");if(!i){await c.click(),await s.click(),console.info("Testnet networks are already visible.");return}await r.locator("label[class='toggle-button toggle-button--off']").click(),await t.getByTestId("Sepolia").scrollIntoViewIfNeeded(),await Y(t.getByTestId("Sepolia")).toBeVisible(),await c.click(),await s.click()}async function Z({page:t,mainAccountName:e,...o}){console.info(At("yellowBright",` + \u{1F98A} MetaMask onboarding started...`,{validateStream:!1}));let a=await q("metamask"),n=new C,r=t.locator("img[class='loading-spinner']"),i=t.getByTestId(y.createWalletButton),c=t.getByTestId(y.importWalletButton),s=t.getByTestId(y.createNewPasswordInput),d=t.getByTestId(y.confirmNewPasswordInput),l=t.getByTestId(y.confirmPasswordCheckbox),w=t.getByTestId(y.createPasswordButton),f=t.getByTestId(y.metamaskMetricsIAgreeButton),g=t.getByTestId(y.onboardingDoneButton);if(await r.waitFor({state:"detached",timeout:3e4}),o.mode==="create"){let _=t.getByTestId(y.useSecretRecoveryPhraseButton);await i.click(),await _.click(),await s.fill(a),await d.fill(a),await l.click(),await w.click(),await t.getByTestId(y.revealSecretRecoveryPhraseButton).click(),await t.getByTestId(y.recoveryPhraseRemindMeLaterButton).click(),await f.click()}if(o.mode==="import"){let{secretRecoveryPhrase:_}=o,A=_.split(" "),$=t.getByTestId(y.importUsingSecretRecoveryPhraseButton);await c.click(),await $.click();let at=t.getByTestId(y.secretRecoveryPhraseTextAreaInput);await at.fill(A[0]),await at.press("Space");for(let H=1;H{if(k){let{targetInfos:_}=await k.send("Target.getTargets"),A=_.find($=>$.url===P);return S=A,!!A}},{timeout:15e3}).toBe(!0),S&&await k?.send("Target.closeTarget",{targetId:S.targetId}),await t.goto(await n.indexUrl()),await r.waitFor({state:"detached",timeout:3e4}),await L(t.getByTestId(p.buyButton)).toBeVisible(),await L(t.getByTestId(p.swapButton)).toBeVisible(),await L(t.getByTestId(p.sendButton)).toBeVisible(),await L(t.getByTestId(p.receiveButton)).toBeVisible(),await W({page:t}),e&&await X(t,e),await Bt(t),console.info(At("greenBright","\u2728 MetaMask onboarding completed successfully",{validateStream:!1}))}import{expect as te}from"@playwright/test";async function It(t){let e=t.getByTestId(z.cancelButton);await te(e).toBeEnabled(),await e.click()}import{expect as b}from"@playwright/test";async function Pt({page:t,currentAccountName:e,newAccountName:o}){let a=t.getByTestId(p.accountMenuButton);if(await a.textContent()===o)throw Error(`The account to be renamed "${o}" already exists.`);await b(a).toBeVisible({timeout:15e3}),await a.click(),await b(t.getByRole("heading",{name:/accounts/i})).toBeVisible();let r=t.getByTestId(I.addMultichainAccountButton),i=await r.textContent();i?.includes("Syncing")&&await b.poll(async()=>(await r.textContent())?.trim()??"",{timeout:6e4}).not.toBe(i);let c=await t.getByTestId(/^multichain-account-cell/).all(),s=null;for(let k of c)if((await k.textContent())?.includes(e)){s=k;break}if(!s)throw Error(`Account with name "${e}" not found.`);if((await s.textContent())?.split("$")[0]===o)throw Error(`The new account name "${o}" is the same as the old account name "${e}".`);let l=t.locator(`div[aria-label='${e} options']`);await b(l).toBeVisible(),await l.click(),await b(t.getByRole("tooltip")).toBeVisible();let w=t.locator(`div[aria-label='${I.renameAccountLabel}']`);await b(w).toBeVisible(),await w.click();let f=t.getByRole("dialog"),g=f.getByRole("heading",{name:/rename/i});await b(g).toBeVisible();let T=f.getByRole("textbox");await b(T).toBeVisible(),await T.fill(o);let P=f.getByRole("button",{name:/confirm/i});await b(P).toBeEnabled(),await P.click();for(let k of c)if((await k.textContent())?.includes(o)){await b(k).toBeVisible(),await b(k).toContainText(o);break}await t.locator("button[aria-label='Back']").first().click()}import{expect as tt}from"@playwright/test";async function St({page:t,networkType:e,chainName:o}){let a=t.getByTestId(p.openNetworkSelectorButton);await a.click();let n=t.getByTestId("modal-header-close-button");if(e==="testnet"||e==="custom"){let s=t.getByRole("tab",{name:"Custom"});await s.click(),await t.locator("p:has-text('Testnets')").isVisible().catch(()=>!1)||(await n.click(),await W({page:t}),await a.click(),await s.click());let w=t.locator(`div div[data-testid='${o}']`),f=await w.textContent();tt(f).toBe(o),await w.click();return}await t.getByRole("tab",{name:"Popular"}).click();let i=t.locator(`div div[data-testid='${o}']`),c=await i.textContent();tt(c).toBe(o),await i.click(),await tt(a).toContainText(o,{timeout:3e4})}import{expect as vt}from"@playwright/test";async function U(t){let e=await q("metamask"),o=t.getByTestId(V.passwordInput);if(await t.getByTestId(p.openNetworkSelectorButton).isVisible().catch(()=>!1)){console.info("\u{1F4A1} Wallet is already unlocked");return}await o.fill(e);let r=t.getByTestId(V.unlockButton);await vt(r).toBeVisible(),await r.click(),await j(t),await vt(t.getByTestId(p.buyButton)).toBeVisible({timeout:3e4})}var N=class extends C{page;constructor(e){super(),this.page=e}async onboard(e){await Z({page:this.page,...e})}async unlock(){await U(this.page)}async lock(){await xt(this.page)}async renameAccount({newAccountName:e,currentAccountName:o}){await Pt({page:this.page,newAccountName:e,currentAccountName:o})}async addAccount({privateKey:e,accountName:o}){await ut({page:this.page,privateKey:e,accountName:o})}async switchAccount(e){await X(this.page,e)}async switchNetwork({...e}){await St({page:this.page,...e})}async getAccountAddress(e){return await yt(this.page,e)}async toggleShowTestnetNetwork(){await W({page:this.page})}async addCustomNetwork({chainId:e,currencySymbol:o,networkName:a,rpcUrl:n}){await wt({page:this.page,chainId:e,currencySymbol:o,networkName:a,rpcUrl:n})}async connectToApp(){await gt(await this.promptPage(this.page.context()))}async confirmTransaction(e){await ft(await this.promptPage(this.page.context()),e)}async rejectTransaction(){await It(await this.promptPage(this.page.context()))}};import Rt from"fs";import ne from"path";import{test as re,chromium as ie}from"@playwright/test";import{Instance as se,Pool as ce}from"prool";import{expect as ee}from"@playwright/test";async function et(t,e){let o=await t.newPage();return await ee(async()=>{await o.goto(e),await R(o)}).toPass(),o}async function ot(t,e){let o=await e.newPage();for(let{origin:a,localStorage:n}of t){let r=o.mainFrame();await r.goto(a),await r.evaluate(i=>{i.forEach(({name:c,value:s})=>{window.localStorage.setItem(c,s)})},n)}await o.close()}import oe from"fs/promises";async function Et(t){await oe.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var ae=35e3;async function Q(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),ae))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await Et(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var O,Vn=({slowMo:t=0,profileName:e}={})=>re.extend({contextPath:async({browserName:o},a,n)=>{let r=await E(`${o}-${n.testId}`);await a(r)},context:async({context:o,contextPath:a},n)=>{let r=new C,i=h(r.name),c=await F(r.name),s=ne.resolve(i,e??"wallet-data");if(!Rt.existsSync(s))throw new Error("\u274C Cache for MetaMask wallet data not found. Create it first");Rt.cpSync(s,a,{recursive:!0,force:!0});let d=M(c,t),l=await ie.launchPersistentContext(a,{headless:!1,args:d,slowMo:process.env.HEADLESS?0:t}),{cookies:w,origins:f}=await o.storageState();w&&await l.addCookies(w),f&&f.length>0&&await ot(f,l);let g=await r.indexUrl();await l.waitForEvent("page",{predicate:x=>x.url().startsWith(g),timeout:3e4}),O=l.pages().find(x=>x.url().startsWith(g))??await et(l,g);for(let x of l.pages())x.url().includes("about:blank")&&await x.close();await O.locator("img[class='loading-spinner']").waitFor({state:"detached",timeout:45e3}),await U(O),await n(l),await Q(l,a)},metamaskPage:async({context:o},a)=>{await a(O)},metamask:async({context:o},a)=>{let n=new N(O);await a(n)},createAnvilNode:async({context:o},a,n)=>{let r=n.workerIndex,i;await a(async c=>{i=ce.define({instance:se.anvil(c)});let s=await i.start(r),d=`http://${s.host}:${s.port}`,w=c?.chainId??31337;return{rpcUrl:d,anvil:s,chainId:w}}),i&&await i.stop(r)},connectToAnvil:async({context:o,metamask:a,createAnvilNode:n},r)=>{await r(async()=>{let{chainId:i,rpcUrl:c}=await n({chainId:2251});await a.addCustomNetwork({chainId:i,currencySymbol:"ETH",networkName:"Anvil Localnet",rpcUrl:c})})}});import{test as ue}from"@playwright/test";import{Instance as me,Pool as we}from"prool";import _t from"fs";import le from"path";import{chromium as de}from"@playwright/test";async function Ft({workerInfo:t,profileName:e,slowMo:o}){let a=new C,n=await E(t.workerIndex.toString()),r=h(a.name),i=le.resolve(r,e??"wallet-data");if(!_t.existsSync(i))throw new Error(`Cache for ${a.name} does not exist. Create it first!`);_t.cpSync(i,n,{recursive:!0,force:!0});let c=await F(a.name),s=M(c,o??0),d=await de.launchPersistentContext(n,{headless:!1,args:s,slowMo:process.env.HEADLESS?0:o}),l=await a.indexUrl();await d.waitForEvent("page",{predicate:g=>g.url().startsWith(l),timeout:4e4});let w=d.pages().find(g=>g.url().startsWith(l));w||(w=await d.newPage(),await w.goto(l),await R(w));for(let g of d.pages())g.url().includes("about:blank")&&await g.close();return await w.locator("img[class='loading-spinner']").waitFor({state:"detached"}),{context:d,walletPage:w,contextPath:n}}var nr=({profileName:t,slowMo:e}={})=>ue.extend({workerScopeContents:[async({browser:o},a,n)=>{let{context:r,contextPath:i,walletPage:c}=await Ft({workerInfo:n,profileName:t,slowMo:e}),s=new N(c);await s.unlock(),await a({wallet:s,walletPage:c,context:r}),await Q(r,i)},{scope:"worker"}],createAnvilNode:async({context:o},a,n)=>{let r=n.workerIndex,i;await a(async c=>{i=we.define({instance:me.anvil(c)});let s=await i.start(r),d=`http://${s.host}:${s.port}`,w=c?.chainId??31337;return{rpcUrl:d,anvil:s,chainId:w}}),i&&await i.stop(r)},connectToAnvil:async({context:o,createAnvilNode:a,workerScopeContents:n},r)=>{await r(async()=>{let{wallet:i}=n,{chainId:c,rpcUrl:s}=await a({chainId:2251});await i.addCustomNetwork({chainId:c,currencySymbol:"ETH",networkName:"Anvil Localnet",rpcUrl:s})})}});export{N as Metamask,Vn as metamaskFixture,nr as metamaskWorkerScopeFixture,ze as workerScopeContext}; diff --git a/dist/wallets/petra/index.js b/dist/wallets/petra/index.js index de6c2fa..209a0f3 100644 --- a/dist/wallets/petra/index.js +++ b/dist/wallets/petra/index.js @@ -1,4 +1,4 @@ -import Y from"fs";import Pt from"path";import{chromium as Bt}from"@playwright/test";import ht from"path";var G=".wallet-cache",X=".wallet-context";var Z="13.33.0",W="https://github.com/amaify/chainwright/releases/download/v0.1.0/",xt=`https://github.com/MetaMask/metamask-extension/releases/download/v${Z}/`,Ut=`${xt}metamask-chrome-${Z}.zip`,Nt=`${W}solflare-wallet-extension-v2.19.1.zip`,$t=`${W}petra-wallet-extension-v2.4.8.zip`,Vt=`${W}phantom-wallet-extension-v26.10.0.zip`,Kt=`${W}meteor-wallet-extension-v0.7.0.zip`,zt=`${W}keplr-wallet-extension-v0.13.39.zip`;async function I(t){return ht.resolve(process.cwd(),X,t)}import yt from"path";function h(t){return yt.resolve(process.cwd(),G,t)}import J from"fs";import gt from"path";async function D(t){try{let o=h(t),e=gt.resolve(o,"extension-path.txt");if(!J.existsSync(e))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let r=J.readFileSync(e,"utf-8").trim();if(!r)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return r}catch(o){throw new Error(`\u274C Failed to get ${t} extension path: ${o.message}`)}}function O(t,o){let e=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(e.push("--headless=new"),o>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),e}async function q({wallet:t,workerInfo:o,profileName:e,slowMo:r}){let a=await I(o.workerIndex.toString()),c=h(t.name),s=Pt.resolve(c,e??"wallet-data");if(!Y.existsSync(s))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);Y.cpSync(s,a,{recursive:!0,force:!0});let l=await D(t.name),w=O(l,r??0),d=await Bt.launchPersistentContext(a,{headless:!1,args:w,slowMo:process.env.HEADLESS?0:r}),p=await t.indexUrl(),f=d.pages()[0];return f||(f=await d.newPage()),await f.goto(p),{context:d,walletPage:f,contextPath:a}}var u={depositButton:"button:has-text('Deposit')",sendButton:"button:has-text('Send')",receiveButton:"button:has-text('Receive')",settingsMenu:"button[aria-label='Settings']",lockButton:"global-menu-lock",accountMenuButton:"button[data-part='trigger']",accountDialog:"div[role='dialog']",backButton:"button[id='back-button']"},V={networkSection:"a[href='/settings/network']",backButton:"button[id='back-button']"},K={passwordInput:"input[name='password']",unlockButton:"button:has-text('Unlock')"},y={accountOptionsMenuButton:"button[data-scope='popover']",editAccountButton:"button[aria-label='Edit account name']",renameAccountInput:"input[name='name']",saveButton:"button:has-text('Save')",cancelButton:"button:has-text('Cancel')",addAccountButton:"button:has-text('Add accounts')",addAccountWithPrivateKeyButton:"button:has-text('Import private key')",addAccountWithMnemonicButton:"button:has-text('Import mnemonic')"};var m={createWalletButton:"button:has-text('Create an account')",createSeedPhraseButton:"button:has-text('Create a seed phrase wallet')",createNewPasswordInput:"input[name='password']",confirmNewPasswordInput:"input[name='confirmPassword']",confirmPasswordCheckbox:"label>div[data-scope='checkbox']",continueButton:"button:has-text('Continue')",skipCopyRecoveryPhraseButton:"button:has-text('Skip')",getStartedButton:"button:has-text('Get started')",onboardingCompleteText:"h1:has-text('Your wallet is ready, you may close this window')",importWalletButton:"button:has-text('Import an account')",importUsingPrivateKeyButton:"button:has-text('Import private key')",importUsingMnemonicButton:"button:has-text('Import mnemonic')",importButton:"button:has-text('Import')",privateKeyInput:"input[name='privateKey']"};import{expect as _}from"@playwright/test";import bt from"zod";async function S({page:t,newAccountName:o}){let e=bt.string().min(1,"Account name cannot be an empty string").parse(o);await t.locator(u.settingsMenu).click(),await _(t.getByText("Settings").first()).toBeVisible();let a=t.locator(y.editAccountButton);await _(a).toBeVisible(),await a.click(),await _(t.getByText("Account name").first()).toBeVisible();let c=t.locator(y.renameAccountInput);if(await c.getAttribute("value")===e)throw Error(`The account to be renamed "${e}" already exists.`);await c.fill(e);let l=t.locator(y.saveButton);await _(l).toBeEnabled(),await l.click(),await _(t.getByText(e).first()).toBeVisible(),await t.locator(u.backButton).click(),await Promise.allSettled([t.locator(u.depositButton).waitFor({state:"visible",timeout:2e4}),t.locator(u.sendButton).waitFor({state:"visible",timeout:2e4})])}async function R({page:t,accountName:o,mode:e,...r}){if(await t.locator(y.accountOptionsMenuButton).first().click(),await t.getByRole("dialog").locator(y.addAccountButton).click(),e==="privateKey"){let l="privateKey"in r?r.privateKey:"";if(await t.locator(y.addAccountWithPrivateKeyButton).click(),await t.locator(m.privateKeyInput).fill(l),await t.locator(m.importButton).click(),(await t.getByRole("status").locator("div[data-part='description']",{hasText:"Account already exists in wallet"}).textContent({timeout:3e3}).catch(()=>null))?.includes("Account already exists in wallet"))throw Error(`Account ${o} already exists in wallet`);await S({page:t,newAccountName:o})}if(e==="mnemonic"){let w=("mnemonicPhrase"in r?r.mnemonicPhrase:"").split(" ");await t.locator(y.addAccountWithMnemonicButton).click();for(let[A,g]of w.entries())await t.locator(`input[name="mnemonic-${String.fromCharCode(97+A)}"]`).fill(g);if(await t.locator(m.continueButton).click(),(await t.getByRole("status").locator("div[data-part='description']",{hasText:"Account already exists in wallet"}).textContent({timeout:3e3}).catch(()=>null))?.includes("Account already exists in wallet"))throw Error(`Account ${o} already exists in wallet`);await S({page:t,newAccountName:o})}}function Q(t){return new Promise(o=>setTimeout(o,t))}var C={approveButton:'button:has-text("Approve")',cancelButton:'button:has-text("Cancel")'};var At=/^[A-Z0-9]+(?:_[A-Z0-9]+)+$/;function tt(t){return At.test(t)?t.toLowerCase().replace(/_/g," ").replace(/\b[a-z]/g,o=>o.toUpperCase()):t}async function kt(t,o){for(;;){let r=o();if(r||t.isClosed())break;try{let a=t.locator("div:has(> h2:has-text('Simulation error'))");if(await a.isVisible().catch(()=>!1)){let s=await a.locator("p").textContent();throw new Error(`[Confirm Transaction Error]: ${tt(s||"Unexpected error!")}`)}}catch(a){if(t.isClosed())break;throw a instanceof Error?a:new Error(`[Confirm Transaction Error]: ${a}`)}if(r||t.isClosed())break;await Q(300)}}async function ot(t){let o=!1;kt(t,()=>o).catch(async a=>{t.isClosed()||console.error(a.message)}),await t.locator(C.approveButton).click(),o=!0}import{expect as St}from"@playwright/test";async function E(t,o){let e=t.locator(y.accountOptionsMenuButton).first();if((await e.textContent())?.split("Switch wallet")[1]?.split("0x")[0]?.toLowerCase().trim().includes(o.toLowerCase().trim())){console.info(` - Switching to ${o} account aborted because the account is already selected.`);return}await e.click();let c=await t.getByRole("dialog").locator("> div > div > button[type='button']").all(),s=null;for(let l of c){let d=(await l.textContent())?.split("0x")[0]?.split("...")[0];if(o.toLowerCase().includes(d?.toLowerCase()??"")){s=l;break}}if(!s)throw new Error(`Account with name "${o}" not found.`);await s.click()}async function et(t,o){o&&await E(t,o);let e=t.locator(C.approveButton);await St(e).toBeEnabled({timeout:2e4}),await e.click()}async function rt(t){return await t.getByRole("button",{name:"Copy Address",exact:!0}).click(),await t.evaluate(async()=>await navigator.clipboard.readText())}import{expect as at}from"@playwright/test";async function nt(t){let o=t.locator(u.settingsMenu);await at(o).toBeVisible(),await o.click(),await at(t.getByText("Settings").first()).toBeVisible()}async function it(t){await nt(t),await t.getByRole("button",{name:/lock wallet/i}).click(),await t.getByRole("heading",{name:/welcome/i}).waitFor({state:"visible",timeout:2e4})}import{styleText as ut}from"util";import{expect as b}from"@playwright/test";async function M(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}import ct from"fs";import Ct from"path";async function U(t){let o=h(t),e=Ct.resolve(o,"password.txt");try{if(!ct.existsSync(e))throw new Error("\u274C password.txt not found. Run setup script first.");return ct.readFileSync(e,"utf-8")}catch(r){throw new Error(`\u274C Failed to get ${t} password from cache: ${r.message}`)}}import{expect as Et}from"@playwright/test";async function st({context:t,path:o,locator:e}){let r;try{await Et.poll(async()=>(r=t.pages().filter(a=>a.url().startsWith("chrome-extension://")).find(a=>a.url().match(o)),!!r),{timeout:9e4}).toBe(!0)}catch{let a=t.pages().filter(c=>c.url().startsWith("chrome-extension://")).map(c=>c.url());throw new Error([`Popup page with path "${o}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(a)}`].join(` -`))}if(!r)throw new Error(`Popup page with path ${o} not found in context.`);return await Tt(r,e),await r.setViewportSize({width:360,height:592}),r}async function Tt(t,o){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(o).first().waitFor({state:"attached",timeout:45e3})}import lt from"fs";import vt from"path";async function pt(t){let o=h(t),e=vt.resolve(o,"extension-id.txt");try{if(!lt.existsSync(e))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return lt.readFileSync(e,"utf-8")}catch(r){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${r.message}`)}}var P=class{name="petra";onboardingPath="/onboarding.html";async indexUrl(){return`chrome-extension://${await this.extensionId()}/index.html`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/prompt.html`}async extensionId(){return await pt(this.name)}async promptPage(o){let e=await this.promptUrl();return await st({context:o,path:e,locator:"div[id='prompt']"})}};async function N(t,o){await t.locator(u.settingsMenu).click(),await t.locator(V.networkSection).click(),await t.locator(`div:has(> span:has-text("${o}"))`).last().click();let c=t.locator(V.backButton);await c.click(),await c.click()}var T=3e4;async function z({page:t,network:o,...e}){console.info(ut("yellowBright",` - Petra onboarding started...`,{validateStream:!1}));let r=new P,a=await U("petra"),c=t.locator(m.createWalletButton),s=t.locator(m.importWalletButton),l=t.locator(m.createNewPasswordInput),w=t.locator(m.confirmNewPasswordInput),d=t.locator(m.confirmPasswordCheckbox),p=t.locator(m.continueButton),f=t.locator(m.getStartedButton),B=t.locator(m.onboardingCompleteText);if(e.mode==="create"){let x=t.locator(m.createSeedPhraseButton);await c.click(),await x.click(),await l.fill(a),await w.fill(a),await d.click(),await p.click(),await t.locator(m.skipCopyRecoveryPhraseButton).click(),await f.click(),await b(B).toBeVisible({timeout:25e3}),await t.goto(await r.indexUrl()),await b(t.locator(u.depositButton)).toBeVisible({timeout:T}),await b(t.locator(u.sendButton)).toBeVisible({timeout:T})}if(e.mode==="importPrivateKey"){let{privateKey:x}=e,A=t.locator(m.importUsingPrivateKeyButton),g=t.locator(m.privateKeyInput),k=t.locator(m.importButton);await s.click(),await A.click(),await g.fill(x),await k.click(),await l.fill(a),await w.fill(a),await d.click(),await p.click(),await f.click(),await b(B).toBeVisible({timeout:25e3}),await t.goto(await r.indexUrl()),await M(t),await b(t.locator(u.depositButton)).toBeVisible({timeout:T}),await b(t.locator(u.sendButton)).toBeVisible({timeout:T})}if(e.mode==="importMnemonic"){let{secretRecoveryPhrase:x}=e,A=t.locator(m.importUsingMnemonicButton);await s.click(),await A.click();for(let[g,k]of x.split(" ").entries())await t.locator(`input[name="mnemonic-${String.fromCharCode(97+g)}"]`).fill(k);await p.click(),await l.fill(a),await w.fill(a),await d.click(),await p.click(),await f.click(),await b(B).toBeVisible({timeout:25e3}),await t.goto(await r.indexUrl()),await b(t.locator(u.depositButton)).toBeVisible({timeout:T}),await b(t.locator(u.sendButton)).toBeVisible({timeout:T})}if(await S({page:t,newAccountName:e.accountName}),e.additionalAccounts&&e.additionalAccounts.length>0){for(let{...x}of e.additionalAccounts)await R({page:t,...x});await E(t,e.accountName)}await N(t,o),console.info(ut("greenBright","\u2728 Petra onboarding completed successfully",{validateStream:!1}))}import{expect as Wt}from"@playwright/test";async function mt(t){let o=t.locator(C.cancelButton);await Wt(o).toBeEnabled(),await o.click()}import{expect as wt}from"@playwright/test";async function F(t){let o=await U("petra"),e=t.locator(K.passwordInput);if(!await e.isVisible().then(()=>!0).catch(()=>!1)){console.info("\u{1F4A1} Wallet is already unlocked");return}await wt(e).toBeVisible({timeout:15e3}),await e.fill(o);let a=t.locator(K.unlockButton);await wt(a).toBeEnabled(),await a.click(),await Promise.allSettled([t.locator(u.sendButton).waitFor({state:"visible",timeout:2e4}),t.locator(u.receiveButton).waitFor({state:"visible",timeout:2e4})])}var v=class extends P{page;constructor(o){super(),this.page=o}async onboard(o){await z({page:this.page,...o})}async unlock(){await F(this.page)}async lock(){await it(this.page)}async renameAccount({newAccountName:o}){await S({page:this.page,newAccountName:o})}async switchNetwork(o){await N(this.page,o)}async switchAccount(o){await E(this.page,o)}async getAccountAddress(){return await rt(this.page)}async addAccount({accountName:o,...e}){await R({page:this.page,accountName:o,...e})}async connectToApp(o){await et(await this.promptPage(this.page.context()),o)}async confirmTransaction(){await ot(await this.promptPage(this.page.context()))}async rejectTransaction(){await mt(await this.promptPage(this.page.context()))}};import ft from"fs";import Ft from"path";import{test as Lt,chromium as Dt}from"@playwright/test";import{expect as It}from"@playwright/test";async function H(t,o){let e=await t.newPage();return await It(async()=>{await e.goto(o),await M(e)}).toPass(),e}async function j(t,o){let e=await o.newPage();for(let{origin:r,localStorage:a}of t){let c=e.mainFrame();await c.goto(r),await c.evaluate(s=>{s.forEach(({name:l,value:w})=>{window.localStorage.setItem(l,w)})},a)}await e.close()}import _t from"fs/promises";async function dt(t){await _t.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Mt=35e3;async function $(t,o){try{await Promise.race([t.close(),new Promise((e,r)=>setTimeout(()=>r(new Error("Context close timed out")),Mt))])}catch(e){console.warn(`Browser context close did not complete cleanly: ${e.message}`)}try{await dt(o)}catch(e){console.error(`Failed to remove temporary context directory at ${o}. Error:`,e)}}var L,Lr=({slowMo:t=0,profileName:o}={})=>Lt.extend({contextPath:async({browserName:e},r,a)=>{let c=await I(`${e}-${a.testId}`);await r(c)},context:async({context:e,contextPath:r},a)=>{let c=new P,s=h(c.name),l=await D(c.name),w=Ft.resolve(s,o??"wallet-data");if(!ft.existsSync(w))throw new Error("\u274C Cache for Petra wallet data not found. Create it first");ft.cpSync(w,r,{recursive:!0,force:!0});let d=O(l,t),p=await Dt.launchPersistentContext(r,{headless:!1,args:d,slowMo:process.env.HEADLESS?0:t});await p.grantPermissions(["clipboard-read"]);let{cookies:f,origins:B}=await e.storageState();f&&await p.addCookies(f),B&&B.length>0&&await j(B,p);let x=await c.indexUrl();L=p.pages().find(g=>g.url().startsWith(x))||await H(p,x);for(let g of p.pages()){let k=g.url();(k.includes("about:blank")||k.includes(c.onboardingPath))&&await g.close()}await L.bringToFront(),await F(L),await a(p),await $(p,r)},petraPage:async({context:e},r)=>{await r(L)},petra:async({context:e},r)=>{let a=new v(L);await r(a)}});import{test as Ot}from"@playwright/test";var Kr=({slowMo:t,profileName:o}={})=>Ot.extend({workerScopeContents:[async({browser:e},r,a)=>{let c=new P,{context:s,contextPath:l,walletPage:w}=await q({wallet:c,workerInfo:a,profileName:o,slowMo:t});await s.grantPermissions(["clipboard-read"]);for(let p of s.pages())p.url().includes(c.onboardingPath)&&await p.close();let d=new v(w);await d.unlock(),await r({wallet:d,walletPage:w,context:s}),await $(s,l)},{scope:"worker"}]});export{v as Petra,Lr as petraFixture,Kr as petraWorkerScopeFixture,q as workerScopeContext}; +import Y from"fs";import Pt from"path";import{chromium as Bt}from"@playwright/test";import ht from"path";var G=".wallet-cache",X=".wallet-context";var Z="13.33.0",W="https://github.com/amaify/chainwright/releases/download/v0.1.0/",xt=`https://github.com/MetaMask/metamask-extension/releases/download/v${Z}/`,Rt=`${xt}metamask-chrome-${Z}.zip`,Ut=`${W}solflare-wallet-extension-v2.19.1.zip`,Nt=`${W}petra-wallet-extension-v2.4.8.zip`,$t=`${W}phantom-wallet-extension-v26.10.0.zip`,Vt=`${W}meteor-wallet-extension-v0.7.0.zip`,Kt=`${W}keplr-wallet-extension-v0.13.39.zip`;async function I(t){return ht.resolve(process.cwd(),X,t)}import yt from"path";function h(t){return yt.resolve(process.cwd(),G,t)}import J from"fs";import gt from"path";async function D(t){try{let o=h(t),e=gt.resolve(o,"extension-path.txt");if(!J.existsSync(e))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let r=J.readFileSync(e,"utf-8").trim();if(!r)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return r}catch(o){throw new Error(`\u274C Failed to get ${t} extension path: ${o.message}`)}}function O(t,o){let e=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(e.push("--headless=new"),o>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),e}async function q({wallet:t,workerInfo:o,profileName:e,slowMo:r}){let a=await I(o.workerIndex.toString()),c=h(t.name),s=Pt.resolve(c,e??"wallet-data");if(!Y.existsSync(s))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);Y.cpSync(s,a,{recursive:!0,force:!0});let m=await D(t.name),w=O(m,r??0),d=await Bt.launchPersistentContext(a,{headless:!1,args:w,slowMo:process.env.HEADLESS?0:r}),l=await t.indexUrl(),f=d.pages()[0];return f||(f=await d.newPage()),await f.goto(l),{context:d,walletPage:f,contextPath:a}}var p={depositButton:"button:has-text('Deposit')",sendButton:"button:has-text('Send')",receiveButton:"button:has-text('Receive')",settingsMenu:"button[aria-label='Settings']",lockButton:"global-menu-lock",accountMenuButton:"button[data-part='trigger']",accountDialog:"div[role='dialog']",backButton:"button[id='back-button']"},V={networkSection:"a[href='/settings/network']",backButton:"button[id='back-button']"},K={passwordInput:"input[name='password']",unlockButton:"button:has-text('Unlock')"},y={accountOptionsMenuButton:"button[data-scope='popover']",editAccountButton:"button[aria-label='Edit account name']",renameAccountInput:"input[name='name']",saveButton:"button:has-text('Save')",cancelButton:"button:has-text('Cancel')",addAccountButton:"button:has-text('Add accounts')",addAccountWithPrivateKeyButton:"button:has-text('Import private key')",addAccountWithMnemonicButton:"button:has-text('Import mnemonic')"};var u={createWalletButton:"button:has-text('Create an account')",createSeedPhraseButton:"button:has-text('Create a seed phrase wallet')",createNewPasswordInput:"input[name='password']",confirmNewPasswordInput:"input[name='confirmPassword']",confirmPasswordCheckbox:"label>div[data-scope='checkbox']",continueButton:"button:has-text('Continue')",skipCopyRecoveryPhraseButton:"button:has-text('Skip')",getStartedButton:"button:has-text('Get started')",onboardingCompleteText:"h1:has-text('Your wallet is ready, you may close this window')",importWalletButton:"button:has-text('Import an account')",importUsingPrivateKeyButton:"button:has-text('Import private key')",importUsingMnemonicButton:"button:has-text('Import mnemonic')",importButton:"button:has-text('Import')",privateKeyInput:"input[name='privateKey']"};import{expect as _}from"@playwright/test";async function S({page:t,newAccountName:o}){await t.locator(p.settingsMenu).click(),await _(t.getByText("Settings").first()).toBeVisible();let r=t.locator(y.editAccountButton);await _(r).toBeVisible(),await r.click(),await _(t.getByText("Account name").first()).toBeVisible();let a=t.locator(y.renameAccountInput);if(await a.getAttribute("value")===o)throw Error(`The account to be renamed "${o}" already exists.`);await a.fill(o);let s=t.locator(y.saveButton);await _(s).toBeEnabled(),await s.click(),await _(t.getByText(o).first()).toBeVisible(),await t.locator(p.backButton).click(),await Promise.allSettled([t.locator(p.depositButton).waitFor({state:"visible",timeout:2e4}),t.locator(p.sendButton).waitFor({state:"visible",timeout:2e4})])}async function R({page:t,accountName:o,mode:e,...r}){if(await t.locator(y.accountOptionsMenuButton).first().click(),await t.getByRole("dialog").locator(y.addAccountButton).click(),e==="privateKey"){let m="privateKey"in r?r.privateKey:"";if(await t.locator(y.addAccountWithPrivateKeyButton).click(),await t.locator(u.privateKeyInput).fill(m),await t.locator(u.importButton).click(),(await t.getByRole("status").locator("div[data-part='description']",{hasText:"Account already exists in wallet"}).textContent({timeout:3e3}).catch(()=>null))?.includes("Account already exists in wallet"))throw Error(`Account ${o} already exists in wallet`);await S({page:t,newAccountName:o})}if(e==="mnemonic"){let w=("mnemonicPhrase"in r?r.mnemonicPhrase:"").split(" ");await t.locator(y.addAccountWithMnemonicButton).click();for(let[A,g]of w.entries())await t.locator(`input[name="mnemonic-${String.fromCharCode(97+A)}"]`).fill(g);if(await t.locator(u.continueButton).click(),(await t.getByRole("status").locator("div[data-part='description']",{hasText:"Account already exists in wallet"}).textContent({timeout:3e3}).catch(()=>null))?.includes("Account already exists in wallet"))throw Error(`Account ${o} already exists in wallet`);await S({page:t,newAccountName:o})}}function Q(t){return new Promise(o=>setTimeout(o,t))}var C={approveButton:'button:has-text("Approve")',cancelButton:'button:has-text("Cancel")'};var bt=/^[A-Z0-9]+(?:_[A-Z0-9]+)+$/;function tt(t){return bt.test(t)?t.toLowerCase().replace(/_/g," ").replace(/\b[a-z]/g,o=>o.toUpperCase()):t}async function At(t,o){for(;;){let r=o();if(r||t.isClosed())break;try{let a=t.locator("div:has(> h2:has-text('Simulation error'))");if(await a.isVisible().catch(()=>!1)){let s=await a.locator("p").textContent();throw new Error(`[Confirm Transaction Error]: ${tt(s||"Unexpected error!")}`)}}catch(a){if(t.isClosed())break;throw a instanceof Error?a:new Error(`[Confirm Transaction Error]: ${a}`)}if(r||t.isClosed())break;await Q(300)}}async function ot(t){let o=!1;At(t,()=>o).catch(async a=>{t.isClosed()||console.error(a.message)}),await t.locator(C.approveButton).click(),o=!0}import{expect as kt}from"@playwright/test";async function E(t,o){let e=t.locator(y.accountOptionsMenuButton).first();if((await e.textContent())?.split("Switch wallet")[1]?.split("0x")[0]?.toLowerCase().trim().includes(o.toLowerCase().trim())){console.info(` + Switching to ${o} account aborted because the account is already selected.`);return}await e.click();let c=await t.getByRole("dialog").locator("> div > div > button[type='button']").all(),s=null;for(let m of c){let d=(await m.textContent())?.split("0x")[0]?.split("...")[0];if(o.toLowerCase().includes(d?.toLowerCase()??"")){s=m;break}}if(!s)throw new Error(`Account with name "${o}" not found.`);await s.click()}async function et(t,o){o&&await E(t,o);let e=t.locator(C.approveButton);await kt(e).toBeEnabled({timeout:2e4}),await e.click()}async function rt(t){return await t.getByRole("button",{name:"Copy Address",exact:!0}).click(),await t.evaluate(async()=>await navigator.clipboard.readText())}import{expect as at}from"@playwright/test";async function nt(t){let o=t.locator(p.settingsMenu);await at(o).toBeVisible(),await o.click(),await at(t.getByText("Settings").first()).toBeVisible()}async function it(t){await nt(t),await t.getByRole("button",{name:/lock wallet/i}).click(),await t.getByRole("heading",{name:/welcome/i}).waitFor({state:"visible",timeout:2e4})}import{styleText as ut}from"util";import{expect as b}from"@playwright/test";async function M(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}import ct from"fs";import St from"path";async function U(t){let o=h(t),e=St.resolve(o,"password.txt");try{if(!ct.existsSync(e))throw new Error("\u274C password.txt not found. Run setup script first.");return ct.readFileSync(e,"utf-8")}catch(r){throw new Error(`\u274C Failed to get ${t} password from cache: ${r.message}`)}}import{expect as Ct}from"@playwright/test";async function st({context:t,path:o,locator:e}){let r;try{await Ct.poll(async()=>(r=t.pages().filter(a=>a.url().startsWith("chrome-extension://")).find(a=>a.url().match(o)),!!r),{timeout:9e4}).toBe(!0)}catch{let a=t.pages().filter(c=>c.url().startsWith("chrome-extension://")).map(c=>c.url());throw new Error([`Popup page with path "${o}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(a)}`].join(` +`))}if(!r)throw new Error(`Popup page with path ${o} not found in context.`);return await Et(r,e),await r.setViewportSize({width:360,height:592}),r}async function Et(t,o){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(o).first().waitFor({state:"attached",timeout:45e3})}import lt from"fs";import Tt from"path";async function pt(t){let o=h(t),e=Tt.resolve(o,"extension-id.txt");try{if(!lt.existsSync(e))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return lt.readFileSync(e,"utf-8")}catch(r){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${r.message}`)}}var P=class{name="petra";onboardingPath="/onboarding.html";async indexUrl(){return`chrome-extension://${await this.extensionId()}/index.html`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/prompt.html`}async extensionId(){return await pt(this.name)}async promptPage(o){let e=await this.promptUrl();return await st({context:o,path:e,locator:"div[id='prompt']"})}};async function N(t,o){await t.locator(p.settingsMenu).click(),await t.locator(V.networkSection).click(),await t.locator(`div:has(> span:has-text("${o}"))`).last().click();let c=t.locator(V.backButton);await c.click(),await c.click()}var T=3e4;async function z({page:t,network:o,...e}){console.info(ut("yellowBright",` + Petra onboarding started...`,{validateStream:!1}));let r=new P,a=await U("petra"),c=t.locator(u.createWalletButton),s=t.locator(u.importWalletButton),m=t.locator(u.createNewPasswordInput),w=t.locator(u.confirmNewPasswordInput),d=t.locator(u.confirmPasswordCheckbox),l=t.locator(u.continueButton),f=t.locator(u.getStartedButton),B=t.locator(u.onboardingCompleteText);if(e.mode==="create"){let x=t.locator(u.createSeedPhraseButton);await c.click(),await x.click(),await m.fill(a),await w.fill(a),await d.click(),await l.click(),await t.locator(u.skipCopyRecoveryPhraseButton).click(),await f.click(),await b(B).toBeVisible({timeout:25e3}),await t.goto(await r.indexUrl()),await b(t.locator(p.depositButton)).toBeVisible({timeout:T}),await b(t.locator(p.sendButton)).toBeVisible({timeout:T})}if(e.mode==="importPrivateKey"){let{privateKey:x}=e,A=t.locator(u.importUsingPrivateKeyButton),g=t.locator(u.privateKeyInput),k=t.locator(u.importButton);await s.click(),await A.click(),await g.fill(x),await k.click(),await m.fill(a),await w.fill(a),await d.click(),await l.click(),await f.click(),await b(B).toBeVisible({timeout:25e3}),await t.goto(await r.indexUrl()),await M(t),await b(t.locator(p.depositButton)).toBeVisible({timeout:T}),await b(t.locator(p.sendButton)).toBeVisible({timeout:T})}if(e.mode==="importMnemonic"){let{secretRecoveryPhrase:x}=e,A=t.locator(u.importUsingMnemonicButton);await s.click(),await A.click();for(let[g,k]of x.split(" ").entries())await t.locator(`input[name="mnemonic-${String.fromCharCode(97+g)}"]`).fill(k);await l.click(),await m.fill(a),await w.fill(a),await d.click(),await l.click(),await f.click(),await b(B).toBeVisible({timeout:25e3}),await t.goto(await r.indexUrl()),await b(t.locator(p.depositButton)).toBeVisible({timeout:T}),await b(t.locator(p.sendButton)).toBeVisible({timeout:T})}if(await S({page:t,newAccountName:e.accountName}),e.additionalAccounts&&e.additionalAccounts.length>0){for(let{...x}of e.additionalAccounts)await R({page:t,...x});await E(t,e.accountName)}await N(t,o),console.info(ut("greenBright","\u2728 Petra onboarding completed successfully",{validateStream:!1}))}import{expect as vt}from"@playwright/test";async function mt(t){let o=t.locator(C.cancelButton);await vt(o).toBeEnabled(),await o.click()}import{expect as wt}from"@playwright/test";async function F(t){let o=await U("petra"),e=t.locator(K.passwordInput);if(!await e.isVisible().then(()=>!0).catch(()=>!1)){console.info("\u{1F4A1} Wallet is already unlocked");return}await wt(e).toBeVisible({timeout:15e3}),await e.fill(o);let a=t.locator(K.unlockButton);await wt(a).toBeEnabled(),await a.click(),await Promise.allSettled([t.locator(p.sendButton).waitFor({state:"visible",timeout:2e4}),t.locator(p.receiveButton).waitFor({state:"visible",timeout:2e4})])}var v=class extends P{page;constructor(o){super(),this.page=o}async onboard(o){await z({page:this.page,...o})}async unlock(){await F(this.page)}async lock(){await it(this.page)}async renameAccount({newAccountName:o}){await S({page:this.page,newAccountName:o})}async switchNetwork(o){await N(this.page,o)}async switchAccount(o){await E(this.page,o)}async getAccountAddress(){return await rt(this.page)}async addAccount({accountName:o,...e}){await R({page:this.page,accountName:o,...e})}async connectToApp(o){await et(await this.promptPage(this.page.context()),o)}async confirmTransaction(){await ot(await this.promptPage(this.page.context()))}async rejectTransaction(){await mt(await this.promptPage(this.page.context()))}};import ft from"fs";import Mt from"path";import{test as Ft,chromium as Lt}from"@playwright/test";import{expect as Wt}from"@playwright/test";async function H(t,o){let e=await t.newPage();return await Wt(async()=>{await e.goto(o),await M(e)}).toPass(),e}async function j(t,o){let e=await o.newPage();for(let{origin:r,localStorage:a}of t){let c=e.mainFrame();await c.goto(r),await c.evaluate(s=>{s.forEach(({name:m,value:w})=>{window.localStorage.setItem(m,w)})},a)}await e.close()}import It from"fs/promises";async function dt(t){await It.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var _t=35e3;async function $(t,o){try{await Promise.race([t.close(),new Promise((e,r)=>setTimeout(()=>r(new Error("Context close timed out")),_t))])}catch(e){console.warn(`Browser context close did not complete cleanly: ${e.message}`)}try{await dt(o)}catch(e){console.error(`Failed to remove temporary context directory at ${o}. Error:`,e)}}var L,Mr=({slowMo:t=0,profileName:o}={})=>Ft.extend({contextPath:async({browserName:e},r,a)=>{let c=await I(`${e}-${a.testId}`);await r(c)},context:async({context:e,contextPath:r},a)=>{let c=new P,s=h(c.name),m=await D(c.name),w=Mt.resolve(s,o??"wallet-data");if(!ft.existsSync(w))throw new Error("\u274C Cache for Petra wallet data not found. Create it first");ft.cpSync(w,r,{recursive:!0,force:!0});let d=O(m,t),l=await Lt.launchPersistentContext(r,{headless:!1,args:d,slowMo:process.env.HEADLESS?0:t});await l.grantPermissions(["clipboard-read"]);let{cookies:f,origins:B}=await e.storageState();f&&await l.addCookies(f),B&&B.length>0&&await j(B,l);let x=await c.indexUrl();L=l.pages().find(g=>g.url().startsWith(x))||await H(l,x);for(let g of l.pages()){let k=g.url();(k.includes("about:blank")||k.includes(c.onboardingPath))&&await g.close()}await L.bringToFront(),await F(L),await a(l),await $(l,r)},petraPage:async({context:e},r)=>{await r(L)},petra:async({context:e},r)=>{let a=new v(L);await r(a)}});import{test as Dt}from"@playwright/test";var $r=({slowMo:t,profileName:o}={})=>Dt.extend({workerScopeContents:[async({browser:e},r,a)=>{let c=new P,{context:s,contextPath:m,walletPage:w}=await q({wallet:c,workerInfo:a,profileName:o,slowMo:t});await s.grantPermissions(["clipboard-read"]);for(let l of s.pages())l.url().includes(c.onboardingPath)&&await l.close();let d=new v(w);await d.unlock(),await r({wallet:d,walletPage:w,context:s}),await $(s,m)},{scope:"worker"}]});export{v as Petra,Mr as petraFixture,$r as petraWorkerScopeFixture,q as workerScopeContext}; diff --git a/dist/wallets/phantom/index.js b/dist/wallets/phantom/index.js index 190f8b5..978d103 100644 --- a/dist/wallets/phantom/index.js +++ b/dist/wallets/phantom/index.js @@ -1,4 +1,4 @@ -import nt from"fs";import Tt from"path";import{chromium as St}from"@playwright/test";import At from"path";var tt=".wallet-cache",et=".wallet-context";var ot="13.33.0",M="https://github.com/amaify/chainwright/releases/download/v0.1.0/",kt=`https://github.com/MetaMask/metamask-extension/releases/download/v${ot}/`,Xt=`${kt}metamask-chrome-${ot}.zip`,Jt=`${M}solflare-wallet-extension-v2.19.1.zip`,qt=`${M}petra-wallet-extension-v2.4.8.zip`,Qt=`${M}phantom-wallet-extension-v26.10.0.zip`,Yt=`${M}meteor-wallet-extension-v0.7.0.zip`,Zt=`${M}keplr-wallet-extension-v0.13.39.zip`;async function T(t){return At.resolve(process.cwd(),et,t)}import Ct from"path";function x(t){return Ct.resolve(process.cwd(),tt,t)}import at from"fs";import bt from"path";async function S(t){try{let e=x(t),o=bt.resolve(e,"extension-path.txt");if(!at.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=at.readFileSync(o,"utf-8").trim();if(!a)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return a}catch(e){throw new Error(`\u274C Failed to get ${t} extension path: ${e.message}`)}}function I(t,e){let o=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(o.push("--headless=new"),e>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),o}async function Ce({wallet:t,workerInfo:e,profileName:o,slowMo:a}){let n=await T(e.workerIndex.toString()),r=x(t.name),u=Tt.resolve(r,o??"wallet-data");if(!nt.existsSync(u))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);nt.cpSync(u,n,{recursive:!0,force:!0});let s=await S(t.name),p=I(s,a??0),l=await St.launchPersistentContext(n,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:a}),f=await t.indexUrl(),d=l.pages()[0];return d||(d=await l.newPage()),await d.goto(f),{context:l,walletPage:d,contextPath:n}}var h={openMenuButton:"settings-menu-open-button",settingsButton:"sidebar_menu-button-settings",addAccountButton:"sidebar_menu-button-add_account",unlockWalletButton:"unlock-form-submit-button",manageAccountsButton:"sidebar_menu-button-manage_accounts",homeHeaderAccountName:"home-header-account-name"},B={lockWalletButton:"lock-menu-item",closeMenuButton:"settings-menu-close-button",developerSettingsButton:"settings-item-developer-settings",activeNetworksButton:"settings-item-active-networks"},rt={accountProfileContainer:"sortable-account-container"};var m={createNewWalletButton:"button:has-text('Create a new wallet')",IAlreadyHaveAWalletButton:"button:has-text('I already have a wallet')",importRecoveryPhraseButton:"button:has-text('Import Recovery Phrase')",importPrivateKeyButton:"button:has-text('Import Private Key')",createSeedPhraseWalletButton:"create-manual-seed-phrase",passwordInput:"onboarding-form-password-input",passwordConfirmInput:"onboarding-form-confirm-password-input",termsCheckBox:"onboarding-form-terms-of-service-checkbox",continueButton:"button:has-text('Continue')",importWalletButton:"button:has-text('Import Wallet')",getStartedButton:"button:has-text('Get Started')",recoveryPhraseSavedCheckbox:"onboarding-form-saved-secret-recovery-phrase-checkbox",recoveryPhraseInput:"secret-recovery-phrase-word-input"};async function R({page:t,privateKey:e,accountName:o,chain:a}){await t.getByTestId(h.openMenuButton).click(),await t.getByTestId(h.addAccountButton).click(),await t.locator(m.importPrivateKeyButton).click();let s=t.locator("span[id^='button--listbox-input--']"),p=await s.textContent(),l=t.locator("input[name='name']"),f=t.locator("textarea[name='privateKey']");p!==a&&(await s.click(),await t.locator("ul[id^='listbox--listbox-input--']").locator(`li[data-label='${a}']`).click()),await l.fill(o),await f.fill(e),await t.locator("button:has-text('Import')").click()}var v={confirmButton:"primary-button",cancelButton:"secondary-button"};async function it(t){let e=t.getByTestId(v.confirmButton);await t.getByTestId("approve-transaction").waitFor({state:"attached"});let a=t.getByRole("button",{name:"Confirm anyway",exact:!0});if(await a.isVisible().catch(()=>!1)){await a.click();return}await e.click()}import{expect as vt}from"@playwright/test";import It from"zod";async function E(t,e){let o=It.string().min(1,"Account name cannot be an empty string").parse(e);await t.getByTestId(h.openMenuButton).click();let n=null,r=await t.locator("div[data-testid='account-menu'] div[data-testid='tooltip_interactive-wrapper']").all();for(let u of r)if((await u.textContent())?.includes(o)){n=u;break}if(!n)throw new Error(`Account with name "${o}" not found in the account list.`);await n.click()}async function ct(t,e){e&&await E(t,e);let o=t.getByTestId(v.confirmButton);await vt(o).toBeEnabled({timeout:15e3}),await o.click()}import Et from"zod";var Wt=t=>t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function st({page:t,accountName:e,chain:o}){let a=Et.string().min(1,"Account name cannot be an empty string").parse(e);await t.getByTestId(h.openMenuButton).click(),await t.getByTestId(h.manageAccountsButton).click(),await t.getByTestId(`manage-accounts-sortable-${a}`).click();let s=t.getByRole("button",{name:/Account Address(?:es)?/i});await s.waitFor({state:"visible",timeout:2e4});let l=await s.locator("div[data-name='row.pair'] > div").last().textContent();if(l&&Number(l)===1){await s.locator("> div > div").last().click(),await t.getByTestId("header--back").click();let w=t.getByTestId(B.closeMenuButton);await w.waitFor({state:"visible",timeout:15e3}),await w.click()}else{await s.click();let d=new RegExp(`${Wt(o.network)}`,"i");await t.getByRole("button",{name:d}).locator("> div").last().locator("> div").last().locator("div > button").last().click(),await t.getByRole("button",{name:"Close",exact:!0}).last().click();let C=t.getByTestId("header--back");await C.waitFor({state:"visible",timeout:15e3}),await C.click();let b=t.getByTestId(B.closeMenuButton);await b.waitFor({state:"visible",timeout:15e3}),await b.click()}return await t.evaluate(async()=>await navigator.clipboard.readText())}async function lt(t){await t.getByTestId(h.openMenuButton).click(),await t.getByTestId(h.settingsButton).click(),await t.getByTestId(B.lockWalletButton).click()}import{styleText as wt}from"util";import{expect as Lt}from"@playwright/test";function D(t){return new Promise(e=>setTimeout(e,t))}import ut from"fs";import Ft from"path";async function $(t){let e=x(t),o=Ft.resolve(e,"password.txt");try{if(!ut.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return ut.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Nt}from"@playwright/test";async function mt({context:t,path:e,locator:o}){let a;try{await Nt.poll(async()=>(a=t.pages().filter(n=>n.url().startsWith("chrome-extension://")).find(n=>n.url().match(e)),!!a),{timeout:9e4}).toBe(!0)}catch{let n=t.pages().filter(r=>r.url().startsWith("chrome-extension://")).map(r=>r.url());throw new Error([`Popup page with path "${e}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(n)}`].join(` -`))}if(!a)throw new Error(`Popup page with path ${e} not found in context.`);return await _t(a,o),await a.setViewportSize({width:360,height:592}),a}async function _t(t,e){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(e).first().waitFor({state:"attached",timeout:45e3})}import pt from"fs";import Mt from"path";async function dt(t){let e=x(t),o=Mt.resolve(e,"extension-id.txt");try{if(!pt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return pt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var k=class{name="phantom";onboardingPath="/onboarding.html";async indexUrl(){return`chrome-extension://${await this.extensionId()}/popup.html`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/notification.html`}async extensionId(){return await dt(this.name)}async promptPage(e){let o=await this.promptUrl();return await mt({context:e,path:o,locator:"div[id='root']"})}};async function W(t,e){for(;!e.aborted&&!t.isClosed();){try{let a=t.locator("div[id='modal']").locator("div > svg").first();if(await a.isVisible().catch(()=>!1)){await a.click();return}}catch(a){if(console.error("[autoClosePhantomNotification]: ",a),t.isClosed())return}await D(300)}}async function U({page:t,currentAccountName:e,newAccountName:o}){await t.getByTestId(h.openMenuButton).click(),await t.getByTestId(h.manageAccountsButton).click();let u=await t.getByTestId(rt.accountProfileContainer).locator("div[data-testid^='manage-accounts-sortable'] div > p").all(),s=null;for(let g of u)if((await g.textContent())?.toLowerCase()===e.toLowerCase()){s=g;break}if(!s)throw new Error(`Account with name "${e}" not found`);await s.click(),await t.locator("button:has-text('Account Name')").click();let l=t.locator("input[name='name']");await l.clear(),await l.fill(o),await t.getByTestId("primary-button").click(),await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click()}async function H(t){await t.getByTestId(h.openMenuButton).click(),await t.getByTestId(h.settingsButton).click()}async function V({page:t,...e}){await H(t);let o=t.locator(`button[id='${B.developerSettingsButton}']`);await o.scrollIntoViewIfNeeded(),await o.click();let a=t.getByTestId("toggleTestNetwork"),r=await a.locator("label[data-testid='toggleTestNetwork-switch'] > input[aria-label='Toggle']").isChecked().catch(()=>!1);if(!r&&e.mode==="on"&&await a.click(),r&&e.mode==="off"){await a.click(),await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click();return}if(e.mode==="on"&&e.chain==="Solana"){let{network:p}=e;await t.locator(`button:has-text("${p}")`).click()}if(e.mode==="on"&&e.chain==="Ethereum"){let{network:p}=e;if(!await t.getByText("EVM",{exact:!0}).isVisible().catch(()=>!1))throw new Error(["EVM testnet options are not available. Please ensure Ethereum is enabled in optional chains.","To enable Ethereum, call the 'toggleOptionalChain' action before switching the network.","toggleOptionalChain({ page: page, toggleMode: 'on', supportedChains: ['Ethereum'] })","Tip: For persistence, enable Ethereum in your setup file after the onboarding step completes."].join(` -`));await t.locator(`button:has-text("${p}")`).click()}await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click()}async function G({page:t,additionalAccounts:e,...o}){console.info(wt("yellowBright",` - Phantom onboarding started...`,{validateStream:!1}));let a=await $("phantom");if(o.mode==="create"){await t.locator(m.createNewWalletButton).click(),await t.getByTestId(m.createSeedPhraseWalletButton).click();let g=t.getByTestId(m.passwordInput),y=t.getByTestId(m.passwordConfirmInput),A=t.getByTestId(m.termsCheckBox),P=t.locator(m.continueButton);await g.fill(a),await y.fill(a),await A.click(),await P.click(),await P.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.getByTestId(m.recoveryPhraseSavedCheckbox).click(),await P.click(),await D(1e3),await P.click(),await t.locator(m.getStartedButton).last().click()}if(o.mode==="recovery phrase"){let w=o.secretRecoveryPhrase.split(" ");await t.locator(m.IAlreadyHaveAWalletButton).click(),await t.locator(m.importRecoveryPhraseButton).click();for(let[_,Z]of Object.entries(w))await t.getByTestId(`${m.recoveryPhraseInput}-${_}`).fill(Z);await t.locator(m.importWalletButton).click(),await t.locator("p:has-text('Finding accounts with activity')").waitFor({state:"detached",timeout:6e4});let C=t.locator(m.continueButton);await C.click();let b=t.getByTestId(m.passwordInput),O=t.getByTestId(m.passwordConfirmInput),J=t.getByTestId(m.termsCheckBox);await b.fill(a),await O.fill(a),await J.click(),await C.click(),await C.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.getByRole("textbox",{name:"Username @ Clear",exact:!0}).waitFor({state:"attached",timeout:5e3}).then(async()=>{await t.getByRole("button",{name:"Continue",exact:!0}).click()}).catch(()=>{}),await t.locator(m.getStartedButton).last().click()}if(o.mode==="private key"){await t.locator(m.IAlreadyHaveAWalletButton).click();let{privateKey:w,chain:g,accountName:y}=o;await t.locator(m.importPrivateKeyButton).click();let P=t.locator("span[id='button--listbox-input--1']"),C=await P.textContent(),b=t.locator("input[name='name']"),O=t.locator("textarea[name='privateKey']");C!==g&&(await P.click(),await t.locator("ul[id='listbox--listbox-input--1']").locator(`li[data-label='${g}']`).click()),await b.fill(y),await O.fill(w),await t.locator("button:has-text('Import')").click();let q=t.getByTestId(m.passwordInput),Q=t.getByTestId(m.passwordConfirmInput),Y=t.getByTestId(m.termsCheckBox);await q.fill(a),await Q.fill(a),await Y.click();let _=t.locator(m.continueButton);await _.click(),await _.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.locator(m.getStartedButton).last().click()}let n=await t.context().newPage(),r=await new k().indexUrl();await n.goto(r);let s=await t.context().browser()?.newBrowserCDPSession(),p;await Lt.poll(async()=>{if(s){let{targetInfos:d}=await s.send("Target.getTargets"),g=d.filter(y=>y.title==="Phantom Wallet").find(y=>!y.attached&&y.url===r);return p=g,!!g}},{timeout:2e4}).toBe(!0),p&&await s?.send("Target.closeTarget",{targetId:p.targetId});let l=await n.getByTestId("home-header-account-name").textContent();if(!l)throw new Error("Cannot find initial account name");if(o.mode==="create"||o.mode==="recovery phrase"){let{accountName:d}=o;await U({page:n,newAccountName:d,currentAccountName:l})}if(e&&e.length>0){let d=new AbortController;W(n,d.signal).catch(w=>console.error({error:w}));for(let{accountName:w,chain:g,privateKey:y}of e)await R({page:n,privateKey:y,accountName:w,chain:g}),d.abort();await E(n,o.accountName)}o.toggleNetworkMode&&await V({page:n,...o.toggleNetworkMode}),console.info(wt("greenBright","\u2728 Phantom onboarding completed successfully",{validateStream:!1}))}async function ft(t){let e=t.getByTestId(v.cancelButton);await t.getByTestId("approve-transaction").waitFor({state:"attached"}),await e.click()}async function gt({page:t,supportedChains:e,toggleMode:o="off"}){if(await H(t),await t.locator("button[id='settings-item-active-networks']").click(),e.length===0)throw Error("Supported chains array cannot be empty for toggle mode other than 'onboard'");for(let u of e){let s=t.locator(`button[id='toggle-${u.toLowerCase()}']`),l=await s.locator(`label[data-testid='toggle-${u.toLowerCase()}-switch'] > input[aria-label='Toggle']`).isChecked().catch(()=>!1);o==="off"&&l&&await s.click(),o==="on"&&!l&&await s.click()}await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click()}async function K(t){let e=await $("phantom"),o=t.locator("input[name='password']"),a=t.getByTestId("unlock-form-submit-button");await o.fill(e),await a.click(),await a.waitFor({state:"detached"})}var F=class extends k{page;constructor(e){super(),this.page=e}async onboard({...e}){await G({page:this.page,...e})}async unlock(){await K(this.page)}async lock(){await lt(this.page)}async renameAccount({...e}){await U({page:this.page,...e})}async switchAccount(e){await E(this.page,e)}async getAccountAddress({accountName:e,chain:o}){return await st({page:this.page,accountName:e,chain:o})}async addAccount({...e}){await R({page:this.page,...e})}async toggleOptionalChains({toggleMode:e,supportedChains:o}){await gt({page:this.page,supportedChains:o,toggleMode:e})}async switchNetwork({...e}){await V({page:this.page,...e})}async connectToApp(e){await ct(await this.promptPage(this.page.context()),e)}async confirmTransaction(){await it(await this.promptPage(this.page.context()))}async rejectTransaction(){await ft(await this.promptPage(this.page.context()))}};import yt from"fs";import $t from"path";import{test as Ut,chromium as Ht}from"@playwright/test";import{expect as Ot}from"@playwright/test";async function j(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}async function L(t,e){let o=await t.newPage();return await Ot(async()=>{await o.goto(e),await j(o)}).toPass(),o}async function X(t,e){let o=await e.newPage();for(let{origin:a,localStorage:n}of t){let r=o.mainFrame();await r.goto(a),await r.evaluate(u=>{u.forEach(({name:s,value:p})=>{window.localStorage.setItem(s,p)})},n)}await o.close()}import Rt from"fs/promises";async function ht(t){await Rt.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Dt=35e3;async function z(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),Dt))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await ht(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var N,ja=({slowMo:t=0,profileName:e}={})=>Ut.extend({contextPath:async({browserName:o},a,n)=>{let r=await T(`${o}-${n.testId}`);await a(r)},context:async({context:o,contextPath:a},n)=>{let r=new k,u=x(r.name),s=await S(r.name),p=$t.resolve(u,e??"wallet-data");if(!yt.existsSync(p))throw new Error("\u274C Cache for Phantom wallet data not found. Create it first");yt.cpSync(p,a,{recursive:!0,force:!0});let l=I(s,t),f=await Ht.launchPersistentContext(a,{headless:!1,args:l,slowMo:process.env.HEADLESS?0:t});await f.grantPermissions(["clipboard-read"]);let{cookies:d,origins:w}=await o.storageState();d&&await f.addCookies(d),w&&w.length>0&&await X(w,f);let g=await r.indexUrl();N=f.pages().find(A=>A.url().startsWith(g))||await L(f,g);for(let A of f.pages())A.url().includes("about:blank")&&await A.close();await N.bringToFront(),await K(N),await n(f),await z(f,a)},phantomPage:async({context:o},a)=>{await a(N)},phantom:async({context:o},a)=>{let n=new F(N);await a(n)},autoCloseNotification:[async({context:o},a)=>{let n=new AbortController,r=W(N,n.signal);await a(void 0),n.abort(),await r.catch(u=>{console.error(`Auto close notification error: ${u.message}`)})},{auto:!0}]});import{test as zt}from"@playwright/test";import xt from"fs";import Vt from"path";import{chromium as Kt}from"@playwright/test";async function Bt({workerInfo:t,profileName:e,slowMo:o}){let a=new k,n=await T(t.workerIndex.toString()),r=x(a.name),u=Vt.resolve(r,e??"wallet-data");if(!xt.existsSync(u))throw new Error(`Cache for ${a.name} does not exist. Create it first!`);xt.cpSync(u,n,{recursive:!0,force:!0});let s=await S(a.name),p=I(s,o??0),l=await Kt.launchPersistentContext(n,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:o}),f=await a.indexUrl(),d=await L(l,f);return{context:l,walletPage:d,contextPath:n}}var dn=({slowMo:t,profileName:e}={})=>zt.extend({workerScopeContents:[async({browser:o},a,n)=>{let{context:r,contextPath:u,walletPage:s}=await Bt({workerInfo:n,profileName:e,slowMo:t});await r.grantPermissions(["clipboard-read"]);for(let l of r.pages())l.url().includes("about:blank")&&await l.close();let p=new F(s);await p.unlock(),await a({wallet:p,walletPage:s,context:r}),await z(r,u)},{scope:"worker"}],autoCloseNotification:[async({workerScopeContents:o},a)=>{let n=new AbortController,r=W(o.walletPage,n.signal);await a(void 0),n.abort(),await r.catch(u=>{console.error(`Auto close notification error: ${u.message}`)})},{auto:!0}]});export{F as Phantom,ja as phantomFixture,dn as phantomWorkerScopeFixture,Ce as workerScopeContext}; +import nt from"fs";import Tt from"path";import{chromium as St}from"@playwright/test";import At from"path";var tt=".wallet-cache",et=".wallet-context";var ot="13.33.0",N="https://github.com/amaify/chainwright/releases/download/v0.1.0/",kt=`https://github.com/MetaMask/metamask-extension/releases/download/v${ot}/`,jt=`${kt}metamask-chrome-${ot}.zip`,zt=`${N}solflare-wallet-extension-v2.19.1.zip`,Xt=`${N}petra-wallet-extension-v2.4.8.zip`,Jt=`${N}phantom-wallet-extension-v26.10.0.zip`,qt=`${N}meteor-wallet-extension-v0.7.0.zip`,Qt=`${N}keplr-wallet-extension-v0.13.39.zip`;async function b(t){return At.resolve(process.cwd(),et,t)}import Ct from"path";function x(t){return Ct.resolve(process.cwd(),tt,t)}import at from"fs";import bt from"path";async function T(t){try{let e=x(t),o=bt.resolve(e,"extension-path.txt");if(!at.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=at.readFileSync(o,"utf-8").trim();if(!a)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return a}catch(e){throw new Error(`\u274C Failed to get ${t} extension path: ${e.message}`)}}function S(t,e){let o=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(o.push("--headless=new"),e>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),o}async function ke({wallet:t,workerInfo:e,profileName:o,slowMo:a}){let n=await b(e.workerIndex.toString()),r=x(t.name),s=Tt.resolve(r,o??"wallet-data");if(!nt.existsSync(s))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);nt.cpSync(s,n,{recursive:!0,force:!0});let l=await T(t.name),u=S(l,a??0),p=await St.launchPersistentContext(n,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:a}),d=await t.indexUrl(),w=p.pages()[0];return w||(w=await p.newPage()),await w.goto(d),{context:p,walletPage:w,contextPath:n}}var g={openMenuButton:"settings-menu-open-button",settingsButton:"sidebar_menu-button-settings",addAccountButton:"sidebar_menu-button-add_account",unlockWalletButton:"unlock-form-submit-button",manageAccountsButton:"sidebar_menu-button-manage_accounts",homeHeaderAccountName:"home-header-account-name"},B={lockWalletButton:"lock-menu-item",closeMenuButton:"settings-menu-close-button",developerSettingsButton:"settings-item-developer-settings",activeNetworksButton:"settings-item-active-networks"},rt={accountProfileContainer:"sortable-account-container"};var m={createNewWalletButton:"button:has-text('Create a new wallet')",IAlreadyHaveAWalletButton:"button:has-text('I already have a wallet')",importRecoveryPhraseButton:"button:has-text('Import Recovery Phrase')",importPrivateKeyButton:"button:has-text('Import Private Key')",createSeedPhraseWalletButton:"create-manual-seed-phrase",passwordInput:"onboarding-form-password-input",passwordConfirmInput:"onboarding-form-confirm-password-input",termsCheckBox:"onboarding-form-terms-of-service-checkbox",continueButton:"button:has-text('Continue')",importWalletButton:"button:has-text('Import Wallet')",getStartedButton:"button:has-text('Get Started')",recoveryPhraseSavedCheckbox:"onboarding-form-saved-secret-recovery-phrase-checkbox",recoveryPhraseInput:"secret-recovery-phrase-word-input"};async function R({page:t,privateKey:e,accountName:o,chain:a}){await t.getByTestId(g.openMenuButton).click(),await t.getByTestId(g.addAccountButton).click(),await t.locator(m.importPrivateKeyButton).click();let l=t.locator("span[id^='button--listbox-input--']"),u=await l.textContent(),p=t.locator("input[name='name']"),d=t.locator("textarea[name='privateKey']");u!==a&&(await l.click(),await t.locator("ul[id^='listbox--listbox-input--']").locator(`li[data-label='${a}']`).click()),await p.fill(o),await d.fill(e),await t.locator("button:has-text('Import')").click()}var I={confirmButton:"primary-button",cancelButton:"secondary-button"};async function it(t){let e=t.getByTestId(I.confirmButton);await t.getByTestId("approve-transaction").waitFor({state:"attached"});let a=t.getByRole("button",{name:"Confirm anyway",exact:!0});if(await a.isVisible().catch(()=>!1)){await a.click();return}await e.click()}import{expect as It}from"@playwright/test";async function v(t,e){await t.getByTestId(g.openMenuButton).click();let a=null,n=await t.locator("div[data-testid='account-menu'] div[data-testid='tooltip_interactive-wrapper']").all();for(let r of n)if((await r.textContent())?.includes(e)){a=r;break}if(!a)throw new Error(`Account with name "${e}" not found in the account list.`);await a.click()}async function ct(t,e){e&&await v(t,e);let o=t.getByTestId(I.confirmButton);await It(o).toBeEnabled({timeout:15e3}),await o.click()}var vt=t=>t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function st({page:t,accountName:e,chain:o}){await t.getByTestId(g.openMenuButton).click(),await t.getByTestId(g.manageAccountsButton).click(),await t.getByTestId(`manage-accounts-sortable-${e}`).click();let s=t.getByRole("button",{name:/Account Address(?:es)?/i});await s.waitFor({state:"visible",timeout:2e4});let u=await s.locator("div[data-name='row.pair'] > div").last().textContent();if(u&&Number(u)===1){await s.locator("> div > div").last().click(),await t.getByTestId("header--back").click();let w=t.getByTestId(B.closeMenuButton);await w.waitFor({state:"visible",timeout:15e3}),await w.click()}else{await s.click();let d=new RegExp(`${vt(o.network)}`,"i");await t.getByRole("button",{name:d}).locator("> div").last().locator("> div").last().locator("div > button").last().click(),await t.getByRole("button",{name:"Close",exact:!0}).last().click();let P=t.getByTestId("header--back");await P.waitFor({state:"visible",timeout:15e3}),await P.click();let C=t.getByTestId(B.closeMenuButton);await C.waitFor({state:"visible",timeout:15e3}),await C.click()}return await t.evaluate(async()=>await navigator.clipboard.readText())}async function lt(t){await t.getByTestId(g.openMenuButton).click(),await t.getByTestId(g.settingsButton).click(),await t.getByTestId(B.lockWalletButton).click()}import{styleText as dt}from"util";import{expect as Nt}from"@playwright/test";function D(t){return new Promise(e=>setTimeout(e,t))}import ut from"fs";import Et from"path";async function $(t){let e=x(t),o=Et.resolve(e,"password.txt");try{if(!ut.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return ut.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Wt}from"@playwright/test";async function mt({context:t,path:e,locator:o}){let a;try{await Wt.poll(async()=>(a=t.pages().filter(n=>n.url().startsWith("chrome-extension://")).find(n=>n.url().match(e)),!!a),{timeout:9e4}).toBe(!0)}catch{let n=t.pages().filter(r=>r.url().startsWith("chrome-extension://")).map(r=>r.url());throw new Error([`Popup page with path "${e}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(n)}`].join(` +`))}if(!a)throw new Error(`Popup page with path ${e} not found in context.`);return await Ft(a,o),await a.setViewportSize({width:360,height:592}),a}async function Ft(t,e){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(e).first().waitFor({state:"attached",timeout:45e3})}import pt from"fs";import _t from"path";async function wt(t){let e=x(t),o=_t.resolve(e,"extension-id.txt");try{if(!pt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return pt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var k=class{name="phantom";onboardingPath="/onboarding.html";async indexUrl(){return`chrome-extension://${await this.extensionId()}/popup.html`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/notification.html`}async extensionId(){return await wt(this.name)}async promptPage(e){let o=await this.promptUrl();return await mt({context:e,path:o,locator:"div[id='root']"})}};async function E(t,e){for(;!e.aborted&&!t.isClosed();){try{let a=t.locator("div[id='modal']").locator("div > svg").first();if(await a.isVisible().catch(()=>!1)){await a.click();return}}catch(a){if(console.error("[autoClosePhantomNotification]: ",a),t.isClosed())return}await D(300)}}async function U({page:t,currentAccountName:e,newAccountName:o}){await t.getByTestId(g.openMenuButton).click(),await t.getByTestId(g.manageAccountsButton).click();let s=await t.getByTestId(rt.accountProfileContainer).locator("div[data-testid^='manage-accounts-sortable'] div > p").all(),l=null;for(let f of s)if((await f.textContent())?.toLowerCase()===e.toLowerCase()){l=f;break}if(!l)throw new Error(`Account with name "${e}" not found`);await l.click(),await t.locator("button:has-text('Account Name')").click();let p=t.locator("input[name='name']");await p.clear(),await p.fill(o),await t.getByTestId("primary-button").click(),await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click()}async function H(t){await t.getByTestId(g.openMenuButton).click(),await t.getByTestId(g.settingsButton).click()}async function V({page:t,...e}){await H(t);let o=t.locator(`button[id='${B.developerSettingsButton}']`);await o.scrollIntoViewIfNeeded(),await o.click();let a=t.getByTestId("toggleTestNetwork"),r=await a.locator("label[data-testid='toggleTestNetwork-switch'] > input[aria-label='Toggle']").isChecked().catch(()=>!1);if(!r&&e.mode==="on"&&await a.click(),r&&e.mode==="off"){await a.click(),await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click();return}if(e.mode==="on"&&e.chain==="Solana"){let{network:u}=e;await t.locator(`button:has-text("${u}")`).click()}if(e.mode==="on"&&e.chain==="Ethereum"){let{network:u}=e;if(!await t.getByText("EVM",{exact:!0}).isVisible().catch(()=>!1))throw new Error(["EVM testnet options are not available. Please ensure Ethereum is enabled in optional chains.","To enable Ethereum, call the 'toggleOptionalChain' action before switching the network.","toggleOptionalChain({ page: page, toggleMode: 'on', supportedChains: ['Ethereum'] })","Tip: For persistence, enable Ethereum in your setup file after the onboarding step completes."].join(` +`));await t.locator(`button:has-text("${u}")`).click()}await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click()}async function j({page:t,additionalAccounts:e,...o}){console.info(dt("yellowBright",` + Phantom onboarding started...`,{validateStream:!1}));let a=await $("phantom");if(o.mode==="create"){await t.locator(m.createNewWalletButton).click(),await t.getByTestId(m.createSeedPhraseWalletButton).click();let f=t.getByTestId(m.passwordInput),y=t.getByTestId(m.passwordConfirmInput),A=t.getByTestId(m.termsCheckBox),P=t.locator(m.continueButton);await f.fill(a),await y.fill(a),await A.click(),await P.click(),await P.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.getByTestId(m.recoveryPhraseSavedCheckbox).click(),await P.click(),await D(1e3),await P.click(),await t.locator(m.getStartedButton).last().click()}if(o.mode==="recovery phrase"){let h=o.secretRecoveryPhrase.split(" ");await t.locator(m.IAlreadyHaveAWalletButton).click(),await t.locator(m.importRecoveryPhraseButton).click();for(let[_,Z]of Object.entries(h))await t.getByTestId(`${m.recoveryPhraseInput}-${_}`).fill(Z);await t.locator(m.importWalletButton).click(),await t.locator("p:has-text('Finding accounts with activity')").waitFor({state:"detached",timeout:6e4});let C=t.locator(m.continueButton);await C.click();let L=t.getByTestId(m.passwordInput),O=t.getByTestId(m.passwordConfirmInput),J=t.getByTestId(m.termsCheckBox);await L.fill(a),await O.fill(a),await J.click(),await C.click(),await C.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.getByRole("textbox",{name:"Username @ Clear",exact:!0}).waitFor({state:"attached",timeout:5e3}).then(async()=>{await t.getByRole("button",{name:"Continue",exact:!0}).click()}).catch(()=>{}),await t.locator(m.getStartedButton).last().click()}if(o.mode==="private key"){await t.locator(m.IAlreadyHaveAWalletButton).click();let{privateKey:h,chain:f,accountName:y}=o;await t.locator(m.importPrivateKeyButton).click();let P=t.locator("span[id='button--listbox-input--1']"),C=await P.textContent(),L=t.locator("input[name='name']"),O=t.locator("textarea[name='privateKey']");C!==f&&(await P.click(),await t.locator("ul[id='listbox--listbox-input--1']").locator(`li[data-label='${f}']`).click()),await L.fill(y),await O.fill(h),await t.locator("button:has-text('Import')").click();let q=t.getByTestId(m.passwordInput),Q=t.getByTestId(m.passwordConfirmInput),Y=t.getByTestId(m.termsCheckBox);await q.fill(a),await Q.fill(a),await Y.click();let _=t.locator(m.continueButton);await _.click(),await _.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.locator(m.getStartedButton).last().click()}let n=await t.context().newPage(),r=await new k().indexUrl();await n.goto(r);let l=await t.context().browser()?.newBrowserCDPSession(),u;await Nt.poll(async()=>{if(l){let{targetInfos:w}=await l.send("Target.getTargets"),f=w.filter(y=>y.title==="Phantom Wallet").find(y=>!y.attached&&y.url===r);return u=f,!!f}},{timeout:2e4}).toBe(!0),u&&await l?.send("Target.closeTarget",{targetId:u.targetId});let p=await n.getByTestId("home-header-account-name").textContent();if(!p)throw new Error("Cannot find initial account name");if(o.mode==="create"||o.mode==="recovery phrase"){let{accountName:w}=o;await U({page:n,newAccountName:w,currentAccountName:p})}if(e&&e.length>0){let w=new AbortController;E(n,w.signal).catch(h=>console.error({error:h}));for(let{accountName:h,chain:f,privateKey:y}of e)await R({page:n,privateKey:y,accountName:h,chain:f}),w.abort();await v(n,o.accountName)}o.toggleNetworkMode&&await V({page:n,...o.toggleNetworkMode}),console.info(dt("greenBright","\u2728 Phantom onboarding completed successfully",{validateStream:!1}))}async function ft(t){let e=t.getByTestId(I.cancelButton);await t.getByTestId("approve-transaction").waitFor({state:"attached"}),await e.click()}async function gt({page:t,supportedChains:e,toggleMode:o="off"}){if(await H(t),await t.locator("button[id='settings-item-active-networks']").click(),e.length===0)throw Error("Supported chains array cannot be empty for toggle mode other than 'onboard'");for(let s of e){let l=t.locator(`button[id='toggle-${s.toLowerCase()}']`),p=await l.locator(`label[data-testid='toggle-${s.toLowerCase()}-switch'] > input[aria-label='Toggle']`).isChecked().catch(()=>!1);o==="off"&&p&&await l.click(),o==="on"&&!p&&await l.click()}await t.getByTestId("header--back").click(),await t.getByTestId(B.closeMenuButton).click()}async function K(t){let e=await $("phantom"),o=t.locator("input[name='password']"),a=t.getByTestId("unlock-form-submit-button");await o.fill(e),await a.click(),await a.waitFor({state:"detached"})}var W=class extends k{page;constructor(e){super(),this.page=e}async onboard({...e}){await j({page:this.page,...e})}async unlock(){await K(this.page)}async lock(){await lt(this.page)}async renameAccount({...e}){await U({page:this.page,...e})}async switchAccount(e){await v(this.page,e)}async getAccountAddress({accountName:e,chain:o}){return await st({page:this.page,accountName:e,chain:o})}async addAccount({...e}){await R({page:this.page,...e})}async toggleOptionalChains({toggleMode:e,supportedChains:o}){await gt({page:this.page,supportedChains:o,toggleMode:e})}async switchNetwork({...e}){await V({page:this.page,...e})}async connectToApp(e){await ct(await this.promptPage(this.page.context()),e)}async confirmTransaction(){await it(await this.promptPage(this.page.context()))}async rejectTransaction(){await ft(await this.promptPage(this.page.context()))}};import yt from"fs";import Rt from"path";import{test as Dt,chromium as $t}from"@playwright/test";import{expect as Mt}from"@playwright/test";async function z(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}async function M(t,e){let o=await t.newPage();return await Mt(async()=>{await o.goto(e),await z(o)}).toPass(),o}async function X(t,e){let o=await e.newPage();for(let{origin:a,localStorage:n}of t){let r=o.mainFrame();await r.goto(a),await r.evaluate(s=>{s.forEach(({name:l,value:u})=>{window.localStorage.setItem(l,u)})},n)}await o.close()}import Lt from"fs/promises";async function ht(t){await Lt.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Ot=35e3;async function G(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),Ot))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await ht(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var F,Va=({slowMo:t=0,profileName:e}={})=>Dt.extend({contextPath:async({browserName:o},a,n)=>{let r=await b(`${o}-${n.testId}`);await a(r)},context:async({context:o,contextPath:a},n)=>{let r=new k,s=x(r.name),l=await T(r.name),u=Rt.resolve(s,e??"wallet-data");if(!yt.existsSync(u))throw new Error("\u274C Cache for Phantom wallet data not found. Create it first");yt.cpSync(u,a,{recursive:!0,force:!0});let p=S(l,t),d=await $t.launchPersistentContext(a,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:t});await d.grantPermissions(["clipboard-read"]);let{cookies:w,origins:h}=await o.storageState();w&&await d.addCookies(w),h&&h.length>0&&await X(h,d);let f=await r.indexUrl();F=d.pages().find(A=>A.url().startsWith(f))||await M(d,f);for(let A of d.pages())A.url().includes("about:blank")&&await A.close();await F.bringToFront(),await K(F),await n(d),await G(d,a)},phantomPage:async({context:o},a)=>{await a(F)},phantom:async({context:o},a)=>{let n=new W(F);await a(n)},autoCloseNotification:[async({context:o},a)=>{let n=new AbortController,r=E(F,n.signal);await a(void 0),n.abort(),await r.catch(s=>{console.error(`Auto close notification error: ${s.message}`)})},{auto:!0}]});import{test as Vt}from"@playwright/test";import xt from"fs";import Ut from"path";import{chromium as Ht}from"@playwright/test";async function Bt({workerInfo:t,profileName:e,slowMo:o}){let a=new k,n=await b(t.workerIndex.toString()),r=x(a.name),s=Ut.resolve(r,e??"wallet-data");if(!xt.existsSync(s))throw new Error(`Cache for ${a.name} does not exist. Create it first!`);xt.cpSync(s,n,{recursive:!0,force:!0});let l=await T(a.name),u=S(l,o??0),p=await Ht.launchPersistentContext(n,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:o}),d=await a.indexUrl(),w=await M(p,d);return{context:p,walletPage:w,contextPath:n}}var ln=({slowMo:t,profileName:e}={})=>Vt.extend({workerScopeContents:[async({browser:o},a,n)=>{let{context:r,contextPath:s,walletPage:l}=await Bt({workerInfo:n,profileName:e,slowMo:t});await r.grantPermissions(["clipboard-read"]);for(let p of r.pages())p.url().includes("about:blank")&&await p.close();let u=new W(l);await u.unlock(),await a({wallet:u,walletPage:l,context:r}),await G(r,s)},{scope:"worker"}],autoCloseNotification:[async({workerScopeContents:o},a)=>{let n=new AbortController,r=E(o.walletPage,n.signal);await a(void 0),n.abort(),await r.catch(s=>{console.error(`Auto close notification error: ${s.message}`)})},{auto:!0}]});export{W as Phantom,Va as phantomFixture,ln as phantomWorkerScopeFixture,ke as workerScopeContext}; diff --git a/dist/wallets/solflare/index.d.ts b/dist/wallets/solflare/index.d.ts index c582373..c912a36 100644 --- a/dist/wallets/solflare/index.d.ts +++ b/dist/wallets/solflare/index.d.ts @@ -3,7 +3,6 @@ import { W as WorkerScopeFixture } from '../../worker-scope-context-DSkOcWf-.js' export { w as workerScopeContext } from '../../worker-scope-context-DSkOcWf-.js'; import * as _playwright_test from '@playwright/test'; import { BrowserContext, Page } from '@playwright/test'; -import z from 'zod'; declare class SolflareProfile { readonly name: "solflare"; @@ -21,11 +20,10 @@ type OnboardingArgs = { additionalAccounts?: Array; }; type SwitchNetwork = Omit, "recoveryPhrase">["network"]; -declare const addAccountSchema: z.ZodObject<{ - walletName: z.ZodString; - privateKey: z.ZodString; -}, z.core.$strip>; -type AddAccountArgs = z.infer; +type AddAccountArgs = { + walletName: string; + privateKey: string; +}; type RenameAccountArgs = { currentAccountName: string; newAccountName: string; diff --git a/dist/wallets/solflare/index.js b/dist/wallets/solflare/index.js index 36a0db7..6856758 100644 --- a/dist/wallets/solflare/index.js +++ b/dist/wallets/solflare/index.js @@ -1,7 +1,7 @@ -import G from"fs";import yt from"path";import{chromium as xt}from"@playwright/test";import ft from"path";var j=".wallet-cache",z=".wallet-context";var q="13.33.0",T="https://github.com/amaify/chainwright/releases/download/v0.1.0/",wt=`https://github.com/MetaMask/metamask-extension/releases/download/v${q}/`,Mt=`${wt}metamask-chrome-${q}.zip`,_t=`${T}solflare-wallet-extension-v2.19.1.zip`,Dt=`${T}petra-wallet-extension-v2.4.8.zip`,Lt=`${T}phantom-wallet-extension-v26.10.0.zip`,Ot=`${T}meteor-wallet-extension-v0.7.0.zip`,Rt=`${T}keplr-wallet-extension-v0.13.39.zip`;async function P(t){return ft.resolve(process.cwd(),z,t)}import dt from"path";function w(t){return dt.resolve(process.cwd(),j,t)}import X from"fs";import gt from"path";async function S(t){try{let e=w(t),r=gt.resolve(e,"extension-path.txt");if(!X.existsSync(r))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let o=X.readFileSync(r,"utf-8").trim();if(!o)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return o}catch(e){throw new Error(`\u274C Failed to get ${t} extension path: ${e.message}`)}}function B(t,e){let r=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(r.push("--headless=new"),e>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),r}async function pe({wallet:t,workerInfo:e,profileName:r,slowMo:o}){let a=await P(e.workerIndex.toString()),s=w(t.name),c=yt.resolve(s,r??"wallet-data");if(!G.existsSync(c))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);G.cpSync(c,a,{recursive:!0,force:!0});let p=await S(t.name),l=B(p,o??0),u=await xt.launchPersistentContext(a,{headless:!1,args:l,slowMo:process.env.HEADLESS?0:o}),m=await t.indexUrl(),f=u.pages()[0];return f||(f=await u.newPage()),await f.goto(m),{context:u,walletPage:f,contextPath:a}}var A={networkSettings:"li-settings-network",selectNetwork:"select-network",confirmModal:"modal-confirm",confifmButton:"btn-confirm",securityAndPrivacyButton:"nav-item-security-and-privacy",lockButton:"btn-lock"},k={portfolioButton:"nav-item-portfolio",walletSelectorButton:"icon-section-wallet-picker-arrow-right"};import V from"zod";var J=V.object({walletName:V.string().min(1,"Wallet name cannot be an empty string"),privateKey:V.string().min(1,"Private key cannot be an empty string")});async function W({page:t,privateKey:e,walletName:r}){let o=J.parse({privateKey:e,walletName:r});await t.getByTestId(k.walletSelectorButton).click(),await t.getByTestId("icon-btn-add").click(),await t.getByTestId("li-add-wallet-privateKey-add").click();let p=t.getByTestId("input-name"),l=t.getByTestId("input-private-key");await p.fill(o.walletName),await l.fill(`${o.privateKey}`),await t.getByTestId("btn-import").click(),await t.locator("span:has-text('My wallets')").waitFor({state:"attached"}),await t.getByRole("dialog").getByTestId("icon-btn-close").click()}import{expect as ht}from"@playwright/test";var F={approveButton:"btn-approve",rejectButton:"btn-reject"};async function Q(t){let e=t.getByTestId(F.approveButton);if(await t.getByTestId("section-network-fee").waitFor({state:"attached",timeout:45e3}).catch(()=>!1),await t.getByTestId("info-box-network-mismatch").isVisible().catch(()=>!1)){console.error(` +import X from"fs";import dt from"path";import{chromium as gt}from"@playwright/test";import ut from"path";var j=".wallet-cache",K=".wallet-context";var z="13.33.0",T="https://github.com/amaify/chainwright/releases/download/v0.1.0/",mt=`https://github.com/MetaMask/metamask-extension/releases/download/v${z}/`,Ft=`${mt}metamask-chrome-${z}.zip`,_t=`${T}solflare-wallet-extension-v2.19.1.zip`,Mt=`${T}petra-wallet-extension-v2.4.8.zip`,Dt=`${T}phantom-wallet-extension-v26.10.0.zip`,Nt=`${T}meteor-wallet-extension-v0.7.0.zip`,Lt=`${T}keplr-wallet-extension-v0.13.39.zip`;async function P(t){return ut.resolve(process.cwd(),K,t)}import wt from"path";function w(t){return wt.resolve(process.cwd(),j,t)}import q from"fs";import ft from"path";async function S(t){try{let e=w(t),o=ft.resolve(e,"extension-path.txt");if(!q.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let r=q.readFileSync(o,"utf-8").trim();if(!r)throw new Error("\u274C extension-path.txt is empty. Run setup script first.");return r}catch(e){throw new Error(`\u274C Failed to get ${t} extension path: ${e.message}`)}}function A(t,e){let o=[`--disable-extensions-except=${t}`,`--load-extension=${t}`];return process.env.HEADLESS&&(o.push("--headless=new"),e>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!")),o}async function ce({wallet:t,workerInfo:e,profileName:o,slowMo:r}){let a=await P(e.workerIndex.toString()),s=w(t.name),c=dt.resolve(s,o??"wallet-data");if(!X.existsSync(c))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);X.cpSync(c,a,{recursive:!0,force:!0});let l=await S(t.name),p=A(l,r??0),u=await gt.launchPersistentContext(a,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:r}),m=await t.indexUrl(),f=u.pages()[0];return f||(f=await u.newPage()),await f.goto(m),{context:u,walletPage:f,contextPath:a}}var B={networkSettings:"li-settings-network",selectNetwork:"select-network",confirmModal:"modal-confirm",confifmButton:"btn-confirm",securityAndPrivacyButton:"nav-item-security-and-privacy",lockButton:"btn-lock"},k={portfolioButton:"nav-item-portfolio",walletSelectorButton:"icon-section-wallet-picker-arrow-right"};async function W({page:t,privateKey:e,walletName:o}){await t.getByTestId(k.walletSelectorButton).click(),await t.getByTestId("icon-btn-add").click(),await t.getByTestId("li-add-wallet-privateKey-add").click();let c=t.getByTestId("input-name"),l=t.getByTestId("input-private-key");await c.fill(o),await l.fill(`${e}`),await t.getByTestId("btn-import").click(),await t.locator("span:has-text('My wallets')").waitFor({state:"attached"}),await t.getByRole("dialog").getByTestId("icon-btn-close").click()}import{expect as yt}from"@playwright/test";var F={approveButton:"btn-approve",rejectButton:"btn-reject"};async function G(t){let e=t.getByTestId(F.approveButton);if(await t.getByTestId("section-network-fee").waitFor({state:"attached",timeout:45e3}).catch(()=>!1),await t.getByTestId("info-box-network-mismatch").isVisible().catch(()=>!1)){console.error(` - A 'Network mismatch' error was detected in the transaction confirmation popup. Closing the popup and aborting the transaction confirmation process.`),await t.getByRole("button",{name:"Close",exact:!0}).click();return}let s=t.locator("div[data-id='control-label']");await s.isVisible().catch(()=>!1)&&await s.click(),await ht(e).toBeEnabled(),await e.click()}async function N(t,e){await t.getByTestId("icon-section-wallet-picker-arrow-right").click();let o=t.getByTestId("list-item-m-title").filter({hasText:e}).locator("xpath=../..");if(!await o.isVisible().catch(()=>!1))throw new Error(`Account "${e}" not found. Make sure the account is onboarded or verify the account name.`);await o.click()}async function Y(t,e){e&&await N(t,e),await t.getByRole("button",{name:"Connect",exact:!0}).click()}async function Z(t){return await t.getByTestId("icon-section-wallet-picker-copy").click(),await t.evaluate(async()=>await navigator.clipboard.readText())}async function M(t){let e=t.getByRole("button",{name:"settings",exact:!0});await e.waitFor({state:"attached",timeout:3e4}),await e.click()}async function tt(t){await M(t),await t.getByTestId(A.securityAndPrivacyButton).click(),await t.getByTestId("li-settings-lock").getByTestId(A.lockButton).click()}import{styleText as rt}from"util";import et from"fs";import Pt from"path";async function _(t){let e=w(t),r=Pt.resolve(e,"password.txt");try{if(!et.existsSync(r))throw new Error("\u274C password.txt not found. Run setup script first.");return et.readFileSync(r,"utf-8")}catch(o){throw new Error(`\u274C Failed to get ${t} password from cache: ${o.message}`)}}var x={alreadyHaveAWalletButton:"btn-already-have-wallet",recoveryPhraseInput:"input-recovery-phrase",continueButton:"btn-continue",passwordInput:"input-new-password",repeatPasswordInput:"input-repeat-password",quickSetupButton:"btn-quick-setup",IAgreeButton:"btn-explore"};function ot(t){return new Promise(e=>setTimeout(e,t))}async function b(t,e){for(;!e.aborted&&!t.isClosed();){try{let o=t.locator("div[role='dialog']").locator("button[data-testid='icon-btn-whats-new-modal-close']");if(await o.isVisible().catch(()=>!1)){await o.click();return}}catch(o){console.error("[autoCloseSolflareNotification]: ",o)}await ot(150)}}async function D({page:t,currentAccountName:e,newAccountName:r}){if(e===r){console.warn(` + A 'Network mismatch' error was detected in the transaction confirmation popup. Closing the popup and aborting the transaction confirmation process.`),await t.getByRole("button",{name:"Close",exact:!0}).click();return}let s=t.locator("div[data-id='control-label']");await s.isVisible().catch(()=>!1)&&await s.click(),await yt(e).toBeEnabled(),await e.click()}async function _(t,e){await t.getByTestId("icon-section-wallet-picker-arrow-right").click();let r=t.getByTestId("list-item-m-title").filter({hasText:e}).locator("xpath=../..");if(!await r.isVisible().catch(()=>!1))throw new Error(`Account "${e}" not found. Make sure the account is onboarded or verify the account name.`);await r.click()}async function J(t,e){e&&await _(t,e),await t.getByRole("button",{name:"Connect",exact:!0}).click()}async function Q(t){return await t.getByTestId("icon-section-wallet-picker-copy").click(),await t.evaluate(async()=>await navigator.clipboard.readText())}async function M(t){let e=t.getByRole("button",{name:"settings",exact:!0});await e.waitFor({state:"attached",timeout:3e4}),await e.click()}async function Y(t){await M(t),await t.getByTestId(B.securityAndPrivacyButton).click(),await t.getByTestId("li-settings-lock").getByTestId(B.lockButton).click()}import{styleText as et}from"util";import Z from"fs";import xt from"path";async function D(t){let e=w(t),o=xt.resolve(e,"password.txt");try{if(!Z.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return Z.readFileSync(o,"utf-8")}catch(r){throw new Error(`\u274C Failed to get ${t} password from cache: ${r.message}`)}}var x={alreadyHaveAWalletButton:"btn-already-have-wallet",recoveryPhraseInput:"input-recovery-phrase",continueButton:"btn-continue",passwordInput:"input-new-password",repeatPasswordInput:"input-repeat-password",quickSetupButton:"btn-quick-setup",IAgreeButton:"btn-explore"};function tt(t){return new Promise(e=>setTimeout(e,t))}async function C(t,e){for(;!e.aborted&&!t.isClosed();){try{let r=t.locator("div[role='dialog']").locator("button[data-testid='icon-btn-whats-new-modal-close']");if(await r.isVisible().catch(()=>!1)){await r.click();return}}catch(r){console.error("[autoCloseSolflareNotification]: ",r)}await tt(150)}}async function N({page:t,currentAccountName:e,newAccountName:o}){if(e===o){console.warn(` - Current account name and new account name are the same: "${e}". Skipping rename.`);return}await t.getByTestId(k.walletSelectorButton).click();let a=t.locator(`button[data-testid^='li-wallets']:has-text('${e}')`);if(!await a.isVisible().catch(()=>!1))throw new Error(`Account "${e}" not found. Make sure the account is available.`);await a.hover({timeout:2e4}),await a.getByTestId("icon-btn-three-dots").click({timeout:2e4});let p=t.getByTestId("li-manage-wallet-rename-wallet");await p.click();let l=t.getByTestId("input-name");await l.clear(),await l.fill(r),await t.getByTestId("btn-save").click(),await p.waitFor({state:"attached",timeout:15e3}),await t.getByTestId("icon-btn-close").click()}async function L(t,e){await M(t);let o=t.getByTestId("li-settings-network").getByRole("combobox");await o.locator(" > p").textContent()!==e?(await o.click(),await t.getByTestId(A.selectNetwork).getByRole("option",{name:e,exact:!0}).click(),(e==="Devnet"||e==="Testnet")&&await t.getByTestId(A.confirmModal).getByTestId(A.confifmButton).click()):console.info(`Network is already set to ${e}`),await t.getByTestId(k.portfolioButton).click()}async function at({page:t,recoveryPhrase:e,network:r,walletName:o,additionalAccounts:a}){console.info(rt("yellowBright",` - Solflare onboarding started...`,{validateStream:!1}));let s=await _("solflare");await t.getByTestId(x.alreadyHaveAWalletButton).click();let p=e.split(" ");for(let[d,E]of Object.entries(p))await t.getByTestId(`${x.recoveryPhraseInput}-${Number(d)+1}`).fill(E);let l=t.getByTestId(x.continueButton);await l.click();let u=t.getByTestId(x.passwordInput),m=t.getByTestId(x.repeatPasswordInput);await u.fill(s),await m.fill(s),await l.click(),await t.locator("div",{hasText:"Detecting your existing accounts. This process can take up to a minute."}).waitFor({state:"detached"}),await t.getByTestId(x.quickSetupButton).click(),await t.getByTestId(x.IAgreeButton).click();let U=new AbortController;if(b(t,U.signal).catch(d=>console.error({error:d})),o&&await D({page:t,currentAccountName:"Main Wallet",newAccountName:o}),r&&await L(t,r),a&&a.length>0)for(let{privateKey:d,walletName:E}of a)await W({page:t,privateKey:d,walletName:E});U.abort(),console.info(rt("greenBright","\u2728 Solflare onboarding completed successfully",{validateStream:!1}))}async function nt(t){await t.getByTestId(F.rejectButton).click()}async function O(t){let e=await _("solflare");await t.getByTestId("input-password").fill(e),await t.getByTestId("btn-unlock").click(),await t.getByTestId("nav-main").waitFor({state:"attached",timeout:3e4})}import{expect as At}from"@playwright/test";async function it({context:t,path:e,locator:r}){let o;try{await At.poll(async()=>(o=t.pages().filter(a=>a.url().startsWith("chrome-extension://")).find(a=>a.url().match(e)),!!o),{timeout:9e4}).toBe(!0)}catch{let a=t.pages().filter(s=>s.url().startsWith("chrome-extension://")).map(s=>s.url());throw new Error([`Popup page with path "${e}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(a)}`].join(` -`))}if(!o)throw new Error(`Popup page with path ${e} not found in context.`);return await St(o,r),await o.setViewportSize({width:360,height:592}),o}async function St(t,e){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(e).first().waitFor({state:"attached",timeout:45e3})}import st from"fs";import Bt from"path";async function ct(t){let e=w(t),r=Bt.resolve(e,"extension-id.txt");try{if(!st.existsSync(r))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return st.readFileSync(r,"utf-8")}catch(o){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${o.message}`)}}var h=class{name="solflare";onboardingPath="wallet.html#/onboard";async indexUrl(){return`chrome-extension://${await this.extensionId()}/wallet.html#/portfolio`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/confirm_popup.html`}async extensionId(){return await ct(this.name)}async promptPage(e){let r=await this.promptUrl();return await it({context:e,path:r,locator:"div[data-testid='page-dapp-connect'], div[data-testid='page-tx-sign']"})}};var C=class extends h{page;constructor(e){super(),this.page=e}async onboard({recoveryPhrase:e,network:r,additionalAccounts:o,walletName:a}){await at({page:this.page,recoveryPhrase:e,network:r,additionalAccounts:o,walletName:a})}async unlock(){await O(this.page)}async lock(){await tt(this.page)}async renameAccount({currentAccountName:e,newAccountName:r}){await D({page:this.page,currentAccountName:e,newAccountName:r})}async switchNetwork(e){await L(this.page,e)}async switchAccount(e){await N(this.page,e)}async getAccountAddress(){return await Z(this.page)}async addAccount({privateKey:e,walletName:r}){await W({page:this.page,privateKey:e,walletName:r})}async connectToApp(e){await Y(await this.promptPage(this.page.context()),e)}async confirmTransaction(){await Q(await this.promptPage(this.page.context()))}async rejectTransaction(){await nt(await this.promptPage(this.page.context()))}};import pt from"fs";import Tt from"path";import{test as It,chromium as vt}from"@playwright/test";import{expect as kt}from"@playwright/test";async function I(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}async function H(t,e){let r=await t.newPage();return await kt(async()=>{await r.goto(e),await I(r)}).toPass(),r}async function K(t,e){let r=await e.newPage();for(let{origin:o,localStorage:a}of t){let s=r.mainFrame();await s.goto(o),await s.evaluate(c=>{c.forEach(({name:p,value:l})=>{window.localStorage.setItem(p,l)})},a)}await r.close()}import bt from"fs/promises";async function lt(t){await bt.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Ct=35e3;async function R(t,e){try{await Promise.race([t.close(),new Promise((r,o)=>setTimeout(()=>o(new Error("Context close timed out")),Ct))])}catch(r){console.warn(`Browser context close did not complete cleanly: ${r.message}`)}try{await lt(e)}catch(r){console.error(`Failed to remove temporary context directory at ${e}. Error:`,r)}}var v,Sr=({slowMo:t=0,profileName:e}={})=>It.extend({contextPath:async({browserName:r},o,a)=>{let s=await P(`${r}-${a.testId}`);await o(s)},context:async({context:r,contextPath:o},a)=>{let s=new h,c=w(s.name),p=await S(s.name),l=Tt.resolve(c,e??"wallet-data");if(!pt.existsSync(l))throw new Error("\u274C Cache for Solflare wallet data not found. Create it first");await pt.promises.cp(l,o,{recursive:!0,force:!0});let u=B(p,t),m=await vt.launchPersistentContext(o,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:t});await m.grantPermissions(["clipboard-read"]);let{cookies:f,origins:g}=await r.storageState();f&&await m.addCookies(f),g&&g.length>0&&await K(g,m);let y=await s.indexUrl(),$=y.split("#")[0]??"";await m.waitForEvent("page",{predicate:d=>d.url().includes($),timeout:15e3}),v=m.pages().find(d=>d.url().startsWith($))||await H(m,y);for(let d of m.pages())d.url().includes("about:blank")&&await d.close();await O(v),await a(m),await R(m,o)},solflarePage:async({context:r},o)=>{await o(v)},solflare:async({context:r},o)=>{let a=new C(v);await o(a)},autoCloseNotification:[async({context:r},o)=>{let a=new AbortController,s=b(v,a.signal);await o(void 0),a.abort(),await s.catch(c=>{console.error(`Auto close notification error: ${c.message}`)})},{auto:!0}]});import{test as Ft}from"@playwright/test";import mt from"fs";import Et from"path";import{chromium as Wt}from"@playwright/test";async function ut({workerInfo:t,profileName:e,slowMo:r}){let o=new h,a=await P(t.workerIndex.toString()),s=w(o.name),c=Et.resolve(s,e??"wallet-data");if(!mt.existsSync(c))throw new Error(`Cache for ${o.name} does not exist. Create it first!`);mt.cpSync(c,a,{recursive:!0,force:!0});let p=await S(o.name),l=B(p,r??0),u=await Wt.launchPersistentContext(a,{headless:!1,args:l,slowMo:process.env.HEADLESS?0:r}),m=await o.indexUrl(),f=m.split("#")[0]??"";await u.waitForEvent("page",{predicate:y=>y.url().includes(f),timeout:15e3});let g=u.pages().find(y=>y.url().startsWith(f));g||(g=await u.newPage(),await g.goto(m),await I(g));for(let y of u.pages())y.url().includes("about:blank")&&await y.close();return{context:u,walletPage:g,contextPath:a}}var Ur=({slowMo:t,profileName:e}={})=>Ft.extend({workerScopeContents:[async({browser:r},o,a)=>{let{context:s,contextPath:c,walletPage:p}=await ut({workerInfo:a,profileName:e,slowMo:t});await s.grantPermissions(["clipboard-read"]);let l=new C(p);await l.unlock(),await o({wallet:l,walletPage:p,context:s}),await R(s,c)},{scope:"worker"}],autoCloseNotification:[async({workerScopeContents:r},o)=>{let a=new AbortController,s=b(r.walletPage,a.signal);await o(void 0),a.abort(),await s.catch(c=>{console.error(`Auto close notification error: ${c.message}`)})},{auto:!0}]});export{C as Solflare,Sr as solflareFixture,Ur as solflareWorkerScopeFixture,pe as workerScopeContext}; + Current account name and new account name are the same: "${e}". Skipping rename.`);return}await t.getByTestId(k.walletSelectorButton).click();let a=t.locator(`button[data-testid^='li-wallets']:has-text('${e}')`);if(!await a.isVisible().catch(()=>!1))throw new Error(`Account "${e}" not found. Make sure the account is available.`);await a.hover({timeout:2e4}),await a.getByTestId("icon-btn-three-dots").click({timeout:2e4});let l=t.getByTestId("li-manage-wallet-rename-wallet");await l.click();let p=t.getByTestId("input-name");await p.clear(),await p.fill(o),await t.getByTestId("btn-save").click(),await l.waitFor({state:"attached",timeout:15e3}),await t.getByTestId("icon-btn-close").click()}async function L(t,e){await M(t);let r=t.getByTestId("li-settings-network").getByRole("combobox");await r.locator(" > p").textContent()!==e?(await r.click(),await t.getByTestId(B.selectNetwork).getByRole("option",{name:e,exact:!0}).click(),(e==="Devnet"||e==="Testnet")&&await t.getByTestId(B.confirmModal).getByTestId(B.confifmButton).click()):console.info(`Network is already set to ${e}`),await t.getByTestId(k.portfolioButton).click()}async function ot({page:t,recoveryPhrase:e,network:o,walletName:r,additionalAccounts:a}){console.info(et("yellowBright",` + Solflare onboarding started...`,{validateStream:!1}));let s=await D("solflare");await t.getByTestId(x.alreadyHaveAWalletButton).click();let l=e.split(" ");for(let[d,v]of Object.entries(l))await t.getByTestId(`${x.recoveryPhraseInput}-${Number(d)+1}`).fill(v);let p=t.getByTestId(x.continueButton);await p.click();let u=t.getByTestId(x.passwordInput),m=t.getByTestId(x.repeatPasswordInput);await u.fill(s),await m.fill(s),await p.click(),await t.locator("div",{hasText:"Detecting your existing accounts. This process can take up to a minute."}).waitFor({state:"detached"}),await t.getByTestId(x.quickSetupButton).click(),await t.getByTestId(x.IAgreeButton).click();let U=new AbortController;if(C(t,U.signal).catch(d=>console.error({error:d})),r&&await N({page:t,currentAccountName:"Main Wallet",newAccountName:r}),o&&await L(t,o),a&&a.length>0)for(let{privateKey:d,walletName:v}of a)await W({page:t,privateKey:d,walletName:v});U.abort(),console.info(et("greenBright","\u2728 Solflare onboarding completed successfully",{validateStream:!1}))}async function rt(t){await t.getByTestId(F.rejectButton).click()}async function R(t){let e=await D("solflare");await t.getByTestId("input-password").fill(e),await t.getByTestId("btn-unlock").click(),await t.getByTestId("nav-main").waitFor({state:"attached",timeout:3e4})}import{expect as ht}from"@playwright/test";async function at({context:t,path:e,locator:o}){let r;try{await ht.poll(async()=>(r=t.pages().filter(a=>a.url().startsWith("chrome-extension://")).find(a=>a.url().match(e)),!!r),{timeout:9e4}).toBe(!0)}catch{let a=t.pages().filter(s=>s.url().startsWith("chrome-extension://")).map(s=>s.url());throw new Error([`Popup page with path "${e}" not found in context after 90s. `,`Pages in context: ${JSON.stringify(a)}`].join(` +`))}if(!r)throw new Error(`Popup page with path ${e} not found in context.`);return await Pt(r,o),await r.setViewportSize({width:360,height:592}),r}async function Pt(t,e){await t.waitForLoadState("load",{timeout:45e3}),await t.waitForLoadState("domcontentloaded",{timeout:45e3}),await t.locator(e).first().waitFor({state:"attached",timeout:45e3})}import nt from"fs";import Bt from"path";async function it(t){let e=w(t),o=Bt.resolve(e,"extension-id.txt");try{if(!nt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return nt.readFileSync(o,"utf-8")}catch(r){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${r.message}`)}}var h=class{name="solflare";onboardingPath="wallet.html#/onboard";async indexUrl(){return`chrome-extension://${await this.extensionId()}/wallet.html#/portfolio`}async promptUrl(){return`chrome-extension://${await this.extensionId()}/confirm_popup.html`}async extensionId(){return await it(this.name)}async promptPage(e){let o=await this.promptUrl();return await at({context:e,path:o,locator:"div[data-testid='page-dapp-connect'], div[data-testid='page-tx-sign']"})}};var b=class extends h{page;constructor(e){super(),this.page=e}async onboard({recoveryPhrase:e,network:o,additionalAccounts:r,walletName:a}){await ot({page:this.page,recoveryPhrase:e,network:o,additionalAccounts:r,walletName:a})}async unlock(){await R(this.page)}async lock(){await Y(this.page)}async renameAccount({currentAccountName:e,newAccountName:o}){await N({page:this.page,currentAccountName:e,newAccountName:o})}async switchNetwork(e){await L(this.page,e)}async switchAccount(e){await _(this.page,e)}async getAccountAddress(){return await Q(this.page)}async addAccount({privateKey:e,walletName:o}){await W({page:this.page,privateKey:e,walletName:o})}async connectToApp(e){await J(await this.promptPage(this.page.context()),e)}async confirmTransaction(){await G(await this.promptPage(this.page.context()))}async rejectTransaction(){await rt(await this.promptPage(this.page.context()))}};import ct from"fs";import Ct from"path";import{test as bt,chromium as Tt}from"@playwright/test";import{expect as St}from"@playwright/test";async function I(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}async function V(t,e){let o=await t.newPage();return await St(async()=>{await o.goto(e),await I(o)}).toPass(),o}async function H(t,e){let o=await e.newPage();for(let{origin:r,localStorage:a}of t){let s=o.mainFrame();await s.goto(r),await s.evaluate(c=>{c.forEach(({name:l,value:p})=>{window.localStorage.setItem(l,p)})},a)}await o.close()}import At from"fs/promises";async function st(t){await At.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var kt=35e3;async function O(t,e){try{await Promise.race([t.close(),new Promise((o,r)=>setTimeout(()=>r(new Error("Context close timed out")),kt))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await st(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var E,gr=({slowMo:t=0,profileName:e}={})=>bt.extend({contextPath:async({browserName:o},r,a)=>{let s=await P(`${o}-${a.testId}`);await r(s)},context:async({context:o,contextPath:r},a)=>{let s=new h,c=w(s.name),l=await S(s.name),p=Ct.resolve(c,e??"wallet-data");if(!ct.existsSync(p))throw new Error("\u274C Cache for Solflare wallet data not found. Create it first");await ct.promises.cp(p,r,{recursive:!0,force:!0});let u=A(l,t),m=await Tt.launchPersistentContext(r,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:t});await m.grantPermissions(["clipboard-read"]);let{cookies:f,origins:g}=await o.storageState();f&&await m.addCookies(f),g&&g.length>0&&await H(g,m);let y=await s.indexUrl(),$=y.split("#")[0]??"";await m.waitForEvent("page",{predicate:d=>d.url().includes($),timeout:15e3}),E=m.pages().find(d=>d.url().startsWith($))||await V(m,y);for(let d of m.pages())d.url().includes("about:blank")&&await d.close();await R(E),await a(m),await O(m,r)},solflarePage:async({context:o},r)=>{await r(E)},solflare:async({context:o},r)=>{let a=new b(E);await r(a)},autoCloseNotification:[async({context:o},r)=>{let a=new AbortController,s=C(E,a.signal);await r(void 0),a.abort(),await s.catch(c=>{console.error(`Auto close notification error: ${c.message}`)})},{auto:!0}]});import{test as vt}from"@playwright/test";import lt from"fs";import It from"path";import{chromium as Et}from"@playwright/test";async function pt({workerInfo:t,profileName:e,slowMo:o}){let r=new h,a=await P(t.workerIndex.toString()),s=w(r.name),c=It.resolve(s,e??"wallet-data");if(!lt.existsSync(c))throw new Error(`Cache for ${r.name} does not exist. Create it first!`);lt.cpSync(c,a,{recursive:!0,force:!0});let l=await S(r.name),p=A(l,o??0),u=await Et.launchPersistentContext(a,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:o}),m=await r.indexUrl(),f=m.split("#")[0]??"";await u.waitForEvent("page",{predicate:y=>y.url().includes(f),timeout:15e3});let g=u.pages().find(y=>y.url().startsWith(f));g||(g=await u.newPage(),await g.goto(m),await I(g));for(let y of u.pages())y.url().includes("about:blank")&&await y.close();return{context:u,walletPage:g,contextPath:a}}var Dr=({slowMo:t,profileName:e}={})=>vt.extend({workerScopeContents:[async({browser:o},r,a)=>{let{context:s,contextPath:c,walletPage:l}=await pt({workerInfo:a,profileName:e,slowMo:t});await s.grantPermissions(["clipboard-read"]);let p=new b(l);await p.unlock(),await r({wallet:p,walletPage:l,context:s}),await O(s,c)},{scope:"worker"}],autoCloseNotification:[async({workerScopeContents:o},r)=>{let a=new AbortController,s=C(o.walletPage,a.signal);await r(void 0),a.abort(),await s.catch(c=>{console.error(`Auto close notification error: ${c.message}`)})},{auto:!0}]});export{b as Solflare,gr as solflareFixture,Dr as solflareWorkerScopeFixture,ce as workerScopeContext}; diff --git a/package.json b/package.json index 37dfd42..12ff27c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chainwright", - "version": "0.10.15", + "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", @@ -91,17 +95,18 @@ "add:wallet": "tsx scripts/add-wallet.ts" }, "peerDependencies": { - "@playwright/test": "1.61.1" + "@playwright/test": "1.62.1" }, "devDependencies": { - "@biomejs/biome": "2.4.15", + "@biomejs/biome": "2.5.7", "@changesets/cli": "^2.31.1", - "@playwright/test": "1.61.1", + "@playwright/test": "1.62.1", "@types/adm-zip": "^0.5.8", "@types/cli-progress": "^3.11.6", - "@types/node": "^26.0.1", + "@types/node": "^26.2.0", "tsup": "^8.5.1", - "typescript": "^6.0.3", + "tsx": "^4.23.12", + "typescript": "^5.9.3", "vitest": "^4.1.10" }, "dependencies": { @@ -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.10", - "zod": "^4.4.3" + "prool": "^0.2.14" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 652b4d5..795fc58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: dependencies: '@inquirer/checkbox': specifier: ^5.2.1 - version: 5.2.1(@types/node@26.0.1) + version: 5.2.1(@types/node@26.2.0) adm-zip: specifier: ^0.6.0 version: 0.6.0 @@ -23,28 +23,19 @@ 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 - tsx: - specifier: ^4.23.10 - version: 4.23.10 - zod: - specifier: ^4.4.3 - version: 4.4.3 + version: 0.2.14(debug@4.4.3) devDependencies: '@biomejs/biome': - specifier: 2.4.15 - version: 2.4.15 + specifier: 2.5.7 + version: 2.5.7 '@changesets/cli': specifier: ^2.31.1 - version: 2.31.1(@types/node@26.0.1) + version: 2.31.1(@types/node@26.2.0) '@playwright/test': - specifier: 1.61.1 - version: 1.61.1 + specifier: 1.62.1 + version: 1.62.1 '@types/adm-zip': specifier: ^0.5.8 version: 0.5.8 @@ -52,17 +43,20 @@ importers: specifier: ^3.11.6 version: 3.11.6 '@types/node': - specifier: ^26.0.1 - version: 26.0.1 + specifier: ^26.2.0 + version: 26.2.0 tsup: specifier: ^8.5.1 - version: 8.5.1(postcss@8.5.26)(tsx@4.23.10)(typescript@6.0.3) + version: 8.5.1(postcss@8.5.26)(tsx@4.23.12)(typescript@5.9.3) + tsx: + specifier: ^4.23.12 + version: 4.23.12 typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^5.9.3 + version: 5.9.3 vitest: specifier: ^4.1.10 - version: 4.1.10(@types/node@26.0.1)(vite@8.0.13(@types/node@26.0.1)(esbuild@0.27.7)(tsx@4.23.10)) + version: 4.1.10(@types/node@26.2.0)(vite@8.0.13(@types/node@26.2.0)(esbuild@0.27.7)(tsx@4.23.12)) packages: @@ -70,59 +64,59 @@ packages: resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.4.15': - resolution: {integrity: sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==} + '@biomejs/biome@2.5.7': + resolution: {integrity: sha512-zr8K/DcY5tYsQOQwqMJ0AWElo6QgmgNI7idXgXLhevVszlt8RGVpesEJPqx3ThazLaOwjJ5Y8fz3BtH5fGZNsw==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.4.15': - resolution: {integrity: sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==} + '@biomejs/cli-darwin-arm64@2.5.7': + resolution: {integrity: sha512-vxo/Ls3/PYdQWyLhYYcgMOCzQypAjcY+iihS8M0wW03l16TCLW4zqZzGo75gm1VdCMj38hTVZ31KBWrZ4G9dJw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.4.15': - resolution: {integrity: sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==} + '@biomejs/cli-darwin-x64@2.5.7': + resolution: {integrity: sha512-Cd3Ga61amT/Yl/0x8elP5hhGYaFy4bw6WuysTgf7oo8TA5tJ5A1k+DkVoJ2BHbTVil51gTX9VPzArnrlLJ3Kyg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.4.15': - resolution: {integrity: sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==} + '@biomejs/cli-linux-arm64-musl@2.5.7': + resolution: {integrity: sha512-xPI5yB6XlpDbNkS+bm1t42olw5c4l3UrlOmLg7KtLJvjvkNF/1V4tnUgfkylGIeb3u/T+BzMGYqgQhzjAoJzuQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-arm64@2.4.15': - resolution: {integrity: sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==} + '@biomejs/cli-linux-arm64@2.5.7': + resolution: {integrity: sha512-rR2QE0yF2GYSuYuKIa7pKvODGJqnOH+2eDREAM8wV+mWKSkMQKdAp4zXEZfTaxY8PMoNONnpgSWcBCyLDPDOKg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.4.15': - resolution: {integrity: sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==} + '@biomejs/cli-linux-x64-musl@2.5.7': + resolution: {integrity: sha512-rE5VZi+qtmPgQH+l7jVxYoZ18b/TiHEhulhMpjmCZH1PltSbjRcxNWywC3HZ9tYottG7ORkeTtoscBilKSBm0g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.4.15': - resolution: {integrity: sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==} + '@biomejs/cli-linux-x64@2.5.7': + resolution: {integrity: sha512-FQgqJhscrqJUFptGaRSUJWlXAExwWcDwLuK49dvKfkQ1bB5SEEyFssnsxQY83Xm6jR0EbbX3+8+D5bfvYqUG2Q==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.4.15': - resolution: {integrity: sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==} + '@biomejs/cli-win32-arm64@2.5.7': + resolution: {integrity: sha512-Oq4x0CCwP4jirrcTywXs5kOGZ4v5vuEP+gWrbtjApOA2CL9F3F9GlIdQIci8AKSCa/zURanMRpX/4wQ7Am6hHg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.4.15': - resolution: {integrity: sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==} + '@biomejs/cli-win32-x64@2.5.7': + resolution: {integrity: sha512-V+0wu/nrj2S+MhP4EQ0uHNolP0IALEsz45pg0WoKkHfDeh0+ItHwP/p7bX5RPoMOl9NkpHYWdYPhIcy2mACHvQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -592,9 +586,9 @@ packages: '@oxc-project/types@0.130.0': resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} - '@playwright/test@1.61.1': - resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} - engines: {node: '>=18'} + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} hasBin: true '@rolldown/binding-android-arm64@1.0.1': @@ -867,11 +861,8 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@25.9.0': - resolution: {integrity: sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==} - - '@types/node@26.0.1': - resolution: {integrity: sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==} + '@types/node@26.2.0': + resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} '@vitest/expect@4.1.10': resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} @@ -936,18 +927,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'} @@ -1155,10 +1138,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'} @@ -1332,10 +1311,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==} @@ -1347,10 +1322,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'} @@ -1434,10 +1405,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'} @@ -1471,14 +1438,14 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - playwright-core@1.61.1: - resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} - engines: {node: '>=18'} + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} hasBin: true - playwright@1.61.1: - resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} - engines: {node: '>=18'} + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} hasBin: true postcss-load-config@6.0.1: @@ -1700,22 +1667,19 @@ packages: typescript: optional: true - tsx@4.23.10: - resolution: {integrity: sha512-0Vb9eKU47njkxv/6B8CRZRDsxNDT/Pz+BIU+M5jw7xL3TdzAjSxlZUxu0xFL/kLpaG3sHZ0LH2wbK1T1yo7CUQ==} + tsx@4.23.12: + resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} engines: {node: '>=18.0.0'} hasBin: true - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} @@ -1829,46 +1793,43 @@ 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': {} - '@biomejs/biome@2.4.15': + '@biomejs/biome@2.5.7': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.15 - '@biomejs/cli-darwin-x64': 2.4.15 - '@biomejs/cli-linux-arm64': 2.4.15 - '@biomejs/cli-linux-arm64-musl': 2.4.15 - '@biomejs/cli-linux-x64': 2.4.15 - '@biomejs/cli-linux-x64-musl': 2.4.15 - '@biomejs/cli-win32-arm64': 2.4.15 - '@biomejs/cli-win32-x64': 2.4.15 + '@biomejs/cli-darwin-arm64': 2.5.7 + '@biomejs/cli-darwin-x64': 2.5.7 + '@biomejs/cli-linux-arm64': 2.5.7 + '@biomejs/cli-linux-arm64-musl': 2.5.7 + '@biomejs/cli-linux-x64': 2.5.7 + '@biomejs/cli-linux-x64-musl': 2.5.7 + '@biomejs/cli-win32-arm64': 2.5.7 + '@biomejs/cli-win32-x64': 2.5.7 - '@biomejs/cli-darwin-arm64@2.4.15': + '@biomejs/cli-darwin-arm64@2.5.7': optional: true - '@biomejs/cli-darwin-x64@2.4.15': + '@biomejs/cli-darwin-x64@2.5.7': optional: true - '@biomejs/cli-linux-arm64-musl@2.4.15': + '@biomejs/cli-linux-arm64-musl@2.5.7': optional: true - '@biomejs/cli-linux-arm64@2.4.15': + '@biomejs/cli-linux-arm64@2.5.7': optional: true - '@biomejs/cli-linux-x64-musl@2.4.15': + '@biomejs/cli-linux-x64-musl@2.5.7': optional: true - '@biomejs/cli-linux-x64@2.4.15': + '@biomejs/cli-linux-x64@2.5.7': optional: true - '@biomejs/cli-win32-arm64@2.4.15': + '@biomejs/cli-win32-arm64@2.5.7': optional: true - '@biomejs/cli-win32-x64@2.4.15': + '@biomejs/cli-win32-x64@2.5.7': optional: true '@changesets/apply-release-plan@7.1.1': @@ -1900,7 +1861,7 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.31.1(@types/node@26.0.1)': + '@changesets/cli@2.31.1(@types/node@26.2.0)': dependencies: '@changesets/apply-release-plan': 7.1.1 '@changesets/assemble-release-plan': 6.0.10 @@ -1916,7 +1877,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@26.0.1) + '@inquirer/external-editor': 1.0.3(@types/node@26.2.0) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 enquirer: 2.4.1 @@ -2188,39 +2149,39 @@ snapshots: '@inquirer/ansi@2.0.7': {} - '@inquirer/checkbox@5.2.1(@types/node@26.0.1)': + '@inquirer/checkbox@5.2.1(@types/node@26.2.0)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@26.0.1) + '@inquirer/core': 11.2.1(@types/node@26.2.0) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.0.1) + '@inquirer/type': 4.0.7(@types/node@26.2.0) optionalDependencies: - '@types/node': 26.0.1 + '@types/node': 26.2.0 - '@inquirer/core@11.2.1(@types/node@26.0.1)': + '@inquirer/core@11.2.1(@types/node@26.2.0)': dependencies: '@inquirer/ansi': 2.0.7 '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.0.1) + '@inquirer/type': 4.0.7(@types/node@26.2.0) cli-width: 4.1.0 fast-wrap-ansi: 0.2.0 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 26.0.1 + '@types/node': 26.2.0 - '@inquirer/external-editor@1.0.3(@types/node@26.0.1)': + '@inquirer/external-editor@1.0.3(@types/node@26.2.0)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 26.0.1 + '@types/node': 26.2.0 '@inquirer/figures@2.0.7': {} - '@inquirer/type@4.0.7(@types/node@26.0.1)': + '@inquirer/type@4.0.7(@types/node@26.2.0)': optionalDependencies: - '@types/node': 26.0.1 + '@types/node': 26.2.0 '@isaacs/fs-minipass@4.0.1': dependencies: @@ -2277,9 +2238,9 @@ snapshots: '@oxc-project/types@0.130.0': {} - '@playwright/test@1.61.1': + '@playwright/test@1.62.1': dependencies: - playwright: 1.61.1 + playwright: 1.62.1 '@rolldown/binding-android-arm64@1.0.1': optional: true @@ -2420,7 +2381,7 @@ snapshots: '@types/adm-zip@0.5.8': dependencies: - '@types/node': 25.9.0 + '@types/node': 26.2.0 '@types/chai@5.2.3': dependencies: @@ -2429,7 +2390,7 @@ snapshots: '@types/cli-progress@3.11.6': dependencies: - '@types/node': 25.9.0 + '@types/node': 26.2.0 '@types/deep-eql@4.0.2': {} @@ -2439,11 +2400,7 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@25.9.0': - dependencies: - undici-types: 7.24.6 - - '@types/node@26.0.1': + '@types/node@26.2.0': dependencies: undici-types: 8.3.0 @@ -2456,13 +2413,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.10(vite@8.0.13(@types/node@26.0.1)(esbuild@0.27.7)(tsx@4.23.10))': + '@vitest/mocker@4.1.10(vite@8.0.13(@types/node@26.2.0)(esbuild@0.27.7)(tsx@4.23.12))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.13(@types/node@26.0.1)(esbuild@0.27.7)(tsx@4.23.10) + vite: 8.0.13(@types/node@26.2.0)(esbuild@0.27.7)(tsx@4.23.12) '@vitest/pretty-format@4.1.10': dependencies: @@ -2508,16 +2465,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 @@ -2720,7 +2671,9 @@ snapshots: mlly: 1.8.2 rollup: 4.60.4 - follow-redirects@1.16.0: {} + follow-redirects@1.16.0(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 fs-extra@7.0.1: dependencies: @@ -2751,12 +2704,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 @@ -2768,10 +2715,10 @@ snapshots: graceful-fs@4.2.11: {} - http-proxy@1.18.1: + http-proxy@1.18.1(debug@4.4.3): dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.16.0 + follow-redirects: 1.16.0(debug@4.4.3) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -2886,8 +2833,6 @@ snapshots: lodash.startcase@4.4.0: {} - lru-cache@11.4.0: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2899,10 +2844,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: @@ -2969,11 +2910,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: {} @@ -2996,20 +2932,20 @@ snapshots: mlly: 1.8.2 pathe: 2.0.3 - playwright-core@1.61.1: {} + playwright-core@1.62.1: {} - playwright@1.61.1: + playwright@1.62.1: dependencies: - playwright-core: 1.61.1 + playwright-core: 1.62.1 optionalDependencies: fsevents: 2.3.2 - postcss-load-config@6.0.1(postcss@8.5.26)(tsx@4.23.10): + postcss-load-config@6.0.1(postcss@8.5.26)(tsx@4.23.12): dependencies: lilconfig: 3.1.3 optionalDependencies: postcss: 8.5.26 - tsx: 4.23.10 + tsx: 4.23.12 postcss@8.5.26: dependencies: @@ -3023,13 +2959,13 @@ snapshots: dependencies: parse-ms: 4.0.0 - prool@0.2.14: + prool@0.2.14(debug@4.4.3): dependencies: change-case: 5.4.4 eventemitter3: 5.0.4 execa: 9.6.1 get-port: 7.2.0 - http-proxy: 1.18.1 + http-proxy: 1.18.1(debug@4.4.3) tar: 7.5.22 transitivePeerDependencies: - debug @@ -3211,7 +3147,7 @@ snapshots: tslib@2.8.1: optional: true - tsup@8.5.1(postcss@8.5.26)(tsx@4.23.10)(typescript@6.0.3): + tsup@8.5.1(postcss@8.5.26)(tsx@4.23.12)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -3222,7 +3158,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.26)(tsx@4.23.10) + postcss-load-config: 6.0.1(postcss@8.5.26)(tsx@4.23.12) resolve-from: 5.0.0 rollup: 4.60.4 source-map: 0.7.6 @@ -3232,32 +3168,30 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: postcss: 8.5.26 - typescript: 6.0.3 + typescript: 5.9.3 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - tsx@4.23.10: + tsx@4.23.12: dependencies: esbuild: 0.28.0 optionalDependencies: fsevents: 2.3.3 - typescript@6.0.3: {} + typescript@5.9.3: {} ufo@1.6.4: {} - undici-types@7.24.6: {} - undici-types@8.3.0: {} unicorn-magic@0.3.0: {} universalify@0.1.2: {} - vite@8.0.13(@types/node@26.0.1)(esbuild@0.27.7)(tsx@4.23.10): + vite@8.0.13(@types/node@26.2.0)(esbuild@0.27.7)(tsx@4.23.12): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -3265,15 +3199,15 @@ snapshots: rolldown: 1.0.1 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.0.1 + '@types/node': 26.2.0 esbuild: 0.27.7 fsevents: 2.3.3 - tsx: 4.23.10 + tsx: 4.23.12 - vitest@4.1.10(@types/node@26.0.1)(vite@8.0.13(@types/node@26.0.1)(esbuild@0.27.7)(tsx@4.23.10)): + vitest@4.1.10(@types/node@26.2.0)(vite@8.0.13(@types/node@26.2.0)(esbuild@0.27.7)(tsx@4.23.12)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.0.13(@types/node@26.0.1)(esbuild@0.27.7)(tsx@4.23.10)) + '@vitest/mocker': 4.1.10(vite@8.0.13(@types/node@26.2.0)(esbuild@0.27.7)(tsx@4.23.12)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -3290,10 +3224,10 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.13(@types/node@26.0.1)(esbuild@0.27.7)(tsx@4.23.10) + vite: 8.0.13(@types/node@26.2.0)(esbuild@0.27.7)(tsx@4.23.12) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 26.0.1 + '@types/node': 26.2.0 transitivePeerDependencies: - msw @@ -3309,5 +3243,3 @@ snapshots: yallist@5.0.0: {} yoctocolors@2.1.2: {} - - zod@4.4.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b67f8ca..a890dbc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,6 @@ allowBuilds: esbuild: true minimumReleaseAgeExclude: - - tsx@4.23.9 || 4.23.10 + - tsx@4.23.9 || 4.23.10 || 4.23.12 overrides: tar: ^7.5.21 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/tsconfig.json b/tsconfig.json index b950d3c..90d2061 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ "lib": ["dom", "dom.iterable", "esnext"], "types": ["node", "vitest/globals"], "target": "es2024", - "ignoreDeprecations": "6.0", "declaration": true, "module": "esnext", "moduleResolution": "bundler", 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",