From 1cd667cf54bef37cf5ce780cf141fab558f35c97 Mon Sep 17 00:00:00 2001 From: Ugwuanyi TobeChukwu <39131739+amaify@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:47:33 +0100 Subject: [PATCH 1/5] [Solflare] - Close the "what's new" modal during onboarding --- src/wallets/phantom/actions/onboard.phantom.ts | 7 +++---- src/wallets/phantom/phantom-fixture.ts | 7 +++---- src/wallets/phantom/utils.ts | 18 ++++-------------- .../solflare/actions/onboard.solflare.ts | 7 +++++++ src/wallets/solflare/solflare-fixture.ts | 7 +++---- src/wallets/solflare/utils.ts | 15 +++------------ 6 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/wallets/phantom/actions/onboard.phantom.ts b/src/wallets/phantom/actions/onboard.phantom.ts index 98d0e69..52f3448 100644 --- a/src/wallets/phantom/actions/onboard.phantom.ts +++ b/src/wallets/phantom/actions/onboard.phantom.ts @@ -199,14 +199,13 @@ export default async function onboard({ page, additionalAccounts, ...args }: Onb } if (additionalAccounts && additionalAccounts.length > 0) { - let cancelled = false; - const isCancelled = () => cancelled; + const autoCloseController = new AbortController(); - autoClosePhantomNotification(newPage, isCancelled).catch((error) => console.error({ error })); + autoClosePhantomNotification(newPage, autoCloseController.signal).catch((error) => console.error({ error })); for (const { accountName, chain, privateKey } of additionalAccounts) { await addAccount({ page: newPage, privateKey, accountName, chain }); - cancelled = true; + autoCloseController.abort(); } await switchAccount(newPage, args.accountName); diff --git a/src/wallets/phantom/phantom-fixture.ts b/src/wallets/phantom/phantom-fixture.ts index 13096da..45d265f 100644 --- a/src/wallets/phantom/phantom-fixture.ts +++ b/src/wallets/phantom/phantom-fixture.ts @@ -77,13 +77,12 @@ export const phantomFixture = ({ slowMo = 0, profileName }: WalletProfileFixture }, autoCloseNotification: [ async ({ context: _ }, use) => { - let cancelled = false; - const isCancelled = () => cancelled; - const runner = autoClosePhantomNotification(_phantomPage, isCancelled); + const autoCloseController = new AbortController(); + const runner = autoClosePhantomNotification(_phantomPage, autoCloseController.signal); await use(undefined); - cancelled = true; + autoCloseController.abort(); await runner.catch((error) => { console.error(`Auto close notification error: ${(error as Error).message}`); }); diff --git a/src/wallets/phantom/utils.ts b/src/wallets/phantom/utils.ts index 01d1240..e7404a7 100644 --- a/src/wallets/phantom/utils.ts +++ b/src/wallets/phantom/utils.ts @@ -1,33 +1,23 @@ import type { Page } from "@playwright/test"; import { sleep } from "@/utils/sleep"; -export async function autoClosePhantomNotification(page: Page, isCancelled: () => boolean) { +export async function autoClosePhantomNotification(page: Page, signal: AbortSignal) { const INTERVAL = 300; - let IS_POLLING_COMPLETE = false; - - while (!isCancelled()) { - const _isCancelled = isCancelled(); - - // Check if notification is closed - // If it's closed or cancelled, there's no need to check again - if (_isCancelled || IS_POLLING_COMPLETE || page.isClosed()) break; + while (!signal.aborted && !page.isClosed()) { try { const notificationPopupBackButton = page.locator("div[id='modal']").locator("div > svg").first(); const isNotificationButtonVisible = await notificationPopupBackButton.isVisible().catch(() => false); if (isNotificationButtonVisible) { await notificationPopupBackButton.click(); - IS_POLLING_COMPLETE = true; + return; } } catch (error) { - if (page.isClosed()) break; console.error("[autoClosePhantomNotification]: ", error); + if (page.isClosed()) return; } - // Check if polling is complete - if (_isCancelled || IS_POLLING_COMPLETE || page.isClosed()) break; - await sleep(INTERVAL); } } diff --git a/src/wallets/solflare/actions/onboard.solflare.ts b/src/wallets/solflare/actions/onboard.solflare.ts index db8cb40..9d50943 100644 --- a/src/wallets/solflare/actions/onboard.solflare.ts +++ b/src/wallets/solflare/actions/onboard.solflare.ts @@ -3,6 +3,7 @@ import type { Page } from "@playwright/test"; import { getWalletPasswordFromCache } from "@/utils/wallets/get-wallet-password-from-cache"; import { onboardingSelectors } from "../selectors/onboard-selectors.solflare"; import type { OnboardingArgs } from "../types"; +import { autoCloseSolflareNotification } from "../utils"; import { addAccount } from "./add-account.solflare"; import { renameAccount } from "./rename-account.solflare"; import { switchNetwork } from "./switch-network.solflare"; @@ -42,6 +43,10 @@ export async function onboard({ page, recoveryPhrase, network, walletName, addit const IAgreeButton = page.getByTestId(onboardingSelectors.IAgreeButton); await IAgreeButton.click(); + const autoCloseController = new AbortController(); + + autoCloseSolflareNotification(page, autoCloseController.signal).catch((error) => console.error({ error })); + if (walletName) { // "Main Wallet" is the default wallet name for the fist wallet in Solflare. await renameAccount({ page, currentAccountName: "Main Wallet", newAccountName: walletName }); @@ -56,5 +61,7 @@ export async function onboard({ page, recoveryPhrase, network, walletName, addit } } + autoCloseController.abort(); + console.info(styleText("greenBright", "✨ Solflare onboarding completed successfully", { validateStream: false })); } diff --git a/src/wallets/solflare/solflare-fixture.ts b/src/wallets/solflare/solflare-fixture.ts index df65257..f4dd93f 100644 --- a/src/wallets/solflare/solflare-fixture.ts +++ b/src/wallets/solflare/solflare-fixture.ts @@ -81,13 +81,12 @@ export const solflareFixture = ({ slowMo = 0, profileName }: WalletProfileFixtur }, autoCloseNotification: [ async ({ context: _ }, use) => { - let cancelled = false; - const isCancelled = () => cancelled; - const runner = autoCloseSolflareNotification(_solflarePage, isCancelled); + const autoCloseController = new AbortController(); + const runner = autoCloseSolflareNotification(_solflarePage, autoCloseController.signal); await use(undefined); - cancelled = true; + autoCloseController.abort(); await runner.catch((error) => { console.error(`Auto close notification error: ${(error as Error).message}`); }); diff --git a/src/wallets/solflare/utils.ts b/src/wallets/solflare/utils.ts index ae6822e..ed94685 100644 --- a/src/wallets/solflare/utils.ts +++ b/src/wallets/solflare/utils.ts @@ -1,17 +1,10 @@ import type { Page } from "@playwright/test"; import { sleep } from "@/utils/sleep"; -export async function autoCloseSolflareNotification(page: Page, isCancelled: () => boolean) { +export async function autoCloseSolflareNotification(page: Page, signal: AbortSignal) { const INTERVAL = 150; - let IS_POLLING_COMPLETE = false; - - while (!isCancelled()) { - const _isCancelled = isCancelled(); - - // Check if notification is closed - // If it's closed or cancelled, there's no need to check again - if (_isCancelled || IS_POLLING_COMPLETE || page.isClosed()) break; + while (!signal.aborted && !page.isClosed()) { try { const notificationPopupCloseButton = page .locator("div[role='dialog']") @@ -23,14 +16,12 @@ export async function autoCloseSolflareNotification(page: Page, isCancelled: () if (isNotificationPopupCloseButtonVisisble) { await notificationPopupCloseButton.click(); - IS_POLLING_COMPLETE = true; + return; } } catch (error) { console.error("[autoCloseSolflareNotification]: ", error); } - if (_isCancelled || IS_POLLING_COMPLETE || page.isClosed()) break; - await sleep(INTERVAL); } } From 89ae08fa51c5ad797f5e1fca4a683b9e36b65216 Mon Sep 17 00:00:00 2001 From: Ugwuanyi TobeChukwu <39131739+amaify@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:50:19 +0100 Subject: [PATCH 2/5] Prepare for publishing --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6309089..f832a38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @tobelabs/chainwright +## 0.10.10 + +### Patch Changes + +- [Solflare] - Close the "What's new" modal popup during onboarding + ## 0.10.9 ### Patch Changes diff --git a/package.json b/package.json index da30ba9..502f3bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chainwright", - "version": "0.10.9", + "version": "0.10.10", "description": "Playwright Web3 wallet testing framework for end-to-end dApp automation with MetaMask, Phantom, Solflare, Petra, Meteor, and Keplr", "type": "module", "license": "MIT", From 511f2d61ed00fa39105b2842616dfa7b8ee64765 Mon Sep 17 00:00:00 2001 From: Ugwuanyi TobeChukwu <39131739+amaify@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:08:49 +0100 Subject: [PATCH 3/5] [Linting] - Fix linting that breaks deployment --- .github/workflows/linting-and-unit-tests.yaml | 2 +- package.json | 2 ++ src/wallets/phantom/phantom-worker-scope-fixture.ts | 7 +++---- src/wallets/solflare/solflare-worker-scope-fixture.ts | 10 ++++++---- tsconfig.json | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/linting-and-unit-tests.yaml b/.github/workflows/linting-and-unit-tests.yaml index f1c651c..41866d2 100644 --- a/.github/workflows/linting-and-unit-tests.yaml +++ b/.github/workflows/linting-and-unit-tests.yaml @@ -31,7 +31,7 @@ jobs: run: pnpm install --no-frozen-lockfile - name: Run Linting - run: pnpm run lint + run: pnpm run check:ci - name: Run unit tests run: pnpm run tests diff --git a/package.json b/package.json index 502f3bc..7bb3832 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,8 @@ "setup-wallets": "tsx src/cli/index.ts ./tests/wallet-setup --all -f", "tests": "vitest --exclude '**/*.spec.ts'", "lint": "biome check ./src", + "check:types": "tsc", + "check:ci": "pnpm run lint && pnpm run check:types", "format": "biome format --write ./src", "tests:e2e:debug": "playwright test --config=tests/playwright.config.ts --debug", "tests:e2e:ui": "playwright test --config=tests/playwright.config.ts --ui", diff --git a/src/wallets/phantom/phantom-worker-scope-fixture.ts b/src/wallets/phantom/phantom-worker-scope-fixture.ts index db2840a..6733366 100644 --- a/src/wallets/phantom/phantom-worker-scope-fixture.ts +++ b/src/wallets/phantom/phantom-worker-scope-fixture.ts @@ -37,13 +37,12 @@ export const phantomWorkerScopeFixture = ({ slowMo, profileName }: WalletProfile ], autoCloseNotification: [ async ({ workerScopeContents }, use) => { - let cancelled = false; - const isCancelled = () => cancelled; - const runner = autoClosePhantomNotification(workerScopeContents.walletPage, isCancelled); + const autoCloseController = new AbortController(); + const runner = autoClosePhantomNotification(workerScopeContents.walletPage, autoCloseController.signal); await use(undefined); - cancelled = true; + autoCloseController.abort(); await runner.catch((error) => { console.error(`Auto close notification error: ${(error as Error).message}`); }); diff --git a/src/wallets/solflare/solflare-worker-scope-fixture.ts b/src/wallets/solflare/solflare-worker-scope-fixture.ts index 9db98e6..e642860 100644 --- a/src/wallets/solflare/solflare-worker-scope-fixture.ts +++ b/src/wallets/solflare/solflare-worker-scope-fixture.ts @@ -31,13 +31,15 @@ export const solflareWorkerScopeFixture = ({ slowMo, profileName }: WalletProfil ], autoCloseNotification: [ async ({ workerScopeContents }, use) => { - let cancelled = false; - const isCancelled = () => cancelled; - const runner = autoCloseSolflareNotification(workerScopeContents.walletPage, isCancelled); + const autoCloseController = new AbortController(); + const runner = autoCloseSolflareNotification( + workerScopeContents.walletPage, + autoCloseController.signal, + ); await use(undefined); - cancelled = true; + autoCloseController.abort(); await runner.catch((error) => { console.error(`Auto close notification error: ${(error as Error).message}`); }); diff --git a/tsconfig.json b/tsconfig.json index 2aedf3a..b950d3c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "@/tests/*": ["./tests/*"] } }, - "include": ["**/*.ts", "**/*.tsx"], + "include": ["src/**/*.ts", "tests/**/*.ts", "environment.d.ts"], "exclude": ["node_modules", "docs"] } From a571307969b71a40c1c626cc32ac1de8515efc41 Mon Sep 17 00:00:00 2001 From: Ugwuanyi TobeChukwu <39131739+amaify@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:09:44 +0100 Subject: [PATCH 4/5] chore: run the changeset command --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f832a38..c680ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @tobelabs/chainwright +## 0.10.11 + +### Patch Changes + +- [Linting] - Fix linting that breaks deployment + ## 0.10.10 ### Patch Changes diff --git a/package.json b/package.json index 7bb3832..bdc0aea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chainwright", - "version": "0.10.10", + "version": "0.10.11", "description": "Playwright Web3 wallet testing framework for end-to-end dApp automation with MetaMask, Phantom, Solflare, Petra, Meteor, and Keplr", "type": "module", "license": "MIT", From 731fcbf2cd2018c0db9ff3a680cb6095ca0a73a7 Mon Sep 17 00:00:00 2001 From: amaify2 Date: Wed, 22 Jul 2026 13:23:28 +0100 Subject: [PATCH 5/5] build: Rebuild dist after syncing upstream Co-Authored-By: Claude Fable 5 --- dist/wallets/phantom/index.js | 8 ++++---- dist/wallets/solflare/index.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dist/wallets/phantom/index.js b/dist/wallets/phantom/index.js index e8f4516..7f289ae 100644 --- a/dist/wallets/phantom/index.js +++ b/dist/wallets/phantom/index.js @@ -1,4 +1,4 @@ -import rt from"fs";import It from"path";import{chromium as vt}from"@playwright/test";import bt from"path";var tt=".wallet-cache",et=".wallet-context";var ot="13.33.0",at="0.13.39",O="https://github.com/amaify/chainwright/releases/download/v0.1.0/",At=`https://github.com/chainapsis/keplr-wallet/releases/download/v${at}/`,Ct=`https://github.com/MetaMask/metamask-extension/releases/download/v${ot}/`,qt=`${Ct}metamask-chrome-${ot}.zip`,Qt=`${O}solflare-wallet-extension-v2.19.1.zip`,Yt=`${O}petra-wallet-extension-v2.4.8.zip`,Zt=`${O}phantom-wallet-extension-v26.10.0.zip`,te=`${O}meteor-wallet-extension-v0.7.0.zip`,ee=`${At}keplr-extension-manifest-v3-v${at}.zip`;async function T(t){return bt.resolve(process.cwd(),et,t)}import Tt from"path";function x(t){return Tt.resolve(process.cwd(),tt,t)}import nt from"fs";import St from"path";async function S(t){try{let e=x(t),o=St.resolve(e,"extension-path.txt");if(!nt.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=nt.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 Te({wallet:t,workerInfo:e,profileName:o,slowMo:a}){let n=await T(e.workerIndex.toString()),r=x(t.name),l=It.resolve(r,o??"wallet-data");if(!rt.existsSync(l))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);rt.cpSync(l,n,{recursive:!0,force:!0});let s=await S(t.name),p=I(s,a??0),u=await vt.launchPersistentContext(n,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:a}),f=await t.indexUrl(),d=u.pages()[0];return d||(d=await u.newPage()),await d.goto(f),{context:u,walletPage:d,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"},it={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 s=t.locator("span[id^='button--listbox-input--']"),p=await s.textContent(),u=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 u.fill(o),await f.fill(e),await t.locator("button:has-text('Import')").click()}var v={confirmButton:"primary-button",cancelButton:"secondary-button"};async function ct(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 Wt}from"@playwright/test";import Et from"zod";async function E(t,e){let o=Et.string().min(1,"Account name cannot be an empty string").parse(e);await t.getByTestId(g.openMenuButton).click();let n=null,r=await t.locator("div[data-testid='account-menu'] div[data-testid='tooltip_interactive-wrapper']").all();for(let l of r)if((await l.textContent())?.includes(o)){n=l;break}if(!n)throw new Error(`Account with name "${o}" not found in the account list.`);await n.click()}async function st(t,e){e&&await E(t,e);let o=t.getByTestId(v.confirmButton);await Wt(o).toBeEnabled({timeout:15e3}),await o.click()}import _t from"zod";var Nt=t=>t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function lt({page:t,accountName:e,chain:o}){let a=_t.string().min(1,"Account name cannot be an empty string").parse(e);await t.getByTestId(g.openMenuButton).click(),await t.getByTestId(g.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 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 h=t.getByTestId(B.closeMenuButton);await h.waitFor({state:"visible",timeout:15e3}),await h.click()}else{await s.click();let d=new RegExp(`${Nt(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 ut(t){await t.getByTestId(g.openMenuButton).click(),await t.getByTestId(g.settingsButton).click(),await t.getByTestId(B.lockWalletButton).click()}import{styleText as ft}from"util";import{expect as Rt}from"@playwright/test";function D(t){return new Promise(e=>setTimeout(e,t))}import mt from"fs";import Ft from"path";async function $(t){let e=x(t),o=Ft.resolve(e,"password.txt");try{if(!mt.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return mt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Lt}from"@playwright/test";async function pt({context:t,path:e,locator:o}){let a;try{await Lt.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 Mt(a,o),await a.setViewportSize({width:360,height:592}),a}async function Mt(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 dt from"fs";import Ot from"path";async function wt(t){let e=x(t),o=Ot.resolve(e,"extension-id.txt");try{if(!dt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return dt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} extension ID from cache: ${a.message}`)}}var A=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 pt({context:e,path:o,locator:"div[id='root']"})}};async function W(t,e){let a=!1;for(;!e();){let n=e();if(n||a||t.isClosed())break;try{let r=t.locator("div[id='modal']").locator("div > svg").first();await r.isVisible().catch(()=>!1)&&(await r.click(),a=!0)}catch(r){if(t.isClosed())break;console.error("[autoClosePhantomNotification]: ",r)}if(n||a||t.isClosed())break;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 l=await t.getByTestId(it.accountProfileContainer).locator("div[data-testid^='manage-accounts-sortable'] div > p").all(),s=null;for(let w of l)if((await w.textContent())?.toLowerCase()===e.toLowerCase()){s=w;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 u=t.locator("input[name='name']");await u.clear(),await u.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: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 z({page:t,additionalAccounts:e,...o}){console.info(ft("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 w=t.getByTestId(m.passwordInput),y=t.getByTestId(m.passwordConfirmInput),P=t.getByTestId(m.termsCheckBox),k=t.locator(m.continueButton);await w.fill(a),await y.fill(a),await P.click(),await k.click(),await k.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.getByTestId(m.recoveryPhraseSavedCheckbox).click(),await k.click(),await D(1e3),await k.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[F,Z]of Object.entries(h))await t.getByTestId(`${m.recoveryPhraseInput}-${F}`).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),M=t.getByTestId(m.passwordConfirmInput),J=t.getByTestId(m.termsCheckBox);await b.fill(a),await M.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:w,accountName:y}=o;await t.locator(m.importPrivateKeyButton).click();let k=t.locator("span[id='button--listbox-input--1']"),C=await k.textContent(),b=t.locator("input[name='name']"),M=t.locator("textarea[name='privateKey']");C!==w&&(await k.click(),await t.locator("ul[id='listbox--listbox-input--1']").locator(`li[data-label='${w}']`).click()),await b.fill(y),await M.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 F=t.locator(m.continueButton);await F.click(),await F.locator("> div > svg").waitFor({state:"detached",timeout:3e4}),await t.locator(m.getStartedButton).last().click()}let n=await t.context().newPage(),r=await new A().indexUrl();await n.goto(r);let s=await t.context().browser()?.newBrowserCDPSession(),p;await Rt.poll(async()=>{if(s){let{targetInfos:d}=await s.send("Target.getTargets"),w=d.filter(y=>y.title==="Phantom Wallet").find(y=>!y.attached&&y.url===r);return p=w,!!w}},{timeout:2e4}).toBe(!0),p&&await s?.send("Target.closeTarget",{targetId:p.targetId});let u=await n.getByTestId("home-header-account-name").textContent();if(!u)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:u})}if(e&&e.length>0){let d=!1;W(n,()=>d).catch(w=>console.error({error:w}));for(let{accountName:w,chain:y,privateKey:P}of e)await R({page:n,privateKey:P,accountName:w,chain:y}),d=!0;await E(n,o.accountName)}o.toggleNetworkMode&&await V({page:n,...o.toggleNetworkMode}),console.info(ft("greenBright","\u2728 Phantom onboarding completed successfully",{validateStream:!1}))}async function gt(t){let e=t.getByTestId(v.cancelButton);await t.getByTestId("approve-transaction").waitFor({state:"attached"}),await e.click()}async function ht({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 l of e){let s=t.locator(`button[id='toggle-${l.toLowerCase()}']`),u=await s.locator(`label[data-testid='toggle-${l.toLowerCase()}-switch'] > input[aria-label='Toggle']`).isChecked().catch(()=>!1);o==="off"&&u&&await s.click(),o==="on"&&!u&&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 _=class extends A{page;constructor(e){super(),this.page=e}async onboard({...e}){await z({page:this.page,...e})}async unlock(){await K(this.page)}async lock(){await ut(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 lt({page:this.page,accountName:e,chain:o})}async addAccount({...e}){await R({page:this.page,...e})}async toggleOptionalChains({toggleMode:e,supportedChains:o}){await ht({page:this.page,supportedChains:o,toggleMode:e})}async switchNetwork({...e}){await V({page:this.page,...e})}async connectToApp(e){await st(await this.promptPage(this.page.context()),e)}async confirmTransaction(){await ct(await this.promptPage(this.page.context()))}async rejectTransaction(){await gt(await this.promptPage(this.page.context()))}};import xt from"fs";import Ht from"path";import{test as Vt,chromium as Kt}from"@playwright/test";import{expect as Dt}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 Dt(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(l=>{l.forEach(({name:s,value:p})=>{window.localStorage.setItem(s,p)})},n)}await o.close()}import $t from"fs/promises";async function yt(t){await $t.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Ut=35e3;async function G(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),Ut))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await yt(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var N,Ja=({slowMo:t=0,profileName:e}={})=>Vt.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 A,l=x(r.name),s=await S(r.name),p=Ht.resolve(l,e??"wallet-data");if(!xt.existsSync(p))throw new Error("\u274C Cache for Phantom wallet data not found. Create it first");xt.cpSync(p,a,{recursive:!0,force:!0});let u=I(s,t),f=await Kt.launchPersistentContext(a,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:t});await f.grantPermissions(["clipboard-read"]);let{cookies:d,origins:h}=await o.storageState();d&&await f.addCookies(d),h&&h.length>0&&await X(h,f);let w=await r.indexUrl();N=f.pages().find(P=>P.url().startsWith(w))||await L(f,w);for(let P of f.pages())P.url().includes("about:blank")&&await P.close();await N.bringToFront(),await K(N),await n(f),await G(f,a)},phantomPage:async({context:o},a)=>{await a(N)},phantom:async({context:o},a)=>{let n=new _(N);await a(n)},autoCloseNotification:[async({context:o},a)=>{let n=!1,l=W(N,()=>n);await a(void 0),n=!0,await l.catch(s=>{console.error(`Auto close notification error: ${s.message}`)})},{auto:!0}]});import{test as jt}from"@playwright/test";import Bt from"fs";import Gt from"path";import{chromium as zt}from"@playwright/test";async function Pt({workerInfo:t,profileName:e,slowMo:o}){let a=new A,n=await T(t.workerIndex.toString()),r=x(a.name),l=Gt.resolve(r,e??"wallet-data");if(!Bt.existsSync(l))throw new Error(`Cache for ${a.name} does not exist. Create it first!`);Bt.cpSync(l,n,{recursive:!0,force:!0});let s=await S(a.name),p=I(s,o??0),u=await zt.launchPersistentContext(n,{headless:!1,args:p,slowMo:process.env.HEADLESS?0:o}),f=await a.indexUrl(),d=await L(u,f);return{context:u,walletPage:d,contextPath:n}}var fn=({slowMo:t,profileName:e}={})=>jt.extend({workerScopeContents:[async({browser:o},a,n)=>{let{context:r,contextPath:l,walletPage:s}=await Pt({workerInfo:n,profileName:e,slowMo:t});await r.grantPermissions(["clipboard-read"]);for(let u of r.pages())u.url().includes("about:blank")&&await u.close();let p=new _(s);await p.unlock(),await a({wallet:p,walletPage:s,context:r}),await G(r,l)},{scope:"worker"}],autoCloseNotification:[async({workerScopeContents:o},a)=>{let n=!1,r=()=>n,l=W(o.walletPage,r);await a(void 0),n=!0,await l.catch(s=>{console.error(`Auto close notification error: ${s.message}`)})},{auto:!0}]});export{_ as Phantom,Ja as phantomFixture,fn as phantomWorkerScopeFixture,Te as workerScopeContext}; +import rt from"fs";import It from"path";import{chromium as vt}from"@playwright/test";import bt from"path";var tt=".wallet-cache",et=".wallet-context";var ot="13.33.0",at="0.13.39",R="https://github.com/amaify/chainwright/releases/download/v0.1.0/",At=`https://github.com/chainapsis/keplr-wallet/releases/download/v${at}/`,Ct=`https://github.com/MetaMask/metamask-extension/releases/download/v${ot}/`,qt=`${Ct}metamask-chrome-${ot}.zip`,Qt=`${R}solflare-wallet-extension-v2.19.1.zip`,Yt=`${R}petra-wallet-extension-v2.4.8.zip`,Zt=`${R}phantom-wallet-extension-v26.10.0.zip`,te=`${R}meteor-wallet-extension-v0.7.0.zip`,ee=`${At}keplr-extension-manifest-v3-v${at}.zip`;async function T(t){return bt.resolve(process.cwd(),et,t)}import Tt from"path";function x(t){return Tt.resolve(process.cwd(),tt,t)}import nt from"fs";import St from"path";async function S(t){try{let e=x(t),o=St.resolve(e,"extension-path.txt");if(!nt.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let a=nt.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 Te({wallet:t,workerInfo:e,profileName:o,slowMo:a}){let n=await T(e.workerIndex.toString()),r=x(t.name),u=It.resolve(r,o??"wallet-data");if(!rt.existsSync(u))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);rt.cpSync(u,n,{recursive:!0,force:!0});let s=await S(t.name),p=I(s,a??0),l=await vt.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"},it={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 O({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 ct(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 Wt}from"@playwright/test";import Et from"zod";async function E(t,e){let o=Et.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 st(t,e){e&&await E(t,e);let o=t.getByTestId(v.confirmButton);await Wt(o).toBeEnabled({timeout:15e3}),await o.click()}import _t from"zod";var Ft=t=>t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");async function lt({page:t,accountName:e,chain:o}){let a=_t.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(`${Ft(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 ut(t){await t.getByTestId(h.openMenuButton).click(),await t.getByTestId(h.settingsButton).click(),await t.getByTestId(B.lockWalletButton).click()}import{styleText as ft}from"util";import{expect as Ot}from"@playwright/test";function D(t){return new Promise(e=>setTimeout(e,t))}import mt from"fs";import Nt from"path";async function $(t){let e=x(t),o=Nt.resolve(e,"password.txt");try{if(!mt.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return mt.readFileSync(o,"utf-8")}catch(a){throw new Error(`\u274C Failed to get ${t} password from cache: ${a.message}`)}}import{expect as Lt}from"@playwright/test";async function pt({context:t,path:e,locator:o}){let a;try{await Lt.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 Mt(a,o),await a.setViewportSize({width:360,height:592}),a}async function Mt(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 dt from"fs";import Rt from"path";async function wt(t){let e=x(t),o=Rt.resolve(e,"extension-id.txt");try{if(!dt.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return dt.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 pt({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(it.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(ft("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[N,Z]of Object.entries(w))await t.getByTestId(`${m.recoveryPhraseInput}-${N}`).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),M=t.getByTestId(m.passwordConfirmInput),J=t.getByTestId(m.termsCheckBox);await b.fill(a),await M.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']"),M=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 M.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 N=t.locator(m.continueButton);await N.click(),await N.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 Ot.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 O({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(ft("greenBright","\u2728 Phantom onboarding completed successfully",{validateStream:!1}))}async function gt(t){let e=t.getByTestId(v.cancelButton);await t.getByTestId("approve-transaction").waitFor({state:"attached"}),await e.click()}async function ht({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 _=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 ut(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 lt({page:this.page,accountName:e,chain:o})}async addAccount({...e}){await O({page:this.page,...e})}async toggleOptionalChains({toggleMode:e,supportedChains:o}){await ht({page:this.page,supportedChains:o,toggleMode:e})}async switchNetwork({...e}){await V({page:this.page,...e})}async connectToApp(e){await st(await this.promptPage(this.page.context()),e)}async confirmTransaction(){await ct(await this.promptPage(this.page.context()))}async rejectTransaction(){await gt(await this.promptPage(this.page.context()))}};import xt from"fs";import Ht from"path";import{test as Vt,chromium as Kt}from"@playwright/test";import{expect as Dt}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 Dt(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 $t from"fs/promises";async function yt(t){await $t.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var Ut=35e3;async function z(t,e){try{await Promise.race([t.close(),new Promise((o,a)=>setTimeout(()=>a(new Error("Context close timed out")),Ut))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await yt(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}var F,Ja=({slowMo:t=0,profileName:e}={})=>Vt.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=Ht.resolve(u,e??"wallet-data");if(!xt.existsSync(p))throw new Error("\u274C Cache for Phantom wallet data not found. Create it first");xt.cpSync(p,a,{recursive:!0,force:!0});let l=I(s,t),f=await Kt.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();F=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 F.bringToFront(),await K(F),await n(f),await z(f,a)},phantomPage:async({context:o},a)=>{await a(F)},phantom:async({context:o},a)=>{let n=new _(F);await a(n)},autoCloseNotification:[async({context:o},a)=>{let n=new AbortController,r=W(F,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 jt}from"@playwright/test";import Bt from"fs";import zt from"path";import{chromium as Gt}from"@playwright/test";async function Pt({workerInfo:t,profileName:e,slowMo:o}){let a=new k,n=await T(t.workerIndex.toString()),r=x(a.name),u=zt.resolve(r,e??"wallet-data");if(!Bt.existsSync(u))throw new Error(`Cache for ${a.name} does not exist. Create it first!`);Bt.cpSync(u,n,{recursive:!0,force:!0});let s=await S(a.name),p=I(s,o??0),l=await Gt.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 fn=({slowMo:t,profileName:e}={})=>jt.extend({workerScopeContents:[async({browser:o},a,n)=>{let{context:r,contextPath:u,walletPage:s}=await Pt({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 _(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{_ as Phantom,Ja as phantomFixture,fn as phantomWorkerScopeFixture,Te as workerScopeContext}; diff --git a/dist/wallets/solflare/index.js b/dist/wallets/solflare/index.js index 5df21f3..baa578e 100644 --- a/dist/wallets/solflare/index.js +++ b/dist/wallets/solflare/index.js @@ -1,7 +1,7 @@ -import X from"fs";import ht from"path";import{chromium as Pt}from"@playwright/test";import gt from"path";var H=".wallet-cache",j=".wallet-context";var z="13.33.0",q="0.13.39",E="https://github.com/amaify/chainwright/releases/download/v0.1.0/",ft=`https://github.com/chainapsis/keplr-wallet/releases/download/v${q}/`,dt=`https://github.com/MetaMask/metamask-extension/releases/download/v${z}/`,Mt=`${dt}metamask-chrome-${z}.zip`,Dt=`${E}solflare-wallet-extension-v2.19.1.zip`,Lt=`${E}petra-wallet-extension-v2.4.8.zip`,Rt=`${E}phantom-wallet-extension-v26.10.0.zip`,Ot=`${E}meteor-wallet-extension-v0.7.0.zip`,$t=`${ft}keplr-extension-manifest-v3-v${q}.zip`;async function P(t){return gt.resolve(process.cwd(),j,t)}import yt from"path";function w(t){return yt.resolve(process.cwd(),H,t)}import G from"fs";import xt from"path";async function A(t){try{let e=w(t),o=xt.resolve(e,"extension-path.txt");if(!G.existsSync(o))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let r=G.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 B(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 me({wallet:t,workerInfo:e,profileName:o,slowMo:r}){let a=await P(e.workerIndex.toString()),n=w(t.name),l=ht.resolve(n,o??"wallet-data");if(!X.existsSync(l))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);X.cpSync(l,a,{recursive:!0,force:!0});let c=await A(t.name),p=B(c,r??0),u=await Pt.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 S={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 U from"zod";var J=U.object({walletName:U.string().min(1,"Wallet name cannot be an empty string"),privateKey:U.string().min(1,"Private key cannot be an empty string")});async function v({page:t,privateKey:e,walletName:o}){let r=J.parse({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"),p=t.getByTestId("input-private-key");await c.fill(r.walletName),await p.fill(`${r.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 St}from"@playwright/test";var W={approveButton:"btn-approve",rejectButton:"btn-reject"};async function Q(t){let e=t.getByTestId(W.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 J from"fs";import ht from"path";import{chromium as Pt}from"@playwright/test";import gt from"path";var j=".wallet-cache",z=".wallet-context";var q="13.33.0",X="0.13.39",v="https://github.com/amaify/chainwright/releases/download/v0.1.0/",ft=`https://github.com/chainapsis/keplr-wallet/releases/download/v${X}/`,dt=`https://github.com/MetaMask/metamask-extension/releases/download/v${q}/`,Dt=`${dt}metamask-chrome-${q}.zip`,Lt=`${v}solflare-wallet-extension-v2.19.1.zip`,Rt=`${v}petra-wallet-extension-v2.4.8.zip`,Ot=`${v}phantom-wallet-extension-v26.10.0.zip`,$t=`${v}meteor-wallet-extension-v0.7.0.zip`,Ut=`${ft}keplr-extension-manifest-v3-v${X}.zip`;async function P(t){return gt.resolve(process.cwd(),z,t)}import yt from"path";function w(t){return yt.resolve(process.cwd(),j,t)}import G from"fs";import xt from"path";async function S(t){try{let e=w(t),r=xt.resolve(e,"extension-path.txt");if(!G.existsSync(r))throw new Error("\u274C extension-path.txt not found. Run setup script first.");let o=G.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 ue({wallet:t,workerInfo:e,profileName:r,slowMo:o}){let a=await P(e.workerIndex.toString()),s=w(t.name),c=ht.resolve(s,r??"wallet-data");if(!J.existsSync(c))throw new Error(`Cache for ${t.name} does not exist. Create it first!`);J.cpSync(c,a,{recursive:!0,force:!0});let p=await S(t.name),l=B(p,o??0),u=await Pt.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 Q=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=Q.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 At}from"@playwright/test";var _={approveButton:"btn-approve",rejectButton:"btn-reject"};async function Y(t){let e=t.getByTestId(_.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 n=t.locator("div[data-id='control-label']");await n.isVisible().catch(()=>!1)&&await n.click(),await St(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 Y(t,e){e&&await _(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 N(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 N(t),await t.getByTestId(S.securityAndPrivacyButton).click(),await t.getByTestId("li-settings-lock").getByTestId(S.lockButton).click()}import{styleText as ot}from"util";import et from"fs";import At from"path";async function F(t){let e=w(t),o=At.resolve(e,"password.txt");try{if(!et.existsSync(o))throw new Error("\u274C password.txt not found. Run setup script first.");return et.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"};async function M({page:t,currentAccountName:e,newAccountName:o}){if(e===o){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 At(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 Z(t,e){e&&await N(t,e),await t.getByRole("button",{name:"Connect",exact:!0}).click()}async function tt(t){return await t.getByTestId("icon-section-wallet-picker-copy").click(),await t.evaluate(async()=>await navigator.clipboard.readText())}async function F(t){let e=t.getByRole("button",{name:"settings",exact:!0});await e.waitFor({state:"attached",timeout:3e4}),await e.click()}async function et(t){await F(t),await t.getByTestId(A.securityAndPrivacyButton).click(),await t.getByTestId("li-settings-lock").getByTestId(A.lockButton).click()}import{styleText as at}from"util";import ot from"fs";import St from"path";async function M(t){let e=w(t),r=St.resolve(e,"password.txt");try{if(!ot.existsSync(r))throw new Error("\u274C password.txt not found. Run setup script first.");return ot.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 rt(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 rt(150)}}async function D({page:t,currentAccountName:e,newAccountName:r}){if(e===r){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 c=t.getByTestId("li-manage-wallet-rename-wallet");await c.click();let p=t.getByTestId("input-name");await p.clear(),await p.fill(o),await t.getByTestId("btn-save").click(),await c.waitFor({state:"attached",timeout:15e3}),await t.getByTestId("icon-btn-close").click()}async function D(t,e){await N(t);let r=t.getByTestId("li-settings-network").getByRole("combobox");await r.locator(" > p").textContent()!==e?(await r.click(),await t.getByTestId(S.selectNetwork).getByRole("option",{name:e,exact:!0}).click(),(e==="Devnet"||e==="Testnet")&&await t.getByTestId(S.confirmModal).getByTestId(S.confifmButton).click()):console.info(`Network is already set to ${e}`),await t.getByTestId(k.portfolioButton).click()}async function rt({page:t,recoveryPhrase:e,network:o,walletName:r,additionalAccounts:a}){console.info(ot("yellowBright",` - Solflare onboarding started...`,{validateStream:!1}));let n=await F("solflare");await t.getByTestId(x.alreadyHaveAWalletButton).click();let c=e.split(" ");for(let[I,y]of Object.entries(c))await t.getByTestId(`${x.recoveryPhraseInput}-${Number(I)+1}`).fill(y);let p=t.getByTestId(x.continueButton);await p.click();let u=t.getByTestId(x.passwordInput),m=t.getByTestId(x.repeatPasswordInput);if(await u.fill(n),await m.fill(n),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(),r&&await M({page:t,currentAccountName:"Main Wallet",newAccountName:r}),o&&await D(t,o),a&&a.length>0)for(let{privateKey:I,walletName:y}of a)await v({page:t,privateKey:I,walletName:y});console.info(ot("greenBright","\u2728 Solflare onboarding completed successfully",{validateStream:!1}))}async function at(t){await t.getByTestId(W.rejectButton).click()}async function L(t){let e=await F("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 Bt}from"@playwright/test";async function nt({context:t,path:e,locator:o}){let r;try{await Bt.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(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(a)}`].join(` -`))}if(!r)throw new Error(`Popup page with path ${e} not found in context.`);return await kt(r,o),await r.setViewportSize({width:360,height:592}),r}async function kt(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 it from"fs";import bt from"path";async function st(t){let e=w(t),o=bt.resolve(e,"extension-id.txt");try{if(!it.existsSync(o))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return it.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 st(this.name)}async promptPage(e){let o=await this.promptUrl();return await nt({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 rt({page:this.page,recoveryPhrase:e,network:o,additionalAccounts:r,walletName:a})}async unlock(){await L(this.page)}async lock(){await tt(this.page)}async renameAccount({currentAccountName:e,newAccountName:o}){await M({page:this.page,currentAccountName:e,newAccountName:o})}async switchNetwork(e){await D(this.page,e)}async switchAccount(e){await _(this.page,e)}async getAccountAddress(){return await Z(this.page)}async addAccount({privateKey:e,walletName:o}){await v({page:this.page,privateKey:e,walletName:o})}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 at(await this.promptPage(this.page.context()))}};import pt from"fs";import Et from"path";import{test as vt,chromium as Wt}from"@playwright/test";import{expect as Tt}from"@playwright/test";async function T(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 Tt(async()=>{await o.goto(e),await T(o)}).toPass(),o}async function K(t,e){let o=await e.newPage();for(let{origin:r,localStorage:a}of t){let n=o.mainFrame();await n.goto(r),await n.evaluate(l=>{l.forEach(({name:c,value:p})=>{window.localStorage.setItem(c,p)})},a)}await o.close()}import Ct from"fs/promises";async function ct(t){await Ct.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var It=35e3;async function R(t,e){try{await Promise.race([t.close(),new Promise((o,r)=>setTimeout(()=>r(new Error("Context close timed out")),It))])}catch(o){console.warn(`Browser context close did not complete cleanly: ${o.message}`)}try{await ct(e)}catch(o){console.error(`Failed to remove temporary context directory at ${e}. Error:`,o)}}function lt(t){return new Promise(e=>setTimeout(e,t))}async function O(t,e){let r=!1;for(;!e();){let a=e();if(a||r||t.isClosed())break;try{let n=t.locator("div[role='dialog']").locator("button[data-testid='icon-btn-whats-new-modal-close']");await n.isVisible().catch(()=>!1)&&(await n.click(),r=!0)}catch(n){console.error("[autoCloseSolflareNotification]: ",n)}if(a||r||t.isClosed())break;await lt(150)}}var C,Ar=({slowMo:t=0,profileName:e}={})=>vt.extend({contextPath:async({browserName:o},r,a)=>{let n=await P(`${o}-${a.testId}`);await r(n)},context:async({context:o,contextPath:r},a)=>{let n=new h,l=w(n.name),c=await A(n.name),p=Et.resolve(l,e??"wallet-data");if(!pt.existsSync(p))throw new Error("\u274C Cache for Solflare wallet data not found. Create it first");await pt.promises.cp(p,r,{recursive:!0,force:!0});let u=B(c,t),m=await Wt.launchPersistentContext(r,{headless:!1,args:u,slowMo:process.env.HEADLESS?0:t});await m.grantPermissions(["clipboard-read"]);let{cookies:f,origins:d}=await o.storageState();f&&await m.addCookies(f),d&&d.length>0&&await K(d,m);let g=await n.indexUrl(),$=g.split("#")[0]??"";await m.waitForEvent("page",{predicate:y=>y.url().includes($),timeout:15e3}),C=m.pages().find(y=>y.url().startsWith($))||await V(m,g);for(let y of m.pages())y.url().includes("about:blank")&&await y.close();await L(C),await a(m),await R(m,r)},solflarePage:async({context:o},r)=>{await r(C)},solflare:async({context:o},r)=>{let a=new b(C);await r(a)},autoCloseNotification:[async({context:o},r)=>{let a=!1,l=O(C,()=>a);await r(void 0),a=!0,await l.catch(c=>{console.error(`Auto close notification error: ${c.message}`)})},{auto:!0}]});import{test as Ft}from"@playwright/test";import mt from"fs";import _t from"path";import{chromium as Nt}from"@playwright/test";async function ut({workerInfo:t,profileName:e,slowMo:o}){let r=new h,a=await P(t.workerIndex.toString()),n=w(r.name),l=_t.resolve(n,e??"wallet-data");if(!mt.existsSync(l))throw new Error(`Cache for ${r.name} does not exist. Create it first!`);mt.cpSync(l,a,{recursive:!0,force:!0});let c=await A(r.name),p=B(c,o??0),u=await Nt.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:g=>g.url().includes(f),timeout:15e3});let d=u.pages().find(g=>g.url().startsWith(f));d||(d=await u.newPage(),await d.goto(m),await T(d));for(let g of u.pages())g.url().includes("about:blank")&&await g.close();return{context:u,walletPage:d,contextPath:a}}var Ur=({slowMo:t,profileName:e}={})=>Ft.extend({workerScopeContents:[async({browser:o},r,a)=>{let{context:n,contextPath:l,walletPage:c}=await ut({workerInfo:a,profileName:e,slowMo:t});await n.grantPermissions(["clipboard-read"]);let p=new b(c);await p.unlock(),await r({wallet:p,walletPage:c,context:n}),await R(n,l)},{scope:"worker"}],autoCloseNotification:[async({workerScopeContents:o},r)=>{let a=!1,n=()=>a,l=O(o.walletPage,n);await r(void 0),a=!0,await l.catch(c=>{console.error(`Auto close notification error: ${c.message}`)})},{auto:!0}]});export{b as Solflare,Ar as solflareFixture,Ur as solflareWorkerScopeFixture,me 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 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 F(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 nt({page:t,recoveryPhrase:e,network:r,walletName:o,additionalAccounts:a}){console.info(at("yellowBright",` + Solflare onboarding started...`,{validateStream:!1}));let s=await M("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(at("greenBright","\u2728 Solflare onboarding completed successfully",{validateStream:!1}))}async function it(t){await t.getByTestId(_.rejectButton).click()}async function R(t){let e=await M("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 Bt}from"@playwright/test";async function st({context:t,path:e,locator:r}){let o;try{await Bt.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 kt(o,r),await o.setViewportSize({width:360,height:592}),o}async function kt(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 bt from"path";async function lt(t){let e=w(t),r=bt.resolve(e,"extension-id.txt");try{if(!ct.existsSync(r))throw new Error("\u274C extension-id.txt not found. Run setup script first.");return ct.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 lt(this.name)}async promptPage(e){let r=await this.promptUrl();return await st({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 nt({page:this.page,recoveryPhrase:e,network:r,additionalAccounts:o,walletName:a})}async unlock(){await R(this.page)}async lock(){await et(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 tt(this.page)}async addAccount({privateKey:e,walletName:r}){await W({page:this.page,privateKey:e,walletName:r})}async connectToApp(e){await Z(await this.promptPage(this.page.context()),e)}async confirmTransaction(){await Y(await this.promptPage(this.page.context()))}async rejectTransaction(){await it(await this.promptPage(this.page.context()))}};import mt from"fs";import Et from"path";import{test as vt,chromium as Wt}from"@playwright/test";import{expect as Ct}from"@playwright/test";async function T(t){await t.waitForLoadState("load",{timeout:15e3}),await t.waitForLoadState("domcontentloaded",{timeout:15e3})}async function K(t,e){let r=await t.newPage();return await Ct(async()=>{await r.goto(e),await T(r)}).toPass(),r}async function H(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 Tt from"fs/promises";async function pt(t){await Tt.rm(t,{maxRetries:50,retryDelay:500,recursive:!0,force:!0})}var It=35e3;async function O(t,e){try{await Promise.race([t.close(),new Promise((r,o)=>setTimeout(()=>o(new Error("Context close timed out")),It))])}catch(r){console.warn(`Browser context close did not complete cleanly: ${r.message}`)}try{await pt(e)}catch(r){console.error(`Failed to remove temporary context directory at ${e}. Error:`,r)}}var I,kr=({slowMo:t=0,profileName:e}={})=>vt.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=Et.resolve(c,e??"wallet-data");if(!mt.existsSync(l))throw new Error("\u274C Cache for Solflare wallet data not found. Create it first");await mt.promises.cp(l,o,{recursive:!0,force:!0});let u=B(p,t),m=await Wt.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 H(g,m);let y=await s.indexUrl(),$=y.split("#")[0]??"";await m.waitForEvent("page",{predicate:d=>d.url().includes($),timeout:15e3}),I=m.pages().find(d=>d.url().startsWith($))||await K(m,y);for(let d of m.pages())d.url().includes("about:blank")&&await d.close();await R(I),await a(m),await O(m,o)},solflarePage:async({context:r},o)=>{await o(I)},solflare:async({context:r},o)=>{let a=new C(I);await o(a)},autoCloseNotification:[async({context:r},o)=>{let a=new AbortController,s=b(I,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 ut from"fs";import _t from"path";import{chromium as Nt}from"@playwright/test";async function wt({workerInfo:t,profileName:e,slowMo:r}){let o=new h,a=await P(t.workerIndex.toString()),s=w(o.name),c=_t.resolve(s,e??"wallet-data");if(!ut.existsSync(c))throw new Error(`Cache for ${o.name} does not exist. Create it first!`);ut.cpSync(c,a,{recursive:!0,force:!0});let p=await S(o.name),l=B(p,r??0),u=await Nt.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 T(g));for(let y of u.pages())y.url().includes("about:blank")&&await y.close();return{context:u,walletPage:g,contextPath:a}}var Kr=({slowMo:t,profileName:e}={})=>Ft.extend({workerScopeContents:[async({browser:r},o,a)=>{let{context:s,contextPath:c,walletPage:p}=await wt({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 O(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,kr as solflareFixture,Kr as solflareWorkerScopeFixture,ue as workerScopeContext};