From 6d0ca8f3955177a74f452bb0a372144ecd3a653e Mon Sep 17 00:00:00 2001 From: juan-langa Date: Mon, 9 Dec 2024 10:52:39 +0100 Subject: [PATCH 01/36] Phantom support for Playwright --- docs/api/typedoc-sidebar.json | 77 ++++- package.json | 5 +- packages/cache/package.json | 1 + packages/cache/src/cli/cliEntrypoint.ts | 20 +- packages/cache/src/index.ts | 1 + packages/cache/src/prepareExtensionPhantom.ts | 21 ++ packages/cache/src/unzipArchive.ts | 25 ++ packages/cache/tsconfig.build.json | 2 +- packages/cache/tsconfig.json | 7 +- packages/cache/unzip-crx-3.d.ts | 1 + pnpm-lock.yaml | 170 ++++++++- release/package.json | 7 +- release/src/playwright/index.ts | 1 + wallets/phantom/environment.d.ts | 10 + wallets/phantom/package.json | 54 +++ wallets/phantom/playwright.config.ts | 60 ++++ wallets/phantom/src/index.ts | 0 wallets/phantom/src/playwright/Phantom.ts | 322 ++++++++++++++++++ .../fixture-actions/getExtensionId.ts | 46 +++ .../src/playwright/fixture-actions/index.ts | 3 + .../fixture-actions/persistLocalStorage.ts | 24 ++ .../prepareExtensionPhantom.ts | 0 .../fixture-actions/unlockForFixture.ts | 34 ++ .../playwright/fixtures/phantomFixtures.ts | 154 +++++++++ wallets/phantom/src/playwright/index.ts | 3 + .../src/playwright/pages/CrashPage/page.ts | 6 + .../pages/HomePage/actions/addNewAccount.ts | 19 ++ .../HomePage/actions/getAccountAddress.ts | 16 + .../actions/importWalletFromPrivateKey.ts | 47 +++ .../pages/HomePage/actions/index.ts | 9 + .../playwright/pages/HomePage/actions/lock.ts | 10 + .../HomePage/actions/popups/closePopover.ts | 12 + .../popups/closeRecoveryPhraseReminder.ts | 10 + .../pages/HomePage/actions/popups/index.ts | 2 + .../pages/HomePage/actions/renameAccount.ts | 43 +++ .../pages/HomePage/actions/settings.ts | 23 ++ .../pages/HomePage/actions/switchAccount.ts | 30 ++ .../pages/HomePage/actions/switchNetwork.ts | 11 + .../HomePage/actions/toggleTestnetMode.ts | 14 + .../src/playwright/pages/HomePage/page.ts | 72 ++++ .../actions/approvePermission.ts | 30 ++ .../actions/closeUnsupportedNetworkWarning.ts | 10 + .../NotificationPage/actions/connectToDapp.ts | 16 + .../pages/NotificationPage/actions/index.ts | 7 + .../actions/signSimpleMessage.ts | 23 ++ .../actions/signStructuredMessage.ts | 25 ++ .../pages/NotificationPage/actions/token.ts | 10 + .../NotificationPage/actions/transaction.ts | 64 ++++ .../playwright/pages/NotificationPage/page.ts | 104 ++++++ .../helpers/confirmSecretRecoveryPhrase.ts | 21 ++ .../actions/helpers/createPassword.ts | 14 + .../OnboardingPage/actions/helpers/index.ts | 2 + .../OnboardingPage/actions/importWallet.ts | 25 ++ .../pages/OnboardingPage/actions/index.ts | 1 + .../playwright/pages/OnboardingPage/page.ts | 18 + .../pages/UnlockPage/actions/index.ts | 1 + .../pages/UnlockPage/actions/unlock.ts | 10 + .../src/playwright/pages/UnlockPage/page.ts | 18 + wallets/phantom/src/playwright/pages/index.ts | 5 + .../src/playwright/utils/allTextContents.ts | 10 + .../utils/clickLocatorIfCondition.ts | 10 + .../getNotificationPageAndWaitForLoad.ts | 27 ++ .../phantom/src/playwright/utils/toggle.ts | 32 ++ .../phantom/src/playwright/utils/waitFor.ts | 138 ++++++++ .../utils/waitForSpinnerToVanish.ts | 13 + .../phantom/src/prepareExtensionPhantom.ts | 31 ++ .../src/selectors/createDataTestSelector.ts | 7 + wallets/phantom/src/selectors/error/index.ts | 7 + wallets/phantom/src/selectors/index.ts | 9 + .../phantom/src/selectors/loading/index.ts | 17 + .../src/selectors/pages/CrashPage/index.ts | 6 + .../src/selectors/pages/HomePage/index.ts | 115 +++++++ .../src/selectors/pages/HomePage/settings.ts | 26 ++ .../pages/NotificationPage/actionFooter.ts | 9 + .../pages/NotificationPage/connectPage.ts | 4 + .../pages/NotificationPage/ethereumRpcPage.ts | 4 + .../selectors/pages/NotificationPage/index.ts | 13 + .../pages/NotificationPage/permissionPage.ts | 10 + .../pages/NotificationPage/signaturePage.ts | 25 ++ .../pages/NotificationPage/transactionPage.ts | 15 + .../pages/OnboardingPage/analyticsPage.ts | 6 + .../pages/OnboardingPage/getStartedPage.ts | 8 + .../selectors/pages/OnboardingPage/index.ts | 25 ++ .../pages/OnboardingPage/pinExtensionPage.ts | 6 + .../secretRecoveryPhrasePage.ts | 36 ++ .../walletCreationSuccessPage.ts | 5 + .../src/selectors/pages/SettingsPage/index.ts | 23 ++ .../src/selectors/pages/UnlockPage/index.ts | 6 + wallets/phantom/src/type/GasSettings.ts | 25 ++ wallets/phantom/src/type/Networks.ts | 1 + wallets/phantom/src/type/PhantomAbstract.ts | 158 +++++++++ .../commonSteps/connectPhantomToTestDapp.ts | 10 + .../test/playwright/e2e/addNewAccount.spec.ts | 23 ++ .../e2e/approveTokenPermission.spec.ts | 27 ++ .../playwright/e2e/confirmSignature.spec.ts | 78 +++++ .../playwright/e2e/confirmTransaction.spec.ts | 44 +++ .../test/playwright/e2e/connectToDapp.spec.ts | 49 +++ .../playwright/e2e/getAccountAddress.spec.ts | 27 ++ .../playwright/e2e/goBackToHomePage.spec.ts | 22 ++ .../e2e/importWalletFromPrivateKey.spec.ts | 48 +++ .../phantom/test/playwright/e2e/lock.spec.ts | 16 + .../test/playwright/e2e/openSettings.spec.ts | 15 + .../playwright/e2e/rejectSignature.spec.ts | 47 +++ .../e2e/rejectTokenPermission.spec.ts | 32 ++ .../playwright/e2e/rejectTransaction.spec.ts | 31 ++ .../test/playwright/e2e/renameAccount.spec.ts | 19 ++ .../test/playwright/e2e/resetApp.spec.ts | 27 ++ .../test/playwright/e2e/switchAccount.spec.ts | 38 +++ .../playwright/e2e/toggleTestnetMode.spec.ts | 20 ++ .../test/playwright/e2e/unlock.spec.ts | 18 + wallets/phantom/test/playwright/synpress.ts | 5 + .../playwright/wallet-setup/basic.setup.ts | 11 + wallets/phantom/tsconfig.build.json | 12 + wallets/phantom/tsconfig.json | 12 + wallets/phantom/tsup.config.ts | 12 + wallets/phantom/vitest.config.ts | 7 + 116 files changed, 3218 insertions(+), 35 deletions(-) create mode 100644 packages/cache/src/prepareExtensionPhantom.ts create mode 100644 packages/cache/unzip-crx-3.d.ts create mode 100644 wallets/phantom/environment.d.ts create mode 100644 wallets/phantom/package.json create mode 100644 wallets/phantom/playwright.config.ts create mode 100644 wallets/phantom/src/index.ts create mode 100644 wallets/phantom/src/playwright/Phantom.ts create mode 100644 wallets/phantom/src/playwright/fixture-actions/getExtensionId.ts create mode 100644 wallets/phantom/src/playwright/fixture-actions/index.ts create mode 100644 wallets/phantom/src/playwright/fixture-actions/persistLocalStorage.ts create mode 100644 wallets/phantom/src/playwright/fixture-actions/prepareExtensionPhantom.ts create mode 100644 wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts create mode 100644 wallets/phantom/src/playwright/fixtures/phantomFixtures.ts create mode 100644 wallets/phantom/src/playwright/index.ts create mode 100644 wallets/phantom/src/playwright/pages/CrashPage/page.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/addNewAccount.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/index.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/lock.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/renameAccount.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/settings.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/switchAccount.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/toggleTestnetMode.ts create mode 100644 wallets/phantom/src/playwright/pages/HomePage/page.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/closeUnsupportedNetworkWarning.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/connectToDapp.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/signSimpleMessage.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/signStructuredMessage.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/transaction.ts create mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/page.ts create mode 100644 wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/confirmSecretRecoveryPhrase.ts create mode 100644 wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/createPassword.ts create mode 100644 wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/index.ts create mode 100644 wallets/phantom/src/playwright/pages/OnboardingPage/actions/importWallet.ts create mode 100644 wallets/phantom/src/playwright/pages/OnboardingPage/actions/index.ts create mode 100644 wallets/phantom/src/playwright/pages/OnboardingPage/page.ts create mode 100644 wallets/phantom/src/playwright/pages/UnlockPage/actions/index.ts create mode 100644 wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts create mode 100644 wallets/phantom/src/playwright/pages/UnlockPage/page.ts create mode 100644 wallets/phantom/src/playwright/pages/index.ts create mode 100644 wallets/phantom/src/playwright/utils/allTextContents.ts create mode 100644 wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts create mode 100644 wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts create mode 100644 wallets/phantom/src/playwright/utils/toggle.ts create mode 100644 wallets/phantom/src/playwright/utils/waitFor.ts create mode 100644 wallets/phantom/src/playwright/utils/waitForSpinnerToVanish.ts create mode 100644 wallets/phantom/src/prepareExtensionPhantom.ts create mode 100644 wallets/phantom/src/selectors/createDataTestSelector.ts create mode 100644 wallets/phantom/src/selectors/error/index.ts create mode 100644 wallets/phantom/src/selectors/index.ts create mode 100644 wallets/phantom/src/selectors/loading/index.ts create mode 100644 wallets/phantom/src/selectors/pages/CrashPage/index.ts create mode 100644 wallets/phantom/src/selectors/pages/HomePage/index.ts create mode 100644 wallets/phantom/src/selectors/pages/HomePage/settings.ts create mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/actionFooter.ts create mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts create mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts create mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/index.ts create mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts create mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts create mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/transactionPage.ts create mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts create mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts create mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/index.ts create mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts create mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts create mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/walletCreationSuccessPage.ts create mode 100644 wallets/phantom/src/selectors/pages/SettingsPage/index.ts create mode 100644 wallets/phantom/src/selectors/pages/UnlockPage/index.ts create mode 100644 wallets/phantom/src/type/GasSettings.ts create mode 100644 wallets/phantom/src/type/Networks.ts create mode 100644 wallets/phantom/src/type/PhantomAbstract.ts create mode 100644 wallets/phantom/test/playwright/commonSteps/connectPhantomToTestDapp.ts create mode 100644 wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/getAccountAddress.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/goBackToHomePage.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/importWalletFromPrivateKey.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/lock.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/openSettings.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/renameAccount.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/resetApp.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/switchAccount.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/toggleTestnetMode.spec.ts create mode 100644 wallets/phantom/test/playwright/e2e/unlock.spec.ts create mode 100644 wallets/phantom/test/playwright/synpress.ts create mode 100644 wallets/phantom/test/playwright/wallet-setup/basic.setup.ts create mode 100644 wallets/phantom/tsconfig.build.json create mode 100644 wallets/phantom/tsconfig.json create mode 100644 wallets/phantom/tsup.config.ts create mode 100644 wallets/phantom/vitest.config.ts diff --git a/docs/api/typedoc-sidebar.json b/docs/api/typedoc-sidebar.json index 30282d5dc..668b3da35 100644 --- a/docs/api/typedoc-sidebar.json +++ b/docs/api/typedoc-sidebar.json @@ -17,8 +17,14 @@ "text": "configureSynpressForEthereumWalletMock", "link": "/api/cypress/functions/configureSynpressForEthereumWalletMock.md" }, - { "text": "configureSynpressForMetaMask", "link": "/api/cypress/functions/configureSynpressForMetaMask.md" }, - { "text": "initMetaMask", "link": "/api/cypress/functions/initMetaMask.md" } + { + "text": "configureSynpressForMetaMask", + "link": "/api/cypress/functions/configureSynpressForMetaMask.md" + }, + { + "text": "initMetaMask", + "link": "/api/cypress/functions/initMetaMask.md" + } ] }, { @@ -30,7 +36,10 @@ "text": "Functions", "collapsed": true, "items": [ - { "text": "mockEthereum", "link": "/api/cypress/support/functions/mockEthereum.md" }, + { + "text": "mockEthereum", + "link": "/api/cypress/support/functions/mockEthereum.md" + }, { "text": "synpressCommandsForEthereumWalletMock", "link": "/api/cypress/support/functions/synpressCommandsForEthereumWalletMock.md" @@ -54,8 +63,14 @@ "text": "Functions", "collapsed": true, "items": [ - { "text": "defineWalletSetup", "link": "/api/index/functions/defineWalletSetup.md" }, - { "text": "testWithSynpress", "link": "/api/index/functions/testWithSynpress.md" } + { + "text": "defineWalletSetup", + "link": "/api/index/functions/defineWalletSetup.md" + }, + { + "text": "testWithSynpress", + "link": "/api/index/functions/testWithSynpress.md" + } ] } ] @@ -69,28 +84,60 @@ "text": "Classes", "collapsed": true, "items": [ - { "text": "EthereumWalletMock", "link": "/api/playwright/classes/EthereumWalletMock.md" }, - { "text": "MetaMask", "link": "/api/playwright/classes/MetaMask.md" } + { + "text": "EthereumWalletMock", + "link": "/api/playwright/classes/EthereumWalletMock.md" + }, + { "text": "MetaMask", "link": "/api/playwright/classes/MetaMask.md" }, + { "text": "Phantom", "link": "/api/playwright/classes/Phantom.md" } ] }, { "text": "Variables", "collapsed": true, "items": [ - { "text": "DEFAULT_NETWORK_ID", "link": "/api/playwright/variables/DEFAULT_NETWORK_ID.md" }, - { "text": "PRIVATE_KEY", "link": "/api/playwright/variables/PRIVATE_KEY.md" }, - { "text": "web3MockPath", "link": "/api/playwright/variables/web3MockPath.md" } + { + "text": "DEFAULT_NETWORK_ID", + "link": "/api/playwright/variables/DEFAULT_NETWORK_ID.md" + }, + { + "text": "PRIVATE_KEY", + "link": "/api/playwright/variables/PRIVATE_KEY.md" + }, + { + "text": "web3MockPath", + "link": "/api/playwright/variables/web3MockPath.md" + } ] }, { "text": "Functions", "collapsed": true, "items": [ - { "text": "ethereumWalletMockFixtures", "link": "/api/playwright/functions/ethereumWalletMockFixtures.md" }, - { "text": "getExtensionId", "link": "/api/playwright/functions/getExtensionId.md" }, - { "text": "metaMaskFixtures", "link": "/api/playwright/functions/metaMaskFixtures.md" }, - { "text": "mockEthereum", "link": "/api/playwright/functions/mockEthereum.md" }, - { "text": "unlockForFixture", "link": "/api/playwright/functions/unlockForFixture.md" } + { + "text": "ethereumWalletMockFixtures", + "link": "/api/playwright/functions/ethereumWalletMockFixtures.md" + }, + { + "text": "getExtensionId", + "link": "/api/playwright/functions/getExtensionId.md" + }, + { + "text": "metaMaskFixtures", + "link": "/api/playwright/functions/metaMaskFixtures.md" + }, + { + "text": "phantomFixtures", + "link": "/api/playwright/functions/phantomFixtures.md" + }, + { + "text": "mockEthereum", + "link": "/api/playwright/functions/mockEthereum.md" + }, + { + "text": "unlockForFixture", + "link": "/api/playwright/functions/unlockForFixture.md" + } ] } ] diff --git a/package.json b/package.json index 704740877..2ed39536d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "scripts": { "build": "turbo build", "build:cache": "turbo build:cache --filter=@synthetixio/synpress-metamask", + "build:cache:phantom": "turbo build:cache --filter=@synthetixio/synpress-phantom", "docs:build": "turbo docs:build --filter=docs", "format": "biome format . --write", "format:check": "biome format . --error-on-warnings", @@ -15,8 +16,8 @@ "sort-package-json": "sort-package-json 'package.json' '{packages,wallets,examples}/*/package.json'", "sort-package-json:check": "sort-package-json 'package.json' '{packages,wallets,examples}/*/package.json' --check", "test": "turbo test", - "test:playwright:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock", - "test:playwright:headless": "turbo test:playwright:headless --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock", + "test:playwright:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock --filter=@synthetixio/synpress-phantom", + "test:playwright:headless": "turbo test:playwright:headless --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock --filter=@synthetixio/synpress-phantom", "update:deps": "ncu -u -ws --root" }, "lint-staged": { diff --git a/packages/cache/package.json b/packages/cache/package.json index bb3d9a78c..0c340e507 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -38,6 +38,7 @@ "gradient-string": "2.0.2", "progress": "2.0.3", "tsup": "8.0.2", + "unzip-crx-3": "0.2.0", "unzipper": "0.10.14", "zod": "3.22.4" }, diff --git a/packages/cache/src/cli/cliEntrypoint.ts b/packages/cache/src/cli/cliEntrypoint.ts index 9851573f4..104c262a8 100644 --- a/packages/cache/src/cli/cliEntrypoint.ts +++ b/packages/cache/src/cli/cliEntrypoint.ts @@ -6,6 +6,7 @@ import { rimraf } from 'rimraf' import { WALLET_SETUP_DIR_NAME } from '../constants' import { createCache } from '../createCache' import { prepareExtension } from '../prepareExtension' +import { prepareExtensionPhantom } from '../prepareExtensionPhantom' import { compileWalletSetupFunctions } from './compileWalletSetupFunctions' import { footer } from './footer' @@ -13,6 +14,7 @@ interface CliFlags { headless: boolean force: boolean debug: boolean + phantom: boolean } // TODO: Add unit tests for the CLI! @@ -30,6 +32,7 @@ export const cliEntrypoint = async () => { ) .option('-f, --force', 'Force the creation of cache even if it already exists', false) .option('-d, --debug', 'If this flag is present, the compilation files are not going to be deleted', false) + .option('-p, --phantom', 'If this flag is present, Phantom extension will be installed instead of Metamask', false) .helpOption(undefined, 'Display help for command') .addHelpText('afterAll', `\n${footer}\n`) .parse(process.argv) @@ -47,7 +50,14 @@ export const cliEntrypoint = async () => { if (flags.debug) { console.log('[DEBUG] Running with the following options:') - console.log({ cacheDir: walletSetupDir, ...flags, headless: Boolean(process.env.HEADLESS) ?? false }, '\n') + console.log( + { + cacheDir: walletSetupDir, + ...flags, + headless: Boolean(process.env.HEADLESS) ?? false + }, + '\n' + ) } if (os.platform() === 'win32') { @@ -64,8 +74,12 @@ export const cliEntrypoint = async () => { const compiledWalletSetupDirPath = await compileWalletSetupFunctions(walletSetupDir, flags.debug) - // TODO: We should be using `prepareExtension` function from the wallet itself! - await createCache(compiledWalletSetupDirPath, prepareExtension, flags.force) + // TODO: We should be using `prepareExtension` functions from the wallet itself! + if (flags.phantom) { + await createCache(compiledWalletSetupDirPath, prepareExtensionPhantom, flags.force) + } else { + await createCache(compiledWalletSetupDirPath, prepareExtension, flags.force) + } if (!flags.debug) { await rimraf(compiledWalletSetupDirPath) diff --git a/packages/cache/src/index.ts b/packages/cache/src/index.ts index 027dbc751..c1fdff25a 100644 --- a/packages/cache/src/index.ts +++ b/packages/cache/src/index.ts @@ -8,3 +8,4 @@ export * from './utils/createTempContextDir' export * from './utils/removeTempContextDir' export * from './prepareExtension' export * from './cli/cliEntrypoint' +export * from './prepareExtensionPhantom' diff --git a/packages/cache/src/prepareExtensionPhantom.ts b/packages/cache/src/prepareExtensionPhantom.ts new file mode 100644 index 000000000..575f0e8f6 --- /dev/null +++ b/packages/cache/src/prepareExtensionPhantom.ts @@ -0,0 +1,21 @@ +import { downloadFile, ensureCacheDirExists, unzipArchivePhantom } from '.' + +export const DEFAULT_PHANTOM_VERSION = 'latest' +export const PHANTOM_EXTENSION_DOWNLOAD_URL = 'https://crx-backup.phantom.dev/latest.crx' + +// NOTE: This function is copied from `wallets/phantom/src/prepareExtensionPhantom.ts` only TEMPORARILY! +export async function prepareExtensionPhantom() { + const cacheDirPath = ensureCacheDirExists() + + const downloadResult = await downloadFile({ + url: PHANTOM_EXTENSION_DOWNLOAD_URL, + outputDir: cacheDirPath, + fileName: 'phantom-chrome-latest.crx' + }) + + const unzipResult = await unzipArchivePhantom({ + archivePath: downloadResult.filePath + }) + + return unzipResult.outputPath +} diff --git a/packages/cache/src/unzipArchive.ts b/packages/cache/src/unzipArchive.ts index ab4af5052..dfb8c1955 100644 --- a/packages/cache/src/unzipArchive.ts +++ b/packages/cache/src/unzipArchive.ts @@ -1,5 +1,6 @@ import path from 'node:path' import fs from 'fs-extra' +import unzipCrx from 'unzip-crx-3' import unzippper from 'unzipper' type UnzipArchiveOptions = { @@ -73,3 +74,27 @@ export async function unzipArchive(options: UnzipArchiveOptions) { throw new Error(`[UnzipFile] Error unzipping the file - ${error.message}`) }) } + +export async function unzipArchivePhantom(options: UnzipArchiveOptions) { + const { archivePath, overwrite } = options + + const archiveFileExtension = archivePath.split('.').slice(-1) + const outputPath = archivePath.replace(`.${archiveFileExtension}`, '') + + const fileExists = fs.existsSync(outputPath) + if (fileExists && !overwrite) { + return { + outputPath, + unzipSkipped: true + } + } + + // Creates the output directory + fs.mkdirSync(outputPath, { recursive: true }) + + await unzipCrx(archivePath, outputPath) + + // TODO: Handle errors + + return { outputPath } +} diff --git a/packages/cache/tsconfig.build.json b/packages/cache/tsconfig.build.json index 9af73f809..a0d6937e9 100644 --- a/packages/cache/tsconfig.build.json +++ b/packages/cache/tsconfig.build.json @@ -8,5 +8,5 @@ "declarationMap": true }, "include": ["src"], - "files": ["environment.d.ts"] + "files": ["environment.d.ts", "unzip-crx-3.d.ts"] } diff --git a/packages/cache/tsconfig.json b/packages/cache/tsconfig.json index 14c18c50e..9f68e10a6 100644 --- a/packages/cache/tsconfig.json +++ b/packages/cache/tsconfig.json @@ -4,9 +4,6 @@ "rootDir": ".", "lib": ["DOM"] }, - "include": [ - "src", - "test" - ], - "files": ["environment.d.ts"] + "include": ["src", "test"], + "files": ["environment.d.ts", "unzip-crx-3.d.ts"] } diff --git a/packages/cache/unzip-crx-3.d.ts b/packages/cache/unzip-crx-3.d.ts new file mode 100644 index 000000000..bee17e7ac --- /dev/null +++ b/packages/cache/unzip-crx-3.d.ts @@ -0,0 +1 @@ +declare module 'unzip-crx-3' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aada3eba7..9c5f23a7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,6 +151,9 @@ importers: tsup: specifier: 8.0.2 version: 8.0.2(postcss@8.4.41)(typescript@5.3.3) + unzip-crx-3: + specifier: 0.2.0 + version: 0.2.0 unzipper: specifier: 0.10.14 version: 0.10.14 @@ -160,7 +163,7 @@ importers: devDependencies: '@synthetixio/synpress-tsconfig': specifier: 0.0.4 - version: 0.0.4 + version: link:../tsconfig '@types/archiver': specifier: 6.0.2 version: 6.0.2 @@ -242,6 +245,9 @@ importers: '@synthetixio/synpress-metamask': specifier: workspace:* version: link:../wallets/metamask + '@synthetixio/synpress-phantom': + specifier: workspace:* + version: link:../wallets/phantom devDependencies: '@synthetixio/synpress-tsconfig': specifier: 0.0.4 @@ -351,6 +357,55 @@ importers: specifier: 1.2.2 version: 1.2.2(@types/node@20.11.17) + wallets/phantom: + dependencies: + '@playwright/test': + specifier: 1.48.2 + version: 1.48.2 + '@synthetixio/synpress-cache': + specifier: workspace:* + version: link:../../packages/cache + '@synthetixio/synpress-core': + specifier: workspace:* + version: link:../../packages/core + '@viem/anvil': + specifier: 0.0.7 + version: 0.0.7 + fs-extra: + specifier: 11.2.0 + version: 11.2.0 + zod: + specifier: 3.22.4 + version: 3.22.4 + devDependencies: + '@synthetixio/synpress-tsconfig': + specifier: 0.0.4 + version: 0.0.4 + '@types/fs-extra': + specifier: 11.0.4 + version: 11.0.4 + '@types/node': + specifier: 20.11.17 + version: 20.11.17 + '@vitest/coverage-v8': + specifier: 1.2.2 + version: 1.2.2(vitest@1.2.2(@types/node@20.11.17)) + cypress: + specifier: 13.17.0 + version: 13.17.0 + rimraf: + specifier: 5.0.5 + version: 5.0.5 + tsup: + specifier: 8.0.2 + version: 8.0.2(postcss@8.4.41)(typescript@5.3.3) + typescript: + specifier: 5.3.3 + version: 5.3.3 + vitest: + specifier: 1.2.2 + version: 1.2.2(@types/node@20.11.17) + packages: '@adraffy/ens-normalize@1.10.0': @@ -2028,6 +2083,10 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + ci-info@4.1.0: + resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} + engines: {node: '>=8'} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2223,6 +2282,11 @@ packages: engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true + cypress@13.17.0: + resolution: {integrity: sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==} + engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} + hasBin: true + dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} @@ -2902,6 +2966,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} @@ -3230,6 +3297,9 @@ packages: resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} engines: {'0': node >=0.6.0} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3253,6 +3323,9 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@3.0.0: resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} engines: {node: '>=14'} @@ -3310,6 +3383,7 @@ packages: lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} @@ -3750,6 +3824,9 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parse-github-url@1.0.3: resolution: {integrity: sha512-tfalY5/4SqGaV/GIGzWyHnFjlpTPTNpENR9Ea2lLldSJ8EWXMsvacWucqY3m3I4YPtas15IxTLQVQ5NSYXPrww==} engines: {node: '>= 0.10'} @@ -4737,6 +4814,9 @@ packages: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} + unzip-crx-3@0.2.0: + resolution: {integrity: sha512-0+JiUq/z7faJ6oifVB5nSwt589v1KCduqIJupNVDoWSXZtWDmjDGO3RAEOvwJ07w90aoXoP4enKsR7ecMrJtWQ==} + unzipper@0.10.14: resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} @@ -5000,6 +5080,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yaku@0.16.7: + resolution: {integrity: sha512-Syu3IB3rZvKvYk7yTiyl1bo/jiEFaaStrgv1V2TIJTqYPStSMQVO8EQjg/z+DRzLq/4LIIharNT3iH1hylEIRw==} + yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} @@ -6382,7 +6465,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.6 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 @@ -6963,6 +7046,8 @@ snapshots: ci-info@3.9.0: {} + ci-info@4.1.0: {} + clean-stack@2.2.0: {} cli-boxes@3.0.0: {} @@ -7200,6 +7285,52 @@ snapshots: untildify: 4.0.0 yauzl: 2.10.0 + cypress@13.17.0: + dependencies: + '@cypress/request': 3.0.6 + '@cypress/xvfb': 1.2.4(supports-color@8.1.1) + '@types/sinonjs__fake-timers': 8.1.1 + '@types/sizzle': 2.3.8 + arch: 2.2.0 + blob-util: 2.0.2 + bluebird: 3.7.2 + buffer: 5.7.1 + cachedir: 2.4.0 + chalk: 4.1.2 + check-more-types: 2.24.0 + ci-info: 4.1.0 + cli-cursor: 3.1.0 + cli-table3: 0.6.5 + commander: 6.2.1 + common-tags: 1.8.2 + dayjs: 1.11.13 + debug: 4.3.6(supports-color@8.1.1) + enquirer: 2.4.1 + eventemitter2: 6.4.7 + execa: 4.1.0 + executable: 4.1.1 + extract-zip: 2.0.1(supports-color@8.1.1) + figures: 3.2.0 + fs-extra: 9.1.0 + getos: 3.2.1 + is-installed-globally: 0.4.0 + lazy-ass: 1.6.0 + listr2: 3.14.0(enquirer@2.4.1) + lodash: 4.17.21 + log-symbols: 4.1.0 + minimist: 1.2.8 + ospath: 1.2.2 + pretty-bytes: 5.6.0 + process: 0.11.10 + proxy-from-env: 1.0.0 + request-progress: 3.0.0 + semver: 7.6.3 + supports-color: 8.1.1 + tmp: 0.2.3 + tree-kill: 1.2.2 + untildify: 4.0.0 + yauzl: 2.10.0 + dashdash@1.14.1: dependencies: assert-plus: 1.0.0 @@ -7238,6 +7369,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.6: + dependencies: + ms: 2.1.2 + debug@4.3.6(supports-color@8.1.1): dependencies: ms: 2.1.2 @@ -8045,6 +8180,8 @@ snapshots: ignore@5.3.2: {} + immediate@3.0.6: {} + import-lazy@4.0.0: {} imurmurhash@0.1.4: {} @@ -8229,7 +8366,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.6 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -8316,6 +8453,13 @@ snapshots: json-schema: 0.4.0 verror: 1.10.0 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -8334,6 +8478,10 @@ snapshots: dependencies: readable-stream: 2.3.8 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@3.0.0: {} lilconfig@3.1.2: {} @@ -8946,6 +9094,8 @@ snapshots: - bluebird - supports-color + pako@1.0.11: {} + parse-github-url@1.0.3: {} parse-json@5.2.0: @@ -9777,7 +9927,7 @@ snapshots: bundle-require: 4.2.1(esbuild@0.19.12) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.6 esbuild: 0.19.12 execa: 5.1.1 globby: 11.1.0 @@ -9955,6 +10105,12 @@ snapshots: untildify@4.0.0: {} + unzip-crx-3@0.2.0: + dependencies: + jszip: 3.10.1 + mkdirp: 0.5.6 + yaku: 0.16.7 + unzipper@0.10.14: dependencies: big-integer: 1.6.52 @@ -10039,7 +10195,7 @@ snapshots: vite-node@1.2.2(@types/node@20.11.17): dependencies: cac: 6.7.14 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.6 pathe: 1.1.2 picocolors: 1.0.1 vite: 5.4.2(@types/node@20.11.17) @@ -10129,7 +10285,7 @@ snapshots: acorn-walk: 8.3.3 cac: 6.7.14 chai: 4.5.0 - debug: 4.3.6(supports-color@8.1.1) + debug: 4.3.6 execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.11 @@ -10277,6 +10433,8 @@ snapshots: y18n@5.0.8: {} + yaku@0.16.7: {} + yallist@2.1.2: {} yallist@4.0.0: {} diff --git a/release/package.json b/release/package.json index 5ea548b9f..9c1365703 100644 --- a/release/package.json +++ b/release/package.json @@ -40,9 +40,10 @@ }, "dependencies": { "@synthetixio/ethereum-wallet-mock": "workspace:*", - "@synthetixio/synpress-cache": "0.0.4", - "@synthetixio/synpress-core": "0.0.4", - "@synthetixio/synpress-metamask": "workspace:*" + "@synthetixio/synpress-cache": "workspace:*", + "@synthetixio/synpress-core": "workspace:*", + "@synthetixio/synpress-metamask": "workspace:*", + "@synthetixio/synpress-phantom": "workspace:*" }, "devDependencies": { "@synthetixio/synpress-tsconfig": "0.0.4", diff --git a/release/src/playwright/index.ts b/release/src/playwright/index.ts index f97810c9b..a83beb951 100644 --- a/release/src/playwright/index.ts +++ b/release/src/playwright/index.ts @@ -1,2 +1,3 @@ export * from '@synthetixio/ethereum-wallet-mock/playwright' export * from '@synthetixio/synpress-metamask/playwright' +export * from '@synthetixio/synpress-phantom/playwright' diff --git a/wallets/phantom/environment.d.ts b/wallets/phantom/environment.d.ts new file mode 100644 index 000000000..e214b5b09 --- /dev/null +++ b/wallets/phantom/environment.d.ts @@ -0,0 +1,10 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + CI: boolean + HEADLESS: boolean + } + } +} + +export {} diff --git a/wallets/phantom/package.json b/wallets/phantom/package.json new file mode 100644 index 000000000..bb2b32058 --- /dev/null +++ b/wallets/phantom/package.json @@ -0,0 +1,54 @@ +{ + "name": "@synthetixio/synpress-phantom", + "version": "0.0.4", + "type": "module", + "exports": { + "./playwright": { + "types": "./types/playwright/index.d.ts", + "default": "./dist/playwright/index.js" + } + }, + "main": "./dist/index.js", + "types": "./types/index.d.ts", + "files": [ + "dist", + "src", + "types" + ], + "scripts": { + "build": "pnpm run clean && pnpm run build:dist && pnpm run build:types", + "build:cache": "synpress-cache test/playwright/wallet-setup --phantom --debug", + "build:cache:headless": "synpress-cache test/playwright/wallet-setup --phantom --headless", + "build:cache:headless:force": "synpress-cache test/playwright/wallet-setup --phantom --headless --force", + "build:dist": "tsup --tsconfig tsconfig.build.json", + "build:types": "tsc --emitDeclarationOnly --project tsconfig.build.json", + "clean": "rimraf dist types", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:playwright:headful": "playwright test", + "test:playwright:headless": "HEADLESS=true playwright test", + "test:playwright:headless:ui": "HEADLESS=true playwright test --ui", + "test:watch": "vitest watch", + "types:check": "tsc --noEmit" + }, + "dependencies": { + "@synthetixio/synpress-cache": "workspace:*", + "@synthetixio/synpress-core": "workspace:*", + "@viem/anvil": "0.0.7", + "fs-extra": "11.2.0", + "zod": "3.22.4" + }, + "devDependencies": { + "@synthetixio/synpress-tsconfig": "0.0.4", + "@types/fs-extra": "11.0.4", + "@types/node": "20.11.17", + "@vitest/coverage-v8": "1.2.2", + "rimraf": "5.0.5", + "tsup": "8.0.2", + "typescript": "5.3.3", + "vitest": "1.2.2" + }, + "peerDependencies": { + "@playwright/test": "1.48.2" + } +} diff --git a/wallets/phantom/playwright.config.ts b/wallets/phantom/playwright.config.ts new file mode 100644 index 000000000..1f2f04328 --- /dev/null +++ b/wallets/phantom/playwright.config.ts @@ -0,0 +1,60 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + // Look for test files in the "test/e2e" directory, relative to this configuration file. + testDir: './test/playwright/e2e', + + // We're increasing the timeout to 60 seconds to allow all traces to be recorded. + // Sometimes it threw an error saying that traces were not recorded in the 30 seconds timeout limit. + timeout: 60_000, + + // Run all tests in parallel. + fullyParallel: true, + + // Fail the build on CI if you accidentally left test.only in the source code. + forbidOnly: !!process.env.CI, + + // Fail all remaining tests on CI after the first failure. We want to reduce the feedback loop on CI to minimum. + maxFailures: process.env.CI ? 1 : 0, + + // Opt out of parallel tests on CI since it supports only 1 worker. + workers: process.env.CI ? 1 : undefined, + + // Concise 'dot' for CI, default 'html' when running locally. + // See https://playwright.dev/docs/test-reporters. + reporter: process.env.CI + ? [ + [ + 'html', + { + open: 'never', + outputFolder: `playwright-report-${process.env.HEADLESS ? 'headless' : 'headful'}` + } + ] + ] + : 'html', + + // Shared settings for all the projects below. + // See https://playwright.dev/docs/api/class-testoptions. + use: { + // We are using locally deployed Metamask Test Dapp for somePhantom tests. + baseURL: 'http://localhost:9999', + + // Collect all traces on CI, and only traces for failed tests when running locally. + // See https://playwright.dev/docs/trace-viewer. + trace: process.env.CI ? 'on' : 'retain-on-failure', + // Added for getting account address + permissions: ['clipboard-read'] + }, + + // Configure projects for major browsers. + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + } + ] +}) diff --git a/wallets/phantom/src/index.ts b/wallets/phantom/src/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/wallets/phantom/src/playwright/Phantom.ts b/wallets/phantom/src/playwright/Phantom.ts new file mode 100644 index 000000000..7cf6d9044 --- /dev/null +++ b/wallets/phantom/src/playwright/Phantom.ts @@ -0,0 +1,322 @@ +import type { BrowserContext, Page } from '@playwright/test' +import type { GasSettings } from '../type/GasSettings' +import type { Networks } from '../type/Networks' +import { PhantomAbstract } from '../type/PhantomAbstract' +import { CrashPage, HomePage, NotificationPage, OnboardingPage, UnlockPage } from './pages' + +const NO_EXTENSION_ID_ERROR = new Error('Phantom extensionId is not set') + +/** + * Phantom class for interacting with the Phantom extension in Playwright tests. + * + * This class provides methods to perform various operations on the Phantom extension, + * such as importing wallets, switching networks, confirming transactions, and more. + * + * @class + * @extends PhantomAbstract + */ +export class Phantom extends PhantomAbstract { + /** + * This property can be used to access selectors for the crash page. + * + * @public + * @readonly + */ + readonly crashPage: CrashPage + + /** + * This property can be used to access selectors for the onboarding page. + * + * @public + * @readonly + */ + readonly onboardingPage: OnboardingPage + + /** + * This property can be used to access selectors for the lock page. + * + * @public + * @readonly + */ + readonly unlockPage: UnlockPage + + /** + * This property can be used to access selectors for the home page. + * + * @public + * @readonly + */ + readonly homePage: HomePage + + /** + * This property can be used to access selectors for the notification page. + * + * @public + * @readonly + */ + readonly notificationPage: NotificationPage + + /** + * Creates an instance of Phantom. + * + * @param context - The Playwright BrowserContext in which the Phantom extension is running. + * @param page - The Playwright Page object representing the Phantom extension's main page. + * @param password - The password for the Phantom wallet. + * @param extensionId - The ID of the Phantom extension. Optional if no interaction with dapps is required. + */ + constructor( + readonly context: BrowserContext, + readonly page: Page, + override readonly password: string, + override readonly extensionId?: string + ) { + super(password, extensionId) + + this.crashPage = new CrashPage() + this.onboardingPage = new OnboardingPage(page) + this.unlockPage = new UnlockPage(page) + this.homePage = new HomePage(page) + this.notificationPage = new NotificationPage(page) + } + + /** + * Imports a wallet using the given seed phrase. + * + * @param seedPhrase - The seed phrase to import. + */ + async importWallet(seedPhrase: string): Promise { + await this.onboardingPage.importWallet(seedPhrase, this.password) + } + + /** + * Adds a new account with the given name. + * + * @param accountName - The name for the new account. + */ + async addNewAccount(accountName: string): Promise { + await this.homePage.addNewAccount(accountName) + } + + /** + * Renames the currently selected account. + * + * @param currentAccountName - The current account name. + * @param newAccountName - The new name for the account. + */ + async renameAccount(currentAccountName: string, newAccountName: string): Promise { + await this.homePage.renameAccount(currentAccountName, newAccountName) + } + + /** + * Imports a wallet using the given private key. + * + * @param network - Network that the wallet belongs to. + * @param privateKey - The private key to import. + * @param privateKey - Name given to the new wallet/account. + */ + async importWalletFromPrivateKey( + network: 'solana' | 'ethereum' | 'base' | 'polygon' | 'bitcoin', + privateKey: string, + walletName?: string + ): Promise { + await this.homePage.importWalletFromPrivateKey(network, privateKey, walletName) + } + + /** + * Switches to the account with the given name. + * + * @param accountName - The name of the account to switch to. + */ + async switchAccount(accountName: string): Promise { + await this.homePage.switchAccount(accountName) + } + + /** + * Gets the address of the currently selected account. + * + * @param network - Network that the address belongs to. + * @returns The account address. + */ + async getAccountAddress(network: Networks): Promise { + return await this.homePage.getAccountAddress(network) + } + + /** + * Connects Phantom to a dapp. + * + * @param accounts - Optional array of account addresses to connect. + * @throws {Error} If extensionId is not set. + */ + async connectToDapp(account?: string): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.connectToDapp(this.extensionId, account) + } + + /** + * Locks the Phantom wallet. + */ + async lock(): Promise { + await this.homePage.lock() + } + + /** + * Unlocks the Phantom wallet. + */ + async unlock(): Promise { + await this.unlockPage.unlock(this.password) + } + + /** + * Confirms a signature request. + * + * @throws {Error} If extensionId is not set. + */ + async confirmSignature(): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.signMessage(this.extensionId) + } + + /** + * Confirms a signature request with risk. + * + * @throws {Error} If extensionId is not set. + */ + async confirmSignatureWithRisk(): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.signMessageWithRisk(this.extensionId) + } + + /** + * Rejects a signature request. + * + * @throws {Error} If extensionId is not set. + */ + async rejectSignature(): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.rejectMessage(this.extensionId) + } + + /** + * Confirms a transaction. + * + * @param options - Optional gas settings for the transaction. + * @throws {Error} If extensionId is not set. + */ + async confirmTransaction(options?: { + gasSetting?: GasSettings + }): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.confirmTransaction(this.extensionId, options) + } + + /** + * Rejects a transaction. + * + * @throws {Error} If extensionId is not set. + */ + async rejectTransaction(): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.rejectTransaction(this.extensionId) + } + + /** + * Approves a token permission request. + * + * @param options - Optional settings for the approval. + * @throws {Error} If extensionId is not set. + */ + async approveTokenPermission(options?: { + spendLimit?: 'max' | number + gasSetting?: GasSettings + }): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.approveTokenPermission(this.extensionId, options) + } + + /** + * Rejects a token permission request. + * + * @throws {Error} If extensionId is not set. + */ + async rejectTokenPermission(): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.rejectTokenPermission(this.extensionId) + } + + /** + * Navigates to the home page or wallet dashboard. + */ + async goToHomePage(): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.homePage.goToHomePage(this.extensionId) + } + + /** + * Navigates back to the home page. + */ + async goBackToHomePage(): Promise { + await this.homePage.goBackToHomePage() + } + + /** + * Opens the settings page. + */ + async openSettings(): Promise { + await this.homePage.openSettings() + } + + /** + * Toggles the display of test networks. + */ + async toggleTestnetMode(): Promise { + await this.homePage.toggleTestnetMode() + } + + /** + * Resets the account. + */ + async resetApp(): Promise { + await this.homePage.resetApp() + } + + /** + * Connects Phantom to a dapp. + * + * @param accounts - Optional array of account addresses to connect. + * @throws {Error} If extensionId is not set. + */ + async closeUnsupportedNetworkWarning(): Promise { + if (!this.extensionId) { + throw NO_EXTENSION_ID_ERROR + } + + await this.notificationPage.closeUnsupportedNetworkWarning(this.extensionId) + } +} diff --git a/wallets/phantom/src/playwright/fixture-actions/getExtensionId.ts b/wallets/phantom/src/playwright/fixture-actions/getExtensionId.ts new file mode 100644 index 000000000..e570080d6 --- /dev/null +++ b/wallets/phantom/src/playwright/fixture-actions/getExtensionId.ts @@ -0,0 +1,46 @@ +import type { BrowserContext } from '@playwright/test' +import { z } from 'zod' + +const Extension = z.object({ + id: z.string(), + name: z.string() +}) + +const Extensions = z.array(Extension) + +/** + * Returns the extension ID for the given extension name. The ID is fetched from the `chrome://extensions` page. + * + * ::: tip + * This function soon will be removed to improve the developer experience! 😇 + * ::: + * + * @param context - The browser context. + * @param extensionName - The name of the extension, e.g., `Phantom`. + * + * @returns The extension ID. + */ +export async function getExtensionIdPhantom(context: BrowserContext, extensionName: string) { + const page = await context.newPage() + await page.goto('chrome://extensions') + + const unparsedExtensions = await page.evaluate('chrome.management.getAll()') + + const allExtensions = Extensions.parse(unparsedExtensions) + const targetExtension = allExtensions.find( + (extension) => extension.name.toLowerCase() === extensionName.toLowerCase() + ) + + if (!targetExtension) { + throw new Error( + [ + `[GetExtensionId] Extension with name ${extensionName} not found.`, + `Available extensions: ${allExtensions.map((extension) => extension.name).join(', ')}` + ].join('\n') + ) + } + + await page.close() + + return targetExtension.id +} diff --git a/wallets/phantom/src/playwright/fixture-actions/index.ts b/wallets/phantom/src/playwright/fixture-actions/index.ts new file mode 100644 index 000000000..b9c4f13e6 --- /dev/null +++ b/wallets/phantom/src/playwright/fixture-actions/index.ts @@ -0,0 +1,3 @@ +export * from './unlockForFixture' +export * from './getExtensionId' +export * from './prepareExtensionPhantom' diff --git a/wallets/phantom/src/playwright/fixture-actions/persistLocalStorage.ts b/wallets/phantom/src/playwright/fixture-actions/persistLocalStorage.ts new file mode 100644 index 000000000..e24deb66d --- /dev/null +++ b/wallets/phantom/src/playwright/fixture-actions/persistLocalStorage.ts @@ -0,0 +1,24 @@ +import type { BrowserContext } from '@playwright/test' + +export async function persistLocalStorage( + origins: { + origin: string + localStorage: { name: string; value: string }[] + }[], + context: BrowserContext +) { + const newPage = await context.newPage() + + for (const { origin, localStorage } of origins) { + const frame = newPage.mainFrame() + await frame.goto(origin) + + await frame.evaluate((localStorageData) => { + localStorageData.forEach(({ name, value }) => { + window.localStorage.setItem(name, value) + }) + }, localStorage) + } + + await newPage.close() +} diff --git a/wallets/phantom/src/playwright/fixture-actions/prepareExtensionPhantom.ts b/wallets/phantom/src/playwright/fixture-actions/prepareExtensionPhantom.ts new file mode 100644 index 000000000..e69de29bb diff --git a/wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts b/wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts new file mode 100644 index 000000000..54b760dd0 --- /dev/null +++ b/wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts @@ -0,0 +1,34 @@ +import type { Page } from '@playwright/test' +import { errors as playwrightErrors } from '@playwright/test' +import { Phantom } from '..' +import { waitForSpinnerToVanish } from '../utils/waitForSpinnerToVanish' + +/** + * A more advanced version of the `Phantom.unlock()` function that incorporates various workarounds for Phantom issues, among other things. + * This function should be used instead of the `Phantom.unlock()` when passing it to the `testWithSynpress` function. + * + * @param page - The Phantom tab page. + * @param password - The password of the Phantom wallet. + */ +export async function unlockForFixturePhantom(page: Page, password: string) { + const phantom = new Phantom(page.context(), page, password) + + await unlockWalletButReloadIfSpinnerDoesNotVanish(phantom) +} + +async function unlockWalletButReloadIfSpinnerDoesNotVanish(phantom: Phantom) { + try { + await phantom.unlock() + } catch (e) { + if (e instanceof playwrightErrors.TimeoutError) { + console.warn('[UnlockWalletButReloadIfSpinnerDoesNotVanish] Unlocking Phantom timed out. Reloading page...') + + const page = phantom.page + + await page.reload() + await waitForSpinnerToVanish(page) + } else { + throw e + } + } +} diff --git a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts new file mode 100644 index 000000000..5c83ef6c9 --- /dev/null +++ b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts @@ -0,0 +1,154 @@ +import path from 'node:path' +import { type Page, chromium, expect } from '@playwright/test' +import { test as base } from '@playwright/test' +import { + CACHE_DIR_NAME, + createTempContextDir, + defineWalletSetup, + removeTempContextDir +} from '@synthetixio/synpress-cache' +import fs from 'fs-extra' +import { prepareExtensionPhantom } from '../../prepareExtensionPhantom' +import { Phantom } from '../Phantom' +import { getExtensionIdPhantom, unlockForFixturePhantom } from '../fixture-actions' +import { persistLocalStorage } from '../fixture-actions/persistLocalStorage' +import { waitForPhantomWindowToBeStable } from '../utils/waitFor' + +type PhantomFixtures = { + _contextPath: string + phantom: Phantom + extensionId: string + phantomPage: Page + aavePage: Page + solanaSandboxPage: Page +} + +// If setup phantomPage in a fixture, browser does not handle it properly (even if ethereum.isConnected() is true, it's not reflected on the page). +let _phantomPage: Page + +export const phantomFixtures = (walletSetup: ReturnType, slowMo = 0) => { + return base.extend({ + _contextPath: async ({ browserName }, use, testInfo) => { + const contextPath = await createTempContextDir(browserName, testInfo.testId) + + await use(contextPath) + + const error = await removeTempContextDir(contextPath) + if (error) { + console.error(error) + } + }, + context: async ({ context: currentContext, _contextPath }, use) => { + const cacheDirPath = path.join(process.cwd(), CACHE_DIR_NAME, walletSetup.hash) + if (!(await fs.exists(cacheDirPath))) { + throw new Error(`Cache for ${walletSetup.hash} does not exist. Create it first!`) + } + + // Copying the cache to the temporary context directory. + await fs.copy(cacheDirPath, _contextPath) + + const phantomPath = await prepareExtensionPhantom() + + // We don't need the `--load-extension` arg since the extension is already loaded in the cache. + const browserArgs = [`--disable-extensions-except=${phantomPath}`] + + if (process.env.HEADLESS) { + browserArgs.push('--headless=new') + + if (slowMo > 0) { + console.warn('[WARNING] Slow motion makes no sense in headless mode. It will be ignored!') + } + } + + const context = await chromium.launchPersistentContext(_contextPath, { + headless: false, + args: browserArgs, + slowMo: process.env.HEADLESS ? 0 : slowMo + }) + + const { cookies, origins } = await currentContext.storageState() + + if (cookies) { + await context.addCookies(cookies) + } + if (origins && origins.length > 0) { + await persistLocalStorage(origins, context) + } + + const extensionId = await getExtensionIdPhantom(context, 'Phantom') + + _phantomPage = context.pages()[0] as Page + + await _phantomPage.goto(`chrome-extension://${extensionId}/popup.html`) + + await _phantomPage.waitForTimeout(1_000) + + await waitForPhantomWindowToBeStable(_phantomPage) + + await unlockForFixturePhantom(_phantomPage, walletSetup.walletPassword) + + await use(context) + + await context.close() + }, + phantomPage: async ({ context: _ }, use) => { + await use(_phantomPage) + }, + extensionId: async ({ context }, use) => { + const extensionId = await getExtensionIdPhantom(context, 'Phantom') + + await use(extensionId) + }, + phantom: async ({ context, extensionId }, use) => { + const phantom = new Phantom(context, _phantomPage, walletSetup.walletPassword, extensionId) + + await use(phantom) + }, + page: async ({ page }, use) => { + await page.goto('/') + + await use(page) + }, + aavePage: async ({ page, phantom }, use) => { + await page.goto('https://app.aave.com') + + await phantom.toggleTestnetMode() + + await page.locator('button#settings-button').click() + await page.locator('li:has-text("Testnet mode")').click() + await expect(page.getByRole('button', { name: 'TESTNET' })).toBeVisible() + + await page.getByRole('button', { name: 'Connect wallet' }).first().click() + await page.getByRole('button', { name: 'Phantom' }).click() + + await phantom.connectToDapp() + await phantom.page.waitForTimeout(1_000) + await phantom.closeUnsupportedNetworkWarning() + + await expect(page.getByText('0xf3...2266'), '"0xf3...2266" should be visible').toBeVisible() + + await use(page) + }, + solanaSandboxPage: async ({ page, phantom }, use) => { + await phantom.page.waitForTimeout(1_000) + await phantom.importWalletFromPrivateKey( + 'solana', + 'XQaKFLLSKbzpVzmfJrj4yUjAyFy2Eu7JcNdbPdnLuod2Uw3yf3tjGd4ha1DBfFdjkZFX1PZg3knth2Tz2tvd8C4' + ) + + await phantom.toggleTestnetMode() + + await page.goto('https://r3byv.csb.app/') + await page.locator('a:has-text("Yes, proceed to preview")').click() + await page.getByRole('button', { name: 'Connect to Phantom' }).click() + + await phantom.connectToDapp() + + await page.getByRole('button', { name: 'Clear Logs' }).click() + + await expect(page.getByText('Click a button and watch magic happen...')).toBeVisible() + + await use(page) + } + }) +} diff --git a/wallets/phantom/src/playwright/index.ts b/wallets/phantom/src/playwright/index.ts new file mode 100644 index 000000000..fe0d3a347 --- /dev/null +++ b/wallets/phantom/src/playwright/index.ts @@ -0,0 +1,3 @@ +export * from './Phantom' +export * from './fixtures/phantomFixtures' +export * from './fixture-actions' diff --git a/wallets/phantom/src/playwright/pages/CrashPage/page.ts b/wallets/phantom/src/playwright/pages/CrashPage/page.ts new file mode 100644 index 000000000..e731a6e94 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/CrashPage/page.ts @@ -0,0 +1,6 @@ +import Selectors from '../../../selectors/pages/CrashPage' + +export class CrashPage { + static readonly selectors = Selectors + readonly selectors = Selectors +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/addNewAccount.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/addNewAccount.ts new file mode 100644 index 000000000..305b7d9e2 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/addNewAccount.ts @@ -0,0 +1,19 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/HomePage' + +export async function addNewAccount(page: Page, accountName: string) { + // TODO: Use zod to validate this. + if (accountName.length === 0) { + throw new Error('[AddNewAccount] Account name cannot be an empty string') + } + + await page.locator(Selectors.accountMenu.accountButton).click() + + await page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton).click() + + await page.locator(Selectors.accountMenu.addAccountMenu.createNewAccountButton).click() + + await page.locator(Selectors.accountMenu.addAccountMenu.addNewAccountMenu.accountNameInput).fill(accountName) + + await page.locator(Selectors.accountMenu.addAccountMenu.addNewAccountMenu.createButton).click() +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts new file mode 100644 index 000000000..827cd60d9 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts @@ -0,0 +1,16 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/HomePage' +import type { Networks } from '../../../../type/Networks' + +// TODO - .getAccountAddress() to be updated for all networks +export default async function getAccountAddress(network: Networks, page: Page): Promise { + // Copy account address to clipboard + await page.locator(Selectors.accountMenu.accountName).hover() + await page.locator(Selectors[`${network}WalletAddress`]).click() + + // Get clipboard content + const handle = await page.evaluateHandle(() => navigator.clipboard.readText()) + const account = await handle.jsonValue() + + return account +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts new file mode 100644 index 000000000..297917351 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -0,0 +1,47 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/HomePage' +import type { Networks } from '../../../../type/Networks' +import { waitFor } from '../../../utils/waitFor' + +export async function importWalletFromPrivateKey( + page: Page, + network: Networks, + privateKey: string, + walletName?: string +) { + const extensionUrl = page.url() + await page.goto(extensionUrl.replace('onboarding', 'popup')) + + await page.locator(Selectors.accountMenu.accountButton).click() + + await page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton).click() + + await page.locator(Selectors.accountMenu.addAccountMenu.importAccountPrivateKeyButton).click() + + // SELECT NETWORK + if (network !== 'solana') { + await page.locator(Selectors.accountMenu.addAccountMenu.importAccountMenu.networkOpenMenu).click() + await page.locator(Selectors.accountMenu.addAccountMenu.importAccountMenu[`${network}Network`]).click() + } + + await page + .locator(Selectors.accountMenu.addAccountMenu.importAccountMenu.nameInput) + .fill(walletName ?? 'ImportedWallet') + + await page.locator(Selectors.accountMenu.addAccountMenu.importAccountMenu.privateKeyInput).fill(privateKey) + + const importButton = page.locator(Selectors.accountMenu.addAccountMenu.importAccountMenu.importButton) + + // TODO: Extract & make configurable + const isImportButtonEnabled = await waitFor(() => importButton.isEnabled(), 1_000, false) + + if (!isImportButtonEnabled) { + const errorText = await page.locator(Selectors.accountMenu.addAccountMenu.importAccountMenu.error).textContent({ + timeout: 1_000 // TODO: Extract & make configurable + }) + + throw new Error(`[ImportWalletFromPrivateKey] Importing failed due to error: ${errorText}`) + } + + await importButton.click() +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts new file mode 100644 index 000000000..71155737a --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts @@ -0,0 +1,9 @@ +export * from './popups' +export * from './lock' +export * from './importWalletFromPrivateKey' +export * from './switchAccount' +export * from './settings' +export * from './toggleTestnetMode' +export * from './addNewAccount' +export * from './renameAccount' +export { default as getAccountAddress } from './getAccountAddress' diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/lock.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/lock.ts new file mode 100644 index 000000000..07b28874b --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/lock.ts @@ -0,0 +1,10 @@ +import type { Page } from '@playwright/test' + +import Selectors from '../../../../selectors/pages/HomePage' + +export async function lock(page: Page) { + await page.locator(Selectors.accountMenu.accountButton).click() + await page.locator(Selectors.accountMenu.settings).click() + + await page.locator(Selectors.settings.lockWallet).click() +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts new file mode 100644 index 000000000..36378580b --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts @@ -0,0 +1,12 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../../selectors/pages/HomePage' +import { clickLocatorIfCondition } from '../../../../utils/clickLocatorIfCondition' + +// Closes the popover with news, rainbows, unicorns, and other stuff. +export async function closePopover(page: Page) { + // We're using `first()` here just in case there are multiple popovers, which happens sometimes. + const closeButtonLocator = page.locator(Selectors.popover.closeButton).first() + + // TODO: Extract & make configurable + await clickLocatorIfCondition(closeButtonLocator, () => closeButtonLocator.isVisible(), 1_000) +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts new file mode 100644 index 000000000..1664221e0 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts @@ -0,0 +1,10 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../../selectors/pages/HomePage' +import { clickLocatorIfCondition } from '../../../../utils/clickLocatorIfCondition' + +export async function closeRecoveryPhraseReminder(page: Page) { + const closeButtonLocator = page.locator(Selectors.recoveryPhraseReminder.gotItButton) + + // TODO: Extract & make configurable + await clickLocatorIfCondition(closeButtonLocator, () => closeButtonLocator.isVisible(), 1_000) +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts new file mode 100644 index 000000000..2c83dc704 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts @@ -0,0 +1,2 @@ +export * from './closePopover' +export * from './closeRecoveryPhraseReminder' diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/renameAccount.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/renameAccount.ts new file mode 100644 index 000000000..d6fc5d4be --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/renameAccount.ts @@ -0,0 +1,43 @@ +import { type Page, expect } from '@playwright/test' +import Selectors from '../../../../selectors/pages/HomePage' +import { allTextContents } from '../../../utils/allTextContents' + +export async function renameAccount(page: Page, currentAccountName: string, newAccountName: string) { + // TODO: Use zod to validate this. + if (newAccountName.length === 0) { + throw new Error('[RenameAccount] Account name cannot be an empty string') + } + + await page.locator(Selectors.accountMenu.accountButton).click() + + let accountNames: string[] = [] + + await expect(async () => { + const accountNamesLocators = await page.locator(Selectors.accountMenu.accountNames).all() + + accountNames = await allTextContents(accountNamesLocators) + + expect(accountNames.length).toBeGreaterThan(0) + }).toPass() + + const seekedAccountNames = accountNames.filter( + (name) => name.toLocaleLowerCase() === currentAccountName.toLocaleLowerCase() + ) + + if (seekedAccountNames.length === 0) { + throw new Error(`[SwitchAccount] Account with name ${currentAccountName} not found`) + } + + await page.locator(Selectors.accountMenu.manageAccountsButton).click() + + await page.locator(Selectors.manageAccountButton(currentAccountName)).click() + + await page.locator(Selectors.editAccountMenu.accountNameButton).click() + + await page.locator(Selectors.accountMenu.addAccountMenu.addNewAccountMenu.accountNameInput).fill(newAccountName) + + await page.locator(Selectors.accountMenu.renameAccountMenu.saveButton).click() + + // Verify that account has been renamed + await expect(page.locator(Selectors.editAccountMenu.accountNameButton)).toContainText(newAccountName) +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/settings.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/settings.ts new file mode 100644 index 000000000..e9ad22b5f --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/settings.ts @@ -0,0 +1,23 @@ +import type { Page } from '@playwright/test' +import { homePage, notificationPage } from '../../../../selectors' + +async function openSettings(page: Page) { + await page.locator(homePage.accountMenu.accountButton).click() + await page.locator(homePage.accountMenu.settings).click() +} + +async function resetApp(page: Page) { + await openSettings(page) + + await page.locator(homePage.settings.securityAndPrivacyButton).click() + + await page.waitForSelector(homePage.settings.securityAndPrivacy.resetApp) + await page.locator(homePage.settings.securityAndPrivacy.resetApp).click() + + await page.locator(notificationPage.ActionFooter.continueActionButton).click() +} + +export const settings = { + openSettings, + resetApp +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/switchAccount.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/switchAccount.ts new file mode 100644 index 000000000..07a93ae80 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/switchAccount.ts @@ -0,0 +1,30 @@ +import { type Locator, type Page, expect } from '@playwright/test' +import Selectors from '../../../../selectors/pages/HomePage' +import { allTextContents } from '../../../utils/allTextContents' + +export async function switchAccount(page: Page, accountName: string) { + await page.locator(Selectors.accountMenu.accountButton).click() + + let accountNamesLocators: Locator[] = [] + let accountNames: string[] = [] + + await expect(async () => { + accountNamesLocators = await page.locator(Selectors.accountMenu.accountNames).all() + + accountNames = await allTextContents(accountNamesLocators) + + expect(accountNames.length).toBeGreaterThan(0) + }).toPass() + + const seekedAccountNames = accountNames.filter((name) => name.toLocaleLowerCase() === accountName.toLocaleLowerCase()) + + if (seekedAccountNames.length === 0) { + throw new Error(`[SwitchAccount] Account with name ${accountName} not found`) + } + + // biome-ignore lint/style/noNonNullAssertion: this non-null assertion is intentional + const accountIndex = accountNames.indexOf(seekedAccountNames[0]!) // TODO: handle the undefined here better + + // biome-ignore lint/style/noNonNullAssertion: this non-null assertion is intentional + await accountNamesLocators[accountIndex]!.click() // TODO: handle the undefined here better +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts new file mode 100644 index 000000000..f72df6096 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts @@ -0,0 +1,11 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/HomePage' + +export async function openTestnetSection(page: Page) { + const toggleButtonLocator = page.locator(Selectors.networkDropdown.showTestNetworksToggle) + const classes = await toggleButtonLocator.getAttribute('class') + if (classes?.includes('toggle-button--off')) { + await toggleButtonLocator.click() + await page.locator(Selectors.networkDropdown.toggleOn).isChecked() + } +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/toggleTestnetMode.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/toggleTestnetMode.ts new file mode 100644 index 000000000..cf40f4d58 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/toggleTestnetMode.ts @@ -0,0 +1,14 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/HomePage' + +// Toggling this through the network dropdown instead of the settings page is a better approach. +// This is in most cases the faster approach, but it's also more reliable. +export async function toggleTestnetMode(page: Page) { + await page.locator(Selectors.accountMenu.accountButton).click() + + await page.locator(Selectors.accountMenu.settings).click() + + await page.locator(Selectors.settings.developerSettingsButton).click() + + await page.locator(Selectors.settings.devSettings.toggleTestnetMode).click() +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/page.ts b/wallets/phantom/src/playwright/pages/HomePage/page.ts new file mode 100644 index 000000000..7fc9b3ff1 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/page.ts @@ -0,0 +1,72 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../selectors/pages/HomePage' +import type { Networks } from '../../../type/Networks' +import { + addNewAccount, + getAccountAddress, + importWalletFromPrivateKey, + lock, + renameAccount, + settings, + switchAccount, + toggleTestnetMode +} from './actions' + +export class HomePage { + static readonly selectors = Selectors + readonly selectors = Selectors + + readonly page: Page + + constructor(page: Page) { + this.page = page + } + + async goToHomePage(extensionId: string) { + await this.page.goto(`chrome-extension://${extensionId}/popup.html`) + } + + async goBackToHomePage() { + await this.page.locator(Selectors.settings.closeSettingsButton).click() + } + + async lock() { + await lock(this.page) + } + + async addNewAccount(accountName: string) { + await addNewAccount(this.page, accountName) + } + + async renameAccount(currentAccountName: string, newAccountName: string) { + await renameAccount(this.page, currentAccountName, newAccountName) + } + + async getAccountAddress(network: Networks) { + return await getAccountAddress(network, this.page) + } + + async importWalletFromPrivateKey( + network: 'solana' | 'ethereum' | 'base' | 'polygon' | 'bitcoin', + privateKey: string, + walletName?: string + ) { + await importWalletFromPrivateKey(this.page, network, privateKey, walletName) + } + + async switchAccount(accountName: string) { + await switchAccount(this.page, accountName) + } + + async openSettings() { + await settings.openSettings(this.page) + } + + async toggleTestnetMode() { + await toggleTestnetMode(this.page) + } + + async resetApp() { + await settings.resetApp(this.page) + } +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts new file mode 100644 index 000000000..303883003 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts @@ -0,0 +1,30 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/NotificationPage' +import type { GasSettings } from '../../../../type/GasSettings' +import { transaction } from './transaction' + +const editTokenPermission = async (notificationPage: Page, customSpendLimit: 'max' | number) => { + if (customSpendLimit === 'max') { + await notificationPage.locator(Selectors.PermissionPage.approve.maxButton).click() + return + } + + await notificationPage + .locator(Selectors.PermissionPage.approve.customSpendingCapInput) + .fill(customSpendLimit.toString()) +} + +const approveTokenPermission = async (notificationPage: Page, gasSetting: GasSettings) => { + // Approve flow is identical to the confirm transaction flow after we click the "Next" button. + await transaction.confirm(notificationPage, gasSetting) +} + +const rejectTokenPermission = async (notificationPage: Page) => { + await notificationPage.locator(Selectors.ActionFooter.cancelActionButton).click() +} + +export const approvePermission = { + editTokenPermission, + approve: approveTokenPermission, + reject: rejectTokenPermission +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/closeUnsupportedNetworkWarning.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/closeUnsupportedNetworkWarning.ts new file mode 100644 index 000000000..783dde53c --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/closeUnsupportedNetworkWarning.ts @@ -0,0 +1,10 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/NotificationPage' + +async function closeWarning(notificationPage: Page) { + await notificationPage.locator(Selectors.ActionFooter.closeActionButton).click() +} + +export async function closeUnsupportedNetworkWarning(notificationPage: Page) { + await closeWarning(notificationPage) +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/connectToDapp.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/connectToDapp.ts new file mode 100644 index 000000000..eee302c44 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/connectToDapp.ts @@ -0,0 +1,16 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/NotificationPage' +import { switchAccount } from '../../HomePage/actions' + +async function confirmConnection(notificationPage: Page) { + await notificationPage.locator(Selectors.ActionFooter.connectActionButton).click() +} + +// By default, the last account will be selected. If you want to select a specific account, pass `account` parameter. +export async function connectToDapp(notificationPage: Page, account?: string) { + if (account) { + await switchAccount(notificationPage, account) + } + + await confirmConnection(notificationPage) +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts new file mode 100644 index 000000000..9ed19e5a2 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts @@ -0,0 +1,7 @@ +export * from './approvePermission' +export * from './closeUnsupportedNetworkWarning' +export * from './connectToDapp' +export * from './signSimpleMessage' +export * from './signStructuredMessage' +export * from './token' +export * from './transaction' diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/signSimpleMessage.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/signSimpleMessage.ts new file mode 100644 index 000000000..9f2a73e47 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/signSimpleMessage.ts @@ -0,0 +1,23 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/NotificationPage' + +const signMessage = async (notificationPage: Page) => { + await notificationPage.locator(Selectors.ActionFooter.confirmActionButton).click() +} + +const rejectMessage = async (notificationPage: Page) => { + await notificationPage.locator(Selectors.ActionFooter.cancelActionButton).click() +} + +const signMessageWithRisk = async (notificationPage: Page) => { + await notificationPage.locator(Selectors.SignaturePage.riskModal.proceedAnyway).click() + await notificationPage.locator(Selectors.SignaturePage.riskModal.confirmUnsafe).click() + await notificationPage.locator(Selectors.SignaturePage.riskModal.acknowledgeRisks).click() + await notificationPage.locator(Selectors.SignaturePage.riskModal.reconfirmUnsafe).click() +} + +export const signSimpleMessage = { + sign: signMessage, + reject: rejectMessage, + signWithRisk: signMessageWithRisk +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/signStructuredMessage.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/signStructuredMessage.ts new file mode 100644 index 000000000..0283b9c41 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/signStructuredMessage.ts @@ -0,0 +1,25 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/NotificationPage' + +const signMessage = async (notificationPage: Page) => { + const scrollDownButton = notificationPage.locator(Selectors.SignaturePage.structuredMessage.scrollDownButton) + const signButton = notificationPage.locator(Selectors.ActionFooter.confirmActionButton) + + while (await signButton.isDisabled()) { + await scrollDownButton.click() + } + + await signButton.click() +} + +const rejectMessage = async (notificationPage: Page) => { + await notificationPage.locator(Selectors.ActionFooter.cancelActionButton).click() +} + +// Used for: +// - `eth_signTypedData_v3` +// - `eth_signTypedData_v4` +export const signStructuredMessage = { + sign: signMessage, + reject: rejectMessage +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts new file mode 100644 index 000000000..b909eff66 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts @@ -0,0 +1,10 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/NotificationPage' + +async function addNew(notificationPage: Page) { + await notificationPage.locator(Selectors.ActionFooter.confirmActionButton).click() +} + +export const token = { + addNew +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/transaction.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/transaction.ts new file mode 100644 index 000000000..55e81bacd --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/transaction.ts @@ -0,0 +1,64 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/NotificationPage' +import { GasSettingValidation, type GasSettings } from '../../../../type/GasSettings' +import { waitFor } from '../../../utils/waitFor' + +const confirmTransaction = async (notificationPage: Page, options: GasSettings) => { + const gasSetting = GasSettingValidation.parse(options) + + const handleNftSetApprovalForAll = async (page: Page) => { + try { + const nftApproveButtonLocator = page.locator( + Selectors.TransactionPage.nftApproveAllConfirmationPopup.approveButton + ) + const isNfTPopupHidden = await waitFor(() => nftApproveButtonLocator.isHidden(), 3_000, false) + + if (!isNfTPopupHidden) { + await nftApproveButtonLocator.click() + } + } catch (e) { + if (page.isClosed()) { + return + } + + throw new Error(`Failed to handle NFT setApprovalForAll popup: ${e}`) + } + } + + // By default, the `Average` gas setting is used. + if (gasSetting === 'Average') { + await notificationPage.locator(Selectors.ActionFooter.confirmActionButton).click() + + await handleNftSetApprovalForAll(notificationPage) + + return + } + + // TODO: This button can be invisible in case of a network issue. Verify this, and handle in the future. + await notificationPage.locator(Selectors.TransactionPage.editGasFeeMenu.editGasFeeButton).click() + + const handleSlowOrFastGasSetting = async (selector: string) => { + await notificationPage.locator(selector).click() + } + + if (gasSetting === 'Slow') { + await handleSlowOrFastGasSetting(Selectors.TransactionPage.editGasFeeMenu.slowGasFeeButton) + } else if (gasSetting === 'Fast') { + await handleSlowOrFastGasSetting(Selectors.TransactionPage.editGasFeeMenu.fastGasFeeButton) + } + + await notificationPage.locator(Selectors.TransactionPage.editGasFeeMenu.saveButton).click() + + await notificationPage.locator(Selectors.ActionFooter.confirmActionButton).click() + + await handleNftSetApprovalForAll(notificationPage) +} + +const rejectTransaction = async (notificationPage: Page) => { + await notificationPage.locator(Selectors.ActionFooter.cancelActionButton).click() +} + +export const transaction = { + confirm: confirmTransaction, + reject: rejectTransaction +} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/page.ts b/wallets/phantom/src/playwright/pages/NotificationPage/page.ts new file mode 100644 index 000000000..d00a8ab4e --- /dev/null +++ b/wallets/phantom/src/playwright/pages/NotificationPage/page.ts @@ -0,0 +1,104 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../selectors/pages/NotificationPage' +import type { GasSettings } from '../../../type/GasSettings' +import { getNotificationPageAndWaitForLoad } from '../../utils/getNotificationPageAndWaitForLoad' +import { + approvePermission, + closeUnsupportedNetworkWarning, + connectToDapp, + signSimpleMessage, + signStructuredMessage, + transaction +} from './actions' + +export class NotificationPage { + static readonly selectors = Selectors + readonly selectors = Selectors + + readonly page: Page + + constructor(page: Page) { + this.page = page + } + + async connectToDapp(extensionId: string, account?: string) { + const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) + + await connectToDapp(notificationPage, account) + } + + // TODO: Revisit this logic in the future to see if we can increase the performance by utilizing `Promise.race`. + private async beforeMessageSignature(extensionId: string) { + const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) + + const scrollButton = notificationPage.locator(Selectors.SignaturePage.structuredMessage.scrollDownButton) + const isScrollButtonPresent = (await scrollButton.count()) > 0 + + let isScrollButtonVisible = false + if (isScrollButtonPresent) { + await scrollButton.waitFor({ state: 'visible' }) + isScrollButtonVisible = true + } + + return { + notificationPage, + isScrollButtonVisible + } + } + + async signMessage(extensionId: string) { + const { notificationPage, isScrollButtonVisible } = await this.beforeMessageSignature(extensionId) + + if (isScrollButtonVisible) { + await signStructuredMessage.sign(notificationPage) + } else { + await signSimpleMessage.sign(notificationPage) + } + } + + async signMessageWithRisk(extensionId: string) { + const { notificationPage } = await this.beforeMessageSignature(extensionId) + + await signSimpleMessage.signWithRisk(notificationPage) + } + + async rejectMessage(extensionId: string) { + const { notificationPage, isScrollButtonVisible } = await this.beforeMessageSignature(extensionId) + + if (isScrollButtonVisible) { + await signStructuredMessage.reject(notificationPage) + } else { + await signSimpleMessage.reject(notificationPage) + } + } + + async confirmTransaction(extensionId: string, options?: { gasSetting?: GasSettings }) { + const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) + + await transaction.confirm(notificationPage, options?.gasSetting ?? 'Average') + } + + async rejectTransaction(extensionId: string) { + const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) + + await transaction.reject(notificationPage) + } + + async approveTokenPermission(extensionId: string, options?: { gasSetting?: GasSettings }) { + const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) + + await approvePermission.approve(notificationPage, options?.gasSetting ?? 'Average') + } + + async rejectTokenPermission(extensionId: string) { + const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) + + await approvePermission.reject(notificationPage) + } + + async closeUnsupportedNetworkWarning(extensionId: string, account?: string) { + const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) + + await closeUnsupportedNetworkWarning(notificationPage) + } +} diff --git a/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/confirmSecretRecoveryPhrase.ts b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/confirmSecretRecoveryPhrase.ts new file mode 100644 index 000000000..074187374 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/confirmSecretRecoveryPhrase.ts @@ -0,0 +1,21 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../../selectors/pages/OnboardingPage' + +const StepSelectors = Selectors.SecretRecoveryPhrasePageSelectors.recoveryStep + +export async function confirmSecretRecoveryPhrase(page: Page, seedPhrase: string) { + const seedPhraseWords = seedPhrase.split(' ') + + for (const [index, word] of seedPhraseWords.entries()) { + await page.locator(StepSelectors.secretRecoveryPhraseWord(index)).fill(word) + } + + await page.locator(StepSelectors.confirmSecretRecoveryPhraseButton).click() + + if (await page.locator(StepSelectors.error).isVisible({ timeout: 2_000 })) { + const errorText = await page.locator(StepSelectors.error).textContent({ + timeout: 1000 + }) + throw new Error(`[ConfirmSecretRecoveryPhrase] Invalid seed phrase. Error from Phantom: ${errorText}`) + } +} diff --git a/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/createPassword.ts b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/createPassword.ts new file mode 100644 index 000000000..cb6da9e9c --- /dev/null +++ b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/createPassword.ts @@ -0,0 +1,14 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../../selectors/pages/OnboardingPage' + +const StepSelectors = Selectors.SecretRecoveryPhrasePageSelectors.passwordStep + +export async function createPassword(page: Page, password: string) { + await page.locator(StepSelectors.passwordInput).fill(password) + await page.locator(StepSelectors.confirmPasswordInput).fill(password) + + // Using `locator.click()` instead of `locator.check()` as a workaround due to dynamically appearing elements. + await page.locator(StepSelectors.acceptTermsCheckbox).click() + + await page.locator(StepSelectors.continue).click() +} diff --git a/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/index.ts b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/index.ts new file mode 100644 index 000000000..79b348f20 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './confirmSecretRecoveryPhrase' +export * from './createPassword' diff --git a/wallets/phantom/src/playwright/pages/OnboardingPage/actions/importWallet.ts b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/importWallet.ts new file mode 100644 index 000000000..0558703ff --- /dev/null +++ b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/importWallet.ts @@ -0,0 +1,25 @@ +import { type Page, expect } from '@playwright/test' +import Selectors from '../../../../selectors/pages/OnboardingPage' +import { confirmSecretRecoveryPhrase, createPassword } from './helpers' + +export async function importWallet(page: Page, seedPhrase: string, password: string) { + await page.locator(Selectors.GetStartedPageSelectors.importWallet).click() + + await page.locator(Selectors.GetStartedPageSelectors.importRecoveryPhraseButton).click() + + await confirmSecretRecoveryPhrase(page, seedPhrase) + + await expect( + page.locator(Selectors.SecretRecoveryPhrasePageSelectors.viewAccountsButton), + 'Import accounts success screen should be visible' + ).toBeVisible({ timeout: 60_000 }) + + await page.locator(Selectors.SecretRecoveryPhrasePageSelectors.continueButton).click() + + await createPassword(page, password) + + await expect( + page.locator(Selectors.SecretRecoveryPhrasePageSelectors.allDone), + 'All Done success screen should be visible' + ).toBeVisible({ timeout: 10_000 }) +} diff --git a/wallets/phantom/src/playwright/pages/OnboardingPage/actions/index.ts b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/index.ts new file mode 100644 index 000000000..2b7f14e22 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/OnboardingPage/actions/index.ts @@ -0,0 +1 @@ +export * from './importWallet' diff --git a/wallets/phantom/src/playwright/pages/OnboardingPage/page.ts b/wallets/phantom/src/playwright/pages/OnboardingPage/page.ts new file mode 100644 index 000000000..2c03651b4 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/OnboardingPage/page.ts @@ -0,0 +1,18 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../selectors/pages/OnboardingPage' +import { importWallet } from './actions' + +export class OnboardingPage { + static readonly selectors = Selectors + readonly selectors = Selectors + + readonly page: Page + + constructor(page: Page) { + this.page = page + } + + async importWallet(seedPhrase: string, password: string) { + return await importWallet(this.page, seedPhrase, password) + } +} diff --git a/wallets/phantom/src/playwright/pages/UnlockPage/actions/index.ts b/wallets/phantom/src/playwright/pages/UnlockPage/actions/index.ts new file mode 100644 index 000000000..b0dce8ac2 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/UnlockPage/actions/index.ts @@ -0,0 +1 @@ +export * from './unlock' diff --git a/wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts b/wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts new file mode 100644 index 000000000..7130b3fbe --- /dev/null +++ b/wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts @@ -0,0 +1,10 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../../selectors/pages/UnlockPage' +import { waitForSpinnerToVanish } from '../../../utils/waitForSpinnerToVanish' + +export async function unlock(page: Page, password: string) { + await page.locator(Selectors.passwordInput).fill(password) + await page.locator(Selectors.submitButton).click() + + await waitForSpinnerToVanish(page) +} diff --git a/wallets/phantom/src/playwright/pages/UnlockPage/page.ts b/wallets/phantom/src/playwright/pages/UnlockPage/page.ts new file mode 100644 index 000000000..85e197742 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/UnlockPage/page.ts @@ -0,0 +1,18 @@ +import type { Page } from '@playwright/test' +import Selectors from '../../../selectors/pages/UnlockPage' +import { unlock } from './actions' + +export class UnlockPage { + static readonly selectors = Selectors + readonly selectors = Selectors + + readonly page: Page + + constructor(page: Page) { + this.page = page + } + + async unlock(password: string) { + await unlock(this.page, password) + } +} diff --git a/wallets/phantom/src/playwright/pages/index.ts b/wallets/phantom/src/playwright/pages/index.ts new file mode 100644 index 000000000..78f98ea2c --- /dev/null +++ b/wallets/phantom/src/playwright/pages/index.ts @@ -0,0 +1,5 @@ +export * from './OnboardingPage/page' +export * from './CrashPage/page' +export * from './UnlockPage/page' +export * from './HomePage/page' +export * from './NotificationPage/page' diff --git a/wallets/phantom/src/playwright/utils/allTextContents.ts b/wallets/phantom/src/playwright/utils/allTextContents.ts new file mode 100644 index 000000000..09fa95be2 --- /dev/null +++ b/wallets/phantom/src/playwright/utils/allTextContents.ts @@ -0,0 +1,10 @@ +import type { Locator } from '@playwright/test' +import { z } from 'zod' + +// Custom implementation of `locator.allTextContents()` that is not utilizing `.map` which is not accessible under Phantom's scuttling mode. +export async function allTextContents(locators: Locator[]) { + const names = await Promise.all(locators.map((locator) => locator.textContent())) + + // We're making sure that the return type is `string[]` same as `locator.allTextContents()`. + return names.map((name) => z.string().parse(name)) +} diff --git a/wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts b/wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts new file mode 100644 index 000000000..ce919a455 --- /dev/null +++ b/wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts @@ -0,0 +1,10 @@ +import type { Locator } from '@playwright/test' +import { waitFor } from './waitFor' + +// TODO: Extract & make configurable +export async function clickLocatorIfCondition(locator: Locator, condition: () => Promise, timeout = 3_000) { + const shouldClick = await waitFor(condition, timeout, false) + if (shouldClick) { + await locator.click() + } +} diff --git a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts new file mode 100644 index 000000000..7fb17137c --- /dev/null +++ b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts @@ -0,0 +1,27 @@ +import type { BrowserContext, Page } from '@playwright/test' +import { waitForPhantomLoad, waitUntilStable } from './waitFor' + +export async function getNotificationPageAndWaitForLoad(context: BrowserContext, extensionId: string) { + const notificationPageUrl = `chrome-extension://${extensionId}/notification.html` + + const isNotificationPage = (page: Page) => page.url().includes(notificationPageUrl) + + // Check if notification page is already open. + let notificationPage = context.pages().find(isNotificationPage) + + if (!notificationPage) { + notificationPage = await context.waitForEvent('page', { + predicate: isNotificationPage + }) + } + + await waitUntilStable(notificationPage as Page) + + // Set pop-up window viewport size to resemble the actual Phantom pop-up window. + await notificationPage.setViewportSize({ + width: 360, + height: 592 + }) + + return await waitForPhantomLoad(notificationPage) +} diff --git a/wallets/phantom/src/playwright/utils/toggle.ts b/wallets/phantom/src/playwright/utils/toggle.ts new file mode 100644 index 000000000..cfdcc8354 --- /dev/null +++ b/wallets/phantom/src/playwright/utils/toggle.ts @@ -0,0 +1,32 @@ +import type { Locator } from '@playwright/test' +import { waitFor } from './waitFor' + +export async function toggle(toggleLocator: Locator) { + // TODO: Extract timeout + const classes = await toggleLocator.getAttribute('class', { timeout: 3_000 }) + + if (!classes) { + throw new Error('[ToggleShowTestNetworks] Toggle class returned null') + } + + const isOn = classes.includes('toggle-button--on') + + await toggleLocator.click() + + const waitForAction = async () => { + const classes = await toggleLocator.getAttribute('class') + + if (!classes) { + throw new Error('[ToggleShowTestNetworks] Toggle class returned null inside waitFor') + } + + if (isOn) { + return classes.includes('toggle-button--off') + } + + return classes.includes('toggle-button--on') + } + + // TODO: Extract timeout + await waitFor(waitForAction, 3_000, true) +} diff --git a/wallets/phantom/src/playwright/utils/waitFor.ts b/wallets/phantom/src/playwright/utils/waitFor.ts new file mode 100644 index 000000000..f9378161e --- /dev/null +++ b/wallets/phantom/src/playwright/utils/waitFor.ts @@ -0,0 +1,138 @@ +import type { Page } from '@playwright/test' +import { errors } from '@playwright/test' +import { LoadingSelectors } from '../../selectors' +import { ErrorSelectors } from '../../selectors' + +const DEFAULT_TIMEOUT = 2000 + +let retries = 0 + +export const waitToBeHidden = async (selector: string, page: Page) => { + // info: waits for 60 seconds + const locator = page.locator(selector) + for (const element of await locator.all()) { + if ((await element.count()) > 0 && retries < 300) { + retries++ + await page.waitForTimeout(200) + await module.exports.waitToBeHidden(selector, page) + } else if (retries >= 300) { + retries = 0 + throw new Error(`[waitToBeHidden] Max amount of retries reached while waiting for ${selector} to disappear.`) + } + retries = 0 + } +} + +export const waitUntilStable = async (page: Page) => { + await page.waitForLoadState('load', { timeout: 10_000 }) + await page.waitForLoadState('domcontentloaded', { timeout: 10_000 }) + await page.waitForLoadState('networkidle', { timeout: 10_000 }) +} + +export const waitForSelector = async (selector: string, page: Page, timeout: number) => { + await waitUntilStable(page) + + try { + await page.waitForSelector(selector, { state: 'hidden', timeout }) + } catch (error) { + if (error instanceof errors.TimeoutError) { + console.log(`Loading indicator '${selector}' not found - continuing.`) + } else { + console.log(`Error while waiting for loading indicator '${selector}' to disappear`) + throw error + } + } +} + +export const waitForPhantomLoad = async (page: Page) => { + await Promise.all( + LoadingSelectors.loadingIndicators.map(async (selector) => { + await waitForSelector(selector, page, DEFAULT_TIMEOUT) + }) + ) + .then(() => { + return true + }) + .catch((error) => { + console.error('Error: ', error) + }) + + return page +} + +export const waitForPhantomWindowToBeStable = async (page: Page) => { + await waitForPhantomLoad(page) + if ((await page.locator(ErrorSelectors.loadingOverlayErrorButtons).count()) > 0) { + const retryButton = await page.locator(ErrorSelectors.loadingOverlayErrorButtonsRetryButton) + await retryButton.click() + await waitForSelector(LoadingSelectors.loadingOverlay, page, DEFAULT_TIMEOUT) + } + await fixCriticalError(page) +} + +export const fixCriticalError = async (page: Page) => { + for (let times = 0; times < 5; times++) { + if ((await page.locator(ErrorSelectors.criticalError).count()) > 0) { + console.log('[fixCriticalError] Phantom crashed with critical error, refreshing..') + if (times <= 3) { + await page.reload() + await waitForPhantomWindowToBeStable(page) + } else if (times === 4) { + const restartButton = await page.locator(ErrorSelectors.criticalErrorRestartButton) + await restartButton.click() + await waitForPhantomWindowToBeStable(page) + } else { + throw new Error('[fixCriticalError] Max amount of retries to fix critical phantom error has been reached.') + } + } else if ((await page.locator(ErrorSelectors.errorPage).count()) > 0) { + console.log('[fixCriticalError] Phantom crashed with error, refreshing..') + if (times <= 4) { + await page.reload() + await waitForPhantomWindowToBeStable(page) + } else { + throw new Error('[fixCriticalError] Max amount of retries to fix critical phantom error has been reached.') + } + } else { + break + } + } +} + +// Inlining the sleep function here cause this is one of the few places in the entire codebase where sleep should be used! +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + +const timeouts = [0, 20, 50, 100, 100, 500] as const + +// TODO: Box this function. +// This functions mimics the one found in Playwright with a few small differences. +// Custom implementation is needed because Playwright lists errors in the report even if they are caught. +export async function waitFor(action: () => Promise, timeout: number, shouldThrow = true) { + let timeoutsSum = 0 + let timeoutIndex = 0 + + let reachedTimeout = false + + while (!reachedTimeout) { + let nextTimeout = timeouts.at(Math.min(timeoutIndex++, timeouts.length - 1)) as number + + if (timeoutsSum + nextTimeout > timeout) { + nextTimeout = timeout - timeoutsSum + reachedTimeout = true + } else { + timeoutsSum += nextTimeout + } + + await sleep(nextTimeout) + + const result = await action() + if (result) { + return result + } + } + + if (shouldThrow) { + throw new Error(`Timeout ${timeout}ms exceeded.`) + } + + return false +} diff --git a/wallets/phantom/src/playwright/utils/waitForSpinnerToVanish.ts b/wallets/phantom/src/playwright/utils/waitForSpinnerToVanish.ts new file mode 100644 index 000000000..77043750a --- /dev/null +++ b/wallets/phantom/src/playwright/utils/waitForSpinnerToVanish.ts @@ -0,0 +1,13 @@ +import type { Page } from '@playwright/test' +import { LoadingSelectors } from '../../selectors' + +// TODO: Should we decrease the timeout? +// TODO: Not sure if hard coding the timeout is a good idea but must be enough for now. +const DEFAULT_TIMEOUT = 10_000 + +export async function waitForSpinnerToVanish(page: Page) { + await page.locator(LoadingSelectors.spinner).waitFor({ + state: 'hidden', + timeout: DEFAULT_TIMEOUT + }) +} diff --git a/wallets/phantom/src/prepareExtensionPhantom.ts b/wallets/phantom/src/prepareExtensionPhantom.ts new file mode 100644 index 000000000..73556089a --- /dev/null +++ b/wallets/phantom/src/prepareExtensionPhantom.ts @@ -0,0 +1,31 @@ +import path from 'node:path' +import { downloadFile, ensureCacheDirExists, unzipArchivePhantom } from '@synthetixio/synpress-cache' +import fs from 'fs-extra' + +export const DEFAULT_PHANTOM_VERSION = 'latest' +export const PHANTOM_EXTENSION_DOWNLOAD_URL = 'https://crx-backup.phantom.dev/latest.crx' + +export async function prepareExtensionPhantom(forceCache = true) { + let outputDir = '' + if (forceCache) { + outputDir = ensureCacheDirExists() + } else { + outputDir = process.platform === 'win32' ? `file:\\\\\\${outputDir}` : path.resolve('./', 'downloads') + + if (!(await fs.exists(outputDir))) { + fs.mkdirSync(outputDir) + } + } + + const downloadResult = await downloadFile({ + url: PHANTOM_EXTENSION_DOWNLOAD_URL, + outputDir, + fileName: 'phantom-chrome-latest.crx' + }) + + const unzipResult = await unzipArchivePhantom({ + archivePath: downloadResult.filePath + }) + + return unzipResult.outputPath +} diff --git a/wallets/phantom/src/selectors/createDataTestSelector.ts b/wallets/phantom/src/selectors/createDataTestSelector.ts new file mode 100644 index 000000000..705c69420 --- /dev/null +++ b/wallets/phantom/src/selectors/createDataTestSelector.ts @@ -0,0 +1,7 @@ +export const createDataTestSelector = (dataTestId: string) => { + if (dataTestId.includes(' ')) { + throw new Error('[CreateDataTestSelector] dataTestId cannot contain spaces') + } + + return `[data-testid="${dataTestId}"]` +} diff --git a/wallets/phantom/src/selectors/error/index.ts b/wallets/phantom/src/selectors/error/index.ts new file mode 100644 index 000000000..b29b281c3 --- /dev/null +++ b/wallets/phantom/src/selectors/error/index.ts @@ -0,0 +1,7 @@ +export const ErrorSelectors = { + loadingOverlayErrorButtons: '.loading-overlay__error-buttons', + loadingOverlayErrorButtonsRetryButton: '.loading-overlay__error-buttons .btn-primary', + criticalError: '.critical-error', + criticalErrorRestartButton: '#critical-error-button', + errorPage: '.error-page' +} diff --git a/wallets/phantom/src/selectors/index.ts b/wallets/phantom/src/selectors/index.ts new file mode 100644 index 000000000..e98e7d7de --- /dev/null +++ b/wallets/phantom/src/selectors/index.ts @@ -0,0 +1,9 @@ +export * from './loading' +export * from './error' + +export { default as crashPage } from './pages/CrashPage' +export { default as homePage } from './pages/HomePage' +export { default as unlockPage } from './pages/UnlockPage' +export { default as notificationPage } from './pages/NotificationPage' +export { default as onboardingPage } from './pages/OnboardingPage' +export { default as settingsPage } from './pages/SettingsPage' diff --git a/wallets/phantom/src/selectors/loading/index.ts b/wallets/phantom/src/selectors/loading/index.ts new file mode 100644 index 000000000..02b70db37 --- /dev/null +++ b/wallets/phantom/src/selectors/loading/index.ts @@ -0,0 +1,17 @@ +export const LoadingSelectors = { + spinner: '.spinner', + loadingOverlay: '.loading-overlay', + loadingIndicators: [ + '.loading-logo', + '.loading-spinner', + '.loading-overlay', + '.loading-overlay__spinner', + '.loading-span', + '.loading-indicator', + '#loading__logo', + '#loading__spinner', + '.mm-button-base__icon-loading', + '.loading-swaps-quotes', + '.loading-heartbeat' + ] +} diff --git a/wallets/phantom/src/selectors/pages/CrashPage/index.ts b/wallets/phantom/src/selectors/pages/CrashPage/index.ts new file mode 100644 index 000000000..0c94db8ba --- /dev/null +++ b/wallets/phantom/src/selectors/pages/CrashPage/index.ts @@ -0,0 +1,6 @@ +const container = 'section.error-page' + +export default { + header: `${container} > .error-page__header`, + errors: `${container} > .error-page__details li` +} diff --git a/wallets/phantom/src/selectors/pages/HomePage/index.ts b/wallets/phantom/src/selectors/pages/HomePage/index.ts new file mode 100644 index 000000000..1a2ca2ca4 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/HomePage/index.ts @@ -0,0 +1,115 @@ +import { createDataTestSelector } from '../../createDataTestSelector' +import settings from './settings' + +const addNewAccountMenu = { + accountNameInput: `input[placeholder="Name"]`, + createButton: `button${createDataTestSelector('primary-button')}:has-text("Create")` +} + +const renameAccountMenu = { + saveButton: `button${createDataTestSelector('primary-button')}:has-text("Save")`, + confirmRenameButton: 'div.editable-label button.mm-button-icon', + renameInput: '.mm-text-field .mm-box--padding-right-4' +} + +const importAccountMenu = { + networkOpenMenu: '#button--listbox-input--1', + ethereumNetwork: `[data-label="Ethereum"]`, + baseNetwork: `[data-label="Base"]`, + polygonNetwork: `[data-label="Polygon"]`, + bitcoinNetwork: `[data-label="Bitcoin"]`, + nameInput: `input[name="name"]`, + privateKeyInput: `textarea[placeholder="Private key"]`, + importButton: `button:has-text("Import")`, + error: `textarea[placeholder="Private key"] + div` +} + +const addAccountMenu = { + addAccountButton: createDataTestSelector('sidebar_menu-button-add_account'), + createNewAccountButton: createDataTestSelector('add-account-create-new-wallet-button'), + importAccountPrivateKeyButton: 'text=Import Private Key', + addNewAccountMenu, + importAccountMenu +} + +const editAccountMenu = { + accountNameButton: `button:has-text("Account Name")` +} + +const accountMenu = { + accountName: createDataTestSelector('home-header-account-name'), + accountButton: createDataTestSelector('settings-menu-open-button'), + accountNames: '#accounts button > div:nth-child(2)', + manageAccountsButton: createDataTestSelector('sidebar_menu-button-manage_accounts'), + settings: createDataTestSelector('sidebar_menu-button-settings'), + addAccountMenu, + renameAccountMenu +} + +const manageAccountButton = (accountName: string) => + `[role="button"][data-testid="manage-accounts-sortable-${accountName}"]` + +const threeDotsMenu = { + threeDotsButton: createDataTestSelector('account-options-menu-button'), + settingsButton: createDataTestSelector('global-menu-settings'), + lockButton: createDataTestSelector('global-menu-lock'), + accountDetailsButton: createDataTestSelector('account-list-menu-details'), + accountDetailsCloseButton: '.mm-modal-content .mm-modal-header button.mm-button-icon.mm-button-icon--size-sm' +} + +const popoverContainer = '.popover-container' +const popover = { + closeButton: `${popoverContainer} ${createDataTestSelector('popover-close')}` +} + +const recoveryPhraseReminder = { + gotItButton: '.recovery-phrase-reminder button.btn-primary' +} + +const networkDropdownContainer = '.multichain-network-list-menu-content-wrapper' +const networkDropdown = { + dropdownButton: createDataTestSelector('network-display'), + closeDropdownButton: `${networkDropdownContainer} > section > div:nth-child(1) button`, + networksList: `${networkDropdownContainer} .multichain-network-list-menu`, + networks: `${networkDropdownContainer} .multichain-network-list-item p`, + showTestNetworksToggle: `${networkDropdownContainer} > section > div > label.toggle-button`, + toggleOff: `${networkDropdownContainer} label.toggle-button.toggle-button--off`, + toggleOn: `${networkDropdownContainer} label.toggle-button.toggle-button--on`, + closeNetworkPopupButton: + '.mm-modal-header button.mm-button-icon.mm-box--color-icon-default.mm-box--background-color-transparent.mm-box--rounded-lg' +} + +const tabContainer = '.tabs__content' +const activityTab = { + activityTabButton: `${createDataTestSelector('home__activity-tab')}`, + transactionsList: `${tabContainer} .transaction-list__transactions`, + pendingQueuedTransactions: `${tabContainer} .transaction-list__pending-transactions .transaction-list-item .transaction-status-label--queued`, + pendingUnapprovedTransactions: `${tabContainer} .transaction-list__pending-transactions .transaction-list-item .transaction-status-label--unapproved`, + pendingApprovedTransactions: `${tabContainer} .transaction-list__pending-transactions .transaction-list-item .transaction-status-label--pending`, + completedTransactions: `${tabContainer} .transaction-list__completed-transactions .transaction-list-item` +} + +const singleToken = '.multichain-token-list-item' + +export default { + solanaWalletAddress: createDataTestSelector('account-header-chain-solana:101'), + ethereumWalletAddress: createDataTestSelector('account-header-chain-eip155:1'), + baseWalletAddress: createDataTestSelector('account-header-chain-eip155:8453'), + polygonWalletAddress: createDataTestSelector('account-header-chain-eip155:137'), + bitcoinWalletAddress: createDataTestSelector('account-header-chain-bip122:000000000019d6689c085ae165831e93'), + copyAccountAddressButton: createDataTestSelector('address-copy-button-text'), + currentNetwork: `${createDataTestSelector('network-display')} span:nth-of-type(1)`, + headerBackButton: createDataTestSelector('header--back'), + threeDotsMenu, + settings, + activityTab, + networkDropdown, + accountMenu, + editAccountMenu, + recoveryPhraseReminder, + popover, + portfolio: { + singleToken + }, + manageAccountButton +} diff --git a/wallets/phantom/src/selectors/pages/HomePage/settings.ts b/wallets/phantom/src/selectors/pages/HomePage/settings.ts new file mode 100644 index 000000000..2ae518506 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/HomePage/settings.ts @@ -0,0 +1,26 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +const advanced = { + showTestNetworksToggle: `${createDataTestSelector('advanced-setting-show-testnet-conversion')} .toggle-button`, + dismissSecretRecoveryPhraseReminderToggle: '.settings-page__content-row:nth-of-type(11) .toggle-button' +} + +const devSettings = { + toggleTestnetMode: createDataTestSelector('toggleTestNetwork'), + toggleEnableCopyTransaction: createDataTestSelector('solana-copy-transaction') +} + +const securityAndPrivacy = { + resetApp: 'button:has-text("Reset App")' +} + +export default { + securityAndPrivacyButton: createDataTestSelector('settings-item-security-and-privacy'), + lockWallet: createDataTestSelector('lock-menu-item'), + unlocWallet: createDataTestSelector('data-testid="unlock-form-submit-button"'), + developerSettingsButton: createDataTestSelector('settings-item-developer-settings'), + closeSettingsButton: createDataTestSelector('settings-menu-close-button'), + advanced, + devSettings, + securityAndPrivacy +} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/actionFooter.ts b/wallets/phantom/src/selectors/pages/NotificationPage/actionFooter.ts new file mode 100644 index 000000000..d0af7cd76 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/NotificationPage/actionFooter.ts @@ -0,0 +1,9 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +export default { + connectActionButton: `button${createDataTestSelector('primary-button')}:has-text("Connect")`, + confirmActionButton: `button${createDataTestSelector('primary-button')}:has-text("Confirm")`, + continueActionButton: `button${createDataTestSelector('primary-button')}:has-text("Continue")`, + cancelActionButton: `button${createDataTestSelector('secondary-button')}:has-text("Cancel")`, + closeActionButton: createDataTestSelector('button-close') +} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts new file mode 100644 index 000000000..3e50f4349 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts @@ -0,0 +1,4 @@ +export default { + accountOption: '.choose-account-list .choose-account-list__list .choose-account-list__account', + accountCheckbox: 'input.choose-account-list__list-check-box' +} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts new file mode 100644 index 000000000..56e2fe8cd --- /dev/null +++ b/wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts @@ -0,0 +1,4 @@ +export default { + approveNewRpc: '.confirmation-warning-modal__content .mm-button-primary--type-danger', + rejectNewRpc: '.confirmation-warning-modal__content .mm-button-secondary' +} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/index.ts b/wallets/phantom/src/selectors/pages/NotificationPage/index.ts new file mode 100644 index 000000000..66c074de2 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/NotificationPage/index.ts @@ -0,0 +1,13 @@ +import ActionFooter from './actionFooter' +import ConnectPage from './connectPage' +import PermissionPage from './permissionPage' +import SignaturePage from './signaturePage' +import TransactionPage from './transactionPage' + +export default { + ActionFooter, + ConnectPage, + PermissionPage, + SignaturePage, + TransactionPage +} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts new file mode 100644 index 000000000..70fe9b90f --- /dev/null +++ b/wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts @@ -0,0 +1,10 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +const approve = { + maxButton: createDataTestSelector('custom-spending-cap-max-button'), + customSpendingCapInput: createDataTestSelector('custom-spending-cap-input') +} + +export default { + approve +} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts new file mode 100644 index 000000000..32e35e013 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts @@ -0,0 +1,25 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +const simpleMessage = { + signButton: `.request-signature__footer ${createDataTestSelector('request-signature__sign')}`, + rejectButton: '.request-signature__footer button.btn-secondary' +} + +const structuredMessage = { + scrollDownButton: `.signature-request-message ${createDataTestSelector('signature-request-scroll-button')}`, + signButton: `.signature-request-footer ${createDataTestSelector('signature-sign-button')}`, + rejectButton: `.signature-request-footer ${createDataTestSelector('signature-cancel-button')}` +} + +const riskModal = { + proceedAnyway: 'text=Proceed anyway (unsafe)', + confirmUnsafe: 'text=Confirm (unsafe)', + acknowledgeRisks: createDataTestSelector('acknowledge--button'), + reconfirmUnsafe: 'text=Yes, confirm (unsafe)' +} + +export default { + simpleMessage, + structuredMessage, + riskModal +} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/transactionPage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/transactionPage.ts new file mode 100644 index 000000000..9dd1ce5ea --- /dev/null +++ b/wallets/phantom/src/selectors/pages/NotificationPage/transactionPage.ts @@ -0,0 +1,15 @@ +const editGasFeeMenu = { + editGasFeeButton: 'button:has-text("Network Fee")', + slowGasFeeButton: 'div > div p:has-text("Slow")', + fastGasFeeButton: 'div > div p:has-text("Fast")', + saveButton: 'button:has-text("Save")' +} + +const nftApproveAllConfirmationPopup = { + approveButton: '.set-approval-for-all-warning__content button.set-approval-for-all-warning__footer__approve-button' +} + +export default { + editGasFeeMenu, + nftApproveAllConfirmationPopup +} diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts new file mode 100644 index 000000000..7738bd2ed --- /dev/null +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts @@ -0,0 +1,6 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +export default { + optIn: createDataTestSelector('metametrics-i-agree'), + optOut: createDataTestSelector('metametrics-no-thanks') +} diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts new file mode 100644 index 000000000..67291216e --- /dev/null +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts @@ -0,0 +1,8 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +export default { + termsOfServiceCheckbox: createDataTestSelector('onboarding-terms-checkbox'), + createNewWallet: `button:has-text("Create a new wallet")`, + importWallet: 'text=I already have a wallet', + importRecoveryPhraseButton: 'text=Import Recovery Phrase' +} diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/index.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/index.ts new file mode 100644 index 000000000..c681569c8 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/index.ts @@ -0,0 +1,25 @@ +import AnalyticsPageSelectors from './analyticsPage' +import GetStartedPageSelectors from './getStartedPage' +import PinExtensionPageSelectors from './pinExtensionPage' +import SecretRecoveryPhrasePageSelectors from './secretRecoveryPhrasePage' +import WalletCreationSuccessPageSelectors from './walletCreationSuccessPage' + +// biome-ignore format: empty lines should be preserved +export default { + // Initial Welcome Page + GetStartedPageSelectors, + + // 2nd Page + AnalyticsPageSelectors, + + // 3rd Page with two steps: + // - Input Secret Recovery Phrase + // - Create Password + SecretRecoveryPhrasePageSelectors, + + // 4th Page + WalletCreationSuccessPageSelectors, + + // 5th Page + PinExtensionPageSelectors, +}; diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts new file mode 100644 index 000000000..64a9b9634 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts @@ -0,0 +1,6 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +export default { + nextButton: createDataTestSelector('pin-extension-next'), + confirmButton: createDataTestSelector('pin-extension-done') +} diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts new file mode 100644 index 000000000..92d8b9f23 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts @@ -0,0 +1,36 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +const recoveryStep = { + selectNumberOfWordsDropdown: '.import-srp__number-of-words-dropdown > .dropdown__select', + selectNumberOfWordsOption: (option: number | string) => `${option}`, + // secretRecoveryPhraseWord: (index: number) => createDataTestSelector(`import-srp__srp-word-${index}`), + secretRecoveryPhraseWord: (index: number) => createDataTestSelector(`secret-recovery-phrase-word-input-${index}`), + // confirmSecretRecoveryPhraseButton: + // createDataTestSelector("import-srp-confirm"), + confirmSecretRecoveryPhraseButton: createDataTestSelector('onboarding-form-submit-button'), + // error: ".mm-banner-alert.import-srp__srp-error div", + error: createDataTestSelector('onboarding-import-secret-recovery-phrase-error-message') +} + +const viewAccountsButton = createDataTestSelector('onboarding-form-secondary-button') + +const continueButton = createDataTestSelector('onboarding-form-submit-button') + +const passwordStep = { + passwordInput: createDataTestSelector('onboarding-form-password-input'), + confirmPasswordInput: createDataTestSelector('onboarding-form-confirm-password-input'), + acceptTermsCheckbox: createDataTestSelector('onboarding-form-terms-of-service-checkbox'), + // importWalletButton: createDataTestSelector("onboarding-form-submit-button"), + continue: continueButton, + error: `${createDataTestSelector('create-password-new')} + h6 > span > span` +} + +const allDone = `text=You're all done!` + +export default { + recoveryStep, + viewAccountsButton, + continueButton, + passwordStep, + allDone +} diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/walletCreationSuccessPage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/walletCreationSuccessPage.ts new file mode 100644 index 000000000..726626a15 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/walletCreationSuccessPage.ts @@ -0,0 +1,5 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +export default { + confirmButton: createDataTestSelector('onboarding-complete-done') +} diff --git a/wallets/phantom/src/selectors/pages/SettingsPage/index.ts b/wallets/phantom/src/selectors/pages/SettingsPage/index.ts new file mode 100644 index 000000000..57e201a10 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/SettingsPage/index.ts @@ -0,0 +1,23 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +const menuOption = '.settings-page__content__tabs .tab-bar .tab-bar__tab' + +const settings = { + menuOption, + advancedSettings: `${menuOption}:nth-child(2)`, + ethSignToggle: `${createDataTestSelector('advanced-setting-toggle-ethsign')} .eth-sign-toggle`, + ethSignWarning: + '.settings-page__content-row .mm-banner-alert.mm-banner-alert--severity-danger.mm-box--background-color-error-muted' +} + +const confirmationModal = { + confirmationCheckbox: createDataTestSelector('eth-sign__checkbox'), + continueButton: '.modal__content button.mm-button-primary', + manualConfirmationInput: '#enter-eth-sign-text', + enableButton: '.modal__content button.mm-button-primary.mm-button-primary--type-danger' +} + +export default { + settings, + confirmationModal +} diff --git a/wallets/phantom/src/selectors/pages/UnlockPage/index.ts b/wallets/phantom/src/selectors/pages/UnlockPage/index.ts new file mode 100644 index 000000000..680748a28 --- /dev/null +++ b/wallets/phantom/src/selectors/pages/UnlockPage/index.ts @@ -0,0 +1,6 @@ +import { createDataTestSelector } from '../../createDataTestSelector' + +export default { + passwordInput: createDataTestSelector('unlock-form-password-input'), + submitButton: createDataTestSelector('unlock-form-submit-button') +} diff --git a/wallets/phantom/src/type/GasSettings.ts b/wallets/phantom/src/type/GasSettings.ts new file mode 100644 index 000000000..e1ecd9870 --- /dev/null +++ b/wallets/phantom/src/type/GasSettings.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' + +export const GasSettingValidation = z.union([ + z.literal('Slow'), + z.literal('Fast'), + z.literal('Average'), + z + .object({ + maxBaseFee: z.number(), + priorityFee: z.number(), + // TODO: Add gasLimit range validation. + gasLimit: z.number().optional() + }) + .superRefine(({ maxBaseFee, priorityFee }, ctx) => { + if (priorityFee > maxBaseFee) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Max base fee cannot be lower than priority fee', + path: ['Phantom', 'confirmTransaction', 'gasSetting', 'maxBaseFee'] + }) + } + }) +]) + +export type GasSettings = z.input diff --git a/wallets/phantom/src/type/Networks.ts b/wallets/phantom/src/type/Networks.ts new file mode 100644 index 000000000..372d8e67b --- /dev/null +++ b/wallets/phantom/src/type/Networks.ts @@ -0,0 +1 @@ +export type Networks = 'solana' | 'ethereum' | 'base' | 'polygon' | 'bitcoin' diff --git a/wallets/phantom/src/type/PhantomAbstract.ts b/wallets/phantom/src/type/PhantomAbstract.ts new file mode 100644 index 000000000..21f388c7a --- /dev/null +++ b/wallets/phantom/src/type/PhantomAbstract.ts @@ -0,0 +1,158 @@ +import type { GasSettings } from './GasSettings' +import type { Networks } from './Networks' + +export abstract class PhantomAbstract { + /** + * @param password - The password of the Phantom wallet. + * @param extensionId - The extension ID of the Phantom extension. Optional if no interaction with the dapp is required. + * + * @returns A new instance of the Phantom class. + */ + constructor( + /** + * The password of the Phantom wallet. + */ + readonly password: string, + /** + * The extension ID of the Phantom extension. Optional if no interaction with the dapp is required. + */ + readonly extensionId?: string + ) { + this.password = password + this.extensionId = extensionId + } + + /** + * Imports a wallet using the given seed phrase. + * + * @param seedPhrase - The seed phrase to import. + */ + abstract importWallet(seedPhrase: string): void + + /** + * Adds a new account with the given name. This account is based on the initially imported seed phrase. + * + * @param accountName - The name of the new account. + */ + abstract addNewAccount(accountName: string): void + + /** + * Imports a wallet using the given private key. + * + * @param privateKey - The private key to import. + */ + abstract importWalletFromPrivateKey(network: Networks, privateKey: string, walletName?: string): void + + /** + * Switches to the account with the given name. + * + * @param accountName - The name of the account to switch to. + */ + abstract switchAccount(accountName: string): void + + /** + * Retrieves the current account address. + */ + abstract getAccountAddress(network: Networks): void + + /** + * Connects to the dapp using the currently selected account. + */ + abstract connectToDapp(account?: string): void + + /** + * Locks Phantom. + */ + abstract lock(): void + + /** + * Unlocks Phantom. + */ + abstract unlock(): void + + /** + * Confirms a signature request. This function supports all types of commonly used signatures. + */ + abstract confirmSignature(): void + + /** + * Confirms a signature request with potential risk. + */ + abstract confirmSignatureWithRisk(): void + + /** + * Rejects a signature request. This function supports all types of commonly used signatures. + */ + abstract rejectSignature(): void + + /** + * Confirms a transaction request. + * + * @param options - The transaction options. + * @param options.gasSetting - The gas setting to use for the transaction. + */ + abstract confirmTransaction(options?: { gasSetting?: GasSettings }): void + + /** + * Rejects a transaction request. + */ + abstract rejectTransaction(): void + + /** + * Approves a permission request to spend tokens. + * + * ::: warning + * For NFT approvals, use `confirmTransaction` method. + * ::: + * + * @param options - The permission options. + * @param options.spendLimit - The spend limit to use for the permission. + * @param options.gasSetting - The gas setting to use for the approval transaction. + */ + abstract approveTokenPermission(options?: { + spendLimit?: 'max' | number + gasSetting?: GasSettings + }): void + + /** + * Rejects a permission request to spend tokens. + * + * ::: warning + * For NFT approvals, use `confirmTransaction` method. + * ::: + */ + abstract rejectTokenPermission(): void + + /** + * Navigates to the home page of Phantom tab. + */ + abstract goToHomePage(): void + + /** + * Goes back to the home page of Phantom tab. + */ + abstract goBackToHomePage(): void + + /** + * Opens the settings page. + */ + abstract openSettings(): void + + /** + * Toggles the "Show Test Networks" setting. + * + * ::: warning + * This function requires the correct menu to be already opened. + * ::: + */ + abstract toggleTestnetMode(): void + + /** + * Resets the account. + * + * ::: warning + * This function requires the correct menu to be already opened. + * ::: + */ + abstract resetApp(): void +} diff --git a/wallets/phantom/test/playwright/commonSteps/connectPhantomToTestDapp.ts b/wallets/phantom/test/playwright/commonSteps/connectPhantomToTestDapp.ts new file mode 100644 index 000000000..ebc7b47c1 --- /dev/null +++ b/wallets/phantom/test/playwright/commonSteps/connectPhantomToTestDapp.ts @@ -0,0 +1,10 @@ +import { type Page, expect } from '@playwright/test' +import type { Phantom } from '../../../src/playwright' + +export const connectPhantomToTestDapp = async (page: Page, phantom: Phantom) => { + await page.locator('#connectButton').click() + + await phantom.connectToDapp() + + await expect(page.locator('#accounts')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') +} diff --git a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts new file mode 100644 index 000000000..9dfc05948 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts @@ -0,0 +1,23 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should add a new account with specified name', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + const accountName = 'Test Account' + await phantom.addNewAccount(accountName) + + await expect(phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName)).toHaveText(accountName) +}) + +test('should throw an error if an empty account name is passed', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await expect(phantom.addNewAccount('')).rejects.toThrowError('[AddNewAccount] Account name cannot be an empty string') +}) diff --git a/wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts b/wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts new file mode 100644 index 000000000..4399e1110 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts @@ -0,0 +1,27 @@ +import synpress from '../synpress' + +const test = synpress + +const { expect } = test + +test('should approve token with the default limit', async ({ aavePage, phantom }) => { + test.setTimeout(80_000) + + await aavePage.goto( + 'https://app.aave.com/reserve-overview/?underlyingAsset=0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357&marketName=proto_sepolia_v3' + ) + + await aavePage.getByRole('button', { name: 'Supply' }).click() + await aavePage.locator('input[aria-label="amount input"]').fill('1') + + const supplyDaiLocator = aavePage.getByRole('button', { + name: 'Supply DAI' + }) + await expect(supplyDaiLocator).toBeDisabled() + + await aavePage.locator('button:has-text("Approve DAI to continue")').click() + + await phantom.approveTokenPermission() + + await expect(supplyDaiLocator).toBeEnabled({ timeout: 10_000 }) +}) diff --git a/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts new file mode 100644 index 000000000..6d7c55d6b --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts @@ -0,0 +1,78 @@ +import { connectPhantomToTestDapp } from '../commonSteps/connectPhantomToTestDapp' +import synpress from '../synpress' + +const test = synpress + +const { expect } = test + +test('should confirm `personal_sign`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#personalSign').click() + + await phantom.confirmSignature() + + await expect(page.locator('#personalSignResult')).toHaveText( + '0xf95b3efc808585303e20573e960993cde30c7f5a0f1c25cfab0379d5a14311d17898199814c8ebe66ec80b2b11690f840bde539f862ff4f04468d2a40f15178a1b' + ) +}) + +test('should confirm `eth_signTypedData`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#signTypedData').click() + + await phantom.confirmSignature() + + await expect(page.locator('#signTypedDataResult')).toHaveText( + '0xd75eece0d337f4e425f87bd112c849561956afe4f154cdd07d1d4cba7a979b481ba6ceede5c0eb9daa66bec4eea6e7ecfee5496274ef2a93b69abd97531519b21c' + ) + + await page.locator('#signTypedDataVerify').click() + + await expect(page.locator('#signTypedDataVerifyResult')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') +}) + +test('should confirm `eth_signTypedData_v3`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#signTypedDataV3').click() + + await phantom.confirmSignature() + + await expect(page.locator('#signTypedDataV3Result')).toHaveText( + '0x6ea8bb309a3401225701f3565e32519f94a0ea91a5910ce9229fe488e773584c0390416a2190d9560219dab757ecca2029e63fa9d1c2aebf676cc25b9f03126a1b' + ) + + await page.locator('#signTypedDataV3Verify').click() + + await expect(page.locator('#signTypedDataV3VerifyResult')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') +}) + +test('should confirm `eth_signTypedData_v4`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#signTypedDataV4').click() + + await phantom.confirmSignature() + + await expect(page.locator('#signTypedDataV4Result')).toHaveText( + '0x1cf422c4a319c19ecb89c960e7c296810278fa2bef256c7e9419b285c8216c547b3371fa1ec3987ce08561d3ed779845393d8d3e4311376d0bc0846f37d1b2821c' + ) + + await page.locator('#signTypedDataV4Verify').click() + + await expect(page.locator('#signTypedDataV4VerifyResult')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') +}) + +test('should confirm `eth_sign`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#ethSign').click() + + await phantom.confirmSignatureWithRisk() + + await expect(page.locator('#ethSignResult')).toContainText( + '0x369af83ed2fcee757c12d59d921289722d7c6bc11f484f667f2ef8b74b068a0e281ffff7099fc5a4af534f5dbaaab92238b715dd887e3a9f8e2298dc99f554e81b' + ) +}) diff --git a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts new file mode 100644 index 000000000..da8212b6d --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts @@ -0,0 +1,44 @@ +import { connectPhantomToTestDapp } from '../commonSteps/connectPhantomToTestDapp' +import synpress from '../synpress' + +const test = synpress + +const { expect } = test + +test('should Sign Transaction ', async ({ solanaSandboxPage, phantom }) => { + await solanaSandboxPage.getByRole('button', { name: 'Sign Transaction' }).click() + await phantom.confirmTransaction() + + await expect(solanaSandboxPage.getByText('> success')).toBeVisible() +}) + +test('should Sign All Transactions ', async ({ solanaSandboxPage, phantom }) => { + await solanaSandboxPage.getByRole('button', { name: 'Sign All Transaction' }).click() + await phantom.confirmTransaction() + + await expect(solanaSandboxPage.getByText('> success')).toBeVisible() +}) + +test('should confirm contract deployment with default gas setting', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await expect(page.locator('#tokenAddresses')).toBeEmpty() + await page.locator('#createToken').click() + + await phantom.confirmTransaction() + + await expect(page.locator('#tokenAddresses')).toContainText('Creation Failed') +}) + +;(['Slow', 'Fast'] as const).forEach((gasSetting) => { + test(`should confirm contract deployment with ${gasSetting} gas setting`, async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await expect(page.locator('#tokenAddresses')).toBeEmpty() + await page.locator('#createToken').click() + + await phantom.confirmTransaction({ gasSetting }) + + await expect(page.locator('#tokenAddresses')).toContainText('Creation Failed') + }) +}) diff --git a/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts b/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts new file mode 100644 index 000000000..cce4a8abc --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts @@ -0,0 +1,49 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should connect wallet to dapp', async ({ context, page, extensionId }) => { + const phantom = new Phantom(context, page, basicSetup.walletPassword, extensionId) + + await page.goto('/') + + await page.locator('#connectButton').click() + + await phantom.connectToDapp() + + await expect(page.locator('#accounts')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') +}) + +test('should connect multiple wallets to dapp', async ({ context, page, phantomPage, extensionId }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword, extensionId) + + await phantom.addNewAccount('NewAccount1') + await phantom.addNewAccount('NewAccount2') + + await page.goto('/') + await page.locator('#connectButton').click() + + await phantom.connectToDapp('NewAccount1') + + // Get address for account connected to testdapp + await expect(page.locator('#accounts')).toContainText('0x') + const testDappAccountAddress = await page.locator('#accounts').innerText() + + // Verify that active account in Phantom extension is 'NewAccount2' + await expect(phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName)).toHaveText('NewAccount2') + // Verify that address for active account in Phantom extension is not connected to test dapp + const phantomActiveAccountAddress = await phantom.getAccountAddress('ethereum') + expect(testDappAccountAddress).not.toEqual(phantomActiveAccountAddress) + + // Switch to 'NewAccount1' in Phantom extension and verify that this is the one connected to test dapp + await phantom.switchAccount('NewAccount1') + const phantomNewAccount1Address = await phantom.getAccountAddress('ethereum') + + // Two accounts connected + expect(testDappAccountAddress.toLowerCase()).toEqual(phantomNewAccount1Address.toLowerCase()) +}) diff --git a/wallets/phantom/test/playwright/e2e/getAccountAddress.spec.ts b/wallets/phantom/test/playwright/e2e/getAccountAddress.spec.ts new file mode 100644 index 000000000..289e45b89 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/getAccountAddress.spec.ts @@ -0,0 +1,27 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should get account address for all available networks', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + const solanaAccountAddress = await phantom.getAccountAddress('solana') + expect(solanaAccountAddress).toEqual('oeYf6KAJkLYhBuR8CiGc6L4D4Xtfepr85fuDgA9kq96') + + const ethereumAccountAddress = await phantom.getAccountAddress('ethereum') + expect(ethereumAccountAddress).toEqual('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266') + + const baseAccountAddress = await phantom.getAccountAddress('base') + expect(baseAccountAddress).toEqual('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266') + + const polygonAccountAddress = await phantom.getAccountAddress('polygon') + expect(polygonAccountAddress).toEqual('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266') + + const bitcoinAccountAddress = await phantom.getAccountAddress('bitcoin') + expect(bitcoinAccountAddress).toEqual('bc1q4qw42stdzjqs59xvlrlxr8526e3nunw7mp73te') +}) diff --git a/wallets/phantom/test/playwright/e2e/goBackToHomePage.spec.ts b/wallets/phantom/test/playwright/e2e/goBackToHomePage.spec.ts new file mode 100644 index 000000000..5818741e7 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/goBackToHomePage.spec.ts @@ -0,0 +1,22 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should go back to the home page', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await expect(phantomPage.locator(phantom.homePage.selectors.settings.lockWallet)).not.toBeVisible() + + await phantom.openSettings() + + await expect(phantomPage.locator(phantom.homePage.selectors.settings.lockWallet)).toBeVisible() + + await phantom.goBackToHomePage() + + await expect(phantomPage.locator(phantom.homePage.selectors.settings.lockWallet)).not.toBeVisible() +}) diff --git a/wallets/phantom/test/playwright/e2e/importWalletFromPrivateKey.spec.ts b/wallets/phantom/test/playwright/e2e/importWalletFromPrivateKey.spec.ts new file mode 100644 index 000000000..d1feff425 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/importWalletFromPrivateKey.spec.ts @@ -0,0 +1,48 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +const privateKey = 'ea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a' + +test('should import a new wallet from private key', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await phantom.importWalletFromPrivateKey('ethereum', privateKey) + + await phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName).hover() + await expect(phantomPage.locator(phantom.homePage.selectors.ethereumWalletAddress)).toContainText('0xa2ce...6801') +}) + +// Flaky test - To be improved +test.skip('should throw an error if trying to import private key for the 2nd time', async ({ + context, + phantomPage +}) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await phantom.importWalletFromPrivateKey('ethereum', privateKey) + + // To avoid random fails + await phantomPage.waitForTimeout(2_000) + + const importWalletPromise = phantom.importWalletFromPrivateKey('ethereum', privateKey) + + await expect(importWalletPromise).rejects.toThrowError( + '[ImportWalletFromPrivateKey] Importing failed due to error: This account already exists in your wallet' + ) +}) + +test('should throw an error if the private key is invalid', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + const importWalletPromise = phantom.importWalletFromPrivateKey('ethereum', '0xdeadbeef') + + await expect(importWalletPromise).rejects.toThrowError( + '[ImportWalletFromPrivateKey] Importing failed due to error: Incorrect format' + ) +}) diff --git a/wallets/phantom/test/playwright/e2e/lock.spec.ts b/wallets/phantom/test/playwright/e2e/lock.spec.ts new file mode 100644 index 000000000..e6b748038 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/lock.spec.ts @@ -0,0 +1,16 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should lock the wallet', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await phantom.lock() + + await expect(phantomPage.locator(phantom.unlockPage.selectors.submitButton)).toBeVisible() +}) diff --git a/wallets/phantom/test/playwright/e2e/openSettings.spec.ts b/wallets/phantom/test/playwright/e2e/openSettings.spec.ts new file mode 100644 index 000000000..f99d2ab53 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/openSettings.spec.ts @@ -0,0 +1,15 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should open settings', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await phantom.openSettings() + + await expect(phantomPage.locator(phantom.homePage.selectors.settings.lockWallet)).toBeVisible() +}) diff --git a/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts b/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts new file mode 100644 index 000000000..8abd7f670 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts @@ -0,0 +1,47 @@ +import { connectPhantomToTestDapp } from '../commonSteps/connectPhantomToTestDapp' +import synpress from '../synpress' + +const test = synpress + +const { expect } = test + +test('should reject `personal_sign`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#personalSign').click() + + await phantom.rejectSignature() + + await expect(page.locator('#personalSign')).toHaveText('Error: User rejected the request.') + await expect(page.locator('#personalSignResult')).toHaveText('') +}) + +test('should reject `eth_signTypedData`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#signTypedData').click() + + await phantom.rejectSignature() + + await expect(page.locator('#signTypedDataResult')).toHaveText('Error: User rejected the request.') +}) + +test('should reject `eth_signTypedData_v3`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#signTypedDataV3').click() + + await phantom.rejectSignature() + + await expect(page.locator('#signTypedDataV3Result')).toHaveText('Error: User rejected the request.') +}) + +test('should reject `eth_signTypedData_v4`', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await page.locator('#signTypedDataV4').click() + + await phantom.rejectSignature() + + await expect(page.locator('#signTypedDataV4Result')).toHaveText('Error: User rejected the request.') +}) diff --git a/wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts b/wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts new file mode 100644 index 000000000..a83b39768 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts @@ -0,0 +1,32 @@ +import synpress from '../synpress' + +const test = synpress + +const { expect } = test + +test('should reject approve token request', async ({ aavePage, phantom }) => { + test.setTimeout(80_000) + + await aavePage.goto( + 'https://app.aave.com/reserve-overview/?underlyingAsset=0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357&marketName=proto_sepolia_v3' + ) + + await aavePage.getByRole('button', { name: 'Supply' }).click() + await aavePage.locator('input[aria-label="amount input"]').fill('1') + + const supplyDaiLocator = aavePage.getByRole('button', { + name: 'Supply DAI' + }) + await expect(supplyDaiLocator).toBeDisabled() + + await aavePage.locator('button:has-text("Approve DAI to continue")').click() + + await expect(aavePage.locator('button:has-text("Approve DAI to continue")')).not.toBeVisible() + await expect(aavePage.locator('button:has-text("Approving DAI...")')).toBeVisible() + + await phantom.rejectTokenPermission() + + await expect(aavePage.locator('button:has-text("Approving DAI...")')).not.toBeVisible({ timeout: 10_000 }) + await expect(aavePage.locator('button:has-text("Approve DAI to continue")')).toBeVisible() + await expect(aavePage.getByText('There was some error. Please try changing the parameters or')).toBeVisible() +}) diff --git a/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts new file mode 100644 index 000000000..6cb2e61c2 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts @@ -0,0 +1,31 @@ +import { connectPhantomToTestDapp } from '../commonSteps/connectPhantomToTestDapp' +import synpress from '../synpress' + +const test = synpress + +const { expect } = test + +test('should Reject Transaction ', async ({ solanaSandboxPage, phantom }) => { + await solanaSandboxPage.getByRole('button', { name: 'Sign Transaction' }).click() + await phantom.rejectTransaction() + + await expect(solanaSandboxPage.getByText('User rejected the request.')).toBeVisible() +}) + +test('should Reject All Transactions ', async ({ solanaSandboxPage, phantom }) => { + await solanaSandboxPage.getByRole('button', { name: 'Sign All Transaction' }).click() + await phantom.rejectTransaction() + + await expect(solanaSandboxPage.getByText('User rejected the request.')).toBeVisible() +}) + +test('should reject contract deployment', async ({ page, phantom }) => { + connectPhantomToTestDapp(page, phantom) + + await expect(page.locator('#tokenAddresses')).toBeEmpty() + await page.locator('#createToken').click() + + await phantom.rejectTransaction() + + await expect(page.locator('#tokenAddresses')).toContainText('Creation Failed') +}) diff --git a/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts b/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts new file mode 100644 index 000000000..a63d3e46c --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts @@ -0,0 +1,19 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should rename current account with specified name', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + const accountName = 'Test Account' + await phantom.renameAccount('Account 1', accountName) + + await phantomPage.reload() + + await expect(phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName)).toHaveText(accountName) +}) diff --git a/wallets/phantom/test/playwright/e2e/resetApp.spec.ts b/wallets/phantom/test/playwright/e2e/resetApp.spec.ts new file mode 100644 index 000000000..1ae740e22 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/resetApp.spec.ts @@ -0,0 +1,27 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('reset the app', async ({ context, phantomPage }) => { + test.setTimeout(40_000) + + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await expect(phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName)).toHaveText('Account 1') + + await phantom.resetApp() + + await expect(async () => { + const newPhantomPage = context.pages()[1] + + if (newPhantomPage) { + const newPhantomPageUrl = newPhantomPage?.url() + expect(newPhantomPageUrl).toContain('onboarding') + } + }).toPass() +}) diff --git a/wallets/phantom/test/playwright/e2e/switchAccount.spec.ts b/wallets/phantom/test/playwright/e2e/switchAccount.spec.ts new file mode 100644 index 000000000..0df13d1b9 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/switchAccount.spec.ts @@ -0,0 +1,38 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should switch account', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await phantom.importWalletFromPrivateKey( + 'ethereum', + 'ea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a' + ) + + await phantom.importWalletFromPrivateKey( + 'ethereum', + '7dd4aab86170c0edbdcf97600eff0ae319fdc94149c5e8c33d5439f8417a40bf' + ) + + await phantom.switchAccount('Account 1') + + await expect(phantomPage.getByTestId('home-header-account-name')).toContainText('Account 1') + + await phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName).hover() + await expect(phantomPage.locator(phantom.homePage.selectors.ethereumWalletAddress)).toContainText('0xf39F...2266') +}) + +test('should throw an error if there is no account with target name', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + const accountName = 'Account 420' + const switchAccountPromise = phantom.switchAccount(accountName) + + await expect(switchAccountPromise).rejects.toThrowError(`[SwitchAccount] Account with name ${accountName} not found`) +}) diff --git a/wallets/phantom/test/playwright/e2e/toggleTestnetMode.spec.ts b/wallets/phantom/test/playwright/e2e/toggleTestnetMode.spec.ts new file mode 100644 index 000000000..3470f7653 --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/toggleTestnetMode.spec.ts @@ -0,0 +1,20 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import Selectors from '../../../src/selectors/pages/HomePage' +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should toggle the "Testnet Mode" option from Developer Settings menu', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await phantom.toggleTestnetMode() + + await phantomPage.locator(Selectors.headerBackButton).click() + await phantom.goBackToHomePage() + + await expect(phantomPage.getByText('You are currently in Testnet Mode')).toBeVisible() +}) diff --git a/wallets/phantom/test/playwright/e2e/unlock.spec.ts b/wallets/phantom/test/playwright/e2e/unlock.spec.ts new file mode 100644 index 000000000..ebafc283d --- /dev/null +++ b/wallets/phantom/test/playwright/e2e/unlock.spec.ts @@ -0,0 +1,18 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { Phantom, phantomFixtures } from '../../../src/playwright' + +import basicSetup from '../wallet-setup/basic.setup' + +const test = testWithSynpress(phantomFixtures(basicSetup)) + +const { expect } = test + +test('should unlock the wallet', async ({ context, phantomPage }) => { + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + + await phantom.lock() + + await phantom.unlock() + + await expect(phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName)).toHaveText('Account 1') +}) diff --git a/wallets/phantom/test/playwright/synpress.ts b/wallets/phantom/test/playwright/synpress.ts new file mode 100644 index 000000000..48099bf3d --- /dev/null +++ b/wallets/phantom/test/playwright/synpress.ts @@ -0,0 +1,5 @@ +import { testWithSynpress } from '@synthetixio/synpress-core' +import { phantomFixtures } from '../../src/playwright' +import basicSetup from './wallet-setup/basic.setup' + +export default testWithSynpress(phantomFixtures(basicSetup)) diff --git a/wallets/phantom/test/playwright/wallet-setup/basic.setup.ts b/wallets/phantom/test/playwright/wallet-setup/basic.setup.ts new file mode 100644 index 000000000..56ce9f314 --- /dev/null +++ b/wallets/phantom/test/playwright/wallet-setup/basic.setup.ts @@ -0,0 +1,11 @@ +import { defineWalletSetup } from '@synthetixio/synpress-cache' +import { Phantom } from '../../../src/playwright' + +export const SEED_PHRASE = 'test test test test test test test test test test test junk' +export const PASSWORD = 'Tester@1234' + +export default defineWalletSetup(PASSWORD, async (context, walletPage) => { + const phantom = new Phantom(context, walletPage, PASSWORD) + + await phantom.importWallet(SEED_PHRASE) +}) diff --git a/wallets/phantom/tsconfig.build.json b/wallets/phantom/tsconfig.build.json new file mode 100644 index 000000000..9af73f809 --- /dev/null +++ b/wallets/phantom/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "@synthetixio/synpress-tsconfig/base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "types", + "declaration": true, + "sourceMap": true, + "declarationMap": true + }, + "include": ["src"], + "files": ["environment.d.ts"] +} diff --git a/wallets/phantom/tsconfig.json b/wallets/phantom/tsconfig.json new file mode 100644 index 000000000..dad11ada4 --- /dev/null +++ b/wallets/phantom/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "rootDir": ".", + "esModuleInterop": true, + "exactOptionalPropertyTypes": false, // Allows for `undefined` in `playwright.config.ts` + "types": ["cypress"], + "sourceMap": false + }, + "include": ["src", "test"], + "files": ["environment.d.ts", "playwright.config.ts", "vitest.config.ts"] +} diff --git a/wallets/phantom/tsup.config.ts b/wallets/phantom/tsup.config.ts new file mode 100644 index 000000000..20c19819d --- /dev/null +++ b/wallets/phantom/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + name: 'phantom', + entry: ['src/index.ts', 'src/playwright/index.ts', 'src/cypress/index.ts', 'src/cypress/support/index.ts'], + outDir: 'dist', + format: 'esm', + splitting: false, + treeshake: true, + sourcemap: true, + external: ['@playwright/test'] +}) diff --git a/wallets/phantom/vitest.config.ts b/wallets/phantom/vitest.config.ts new file mode 100644 index 000000000..42ec65bf9 --- /dev/null +++ b/wallets/phantom/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + dir: 'test/unit' + } +}) From ce2a2d8fbe075c57c46a3bf8c4c7917615961348 Mon Sep 17 00:00:00 2001 From: zgz2020 <47563814+zgz2020@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:22:09 +0100 Subject: [PATCH 02/36] Update typedoc-sidebar.json --- docs/api/typedoc-sidebar.json | 77 +++++++---------------------------- 1 file changed, 15 insertions(+), 62 deletions(-) diff --git a/docs/api/typedoc-sidebar.json b/docs/api/typedoc-sidebar.json index 668b3da35..52a7709cf 100644 --- a/docs/api/typedoc-sidebar.json +++ b/docs/api/typedoc-sidebar.json @@ -17,14 +17,8 @@ "text": "configureSynpressForEthereumWalletMock", "link": "/api/cypress/functions/configureSynpressForEthereumWalletMock.md" }, - { - "text": "configureSynpressForMetaMask", - "link": "/api/cypress/functions/configureSynpressForMetaMask.md" - }, - { - "text": "initMetaMask", - "link": "/api/cypress/functions/initMetaMask.md" - } + { "text": "configureSynpressForMetaMask", "link": "/api/cypress/functions/configureSynpressForMetaMask.md" }, + { "text": "initMetaMask", "link": "/api/cypress/functions/initMetaMask.md" } ] }, { @@ -36,10 +30,7 @@ "text": "Functions", "collapsed": true, "items": [ - { - "text": "mockEthereum", - "link": "/api/cypress/support/functions/mockEthereum.md" - }, + { "text": "mockEthereum", "link": "/api/cypress/support/functions/mockEthereum.md" }, { "text": "synpressCommandsForEthereumWalletMock", "link": "/api/cypress/support/functions/synpressCommandsForEthereumWalletMock.md" @@ -63,14 +54,8 @@ "text": "Functions", "collapsed": true, "items": [ - { - "text": "defineWalletSetup", - "link": "/api/index/functions/defineWalletSetup.md" - }, - { - "text": "testWithSynpress", - "link": "/api/index/functions/testWithSynpress.md" - } + { "text": "defineWalletSetup", "link": "/api/index/functions/defineWalletSetup.md" }, + { "text": "testWithSynpress", "link": "/api/index/functions/testWithSynpress.md" } ] } ] @@ -84,60 +69,28 @@ "text": "Classes", "collapsed": true, "items": [ - { - "text": "EthereumWalletMock", - "link": "/api/playwright/classes/EthereumWalletMock.md" - }, - { "text": "MetaMask", "link": "/api/playwright/classes/MetaMask.md" }, - { "text": "Phantom", "link": "/api/playwright/classes/Phantom.md" } + { "text": "EthereumWalletMock", "link": "/api/playwright/classes/EthereumWalletMock.md" }, + { "text": "MetaMask", "link": "/api/playwright/classes/MetaMask.md" } ] }, { "text": "Variables", "collapsed": true, "items": [ - { - "text": "DEFAULT_NETWORK_ID", - "link": "/api/playwright/variables/DEFAULT_NETWORK_ID.md" - }, - { - "text": "PRIVATE_KEY", - "link": "/api/playwright/variables/PRIVATE_KEY.md" - }, - { - "text": "web3MockPath", - "link": "/api/playwright/variables/web3MockPath.md" - } + { "text": "DEFAULT_NETWORK_ID", "link": "/api/playwright/variables/DEFAULT_NETWORK_ID.md" }, + { "text": "PRIVATE_KEY", "link": "/api/playwright/variables/PRIVATE_KEY.md" }, + { "text": "web3MockPath", "link": "/api/playwright/variables/web3MockPath.md" } ] }, { "text": "Functions", "collapsed": true, "items": [ - { - "text": "ethereumWalletMockFixtures", - "link": "/api/playwright/functions/ethereumWalletMockFixtures.md" - }, - { - "text": "getExtensionId", - "link": "/api/playwright/functions/getExtensionId.md" - }, - { - "text": "metaMaskFixtures", - "link": "/api/playwright/functions/metaMaskFixtures.md" - }, - { - "text": "phantomFixtures", - "link": "/api/playwright/functions/phantomFixtures.md" - }, - { - "text": "mockEthereum", - "link": "/api/playwright/functions/mockEthereum.md" - }, - { - "text": "unlockForFixture", - "link": "/api/playwright/functions/unlockForFixture.md" - } + { "text": "ethereumWalletMockFixtures", "link": "/api/playwright/functions/ethereumWalletMockFixtures.md" }, + { "text": "getExtensionId", "link": "/api/playwright/functions/getExtensionId.md" }, + { "text": "metaMaskFixtures", "link": "/api/playwright/functions/metaMaskFixtures.md" }, + { "text": "mockEthereum", "link": "/api/playwright/functions/mockEthereum.md" }, + { "text": "unlockForFixture", "link": "/api/playwright/functions/unlockForFixture.md" } ] } ] From 2d251fec20bb7917ec132e2257bf7017db0f6e37 Mon Sep 17 00:00:00 2001 From: zgz2020 <47563814+zgz2020@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:23:29 +0100 Subject: [PATCH 03/36] Update typedoc-sidebar.json --- docs/api/typedoc-sidebar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/typedoc-sidebar.json b/docs/api/typedoc-sidebar.json index 52a7709cf..30282d5dc 100644 --- a/docs/api/typedoc-sidebar.json +++ b/docs/api/typedoc-sidebar.json @@ -86,7 +86,7 @@ "text": "Functions", "collapsed": true, "items": [ - { "text": "ethereumWalletMockFixtures", "link": "/api/playwright/functions/ethereumWalletMockFixtures.md" }, + { "text": "ethereumWalletMockFixtures", "link": "/api/playwright/functions/ethereumWalletMockFixtures.md" }, { "text": "getExtensionId", "link": "/api/playwright/functions/getExtensionId.md" }, { "text": "metaMaskFixtures", "link": "/api/playwright/functions/metaMaskFixtures.md" }, { "text": "mockEthereum", "link": "/api/playwright/functions/mockEthereum.md" }, From 91125ab91a277e1748cf1a7eb50c7b80e84ac2fa Mon Sep 17 00:00:00 2001 From: juan-langa Date: Wed, 19 Feb 2025 08:39:37 +0100 Subject: [PATCH 04/36] Phantom support for Playwright --- wallets/phantom/src/playwright/Phantom.ts | 11 +--- .../playwright/fixtures/phantomFixtures.ts | 45 +--------------- .../src/playwright/pages/CrashPage/page.ts | 6 --- .../pages/HomePage/actions/index.ts | 1 - .../HomePage/actions/popups/closePopover.ts | 12 ----- .../popups/closeRecoveryPhraseReminder.ts | 10 ---- .../pages/HomePage/actions/popups/index.ts | 2 - .../pages/HomePage/actions/switchNetwork.ts | 11 ---- .../actions/approvePermission.ts | 12 ----- .../pages/NotificationPage/actions/index.ts | 1 - .../pages/NotificationPage/actions/token.ts | 10 ---- .../playwright/pages/NotificationPage/page.ts | 2 +- wallets/phantom/src/playwright/pages/index.ts | 1 - .../utils/clickLocatorIfCondition.ts | 10 ---- wallets/phantom/src/selectors/index.ts | 2 - .../src/selectors/pages/CrashPage/index.ts | 6 --- .../src/selectors/pages/HomePage/index.ts | 54 +------------------ .../src/selectors/pages/HomePage/settings.ts | 7 +-- .../pages/NotificationPage/connectPage.ts | 4 -- .../pages/NotificationPage/ethereumRpcPage.ts | 4 -- .../selectors/pages/NotificationPage/index.ts | 4 -- .../pages/NotificationPage/permissionPage.ts | 10 ---- .../pages/NotificationPage/signaturePage.ts | 10 +--- .../pages/OnboardingPage/analyticsPage.ts | 6 --- .../pages/OnboardingPage/getStartedPage.ts | 4 -- .../selectors/pages/OnboardingPage/index.ts | 15 ------ .../pages/OnboardingPage/pinExtensionPage.ts | 6 --- .../secretRecoveryPhrasePage.ts | 7 --- .../src/selectors/pages/SettingsPage/index.ts | 23 -------- .../test/playwright/commonSteps/aaveSetup.ts | 36 +++++++++++++ .../commonSteps/solanaSandboxSetup.ts | 22 ++++++++ .../e2e/approveTokenPermission.spec.ts | 23 +++----- .../playwright/e2e/confirmSignature.spec.ts | 10 ++-- .../playwright/e2e/confirmTransaction.spec.ts | 21 +++++--- .../playwright/e2e/rejectSignature.spec.ts | 8 +-- .../e2e/rejectTokenPermission.spec.ts | 27 +++------- .../playwright/e2e/rejectTransaction.spec.ts | 19 ++++--- 37 files changed, 114 insertions(+), 348 deletions(-) delete mode 100644 wallets/phantom/src/playwright/pages/CrashPage/page.ts delete mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts delete mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts delete mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts delete mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts delete mode 100644 wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts delete mode 100644 wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts delete mode 100644 wallets/phantom/src/selectors/pages/CrashPage/index.ts delete mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts delete mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts delete mode 100644 wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts delete mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts delete mode 100644 wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts delete mode 100644 wallets/phantom/src/selectors/pages/SettingsPage/index.ts create mode 100644 wallets/phantom/test/playwright/commonSteps/aaveSetup.ts create mode 100644 wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts diff --git a/wallets/phantom/src/playwright/Phantom.ts b/wallets/phantom/src/playwright/Phantom.ts index 7cf6d9044..557e8d06d 100644 --- a/wallets/phantom/src/playwright/Phantom.ts +++ b/wallets/phantom/src/playwright/Phantom.ts @@ -2,7 +2,7 @@ import type { BrowserContext, Page } from '@playwright/test' import type { GasSettings } from '../type/GasSettings' import type { Networks } from '../type/Networks' import { PhantomAbstract } from '../type/PhantomAbstract' -import { CrashPage, HomePage, NotificationPage, OnboardingPage, UnlockPage } from './pages' +import { HomePage, NotificationPage, OnboardingPage, UnlockPage } from './pages' const NO_EXTENSION_ID_ERROR = new Error('Phantom extensionId is not set') @@ -16,14 +16,6 @@ const NO_EXTENSION_ID_ERROR = new Error('Phantom extensionId is not set') * @extends PhantomAbstract */ export class Phantom extends PhantomAbstract { - /** - * This property can be used to access selectors for the crash page. - * - * @public - * @readonly - */ - readonly crashPage: CrashPage - /** * This property can be used to access selectors for the onboarding page. * @@ -72,7 +64,6 @@ export class Phantom extends PhantomAbstract { ) { super(password, extensionId) - this.crashPage = new CrashPage() this.onboardingPage = new OnboardingPage(page) this.unlockPage = new UnlockPage(page) this.homePage = new HomePage(page) diff --git a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts index 5c83ef6c9..5f19c6c74 100644 --- a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts +++ b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts @@ -1,5 +1,5 @@ import path from 'node:path' -import { type Page, chromium, expect } from '@playwright/test' +import { type Page, chromium } from '@playwright/test' import { test as base } from '@playwright/test' import { CACHE_DIR_NAME, @@ -19,8 +19,6 @@ type PhantomFixtures = { phantom: Phantom extensionId: string phantomPage: Page - aavePage: Page - solanaSandboxPage: Page } // If setup phantomPage in a fixture, browser does not handle it properly (even if ethereum.isConnected() is true, it's not reflected on the page). @@ -107,47 +105,6 @@ export const phantomFixtures = (walletSetup: ReturnType { await page.goto('/') - await use(page) - }, - aavePage: async ({ page, phantom }, use) => { - await page.goto('https://app.aave.com') - - await phantom.toggleTestnetMode() - - await page.locator('button#settings-button').click() - await page.locator('li:has-text("Testnet mode")').click() - await expect(page.getByRole('button', { name: 'TESTNET' })).toBeVisible() - - await page.getByRole('button', { name: 'Connect wallet' }).first().click() - await page.getByRole('button', { name: 'Phantom' }).click() - - await phantom.connectToDapp() - await phantom.page.waitForTimeout(1_000) - await phantom.closeUnsupportedNetworkWarning() - - await expect(page.getByText('0xf3...2266'), '"0xf3...2266" should be visible').toBeVisible() - - await use(page) - }, - solanaSandboxPage: async ({ page, phantom }, use) => { - await phantom.page.waitForTimeout(1_000) - await phantom.importWalletFromPrivateKey( - 'solana', - 'XQaKFLLSKbzpVzmfJrj4yUjAyFy2Eu7JcNdbPdnLuod2Uw3yf3tjGd4ha1DBfFdjkZFX1PZg3knth2Tz2tvd8C4' - ) - - await phantom.toggleTestnetMode() - - await page.goto('https://r3byv.csb.app/') - await page.locator('a:has-text("Yes, proceed to preview")').click() - await page.getByRole('button', { name: 'Connect to Phantom' }).click() - - await phantom.connectToDapp() - - await page.getByRole('button', { name: 'Clear Logs' }).click() - - await expect(page.getByText('Click a button and watch magic happen...')).toBeVisible() - await use(page) } }) diff --git a/wallets/phantom/src/playwright/pages/CrashPage/page.ts b/wallets/phantom/src/playwright/pages/CrashPage/page.ts deleted file mode 100644 index e731a6e94..000000000 --- a/wallets/phantom/src/playwright/pages/CrashPage/page.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Selectors from '../../../selectors/pages/CrashPage' - -export class CrashPage { - static readonly selectors = Selectors - readonly selectors = Selectors -} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts index 71155737a..f43ab80bc 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts @@ -1,4 +1,3 @@ -export * from './popups' export * from './lock' export * from './importWalletFromPrivateKey' export * from './switchAccount' diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts deleted file mode 100644 index 36378580b..000000000 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closePopover.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Page } from '@playwright/test' -import Selectors from '../../../../../selectors/pages/HomePage' -import { clickLocatorIfCondition } from '../../../../utils/clickLocatorIfCondition' - -// Closes the popover with news, rainbows, unicorns, and other stuff. -export async function closePopover(page: Page) { - // We're using `first()` here just in case there are multiple popovers, which happens sometimes. - const closeButtonLocator = page.locator(Selectors.popover.closeButton).first() - - // TODO: Extract & make configurable - await clickLocatorIfCondition(closeButtonLocator, () => closeButtonLocator.isVisible(), 1_000) -} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts deleted file mode 100644 index 1664221e0..000000000 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/closeRecoveryPhraseReminder.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Page } from '@playwright/test' -import Selectors from '../../../../../selectors/pages/HomePage' -import { clickLocatorIfCondition } from '../../../../utils/clickLocatorIfCondition' - -export async function closeRecoveryPhraseReminder(page: Page) { - const closeButtonLocator = page.locator(Selectors.recoveryPhraseReminder.gotItButton) - - // TODO: Extract & make configurable - await clickLocatorIfCondition(closeButtonLocator, () => closeButtonLocator.isVisible(), 1_000) -} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts deleted file mode 100644 index 2c83dc704..000000000 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/popups/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './closePopover' -export * from './closeRecoveryPhraseReminder' diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts deleted file mode 100644 index f72df6096..000000000 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/switchNetwork.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Page } from '@playwright/test' -import Selectors from '../../../../selectors/pages/HomePage' - -export async function openTestnetSection(page: Page) { - const toggleButtonLocator = page.locator(Selectors.networkDropdown.showTestNetworksToggle) - const classes = await toggleButtonLocator.getAttribute('class') - if (classes?.includes('toggle-button--off')) { - await toggleButtonLocator.click() - await page.locator(Selectors.networkDropdown.toggleOn).isChecked() - } -} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts index 303883003..2e26c1766 100644 --- a/wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/approvePermission.ts @@ -3,17 +3,6 @@ import Selectors from '../../../../selectors/pages/NotificationPage' import type { GasSettings } from '../../../../type/GasSettings' import { transaction } from './transaction' -const editTokenPermission = async (notificationPage: Page, customSpendLimit: 'max' | number) => { - if (customSpendLimit === 'max') { - await notificationPage.locator(Selectors.PermissionPage.approve.maxButton).click() - return - } - - await notificationPage - .locator(Selectors.PermissionPage.approve.customSpendingCapInput) - .fill(customSpendLimit.toString()) -} - const approveTokenPermission = async (notificationPage: Page, gasSetting: GasSettings) => { // Approve flow is identical to the confirm transaction flow after we click the "Next" button. await transaction.confirm(notificationPage, gasSetting) @@ -24,7 +13,6 @@ const rejectTokenPermission = async (notificationPage: Page) => { } export const approvePermission = { - editTokenPermission, approve: approveTokenPermission, reject: rejectTokenPermission } diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts index 9ed19e5a2..35d32209a 100644 --- a/wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts +++ b/wallets/phantom/src/playwright/pages/NotificationPage/actions/index.ts @@ -3,5 +3,4 @@ export * from './closeUnsupportedNetworkWarning' export * from './connectToDapp' export * from './signSimpleMessage' export * from './signStructuredMessage' -export * from './token' export * from './transaction' diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts b/wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts deleted file mode 100644 index b909eff66..000000000 --- a/wallets/phantom/src/playwright/pages/NotificationPage/actions/token.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Page } from '@playwright/test' -import Selectors from '../../../../selectors/pages/NotificationPage' - -async function addNew(notificationPage: Page) { - await notificationPage.locator(Selectors.ActionFooter.confirmActionButton).click() -} - -export const token = { - addNew -} diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/page.ts b/wallets/phantom/src/playwright/pages/NotificationPage/page.ts index d00a8ab4e..79b98b098 100644 --- a/wallets/phantom/src/playwright/pages/NotificationPage/page.ts +++ b/wallets/phantom/src/playwright/pages/NotificationPage/page.ts @@ -96,7 +96,7 @@ export class NotificationPage { await approvePermission.reject(notificationPage) } - async closeUnsupportedNetworkWarning(extensionId: string, account?: string) { + async closeUnsupportedNetworkWarning(extensionId: string) { const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) await closeUnsupportedNetworkWarning(notificationPage) diff --git a/wallets/phantom/src/playwright/pages/index.ts b/wallets/phantom/src/playwright/pages/index.ts index 78f98ea2c..f421d035a 100644 --- a/wallets/phantom/src/playwright/pages/index.ts +++ b/wallets/phantom/src/playwright/pages/index.ts @@ -1,5 +1,4 @@ export * from './OnboardingPage/page' -export * from './CrashPage/page' export * from './UnlockPage/page' export * from './HomePage/page' export * from './NotificationPage/page' diff --git a/wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts b/wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts deleted file mode 100644 index ce919a455..000000000 --- a/wallets/phantom/src/playwright/utils/clickLocatorIfCondition.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Locator } from '@playwright/test' -import { waitFor } from './waitFor' - -// TODO: Extract & make configurable -export async function clickLocatorIfCondition(locator: Locator, condition: () => Promise, timeout = 3_000) { - const shouldClick = await waitFor(condition, timeout, false) - if (shouldClick) { - await locator.click() - } -} diff --git a/wallets/phantom/src/selectors/index.ts b/wallets/phantom/src/selectors/index.ts index e98e7d7de..835505d07 100644 --- a/wallets/phantom/src/selectors/index.ts +++ b/wallets/phantom/src/selectors/index.ts @@ -1,9 +1,7 @@ export * from './loading' export * from './error' -export { default as crashPage } from './pages/CrashPage' export { default as homePage } from './pages/HomePage' export { default as unlockPage } from './pages/UnlockPage' export { default as notificationPage } from './pages/NotificationPage' export { default as onboardingPage } from './pages/OnboardingPage' -export { default as settingsPage } from './pages/SettingsPage' diff --git a/wallets/phantom/src/selectors/pages/CrashPage/index.ts b/wallets/phantom/src/selectors/pages/CrashPage/index.ts deleted file mode 100644 index 0c94db8ba..000000000 --- a/wallets/phantom/src/selectors/pages/CrashPage/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -const container = 'section.error-page' - -export default { - header: `${container} > .error-page__header`, - errors: `${container} > .error-page__details li` -} diff --git a/wallets/phantom/src/selectors/pages/HomePage/index.ts b/wallets/phantom/src/selectors/pages/HomePage/index.ts index 1a2ca2ca4..f8782ff9a 100644 --- a/wallets/phantom/src/selectors/pages/HomePage/index.ts +++ b/wallets/phantom/src/selectors/pages/HomePage/index.ts @@ -7,9 +7,7 @@ const addNewAccountMenu = { } const renameAccountMenu = { - saveButton: `button${createDataTestSelector('primary-button')}:has-text("Save")`, - confirmRenameButton: 'div.editable-label button.mm-button-icon', - renameInput: '.mm-text-field .mm-box--padding-right-4' + saveButton: `button${createDataTestSelector('primary-button')}:has-text("Save")` } const importAccountMenu = { @@ -49,48 +47,6 @@ const accountMenu = { const manageAccountButton = (accountName: string) => `[role="button"][data-testid="manage-accounts-sortable-${accountName}"]` -const threeDotsMenu = { - threeDotsButton: createDataTestSelector('account-options-menu-button'), - settingsButton: createDataTestSelector('global-menu-settings'), - lockButton: createDataTestSelector('global-menu-lock'), - accountDetailsButton: createDataTestSelector('account-list-menu-details'), - accountDetailsCloseButton: '.mm-modal-content .mm-modal-header button.mm-button-icon.mm-button-icon--size-sm' -} - -const popoverContainer = '.popover-container' -const popover = { - closeButton: `${popoverContainer} ${createDataTestSelector('popover-close')}` -} - -const recoveryPhraseReminder = { - gotItButton: '.recovery-phrase-reminder button.btn-primary' -} - -const networkDropdownContainer = '.multichain-network-list-menu-content-wrapper' -const networkDropdown = { - dropdownButton: createDataTestSelector('network-display'), - closeDropdownButton: `${networkDropdownContainer} > section > div:nth-child(1) button`, - networksList: `${networkDropdownContainer} .multichain-network-list-menu`, - networks: `${networkDropdownContainer} .multichain-network-list-item p`, - showTestNetworksToggle: `${networkDropdownContainer} > section > div > label.toggle-button`, - toggleOff: `${networkDropdownContainer} label.toggle-button.toggle-button--off`, - toggleOn: `${networkDropdownContainer} label.toggle-button.toggle-button--on`, - closeNetworkPopupButton: - '.mm-modal-header button.mm-button-icon.mm-box--color-icon-default.mm-box--background-color-transparent.mm-box--rounded-lg' -} - -const tabContainer = '.tabs__content' -const activityTab = { - activityTabButton: `${createDataTestSelector('home__activity-tab')}`, - transactionsList: `${tabContainer} .transaction-list__transactions`, - pendingQueuedTransactions: `${tabContainer} .transaction-list__pending-transactions .transaction-list-item .transaction-status-label--queued`, - pendingUnapprovedTransactions: `${tabContainer} .transaction-list__pending-transactions .transaction-list-item .transaction-status-label--unapproved`, - pendingApprovedTransactions: `${tabContainer} .transaction-list__pending-transactions .transaction-list-item .transaction-status-label--pending`, - completedTransactions: `${tabContainer} .transaction-list__completed-transactions .transaction-list-item` -} - -const singleToken = '.multichain-token-list-item' - export default { solanaWalletAddress: createDataTestSelector('account-header-chain-solana:101'), ethereumWalletAddress: createDataTestSelector('account-header-chain-eip155:1'), @@ -100,16 +56,8 @@ export default { copyAccountAddressButton: createDataTestSelector('address-copy-button-text'), currentNetwork: `${createDataTestSelector('network-display')} span:nth-of-type(1)`, headerBackButton: createDataTestSelector('header--back'), - threeDotsMenu, settings, - activityTab, - networkDropdown, accountMenu, editAccountMenu, - recoveryPhraseReminder, - popover, - portfolio: { - singleToken - }, manageAccountButton } diff --git a/wallets/phantom/src/selectors/pages/HomePage/settings.ts b/wallets/phantom/src/selectors/pages/HomePage/settings.ts index 2ae518506..8aaaaaaf3 100644 --- a/wallets/phantom/src/selectors/pages/HomePage/settings.ts +++ b/wallets/phantom/src/selectors/pages/HomePage/settings.ts @@ -1,10 +1,5 @@ import { createDataTestSelector } from '../../createDataTestSelector' -const advanced = { - showTestNetworksToggle: `${createDataTestSelector('advanced-setting-show-testnet-conversion')} .toggle-button`, - dismissSecretRecoveryPhraseReminderToggle: '.settings-page__content-row:nth-of-type(11) .toggle-button' -} - const devSettings = { toggleTestnetMode: createDataTestSelector('toggleTestNetwork'), toggleEnableCopyTransaction: createDataTestSelector('solana-copy-transaction') @@ -20,7 +15,7 @@ export default { unlocWallet: createDataTestSelector('data-testid="unlock-form-submit-button"'), developerSettingsButton: createDataTestSelector('settings-item-developer-settings'), closeSettingsButton: createDataTestSelector('settings-menu-close-button'), - advanced, + devSettings, securityAndPrivacy } diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts deleted file mode 100644 index 3e50f4349..000000000 --- a/wallets/phantom/src/selectors/pages/NotificationPage/connectPage.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - accountOption: '.choose-account-list .choose-account-list__list .choose-account-list__account', - accountCheckbox: 'input.choose-account-list__list-check-box' -} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts deleted file mode 100644 index 56e2fe8cd..000000000 --- a/wallets/phantom/src/selectors/pages/NotificationPage/ethereumRpcPage.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - approveNewRpc: '.confirmation-warning-modal__content .mm-button-primary--type-danger', - rejectNewRpc: '.confirmation-warning-modal__content .mm-button-secondary' -} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/index.ts b/wallets/phantom/src/selectors/pages/NotificationPage/index.ts index 66c074de2..2764c386f 100644 --- a/wallets/phantom/src/selectors/pages/NotificationPage/index.ts +++ b/wallets/phantom/src/selectors/pages/NotificationPage/index.ts @@ -1,13 +1,9 @@ import ActionFooter from './actionFooter' -import ConnectPage from './connectPage' -import PermissionPage from './permissionPage' import SignaturePage from './signaturePage' import TransactionPage from './transactionPage' export default { ActionFooter, - ConnectPage, - PermissionPage, SignaturePage, TransactionPage } diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts deleted file mode 100644 index 70fe9b90f..000000000 --- a/wallets/phantom/src/selectors/pages/NotificationPage/permissionPage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createDataTestSelector } from '../../createDataTestSelector' - -const approve = { - maxButton: createDataTestSelector('custom-spending-cap-max-button'), - customSpendingCapInput: createDataTestSelector('custom-spending-cap-input') -} - -export default { - approve -} diff --git a/wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts b/wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts index 32e35e013..fa3649553 100644 --- a/wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts +++ b/wallets/phantom/src/selectors/pages/NotificationPage/signaturePage.ts @@ -1,14 +1,7 @@ import { createDataTestSelector } from '../../createDataTestSelector' -const simpleMessage = { - signButton: `.request-signature__footer ${createDataTestSelector('request-signature__sign')}`, - rejectButton: '.request-signature__footer button.btn-secondary' -} - const structuredMessage = { - scrollDownButton: `.signature-request-message ${createDataTestSelector('signature-request-scroll-button')}`, - signButton: `.signature-request-footer ${createDataTestSelector('signature-sign-button')}`, - rejectButton: `.signature-request-footer ${createDataTestSelector('signature-cancel-button')}` + scrollDownButton: `.signature-request-message ${createDataTestSelector('signature-request-scroll-button')}` } const riskModal = { @@ -19,7 +12,6 @@ const riskModal = { } export default { - simpleMessage, structuredMessage, riskModal } diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts deleted file mode 100644 index 7738bd2ed..000000000 --- a/wallets/phantom/src/selectors/pages/OnboardingPage/analyticsPage.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createDataTestSelector } from '../../createDataTestSelector' - -export default { - optIn: createDataTestSelector('metametrics-i-agree'), - optOut: createDataTestSelector('metametrics-no-thanks') -} diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts index 67291216e..fe065be96 100644 --- a/wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/getStartedPage.ts @@ -1,8 +1,4 @@ -import { createDataTestSelector } from '../../createDataTestSelector' - export default { - termsOfServiceCheckbox: createDataTestSelector('onboarding-terms-checkbox'), - createNewWallet: `button:has-text("Create a new wallet")`, importWallet: 'text=I already have a wallet', importRecoveryPhraseButton: 'text=Import Recovery Phrase' } diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/index.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/index.ts index c681569c8..75c77e41f 100644 --- a/wallets/phantom/src/selectors/pages/OnboardingPage/index.ts +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/index.ts @@ -1,25 +1,10 @@ -import AnalyticsPageSelectors from './analyticsPage' import GetStartedPageSelectors from './getStartedPage' -import PinExtensionPageSelectors from './pinExtensionPage' import SecretRecoveryPhrasePageSelectors from './secretRecoveryPhrasePage' import WalletCreationSuccessPageSelectors from './walletCreationSuccessPage' // biome-ignore format: empty lines should be preserved export default { - // Initial Welcome Page GetStartedPageSelectors, - - // 2nd Page - AnalyticsPageSelectors, - - // 3rd Page with two steps: - // - Input Secret Recovery Phrase - // - Create Password SecretRecoveryPhrasePageSelectors, - - // 4th Page WalletCreationSuccessPageSelectors, - - // 5th Page - PinExtensionPageSelectors, }; diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts deleted file mode 100644 index 64a9b9634..000000000 --- a/wallets/phantom/src/selectors/pages/OnboardingPage/pinExtensionPage.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createDataTestSelector } from '../../createDataTestSelector' - -export default { - nextButton: createDataTestSelector('pin-extension-next'), - confirmButton: createDataTestSelector('pin-extension-done') -} diff --git a/wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts b/wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts index 92d8b9f23..00c36632a 100644 --- a/wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts +++ b/wallets/phantom/src/selectors/pages/OnboardingPage/secretRecoveryPhrasePage.ts @@ -1,14 +1,8 @@ import { createDataTestSelector } from '../../createDataTestSelector' const recoveryStep = { - selectNumberOfWordsDropdown: '.import-srp__number-of-words-dropdown > .dropdown__select', - selectNumberOfWordsOption: (option: number | string) => `${option}`, - // secretRecoveryPhraseWord: (index: number) => createDataTestSelector(`import-srp__srp-word-${index}`), secretRecoveryPhraseWord: (index: number) => createDataTestSelector(`secret-recovery-phrase-word-input-${index}`), - // confirmSecretRecoveryPhraseButton: - // createDataTestSelector("import-srp-confirm"), confirmSecretRecoveryPhraseButton: createDataTestSelector('onboarding-form-submit-button'), - // error: ".mm-banner-alert.import-srp__srp-error div", error: createDataTestSelector('onboarding-import-secret-recovery-phrase-error-message') } @@ -20,7 +14,6 @@ const passwordStep = { passwordInput: createDataTestSelector('onboarding-form-password-input'), confirmPasswordInput: createDataTestSelector('onboarding-form-confirm-password-input'), acceptTermsCheckbox: createDataTestSelector('onboarding-form-terms-of-service-checkbox'), - // importWalletButton: createDataTestSelector("onboarding-form-submit-button"), continue: continueButton, error: `${createDataTestSelector('create-password-new')} + h6 > span > span` } diff --git a/wallets/phantom/src/selectors/pages/SettingsPage/index.ts b/wallets/phantom/src/selectors/pages/SettingsPage/index.ts deleted file mode 100644 index 57e201a10..000000000 --- a/wallets/phantom/src/selectors/pages/SettingsPage/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDataTestSelector } from '../../createDataTestSelector' - -const menuOption = '.settings-page__content__tabs .tab-bar .tab-bar__tab' - -const settings = { - menuOption, - advancedSettings: `${menuOption}:nth-child(2)`, - ethSignToggle: `${createDataTestSelector('advanced-setting-toggle-ethsign')} .eth-sign-toggle`, - ethSignWarning: - '.settings-page__content-row .mm-banner-alert.mm-banner-alert--severity-danger.mm-box--background-color-error-muted' -} - -const confirmationModal = { - confirmationCheckbox: createDataTestSelector('eth-sign__checkbox'), - continueButton: '.modal__content button.mm-button-primary', - manualConfirmationInput: '#enter-eth-sign-text', - enableButton: '.modal__content button.mm-button-primary.mm-button-primary--type-danger' -} - -export default { - settings, - confirmationModal -} diff --git a/wallets/phantom/test/playwright/commonSteps/aaveSetup.ts b/wallets/phantom/test/playwright/commonSteps/aaveSetup.ts new file mode 100644 index 000000000..93ddc2b5a --- /dev/null +++ b/wallets/phantom/test/playwright/commonSteps/aaveSetup.ts @@ -0,0 +1,36 @@ +import { type Page, expect } from '@playwright/test' +import type { Phantom } from '../../../src/playwright' + +export const aaveSetup = async (page: Page, phantom: Phantom) => { + await page.goto('https://app.aave.com') + + await phantom.toggleTestnetMode() + + await page.locator('button#settings-button').click() + await page.locator('li:has-text("Testnet mode")').click() + await expect(page.getByRole('button', { name: 'TESTNET' })).toBeVisible() + + await page.getByRole('button', { name: 'Connect wallet' }).first().click() + await page.getByRole('button', { name: 'Phantom' }).click() + + await phantom.connectToDapp() + await phantom.page.waitForTimeout(1_000) + await phantom.closeUnsupportedNetworkWarning() + + await expect(page.getByText('0xf3...2266'), '"0xf3...2266" should be visible').toBeVisible() + + await page.goto( + 'https://app.aave.com/reserve-overview/?underlyingAsset=0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357&marketName=proto_sepolia_v3' + ) + + await page.getByRole('button', { name: 'Supply' }).click() + await page.locator('input[aria-label="amount input"]').fill('1') + + await expect( + page.getByRole('button', { + name: 'Supply DAI' + }) + ).toBeDisabled() + + await page.locator('button:has-text("Approve DAI to continue")').click() +} diff --git a/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts b/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts new file mode 100644 index 000000000..7f8bb2d99 --- /dev/null +++ b/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts @@ -0,0 +1,22 @@ +import { type Page, expect } from '@playwright/test' +import type { Phantom } from '../../../src/playwright' + +export const solanaSandboxSetup = async (page: Page, phantom: Phantom) => { + await phantom.page.waitForTimeout(1_000) + await phantom.importWalletFromPrivateKey( + 'solana', + 'XQaKFLLSKbzpVzmfJrj4yUjAyFy2Eu7JcNdbPdnLuod2Uw3yf3tjGd4ha1DBfFdjkZFX1PZg3knth2Tz2tvd8C4' + ) + + await phantom.toggleTestnetMode() + + await page.goto('https://r3byv.csb.app/') + await page.locator('a:has-text("Yes, proceed to preview")').click() + await page.getByRole('button', { name: 'Connect to Phantom' }).click() + + await phantom.connectToDapp() + + await page.getByRole('button', { name: 'Clear Logs' }).click() + + await expect(page.getByText('Click a button and watch magic happen...')).toBeVisible() +} diff --git a/wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts b/wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts index 4399e1110..938721984 100644 --- a/wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts +++ b/wallets/phantom/test/playwright/e2e/approveTokenPermission.spec.ts @@ -1,27 +1,20 @@ +import { aaveSetup } from '../commonSteps/aaveSetup' import synpress from '../synpress' const test = synpress const { expect } = test -test('should approve token with the default limit', async ({ aavePage, phantom }) => { +test('should approve token with the default limit', async ({ page, phantom }) => { test.setTimeout(80_000) - await aavePage.goto( - 'https://app.aave.com/reserve-overview/?underlyingAsset=0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357&marketName=proto_sepolia_v3' - ) - - await aavePage.getByRole('button', { name: 'Supply' }).click() - await aavePage.locator('input[aria-label="amount input"]').fill('1') - - const supplyDaiLocator = aavePage.getByRole('button', { - name: 'Supply DAI' - }) - await expect(supplyDaiLocator).toBeDisabled() - - await aavePage.locator('button:has-text("Approve DAI to continue")').click() + await aaveSetup(page, phantom) await phantom.approveTokenPermission() - await expect(supplyDaiLocator).toBeEnabled({ timeout: 10_000 }) + await expect( + page.getByRole('button', { + name: 'Supply DAI' + }) + ).toBeEnabled({ timeout: 10_000 }) }) diff --git a/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts index 6d7c55d6b..824893f74 100644 --- a/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts @@ -6,7 +6,7 @@ const test = synpress const { expect } = test test('should confirm `personal_sign`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#personalSign').click() @@ -18,7 +18,7 @@ test('should confirm `personal_sign`', async ({ page, phantom }) => { }) test('should confirm `eth_signTypedData`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#signTypedData').click() @@ -34,7 +34,7 @@ test('should confirm `eth_signTypedData`', async ({ page, phantom }) => { }) test('should confirm `eth_signTypedData_v3`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#signTypedDataV3').click() @@ -50,7 +50,7 @@ test('should confirm `eth_signTypedData_v3`', async ({ page, phantom }) => { }) test('should confirm `eth_signTypedData_v4`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#signTypedDataV4').click() @@ -66,7 +66,7 @@ test('should confirm `eth_signTypedData_v4`', async ({ page, phantom }) => { }) test('should confirm `eth_sign`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#ethSign').click() diff --git a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts index da8212b6d..57e95a876 100644 --- a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts @@ -1,26 +1,31 @@ import { connectPhantomToTestDapp } from '../commonSteps/connectPhantomToTestDapp' +import { solanaSandboxSetup } from '../commonSteps/solanaSandboxSetup' import synpress from '../synpress' const test = synpress const { expect } = test -test('should Sign Transaction ', async ({ solanaSandboxPage, phantom }) => { - await solanaSandboxPage.getByRole('button', { name: 'Sign Transaction' }).click() +test('should Sign Transaction ', async ({ page, phantom }) => { + await solanaSandboxSetup(page, phantom) + + await page.getByRole('button', { name: 'Sign Transaction' }).click() await phantom.confirmTransaction() - await expect(solanaSandboxPage.getByText('> success')).toBeVisible() + await expect(page.getByText('> success')).toBeVisible() }) -test('should Sign All Transactions ', async ({ solanaSandboxPage, phantom }) => { - await solanaSandboxPage.getByRole('button', { name: 'Sign All Transaction' }).click() +test('should Sign All Transactions ', async ({ page, phantom }) => { + await solanaSandboxSetup(page, phantom) + + await page.getByRole('button', { name: 'Sign All Transaction' }).click() await phantom.confirmTransaction() - await expect(solanaSandboxPage.getByText('> success')).toBeVisible() + await expect(page.getByText('> success')).toBeVisible() }) test('should confirm contract deployment with default gas setting', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await expect(page.locator('#tokenAddresses')).toBeEmpty() await page.locator('#createToken').click() @@ -32,7 +37,7 @@ test('should confirm contract deployment with default gas setting', async ({ pag ;(['Slow', 'Fast'] as const).forEach((gasSetting) => { test(`should confirm contract deployment with ${gasSetting} gas setting`, async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await expect(page.locator('#tokenAddresses')).toBeEmpty() await page.locator('#createToken').click() diff --git a/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts b/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts index 8abd7f670..93eb014f9 100644 --- a/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts +++ b/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts @@ -6,7 +6,7 @@ const test = synpress const { expect } = test test('should reject `personal_sign`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#personalSign').click() @@ -17,7 +17,7 @@ test('should reject `personal_sign`', async ({ page, phantom }) => { }) test('should reject `eth_signTypedData`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#signTypedData').click() @@ -27,7 +27,7 @@ test('should reject `eth_signTypedData`', async ({ page, phantom }) => { }) test('should reject `eth_signTypedData_v3`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#signTypedDataV3').click() @@ -37,7 +37,7 @@ test('should reject `eth_signTypedData_v3`', async ({ page, phantom }) => { }) test('should reject `eth_signTypedData_v4`', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await page.locator('#signTypedDataV4').click() diff --git a/wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts b/wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts index a83b39768..350c6ebbd 100644 --- a/wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts +++ b/wallets/phantom/test/playwright/e2e/rejectTokenPermission.spec.ts @@ -1,32 +1,21 @@ +import { aaveSetup } from '../commonSteps/aaveSetup' import synpress from '../synpress' const test = synpress const { expect } = test -test('should reject approve token request', async ({ aavePage, phantom }) => { +test('should reject approve token request', async ({ page, phantom }) => { test.setTimeout(80_000) - await aavePage.goto( - 'https://app.aave.com/reserve-overview/?underlyingAsset=0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357&marketName=proto_sepolia_v3' - ) + await aaveSetup(page, phantom) - await aavePage.getByRole('button', { name: 'Supply' }).click() - await aavePage.locator('input[aria-label="amount input"]').fill('1') - - const supplyDaiLocator = aavePage.getByRole('button', { - name: 'Supply DAI' - }) - await expect(supplyDaiLocator).toBeDisabled() - - await aavePage.locator('button:has-text("Approve DAI to continue")').click() - - await expect(aavePage.locator('button:has-text("Approve DAI to continue")')).not.toBeVisible() - await expect(aavePage.locator('button:has-text("Approving DAI...")')).toBeVisible() + await expect(page.locator('button:has-text("Approve DAI to continue")')).not.toBeVisible() + await expect(page.locator('button:has-text("Approving DAI...")')).toBeVisible() await phantom.rejectTokenPermission() - await expect(aavePage.locator('button:has-text("Approving DAI...")')).not.toBeVisible({ timeout: 10_000 }) - await expect(aavePage.locator('button:has-text("Approve DAI to continue")')).toBeVisible() - await expect(aavePage.getByText('There was some error. Please try changing the parameters or')).toBeVisible() + await expect(page.locator('button:has-text("Approving DAI...")')).not.toBeVisible({ timeout: 10_000 }) + await expect(page.locator('button:has-text("Approve DAI to continue")')).toBeVisible() + await expect(page.getByText('There was some error. Please try changing the parameters or')).toBeVisible() }) diff --git a/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts index 6cb2e61c2..1a9356c49 100644 --- a/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts @@ -1,26 +1,31 @@ import { connectPhantomToTestDapp } from '../commonSteps/connectPhantomToTestDapp' +import { solanaSandboxSetup } from '../commonSteps/solanaSandboxSetup' import synpress from '../synpress' const test = synpress const { expect } = test -test('should Reject Transaction ', async ({ solanaSandboxPage, phantom }) => { - await solanaSandboxPage.getByRole('button', { name: 'Sign Transaction' }).click() +test('should Reject Transaction ', async ({ page, phantom }) => { + await solanaSandboxSetup(page, phantom) + + await page.getByRole('button', { name: 'Sign Transaction' }).click() await phantom.rejectTransaction() - await expect(solanaSandboxPage.getByText('User rejected the request.')).toBeVisible() + await expect(page.getByText('User rejected the request.')).toBeVisible() }) -test('should Reject All Transactions ', async ({ solanaSandboxPage, phantom }) => { - await solanaSandboxPage.getByRole('button', { name: 'Sign All Transaction' }).click() +test('should Reject All Transactions ', async ({ page, phantom }) => { + await solanaSandboxSetup(page, phantom) + + await page.getByRole('button', { name: 'Sign All Transaction' }).click() await phantom.rejectTransaction() - await expect(solanaSandboxPage.getByText('User rejected the request.')).toBeVisible() + await expect(page.getByText('User rejected the request.')).toBeVisible() }) test('should reject contract deployment', async ({ page, phantom }) => { - connectPhantomToTestDapp(page, phantom) + await connectPhantomToTestDapp(page, phantom) await expect(page.locator('#tokenAddresses')).toBeEmpty() await page.locator('#createToken').click() From 61e12e66ca2fe129174b42193c71326189ba7501 Mon Sep 17 00:00:00 2001 From: drptbl Date: Sat, 22 Feb 2025 23:35:16 +0000 Subject: [PATCH 05/36] chore: build:cache:metamask and build:cache:phantom Signed-off-by: drptbl --- .github/workflows/test.yml | 8 +++-- package.json | 3 +- pnpm-lock.yaml | 69 +++----------------------------------- turbo.json | 3 -- 4 files changed, 12 insertions(+), 71 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ae6dcbdf..220e356a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,9 +61,13 @@ jobs: run: | pnpm run serve:test-dapp & - - name: Build cache + - name: Build cache (metamask) run: | - xvfb-run pnpm run build:cache + xvfb-run pnpm run build:cache:metamask + + - name: Build cache (phantom) + run: | + xvfb-run pnpm run build:cache:phantom - name: Run E2E tests (headful) run: | diff --git a/package.json b/package.json index cce0eb57b..e59d6ff67 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,8 @@ "license": "MIT", "scripts": { "build": "turbo build", - "build:cache": "turbo build:cache --filter=@synthetixio/synpress-metamask", + "build:cache:metamask": "turbo build:cache --filter=@synthetixio/synpress-metamask", "build:cache:phantom": "turbo build:cache --filter=@synthetixio/synpress-phantom", - "docs:build": "turbo docs:build --filter=docs", "format": "biome format . --write", "format:check": "biome format . --error-on-warnings", "preinstall": "npx only-allow pnpm", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c90e14dd6..510d4568d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -388,9 +388,6 @@ importers: '@vitest/coverage-v8': specifier: 1.2.2 version: 1.2.2(vitest@1.2.2(@types/node@20.11.17)) - cypress: - specifier: 13.17.0 - version: 13.17.0 rimraf: specifier: 5.0.5 version: 5.0.5 @@ -1664,6 +1661,9 @@ packages: peerDependencies: '@playwright/test': 1.48.2 + '@synthetixio/synpress-tsconfig@0.0.4': + resolution: {integrity: sha512-hbCj7Tr3fFt1oJ1ceu8aQeUgG8I8SHOUxM6V2sHHSK9+2caEKxg1ehqVBObY7Tt2X4WyZerJa92WPVpX/TKSRA==} + '@synthetixio/synpress-tsconfig@0.0.7': resolution: {integrity: sha512-dY6Qos3SiLeqzpvNVpNCt2Masw8Y9EESgLm9pzp3r4K+ayB/2jsbsaTBfLkT4LieqgV7oQmstV0P03t09Vyc8g==} @@ -2204,10 +2204,6 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - ci-info@4.1.0: - resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} - engines: {node: '>=8'} - clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2386,11 +2382,6 @@ packages: engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true - cypress@13.17.0: - resolution: {integrity: sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==} - engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} - hasBin: true - dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} @@ -6531,6 +6522,8 @@ snapshots: dependencies: '@playwright/test': 1.48.2 + '@synthetixio/synpress-tsconfig@0.0.4': {} + '@synthetixio/synpress-tsconfig@0.0.7': {} '@szmarczak/http-timer@5.0.1': @@ -7161,8 +7154,6 @@ snapshots: ci-info@3.9.0: {} - ci-info@4.1.0: {} - clean-stack@2.2.0: {} cli-boxes@3.0.0: {} @@ -7379,52 +7370,6 @@ snapshots: untildify: 4.0.0 yauzl: 2.10.0 - cypress@13.17.0: - dependencies: - '@cypress/request': 3.0.6 - '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.8 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.4.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - ci-info: 4.1.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.5 - commander: 6.2.1 - common-tags: 1.8.2 - dayjs: 1.11.13 - debug: 4.3.6(supports-color@8.1.1) - enquirer: 2.4.1 - eventemitter2: 6.4.7 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1(supports-color@8.1.1) - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.4.1) - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.8 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - process: 0.11.10 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.6.3 - supports-color: 8.1.1 - tmp: 0.2.3 - tree-kill: 1.2.2 - untildify: 4.0.0 - yauzl: 2.10.0 - dashdash@1.14.1: dependencies: assert-plus: 1.0.0 @@ -7463,10 +7408,6 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.3.6: - dependencies: - ms: 2.1.2 - debug@4.3.6(supports-color@8.1.1): dependencies: ms: 2.1.2 diff --git a/turbo.json b/turbo.json index 58c44315a..17ed57630 100644 --- a/turbo.json +++ b/turbo.json @@ -16,9 +16,6 @@ }, "test:playwright:headless": { "dependsOn": ["build", "build:cache"] - }, - "docs:build": { - "outputs": [".vitepress/dist/**"] } } } From bff85a1071b653195f825e55fda25ffeaabf4ede Mon Sep 17 00:00:00 2001 From: drptbl Date: Sat, 22 Feb 2025 23:38:44 +0000 Subject: [PATCH 06/36] fix: lint errors Signed-off-by: drptbl --- wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts index 57e95a876..1f1dbaf40 100644 --- a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts @@ -34,7 +34,6 @@ test('should confirm contract deployment with default gas setting', async ({ pag await expect(page.locator('#tokenAddresses')).toContainText('Creation Failed') }) - ;(['Slow', 'Fast'] as const).forEach((gasSetting) => { test(`should confirm contract deployment with ${gasSetting} gas setting`, async ({ page, phantom }) => { await connectPhantomToTestDapp(page, phantom) From 1cf492c22471d32b54e36c63f4d2083594b09582 Mon Sep 17 00:00:00 2001 From: drptbl Date: Sun, 23 Feb 2025 01:00:30 +0000 Subject: [PATCH 07/36] chore: add playwright-report to gitignore Signed-off-by: drptbl --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d4a4b71df..1ef33535e 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ playwright/.cache output synpress-source.txt + +playwright-report* From f531f4522144c5b83b1133d5ec3caf93e500b127 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 15:11:04 +0100 Subject: [PATCH 08/36] Fix for Sui and Monad screens --- .../pages/HomePage/actions/importWalletFromPrivateKey.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index 297917351..c5a78840a 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -1,4 +1,4 @@ -import type { Page } from '@playwright/test' +import { type Page, expect } from '@playwright/test' import Selectors from '../../../../selectors/pages/HomePage' import type { Networks } from '../../../../type/Networks' import { waitFor } from '../../../utils/waitFor' @@ -10,8 +10,15 @@ export async function importWalletFromPrivateKey( walletName?: string ) { const extensionUrl = page.url() + await page.goto(extensionUrl.replace('onboarding', 'popup')) + await expect(page.getByRole('button', { name: 'Enable Sui' })).toBeVisible() + await page.getByRole('button', { name: 'Not Now' }).click() + + await expect(page.getByRole('button', { name: 'Enable Monad' })).toBeVisible() + await page.getByRole('button', { name: 'Not Now' }).click() + await page.locator(Selectors.accountMenu.accountButton).click() await page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton).click() From aa88ce513d1d88e118762c61b9e2e05187e89e4e Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 17:00:45 +0100 Subject: [PATCH 09/36] Removing waitForPhantomLoad --- .../phantom/src/playwright/utils/waitFor.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wallets/phantom/src/playwright/utils/waitFor.ts b/wallets/phantom/src/playwright/utils/waitFor.ts index f9378161e..830fd601b 100644 --- a/wallets/phantom/src/playwright/utils/waitFor.ts +++ b/wallets/phantom/src/playwright/utils/waitFor.ts @@ -45,17 +45,17 @@ export const waitForSelector = async (selector: string, page: Page, timeout: num } export const waitForPhantomLoad = async (page: Page) => { - await Promise.all( - LoadingSelectors.loadingIndicators.map(async (selector) => { - await waitForSelector(selector, page, DEFAULT_TIMEOUT) - }) - ) - .then(() => { - return true - }) - .catch((error) => { - console.error('Error: ', error) - }) + // await Promise.all( + // LoadingSelectors.loadingIndicators.map(async (selector) => { + // await waitForSelector(selector, page, DEFAULT_TIMEOUT) + // }) + // ) + // .then(() => { + // return true + // }) + // .catch((error) => { + // console.error('Error: ', error) + // }) return page } From 9661bede4ca1953ae00c7cf0c15569b4a8b61de0 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 17:13:55 +0100 Subject: [PATCH 10/36] Removing waitForLoadState-networkidle --- wallets/phantom/src/playwright/utils/waitFor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallets/phantom/src/playwright/utils/waitFor.ts b/wallets/phantom/src/playwright/utils/waitFor.ts index 830fd601b..6b6a88c32 100644 --- a/wallets/phantom/src/playwright/utils/waitFor.ts +++ b/wallets/phantom/src/playwright/utils/waitFor.ts @@ -26,7 +26,7 @@ export const waitToBeHidden = async (selector: string, page: Page) => { export const waitUntilStable = async (page: Page) => { await page.waitForLoadState('load', { timeout: 10_000 }) await page.waitForLoadState('domcontentloaded', { timeout: 10_000 }) - await page.waitForLoadState('networkidle', { timeout: 10_000 }) + // await page.waitForLoadState('networkidle', { timeout: 10_000 }) } export const waitForSelector = async (selector: string, page: Page, timeout: number) => { From c4f747b9a5847e9b2f38f67c2ace10e8e3c0bd43 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 18:11:30 +0100 Subject: [PATCH 11/36] Removing some cleanup --- .../actions/importWalletFromPrivateKey.ts | 27 ++++++++-- .../playwright/pages/NotificationPage/page.ts | 49 ++++++++++--------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index c5a78840a..e1fabe40f 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -13,13 +13,30 @@ export async function importWalletFromPrivateKey( await page.goto(extensionUrl.replace('onboarding', 'popup')) - await expect(page.getByRole('button', { name: 'Enable Sui' })).toBeVisible() - await page.getByRole('button', { name: 'Not Now' }).click() + // =========== - await expect(page.getByRole('button', { name: 'Enable Monad' })).toBeVisible() - await page.getByRole('button', { name: 'Not Now' }).click() + await page.waitForTimeout(2_000) - await page.locator(Selectors.accountMenu.accountButton).click() + await expect(async () => { + const suiIsVisible = await page.getByRole('button', { name: 'Enable Sui' }).isVisible() + + if (suiIsVisible) { + await page.getByRole('button', { name: 'Not Now' }).click() + } + + const monadIsVisible = await page.getByRole('button', { name: 'Enable Monad' }).isVisible() + + if (monadIsVisible) { + await page.getByRole('button', { name: 'Not Now' }).click() + } + + await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() + await page.locator(Selectors.accountMenu.accountButton).click() + }).toPass() + + // =========== + + // await page.locator(Selectors.accountMenu.accountButton).click(); await page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton).click() diff --git a/wallets/phantom/src/playwright/pages/NotificationPage/page.ts b/wallets/phantom/src/playwright/pages/NotificationPage/page.ts index 79b98b098..4355cd33e 100644 --- a/wallets/phantom/src/playwright/pages/NotificationPage/page.ts +++ b/wallets/phantom/src/playwright/pages/NotificationPage/page.ts @@ -7,7 +7,7 @@ import { closeUnsupportedNetworkWarning, connectToDapp, signSimpleMessage, - signStructuredMessage, + // signStructuredMessage, transaction } from './actions' @@ -31,29 +31,31 @@ export class NotificationPage { private async beforeMessageSignature(extensionId: string) { const notificationPage = await getNotificationPageAndWaitForLoad(this.page.context(), extensionId) - const scrollButton = notificationPage.locator(Selectors.SignaturePage.structuredMessage.scrollDownButton) - const isScrollButtonPresent = (await scrollButton.count()) > 0 + // const scrollButton = notificationPage.locator(Selectors.SignaturePage.structuredMessage.scrollDownButton) + // const isScrollButtonPresent = (await scrollButton.count()) > 0 - let isScrollButtonVisible = false - if (isScrollButtonPresent) { - await scrollButton.waitFor({ state: 'visible' }) - isScrollButtonVisible = true - } + // let isScrollButtonVisible = false + // if (isScrollButtonPresent) { + // await scrollButton.waitFor({ state: 'visible' }) + // isScrollButtonVisible = true + // } return { - notificationPage, - isScrollButtonVisible + notificationPage + // isScrollButtonVisible, } } async signMessage(extensionId: string) { - const { notificationPage, isScrollButtonVisible } = await this.beforeMessageSignature(extensionId) + // const { notificationPage, isScrollButtonVisible } = + const { notificationPage } = await this.beforeMessageSignature(extensionId) - if (isScrollButtonVisible) { - await signStructuredMessage.sign(notificationPage) - } else { - await signSimpleMessage.sign(notificationPage) - } + await signSimpleMessage.sign(notificationPage) + // if (isScrollButtonVisible) { + // await signStructuredMessage.sign(notificationPage); + // } else { + // await signSimpleMessage.sign(notificationPage); + // } } async signMessageWithRisk(extensionId: string) { @@ -63,13 +65,16 @@ export class NotificationPage { } async rejectMessage(extensionId: string) { - const { notificationPage, isScrollButtonVisible } = await this.beforeMessageSignature(extensionId) + //const { notificationPage, isScrollButtonVisible } = + const { notificationPage } = await this.beforeMessageSignature(extensionId) - if (isScrollButtonVisible) { - await signStructuredMessage.reject(notificationPage) - } else { - await signSimpleMessage.reject(notificationPage) - } + await signSimpleMessage.reject(notificationPage) + + // if (isScrollButtonVisible) { + // await signStructuredMessage.reject(notificationPage); + // } else { + // await signSimpleMessage.reject(notificationPage); + // } } async confirmTransaction(extensionId: string, options?: { gasSetting?: GasSettings }) { From 93e1c3705e0f4cf8d92d639630fe65f80aef7916 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 18:29:57 +0100 Subject: [PATCH 12/36] Delay for random fail --- wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts index 824893f74..ac55b01cf 100644 --- a/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts @@ -70,6 +70,9 @@ test('should confirm `eth_sign`', async ({ page, phantom }) => { await page.locator('#ethSign').click() + // To avoid random fails + await page.waitForTimeout(1_000) + await phantom.confirmSignatureWithRisk() await expect(page.locator('#ethSignResult')).toContainText( From 204338ca2d4f92d1c75749b8f48223d938d34370 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 19:15:48 +0100 Subject: [PATCH 13/36] Close Sui and Monad screens if present --- .../actions/closeSuiAndMonadScreen.ts | 15 +++++++++++++ .../actions/importWalletFromPrivateKey.ts | 22 ++++++------------- .../pages/HomePage/actions/index.ts | 9 ++++---- 3 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts new file mode 100644 index 000000000..ef07dadf8 --- /dev/null +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts @@ -0,0 +1,15 @@ +import type { Page } from '@playwright/test' + +export async function closeSuiAndMonadIfPresent(page: Page) { + const suiIsVisible = await page.getByRole('button', { name: 'Enable Sui' }).isVisible() + + if (suiIsVisible) { + await page.getByRole('button', { name: 'Not Now' }).click() + } + + const monadIsVisible = await page.getByRole('button', { name: 'Enable Monad' }).isVisible() + + if (monadIsVisible) { + await page.getByRole('button', { name: 'Not Now' }).click() + } +} diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index e1fabe40f..9607cf788 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -2,6 +2,7 @@ import { type Page, expect } from '@playwright/test' import Selectors from '../../../../selectors/pages/HomePage' import type { Networks } from '../../../../type/Networks' import { waitFor } from '../../../utils/waitFor' +import { closeSuiAndMonadIfPresent } from './closeSuiAndMonadScreen' export async function importWalletFromPrivateKey( page: Page, @@ -16,24 +17,15 @@ export async function importWalletFromPrivateKey( // =========== await page.waitForTimeout(2_000) + await closeSuiAndMonadIfPresent(page) - await expect(async () => { - const suiIsVisible = await page.getByRole('button', { name: 'Enable Sui' }).isVisible() + await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() + await page.locator(Selectors.accountMenu.accountButton).click() - if (suiIsVisible) { - await page.getByRole('button', { name: 'Not Now' }).click() - } - - const monadIsVisible = await page.getByRole('button', { name: 'Enable Monad' }).isVisible() - - if (monadIsVisible) { - await page.getByRole('button', { name: 'Not Now' }).click() - } - - await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() - await page.locator(Selectors.accountMenu.accountButton).click() - }).toPass() + await page.waitForTimeout(2_000) + await closeSuiAndMonadIfPresent(page) + await expect(page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton)).toBeVisible() // =========== // await page.locator(Selectors.accountMenu.accountButton).click(); diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts index f43ab80bc..5a24acd3c 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/index.ts @@ -1,8 +1,9 @@ -export * from './lock' +export * from './addNewAccount' +export * from './closeSuiAndMonadScreen' export * from './importWalletFromPrivateKey' -export * from './switchAccount' +export * from './lock' +export * from './renameAccount' export * from './settings' +export * from './switchAccount' export * from './toggleTestnetMode' -export * from './addNewAccount' -export * from './renameAccount' export { default as getAccountAddress } from './getAccountAddress' From 1f0232b5568b1e6af8398a8a4b9b681dabcc1f44 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 19:32:42 +0100 Subject: [PATCH 14/36] Adding some delays to avoid random fails --- .../test/playwright/e2e/confirmSignature.spec.ts | 14 +++++++++++++- .../test/playwright/e2e/confirmTransaction.spec.ts | 14 ++++++++++++++ .../test/playwright/e2e/connectToDapp.spec.ts | 6 ++++++ .../test/playwright/e2e/rejectSignature.spec.ts | 6 ++++++ .../test/playwright/e2e/rejectTransaction.spec.ts | 11 +++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts index ac55b01cf..806ff8de4 100644 --- a/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmSignature.spec.ts @@ -10,6 +10,9 @@ test('should confirm `personal_sign`', async ({ page, phantom }) => { await page.locator('#personalSign').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmSignature() await expect(page.locator('#personalSignResult')).toHaveText( @@ -22,6 +25,9 @@ test('should confirm `eth_signTypedData`', async ({ page, phantom }) => { await page.locator('#signTypedData').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmSignature() await expect(page.locator('#signTypedDataResult')).toHaveText( @@ -38,6 +44,9 @@ test('should confirm `eth_signTypedData_v3`', async ({ page, phantom }) => { await page.locator('#signTypedDataV3').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmSignature() await expect(page.locator('#signTypedDataV3Result')).toHaveText( @@ -54,6 +63,9 @@ test('should confirm `eth_signTypedData_v4`', async ({ page, phantom }) => { await page.locator('#signTypedDataV4').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmSignature() await expect(page.locator('#signTypedDataV4Result')).toHaveText( @@ -71,7 +83,7 @@ test('should confirm `eth_sign`', async ({ page, phantom }) => { await page.locator('#ethSign').click() // To avoid random fails - await page.waitForTimeout(1_000) + await page.waitForTimeout(2_000) await phantom.confirmSignatureWithRisk() diff --git a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts index 1f1dbaf40..6afc2015d 100644 --- a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts @@ -10,6 +10,10 @@ test('should Sign Transaction ', async ({ page, phantom }) => { await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign Transaction' }).click() + + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmTransaction() await expect(page.getByText('> success')).toBeVisible() @@ -19,6 +23,10 @@ test('should Sign All Transactions ', async ({ page, phantom }) => { await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign All Transaction' }).click() + + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmTransaction() await expect(page.getByText('> success')).toBeVisible() @@ -30,6 +38,9 @@ test('should confirm contract deployment with default gas setting', async ({ pag await expect(page.locator('#tokenAddresses')).toBeEmpty() await page.locator('#createToken').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmTransaction() await expect(page.locator('#tokenAddresses')).toContainText('Creation Failed') @@ -41,6 +52,9 @@ test('should confirm contract deployment with default gas setting', async ({ pag await expect(page.locator('#tokenAddresses')).toBeEmpty() await page.locator('#createToken').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.confirmTransaction({ gasSetting }) await expect(page.locator('#tokenAddresses')).toContainText('Creation Failed') diff --git a/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts b/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts index cce4a8abc..8ee4f004b 100644 --- a/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts +++ b/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts @@ -14,6 +14,9 @@ test('should connect wallet to dapp', async ({ context, page, extensionId }) => await page.locator('#connectButton').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.connectToDapp() await expect(page.locator('#accounts')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') @@ -28,6 +31,9 @@ test('should connect multiple wallets to dapp', async ({ context, page, phantomP await page.goto('/') await page.locator('#connectButton').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.connectToDapp('NewAccount1') // Get address for account connected to testdapp diff --git a/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts b/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts index 93eb014f9..8df7a7b97 100644 --- a/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts +++ b/wallets/phantom/test/playwright/e2e/rejectSignature.spec.ts @@ -31,6 +31,9 @@ test('should reject `eth_signTypedData_v3`', async ({ page, phantom }) => { await page.locator('#signTypedDataV3').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.rejectSignature() await expect(page.locator('#signTypedDataV3Result')).toHaveText('Error: User rejected the request.') @@ -41,6 +44,9 @@ test('should reject `eth_signTypedData_v4`', async ({ page, phantom }) => { await page.locator('#signTypedDataV4').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.rejectSignature() await expect(page.locator('#signTypedDataV4Result')).toHaveText('Error: User rejected the request.') diff --git a/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts index 1a9356c49..b2cd98acf 100644 --- a/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts @@ -10,6 +10,10 @@ test('should Reject Transaction ', async ({ page, phantom }) => { await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign Transaction' }).click() + + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.rejectTransaction() await expect(page.getByText('User rejected the request.')).toBeVisible() @@ -19,6 +23,10 @@ test('should Reject All Transactions ', async ({ page, phantom }) => { await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign All Transaction' }).click() + + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.rejectTransaction() await expect(page.getByText('User rejected the request.')).toBeVisible() @@ -30,6 +38,9 @@ test('should reject contract deployment', async ({ page, phantom }) => { await expect(page.locator('#tokenAddresses')).toBeEmpty() await page.locator('#createToken').click() + // Delay toavoid random fails + await page.waitForTimeout(2_000) + await phantom.rejectTransaction() await expect(page.locator('#tokenAddresses')).toContainText('Creation Failed') From b8d2928f31e76899c9190a3c8e4ff4425828a3dd Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 20:15:13 +0100 Subject: [PATCH 15/36] Adding some delays to avoid random fails --- .../pages/HomePage/actions/importWalletFromPrivateKey.ts | 7 +------ .../test/playwright/commonSteps/solanaSandboxSetup.ts | 3 +++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index 9607cf788..e4a50b5a8 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -14,9 +14,7 @@ export async function importWalletFromPrivateKey( await page.goto(extensionUrl.replace('onboarding', 'popup')) - // =========== - - await page.waitForTimeout(2_000) + await page.waitForTimeout(3_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() @@ -26,9 +24,6 @@ export async function importWalletFromPrivateKey( await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton)).toBeVisible() - // =========== - - // await page.locator(Selectors.accountMenu.accountButton).click(); await page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton).click() diff --git a/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts b/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts index 7f8bb2d99..d960202cb 100644 --- a/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts +++ b/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts @@ -14,6 +14,9 @@ export const solanaSandboxSetup = async (page: Page, phantom: Phantom) => { await page.locator('a:has-text("Yes, proceed to preview")').click() await page.getByRole('button', { name: 'Connect to Phantom' }).click() + // Delay for avoiding random fails + await page.waitForTimeout(2_000) + await phantom.connectToDapp() await page.getByRole('button', { name: 'Clear Logs' }).click() From ae75b3f9d1ed35d0a9974a898766820e57dad407 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 20:37:40 +0100 Subject: [PATCH 16/36] Adding some delays to avoid random fails --- .../pages/HomePage/actions/importWalletFromPrivateKey.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index e4a50b5a8..acc53ea4b 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -14,13 +14,13 @@ export async function importWalletFromPrivateKey( await page.goto(extensionUrl.replace('onboarding', 'popup')) - await page.waitForTimeout(3_000) + await page.waitForTimeout(5_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() await page.locator(Selectors.accountMenu.accountButton).click() - await page.waitForTimeout(2_000) + await page.waitForTimeout(3_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton)).toBeVisible() From 5d4dadf0c69b10a356c9419fc3bb964738d366c8 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 20:50:45 +0100 Subject: [PATCH 17/36] Updating workflow --- .github/workflows/test.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 220e356a4..5dab69d12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,11 +73,20 @@ jobs: run: | xvfb-run pnpm run test:playwright:headful - - name: Archive Playwright report + - name: Archive Playwright report Phantom uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 if: success() || failure() with: - name: playwright-report-headful + name: playwright-report-headful-phantom + path: | + wallets/phantom/playwright-report-headful/ + if-no-files-found: error + + - name: Archive Playwright report Metamask + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 + if: success() || failure() + with: + name: playwright-report-headful-metamask path: | wallets/metamask/playwright-report-headful/ if-no-files-found: error From 19ee3657be8c10193d1fa6bb33425e9eaaaa7d62 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 21:08:35 +0100 Subject: [PATCH 18/36] Adding some delays to avoid random fails --- .../pages/HomePage/actions/closeSuiAndMonadScreen.ts | 2 ++ .../pages/HomePage/actions/importWalletFromPrivateKey.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts index ef07dadf8..bf47deeba 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts @@ -7,6 +7,8 @@ export async function closeSuiAndMonadIfPresent(page: Page) { await page.getByRole('button', { name: 'Not Now' }).click() } + await page.waitForTimeout(2_000) + const monadIsVisible = await page.getByRole('button', { name: 'Enable Monad' }).isVisible() if (monadIsVisible) { diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index acc53ea4b..c63e7fdae 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -14,13 +14,13 @@ export async function importWalletFromPrivateKey( await page.goto(extensionUrl.replace('onboarding', 'popup')) - await page.waitForTimeout(5_000) + await page.waitForTimeout(2_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() await page.locator(Selectors.accountMenu.accountButton).click() - await page.waitForTimeout(3_000) + await page.waitForTimeout(2_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton)).toBeVisible() From d6169edf27b6223b57bfeb7442801f76838b9852 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 21:21:09 +0100 Subject: [PATCH 19/36] Adding some delays to avoid random fails --- .../pages/HomePage/actions/importWalletFromPrivateKey.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index c63e7fdae..db1d3ff80 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -14,13 +14,13 @@ export async function importWalletFromPrivateKey( await page.goto(extensionUrl.replace('onboarding', 'popup')) - await page.waitForTimeout(2_000) + await page.waitForTimeout(3_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() await page.locator(Selectors.accountMenu.accountButton).click() - await page.waitForTimeout(2_000) + await page.waitForTimeout(3_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton)).toBeVisible() From 84c59c009c8eab09145954f2f6ef463448d51d87 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 21:37:49 +0100 Subject: [PATCH 20/36] Adding some delays to avoid random fails --- .../pages/HomePage/actions/importWalletFromPrivateKey.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index db1d3ff80..acc53ea4b 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -14,7 +14,7 @@ export async function importWalletFromPrivateKey( await page.goto(extensionUrl.replace('onboarding', 'popup')) - await page.waitForTimeout(3_000) + await page.waitForTimeout(5_000) await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() From 6de8284edb055c348bb8f2358d2d7bf2b77a8d92 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Sun, 23 Feb 2025 22:01:47 +0100 Subject: [PATCH 21/36] Skipping test --- wallets/phantom/test/playwright/e2e/resetApp.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wallets/phantom/test/playwright/e2e/resetApp.spec.ts b/wallets/phantom/test/playwright/e2e/resetApp.spec.ts index 1ae740e22..ba4d308eb 100644 --- a/wallets/phantom/test/playwright/e2e/resetApp.spec.ts +++ b/wallets/phantom/test/playwright/e2e/resetApp.spec.ts @@ -7,7 +7,8 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test -test('reset the app', async ({ context, phantomPage }) => { +// SKIPPING - Failing on CI becaue of After Hooks - No context +test.skip('reset the app', async ({ context, phantomPage }) => { test.setTimeout(40_000) const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) From 214a4fec5795a258f9ff435e0dee060af24c3b08 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Mon, 24 Feb 2025 12:32:51 +0100 Subject: [PATCH 22/36] ncreasing timeout for phantom notification page --- .../playwright/utils/getNotificationPageAndWaitForLoad.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts index 7fb17137c..a2517b404 100644 --- a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts +++ b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts @@ -11,7 +11,8 @@ export async function getNotificationPageAndWaitForLoad(context: BrowserContext, if (!notificationPage) { notificationPage = await context.waitForEvent('page', { - predicate: isNotificationPage + predicate: isNotificationPage, + timeout: 10_000 }) } @@ -23,5 +24,6 @@ export async function getNotificationPageAndWaitForLoad(context: BrowserContext, height: 592 }) - return await waitForPhantomLoad(notificationPage) + // return await waitForPhantomLoad(notificationPage); + return notificationPage } From 7a418c959bb6c9fb93742f209d8eb4aa0a5856bb Mon Sep 17 00:00:00 2001 From: juan-langa Date: Mon, 24 Feb 2025 12:46:37 +0100 Subject: [PATCH 23/36] Increasing timeout for phantom notification page --- .../src/playwright/utils/getNotificationPageAndWaitForLoad.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts index a2517b404..2b83f5206 100644 --- a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts +++ b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts @@ -1,5 +1,6 @@ import type { BrowserContext, Page } from '@playwright/test' -import { waitForPhantomLoad, waitUntilStable } from './waitFor' +// import { waitForPhantomLoad, waitUntilStable } from './waitFor' +import { waitUntilStable } from './waitFor' export async function getNotificationPageAndWaitForLoad(context: BrowserContext, extensionId: string) { const notificationPageUrl = `chrome-extension://${extensionId}/notification.html` From dc7856b003e16c22baaa3fceb0310ab6721e3b4d Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 17:09:32 +0100 Subject: [PATCH 24/36] Trigger Phantom CI e2e tests manually --- .github/workflows/testjj.yml | 57 ++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/testjj.yml diff --git a/.github/workflows/testjj.yml b/.github/workflows/testjj.yml new file mode 100644 index 000000000..27822a734 --- /dev/null +++ b/.github/workflows/testjj.yml @@ -0,0 +1,57 @@ +name: 🧑‍🔧 Test + +on: + workflow_dispatch + +jobs: + test-e2e-headful: + name: Run E2E tests (headful) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 + + - name: Setup Node & Install dependencies + uses: ./.github/actions/setup + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # pin@v1.3.1 + + - name: Install Playwright dependencies + run: pnpm dlx playwright@1.48.2 install-deps + + # For now, we only need Chromium. + - name: Install browsers for Playwright + run: pnpm dlx playwright@1.48.2 install chromium + + - name: Build project + run: pnpm run build + + - name: Install linux dependencies + run: | + sudo apt-get install --no-install-recommends -y \ + xvfb + + - name: Build project + run: pnpm run build + + - name: Serve MetaMask Test Dapp + run: | + pnpm run serve:test-dapp & + + - name: Build cache (phantom) + run: | + xvfb-run pnpm run build:cache:phantom + + - name: Run E2E tests (headful) + run: | + xvfb-run pnpm run test:playwright:headful + + - name: Archive Playwright report Phantom + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 + if: success() || failure() + with: + name: playwright-report-headful-phantom + path: | + wallets/phantom/playwright-report-headful/ + if-no-files-found: error + diff --git a/package.json b/package.json index e59d6ff67..985e658df 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "sort-package-json": "sort-package-json 'package.json' '{packages,wallets,examples}/*/package.json'", "sort-package-json:check": "sort-package-json 'package.json' '{packages,wallets,examples}/*/package.json' --check", "test": "turbo test", - "test:playwright:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock --filter=@synthetixio/synpress-phantom", + "test:playwright:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-phantom", "test:playwright:headless": "turbo test:playwright:headless --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock --filter=@synthetixio/synpress-phantom", "update:deps": "ncu -u -ws --root" }, From 89fc35aa5c2621be1ba7fe532b3049b0ccd4ae0e Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 17:14:23 +0100 Subject: [PATCH 25/36] Trigger Phantom CI e2e tests manually --- .github/workflows/testjj2.yml | 65 +++++++++++++++++++ .../test/playwright/e2e/addNewAccount.spec.ts | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/testjj2.yml diff --git a/.github/workflows/testjj2.yml b/.github/workflows/testjj2.yml new file mode 100644 index 000000000..147fea6b0 --- /dev/null +++ b/.github/workflows/testjj2.yml @@ -0,0 +1,65 @@ +name: 🧑‍🔧 Test + +on: + push: + branches: + - dev + pull_request: + paths: + - '.github/actions/**/*.yml' + - '.github/workflows/**/*.yml' + - 'packages/**' + - 'wallets/**' + +jobs: + test-e2e-headful: + name: Run E2E tests (headful) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 + + - name: Setup Node & Install dependencies + uses: ./.github/actions/setup + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # pin@v1.3.1 + + - name: Install Playwright dependencies + run: pnpm dlx playwright@1.48.2 install-deps + + # For now, we only need Chromium. + - name: Install browsers for Playwright + run: pnpm dlx playwright@1.48.2 install chromium + + - name: Build project + run: pnpm run build + + - name: Install linux dependencies + run: | + sudo apt-get install --no-install-recommends -y \ + xvfb + + - name: Build project + run: pnpm run build + + - name: Serve MetaMask Test Dapp + run: | + pnpm run serve:test-dapp & + + - name: Build cache (phantom) + run: | + xvfb-run pnpm run build:cache:phantom + + - name: Run E2E tests (headful) + run: | + xvfb-run pnpm run test:playwright:headful + + - name: Archive Playwright report Phantom + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 + if: success() || failure() + with: + name: playwright-report-headful-phantom + path: | + wallets/phantom/playwright-report-headful/ + if-no-files-found: error + diff --git a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts index 9dfc05948..1e46aeb04 100644 --- a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts @@ -7,7 +7,7 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test -test('should add a new account with specified name', async ({ context, phantomPage }) => { +test('should add a new account with specified name a - 1 ', async ({ context, phantomPage }) => { const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) const accountName = 'Test Account' From f61f0310f982a5d993dd4e98914261496a3257a1 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 17:17:26 +0100 Subject: [PATCH 26/36] Trigger Phantom CI e2e tests manually --- wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts index 1e46aeb04..f43c72632 100644 --- a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts @@ -7,7 +7,7 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test -test('should add a new account with specified name a - 1 ', async ({ context, phantomPage }) => { +test('should add a new account with specified name a - 10', async ({ context, phantomPage }) => { const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) const accountName = 'Test Account' From b4d20d1c3153f10b7a53552f41958dcbc3d610e9 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 18:29:01 +0100 Subject: [PATCH 27/36] Testing phantom tests 2 --- .github/workflows/codeql.yml | 50 ++--- .github/workflows/lint.yml | 38 ++-- .github/workflows/test.yml | 184 +++++++++--------- .github/workflows/testjj.yml | 57 ------ .../test/playwright/e2e/addNewAccount.spec.ts | 2 +- 5 files changed, 137 insertions(+), 194 deletions(-) delete mode 100644 .github/workflows/testjj.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bd144ab72..dfd9a662f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,30 +1,30 @@ -name: CodeQL +# name: CodeQL -on: - push: - branches: - - dev - pull_request: - schedule: - - cron: '0 6 * * 3' +# on: +# push: +# branches: +# - dev +# pull_request: +# schedule: +# - cron: '0 6 * * 3' -jobs: - analyze: - runs-on: ubuntu-latest - permissions: - security-events: write +# jobs: +# analyze: +# runs-on: ubuntu-latest +# permissions: +# security-events: write - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 +# steps: +# - name: Checkout +# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 - - name: Initialize CodeQL - uses: github/codeql-action/init@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 - with: - queries: security-and-quality - languages: javascript-typescript +# - name: Initialize CodeQL +# uses: github/codeql-action/init@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 +# with: +# queries: security-and-quality +# languages: javascript-typescript - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 - with: - category: "/language:javascript-typescript" +# - name: Perform CodeQL Analysis +# uses: github/codeql-action/analyze@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 +# with: +# category: "/language:javascript-typescript" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2a80275dc..cb9155906 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,24 +1,24 @@ -name: 🎨 Lint +# name: 🎨 Lint -on: - push: - branches: - - dev - pull_request: +# on: +# push: +# branches: +# - dev +# pull_request: -jobs: - style: - name: Check code style - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 +# jobs: +# style: +# name: Check code style +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 - - name: Setup Node & Install dependencies - uses: ./.github/actions/setup +# - name: Setup Node & Install dependencies +# uses: ./.github/actions/setup - - name: Check linter - run: pnpm run lint:check +# - name: Check linter +# run: pnpm run lint:check - - name: Check order in package.json - if: success() || failure() # Run even if the previous step fails - run: pnpm run sort-package-json:check +# - name: Check order in package.json +# if: success() || failure() # Run even if the previous step fails +# run: pnpm run sort-package-json:check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5dab69d12..f650c1c61 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,92 +1,92 @@ -name: 🧑‍🔧 Test - -on: - push: - branches: - - dev - pull_request: - paths: - - '.github/actions/**/*.yml' - - '.github/workflows/**/*.yml' - - 'packages/**' - - 'wallets/**' - -jobs: - test-unit: - name: Run unit tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 - - - name: Setup Node & Install dependencies - uses: ./.github/actions/setup - - - name: Build project - run: pnpm run build - - - name: Run tests - run: pnpm run test - - test-e2e-headful: - name: Run E2E tests (headful) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 - - - name: Setup Node & Install dependencies - uses: ./.github/actions/setup - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # pin@v1.3.1 - - - name: Install Playwright dependencies - run: pnpm dlx playwright@1.48.2 install-deps - - # For now, we only need Chromium. - - name: Install browsers for Playwright - run: pnpm dlx playwright@1.48.2 install chromium - - - name: Build project - run: pnpm run build - - - name: Install linux dependencies - run: | - sudo apt-get install --no-install-recommends -y \ - xvfb - - - name: Build project - run: pnpm run build - - - name: Serve MetaMask Test Dapp - run: | - pnpm run serve:test-dapp & - - - name: Build cache (metamask) - run: | - xvfb-run pnpm run build:cache:metamask - - - name: Build cache (phantom) - run: | - xvfb-run pnpm run build:cache:phantom - - - name: Run E2E tests (headful) - run: | - xvfb-run pnpm run test:playwright:headful - - - name: Archive Playwright report Phantom - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 - if: success() || failure() - with: - name: playwright-report-headful-phantom - path: | - wallets/phantom/playwright-report-headful/ - if-no-files-found: error - - - name: Archive Playwright report Metamask - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 - if: success() || failure() - with: - name: playwright-report-headful-metamask - path: | - wallets/metamask/playwright-report-headful/ - if-no-files-found: error +# name: 🧑‍🔧 Test + +# on: +# push: +# branches: +# - dev +# pull_request: +# paths: +# - '.github/actions/**/*.yml' +# - '.github/workflows/**/*.yml' +# - 'packages/**' +# - 'wallets/**' + +# jobs: +# test-unit: +# name: Run unit tests +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 + +# - name: Setup Node & Install dependencies +# uses: ./.github/actions/setup + +# - name: Build project +# run: pnpm run build + +# - name: Run tests +# run: pnpm run test + +# test-e2e-headful: +# name: Run E2E tests (headful) +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 + +# - name: Setup Node & Install dependencies +# uses: ./.github/actions/setup + +# - name: Install Foundry +# uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # pin@v1.3.1 + +# - name: Install Playwright dependencies +# run: pnpm dlx playwright@1.48.2 install-deps + +# # For now, we only need Chromium. +# - name: Install browsers for Playwright +# run: pnpm dlx playwright@1.48.2 install chromium + +# - name: Build project +# run: pnpm run build + +# - name: Install linux dependencies +# run: | +# sudo apt-get install --no-install-recommends -y \ +# xvfb + +# - name: Build project +# run: pnpm run build + +# - name: Serve MetaMask Test Dapp +# run: | +# pnpm run serve:test-dapp & + +# - name: Build cache (metamask) +# run: | +# xvfb-run pnpm run build:cache:metamask + +# - name: Build cache (phantom) +# run: | +# xvfb-run pnpm run build:cache:phantom + +# - name: Run E2E tests (headful) +# run: | +# xvfb-run pnpm run test:playwright:headful + +# - name: Archive Playwright report Phantom +# uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 +# if: success() || failure() +# with: +# name: playwright-report-headful-phantom +# path: | +# wallets/phantom/playwright-report-headful/ +# if-no-files-found: error + +# - name: Archive Playwright report Metamask +# uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 +# if: success() || failure() +# with: +# name: playwright-report-headful-metamask +# path: | +# wallets/metamask/playwright-report-headful/ +# if-no-files-found: error diff --git a/.github/workflows/testjj.yml b/.github/workflows/testjj.yml deleted file mode 100644 index 27822a734..000000000 --- a/.github/workflows/testjj.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: 🧑‍🔧 Test - -on: - workflow_dispatch - -jobs: - test-e2e-headful: - name: Run E2E tests (headful) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 - - - name: Setup Node & Install dependencies - uses: ./.github/actions/setup - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # pin@v1.3.1 - - - name: Install Playwright dependencies - run: pnpm dlx playwright@1.48.2 install-deps - - # For now, we only need Chromium. - - name: Install browsers for Playwright - run: pnpm dlx playwright@1.48.2 install chromium - - - name: Build project - run: pnpm run build - - - name: Install linux dependencies - run: | - sudo apt-get install --no-install-recommends -y \ - xvfb - - - name: Build project - run: pnpm run build - - - name: Serve MetaMask Test Dapp - run: | - pnpm run serve:test-dapp & - - - name: Build cache (phantom) - run: | - xvfb-run pnpm run build:cache:phantom - - - name: Run E2E tests (headful) - run: | - xvfb-run pnpm run test:playwright:headful - - - name: Archive Playwright report Phantom - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # pin@v4.6.0 - if: success() || failure() - with: - name: playwright-report-headful-phantom - path: | - wallets/phantom/playwright-report-headful/ - if-no-files-found: error - diff --git a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts index f43c72632..a84b99d9f 100644 --- a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts @@ -7,7 +7,7 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test -test('should add a new account with specified name a - 10', async ({ context, phantomPage }) => { +test('should add a new account with specified name a - 11', async ({ context, phantomPage }) => { const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) const accountName = 'Test Account' From 73aa3836c811cd70633e0f2cb60ca1759b15e3cb Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 19:56:47 +0100 Subject: [PATCH 28/36] Testing phantom tests 3 --- wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts index a84b99d9f..937a4c093 100644 --- a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts @@ -7,7 +7,7 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test -test('should add a new account with specified name a - 11', async ({ context, phantomPage }) => { +test('should add a new account with specified name a - 12', async ({ context, phantomPage }) => { const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) const accountName = 'Test Account' From d8f7d6edbcbdce2e251dc37b9f60be23fbd02d40 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 20:33:33 +0100 Subject: [PATCH 29/36] Testing phantom tests 4 --- .../src/playwright/utils/getNotificationPageAndWaitForLoad.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts index 2b83f5206..5221e1215 100644 --- a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts +++ b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts @@ -25,6 +25,8 @@ export async function getNotificationPageAndWaitForLoad(context: BrowserContext, height: 592 }) + await waitUntilStable(notificationPage as Page) + // return await waitForPhantomLoad(notificationPage); return notificationPage } From fcc4a6f6735d7eb3a536e3ede5c4b13e12226656 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 20:51:12 +0100 Subject: [PATCH 30/36] Increase test timeout --- wallets/phantom/test/playwright/e2e/renameAccount.spec.ts | 2 ++ wallets/phantom/test/playwright/e2e/resetApp.spec.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts b/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts index a63d3e46c..26526961f 100644 --- a/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts @@ -8,6 +8,8 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test test('should rename current account with specified name', async ({ context, phantomPage }) => { + test.setTimeout(80_000) + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) const accountName = 'Test Account' diff --git a/wallets/phantom/test/playwright/e2e/resetApp.spec.ts b/wallets/phantom/test/playwright/e2e/resetApp.spec.ts index ba4d308eb..5e2c10173 100644 --- a/wallets/phantom/test/playwright/e2e/resetApp.spec.ts +++ b/wallets/phantom/test/playwright/e2e/resetApp.spec.ts @@ -9,7 +9,7 @@ const { expect } = test // SKIPPING - Failing on CI becaue of After Hooks - No context test.skip('reset the app', async ({ context, phantomPage }) => { - test.setTimeout(40_000) + test.setTimeout(80_000) const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) From 2757cfd71f556bfd0ddf6d47a8ad7c88473234ee Mon Sep 17 00:00:00 2001 From: juan-langa Date: Tue, 25 Feb 2025 22:04:59 +0100 Subject: [PATCH 31/36] Sui and Monad --- .github/workflows/codeql.yml | 50 +++++++++---------- .github/workflows/lint.yml | 38 +++++++------- .github/workflows/testjj2.yml | 2 +- wallets/phantom/playwright.config.ts | 4 +- .../playwright/fixtures/phantomFixtures.ts | 3 ++ .../actions/closeSuiAndMonadScreen.ts | 31 ++++++++---- .../actions/importWalletFromPrivateKey.ts | 10 ++-- .../commonSteps/solanaSandboxSetup.ts | 2 +- 8 files changed, 78 insertions(+), 62 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dfd9a662f..bd144ab72 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,30 +1,30 @@ -# name: CodeQL +name: CodeQL -# on: -# push: -# branches: -# - dev -# pull_request: -# schedule: -# - cron: '0 6 * * 3' +on: + push: + branches: + - dev + pull_request: + schedule: + - cron: '0 6 * * 3' -# jobs: -# analyze: -# runs-on: ubuntu-latest -# permissions: -# security-events: write +jobs: + analyze: + runs-on: ubuntu-latest + permissions: + security-events: write -# steps: -# - name: Checkout -# uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 -# - name: Initialize CodeQL -# uses: github/codeql-action/init@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 -# with: -# queries: security-and-quality -# languages: javascript-typescript + - name: Initialize CodeQL + uses: github/codeql-action/init@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 + with: + queries: security-and-quality + languages: javascript-typescript -# - name: Perform CodeQL Analysis -# uses: github/codeql-action/analyze@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 -# with: -# category: "/language:javascript-typescript" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@7e3036b9cd87fc26dd06747b7aa4b96c27aaef3a # pin@v2.20.3 + with: + category: "/language:javascript-typescript" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cb9155906..2a80275dc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,24 +1,24 @@ -# name: 🎨 Lint +name: 🎨 Lint -# on: -# push: -# branches: -# - dev -# pull_request: +on: + push: + branches: + - dev + pull_request: -# jobs: -# style: -# name: Check code style -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 +jobs: + style: + name: Check code style + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2 -# - name: Setup Node & Install dependencies -# uses: ./.github/actions/setup + - name: Setup Node & Install dependencies + uses: ./.github/actions/setup -# - name: Check linter -# run: pnpm run lint:check + - name: Check linter + run: pnpm run lint:check -# - name: Check order in package.json -# if: success() || failure() # Run even if the previous step fails -# run: pnpm run sort-package-json:check + - name: Check order in package.json + if: success() || failure() # Run even if the previous step fails + run: pnpm run sort-package-json:check diff --git a/.github/workflows/testjj2.yml b/.github/workflows/testjj2.yml index 147fea6b0..b3ae449e7 100644 --- a/.github/workflows/testjj2.yml +++ b/.github/workflows/testjj2.yml @@ -1,4 +1,4 @@ -name: 🧑‍🔧 Test +name: 🧑‍🔧 Test - Phantom on: push: diff --git a/wallets/phantom/playwright.config.ts b/wallets/phantom/playwright.config.ts index 1f2f04328..ff9a9018d 100644 --- a/wallets/phantom/playwright.config.ts +++ b/wallets/phantom/playwright.config.ts @@ -45,7 +45,9 @@ export default defineConfig({ // Collect all traces on CI, and only traces for failed tests when running locally. // See https://playwright.dev/docs/trace-viewer. - trace: process.env.CI ? 'on' : 'retain-on-failure', + // trace: process.env.CI ? 'on' : 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure', // Added for getting account address permissions: ['clipboard-read'] }, diff --git a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts index 5f19c6c74..8463c4be8 100644 --- a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts +++ b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts @@ -12,6 +12,7 @@ import { prepareExtensionPhantom } from '../../prepareExtensionPhantom' import { Phantom } from '../Phantom' import { getExtensionIdPhantom, unlockForFixturePhantom } from '../fixture-actions' import { persistLocalStorage } from '../fixture-actions/persistLocalStorage' +import { closeSuiAndMonadIfPresent } from '../pages/HomePage/actions' import { waitForPhantomWindowToBeStable } from '../utils/waitFor' type PhantomFixtures = { @@ -90,6 +91,8 @@ export const phantomFixtures = (walletSetup: ReturnType { + await closeSuiAndMonadIfPresent(_phantomPage) + await use(_phantomPage) }, extensionId: async ({ context }, use) => { diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts index bf47deeba..1e1f63620 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/closeSuiAndMonadScreen.ts @@ -1,17 +1,28 @@ -import type { Page } from '@playwright/test' +import { type Page, expect } from '@playwright/test' export async function closeSuiAndMonadIfPresent(page: Page) { - const suiIsVisible = await page.getByRole('button', { name: 'Enable Sui' }).isVisible() + // Wait for Phantom page to fully load + const walletValueUsdRegExp = new RegExp('\\$[0-9].[0-9]{3,4}.*[0-9]{1,2}\\%') + await expect(page.getByText(walletValueUsdRegExp), 'Wallet value should be visible').toBeVisible({ timeout: 10_000 }) - if (suiIsVisible) { - await page.getByRole('button', { name: 'Not Now' }).click() - } + // Reload page to trigger Sui and/or Monad screens + await page.reload() - await page.waitForTimeout(2_000) + // Loop until Sui/Monad screens have been closed and Phantompage is ready for testing + // => 'ready for testing' = top 'fungible token' row is clickable + await expect(async () => { + const suiIsVisible = await page.getByRole('button', { name: 'Enable Sui' }).isVisible() - const monadIsVisible = await page.getByRole('button', { name: 'Enable Monad' }).isVisible() + if (suiIsVisible) { + await page.getByRole('button', { name: 'Not Now' }).click() + } - if (monadIsVisible) { - await page.getByRole('button', { name: 'Not Now' }).click() - } + const monadIsVisible = await page.getByRole('button', { name: 'Enable Monad' }).isVisible() + + if (monadIsVisible) { + await page.getByRole('button', { name: 'Not Now' }).click() + } + + await page.locator('[data-testid*="fungible-token-row-"]').first().click({ timeout: 3_000 }) + }).toPass() } diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index acc53ea4b..2bd61a931 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -2,7 +2,7 @@ import { type Page, expect } from '@playwright/test' import Selectors from '../../../../selectors/pages/HomePage' import type { Networks } from '../../../../type/Networks' import { waitFor } from '../../../utils/waitFor' -import { closeSuiAndMonadIfPresent } from './closeSuiAndMonadScreen' +// import { closeSuiAndMonadIfPresent } from './closeSuiAndMonadScreen' export async function importWalletFromPrivateKey( page: Page, @@ -14,14 +14,14 @@ export async function importWalletFromPrivateKey( await page.goto(extensionUrl.replace('onboarding', 'popup')) - await page.waitForTimeout(5_000) - await closeSuiAndMonadIfPresent(page) + // await page.waitForTimeout(5_000) + // await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() await page.locator(Selectors.accountMenu.accountButton).click() - await page.waitForTimeout(3_000) - await closeSuiAndMonadIfPresent(page) + // await page.waitForTimeout(3_000) + // await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton)).toBeVisible() diff --git a/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts b/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts index d960202cb..d3de95472 100644 --- a/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts +++ b/wallets/phantom/test/playwright/commonSteps/solanaSandboxSetup.ts @@ -2,7 +2,7 @@ import { type Page, expect } from '@playwright/test' import type { Phantom } from '../../../src/playwright' export const solanaSandboxSetup = async (page: Page, phantom: Phantom) => { - await phantom.page.waitForTimeout(1_000) + // await phantom.page.waitForTimeout(1_000) await phantom.importWalletFromPrivateKey( 'solana', 'XQaKFLLSKbzpVzmfJrj4yUjAyFy2Eu7JcNdbPdnLuod2Uw3yf3tjGd4ha1DBfFdjkZFX1PZg3knth2Tz2tvd8C4' From 9a292643dc302986470502167ac2a77e82960afc Mon Sep 17 00:00:00 2001 From: juan-langa Date: Wed, 26 Feb 2025 14:47:12 +0100 Subject: [PATCH 32/36] Sui and Monad --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a9864ed9..8932e1650 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -393,7 +393,7 @@ importers: version: 5.0.5 tsup: specifier: 8.0.2 - version: 8.0.2(postcss@8.4.41)(typescript@5.3.3) + version: 8.0.2(postcss@8.5.3)(typescript@5.3.3) typescript: specifier: 5.3.3 version: 5.3.3 From 2975510de73361a02740d22f9edcbf433a28c163 Mon Sep 17 00:00:00 2001 From: juan-langa Date: Thu, 27 Feb 2025 23:18:08 +0100 Subject: [PATCH 33/36] Improvements --- .../src/playwright/fixture-actions/index.ts | 1 - .../fixture-actions/unlockForFixture.ts | 34 --------- .../playwright/fixtures/phantomFixtures.ts | 18 +++-- .../actions/closeSuiAndMonadScreen.ts | 10 ++- .../actions/importWalletFromPrivateKey.ts | 14 ++-- .../pages/UnlockPage/actions/unlock.ts | 2 +- .../getNotificationPageAndWaitForLoad.ts | 8 +-- .../phantom/src/playwright/utils/waitFor.ts | 69 ++++--------------- .../playwright/utils/waitForPopupPageLoad.ts | 20 ++++++ .../playwright/utils/waitForTestPageLoad.ts | 20 ++++++ .../src/selectors/pages/HomePage/index.ts | 5 +- .../test/playwright/e2e/addNewAccount.spec.ts | 2 +- .../playwright/e2e/confirmTransaction.spec.ts | 4 +- .../test/playwright/e2e/renameAccount.spec.ts | 10 ++- .../test/playwright/e2e/switchAccount.spec.ts | 12 ++-- .../test/playwright/e2e/unlock.spec.ts | 2 +- 16 files changed, 104 insertions(+), 127 deletions(-) delete mode 100644 wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts create mode 100644 wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts create mode 100644 wallets/phantom/src/playwright/utils/waitForTestPageLoad.ts diff --git a/wallets/phantom/src/playwright/fixture-actions/index.ts b/wallets/phantom/src/playwright/fixture-actions/index.ts index b9c4f13e6..6d7fc97d1 100644 --- a/wallets/phantom/src/playwright/fixture-actions/index.ts +++ b/wallets/phantom/src/playwright/fixture-actions/index.ts @@ -1,3 +1,2 @@ -export * from './unlockForFixture' export * from './getExtensionId' export * from './prepareExtensionPhantom' diff --git a/wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts b/wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts deleted file mode 100644 index 54b760dd0..000000000 --- a/wallets/phantom/src/playwright/fixture-actions/unlockForFixture.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Page } from '@playwright/test' -import { errors as playwrightErrors } from '@playwright/test' -import { Phantom } from '..' -import { waitForSpinnerToVanish } from '../utils/waitForSpinnerToVanish' - -/** - * A more advanced version of the `Phantom.unlock()` function that incorporates various workarounds for Phantom issues, among other things. - * This function should be used instead of the `Phantom.unlock()` when passing it to the `testWithSynpress` function. - * - * @param page - The Phantom tab page. - * @param password - The password of the Phantom wallet. - */ -export async function unlockForFixturePhantom(page: Page, password: string) { - const phantom = new Phantom(page.context(), page, password) - - await unlockWalletButReloadIfSpinnerDoesNotVanish(phantom) -} - -async function unlockWalletButReloadIfSpinnerDoesNotVanish(phantom: Phantom) { - try { - await phantom.unlock() - } catch (e) { - if (e instanceof playwrightErrors.TimeoutError) { - console.warn('[UnlockWalletButReloadIfSpinnerDoesNotVanish] Unlocking Phantom timed out. Reloading page...') - - const page = phantom.page - - await page.reload() - await waitForSpinnerToVanish(page) - } else { - throw e - } - } -} diff --git a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts index 8463c4be8..4cb9d2f5d 100644 --- a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts +++ b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts @@ -10,10 +10,12 @@ import { import fs from 'fs-extra' import { prepareExtensionPhantom } from '../../prepareExtensionPhantom' import { Phantom } from '../Phantom' -import { getExtensionIdPhantom, unlockForFixturePhantom } from '../fixture-actions' +import { getExtensionIdPhantom } from '../fixture-actions' import { persistLocalStorage } from '../fixture-actions/persistLocalStorage' import { closeSuiAndMonadIfPresent } from '../pages/HomePage/actions' -import { waitForPhantomWindowToBeStable } from '../utils/waitFor' +import { unlock } from '../pages/UnlockPage/actions' +import { waitForPopupPageLoad } from '../utils/waitForPopupPageLoad' +import { waitForTestPageLoad } from '../utils/waitForTestPageLoad' type PhantomFixtures = { _contextPath: string @@ -78,13 +80,13 @@ export const phantomFixtures = (walletSetup: ReturnType { const extensionId = await getExtensionIdPhantom(context, 'Phantom') @@ -109,6 +113,8 @@ export const phantomFixtures = (walletSetup: ReturnType 'ready for testing' = top 'fungible token' row is clickable @@ -15,6 +19,8 @@ export async function closeSuiAndMonadIfPresent(page: Page) { if (suiIsVisible) { await page.getByRole('button', { name: 'Not Now' }).click() + // Wait for Nomad page to load (if it does) + await page.waitForTimeout(1_000) } const monadIsVisible = await page.getByRole('button', { name: 'Enable Monad' }).isVisible() diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts index 2bd61a931..d93b67045 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/importWalletFromPrivateKey.ts @@ -2,7 +2,7 @@ import { type Page, expect } from '@playwright/test' import Selectors from '../../../../selectors/pages/HomePage' import type { Networks } from '../../../../type/Networks' import { waitFor } from '../../../utils/waitFor' -// import { closeSuiAndMonadIfPresent } from './closeSuiAndMonadScreen' +import { closeSuiAndMonadIfPresent } from './closeSuiAndMonadScreen' export async function importWalletFromPrivateKey( page: Page, @@ -10,19 +10,11 @@ export async function importWalletFromPrivateKey( privateKey: string, walletName?: string ) { - const extensionUrl = page.url() - - await page.goto(extensionUrl.replace('onboarding', 'popup')) - - // await page.waitForTimeout(5_000) - // await closeSuiAndMonadIfPresent(page) + await closeSuiAndMonadIfPresent(page) await expect(page.locator(Selectors.accountMenu.accountButton)).toBeVisible() await page.locator(Selectors.accountMenu.accountButton).click() - // await page.waitForTimeout(3_000) - // await closeSuiAndMonadIfPresent(page) - await expect(page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton)).toBeVisible() await page.locator(Selectors.accountMenu.addAccountMenu.addAccountButton).click() @@ -55,4 +47,6 @@ export async function importWalletFromPrivateKey( } await importButton.click() + + await expect(page.locator('[data-testid*="fungible-token-row-"]').first()).toBeVisible({ timeout: 10_000 }) } diff --git a/wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts b/wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts index 7130b3fbe..3c8c3edb7 100644 --- a/wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts +++ b/wallets/phantom/src/playwright/pages/UnlockPage/actions/unlock.ts @@ -1,4 +1,4 @@ -import type { Page } from '@playwright/test' +import { type Page } from '@playwright/test' import Selectors from '../../../../selectors/pages/UnlockPage' import { waitForSpinnerToVanish } from '../../../utils/waitForSpinnerToVanish' diff --git a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts index 5221e1215..7f6073fd2 100644 --- a/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts +++ b/wallets/phantom/src/playwright/utils/getNotificationPageAndWaitForLoad.ts @@ -1,6 +1,5 @@ import type { BrowserContext, Page } from '@playwright/test' -// import { waitForPhantomLoad, waitUntilStable } from './waitFor' -import { waitUntilStable } from './waitFor' +import { waitUntilStableNotificationPage } from './waitFor' export async function getNotificationPageAndWaitForLoad(context: BrowserContext, extensionId: string) { const notificationPageUrl = `chrome-extension://${extensionId}/notification.html` @@ -17,7 +16,7 @@ export async function getNotificationPageAndWaitForLoad(context: BrowserContext, }) } - await waitUntilStable(notificationPage as Page) + await waitUntilStableNotificationPage(notificationPage as Page) // Set pop-up window viewport size to resemble the actual Phantom pop-up window. await notificationPage.setViewportSize({ @@ -25,8 +24,7 @@ export async function getNotificationPageAndWaitForLoad(context: BrowserContext, height: 592 }) - await waitUntilStable(notificationPage as Page) + await waitUntilStableNotificationPage(notificationPage as Page) - // return await waitForPhantomLoad(notificationPage); return notificationPage } diff --git a/wallets/phantom/src/playwright/utils/waitFor.ts b/wallets/phantom/src/playwright/utils/waitFor.ts index 6b6a88c32..657c05faf 100644 --- a/wallets/phantom/src/playwright/utils/waitFor.ts +++ b/wallets/phantom/src/playwright/utils/waitFor.ts @@ -2,6 +2,7 @@ import type { Page } from '@playwright/test' import { errors } from '@playwright/test' import { LoadingSelectors } from '../../selectors' import { ErrorSelectors } from '../../selectors' +import Selectors from '../../selectors/pages/UnlockPage' const DEFAULT_TIMEOUT = 2000 @@ -26,7 +27,19 @@ export const waitToBeHidden = async (selector: string, page: Page) => { export const waitUntilStable = async (page: Page) => { await page.waitForLoadState('load', { timeout: 10_000 }) await page.waitForLoadState('domcontentloaded', { timeout: 10_000 }) - // await page.waitForLoadState('networkidle', { timeout: 10_000 }) +} + +export const waitUntilStableBeforeUnlock = async (page: Page) => { + await page.waitForLoadState('load', { timeout: 10_000 }) + await page.waitForLoadState('domcontentloaded', { timeout: 10_000 }) + await page.waitForSelector(Selectors.submitButton, { timeout: 10_000 }) + await page.locator(Selectors.submitButton).waitFor({ timeout: 10_000 }) +} + +export const waitUntilStableNotificationPage = async (page: Page) => { + await page.waitForLoadState('load', { timeout: 10_000 }) + await page.waitForLoadState('domcontentloaded', { timeout: 10_000 }) + await page.locator('[data-testid="home-header-account-name"]').waitFor({ timeout: 10_000 }) } export const waitForSelector = async (selector: string, page: Page, timeout: number) => { @@ -44,60 +57,6 @@ export const waitForSelector = async (selector: string, page: Page, timeout: num } } -export const waitForPhantomLoad = async (page: Page) => { - // await Promise.all( - // LoadingSelectors.loadingIndicators.map(async (selector) => { - // await waitForSelector(selector, page, DEFAULT_TIMEOUT) - // }) - // ) - // .then(() => { - // return true - // }) - // .catch((error) => { - // console.error('Error: ', error) - // }) - - return page -} - -export const waitForPhantomWindowToBeStable = async (page: Page) => { - await waitForPhantomLoad(page) - if ((await page.locator(ErrorSelectors.loadingOverlayErrorButtons).count()) > 0) { - const retryButton = await page.locator(ErrorSelectors.loadingOverlayErrorButtonsRetryButton) - await retryButton.click() - await waitForSelector(LoadingSelectors.loadingOverlay, page, DEFAULT_TIMEOUT) - } - await fixCriticalError(page) -} - -export const fixCriticalError = async (page: Page) => { - for (let times = 0; times < 5; times++) { - if ((await page.locator(ErrorSelectors.criticalError).count()) > 0) { - console.log('[fixCriticalError] Phantom crashed with critical error, refreshing..') - if (times <= 3) { - await page.reload() - await waitForPhantomWindowToBeStable(page) - } else if (times === 4) { - const restartButton = await page.locator(ErrorSelectors.criticalErrorRestartButton) - await restartButton.click() - await waitForPhantomWindowToBeStable(page) - } else { - throw new Error('[fixCriticalError] Max amount of retries to fix critical phantom error has been reached.') - } - } else if ((await page.locator(ErrorSelectors.errorPage).count()) > 0) { - console.log('[fixCriticalError] Phantom crashed with error, refreshing..') - if (times <= 4) { - await page.reload() - await waitForPhantomWindowToBeStable(page) - } else { - throw new Error('[fixCriticalError] Max amount of retries to fix critical phantom error has been reached.') - } - } else { - break - } - } -} - // Inlining the sleep function here cause this is one of the few places in the entire codebase where sleep should be used! const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) diff --git a/wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts b/wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts new file mode 100644 index 000000000..a99c7b2ad --- /dev/null +++ b/wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts @@ -0,0 +1,20 @@ +import type { BrowserContext, Page } from '@playwright/test' +import { waitUntilStableBeforeUnlock } from './waitFor' + +export async function waitForPopupPageLoad(context: BrowserContext, extensionId: string) { + const popupPageUrl = `chrome-extension://${extensionId}/popup.html` + + const isPopupPage = (page: Page) => page.url().includes(popupPageUrl) + + // Check if popup page is already open. + let popupPage = context.pages().find(isPopupPage) + + if (!popupPage) { + popupPage = await context.waitForEvent('page', { + predicate: isPopupPage, + timeout: 10_000 + }) + } + + await waitUntilStableBeforeUnlock(popupPage as Page) +} diff --git a/wallets/phantom/src/playwright/utils/waitForTestPageLoad.ts b/wallets/phantom/src/playwright/utils/waitForTestPageLoad.ts new file mode 100644 index 000000000..0cfbc7158 --- /dev/null +++ b/wallets/phantom/src/playwright/utils/waitForTestPageLoad.ts @@ -0,0 +1,20 @@ +import type { BrowserContext, Page } from '@playwright/test' +import { waitUntilStable } from './waitFor' + +export async function waitForTestPageLoad(context: BrowserContext) { + const testPageUrl = 'about:blank' + + const isTestPage = (page: Page) => page.url().includes(testPageUrl) + + // Check if test page is already open. + let testPage = context.pages().find(isTestPage) + + if (!testPage) { + testPage = await context.waitForEvent('page', { + predicate: isTestPage, + timeout: 10_000 + }) + } + + await waitUntilStable(testPage as Page) +} diff --git a/wallets/phantom/src/selectors/pages/HomePage/index.ts b/wallets/phantom/src/selectors/pages/HomePage/index.ts index f8782ff9a..77ec0027e 100644 --- a/wallets/phantom/src/selectors/pages/HomePage/index.ts +++ b/wallets/phantom/src/selectors/pages/HomePage/index.ts @@ -11,11 +11,12 @@ const renameAccountMenu = { } const importAccountMenu = { - networkOpenMenu: '#button--listbox-input--1', + networkOpenMenu: '[role="button"]:has-text("Solana")', // '#button--listbox-input--1', ethereumNetwork: `[data-label="Ethereum"]`, baseNetwork: `[data-label="Base"]`, polygonNetwork: `[data-label="Polygon"]`, bitcoinNetwork: `[data-label="Bitcoin"]`, + solanaNetwork: `[data-label="Solana"]`, nameInput: `input[name="name"]`, privateKeyInput: `textarea[placeholder="Private key"]`, importButton: `button:has-text("Import")`, @@ -55,7 +56,7 @@ export default { bitcoinWalletAddress: createDataTestSelector('account-header-chain-bip122:000000000019d6689c085ae165831e93'), copyAccountAddressButton: createDataTestSelector('address-copy-button-text'), currentNetwork: `${createDataTestSelector('network-display')} span:nth-of-type(1)`, - headerBackButton: createDataTestSelector('header--back'), + headerBackButton: `section:has-text("Developer Settings") ${createDataTestSelector('header--back')}`, settings, accountMenu, editAccountMenu, diff --git a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts index 937a4c093..9dfc05948 100644 --- a/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/addNewAccount.spec.ts @@ -7,7 +7,7 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test -test('should add a new account with specified name a - 12', async ({ context, phantomPage }) => { +test('should add a new account with specified name', async ({ context, phantomPage }) => { const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) const accountName = 'Test Account' diff --git a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts index 6afc2015d..6c5a29c2d 100644 --- a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts @@ -6,7 +6,7 @@ const test = synpress const { expect } = test -test('should Sign Transaction ', async ({ page, phantom }) => { +test('should Sign Transaction', async ({ page, phantom }) => { await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign Transaction' }).click() @@ -19,7 +19,7 @@ test('should Sign Transaction ', async ({ page, phantom }) => { await expect(page.getByText('> success')).toBeVisible() }) -test('should Sign All Transactions ', async ({ page, phantom }) => { +test('should Sign All Transactions', async ({ page, phantom }) => { await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign All Transaction' }).click() diff --git a/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts b/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts index 26526961f..c8bb33f8e 100644 --- a/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/renameAccount.spec.ts @@ -8,12 +8,18 @@ const test = testWithSynpress(phantomFixtures(basicSetup)) const { expect } = test test('should rename current account with specified name', async ({ context, phantomPage }) => { - test.setTimeout(80_000) + test.setTimeout(100_000) const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword) + await phantom.importWalletFromPrivateKey( + 'ethereum', + 'ea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a', + 'Imp1' + ) + const accountName = 'Test Account' - await phantom.renameAccount('Account 1', accountName) + await phantom.renameAccount('Imp1', accountName) await phantomPage.reload() diff --git a/wallets/phantom/test/playwright/e2e/switchAccount.spec.ts b/wallets/phantom/test/playwright/e2e/switchAccount.spec.ts index 0df13d1b9..15cfffedd 100644 --- a/wallets/phantom/test/playwright/e2e/switchAccount.spec.ts +++ b/wallets/phantom/test/playwright/e2e/switchAccount.spec.ts @@ -12,20 +12,22 @@ test('should switch account', async ({ context, phantomPage }) => { await phantom.importWalletFromPrivateKey( 'ethereum', - 'ea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a' + 'ea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a', + 'Imp1' ) await phantom.importWalletFromPrivateKey( 'ethereum', - '7dd4aab86170c0edbdcf97600eff0ae319fdc94149c5e8c33d5439f8417a40bf' + '7dd4aab86170c0edbdcf97600eff0ae319fdc94149c5e8c33d5439f8417a40bf', + 'Imp2' ) - await phantom.switchAccount('Account 1') + await phantom.switchAccount('Imp1') - await expect(phantomPage.getByTestId('home-header-account-name')).toContainText('Account 1') + await expect(phantomPage.getByTestId('home-header-account-name')).toContainText('Imp1') await phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName).hover() - await expect(phantomPage.locator(phantom.homePage.selectors.ethereumWalletAddress)).toContainText('0xf39F...2266') + await expect(phantomPage.locator(phantom.homePage.selectors.ethereumWalletAddress)).toContainText('0xa2ce...6801') }) test('should throw an error if there is no account with target name', async ({ context, phantomPage }) => { diff --git a/wallets/phantom/test/playwright/e2e/unlock.spec.ts b/wallets/phantom/test/playwright/e2e/unlock.spec.ts index ebafc283d..76b90f613 100644 --- a/wallets/phantom/test/playwright/e2e/unlock.spec.ts +++ b/wallets/phantom/test/playwright/e2e/unlock.spec.ts @@ -14,5 +14,5 @@ test('should unlock the wallet', async ({ context, phantomPage }) => { await phantom.unlock() - await expect(phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName)).toHaveText('Account 1') + await expect(phantomPage.locator(phantom.homePage.selectors.accountMenu.accountName)).toContainText('Account') }) From 3c46af98d251d2e23a8f6cddd92fdd641ab97fed Mon Sep 17 00:00:00 2001 From: juan-langa Date: Thu, 27 Feb 2025 23:19:40 +0100 Subject: [PATCH 34/36] Improvements --- wallets/phantom/src/playwright/utils/waitFor.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wallets/phantom/src/playwright/utils/waitFor.ts b/wallets/phantom/src/playwright/utils/waitFor.ts index 657c05faf..6dd96dafc 100644 --- a/wallets/phantom/src/playwright/utils/waitFor.ts +++ b/wallets/phantom/src/playwright/utils/waitFor.ts @@ -1,10 +1,7 @@ import type { Page } from '@playwright/test' import { errors } from '@playwright/test' -import { LoadingSelectors } from '../../selectors' -import { ErrorSelectors } from '../../selectors' -import Selectors from '../../selectors/pages/UnlockPage' -const DEFAULT_TIMEOUT = 2000 +import Selectors from '../../selectors/pages/UnlockPage' let retries = 0 From ccfa5e53bde9dff9d600fb5b1c5ff570ae18a91f Mon Sep 17 00:00:00 2001 From: juan-langa Date: Fri, 28 Feb 2025 16:01:25 +0100 Subject: [PATCH 35/36] Testing tests --- .../playwright/fixtures/phantomFixtures.ts | 16 +++-------- .../phantom/src/playwright/utils/waitFor.ts | 8 +++--- .../playwright/utils/waitForPopupPageLoad.ts | 27 ++++++++++++++++++- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts index 4cb9d2f5d..322fa68c2 100644 --- a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts +++ b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts @@ -14,8 +14,8 @@ import { getExtensionIdPhantom } from '../fixture-actions' import { persistLocalStorage } from '../fixture-actions/persistLocalStorage' import { closeSuiAndMonadIfPresent } from '../pages/HomePage/actions' import { unlock } from '../pages/UnlockPage/actions' -import { waitForPopupPageLoad } from '../utils/waitForPopupPageLoad' -import { waitForTestPageLoad } from '../utils/waitForTestPageLoad' +import { loadAndWaitForPopupPage } from '../utils/waitForPopupPageLoad' +// import { waitForTestPageLoad } from "../utils/waitForTestPageLoad"; type PhantomFixtures = { _contextPath: string @@ -78,13 +78,7 @@ export const phantomFixtures = (walletSetup: ReturnType { const extensionId = await getExtensionIdPhantom(context, 'Phantom') @@ -113,8 +105,6 @@ export const phantomFixtures = (walletSetup: ReturnType { export const waitUntilStableBeforeUnlock = async (page: Page) => { await page.waitForLoadState('load', { timeout: 10_000 }) await page.waitForLoadState('domcontentloaded', { timeout: 10_000 }) - await page.waitForSelector(Selectors.submitButton, { timeout: 10_000 }) - await page.locator(Selectors.submitButton).waitFor({ timeout: 10_000 }) + + await expect(async () => { + await expect(page.locator(Selectors.submitButton), '"Unlock" buttonshouldbe visible').toBeVisible() + }).toPass({ timeout: 10_000 }) } export const waitUntilStableNotificationPage = async (page: Page) => { diff --git a/wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts b/wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts index a99c7b2ad..c11718cc9 100644 --- a/wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts +++ b/wallets/phantom/src/playwright/utils/waitForPopupPageLoad.ts @@ -1,5 +1,6 @@ -import type { BrowserContext, Page } from '@playwright/test' +import { type BrowserContext, type Page, expect } from '@playwright/test' import { waitUntilStableBeforeUnlock } from './waitFor' +import { waitForTestPageLoad } from './waitForTestPageLoad' export async function waitForPopupPageLoad(context: BrowserContext, extensionId: string) { const popupPageUrl = `chrome-extension://${extensionId}/popup.html` @@ -18,3 +19,27 @@ export async function waitForPopupPageLoad(context: BrowserContext, extensionId: await waitUntilStableBeforeUnlock(popupPage as Page) } + +export async function loadAndWaitForPopupPage2(page: Page, extensionId: string) { + await expect(async () => { + await page.goto(`chrome-extension://${extensionId}/popup.html`) + + await waitUntilStableBeforeUnlock(page) + }).toPass({ timeout: 35_000 }) +} + +export async function loadAndWaitForPopupPage(context: BrowserContext, extensionId: string) { + let popupPage: Page = context.pages()[0] as Page + + await expect(async () => { + popupPage = await context.newPage() + + await waitForTestPageLoad(context) + + await popupPage.goto(`chrome-extension://${extensionId}/popup.html`) + + await waitUntilStableBeforeUnlock(popupPage) + }).toPass() + + return popupPage +} From 31b965d6cb7902f604dbb4f67c1af047e8e150ac Mon Sep 17 00:00:00 2001 From: juan-langa Date: Fri, 28 Feb 2025 16:10:19 +0100 Subject: [PATCH 36/36] Extend timeout --- pnpm-lock.yaml | 5 +++++ wallets/phantom/src/playwright/fixtures/phantomFixtures.ts | 1 - .../playwright/pages/HomePage/actions/getAccountAddress.ts | 5 ++++- .../phantom/test/playwright/e2e/confirmTransaction.spec.ts | 6 +++++- wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts | 2 ++ .../phantom/test/playwright/e2e/rejectTransaction.spec.ts | 6 ++++++ 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c211f784..ac2b3a160 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1552,6 +1552,9 @@ packages: peerDependencies: '@playwright/test': '*' + '@synthetixio/synpress-tsconfig@0.0.4': + resolution: {integrity: sha512-hbCj7Tr3fFt1oJ1ceu8aQeUgG8I8SHOUxM6V2sHHSK9+2caEKxg1ehqVBObY7Tt2X4WyZerJa92WPVpX/TKSRA==} + '@synthetixio/synpress-tsconfig@0.0.9': resolution: {integrity: sha512-wLwdoHVg2lPAMcm4m36BnJyaxEXZmIfZEMLHAE0eWN16ObqidxzfwFjAVK26GqhVlngjhKTUSUWt3XB9GFNjTQ==} @@ -6252,6 +6255,8 @@ snapshots: dependencies: '@playwright/test': 1.48.2 + '@synthetixio/synpress-tsconfig@0.0.4': {} + '@synthetixio/synpress-tsconfig@0.0.9': {} '@szmarczak/http-timer@5.0.1': diff --git a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts index 322fa68c2..062a47aa3 100644 --- a/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts +++ b/wallets/phantom/src/playwright/fixtures/phantomFixtures.ts @@ -15,7 +15,6 @@ import { persistLocalStorage } from '../fixture-actions/persistLocalStorage' import { closeSuiAndMonadIfPresent } from '../pages/HomePage/actions' import { unlock } from '../pages/UnlockPage/actions' import { loadAndWaitForPopupPage } from '../utils/waitForPopupPageLoad' -// import { waitForTestPageLoad } from "../utils/waitForTestPageLoad"; type PhantomFixtures = { _contextPath: string diff --git a/wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts b/wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts index 827cd60d9..26dbac6cb 100644 --- a/wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts +++ b/wallets/phantom/src/playwright/pages/HomePage/actions/getAccountAddress.ts @@ -6,7 +6,10 @@ import type { Networks } from '../../../../type/Networks' export default async function getAccountAddress(network: Networks, page: Page): Promise { // Copy account address to clipboard await page.locator(Selectors.accountMenu.accountName).hover() - await page.locator(Selectors[`${network}WalletAddress`]).click() + await page + .locator(Selectors[`${network}WalletAddress`]) + .first() + .click() // Get clipboard content const handle = await page.evaluateHandle(() => navigator.clipboard.readText()) diff --git a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts index 6c5a29c2d..d91154fae 100644 --- a/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/confirmTransaction.spec.ts @@ -6,7 +6,9 @@ const test = synpress const { expect } = test -test('should Sign Transaction', async ({ page, phantom }) => { +test('should Sign Transaction - ', async ({ page, phantom }) => { + test.setTimeout(90_000) + await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign Transaction' }).click() @@ -20,6 +22,8 @@ test('should Sign Transaction', async ({ page, phantom }) => { }) test('should Sign All Transactions', async ({ page, phantom }) => { + test.setTimeout(90_000) + await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign All Transaction' }).click() diff --git a/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts b/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts index 8ee4f004b..5e6f75a8e 100644 --- a/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts +++ b/wallets/phantom/test/playwright/e2e/connectToDapp.spec.ts @@ -23,6 +23,8 @@ test('should connect wallet to dapp', async ({ context, page, extensionId }) => }) test('should connect multiple wallets to dapp', async ({ context, page, phantomPage, extensionId }) => { + test.setTimeout(90_000) + const phantom = new Phantom(context, phantomPage, basicSetup.walletPassword, extensionId) await phantom.addNewAccount('NewAccount1') diff --git a/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts index b2cd98acf..d3faccc08 100644 --- a/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts +++ b/wallets/phantom/test/playwright/e2e/rejectTransaction.spec.ts @@ -7,6 +7,8 @@ const test = synpress const { expect } = test test('should Reject Transaction ', async ({ page, phantom }) => { + test.setTimeout(90_000) + await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign Transaction' }).click() @@ -20,6 +22,8 @@ test('should Reject Transaction ', async ({ page, phantom }) => { }) test('should Reject All Transactions ', async ({ page, phantom }) => { + test.setTimeout(90_000) + await solanaSandboxSetup(page, phantom) await page.getByRole('button', { name: 'Sign All Transaction' }).click() @@ -33,6 +37,8 @@ test('should Reject All Transactions ', async ({ page, phantom }) => { }) test('should reject contract deployment', async ({ page, phantom }) => { + test.setTimeout(90_000) + await connectPhantomToTestDapp(page, phantom) await expect(page.locator('#tokenAddresses')).toBeEmpty()