@tobelabs/chainwright 0.6.0 → 0.6.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ import * as _playwright_test from '@playwright/test';
2
+ import { BrowserContext, Page } from '@playwright/test';
3
+ import z from 'zod';
4
+ import { b as WalletProfileFixtureArgs, c as WorkerScopeFixtureArgs } from '../../types-dJRYhQT9.js';
5
+ import { W as WorkerScopeFixture } from '../../worker-scope-context-ze2hEh79.js';
6
+
7
+ declare class KeplrProfile {
8
+ readonly name: "keplr";
9
+ readonly onboardingPath = "register.html";
10
+ indexUrl(): Promise<string>;
11
+ onboardingUrl(): Promise<string>;
12
+ promptUrl(): Promise<string>;
13
+ extensionId(): Promise<string>;
14
+ promptPage(context: BrowserContext): Promise<_playwright_test.Page>;
15
+ }
16
+
17
+ declare const keplrChains: readonly ["Injective", "Injective (Testnet)", "Polygon", "Bitcoin", "Bitcoin Signet", "Bitcoin Testnet"];
18
+ type KeplrChains = (typeof keplrChains)[number];
19
+ type AddAndOnboardingArgs = {
20
+ walletName: string;
21
+ privateKey: string;
22
+ chains: Array<KeplrChains>;
23
+ };
24
+ type OnboardingArgs = Array<AddAndOnboardingArgs>;
25
+ type AddAccountArgs = AddAndOnboardingArgs & {
26
+ mode: "add-account-multiple" | "add-account-single" | "onboard";
27
+ };
28
+ declare const getAccountAddressSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
29
+ chain: z.ZodLiteral<"Injective" | "Injective (Testnet)" | "Polygon">;
30
+ walletName: z.ZodString;
31
+ }, z.core.$strip>, z.ZodObject<{
32
+ chain: z.ZodLiteral<"Bitcoin" | "Bitcoin Signet" | "Bitcoin Testnet">;
33
+ chainTag: z.ZodLiteral<"Taproot" | "Native Segwit">;
34
+ walletName: z.ZodString;
35
+ }, z.core.$strip>], "chain">;
36
+ type GetAccountAddressArgs = z.infer<typeof getAccountAddressSchema>;
37
+ declare const renameAccountSchema: z.ZodObject<{
38
+ currentAccountName: z.ZodString;
39
+ newAccountName: z.ZodString;
40
+ }, z.core.$strip>;
41
+ type RenameAccountArgs = z.infer<typeof renameAccountSchema>;
42
+ type KeplrFixture = {
43
+ contextPath: string;
44
+ keplr: Keplr;
45
+ keplrPage: Page;
46
+ };
47
+
48
+ declare class Keplr extends KeplrProfile {
49
+ page: Page;
50
+ constructor(page: Page);
51
+ /**
52
+ * Onboards the wallet.
53
+ * @param {OnboardingArgs} args - The arguments required for onboarding.
54
+ * @param args[0].chains - The chains to onboard the wallet on.
55
+ * @param args[0].privateKey - The private key of the wallet to onboard.
56
+ * @param args[0].walletName - The name of the wallet to onboard.
57
+ * @example
58
+ * const keplr = new Keplr(page);
59
+ * await keplr.onboard([
60
+ * {
61
+ * chains: ["Injective", "Injective (Testnet)"],
62
+ * privateKey: "private key",
63
+ * walletName: "Wallet Name"
64
+ * }
65
+ * ]);
66
+ */
67
+ onboard(args: OnboardingArgs): Promise<void>;
68
+ /**
69
+ * Unlocks the wallet by entering the password.
70
+ * @example
71
+ * const keplr = new Keplr(page);
72
+ * await keplr.unlock();
73
+ */
74
+ unlock(): Promise<void>;
75
+ /**
76
+ * Locks the wallet by entering the password.
77
+ * This function locks the wallet by opening the settings page and then locking the wallet.
78
+ * @example
79
+ * const keplr = new Keplr(page);
80
+ * await keplr.lock();
81
+ */
82
+ lock(): Promise<void>;
83
+ /**
84
+ * Renames an account in the wallet.
85
+ * @param {RenameAccount} args - The arguments to rename the account.
86
+ * @param args.currentName - The current name of the active account.
87
+ * @param args.newAccountName - The new name of the account.
88
+ * @example
89
+ * const keplr = new Keplr(page);
90
+ * await keplr.renameAccount({ newAccountName: "New Account Name" });
91
+ */
92
+ renameAccount({ currentAccountName, newAccountName }: RenameAccountArgs): Promise<void>;
93
+ /**
94
+ * Switches the current account to the given account.
95
+ * @param accountName - The name of the account to switch to.
96
+ * @example
97
+ * const keplr = new keplr(page);
98
+ * await keplr.switchAccount("Account 1");
99
+ */
100
+ switchAccount(accountName: string): Promise<void>;
101
+ /**
102
+ * Retrieves the current account's address.
103
+ * @returns A promise that resolves with the current account's address as a string.
104
+ *
105
+ * @example
106
+ * const keplr = new Keplr(page);
107
+ * const address = await keplr.getAccountAddress();
108
+ */
109
+ getAccountAddress({ ...args }: GetAccountAddressArgs): Promise<string>;
110
+ /**
111
+ * Adds an account to the wallet via a private key or mnemonic phrase.
112
+ * @param {AddAccountArgs} args - The arguments to add the account.
113
+ * @param args.chains - The chains of the account to add.
114
+ * @param args.privateKey - The private key of the account to add, if the mode is "privateKey".
115
+ * @param args.walletName - The name of the wallet to add the account to.
116
+ * @param args.mode - The mode of adding the account (default: "add-account-multiple").
117
+ * @example
118
+ * const keplr = new Keplr(page);
119
+ * await keplr.addAccount({ chains: ["Testnet"], privateKey: "private key", walletName: "Keplr Wallet", mode: "add-account-multiple" });
120
+ */
121
+ addAccount({ chains, privateKey, walletName, mode }: AddAccountArgs): Promise<void>;
122
+ /**
123
+ * Connects to the wallet.
124
+ * This function connects to the wallet by opening the connect page and then clicking on the connect button.
125
+ * @example
126
+ * const keplr = new Keplr(page);
127
+ * await keplr.connectToApp();
128
+ */
129
+ connectToApp(): Promise<void>;
130
+ /**
131
+ * Confirms a transaction in the wallet by clicking on the "Approve" button.
132
+ * This function confirms a transaction in the wallet by opening the popup page and then clicking on the "Approve" button.
133
+ * @example
134
+ * const keplr = new Keplr(page);
135
+ * await keplr.confirmTransaction();
136
+ */
137
+ confirmTransaction(): Promise<void>;
138
+ /**
139
+ * Rejects a transaction in the wallet by clicking on the "Reject" button.
140
+ * This function rejects a transaction in the wallet by opening the popup page and then clicking on the "Reject" button.
141
+ * @example
142
+ * const keplr = new Keplr(page);
143
+ * await keplr.rejectTransaction();
144
+ */
145
+ rejectTransaction(): Promise<void>;
146
+ }
147
+
148
+ declare const keplrFixture: ({ slowMo, profileName }?: WalletProfileFixtureArgs) => _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & KeplrFixture, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
149
+
150
+ declare const keplrWorkerScopeFixture: ({ slowMo, profileName, dappUrl }?: WorkerScopeFixtureArgs) => _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & KeplrFixture, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions & WorkerScopeFixture<Keplr>>;
151
+
152
+ export { Keplr, keplrFixture, keplrWorkerScopeFixture };
@@ -0,0 +1,3 @@
1
+ import{expect as ot}from"@playwright/test";function W(t){return new Promise(e=>setTimeout(e,t))}async function C(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}import Q from"fs";import At from"path";import yt from"path";var Y=".wallet-cache",q=".wallet-context";var J="13.22.0",_="https://github.com/amaify/chainwright/releases/download/v0.1.0/",Ht=`https://github.com/MetaMask/metamask-extension/releases/download/v${J}/metamask-chrome-${J}.zip`,jt=`${_}solflare-wallet-extension-v2.19.1.zip`,zt=`${_}petra-wallet-extension-v2.4.8.zip`,Gt=`${_}phantom-wallet-extension-v26.10.0.zip`,Xt=`${_}meteor-wallet-extension-v0.7.0.zip`,Jt=`${_}keplr-wallet-extension-v0.13.3.zip`;function w(t){return yt.resolve(process.cwd(),Y,t)}async function R(t){let e=w(t),o=At.resolve(e,"password.txt");try{if(!Q.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return Q.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Pt}from"@playwright/test";async function Z({context:t,path:e,locator:o}){let a;try{await Pt.poll(async()=>(a=t.pages().filter(r=>r.url().startsWith("chrome-extension://")).find(r=>r.url().match(e)),!!a),{timeout:3e4}).toBe(!0)}catch{let r=t.pages().filter(c=>c.url().startsWith("chrome-extension://")).map(c=>c.url());throw new Error(`Popup page with path "${e}" not found in context after 30s. Pages in context: ${JSON.stringify(r)}`)}if(!a)throw new Error(`Popup page with path ${e} not found in context.`);return await vt(a,o),await a.setViewportSize({width:360,height:592}),a}async function vt(t,e){await t.waitForLoadState("load",{timeout:4e4}),await t.waitForLoadState("domcontentloaded",{timeout:4e4}),await t.locator(e).first().waitFor({state:"attached",timeout:4e4})}import tt from"fs";import bt from"path";async function et(t){let e=w(t),o=bt.resolve(e,"extension-id.txt");try{if(!tt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return tt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var g=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 et(this.name)}async promptPage(e){let o=await this.promptUrl();return await Z({context:e,path:o,locator:"div[data-simplebar='init']"})}};var h={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 kt(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async function E({page:t,privateKey:e,walletName:o,chains:a,mode:r="onboard"}){let c=await R("keplr"),s=new g;if(await t.locator(h.importExistingWalletButton).click(),await t.locator(h.usePrivateKeyButton).click(),await t.getByRole("button",{name:"Private key",exact:!0}).click(),await t.locator(h.privateKeyInput).fill(e),await t.getByRole("button",{name:"Import",exact:!0}).click(),await t.locator(h.walletNameInput).fill(o),r==="onboard"){let A=t.locator(h.walletPasswordInput),F=t.locator(h.confirmWalletPasswordInput);await A.fill(c),await F.fill(c)}await t.locator(h.nextButton).click();let y=t.locator("div:has-text('All Native Chains')").nth(-4),P=t.locator("div[cursor='pointer']:has-text('Cosmos Hub')"),b=await y.locator("input[type='checkbox']").getAttribute("checked"),k=await P.locator("input[type='checkbox']").getAttribute("checked");b!==null&&await y.click(),k!==null&&await P.click();let D=t.locator(h.searchNetworkInput);for(let A of a){await D.fill(A);let T=t.locator("div[class='simplebar-content']").locator("div[cursor] > div").first().locator("div").filter({hasText:new RegExp(`^${kt(A)}$`,"i")}).nth(2).locator("../../../../..");await T.waitFor({state:"visible",timeout:2e4}),await T.locator("input[type='checkbox']").getAttribute("checked")===null&&await T.click()}let B=t.locator(h.saveButton);if(await B.scrollIntoViewIfNeeded(),await B.click(),await W(2e3),r==="onboard"){await t.goto(await s.indexUrl());return}if(r==="add-account-single"){let A=t.locator(h.finishButton);await A.waitFor({state:"visible",timeout:2e4}),await ot(A).toBeEnabled({timeout:2e4}),await A.click()}}async function L(t){let o=await new g().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 s;if(await ot.poll(async()=>(s=t.context().pages().find(p=>p.url().match(o)),!!s),{timeout:3e4}).toBe(!0).catch(p=>{console.error(`Failed to find onboarding page with URL matching ${o}. Original error: ${p}`)}),!s)throw new Error(`Onboarding page not found. Expected URL: ${o}`);return await C(s),await s.bringToFront(),s}async function at({page:t,privateKey:e,chains:o,walletName:a,mode:r}){let c=await L(t);await E({page:c,privateKey:e,walletName:a,chains:o,mode:r}),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 Ct}from"@playwright/test";var $={approveButton:"button:has-text('Approve')",rejectButton:"button[color='secondary']"};async function rt(t){let e=t.locator($.approveButton);await Ct(e).toBeEnabled({timeout:2e4}),await e.click()}async function nt(t){t.getByRole("button",{name:"Approve",exact:!0}).click(),await W(1e3)}import{expect as St}from"@playwright/test";import f from"zod";var it=f.discriminatedUnion("chain",[f.object({chain:f.literal(["Injective","Injective (Testnet)","Polygon"]),walletName:f.string().min(1,"Wallet name cannot be an empty string")}),f.object({chain:f.literal(["Bitcoin","Bitcoin Signet","Bitcoin Testnet"]),chainTag:f.literal(["Taproot","Native Segwit"]),walletName:f.string().min(1,"Wallet name cannot be an empty string")})]),ct=f.object({currentAccountName:f.string().min(1,"Current account name cannot be an empty string"),newAccountName:f.string().min(1,"New account name cannot be an empty string")});async function st({page:t,...e}){let o=it.parse({...e});await t.getByRole("textbox",{name:"Search for asset or chain (i.e. ATOM, Cosmos)",exact:!0}).fill(o.chain);let r=t.locator(`div:has-text("${o.chain}")`).nth(-2).filter({hasNot:t.locator("span")});if(await r.waitFor({state:"attached",timeout:2e4}),!await r.isVisible().catch(()=>!1))throw Error(`Make sure "${o.chain}" is activated.`);let s=await r.locator("div").all();St(s.length).toBeGreaterThan(0),await t.locator(`div:has(div:has-text('${o.walletName}'))`).nth(-3).locator("div:has(> div > svg)").click();let u=t.locator("div:has(> div[data-simplebar='init'])").last(),d=u.locator("div:has(> div > input)").locator("input");await d.fill(o.chain);let v=await u.locator("div[cursor='pointer']",{hasText:o.chain}).all(),y;for(let b of v){let k;"chainTag"in e&&(k=e.chainTag);let D=b.locator("div",{hasText:o.chain}).last(),B=k?b.locator("div",{hasText:k}).last():null,F=(B?await B?.isVisible().catch(()=>!1):!1)?await B?.textContent():null,T=await D.textContent(),X=F?`${T} ${F}`:T,xt=k?`${o.chain} ${k}`:o.chain;if(X===xt){y=D.locator("xpath=../../../.."),await d.clear();break}}if(!y)throw Error(`Address for ${o.walletName} account on "${o.chain}" chain not found.`);return await y.hover(),await y.scrollIntoViewIfNeeded(),await y.click(),await t.evaluate(async()=>await navigator.clipboard.readText())}var N={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'])"},H={unlockButton:"button[type='submit']:has-text('Unlock')",passwordInput:"input[placeholder='Type Your Password']"};async function lt(t){await t.locator(N.openSidebarMenuButton).click(),await t.locator(N.lockWalletButton).nth(-1).click(),await t.getByText("Welcome Back").waitFor({state:"visible",timeout:3e4})}import{styleText as pt}from"util";import{expect as Bt}from"@playwright/test";async function K(t,e){let o=t.locator("div[color]").nth(1);if(await o.textContent()===e){console.info(`
2
+ 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 s=t.locator("div:has-text('Select Wallet')").last();await r.click(),await s.waitFor({state:"detached",timeout:3e4})}async function j({page:t,onboard:e}){if(console.info(pt("yellowBright",`
3
+ Keplr onboarding started...`,{validateStream:!1})),e.length===1)for(let{privateKey:o,walletName:a,chains:r}of e)await E({page:t,privateKey:o,walletName:a,chains:r,mode:"onboard"});if(e.length>1){let o=e[0];if(o){let{privateKey:l,walletName:d,chains:x}=o;await E({page:t,privateKey:l,walletName:d,chains:x,mode:"onboard"})}let a=e.slice(1);for(let{privateKey:l,walletName:d,chains:x}of a){let v=await L(t);await E({page:v,privateKey:l,walletName:d,chains:x,mode:"add-account-single"})}await t.locator("div",{hasText:"Select Wallet"}).last().locator("../../..").locator("div > svg").click(),await t.getByRole("link",{name:"Home",exact:!0}).click();let p=t.locator(N.openSidebarMenuButton);await Bt(p).toBeVisible({timeout:3e4});let m=e.at(-1)?.walletName,u=e[0]?.walletName;m&&u&&await K(t,u)}await W(3e3),console.info(pt("greenBright","\u2728 Keplr onboarding completed successfully",{validateStream:!1}))}async function mt(t){let e=t.locator($.rejectButton);await e.waitFor({state:"visible",timeout:2e4}),await e.click()}async function ut({page:t,currentAccountName:e,newAccountName:o}){let a=ct.parse({currentAccountName:e,newAccountName:o});await t.getByRole("link",{name:"Settings",exact:!0}).click(),await t.locator("div[cursor='pointer']").first().click();let s=t.locator("div",{hasText:a.currentAccountName}).nth(-4);if(!await s.isVisible().catch(()=>!1))throw Error(`Account with name "${a.currentAccountName}" not found`);await s.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 U(t){let e=await R("keplr");await t.locator(H.passwordInput).fill(e),await t.locator(H.unlockButton).click(),await t.locator("div:has-text('Deposit')").last().waitFor({state:"visible",timeout:3e4})}var S=class extends g{page;constructor(e){super(),this.page=e}async onboard(e){await j({page:this.page,onboard:e})}async unlock(){await U(this.page)}async lock(){await lt(this.page)}async renameAccount({currentAccountName:e,newAccountName:o}){await ut({page:this.page,currentAccountName:e,newAccountName:o})}async switchAccount(e){await K(this.page,e)}async getAccountAddress({...e}){return await st({page:this.page,...e})}async addAccount({chains:e,privateKey:o,walletName:a,mode:r="add-account-multiple"}){await at({page:this.page,privateKey:o,walletName:a,chains:e,mode:r})}async connectToApp(){await nt(await this.promptPage(this.page.context()))}async confirmTransaction(){await rt(await this.promptPage(this.page.context()))}async rejectTransaction(){await mt(await this.promptPage(this.page.context()))}};import gt from"fs";import _t from"path";import{test as Nt,chromium as Ot}from"@playwright/test";import Tt from"path";async function O(t){return Tt.resolve(process.cwd(),q,t)}import{expect as Wt}from"@playwright/test";async function z(t,e){let o=await t.newPage();return await Wt(async()=>{await o.goto(e),await C(o)}).toPass(),o}async function G(t,e){let o=await e.newPage();for(let{origin:a,localStorage:r}of t){let c=o.mainFrame();await c.goto(a),await c.evaluate(s=>{s.forEach(({name:p,value:m})=>{window.localStorage.setItem(p,m)})},r)}await o.close()}import Et from"fs/promises";async function dt(t){await Et.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var It=35e3;async function M(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),It))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await dt(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}import wt from"fs";import Ft from"path";async function V(t){try{let e=w(t),o=Ft.resolve(e,"extension-path.txt");if(!wt.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=wt.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}`)}}var I,da=({slowMo:t=0,profileName:e}={})=>Nt.extend({contextPath:async({browserName:o},a,r)=>{let c=await O(`${o}-${r.testId}`);await a(c)},context:async({context:o,contextPath:a},r)=>{let c=new g,s=w(c.name),p=await V(c.name),m=_t.resolve(s,e??"wallet-data");if(!gt.existsSync(m))throw new Error("\u274C Cache for Keplr wallet data not found. Create it first");gt.cpSync(m,a,{recursive:!0,force:!0});let u=[`--disable-extensions-except=${p}`,`--load-extension=${p}`];process.env.HEADLESS&&(u.push("--headless=new"),t>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!"));let l=await Ot.launchPersistentContext(a,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:t});await l.grantPermissions(["clipboard-read"]);let{cookies:d,origins:x}=await o.storageState();d&&await l.addCookies(d),x&&x.length>0&&G(x,l);let v=await c.indexUrl();I=l.pages().find(P=>P.url().startsWith(v))||await z(l,v),await C(I);for(let P of l.pages()){let b=P.url();(b.includes("about:blank")||b.includes(c.onboardingPath))&&await P.close()}await I.bringToFront(),await U(I),await r(l),await M(l,a)},keplrPage:async({context:o},a)=>{await a(I)},keplr:async({context:o},a)=>{let r=new S(I);await a(r)}});import{test as Lt}from"@playwright/test";import ht from"fs";import Dt from"path";import{chromium as Rt}from"@playwright/test";async function ft({wallet:t,workerInfo:e,profileName:o,slowMo:a}){let r=await O(e.workerIndex.toString()),c=w(t.name),s=Dt.resolve(c,o??"wallet-data");if(!ht.existsSync(s))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);ht.cpSync(s,r,{recursive:!0,force:!0});let p=await V(t.name),m=await Rt.launchPersistentContext(r,{headless:!1,args:[`--disable-extensions-except=${p}`],slowMo:process.env.HEADLESS?0:a}),u=await t.indexUrl(),l=m.pages()[0];return l||(l=await m.newPage()),await l.goto(u),{context:m,walletPage:l,contextPath:r}}var Wa=({slowMo:t,profileName:e,dappUrl:o}={})=>Lt.extend({workerScopeContents:[async({browser:a},r,c)=>{let s=new g,{context:p,contextPath:m,walletPage:u}=await ft({wallet:s,workerInfo:c,profileName:e,slowMo:t});await p.grantPermissions(["clipboard-read"]);for(let d of p.pages())d.url().includes("about:blank")&&await d.close();let l=new S(u);await l.unlock(),await r({wallet:l,walletPage:u,context:p}),await M(p,m)},{scope:"worker"}],dappPage:[async({workerScopeContents:a},r)=>{let{context:c}=a,s=await c.newPage();o&&await s.goto(o),await r(s)},{scope:"worker"}],keplrPage:async({workerScopeContents:a},r)=>{await r(a.walletPage)},keplr:async({workerScopeContents:a},r)=>{let c=new S(a.walletPage);await r(c)}});export{S as Keplr,da as keplrFixture,Wa as keplrWorkerScopeFixture};
@@ -0,0 +1,203 @@
1
+ import * as _playwright_test from '@playwright/test';
2
+ import { Page, BrowserContext } from '@playwright/test';
3
+ import { Instance } from 'prool';
4
+ import z from 'zod';
5
+ import { b as WalletProfileFixtureArgs, c as WorkerScopeFixtureArgs } from '../../types-dJRYhQT9.js';
6
+ import { W as WorkerScopeFixture } from '../../worker-scope-context-ze2hEh79.js';
7
+
8
+ type RenameAccount = {
9
+ page: Page;
10
+ currentAccountName: string;
11
+ newAccountName: string;
12
+ };
13
+
14
+ type SwitchAccount = {
15
+ page: Page;
16
+ accountName: string;
17
+ };
18
+
19
+ declare class MetamaskProfile {
20
+ readonly name: "metamask";
21
+ readonly onboardingPath = "/home.html#onboarding";
22
+ indexUrl(): Promise<string>;
23
+ promptUrl(): Promise<string>;
24
+ extensionId(): Promise<string>;
25
+ promptPage(context: BrowserContext): Promise<_playwright_test.Page>;
26
+ }
27
+
28
+ type OnboardingArgs = {
29
+ mode: "create";
30
+ mainAccountName?: string;
31
+ } | {
32
+ mode: "import";
33
+ mainAccountName?: string;
34
+ secretRecoveryPhrase: string;
35
+ };
36
+ type AddAccountArgs = {
37
+ privateKey: string;
38
+ accountName: string;
39
+ };
40
+ declare const addCustomNetworkSchema: z.ZodObject<{
41
+ networkName: z.ZodString;
42
+ rpcUrl: z.ZodURL;
43
+ chainId: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
44
+ currencySymbol: z.ZodString;
45
+ }, z.core.$strip>;
46
+ type AddCustomNetwork = z.infer<typeof addCustomNetworkSchema>;
47
+ type SwitchNetwork = {
48
+ chainName: "Ethereum" | "Base" | "Linea";
49
+ networkType: "mainnet";
50
+ } | {
51
+ chainName: "Sepolia" | "Linea Sepolia" | "Mega Testnet" | "Monad Testnet" | (string & {});
52
+ networkType: "testnet" | "custom";
53
+ };
54
+ type GasFeeSettings = {
55
+ feeType: "low" | "medium" | "high";
56
+ } | {
57
+ feeType: "advanced";
58
+ maxBaseFee: string;
59
+ priorityFee: string;
60
+ };
61
+ type GetAccountAddressChains = "Ethereum" | "Tron" | "Bitcoin" | "Solana";
62
+ type MetamaskFixture = {
63
+ contextPath: string;
64
+ metamask: Metamask;
65
+ metamaskPage: Page;
66
+ createAnvilNode: (options?: Instance.anvil.Parameters) => Promise<{
67
+ rpcUrl: string;
68
+ anvil: Instance.anvil.Parameters;
69
+ chainId: number;
70
+ }>;
71
+ connectToAnvil: () => Promise<void>;
72
+ };
73
+
74
+ declare class Metamask extends MetamaskProfile {
75
+ page: Page;
76
+ constructor(page: Page);
77
+ /**
78
+ * Onboards the wallet.
79
+ * This function onboards the wallet by entering the password and other required information.
80
+ * @param {OnboardingArgs} args - The arguments required for onboarding.
81
+ * @param args.mode - Create a new wallet or import an existing wallet.
82
+ * @param args.password - The password for the wallet.
83
+ * @param args.secretRecoveryPhrase - The secret recovery phrase for the wallet when importing a wallet.
84
+ * @example
85
+ * const metamask = new Metamask(page);
86
+ * await metamask.onboard({ mode: "import", password: "password", secretRecoveryPhrase: "Recovery phrase" });
87
+ */
88
+ onboard(args: OnboardingArgs): Promise<void>;
89
+ /**
90
+ * Unlocks the wallet.
91
+ * This function unlocks the wallet by entering the password.
92
+ * @example
93
+ * const metamask = new Metamask(page);
94
+ * await metamask.unlock()
95
+ */
96
+ unlock(): Promise<void>;
97
+ /**
98
+ * Locks the wallet.
99
+ * This function opens the settings page and then locks the wallet.
100
+ * @example
101
+ * const metamask = new Metamask(page);
102
+ * await metamask.lock()
103
+ */
104
+ lock(): Promise<void>;
105
+ /**
106
+ * Renames an account.
107
+ * @param {Omit<RenameAccount, "page">} args - The arguments to rename the account.
108
+ * @param args.newAccountName - The new name of the account.
109
+ * @param args.currentAccountName - The current name of the account.
110
+ * @example
111
+ * const metamask = new Metamask(page);
112
+ * await metamask.renameAccount({ newAccountName: "New Account Name", currentAccountName: "Current Account Name" });
113
+ */
114
+ renameAccount({ newAccountName, currentAccountName }: Omit<RenameAccount, "page">): Promise<void>;
115
+ /**
116
+ * Adds an account to the wallet via a private key.
117
+ * @param {AddAccountArgs} args - The arguments to add the account.
118
+ * @param args.privateKey - The private key of the account to add.
119
+ * @param args.accountName - The name of the account to add.
120
+ * @example
121
+ * const metamask = new Metamask(page);
122
+ * await metamask.addAccount({ privateKey: "private key", accountName: "Account 1" });
123
+ */
124
+ addAccount({ privateKey, accountName }: AddAccountArgs): Promise<void>;
125
+ /**
126
+ * Switches the current account to the given account.
127
+ * @param {Omit<SwitchAccount, "page">} args - The arguments to switch the account.
128
+ * @param args.accountName - The name of the account to switch to.
129
+ * @example
130
+ * const metamask = new Metamask(page);
131
+ * await metamask.switchAccount({ accountName: "Account 1" });
132
+ */
133
+ switchAccount({ accountName }: Omit<SwitchAccount, "page">): Promise<void>;
134
+ /**
135
+ * Switches the current network to the given network.
136
+ * @param {SwitchNetwork} args - The arguments to switch the network.
137
+ * @param args.networkType - It should be "testnet", "mainnet", and "custom".
138
+ * @param args.chainName - (Mainnet): Ethereum, Base, Linea.
139
+ * @param args.chainName - (Testnet): Sepolia, Linea Sepolia, Mega Testnet, Monad Testnet.
140
+ * @example
141
+ * const metamask = new Metamask(page);
142
+ * await metamask.switchNetwork({chainName: "Sepolia", networkType: "testnet"});
143
+ */
144
+ switchNetwork({ ...args }: SwitchNetwork): Promise<void>;
145
+ /**
146
+ * Gets the current account's address.
147
+ * @returns The current account's address as a string.
148
+ * @example
149
+ * const metamask = new Metamask(page);
150
+ * await metamask.getAccountAddress()
151
+ */
152
+ getAccountAddress(network: GetAccountAddressChains): Promise<string | null>;
153
+ /**
154
+ * Toggles the visibility of testnet networks in the wallet's network selector.
155
+ * To persists the change, do it at the point of onboarding.
156
+ * @example
157
+ * const metamask = new Metamask(page);
158
+ * await metamask.toggleShowTestnetNetwork()
159
+ */
160
+ toggleShowTestnetNetwork(): Promise<void>;
161
+ /**
162
+ * Add a custom network to the wallet. If you want to persist the added wallet, do it at
163
+ * the point of onboarding.
164
+ * @param {AddCustomNetwork} options - an object containing the parameters for adding a custom network.
165
+ * @param {number|string} options.chainId - the chain ID of the network.
166
+ * @param {string} options.currencySymbol - the currency symbol of the network.
167
+ * @param {string} options.networkName - the name of the network.
168
+ * @param {string} options.rpcUrl - the RPC URL of the network.
169
+ * @example
170
+ * const metamask = new Metamask(page);
171
+ * await metamask.addCustomNetwork({chainId: 100, currencySymbol: "XDAI", networkName: "Gnosis", rpcUrl: "https://gnosis.oat.farm"});
172
+ */
173
+ addCustomNetwork({ chainId, currencySymbol, networkName, rpcUrl }: AddCustomNetwork): Promise<void>;
174
+ /**
175
+ * Connects to an app by clicking on the "Connect to app" button.
176
+ * If an account is provided, it will be selected before connecting to the app.
177
+ * @param {string} [account] - The account to select before connecting to the app.
178
+ * @example
179
+ * const metamask = new Metamask(page);
180
+ * await metamask.connectToApp("Account 1");
181
+ */
182
+ connectToApp(account?: string): Promise<void>;
183
+ /**
184
+ * Confirms a transaction in the wallet by clicking on the "Confirm" button.
185
+ * @example
186
+ * const metamask = new Metamask(page);
187
+ * await metamask.confirmTransaction();
188
+ */
189
+ confirmTransaction(gasFee?: GasFeeSettings): Promise<void>;
190
+ /**
191
+ * Cancels a transaction in the wallet by clicking on the "Cancel" button.
192
+ * @example
193
+ * const metamask = new Metamask(page);
194
+ * await metamask.cancelTransaction();
195
+ */
196
+ rejectTransaction(): Promise<void>;
197
+ }
198
+
199
+ declare const metamaskFixture: ({ slowMo, profileName }?: WalletProfileFixtureArgs) => _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & MetamaskFixture, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
200
+
201
+ declare const metamaskWorkerScopeFixture: ({ profileName, dappUrl, slowMo }?: WorkerScopeFixtureArgs) => _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & MetamaskFixture, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions & WorkerScopeFixture<Metamask>>;
202
+
203
+ export { Metamask, metamaskFixture, metamaskWorkerScopeFixture };
@@ -0,0 +1,3 @@
1
+ import{expect as h}from"@playwright/test";import st from"zod";import{test as Mt}from"@playwright/test";function it(t,e){t&&(console.warn(`
2
+ \u26A0\uFE0F Skipping test: ${e}`),Mt.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"},F={lockButton:"global-menu-lock",networksButton:"global-menu-networks"},D={passwordInput:"unlock-password",unlockButton:"unlock-submit"},P={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:"add-wallet-modal-import-wallet",importAccountButton:"add-wallet-modal-import-account"};var g={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 ct({page:t,privateKey:e,accountName:o}){let a=st.string().min(1,"Account name cannot be an empty string").trim().parse(o),n=st.string().min(1,"Private key cannot be an empty string").trim().parse(e),r=t.getByTestId(w.accountMenuButton);await h(r).toBeVisible({timeout:3e4}),await r.click(),await h(t.getByRole("heading",{name:/accounts/i})).toBeVisible();let i=t.getByTestId(P.addWalletButton),c=await i.textContent();c?.includes("Syncing")&&await h.poll(async()=>(await i.textContent())?.trim()??"",{timeout:12e4}).not.toBe(c),await h(i).toBeEnabled({timeout:6e4}),await i.click();let s=t.getByRole("dialog");await h(s).toContainText(/add wallet/i),await t.getByTestId(P.importAccountButton).click(),await t.locator("input[id='private-key-box']").fill(n);let p=t.getByTestId(g.importAccountConfirmButton);await h(p).toBeEnabled(),await p.click();let f=t.getByTestId(g.importSRPError),x=await f.isVisible().catch(()=>!1);x&&it(x,`${(await f.textContent())?.split(".")[0]}`);let S=t.locator("div:has(> div[data-testid^='multichain-account-cell-keyring'][class*='mm-box--background-color-background-muted'])"),T=(await S.textContent())?.split("$")[0];T&&await Lt({page:t,accountName:a,activeAccountLocator:S,activeAccountName:T}),await t.locator("button[aria-label='Back']").first().click()}async function Lt({page:t,accountName:e,activeAccountLocator:o,activeAccountName:a}){let n=o.locator(`div[aria-label='${a} options']`);await h(n).toBeVisible(),await n.click(),await h(t.getByRole("tooltip")).toBeVisible();let r=t.locator(`div[aria-label='${P.renameAccountLabel}']`);await h(r).toBeVisible(),await r.click();let i=t.getByRole("dialog"),c=i.getByRole("heading",{name:/rename/i});await h(c).toBeVisible();let s=i.getByRole("textbox");await h(s).toBeVisible(),await s.fill(e);let u=i.getByRole("button",{name:/confirm/i});await h(u).toBeEnabled(),await u.click(),await i.waitFor({state:"detached",timeout:2e4});let d=t.locator("div:has(> div[data-testid^='multichain-account-cell-keyring'][class*='mm-box--background-color-background-muted'])");await h(d).toContainText(e)}import{expect as V}from"@playwright/test";import N from"zod";var lt=N.object({networkName:N.string().min(1,"Network name cannot be an empty string"),rpcUrl:N.url(),chainId:N.number().or(N.string().includes("0x")),currencySymbol:N.string().toUpperCase().min(1,"Currency symbol cannot be an empty string")});import{expect as Wt}from"@playwright/test";async function K(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 Wt(o).toBeVisible(),await o.click())}async function mt({page:t,...e}){let{chainId:o,currencySymbol:a,networkName:n,rpcUrl:r}=lt.parse({...e},{reportInput:!0});await K(t),await t.getByTestId(F.networksButton).click();let c=t.locator("section[role='dialog']");await V(c).toContainText(/manage networks/i),await t.getByRole("button",{name:/add a custom network/i}).click(),await V(c).toContainText(/Add a custom network/i);let u=t.getByTestId("network-form-network-name"),d=t.getByTestId("test-add-rpc-drop-down"),p=t.getByTestId("network-form-chain-id"),f=t.getByTestId("network-form-ticker-input");await u.fill(n),await d.click(),await t.getByRole("tooltip").locator("div:has(> button:has-text('Add RPC URL'))").click(),await V(c).toContainText(/Add RPC URL/i);let T=t.getByTestId("rpc-url-input-test"),B=t.getByRole("button",{name:/Add URL/i});await T.fill(r),await B.click(),await V(c).toContainText(/Add a custom network/i);let y=t.getByTestId("network-form-chain-id-error");if(await y.isVisible().catch(()=>!1)){let C=await y.textContent();throw Error(`RPC error: ${C}`)}await p.fill(`${o}`),await f.fill(a);let I=t.getByRole("button",{name:/save/i});await V(I).toBeEnabled(),await I.click()}import{expect as dt}from"@playwright/test";function v(t){return new Promise(e=>setTimeout(e,t))}var j={confirmButton:"confirm-footer-button",cancelButton:"confirm-footer-cancel-button"};async function ut(t,e){let o=t.getByTestId(j.confirmButton);await v(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 d=t.getByRole("dialog");await dt(d).toBeVisible();let p=d.locator("h4",{hasText:"Insufficient funds"}),f=await d.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 d=t.getByRole("textbox",{name:"Max base fee"}),p=t.getByRole("textbox",{name:"Priority fee"}),f=t.getByRole("button",{name:"Save",exact:!0});await d.fill(e.maxBaseFee),await p.fill(e.priorityFee),await f.click()}}await dt(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 Dt}from"@playwright/test";import{expect as Y}from"@playwright/test";async function M({page:t,accountName: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 n=t.getByTestId(P.addMultichainAccountButton),r=await n.textContent();r?.includes("Syncing")&&await Y.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 s of i)if(await s.scrollIntoViewIfNeeded(),(await s.textContent())?.includes(e)){c=s;break}if(!c)throw Error(`Account with name "${e}" not found.`);await c?.click()}async function pt(t,e){e&&await M({page:t,accountName:e});let o=t.getByRole("button",{name:"Connect",exact:!0});await o.waitFor({state:"visible",timeout:25e3}),await o.click(),await t.getByRole("heading",{name:"Connecting",exact:!0}).waitFor({state:"detached",timeout:3e4});let n;await Dt.poll(async()=>(n=await t.locator("div[class='permissions-connect']").isVisible().catch(()=>!1),n),{timeout:25e3}).toBe(!0).catch(()=>console.error("Notice dialog did not appear within the timeout period."));let r=t.getByTestId("page-container-footer-next");await r.waitFor({state:"visible",timeout:25e3}),await r.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 wt(t,e){return 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(),await t.getByRole("dialog").locator("div > p[data-testid='account-address']").textContent()}import{expect as Ot}from"@playwright/test";import{errors as Vt}from"@playwright/test";async function R(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}var ft={loadingOverlay:"loading-overlay",loadingSpinner:"spinner loading-overlay__spinner"};var Ut=6e4,$t=async(t,e,o)=>{await R(e);try{await e.locator(`div[class="${t}"]`).waitFor({state:"detached",timeout:o})}catch(a){if(a instanceof Vt.TimeoutError)console.info(`Loading indicator '${t}' not found - continuing.`);else throw console.error(`Error while waiting for loading indicator '${t}' to disappear`),a}},q=async t=>{try{await $t(ft.loadingSpinner,t,Ut)}catch(e){console.warn("Warning during MetaMask load:",e)}return await v(300),t};async function gt(t){if(await t.getByTestId(D.unlockButton).isVisible().catch(()=>!1)){console.info("\u{1F4A1} Wallet is already locked");return}await K(t);let a=t.getByTestId(F.lockButton);await Ot(a).toBeVisible(),await a.click(),await q(t)}import{styleText as At}from"util";import{expect as W}from"@playwright/test";import Bt from"fs";import Gt from"path";import Ht from"path";var ht=".wallet-cache",xt=".wallet-context";var yt="13.22.0",U="https://github.com/amaify/chainwright/releases/download/v0.1.0/",ko=`https://github.com/MetaMask/metamask-extension/releases/download/v${yt}/metamask-chrome-${yt}.zip`,bo=`${U}solflare-wallet-extension-v2.19.1.zip`,To=`${U}petra-wallet-extension-v2.4.8.zip`,Ao=`${U}phantom-wallet-extension-v26.10.0.zip`,Co=`${U}meteor-wallet-extension-v0.7.0.zip`,Po=`${U}keplr-wallet-extension-v0.13.3.zip`;function k(t){return Ht.resolve(process.cwd(),ht,t)}async function X(t){let e=k(t),o=Gt.resolve(e,"password.txt");try{if(!Bt.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return Bt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as zt}from"@playwright/test";async function kt({context:t,path:e,locator:o}){let a;try{await zt.poll(async()=>(a=t.pages().filter(n=>n.url().startsWith("chrome-extension://")).find(n=>n.url().match(e)),!!a),{timeout:3e4}).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 30s. Pages in context: ${JSON.stringify(n)}`)}if(!a)throw new Error(`Popup page with path ${e} not found in context.`);return await Kt(a,o),await a.setViewportSize({width:360,height:592}),a}async function Kt(t,e){await t.waitForLoadState("load",{timeout:4e4}),await t.waitForLoadState("domcontentloaded",{timeout:4e4}),await t.locator(e).first().waitFor({state:"attached",timeout:4e4})}import bt from"fs";import jt from"path";async function Tt(t){let e=k(t),o=jt.resolve(e,"extension-id.txt");try{if(!bt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return bt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var A=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 kt({context:e,path:o,locator:"div[data-testid='multichain-page']"})}};import{expect as Z}from"@playwright/test";async function L({page:t}){await t.locator(`div:has(> button[data-testid='${w.openSettingsButton}'])`).click(),await t.getByTestId(F.networksButton).click();let a=t.locator("section[role='dialog']");await Z(a).toBeVisible(),await Z(a).toContainText(/manage networks/i);let n="div:has(> p:has-text('Show test networks'))";await a.locator(n).scrollIntoViewIfNeeded();let r=a.locator(n);if(!await r.locator("label[class='toggle-button toggle-button--off']").isVisible().catch(()=>!1)){await a.getByRole("button",{name:/close/i}).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 Z(t.getByTestId("Sepolia")).toBeVisible(),await a.getByRole("button",{name:/close/i}).click()}async function tt({page:t,mainAccountName:e,...o}){console.info(At("yellowBright",`
3
+ \u{1F98A} MetaMask onboarding started...`,{validateStream:!1}));let a=await X("metamask"),n=new A,r=t.locator("img[class='loading-spinner']"),i=t.getByTestId(g.createWalletButton),c=t.getByTestId(g.importWalletButton),s=t.getByTestId(g.createNewPasswordInput),u=t.getByTestId(g.confirmNewPasswordInput),d=t.getByTestId(g.confirmPasswordCheckbox),p=t.getByTestId(g.createPasswordButton),f=t.getByTestId(g.metamaskMetricsIAgreeButton),x=t.getByTestId(g.onboardingDoneButton);if(await r.waitFor({state:"detached",timeout:3e4}),o.mode==="create"){let I=t.getByTestId(g.useSecretRecoveryPhraseButton);await i.click(),await I.click(),await s.fill(a),await u.fill(a),await d.click(),await p.click(),await t.getByTestId(g.revealSecretRecoveryPhraseButton).click(),await t.getByTestId(g.recoveryPhraseRemindMeLaterButton).click(),await f.click()}if(o.mode==="import"){let{secretRecoveryPhrase:I}=o,C=I.split(" "),G=t.getByTestId(g.importUsingSecretRecoveryPhraseButton);await c.click(),await G.click();let nt=t.getByTestId(g.secretRecoveryPhraseTextAreaInput);await nt.fill(C[0]),await nt.press("Space");for(let z=1;z<C.length;z++){let rt=t.getByTestId(`import-srp__srp-word-${z}`);await rt.fill(C[z]),await rt.press("Space")}await t.getByTestId(g.importWalletConfirmButton).click(),await s.fill(a),await u.fill(a),await d.click(),await p.click(),await f.click();let Nt=t.getByTestId("wallet-ready");await W(Nt).toContainText(/your wallet is ready/i)}await x.click();let T=`chrome-extension://${await n.extensionId()}/sidepanel.html`,y=await t.context().browser()?.newBrowserCDPSession(),_;await W.poll(async()=>{if(y){let{targetInfos:I}=await y.send("Target.getTargets"),C=I.find(G=>G.url===T);return _=C,!!C}},{timeout:15e3}).toBe(!0),_&&await y?.send("Target.closeTarget",{targetId:_.targetId}),await t.goto(await n.indexUrl()),await r.waitFor({state:"detached",timeout:3e4}),await W(t.getByTestId(w.buyButton)).toBeVisible(),await W(t.getByTestId(w.swapButton)).toBeVisible(),await W(t.getByTestId(w.sendButton)).toBeVisible(),await W(t.getByTestId(w.receiveButton)).toBeVisible(),await L({page:t}),e&&await M({page:t,accountName:e}),await v(5e3),console.info(At("greenBright","\u2728 MetaMask onboarding completed successfully",{validateStream:!1}))}import{expect as qt}from"@playwright/test";async function Ct(t){let e=t.getByTestId(j.cancelButton);await v(1e3),await qt(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(w.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(P.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 y of c)if((await y.textContent())?.includes(e)){s=y;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 d=t.locator(`div[aria-label='${e} options']`);await b(d).toBeVisible(),await d.click(),await b(t.getByRole("tooltip")).toBeVisible();let p=t.locator(`div[aria-label='${P.renameAccountLabel}']`);await b(p).toBeVisible(),await p.click();let f=t.getByRole("dialog"),x=f.getByRole("heading",{name:/rename/i});await b(x).toBeVisible();let S=f.getByRole("textbox");await b(S).toBeVisible(),await S.fill(o);let T=f.getByRole("button",{name:/confirm/i});await b(T).toBeEnabled(),await T.click();for(let y of c)if((await y.textContent())?.includes(o)){await b(y).toBeVisible(),await b(y).toContainText(o);break}await t.locator("button[aria-label='Back']").first().click()}import{expect as et}from"@playwright/test";async function St({page:t,networkType:e,chainName:o}){let a=t.getByTestId(w.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 L({page:t}),await a.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(a).toContainText(o,{timeout:3e4})}import{expect as It}from"@playwright/test";async function $(t){let e=await X("metamask"),o=t.getByTestId(D.passwordInput);if(await t.getByTestId(w.openNetworkSelectorButton).isVisible().catch(()=>!1)){console.info("\u{1F4A1} Wallet is already unlocked");return}await o.fill(e);let r=t.getByTestId(D.unlockButton);await It(r).toBeVisible(),await r.click(),await q(t),await It(t.getByTestId(w.buyButton)).toBeVisible({timeout:3e4})}var E=class extends A{page;constructor(e){super(),this.page=e}async onboard(e){await tt({page:this.page,...e})}async unlock(){await $(this.page)}async lock(){await gt(this.page)}async renameAccount({newAccountName:e,currentAccountName:o}){await Pt({page:this.page,newAccountName:e,currentAccountName:o})}async addAccount({privateKey:e,accountName:o}){await ct({page:this.page,privateKey:e,accountName:o})}async switchAccount({accountName:e}){await M({page:this.page,accountName:e})}async switchNetwork({...e}){await St({page:this.page,...e})}async getAccountAddress(e){return await wt(this.page,e)}async toggleShowTestnetNetwork(){await L({page:this.page})}async addCustomNetwork({chainId:e,currencySymbol:o,networkName:a,rpcUrl:n}){await mt({page:this.page,chainId:e,currencySymbol:o,networkName:a,rpcUrl:n})}async connectToApp(e){await pt(await this.promptPage(this.page.context()),e)}async confirmTransaction(e){await ut(await this.promptPage(this.page.context()),e)}async rejectTransaction(){await Ct(await this.promptPage(this.page.context()))}};import Et from"fs";import te from"path";import{test as ee,chromium as oe}from"@playwright/test";import{Instance as ae,Pool as ne}from"prool";import Xt from"path";async function O(t){return Xt.resolve(process.cwd(),xt,t)}import{expect as Qt}from"@playwright/test";async function ot(t,e){let o=await t.newPage();return await Qt(async()=>{await o.goto(e),await R(o)}).toPass(),o}async function at(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 Jt from"fs/promises";async function vt(t){await Jt.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Yt=35e3;async function Q(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),Yt))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await vt(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}import Rt from"fs";import Zt from"path";async function J(t){try{let e=k(t),o=Zt.resolve(e,"extension-path.txt");if(!Rt.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=Rt.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}`)}}var H,In=({slowMo:t=0,profileName:e}={})=>ee.extend({contextPath:async({browserName:o},a,n)=>{let r=await O(`${o}-${n.testId}`);await a(r)},context:async({context:o,contextPath:a},n)=>{let r=new A,i=k(r.name),c=await J(r.name),s=te.resolve(i,e??"wallet-data");if(!Et.existsSync(s))throw new Error("\u274C Cache for MetaMask wallet data not found. Create it first");Et.cpSync(s,a,{recursive:!0,force:!0});let u=[`--disable-extensions-except=${c}`,`--load-extension=${c}`];process.env.HEADLESS&&(u.push("--headless=new"),t>0&&console.warn("\u26A0\uFE0F Slow motion makes no sense in headless mode. It will be ignored!"));let d=await oe.launchPersistentContext(a,{headless:!1,args:[`--disable-extensions-except=${c}`],slowMo:process.env.HEADLESS?0:t}),{cookies:p,origins:f}=await o.storageState();p&&await d.addCookies(p),f&&f.length>0&&at(f,d);let x=await r.indexUrl();await d.waitForEvent("page",{predicate:B=>B.url().startsWith(x),timeout:3e4}),H=d.pages().find(B=>B.url().startsWith(x))??await ot(d,x);for(let B of d.pages())B.url().includes("about:blank")&&await B.close();await H.locator("img[class='loading-spinner']").waitFor({state:"detached"}),await $(H),await n(d),await Q(d,a)},metamaskPage:async({context:o},a)=>{await a(H)},metamask:async({context:o},a)=>{let n=new E(H);await a(n)},createAnvilNode:async({context:o},a,n)=>{let r=n.workerIndex,i;await a(async c=>{i=ne.define({instance:ae.anvil(c)});let s=await i.start(r),u=`http://${s.host}:${s.port}`,p=c?.chainId??31337;return{rpcUrl:u,anvil:s,chainId:p}}),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 se}from"@playwright/test";import{Instance as ce,Pool as le}from"prool";import _t from"fs";import re from"path";import{chromium as ie}from"@playwright/test";async function Ft({workerInfo:t,profileName:e,slowMo:o}){let a=new A,n=await O(t.workerIndex.toString()),r=k(a.name),i=re.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 J(a.name),s=await ie.launchPersistentContext(n,{headless:!1,args:[`--disable-extensions-except=${c}`],slowMo:process.env.HEADLESS?0:o}),u=await a.indexUrl();await s.waitForEvent("page",{predicate:p=>p.url().startsWith(u),timeout:4e4});let d=s.pages().find(p=>p.url().startsWith(u));d||(d=await s.newPage(),await d.goto(u),await R(d));for(let p of s.pages())p.url().includes("about:blank")&&await p.close();return{context:s,walletPage:d,contextPath:n}}var Kn=({profileName:t,dappUrl:e,slowMo:o}={})=>se.extend({workerScopeContents:[async({browser:a},n,r)=>{let{context:i,contextPath:c,walletPage:s}=await Ft({workerInfo:r,profileName:t,slowMo:o});await i.grantPermissions(["clipboard-read"]);let u=new E(s);await u.unlock(),await n({wallet:u,walletPage:s,context:i}),await Q(i,c)},{scope:"worker"}],dappPage:[async({workerScopeContents:a},n)=>{let{context:r}=a,i=await r.newPage();e&&await i.goto(e),await n(i)},{scope:"worker"}],metamaskPage:async({workerScopeContents:a},n)=>{await n(a.walletPage)},metamask:async({workerScopeContents:a},n)=>{let r=new E(a.walletPage);await n(r)},createAnvilNode:async({context:a},n,r)=>{let i=r.workerIndex,c;await n(async s=>{c=le.define({instance:ce.anvil(s)});let u=await c.start(i),d=`http://${u.host}:${u.port}`,f=s?.chainId??31337;return{rpcUrl:d,anvil:u,chainId:f}}),c&&await c.stop(i)},connectToAnvil:async({context:a,metamask:n,createAnvilNode:r},i)=>{await i(async()=>{let{chainId:c,rpcUrl:s}=await r({chainId:2251});await n.addCustomNetwork({chainId:c,currencySymbol:"ETH",networkName:"Anvil Localnet",rpcUrl:s})})}});export{E as Metamask,In as metamaskFixture,Kn as metamaskWorkerScopeFixture};
@@ -0,0 +1,150 @@
1
+ import * as _playwright_test from '@playwright/test';
2
+ import { BrowserContext, Page } from '@playwright/test';
3
+ import { b as WalletProfileFixtureArgs, c as WorkerScopeFixtureArgs } from '../../types-dJRYhQT9.js';
4
+ import { W as WorkerScopeFixture } from '../../worker-scope-context-ze2hEh79.js';
5
+
6
+ declare class MeteorProfile {
7
+ readonly name: "meteor";
8
+ readonly onboardingPath = "ext_index_popup.html";
9
+ indexUrl(): Promise<string>;
10
+ promptUrl(): Promise<string>;
11
+ extensionId(): Promise<string>;
12
+ promptPage(context: BrowserContext): Promise<_playwright_test.Page>;
13
+ }
14
+
15
+ type MeteorNetwork = "Mainnet" | "Testnet";
16
+ type OnboardingArgs = {
17
+ network: MeteorNetwork;
18
+ privateKey: string;
19
+ accountName: string;
20
+ addWallet?: Array<AddAccountArgs>;
21
+ };
22
+ type RenameAccountArgs = {
23
+ newAccountName: string;
24
+ };
25
+ type AddAccountArgs = {
26
+ privateKey: string;
27
+ accountName: string;
28
+ network: MeteorNetwork;
29
+ };
30
+ type MeteorFixture = {
31
+ contextPath: string;
32
+ meteor: Meteor;
33
+ meteorPage: Page;
34
+ };
35
+
36
+ declare class Meteor extends MeteorProfile {
37
+ page: Page;
38
+ constructor(page: Page);
39
+ /**
40
+ * Onboards the wallet.
41
+ * This function onboards the wallet by entering the password and other required information.
42
+ * @param {OnboardingArgs} args - The arguments required for onboarding.
43
+ * @param args.password - The password for the wallet.
44
+ * @param args.secretRecoveryPhrase - The secret recovery phrase for the wallet when importing a wallet.
45
+ * @example
46
+ * const meteor = new Meteor(page);
47
+ * await meteor.onboard({ mode: "importPrivateKey", password: "password", privateKey: "private key" });
48
+ */
49
+ onboard({ network, privateKey, accountName, addWallet }: OnboardingArgs): Promise<void>;
50
+ /**
51
+ * Unlocks the wallet by entering the password.
52
+ * @example
53
+ * const meteor = new Meteor(page);
54
+ * await meteor.unlock();
55
+ */
56
+ unlock(): Promise<void>;
57
+ /**
58
+ * Locks the wallet by entering the password.
59
+ * This function locks the wallet by opening the settings page and then locking the wallet.
60
+ * @example
61
+ * const meteor = new Meteor(page);
62
+ * await meteor.lock();
63
+ */
64
+ lock(): Promise<void>;
65
+ /**
66
+ * Renames an account in the wallet.
67
+ * @param {Omit<RenameAccount, "page">} args - The arguments to rename the account.
68
+ * @param args.newAccountName - The new name of the account.
69
+ * @example
70
+ * const meteor = new Meteor(page);
71
+ * await meteor.renameAccount({ newAccountName: "New Account Name" });
72
+ */
73
+ renameAccount({ newAccountName }: RenameAccountArgs): Promise<void>;
74
+ /**
75
+ * Switches the current network to the given network.
76
+ * @param {SwitchNetwork} networkName - The name of the network to switch to.
77
+ * @example
78
+ * const meteor = new Meteor(page);
79
+ * await meteor.switchNetwork("network name");
80
+ */
81
+ switchNetwork(network: MeteorNetwork): Promise<void>;
82
+ /**
83
+ * Switches the current account to the given account.
84
+ * @param {string} accountName - The name of the account to switch to.
85
+ * @example
86
+ * const meteor = new meteor(page);
87
+ * await meteor.switchAccount("Account 1");
88
+ */
89
+ switchAccount(accountName: string): Promise<void>;
90
+ /**
91
+ * Retrieves the current account's address.
92
+ * @returns A promise that resolves with the current account's address as a string.
93
+ *
94
+ * @example
95
+ * const meteor = new Meteor(page);
96
+ * const address = await meteor.getAccountAddress();
97
+ */
98
+ getAccountAddress(): Promise<string>;
99
+ /**
100
+ * Adds an account to the wallet via a private key or mnemonic phrase.
101
+ * @param {{ accountName, ...args }: AddAccount} - The arguments to add the account.
102
+ * @param {string} args.accountName - The name of the account to add.
103
+ * @param {string} args.privateKey - The private key of the account to add, if the mode is "privateKey".
104
+ * @param {string[]} args.mnemonicPhrase - The mnemonic phrase of the account to add, if the mode is "mnemonic".
105
+ * @example
106
+ * const meteor = new Meteor(page);
107
+ * await meteor.addAccount(TBD);
108
+ */
109
+ addAccount({ accountName, network, privateKey }: AddAccountArgs): Promise<void>;
110
+ /**
111
+ * Opens the settings page for the wallet.
112
+ * @example
113
+ * const meteor = new Meteor(page);
114
+ * await meteor.openSettings();
115
+ */
116
+ openSettings(): Promise<void>;
117
+ /**
118
+ * Connects to an app by clicking on the "Connect" button.
119
+ * If an account is provided, it will be selected before connecting to the app.
120
+ * @param {string} [account] - The account to select before connecting to the app.
121
+ * @example
122
+ * const meteor = new Meteor(page);
123
+ * await meteor.connectToApp("Account 1");
124
+ */
125
+ connectToApp(account?: string): Promise<void>;
126
+ /**
127
+ * Confirms a transaction in the wallet by clicking on the "Approve" button.
128
+ * This function confirms a transaction in the wallet by clicking on the "Approve" button.
129
+ * It first opens the popup page and then clicks on the "Confirm" button.
130
+ * @example
131
+ * const meteor = new Meteor(page);
132
+ * await meteor.confirmTransaction();
133
+ */
134
+ confirmTransaction(): Promise<void>;
135
+ /**
136
+ * Cancels a transaction in the wallet by clicking on the "Cancel" button.
137
+ * This function cancels a transaction in the wallet by clicking on the "Cancel" button.
138
+ * It first opens the popup page and then clicks on the "Cancel" button.
139
+ * @example
140
+ * const meteor = new Meteor(page);
141
+ * await meteor.cancelTransaction();
142
+ */
143
+ rejectTransaction(): Promise<void>;
144
+ }
145
+
146
+ declare const meteorFixture: ({ slowMo, profileName }?: WalletProfileFixtureArgs) => _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & MeteorFixture, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions>;
147
+
148
+ declare const meteorWorkerScopeFixture: ({ slowMo, profileName, dappUrl }?: WorkerScopeFixtureArgs) => _playwright_test.TestType<_playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & MeteorFixture, _playwright_test.PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions & WorkerScopeFixture<Meteor>>;
149
+
150
+ export { Meteor, meteorFixture, meteorWorkerScopeFixture };