From a87e1f47415c42a76ba674dd86dbfc6cab18bd73 Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Wed, 8 Feb 2023 12:42:36 +0100 Subject: [PATCH 01/14] feat: Make sure onboarding a wallet works --- commands/phantom.js | 1290 +++++++++++++++++++++++++ commands/playwright.js | 14 +- pages/phantom/confirmation-page.js | 12 + pages/phantom/first-time-flow-page.js | 85 ++ pages/phantom/main-page.js | 153 +++ pages/phantom/notification-page.js | 146 +++ pages/phantom/page.js | 16 + pages/phantom/settings-page.js | 94 ++ pages/phantom/unlock-page.js | 9 + plugins/index.js | 14 + 10 files changed, 1832 insertions(+), 1 deletion(-) create mode 100644 commands/phantom.js create mode 100644 pages/phantom/confirmation-page.js create mode 100644 pages/phantom/first-time-flow-page.js create mode 100644 pages/phantom/main-page.js create mode 100644 pages/phantom/notification-page.js create mode 100644 pages/phantom/page.js create mode 100644 pages/phantom/settings-page.js create mode 100644 pages/phantom/unlock-page.js diff --git a/commands/phantom.js b/commands/phantom.js new file mode 100644 index 000000000..13e5792d6 --- /dev/null +++ b/commands/phantom.js @@ -0,0 +1,1290 @@ +const log = require('debug')('synpress:phantom'); +const playwright = require('./playwright'); + +const { + welcomePageElements, + firstTimeFlowPageElements, + metametricsPageElements, + firstTimeFlowImportPageElements, + firstTimeFlowCreatePagePageElements, + secureYourWalletPageElements, + revealSeedPageElements, +} = require('../pages/phantom/first-time-flow-page'); +const { mainPageElements } = require('../pages/phantom/main-page'); +const { unlockPageElements } = require('../pages/phantom/unlock-page'); +const { + notificationPageElements, + permissionsPageElements, + confirmPageElements, + signaturePageElements, + encryptionPublicKeyPageElements, + decryptPageElements, + dataSignaturePageElements, + recipientPopupElements, + addTokenPageElements, +} = require('../pages/phantom/notification-page'); +const { + settingsPageElements, + advancedPageElements, + experimentalSettingsPageElements, + resetAccountModalElements, + addNetworkPageElements, +} = require('../pages/phantom/settings-page'); +const { + confirmationPageElements, +} = require('../pages/phantom/confirmation-page'); +const { setNetwork } = require('../helpers'); + +let extensionInitialUrl; +let extensionId; +let extensionHomeUrl; +let extensionSettingsUrl; +let extensionAdvancedSettingsUrl; +let extensionExperimentalSettingsUrl; +let extensionAddNetworkUrl; +let extensionNewAccountUrl; +let extensionImportAccountUrl; +let extensionImportTokenUrl; +let walletAddress; +let switchBackToCypressWindow; + +module.exports = { + extensionId: () => { + return extensionId; + }, + extensionUrls: () => { + return { + extensionInitialUrl, + extensionHomeUrl, + extensionSettingsUrl, + extensionAdvancedSettingsUrl, + extensionExperimentalSettingsUrl, + extensionAddNetworkUrl, + extensionNewAccountUrl, + extensionImportAccountUrl, + extensionImportTokenUrl, + }; + }, + walletAddress: () => { + return walletAddress; + }, + goTo: async url => { + await Promise.all([ + playwright.metamaskWindow().waitForNavigation(), + playwright.metamaskWindow().goto(url), + ]); + await playwright.waitUntilStable(); + }, + goToHome: async () => { + await module.exports.goTo(extensionHomeUrl); + }, + goToSettings: async () => { + await module.exports.goTo(extensionSettingsUrl); + }, + goToAdvancedSettings: async () => { + await module.exports.goTo(extensionAdvancedSettingsUrl); + }, + goToExperimentalSettings: async () => { + await module.exports.goTo(extensionExperimentalSettingsUrl); + }, + goToAddNetwork: async () => { + await module.exports.goTo(extensionAddNetworkUrl); + }, + goToNewAccount: async () => { + await module.exports.goTo(extensionNewAccountUrl); + }, + goToImportAccount: async () => { + await module.exports.goTo(extensionImportAccountUrl); + }, + goToImportToken: async () => { + await module.exports.goTo(extensionImportTokenUrl); + }, + getExtensionDetails: async () => { + extensionInitialUrl = await playwright.metamaskWindow().url(); + extensionId = extensionInitialUrl.match('//(.*?)/')[1]; + extensionHomeUrl = `chrome-extension://${extensionId}/notification.html`; + extensionSettingsUrl = `${extensionHomeUrl}#settings`; + extensionAdvancedSettingsUrl = `${extensionSettingsUrl}/advanced`; + extensionExperimentalSettingsUrl = `${extensionSettingsUrl}/experimental`; + extensionAddNetworkUrl = `${extensionSettingsUrl}/networks/add-network`; + extensionNewAccountUrl = `${extensionHomeUrl}#new-account`; + extensionImportAccountUrl = `${extensionNewAccountUrl}/import`; + extensionImportTokenUrl = `${extensionHomeUrl}#import-token`; + + return { + extensionInitialUrl, + extensionId, + extensionSettingsUrl, + extensionAdvancedSettingsUrl, + extensionExperimentalSettingsUrl, + extensionAddNetworkUrl, + extensionNewAccountUrl, + extensionImportAccountUrl, + extensionImportTokenUrl, + }; + }, + // Phantom doesn't need this, it's well coded :) + fixBlankPage: async () => { + return; + }, + confirmWelcomePage: async () => { + await module.exports.fixBlankPage(); + await playwright.waitAndClick( + welcomePageElements.confirmButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + return true; + }, + closePopupAndTooltips: async () => { + // note: this is required for fast execution of e2e tests to avoid flakiness + // otherwise popup may not be detected properly and not closed + try { + if ( + await playwright + .metamaskWindow() + .locator(mainPageElements.popup.container) + .isVisible() + ) { + const popupBackground = playwright + .metamaskWindow() + .locator(mainPageElements.popup.background); + const popupBackgroundBox = await popupBackground.boundingBox(); + await playwright + .metamaskWindow() + .mouse.click(popupBackgroundBox.x + 1, popupBackgroundBox.y + 1); + } + if ( + await playwright + .metamaskWindow() + .locator(mainPageElements.tippyTooltip.closeButton) + .isVisible() + ) { + await playwright.waitAndClick( + mainPageElements.tippyTooltip.closeButton, + ); + } + if ( + await playwright + .metamaskWindow() + .locator(mainPageElements.actionableMessage.closeButton) + .isVisible() + ) { + await playwright.waitAndClick( + mainPageElements.actionableMessage.closeButton, + ); + } + } catch (ex) { + return true; + } + return true; + }, + closeModal: async () => { + // note: this is required for fast execution of e2e tests to avoid flakiness + // otherwise modal may not be detected properly and not closed + await playwright.metamaskWindow().waitForTimeout(1000); + if ( + await playwright + .metamaskWindow() + .locator(mainPageElements.connectedSites.modal) + .isVisible() + ) { + await playwright.waitAndClick( + mainPageElements.connectedSites.closeButton, + ); + } + return true; + }, + unlock: async password => { + await module.exports.fixBlankPage(); + await playwright.waitAndType(unlockPageElements.passwordInput, password); + await playwright.waitAndClick( + unlockPageElements.unlockButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + return true; + }, + optOutAnalytics: async () => { + await playwright.waitAndClick( + metametricsPageElements.optOutAnalyticsButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + return true; + }, + importWallet: async (secretWords, password) => { + await playwright.waitAndClick(firstTimeFlowPageElements.importWalletButton); + + // STEP: Input mnemonic words and click Import + // todo: add support for more secret words (15/18/21/24) + for (const [index, word] of secretWords.split(' ').entries()) { + await playwright.waitAndType( + firstTimeFlowImportPageElements.secretWordsInput(index), + word, + ); + } + await playwright.waitAndClick( + firstTimeFlowImportPageElements.confirmWordsButton, + ); + + // STEP: Wait for confirm input + // shortcut confirmation + await new Promise(resolve => setTimeout(resolve, 200)); // the transitioning is too fast + await playwright.waitAndClick( + firstTimeFlowImportPageElements.confirmWordsButton, + // 'button:text("Import Selected Accounts")', + ); + + // STEP: Input password, confirm and continue + await playwright.waitAndType( + firstTimeFlowImportPageElements.passwordInput, + password, + ); + await playwright.waitAndType( + firstTimeFlowImportPageElements.confirmPasswordInput, + password, + ); + await playwright.waitAndClick( + firstTimeFlowImportPageElements.termsCheckbox, + ); + // continue to next screen + await playwright.waitAndClick( + firstTimeFlowImportPageElements.continueAfterPasswordButton, + ); + // shortcut confirmation + await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast + await playwright.waitAndClick( + firstTimeFlowImportPageElements.continueOnShortcutConfirm, + ); + // finish + await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast + await playwright.waitAndClick( + firstTimeFlowImportPageElements.continueOnShortcutConfirm, + ); + + await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast + await module.exports.closePopupAndTooltips(); + return true; + }, + createWallet: async password => { + await module.exports.optOutAnalytics(); + await playwright.waitAndClick( + firstTimeFlowPageElements.createWalletButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await playwright.waitAndType( + firstTimeFlowCreatePagePageElements.newPasswordInput, + password, + ); + await playwright.waitAndType( + firstTimeFlowCreatePagePageElements.confirmNewPasswordInput, + password, + ); + await playwright.waitAndClick( + firstTimeFlowCreatePagePageElements.newSignupCheckbox, + ); + await playwright.waitAndClick( + firstTimeFlowCreatePagePageElements.createButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await playwright.waitAndClick( + secureYourWalletPageElements.nextButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await playwright.waitAndClick( + revealSeedPageElements.remindLaterButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + return true; + }, + importAccount: async privateKey => { + await switchToPhantomIfNotActive(); + await module.exports.goToImportAccount(); + await playwright.waitAndType( + mainPageElements.importAccount.input, + privateKey, + ); + await playwright.waitAndClick( + mainPageElements.importAccount.importButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + await switchToCypressIfNotActive(); + return true; + }, + createAccount: async accountName => { + if (accountName) { + accountName = accountName.toLowerCase(); + } + await switchToPhantomIfNotActive(); + await module.exports.goToNewAccount(); + if (accountName) { + await playwright.waitAndType( + mainPageElements.createAccount.input, + accountName, + ); + } + await playwright.waitAndClick(mainPageElements.createAccount.createButton); + await module.exports.closePopupAndTooltips(); + await switchToCypressIfNotActive(); + return true; + }, + switchAccount: async accountNameOrAccountNumber => { + if (typeof accountNameOrAccountNumber === 'string') { + accountNameOrAccountNumber = accountNameOrAccountNumber.toLowerCase(); + } + await switchToPhantomIfNotActive(); + // note: closePopupAndTooltips() is required after changing createAccount() to use direct urls (popup started appearing) + // ^ this change also introduced 500ms delay for closePopupAndTooltips() function + await module.exports.closePopupAndTooltips(); + await playwright.waitAndClick(mainPageElements.accountMenu.button); + if (typeof accountNameOrAccountNumber === 'number') { + await playwright.waitAndClick( + mainPageElements.accountMenu.accountButton(accountNameOrAccountNumber), + ); + } else { + await playwright.waitAndClickByText( + mainPageElements.accountMenu.accountName, + accountNameOrAccountNumber, + ); + } + await module.exports.closePopupAndTooltips(); + await switchToCypressIfNotActive(); + return true; + }, + changeNetwork: async network => { + await switchToPhantomIfNotActive(); + await playwright.waitAndClick(mainPageElements.networkSwitcher.button); + if (typeof network === 'string') { + network = network.toLowerCase(); + if (network === 'mainnet') { + await playwright.waitAndClick( + mainPageElements.networkSwitcher.mainnetNetworkItem, + ); + } else if (network === 'goerli') { + await playwright.waitAndClick( + mainPageElements.networkSwitcher.goerliNetworkItem, + ); + } else if (network === 'sepolia') { + await playwright.waitAndClick( + mainPageElements.networkSwitcher.sepoliaNetworkItem, + ); + } else if (network === 'localhost') { + await playwright.waitAndClick( + mainPageElements.networkSwitcher.localhostNetworkItem, + ); + } else { + await playwright.waitAndClickByText( + mainPageElements.networkSwitcher.dropdownMenuItem, + network, + ); + } + await playwright.waitForText( + mainPageElements.networkSwitcher.networkName, + network, + ); + } else if (typeof network === 'object') { + network.networkName = network.networkName.toLowerCase(); + await playwright.waitAndClickByText( + mainPageElements.networkSwitcher.dropdownMenuItem, + network.networkName, + ); + await playwright.waitForText( + mainPageElements.networkSwitcher.networkName, + network.networkName, + ); + } + await module.exports.closePopupAndTooltips(); + await setNetwork(network); + await switchToCypressIfNotActive(); + return true; + }, + addNetwork: async network => { + await switchToPhantomIfNotActive(); + if ( + process.env.NETWORK_NAME && + process.env.RPC_URL && + process.env.CHAIN_ID + ) { + network = { + networkName: process.env.NETWORK_NAME, + rpcUrl: process.env.RPC_URL, + chainId: process.env.CHAIN_ID, + symbol: process.env.SYMBOL, + blockExplorer: process.env.BLOCK_EXPLORER, + isTestnet: process.env.IS_TESTNET, + }; + } + if (typeof network === 'string') { + network = network.toLowerCase(); + } else if (typeof network === 'object') { + network.networkName = network.networkName.toLowerCase(); + } + await module.exports.goToAddNetwork(); + await playwright.waitAndType( + addNetworkPageElements.networkNameInput, + network.networkName, + ); + await playwright.waitAndType( + addNetworkPageElements.rpcUrlInput, + network.rpcUrl, + ); + await playwright.waitAndType( + addNetworkPageElements.chainIdInput, + network.chainId, + ); + if (network.symbol) { + await playwright.waitAndType( + addNetworkPageElements.symbolInput, + network.symbol, + ); + } + if (network.blockExplorer) { + await playwright.waitAndType( + addNetworkPageElements.blockExplorerInput, + network.blockExplorer, + ); + } + await playwright.waitAndClick( + addNetworkPageElements.saveButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + await setNetwork(network); + await playwright.waitForText( + mainPageElements.networkSwitcher.networkName, + network.networkName, + ); + await switchToCypressIfNotActive(); + return true; + }, + async disconnectWalletFromDapp() { + await switchToPhantomIfNotActive(); + await playwright.waitAndClick(mainPageElements.optionsMenu.button); + await playwright.waitAndClick( + mainPageElements.optionsMenu.connectedSitesButton, + ); + if ( + await playwright + .metamaskWindow() + .locator(mainPageElements.connectedSites.disconnectLabel) + .isVisible() + ) { + console.log( + '[disconnectWalletFromDapp] Wallet is connected to a dapp, disconnecting..', + ); + await playwright.waitAndClick( + mainPageElements.connectedSites.disconnectLabel, + ); + await playwright.waitAndClick( + mainPageElements.connectedSites.disconnectButton, + ); + } else { + console.log( + '[disconnectWalletFromDapp] Wallet is not connected to a dapp, skipping..', + ); + } + await module.exports.closeModal(); + await switchToCypressIfNotActive(); + return true; + }, + async disconnectWalletFromAllDapps() { + await switchToPhantomIfNotActive(); + await playwright.waitAndClick(mainPageElements.optionsMenu.button); + await playwright.waitAndClick( + mainPageElements.optionsMenu.connectedSitesButton, + ); + const disconnectLabels = await playwright + .metamaskWindow() + .$$(mainPageElements.connectedSites.disconnectLabel); + if (disconnectLabels.length) { + console.log( + '[disconnectWalletFromAllDapps] Wallet is connected to dapps, disconnecting..', + ); + // eslint-disable-next-line no-unused-vars + for (const disconnectLabel of disconnectLabels) { + await playwright.waitAndClick( + mainPageElements.connectedSites.disconnectLabel, + ); + await playwright.waitAndClick( + mainPageElements.connectedSites.disconnectButton, + ); + } + } else { + console.log( + '[disconnectWalletFromAllDapps] Wallet is not connected to any dapps, skipping..', + ); + } + await module.exports.closeModal(); + await switchToCypressIfNotActive(); + return true; + }, + activateAdvancedGasControl: async skipSetup => { + return await activateAdvancedSetting( + advancedPageElements.advancedGasControlToggleOn, + advancedPageElements.advancedGasControlToggleOff, + skipSetup, + ); + }, + activateEnhancedTokenDetection: async skipSetup => { + return await activateAdvancedSetting( + advancedPageElements.enhancedTokenDetectionToggleOn, + advancedPageElements.enhancedTokenDetectionToggleOff, + skipSetup, + ); + }, + activateShowHexData: async skipSetup => { + return await activateAdvancedSetting( + advancedPageElements.showHexDataToggleOn, + advancedPageElements.showHexDataToggleOff, + skipSetup, + ); + }, + activateTestnetConversion: async skipSetup => { + return await activateAdvancedSetting( + advancedPageElements.showTestnetConversionOn, + advancedPageElements.showTestnetConversionOff, + skipSetup, + ); + }, + activateShowTestnetNetworks: async skipSetup => { + return await activateAdvancedSetting( + advancedPageElements.showTestnetNetworksOn, + advancedPageElements.showTestnetNetworksOff, + skipSetup, + ); + }, + activateCustomNonce: async skipSetup => { + return await activateAdvancedSetting( + advancedPageElements.customNonceToggleOn, + advancedPageElements.customNonceToggleOff, + skipSetup, + ); + }, + activateDismissBackupReminder: async skipSetup => { + return await activateAdvancedSetting( + advancedPageElements.dismissBackupReminderOn, + advancedPageElements.dismissBackupReminderOff, + skipSetup, + ); + }, + activateEnhancedGasFeeUI: async skipSetup => { + return await activateAdvancedSetting( + experimentalSettingsPageElements.enhancedGasFeeUIToggleOn, + experimentalSettingsPageElements.enhancedGasFeeUIToggleOff, + skipSetup, + true, + ); + }, + activateShowCustomNetworkList: async skipSetup => { + return await activateAdvancedSetting( + experimentalSettingsPageElements.showCustomNetworkListToggleOn, + experimentalSettingsPageElements.showCustomNetworkListToggleOff, + skipSetup, + true, + ); + }, + resetAccount: async () => { + await switchToPhantomIfNotActive(); + await module.exports.goToAdvancedSettings(); + await playwright.waitAndClick(advancedPageElements.resetAccountButton); + await playwright.waitAndClick(resetAccountModalElements.resetButton); + await playwright.waitAndClick( + settingsPageElements.closeButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + await switchToCypressIfNotActive(); + return true; + }, + confirmSignatureRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + if ( + await playwright + .metamaskNotificationWindow() + .locator(signaturePageElements.signatureRequestScrollDownButton) + .isVisible() + ) { + await playwright.waitAndClick( + signaturePageElements.signatureRequestScrollDownButton, + notificationPage, + ); + } + await playwright.waitAndClick( + signaturePageElements.confirmSignatureRequestButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectSignatureRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + signaturePageElements.rejectSignatureRequestButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmDataSignatureRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + if ( + await playwright + .metamaskNotificationWindow() + .locator(signaturePageElements.signatureRequestScrollDownButton) + .isVisible() + ) { + await playwright.waitAndClick( + signaturePageElements.signatureRequestScrollDownButton, + notificationPage, + ); + } + await playwright.waitAndClick( + dataSignaturePageElements.confirmDataSignatureRequestButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectDataSignatureRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + dataSignaturePageElements.rejectDataSignatureRequestButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + importToken: async tokenConfig => { + let tokenData = {}; + await switchToPhantomIfNotActive(); + await module.exports.goToImportToken(); + if (typeof tokenConfig === 'string') { + await playwright.waitAndType( + mainPageElements.importToken.tokenContractAddressInput, + tokenConfig, + ); + tokenData.tokenContractAddress = tokenConfig; + tokenData.tokenSymbol = await playwright.waitAndGetInputValue( + mainPageElements.importToken.tokenSymbolInput, + ); + } else { + await playwright.waitAndType( + mainPageElements.importToken.tokenContractAddressInput, + tokenConfig.address, + ); + tokenData.tokenContractAddress = tokenConfig.address; + await playwright.waitAndClick( + mainPageElements.importToken.tokenEditButton, + await playwright.metamaskWindow(), + { + force: true, + }, + ); + await playwright.waitClearAndType( + tokenConfig.symbol, + mainPageElements.importToken.tokenSymbolInput, + ); + tokenData.tokenSymbol = tokenConfig.symbol; + } + tokenData.tokenDecimals = await playwright.waitAndGetInputValue( + mainPageElements.importToken.tokenDecimalInput, + ); + await playwright.waitAndClick( + mainPageElements.importToken.addCustomTokenButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await playwright.waitAndClick( + mainPageElements.importToken.importTokensButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await playwright.waitAndClick( + mainPageElements.asset.backButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + await switchToCypressIfNotActive(); + tokenData.imported = true; + return tokenData; + }, + confirmAddToken: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + addTokenPageElements.confirmAddTokenButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectAddToken: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + addTokenPageElements.rejectAddTokenButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmPermissionToSpend: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + notificationPageElements.allowToSpendButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectPermissionToSpend: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + notificationPageElements.rejectToSpendButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + acceptAccess: async options => { + const notificationPage = await playwright.switchToMetamaskNotification(); + if (options && options.allAccounts) { + await playwright.waitAndClick( + notificationPageElements.selectAllCheckbox, + notificationPage, + ); + } + await playwright.waitAndClick( + notificationPageElements.nextButton, + notificationPage, + { waitForEvent: 'navi' }, + ); + if (options && options.signInSignature) { + await playwright.waitAndClick( + permissionsPageElements.connectButton, + notificationPage, + { waitForEvent: 'navi' }, + ); + await module.exports.confirmSignatureRequest(); + } else { + await playwright.waitAndClick( + permissionsPageElements.connectButton, + notificationPage, + { waitForEvent: 'close' }, + ); + } + return true; + }, + confirmTransaction: async gasConfig => { + let txData = {}; + const notificationPage = await playwright.switchToMetamaskNotification(); + if (gasConfig) { + log( + '[confirmTransaction] gasConfig is present, determining transaction type..', + ); + if ( + await playwright + .metamaskNotificationWindow() + .locator(confirmPageElements.editGasFeeLegacyButton) + .isVisible() + ) { + log('[confirmTransaction] Looks like legacy tx'); + if (typeof gasConfig === 'object') { + log('[confirmTransaction] Editing legacy tx..'); + await playwright.waitAndClick( + confirmPageElements.editGasFeeLegacyButton, + notificationPage, + ); + if ( + await playwright + .metamaskNotificationWindow() + .locator(confirmPageElements.editGasFeeLegacyOverrideAckButton) + .isVisible() + ) { + log( + '[confirmTransaction] Override acknowledgement modal is present, closing..', + ); + await playwright.waitAndClick( + confirmPageElements.editGasFeeLegacyOverrideAckButton, + notificationPage, + ); + } + if (gasConfig.gasLimit) { + log('[confirmTransaction] Changing gas limit..'); + await playwright.waitAndSetValue( + gasConfig.gasLimit.toString(), + confirmPageElements.gasLimitLegacyInput, + notificationPage, + ); + } + if (gasConfig.gasPrice) { + log('[confirmTransaction] Changing gas price..'); + await playwright.waitAndSetValue( + gasConfig.gasPrice.toString(), + confirmPageElements.gasPriceLegacyInput, + notificationPage, + ); + } + await playwright.waitAndClick( + confirmPageElements.saveCustomGasFeeButton, + notificationPage, + ); + } else { + log( + "[confirmTransaction] Legacy tx doesn't support eip-1559 fees (low, market, aggressive, site), using default values..", + ); + } + } else { + log('[confirmTransaction] Looks like eip-1559 tx'); + await playwright.waitAndClick( + confirmPageElements.editGasFeeButton, + notificationPage, + ); + if (typeof gasConfig === 'string') { + if (gasConfig === 'low') { + log('[confirmTransaction] Changing gas fee to low..'); + await playwright.waitAndClick( + confirmPageElements.gasOptionLowButton, + notificationPage, + ); + } else if (gasConfig === 'market') { + log('[confirmTransaction] Changing gas fee to market..'); + await playwright.waitAndClick( + confirmPageElements.gasOptionMediumButton, + notificationPage, + ); + } else if (gasConfig === 'aggressive') { + log('[confirmTransaction] Changing gas fee to aggressive..'); + await playwright.waitAndClick( + confirmPageElements.gasOptionHighButton, + notificationPage, + ); + } else if (gasConfig === 'site') { + log('[confirmTransaction] Changing gas fee to site suggested..'); + await playwright.waitAndClick( + confirmPageElements.gasOptionDappSuggestedButton, + notificationPage, + ); + } + } else { + log('[confirmTransaction] Editing eip-1559 tx..'); + await playwright.waitAndClick( + confirmPageElements.gasOptionCustomButton, + notificationPage, + ); + if (gasConfig.gasLimit) { + log('[confirmTransaction] Changing gas limit..'); + await playwright.waitAndClick( + confirmPageElements.editGasLimitButton, + notificationPage, + ); + await playwright.waitAndSetValue( + gasConfig.gasLimit.toString(), + confirmPageElements.gasLimitInput, + notificationPage, + ); + } + if (gasConfig.baseFee) { + log('[confirmTransaction] Changing base fee..'); + await playwright.waitAndSetValue( + gasConfig.baseFee.toString(), + confirmPageElements.baseFeeInput, + notificationPage, + ); + } + if (gasConfig.priorityFee) { + log('[confirmTransaction] Changing priority fee..'); + await playwright.waitAndSetValue( + gasConfig.priorityFee.toString(), + confirmPageElements.priorityFeeInput, + notificationPage, + ); + } + await playwright.waitAndClick( + confirmPageElements.saveCustomGasFeeButton, + notificationPage, + ); + } + } + } + log('[confirmTransaction] Checking if recipient address is present..'); + if ( + await playwright + .metamaskNotificationWindow() + .locator(confirmPageElements.recipientButton) + .isVisible() + ) { + log('[confirmTransaction] Getting recipient address..'); + await playwright.waitAndClick( + confirmPageElements.recipientButton, + notificationPage, + ); + txData.recipientPublicAddress = await playwright.waitAndGetValue( + recipientPopupElements.recipientPublicAddress, + notificationPage, + ); + await playwright.waitAndClick( + recipientPopupElements.popupCloseButton, + notificationPage, + ); + } + log('[confirmTransaction] Checking if network name is present..'); + if ( + await playwright + .metamaskNotificationWindow() + .locator(confirmPageElements.networkLabel) + .isVisible() + ) { + log('[confirmTransaction] Getting network name..'); + txData.networkName = await playwright.waitAndGetValue( + confirmPageElements.networkLabel, + notificationPage, + ); + } + // todo: handle setting of custom nonce here + log('[confirmTransaction] Getting transaction nonce..'); + txData.customNonce = await playwright.waitAndGetAttributeValue( + confirmPageElements.customNonceInput, + 'placeholder', + notificationPage, + ); + // todo: fix getting tx data on function multicall + // log('[confirmTransaction] Checking if tx data is present..'); + // if ( + // await playwright + // .metamaskNotificationWindow() + // .locator(confirmPageElements.dataButton) + // .isVisible() + // ) { + // log('[confirmTransaction] Fetching tx data..'); + // await playwright.waitAndClick( + // confirmPageElements.dataButton, + // notificationPage, + // ); + // log('[confirmTransaction] Getting origin value..'); + // txData.origin = await playwright.waitAndGetValue( + // confirmPageElements.originValue, + // notificationPage, + // ); + // log('[confirmTransaction] Getting bytes value..'); + // txData.bytes = await playwright.waitAndGetValue( + // confirmPageElements.bytesValue, + // notificationPage, + // ); + // log('[confirmTransaction] Getting hex data value..'); + // txData.hexData = await playwright.waitAndGetValue( + // confirmPageElements.hexDataValue, + // notificationPage, + // ); + // await playwright.waitAndClick( + // confirmPageElements.detailsButton, + // notificationPage, + // ); + // } + log('[confirmTransaction] Confirming transaction..'); + await playwright.waitAndClick( + confirmPageElements.confirmButton, + notificationPage, + { waitForEvent: 'close' }, + ); + txData.confirmed = true; + log('[confirmTransaction] Transaction confirmed!'); + return txData; + }, + rejectTransaction: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + confirmPageElements.rejectButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmEncryptionPublicKeyRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + encryptionPublicKeyPageElements.confirmEncryptionPublicKeyButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectEncryptionPublicKeyRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + encryptionPublicKeyPageElements.rejectEncryptionPublicKeyButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmDecryptionRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + decryptPageElements.confirmDecryptionRequestButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectDecryptionRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + decryptPageElements.rejectDecryptionRequestButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + allowToAddNetwork: async ({ waitForEvent } = {}) => { + const notificationPage = await playwright.switchToMetamaskNotification(); + if (waitForEvent) { + await playwright.waitAndClick( + confirmationPageElements.footer.approveButton, + notificationPage, + { waitForEvent }, + ); + } else { + await playwright.waitAndClick( + confirmationPageElements.footer.approveButton, + notificationPage, + ); + } + return true; + }, + rejectToAddNetwork: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + confirmationPageElements.footer.cancelButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + allowToSwitchNetwork: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + confirmationPageElements.footer.approveButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectToSwitchNetwork: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + confirmationPageElements.footer.cancelButton, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + allowToAddAndSwitchNetwork: async () => { + await module.exports.allowToAddNetwork(); + await module.exports.allowToSwitchNetwork(); + return true; + }, + getWalletAddress: async () => { + await switchToPhantomIfNotActive(); + await playwright.metamaskWindow().hover(mainPageElements.accountBar.title); + await new Promise(resolve => setTimeout(resolve, 1000)); + await playwright.waitAndClick(mainPageElements.accountBar.ethRow); + walletAddress = await playwright + .metamaskWindow() + .evaluate('navigator.clipboard.readText()'); + await switchToCypressIfNotActive(); + return walletAddress; + }, + initialSetup: async ({ + secretWordsOrPrivateKey, + network, + password, + enableAdvancedSettings, + }) => { + const isCustomNetwork = + (process.env.NETWORK_NAME && + process.env.RPC_URL && + process.env.CHAIN_ID) || + typeof network == 'object'; + + await playwright.init(); + await playwright.assignWindows(); + await playwright.assignActiveTabName('metamask'); + await module.exports.getExtensionDetails(); + await module.exports.fixBlankPage(); + + // password (unlock?) or new set up? + if ( + await playwright + .metamaskWindow() + .locator(firstTimeFlowPageElements.importWalletButton) + .isVisible() + ) { + if (secretWordsOrPrivateKey.includes(' ')) { + // secret words + await module.exports.importWallet(secretWordsOrPrivateKey, password); + } else { + // private key + await module.exports.createWallet(password); + await module.exports.importAccount(secretWordsOrPrivateKey); + } + + // await setupSettings(enableAdvancedSettings); + // if (isCustomNetwork) { + // await module.exports.addNetwork(network); + // } else { + // await module.exports.changeNetwork(network); + // } + await switchToPhantomIfNotActive(); + // await module.exports.goToHome(); + + if ( + await playwright + .metamaskWindow() + .locator(mainPageElements.whatsNew.header) + ) { + await playwright + .metamaskWindow() + .click(mainPageElements.whatsNew.continueButton); + } + walletAddress = await module.exports.getWalletAddress(); + await playwright.switchToCypressWindow(); + return true; + } else if ( + await playwright + .metamaskWindow() + .locator(unlockPageElements.passwordInput) + .isVisible() + ) { + await module.exports.unlock(password); + walletAddress = await module.exports.getWalletAddress(); + await playwright.switchToCypressWindow(); + return true; + } else { + if ( + (await playwright + .metamaskWindow() + .locator(mainPageElements.walletOverview) + .isVisible()) && + !process.env.RESET_PHANTOM + ) { + await switchToPhantomIfNotActive(); + walletAddress = await module.exports.getWalletAddress(); + await playwright.switchToCypressWindow(); + return true; + } else { + // todo: reset phantom state + } + } + }, +}; + +async function switchToPhantomIfNotActive() { + await playwright.switchToMetamaskWindow(); + if (await playwright.isCypressWindowActive()) { + await playwright.switchToMetamaskWindow(); + switchBackToCypressWindow = true; + } + return switchBackToCypressWindow; +} + +async function switchToCypressIfNotActive() { + if (switchBackToCypressWindow) { + await playwright.switchToCypressWindow(); + switchBackToCypressWindow = false; + } + return switchBackToCypressWindow; +} + +async function activateAdvancedSetting( + toggleOn, + toggleOff, + skipSetup, + experimental, +) { + if (!skipSetup) { + await switchToPhantomIfNotActive(); + if (experimental) { + await module.exports.goToExperimentalSettings(); + } else { + await module.exports.goToAdvancedSettings(); + } + } + if (!(await playwright.metamaskWindow().locator(toggleOn).isVisible())) { + await playwright.waitAndClick(toggleOff); + } + if (!skipSetup) { + await playwright.waitAndClick( + settingsPageElements.closeButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + await switchToCypressIfNotActive(); + } + return true; +} + +async function setupSettings(enableAdvancedSettings) { + await switchToPhantomIfNotActive(); + await module.exports.goToAdvancedSettings(); + await module.exports.activateAdvancedGasControl(true); + await module.exports.activateShowHexData(true); + await module.exports.activateShowTestnetNetworks(true); + await module.exports.activateCustomNonce(true); + await module.exports.activateDismissBackupReminder(true); + if (enableAdvancedSettings) { + await module.exports.activateEnhancedTokenDetection(true); + await module.exports.activateTestnetConversion(true); + } + await module.exports.goToExperimentalSettings(); + await module.exports.activateEnhancedGasFeeUI(true); + await playwright.waitAndClick( + settingsPageElements.closeButton, + await playwright.metamaskWindow(), + { + waitForEvent: 'navi', + }, + ); + await module.exports.closePopupAndTooltips(); + await switchToCypressIfNotActive(); + return true; +} diff --git a/commands/playwright.js b/commands/playwright.js index 7867f3e3c..94c43e5dc 100644 --- a/commands/playwright.js +++ b/commands/playwright.js @@ -98,7 +98,19 @@ module.exports = { } return true; }, - async switchToMetamaskWindow() { + switchToMetamaskWindow: async () => { + if (metamaskWindow.isClosed()) { + const newPage = await browser.contexts()[0].newPage(); + await Promise.all([ + newPage.waitForNavigation(), + newPage.goto( + metamaskWindow.url().replace('onboarding.html', 'popup.html'), + ), + ]); + // await newPage.waitUntilStable(); + metamaskWindow = newPage; + } + await metamaskWindow.bringToFront(); await module.exports.assignActiveTabName('metamask'); return true; diff --git a/pages/phantom/confirmation-page.js b/pages/phantom/confirmation-page.js new file mode 100644 index 000000000..b19a1b57d --- /dev/null +++ b/pages/phantom/confirmation-page.js @@ -0,0 +1,12 @@ +const confirmationPage = '.confirmation-page'; +const confirmationPageFooter = `${confirmationPage} .confirmation-footer`; +const footer = { + footer: confirmationPageFooter, + cancelButton: `${confirmationPageFooter} .btn-secondary`, + approveButton: `${confirmationPageFooter} .btn-primary`, +}; + +module.exports.confirmationPageElements = { + confirmationPage, + footer, +}; diff --git a/pages/phantom/first-time-flow-page.js b/pages/phantom/first-time-flow-page.js new file mode 100644 index 000000000..bb80bd93b --- /dev/null +++ b/pages/phantom/first-time-flow-page.js @@ -0,0 +1,85 @@ +const app = '#root'; +const welcomePage = '#root'; +const confirmButton = `${welcomePage} .first-time-flow__button`; +module.exports.welcomePageElements = { + app, + welcomePage, + confirmButton, +}; + +const metametricsPage = '.metametrics-opt-in'; +const optOutAnalyticsButton = `${metametricsPage} [data-testid="page-container-footer-cancel"]`; +module.exports.metametricsPageElements = { + metametricsPage, + optOutAnalyticsButton, +}; + +const firstTimeFlowPage = '.first-time-flow'; +const importWalletButton = `[data-testid="import-recovery-phrase-button"]`; +const createWalletButton = `${firstTimeFlowPage} [data-testid="create-wallet-button"]`; +module.exports.firstTimeFlowPageElements = { + firstTimeFlowPage, + importWalletButton, + createWalletButton, +}; + +const firstTimeFlowImportPage = '.first-time-flow__import'; +const newVaultForm = `${firstTimeFlowImportPage} .create-new-vault__form`; +const secretWordsInput = number => + `[data-testid="secret-recovery-phrase-word-input-${number}"]`; +const confirmWordsButton = `[data-testid="onboarding-form-submit-button"]`; +const passwordInput = `[data-testid="onboarding-form-password-input"]`; +const confirmPasswordInput = `[data-testid="onboarding-form-confirm-password-input"]`; +const termsCheckbox = `[data-testid="onboarding-form-terms-of-service-checkbox"]`; +const continueAfterPasswordButton = + '[data-testid="onboarding-form-submit-button"]'; +const continueOnShortcutConfirm = + '[data-testid="onboarding-form-submit-button"]'; +const importButton = `${newVaultForm} .create-new-vault__submit-button`; + +module.exports.firstTimeFlowImportPageElements = { + firstTimeFlowImportPage, + newVaultForm, + secretWordsInput, + passwordInput, + confirmPasswordInput, + termsCheckbox, + importButton, + confirmWordsButton, + continueAfterPasswordButton, + continueOnShortcutConfirm, +}; + +const firstTimeFlowCreatePage = '.first-time-flow'; +const newPasswordInput = `${firstTimeFlowCreatePage} [data-testid="create-password"]`; +const confirmNewPasswordInput = `${firstTimeFlowCreatePage} [data-testid="confirm-password"]`; +const newSignupCheckbox = `${firstTimeFlowCreatePage} .first-time-flow__checkbox`; +const createButton = `${firstTimeFlowCreatePage} .first-time-flow__button`; +module.exports.firstTimeFlowCreatePagePageElements = { + firstTimeFlowCreatePage, + newPasswordInput, + confirmNewPasswordInput, + newSignupCheckbox, + createButton, +}; + +const secureYourWalletPage = '[data-testid="seed-phrase-intro"]'; +const nextButton = `${secureYourWalletPage} button`; +module.exports.secureYourWalletPageElements = { + secureYourWalletPage, + nextButton, +}; + +const revealSeedPage = '[data-testid="reveal-seed-phrase"]'; +const remindLaterButton = `${revealSeedPage} .first-time-flow__button`; +module.exports.revealSeedPageElements = { + revealSeedPage, + remindLaterButton, +}; + +const endOfFlowPage = '[data-testid="end-of-flow"]'; +const allDoneButton = `${endOfFlowPage} [data-testid="EOF-complete-button"]`; +module.exports.endOfFlowPageElements = { + endOfFlowPage, + allDoneButton, +}; diff --git a/pages/phantom/main-page.js b/pages/phantom/main-page.js new file mode 100644 index 000000000..e978cb23e --- /dev/null +++ b/pages/phantom/main-page.js @@ -0,0 +1,153 @@ +const networkSwitcherButtonSelector = '.network-display'; +const networkSwitcher = { + button: networkSwitcherButtonSelector, + networkName: `${networkSwitcherButtonSelector} .typography`, + dropdownMenu: '[data-testid="network-droppo"]', + dropdownMenuItem: `[data-testid="network-droppo"] .dropdown-menu-item`, + mainnetNetworkItem: `[data-testid="network-droppo"] [data-testid="mainnet-network-item"]`, + goerliNetworkItem: `[data-testid="network-droppo"] [data-testid="goerli-network-item"]`, + sepoliaNetworkItem: `[data-testid="network-droppo"] [data-testid="sepolia-network-item"]`, + localhostNetworkItem: `[data-testid="network-droppo"] [data-testid="Localhost 8545-network-item"]`, + networkButton: number => + `[data-testid="network-droppo"] .dropdown-menu-item:nth-child(${ + 3 + number + })`, +}; + +const walletOverview = '.wallet-overview'; + +const tabs = { + assetsButton: '[data-testid="home__asset-tab"] button', + activityButton: '[data-testid="home__activity-tab"] button', +}; + +const transactionList = '.transaction-list__transactions'; +const pendingTransactionsList = `${transactionList} .transaction-list__pending-transactions`; +const completedTransactionsList = `${transactionList} .transaction-list__completed-transactions`; +const activityTab = { + transactionList, + pendingTransactionsList, + completedTransactionsList, + unconfirmedTransaction: `${pendingTransactionsList} .transaction-list-item--unconfirmed`, + confirmedTransaction: `${completedTransactionsList} .transaction-list-item`, +}; + +const popupSelector = '.popover-container'; +const sendPopupSelector = `${popupSelector} .transaction-list-item-details`; +const popup = { + container: popupSelector, + closeButton: '.popover-header__button', + background: '.popover-bg', + sendPopup: { + container: sendPopupSelector, + speedUpButton: `${sendPopupSelector} .btn-primary`, + cancelButton: `${sendPopupSelector} .btn-secondary`, + transactionStatus: `${sendPopupSelector} .transaction-status`, + copyTxIdButton: `${sendPopupSelector} .transaction-list-item-details__tx-hash .transaction-list-item-details__header-button a`, + // todo: + }, +}; + +const tippyTooltipSelector = '.tippy-popper'; +const tippyTooltip = { + container: tippyTooltipSelector, + closeButton: `${tippyTooltipSelector} button`, +}; + +const actionableMessageSelector = '.actionable-message'; +const actionableMessage = { + container: actionableMessageSelector, + closeButton: `${actionableMessageSelector} button`, +}; + +const accountMenu = { + button: '.account-menu__icon', + accountButton: number => `.account-menu__account:nth-child(${number})`, + accountName: '.account-menu__name', + createAccountButton: '.account-menu__item--clickable:nth-child(6)', + importAccountButton: '.account-menu__item--clickable:nth-child(7)', + settingsButton: '.account-menu__item--clickable:nth-child(11)', +}; + +const optionsMenu = { + button: '[data-testid=account-options-menu-button]', + accountDetailsButton: '[data-testid="account-options-menu__account-details"]', + connectedSitesButton: '[data-testid="account-options-menu__connected-sites"]', +}; + +const whatsNew = { + header: '[data-testid="whats_new-header"]', + continueButton: '[data-testid="whats_new-continue_button"]', +}; + +const accountBar = { + title: '[data-testid="tooltip_interactive-wrapper"]', + ethRow: '[data-testid="account-header-chain-eip155:1"]', +}; + +const connectedSitesSelector = '.connected-sites'; +const connectedSites = { + modal: connectedSitesSelector, + disconnectLabel: `${connectedSitesSelector} .connected-sites-list__content-row-link-button`, + cancelButton: `${connectedSitesSelector} .btn-secondary`, + disconnectButton: `${connectedSitesSelector} .btn-primary`, + closeButton: `${connectedSitesSelector} [data-testid="popover-close"]`, +}; + +const accountModal = { + walletAddressInput: '.account-modal .qr-code__address', + closeButton: '.account-modal__close', +}; + +const importAccountSelector = '.new-account'; +const importAccount = { + page: importAccountSelector, + input: `${importAccountSelector} #private-key-box`, + cancelButton: `${importAccountSelector} .new-account-create-form__button:nth-child(1)`, + importButton: `${importAccountSelector} .new-account-create-form__button:nth-child(2)`, +}; + +const createAccount = { + page: importAccountSelector, + input: `${importAccountSelector} .new-account-create-form__input`, + cancelButton: `${importAccountSelector} .new-account-create-form__button:nth-child(1)`, + createButton: `${importAccountSelector} .new-account-create-form__button:nth-child(2)`, +}; + +const importTokenFormSelector = '.import-token__custom-token-form'; +const importToken = { + form: importTokenFormSelector, + tokenContractAddressInput: `${importTokenFormSelector} #custom-address`, + tokenSymbolInput: `${importTokenFormSelector} #custom-symbol`, + tokenEditButton: `${importTokenFormSelector} .import-token__custom-symbol__edit`, + tokenDecimalInput: `${importTokenFormSelector} #custom-decimals`, + addCustomTokenButton: `[data-testid="page-container-footer-next"]`, + confirmImportTokenContent: '.confirm-import-token', + importTokensButton: `.btn-primary`, +}; + +const assetNavigationSelector = '.asset-navigation'; +const asset = { + navigation: assetNavigationSelector, + backButton: `${assetNavigationSelector} [data-testid="asset__back"]`, +}; + +module.exports.mainPageElements = { + networkSwitcher, + walletOverview, + tabs, + activityTab, + popup, + tippyTooltip, + actionableMessage, + accountMenu, + accountBar, + optionsMenu, + connectedSites, + accountModal, + importAccount, + createAccount, + importToken, + asset, + whatsNew, +}; diff --git a/pages/phantom/notification-page.js b/pages/phantom/notification-page.js new file mode 100644 index 000000000..7844b44c6 --- /dev/null +++ b/pages/phantom/notification-page.js @@ -0,0 +1,146 @@ +const notificationPage = '.notification'; +const notificationAppContent = `${notificationPage} #app-content .app`; +const loadingLogo = `${notificationPage} #loading__logo`; +const loadingSpinner = `${notificationPage} #loading__spinner`; +const nextButton = `${notificationPage} .permissions-connect-choose-account__bottom-buttons .btn-primary`; +const allowToSpendButton = `${notificationPage} [data-testid="page-container-footer-next"]`; +const rejectToSpendButton = `${notificationPage} [data-testid="page-container-footer-cancel"]`; +const selectAllCheckbox = `${notificationPage} .choose-account-list__header-check-box`; +module.exports.notificationPageElements = { + notificationPage, + notificationAppContent, + loadingLogo, + loadingSpinner, + nextButton, + allowToSpendButton, + rejectToSpendButton, + selectAllCheckbox, +}; + +const confirmSignatureRequestButton = `${notificationPage} .request-signature__footer__sign-button`; +const rejectSignatureRequestButton = `${notificationPage} .request-signature__footer__cancel-button`; +const signatureRequestScrollDownButton = `${notificationPage} [data-testid="signature-request-scroll-button"]`; +module.exports.signaturePageElements = { + confirmSignatureRequestButton, + rejectSignatureRequestButton, + signatureRequestScrollDownButton, +}; + +const confirmDataSignatureRequestButton = `${notificationPage} [data-testid="signature-sign-button"]`; +const rejectDataSignatureRequestButton = `${notificationPage} [data-testid="signature-cancel-button"]`; +module.exports.dataSignaturePageElements = { + confirmDataSignatureRequestButton, + rejectDataSignatureRequestButton, + signatureRequestScrollDownButton, +}; + +const permissionsPage = '.permissions-connect'; +const connectButton = `${permissionsPage} .permission-approval-container__footers .btn-primary`; +module.exports.permissionsPageElements = { + permissionsPage, + connectButton, +}; + +const popupContainer = '.popover-container'; +const popupCloseButton = `${popupContainer} .popover-header__button`; +const popupCopyRecipientPublicAddressButton = `${popupContainer} .nickname-popover__public-address button`; +const recipientPublicAddress = `${popupContainer} .nickname-popover__public-address__constant`; +module.exports.recipientPopupElements = { + popupContainer, + popupCloseButton, + popupCopyRecipientPublicAddressButton, + recipientPublicAddress, +}; + +const confirmPageHeader = `${notificationPage} .confirm-page-container-header`; +const confirmPageContent = `${notificationPage} .confirm-page-container-content`; +const networkLabel = `${confirmPageHeader} .network-display`; +const senderButton = `${confirmPageHeader} .sender-to-recipient__party--sender`; +const recipientButton = `${confirmPageHeader} .sender-to-recipient__party--recipient-with-address`; +const editGasFeeLegacyButton = `${notificationPage} .transaction-detail-edit button`; +const editGasFeeLegacyOverrideAckButton = `${notificationPage} .edit-gas-display .edit-gas-display__dapp-acknowledgement-button`; +const editGasLegacyPopup = `${notificationPage} .edit-gas-popover__wrapper`; +const advancedLegacyGasControls = `${editGasLegacyPopup} .edit-gas-display .advanced-gas-controls`; +const gasLimitLegacyInput = `${advancedLegacyGasControls} .form-field:nth-child(1) input`; +const gasPriceLegacyInput = `${advancedLegacyGasControls} .form-field:nth-child(2) input`; +const saveLegacyButton = `${editGasLegacyPopup} .popover-footer .btn-primary`; +const editGasFeeButton = `${notificationPage} [data-testid="edit-gas-fee-button"]`; +const gasOptionLowButton = `${notificationPage} [data-testid="edit-gas-fee-item-low"]`; +const gasOptionMediumButton = `${notificationPage} [data-testid="edit-gas-fee-item-medium"]`; +const gasOptionHighButton = `${notificationPage} [data-testid="edit-gas-fee-item-high"]`; +const gasOptionDappSuggestedButton = `${notificationPage} [data-testid="edit-gas-fee-item-dappSuggested"]`; +const gasOptionCustomButton = `${notificationPage} [data-testid="edit-gas-fee-item-custom"]`; +const baseFeeInput = `${notificationPage} [data-testid="base-fee-input"]`; +const priorityFeeInput = `${notificationPage} [data-testid="priority-fee-input"]`; +const editGasLimitButton = `${notificationPage} [data-testid="advanced-gas-fee-edit"]`; +const gasLimitInput = `${notificationPage} [data-testid="gas-limit-input"]`; +const saveCustomGasFeeButton = `${notificationPage} .popover-container .btn-primary`; +const totalLabel = `${confirmPageContent} .transaction-detail-item:nth-child(2) .transaction-detail-item__detail-values h6:nth-child(2)`; // todo: fix +const customNonceInput = `${confirmPageContent} .custom-nonce-input input`; +const tabs = `${confirmPageContent} .tabs`; +const detailsButton = `${tabs} .tab:nth-child(1) button`; +const dataButton = `${tabs} .tab:nth-child(2) button`; +const dataTab = `${tabs} .confirm-page-container-content__data`; +const originValue = `${dataTab} .confirm-page-container-content__data-box:nth-child(1) .confirm-page-container-content__data-field:nth-child(1) div:nth-child(2)`; +const bytesValue = `${dataTab} .confirm-page-container-content__data-box:nth-child(1) .confirm-page-container-content__data-field:nth-child(2) div:nth-child(2)`; +const hexDataValue = `${dataTab} .confirm-page-container-content__data-box:nth-child(3)`; +const rejectButton = `${confirmPageContent} [data-testid="page-container-footer-cancel"]`; +const confirmButton = `${confirmPageContent} [data-testid="page-container-footer-next"]`; +module.exports.confirmPageElements = { + notificationPage, + confirmPageHeader, + confirmPageContent, + networkLabel, + senderButton, + recipientButton, + editGasFeeLegacyButton, + editGasFeeLegacyOverrideAckButton, + editGasLegacyPopup, + advancedLegacyGasControls, + gasLimitLegacyInput, + gasPriceLegacyInput, + saveLegacyButton, + editGasFeeButton, + gasOptionLowButton, + gasOptionMediumButton, + gasOptionHighButton, + gasOptionDappSuggestedButton, + gasOptionCustomButton, + baseFeeInput, + priorityFeeInput, + editGasLimitButton, + gasLimitInput, + saveCustomGasFeeButton, + totalLabel, + customNonceInput, + tabs, + detailsButton, + dataButton, + dataTab, + originValue, + bytesValue, + hexDataValue, + rejectButton, + confirmButton, +}; + +const confirmEncryptionPublicKeyButton = `${notificationPage} .request-encryption-public-key__footer__sign-button`; +const rejectEncryptionPublicKeyButton = `${notificationPage} .request-encryption-public-key__footer__cancel-button`; +module.exports.encryptionPublicKeyPageElements = { + confirmEncryptionPublicKeyButton, + rejectEncryptionPublicKeyButton, +}; + +const confirmDecryptionRequestButton = `${notificationPage} .request-decrypt-message__footer__sign-button`; +const rejectDecryptionRequestButton = `${notificationPage} .request-decrypt-message__footer__cancel-button`; +module.exports.decryptPageElements = { + confirmDecryptionRequestButton, + rejectDecryptionRequestButton, +}; + +const confirmAddTokenButton = `${notificationPage} .btn-primary`; +const rejectAddTokenButton = `${notificationPage} .btn-secondary`; +module.exports.addTokenPageElements = { + confirmAddTokenButton, + rejectAddTokenButton, +}; diff --git a/pages/phantom/page.js b/pages/phantom/page.js new file mode 100644 index 000000000..2a5e4654b --- /dev/null +++ b/pages/phantom/page.js @@ -0,0 +1,16 @@ +const loadingLogo = '.loading-logo'; +const loadingSpinner = '.loading-spinner'; +const loadingOverlay = '.loading-overlay'; +const loadingOverlaySpinner = '.loading-overlay__spinner'; +const loadingOverlayErrorButtons = '.loading-overlay__error-buttons'; +const loadingOverlayErrorButtonsRetryButton = + '.loading-overlay__error-buttons .btn-primary'; + +module.exports.pageElements = { + loadingLogo, + loadingSpinner, + loadingOverlay, + loadingOverlaySpinner, + loadingOverlayErrorButtons, + loadingOverlayErrorButtonsRetryButton, +}; diff --git a/pages/phantom/settings-page.js b/pages/phantom/settings-page.js new file mode 100644 index 000000000..bcaee5b88 --- /dev/null +++ b/pages/phantom/settings-page.js @@ -0,0 +1,94 @@ +const settingsPage = '.settings-page'; +const advancedButton = `${settingsPage} button:nth-child(2)`; +const networksButton = `${settingsPage} button:nth-child(6)`; +const closeButton = `${settingsPage} .settings-page__header__title-container__close-button`; +module.exports.settingsPageElements = { + settingsPage, + advancedButton, + networksButton, + closeButton, +}; + +const resetAccountButton = + '[data-testid="advanced-setting-reset-account"] button'; +const advancedGasControlToggleOn = + '[data-testid="advanced-setting-advanced-gas-inline"] .toggle-button--on'; +const advancedGasControlToggleOff = + '[data-testid="advanced-setting-advanced-gas-inline"] .toggle-button--off'; +const enhancedTokenDetectionToggleOn = + '[data-testid="advanced-setting-token-detection"] .toggle-button--on'; +const enhancedTokenDetectionToggleOff = + '[data-testid="advanced-setting-token-detection"] .toggle-button--off'; +const showHexDataToggleOn = + '[data-testid="advanced-setting-hex-data"] .toggle-button--on'; +const showHexDataToggleOff = + '[data-testid="advanced-setting-hex-data"] .toggle-button--off'; +const showTestnetConversionOn = + '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(7) .toggle-button--on'; +const showTestnetConversionOff = + '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(7) .toggle-button--off'; +const showTestnetNetworksOn = + '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(8) .toggle-button--on'; +const showTestnetNetworksOff = + '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(8) .toggle-button--off'; +const customNonceToggleOn = + '[data-testid="advanced-setting-custom-nonce"] .toggle-button--on'; +const customNonceToggleOff = + '[data-testid="advanced-setting-custom-nonce"] .toggle-button--off'; +const dismissBackupReminderOn = + '[data-testid="advanced-setting-dismiss-reminder"] .toggle-button--on'; +const dismissBackupReminderOff = + '[data-testid="advanced-setting-dismiss-reminder"] .toggle-button--off'; +module.exports.advancedPageElements = { + resetAccountButton, + advancedGasControlToggleOn, + advancedGasControlToggleOff, + enhancedTokenDetectionToggleOn, + enhancedTokenDetectionToggleOff, + showHexDataToggleOn, + showHexDataToggleOff, + showTestnetConversionOn, + showTestnetConversionOff, + showTestnetNetworksOn, + showTestnetNetworksOff, + dismissBackupReminderOn, + dismissBackupReminderOff, + customNonceToggleOn, + customNonceToggleOff, +}; + +const enhancedGasFeeUIToggleOn = + '.settings-page__content-row:nth-child(1) .toggle-button--on'; +const enhancedGasFeeUIToggleOff = + '.settings-page__content-row:nth-child(1) .toggle-button--off'; +module.exports.experimentalSettingsPageElements = { + enhancedGasFeeUIToggleOn, + enhancedGasFeeUIToggleOff, +}; + +const nevermindButton = '.modal-container .btn-secondary'; +const resetButton = '.modal-container .btn-danger-primary'; +module.exports.resetAccountModalElements = { + nevermindButton, + resetButton, +}; + +const addNetworkButton = '.networks-tab__body button'; +module.exports.networksPageElements = { addNetworkButton }; + +const addNetworkForm = '.networks-tab__add-network-form-body'; +const networkNameInput = `${addNetworkForm} .form-field:nth-child(1) input`; +const rpcUrlInput = `${addNetworkForm} .form-field:nth-child(2) input`; +const chainIdInput = `${addNetworkForm} .form-field:nth-child(3) input`; +const symbolInput = `${addNetworkForm} .form-field:nth-child(4) input`; +const blockExplorerInput = `${addNetworkForm} .form-field:nth-child(5) input`; +const saveButton = '.networks-tab__add-network-form-footer .btn-primary'; +module.exports.addNetworkPageElements = { + addNetworkForm, + networkNameInput, + rpcUrlInput, + chainIdInput, + symbolInput, + blockExplorerInput, + saveButton, +}; diff --git a/pages/phantom/unlock-page.js b/pages/phantom/unlock-page.js new file mode 100644 index 000000000..69e65f4ab --- /dev/null +++ b/pages/phantom/unlock-page.js @@ -0,0 +1,9 @@ +const unlockPage = '[data-testid="unlock-page"]'; +const passwordInput = `${unlockPage} #password`; +const unlockButton = `${unlockPage} .btn-default`; + +module.exports.unlockPageElements = { + unlockPage, + passwordInput, + unlockButton, +}; diff --git a/plugins/index.js b/plugins/index.js index fc1d70371..4bb8622ab 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -29,6 +29,20 @@ module.exports = (on, config) => { if (browser.isHeadless) { arguments_.args.push('--window-size=1920,1080'); } + arguments_.preferences.default.profile = { + content_settings: { + exceptions: { + clipboard: { + '*': { + expiration: '0', + last_modified: '13248200230459161', + model: 0, + setting: 1, + }, + }, + }, + }, + }; } if (!process.env.SKIP_METAMASK_INSTALL) { From cca57ccdbab685820310b71028322414e52641cf Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Thu, 9 Feb 2023 16:39:08 +0100 Subject: [PATCH 02/14] chore: Add phantom selectors --- commands/phantom.js | 1079 +++------------------------- commands/playwright.js | 42 +- commands/synthetix.js | 5 +- pages/phantom/notification-page.js | 45 ++ pages/phantom/settings-page.js | 95 +-- pages/phantom/unlock-page.js | 9 +- plugins/index.js | 152 ++-- support/commands.js | 65 +- 8 files changed, 329 insertions(+), 1163 deletions(-) diff --git a/commands/phantom.js b/commands/phantom.js index 13e5792d6..c5d710bf7 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -22,6 +22,10 @@ const { dataSignaturePageElements, recipientPopupElements, addTokenPageElements, + buttons, + transactionPageElements, + menu, + incorrectModePageElements, } = require('../pages/phantom/notification-page'); const { settingsPageElements, @@ -127,97 +131,9 @@ module.exports = { fixBlankPage: async () => { return; }, - confirmWelcomePage: async () => { - await module.exports.fixBlankPage(); - await playwright.waitAndClick( - welcomePageElements.confirmButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - return true; - }, - closePopupAndTooltips: async () => { - // note: this is required for fast execution of e2e tests to avoid flakiness - // otherwise popup may not be detected properly and not closed - try { - if ( - await playwright - .metamaskWindow() - .locator(mainPageElements.popup.container) - .isVisible() - ) { - const popupBackground = playwright - .metamaskWindow() - .locator(mainPageElements.popup.background); - const popupBackgroundBox = await popupBackground.boundingBox(); - await playwright - .metamaskWindow() - .mouse.click(popupBackgroundBox.x + 1, popupBackgroundBox.y + 1); - } - if ( - await playwright - .metamaskWindow() - .locator(mainPageElements.tippyTooltip.closeButton) - .isVisible() - ) { - await playwright.waitAndClick( - mainPageElements.tippyTooltip.closeButton, - ); - } - if ( - await playwright - .metamaskWindow() - .locator(mainPageElements.actionableMessage.closeButton) - .isVisible() - ) { - await playwright.waitAndClick( - mainPageElements.actionableMessage.closeButton, - ); - } - } catch (ex) { - return true; - } - return true; - }, - closeModal: async () => { - // note: this is required for fast execution of e2e tests to avoid flakiness - // otherwise modal may not be detected properly and not closed - await playwright.metamaskWindow().waitForTimeout(1000); - if ( - await playwright - .metamaskWindow() - .locator(mainPageElements.connectedSites.modal) - .isVisible() - ) { - await playwright.waitAndClick( - mainPageElements.connectedSites.closeButton, - ); - } - return true; - }, - unlock: async password => { - await module.exports.fixBlankPage(); - await playwright.waitAndType(unlockPageElements.passwordInput, password); - await playwright.waitAndClick( - unlockPageElements.unlockButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); - return true; - }, - optOutAnalytics: async () => { - await playwright.waitAndClick( - metametricsPageElements.optOutAnalyticsButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); + acceptAccess: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick(buttons.primaryButton, notificationPage); return true; }, importWallet: async (secretWords, password) => { @@ -271,855 +187,47 @@ module.exports = { ); await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast - await module.exports.closePopupAndTooltips(); - return true; - }, - createWallet: async password => { - await module.exports.optOutAnalytics(); - await playwright.waitAndClick( - firstTimeFlowPageElements.createWalletButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await playwright.waitAndType( - firstTimeFlowCreatePagePageElements.newPasswordInput, - password, - ); - await playwright.waitAndType( - firstTimeFlowCreatePagePageElements.confirmNewPasswordInput, - password, - ); - await playwright.waitAndClick( - firstTimeFlowCreatePagePageElements.newSignupCheckbox, - ); - await playwright.waitAndClick( - firstTimeFlowCreatePagePageElements.createButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await playwright.waitAndClick( - secureYourWalletPageElements.nextButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await playwright.waitAndClick( - revealSeedPageElements.remindLaterButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); + // await module.exports.closePopupAndTooltips(); return true; }, - importAccount: async privateKey => { - await switchToPhantomIfNotActive(); - await module.exports.goToImportAccount(); - await playwright.waitAndType( - mainPageElements.importAccount.input, - privateKey, - ); - await playwright.waitAndClick( - mainPageElements.importAccount.importButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); - await switchToCypressIfNotActive(); - return true; - }, - createAccount: async accountName => { - if (accountName) { - accountName = accountName.toLowerCase(); - } - await switchToPhantomIfNotActive(); - await module.exports.goToNewAccount(); - if (accountName) { - await playwright.waitAndType( - mainPageElements.createAccount.input, - accountName, - ); - } - await playwright.waitAndClick(mainPageElements.createAccount.createButton); - await module.exports.closePopupAndTooltips(); - await switchToCypressIfNotActive(); - return true; - }, - switchAccount: async accountNameOrAccountNumber => { - if (typeof accountNameOrAccountNumber === 'string') { - accountNameOrAccountNumber = accountNameOrAccountNumber.toLowerCase(); - } - await switchToPhantomIfNotActive(); - // note: closePopupAndTooltips() is required after changing createAccount() to use direct urls (popup started appearing) - // ^ this change also introduced 500ms delay for closePopupAndTooltips() function - await module.exports.closePopupAndTooltips(); - await playwright.waitAndClick(mainPageElements.accountMenu.button); - if (typeof accountNameOrAccountNumber === 'number') { - await playwright.waitAndClick( - mainPageElements.accountMenu.accountButton(accountNameOrAccountNumber), - ); - } else { - await playwright.waitAndClickByText( - mainPageElements.accountMenu.accountName, - accountNameOrAccountNumber, - ); - } - await module.exports.closePopupAndTooltips(); - await switchToCypressIfNotActive(); - return true; - }, - changeNetwork: async network => { - await switchToPhantomIfNotActive(); - await playwright.waitAndClick(mainPageElements.networkSwitcher.button); - if (typeof network === 'string') { - network = network.toLowerCase(); - if (network === 'mainnet') { - await playwright.waitAndClick( - mainPageElements.networkSwitcher.mainnetNetworkItem, - ); - } else if (network === 'goerli') { - await playwright.waitAndClick( - mainPageElements.networkSwitcher.goerliNetworkItem, - ); - } else if (network === 'sepolia') { - await playwright.waitAndClick( - mainPageElements.networkSwitcher.sepoliaNetworkItem, - ); - } else if (network === 'localhost') { - await playwright.waitAndClick( - mainPageElements.networkSwitcher.localhostNetworkItem, - ); - } else { - await playwright.waitAndClickByText( - mainPageElements.networkSwitcher.dropdownMenuItem, - network, - ); - } - await playwright.waitForText( - mainPageElements.networkSwitcher.networkName, - network, - ); - } else if (typeof network === 'object') { - network.networkName = network.networkName.toLowerCase(); - await playwright.waitAndClickByText( - mainPageElements.networkSwitcher.dropdownMenuItem, - network.networkName, - ); - await playwright.waitForText( - mainPageElements.networkSwitcher.networkName, - network.networkName, - ); - } - await module.exports.closePopupAndTooltips(); - await setNetwork(network); - await switchToCypressIfNotActive(); - return true; - }, - addNetwork: async network => { - await switchToPhantomIfNotActive(); - if ( - process.env.NETWORK_NAME && - process.env.RPC_URL && - process.env.CHAIN_ID - ) { - network = { - networkName: process.env.NETWORK_NAME, - rpcUrl: process.env.RPC_URL, - chainId: process.env.CHAIN_ID, - symbol: process.env.SYMBOL, - blockExplorer: process.env.BLOCK_EXPLORER, - isTestnet: process.env.IS_TESTNET, - }; - } - if (typeof network === 'string') { - network = network.toLowerCase(); - } else if (typeof network === 'object') { - network.networkName = network.networkName.toLowerCase(); - } - await module.exports.goToAddNetwork(); - await playwright.waitAndType( - addNetworkPageElements.networkNameInput, - network.networkName, - ); - await playwright.waitAndType( - addNetworkPageElements.rpcUrlInput, - network.rpcUrl, - ); - await playwright.waitAndType( - addNetworkPageElements.chainIdInput, - network.chainId, - ); - if (network.symbol) { - await playwright.waitAndType( - addNetworkPageElements.symbolInput, - network.symbol, - ); - } - if (network.blockExplorer) { - await playwright.waitAndType( - addNetworkPageElements.blockExplorerInput, - network.blockExplorer, - ); - } - await playwright.waitAndClick( - addNetworkPageElements.saveButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); - await setNetwork(network); - await playwright.waitForText( - mainPageElements.networkSwitcher.networkName, - network.networkName, - ); - await switchToCypressIfNotActive(); - return true; - }, - async disconnectWalletFromDapp() { - await switchToPhantomIfNotActive(); - await playwright.waitAndClick(mainPageElements.optionsMenu.button); - await playwright.waitAndClick( - mainPageElements.optionsMenu.connectedSitesButton, - ); + closePopupAndTooltips: async () => { + // note: this is required for fast execution of e2e tests to avoid flakiness + // otherwise popup may not be detected properly and not closed + await playwright.metamaskWindow().waitForTimeout(1000); if ( await playwright .metamaskWindow() - .locator(mainPageElements.connectedSites.disconnectLabel) + .locator(mainPageElements.popup.container) .isVisible() ) { - console.log( - '[disconnectWalletFromDapp] Wallet is connected to a dapp, disconnecting..', - ); - await playwright.waitAndClick( - mainPageElements.connectedSites.disconnectLabel, - ); - await playwright.waitAndClick( - mainPageElements.connectedSites.disconnectButton, - ); - } else { - console.log( - '[disconnectWalletFromDapp] Wallet is not connected to a dapp, skipping..', - ); - } - await module.exports.closeModal(); - await switchToCypressIfNotActive(); - return true; - }, - async disconnectWalletFromAllDapps() { - await switchToPhantomIfNotActive(); - await playwright.waitAndClick(mainPageElements.optionsMenu.button); - await playwright.waitAndClick( - mainPageElements.optionsMenu.connectedSitesButton, - ); - const disconnectLabels = await playwright - .metamaskWindow() - .$$(mainPageElements.connectedSites.disconnectLabel); - if (disconnectLabels.length) { - console.log( - '[disconnectWalletFromAllDapps] Wallet is connected to dapps, disconnecting..', - ); - // eslint-disable-next-line no-unused-vars - for (const disconnectLabel of disconnectLabels) { - await playwright.waitAndClick( - mainPageElements.connectedSites.disconnectLabel, - ); - await playwright.waitAndClick( - mainPageElements.connectedSites.disconnectButton, - ); - } - } else { - console.log( - '[disconnectWalletFromAllDapps] Wallet is not connected to any dapps, skipping..', - ); - } - await module.exports.closeModal(); - await switchToCypressIfNotActive(); - return true; - }, - activateAdvancedGasControl: async skipSetup => { - return await activateAdvancedSetting( - advancedPageElements.advancedGasControlToggleOn, - advancedPageElements.advancedGasControlToggleOff, - skipSetup, - ); - }, - activateEnhancedTokenDetection: async skipSetup => { - return await activateAdvancedSetting( - advancedPageElements.enhancedTokenDetectionToggleOn, - advancedPageElements.enhancedTokenDetectionToggleOff, - skipSetup, - ); - }, - activateShowHexData: async skipSetup => { - return await activateAdvancedSetting( - advancedPageElements.showHexDataToggleOn, - advancedPageElements.showHexDataToggleOff, - skipSetup, - ); - }, - activateTestnetConversion: async skipSetup => { - return await activateAdvancedSetting( - advancedPageElements.showTestnetConversionOn, - advancedPageElements.showTestnetConversionOff, - skipSetup, - ); - }, - activateShowTestnetNetworks: async skipSetup => { - return await activateAdvancedSetting( - advancedPageElements.showTestnetNetworksOn, - advancedPageElements.showTestnetNetworksOff, - skipSetup, - ); - }, - activateCustomNonce: async skipSetup => { - return await activateAdvancedSetting( - advancedPageElements.customNonceToggleOn, - advancedPageElements.customNonceToggleOff, - skipSetup, - ); - }, - activateDismissBackupReminder: async skipSetup => { - return await activateAdvancedSetting( - advancedPageElements.dismissBackupReminderOn, - advancedPageElements.dismissBackupReminderOff, - skipSetup, - ); - }, - activateEnhancedGasFeeUI: async skipSetup => { - return await activateAdvancedSetting( - experimentalSettingsPageElements.enhancedGasFeeUIToggleOn, - experimentalSettingsPageElements.enhancedGasFeeUIToggleOff, - skipSetup, - true, - ); - }, - activateShowCustomNetworkList: async skipSetup => { - return await activateAdvancedSetting( - experimentalSettingsPageElements.showCustomNetworkListToggleOn, - experimentalSettingsPageElements.showCustomNetworkListToggleOff, - skipSetup, - true, - ); - }, - resetAccount: async () => { - await switchToPhantomIfNotActive(); - await module.exports.goToAdvancedSettings(); - await playwright.waitAndClick(advancedPageElements.resetAccountButton); - await playwright.waitAndClick(resetAccountModalElements.resetButton); - await playwright.waitAndClick( - settingsPageElements.closeButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); - await switchToCypressIfNotActive(); - return true; - }, - confirmSignatureRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - if ( - await playwright - .metamaskNotificationWindow() - .locator(signaturePageElements.signatureRequestScrollDownButton) - .isVisible() - ) { - await playwright.waitAndClick( - signaturePageElements.signatureRequestScrollDownButton, - notificationPage, - ); - } - await playwright.waitAndClick( - signaturePageElements.confirmSignatureRequestButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - rejectSignatureRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - signaturePageElements.rejectSignatureRequestButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - confirmDataSignatureRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - if ( + const popupBackground = playwright + .metamaskWindow() + .locator(mainPageElements.popup.background); + const popupBackgroundBox = await popupBackground.boundingBox(); await playwright - .metamaskNotificationWindow() - .locator(signaturePageElements.signatureRequestScrollDownButton) - .isVisible() - ) { - await playwright.waitAndClick( - signaturePageElements.signatureRequestScrollDownButton, - notificationPage, - ); - } - await playwright.waitAndClick( - dataSignaturePageElements.confirmDataSignatureRequestButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - rejectDataSignatureRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - dataSignaturePageElements.rejectDataSignatureRequestButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - importToken: async tokenConfig => { - let tokenData = {}; - await switchToPhantomIfNotActive(); - await module.exports.goToImportToken(); - if (typeof tokenConfig === 'string') { - await playwright.waitAndType( - mainPageElements.importToken.tokenContractAddressInput, - tokenConfig, - ); - tokenData.tokenContractAddress = tokenConfig; - tokenData.tokenSymbol = await playwright.waitAndGetInputValue( - mainPageElements.importToken.tokenSymbolInput, - ); - } else { - await playwright.waitAndType( - mainPageElements.importToken.tokenContractAddressInput, - tokenConfig.address, - ); - tokenData.tokenContractAddress = tokenConfig.address; - await playwright.waitAndClick( - mainPageElements.importToken.tokenEditButton, - await playwright.metamaskWindow(), - { - force: true, - }, - ); - await playwright.waitClearAndType( - tokenConfig.symbol, - mainPageElements.importToken.tokenSymbolInput, - ); - tokenData.tokenSymbol = tokenConfig.symbol; - } - tokenData.tokenDecimals = await playwright.waitAndGetInputValue( - mainPageElements.importToken.tokenDecimalInput, - ); - await playwright.waitAndClick( - mainPageElements.importToken.addCustomTokenButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await playwright.waitAndClick( - mainPageElements.importToken.importTokensButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await playwright.waitAndClick( - mainPageElements.asset.backButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); - await switchToCypressIfNotActive(); - tokenData.imported = true; - return tokenData; - }, - confirmAddToken: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - addTokenPageElements.confirmAddTokenButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - rejectAddToken: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - addTokenPageElements.rejectAddTokenButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - confirmPermissionToSpend: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - notificationPageElements.allowToSpendButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - rejectPermissionToSpend: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - notificationPageElements.rejectToSpendButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - acceptAccess: async options => { - const notificationPage = await playwright.switchToMetamaskNotification(); - if (options && options.allAccounts) { - await playwright.waitAndClick( - notificationPageElements.selectAllCheckbox, - notificationPage, - ); - } - await playwright.waitAndClick( - notificationPageElements.nextButton, - notificationPage, - { waitForEvent: 'navi' }, - ); - if (options && options.signInSignature) { - await playwright.waitAndClick( - permissionsPageElements.connectButton, - notificationPage, - { waitForEvent: 'navi' }, - ); - await module.exports.confirmSignatureRequest(); - } else { - await playwright.waitAndClick( - permissionsPageElements.connectButton, - notificationPage, - { waitForEvent: 'close' }, - ); - } - return true; - }, - confirmTransaction: async gasConfig => { - let txData = {}; - const notificationPage = await playwright.switchToMetamaskNotification(); - if (gasConfig) { - log( - '[confirmTransaction] gasConfig is present, determining transaction type..', - ); - if ( - await playwright - .metamaskNotificationWindow() - .locator(confirmPageElements.editGasFeeLegacyButton) - .isVisible() - ) { - log('[confirmTransaction] Looks like legacy tx'); - if (typeof gasConfig === 'object') { - log('[confirmTransaction] Editing legacy tx..'); - await playwright.waitAndClick( - confirmPageElements.editGasFeeLegacyButton, - notificationPage, - ); - if ( - await playwright - .metamaskNotificationWindow() - .locator(confirmPageElements.editGasFeeLegacyOverrideAckButton) - .isVisible() - ) { - log( - '[confirmTransaction] Override acknowledgement modal is present, closing..', - ); - await playwright.waitAndClick( - confirmPageElements.editGasFeeLegacyOverrideAckButton, - notificationPage, - ); - } - if (gasConfig.gasLimit) { - log('[confirmTransaction] Changing gas limit..'); - await playwright.waitAndSetValue( - gasConfig.gasLimit.toString(), - confirmPageElements.gasLimitLegacyInput, - notificationPage, - ); - } - if (gasConfig.gasPrice) { - log('[confirmTransaction] Changing gas price..'); - await playwright.waitAndSetValue( - gasConfig.gasPrice.toString(), - confirmPageElements.gasPriceLegacyInput, - notificationPage, - ); - } - await playwright.waitAndClick( - confirmPageElements.saveCustomGasFeeButton, - notificationPage, - ); - } else { - log( - "[confirmTransaction] Legacy tx doesn't support eip-1559 fees (low, market, aggressive, site), using default values..", - ); - } - } else { - log('[confirmTransaction] Looks like eip-1559 tx'); - await playwright.waitAndClick( - confirmPageElements.editGasFeeButton, - notificationPage, - ); - if (typeof gasConfig === 'string') { - if (gasConfig === 'low') { - log('[confirmTransaction] Changing gas fee to low..'); - await playwright.waitAndClick( - confirmPageElements.gasOptionLowButton, - notificationPage, - ); - } else if (gasConfig === 'market') { - log('[confirmTransaction] Changing gas fee to market..'); - await playwright.waitAndClick( - confirmPageElements.gasOptionMediumButton, - notificationPage, - ); - } else if (gasConfig === 'aggressive') { - log('[confirmTransaction] Changing gas fee to aggressive..'); - await playwright.waitAndClick( - confirmPageElements.gasOptionHighButton, - notificationPage, - ); - } else if (gasConfig === 'site') { - log('[confirmTransaction] Changing gas fee to site suggested..'); - await playwright.waitAndClick( - confirmPageElements.gasOptionDappSuggestedButton, - notificationPage, - ); - } - } else { - log('[confirmTransaction] Editing eip-1559 tx..'); - await playwright.waitAndClick( - confirmPageElements.gasOptionCustomButton, - notificationPage, - ); - if (gasConfig.gasLimit) { - log('[confirmTransaction] Changing gas limit..'); - await playwright.waitAndClick( - confirmPageElements.editGasLimitButton, - notificationPage, - ); - await playwright.waitAndSetValue( - gasConfig.gasLimit.toString(), - confirmPageElements.gasLimitInput, - notificationPage, - ); - } - if (gasConfig.baseFee) { - log('[confirmTransaction] Changing base fee..'); - await playwright.waitAndSetValue( - gasConfig.baseFee.toString(), - confirmPageElements.baseFeeInput, - notificationPage, - ); - } - if (gasConfig.priorityFee) { - log('[confirmTransaction] Changing priority fee..'); - await playwright.waitAndSetValue( - gasConfig.priorityFee.toString(), - confirmPageElements.priorityFeeInput, - notificationPage, - ); - } - await playwright.waitAndClick( - confirmPageElements.saveCustomGasFeeButton, - notificationPage, - ); - } - } + .metamaskWindow() + .mouse.click(popupBackgroundBox.x + 1, popupBackgroundBox.y + 1); } - log('[confirmTransaction] Checking if recipient address is present..'); if ( await playwright - .metamaskNotificationWindow() - .locator(confirmPageElements.recipientButton) + .metamaskWindow() + .locator(mainPageElements.tippyTooltip.closeButton) .isVisible() ) { - log('[confirmTransaction] Getting recipient address..'); - await playwright.waitAndClick( - confirmPageElements.recipientButton, - notificationPage, - ); - txData.recipientPublicAddress = await playwright.waitAndGetValue( - recipientPopupElements.recipientPublicAddress, - notificationPage, - ); - await playwright.waitAndClick( - recipientPopupElements.popupCloseButton, - notificationPage, - ); + await playwright.waitAndClick(mainPageElements.tippyTooltip.closeButton); } - log('[confirmTransaction] Checking if network name is present..'); if ( await playwright - .metamaskNotificationWindow() - .locator(confirmPageElements.networkLabel) + .metamaskWindow() + .locator(mainPageElements.actionableMessage.closeButton) .isVisible() ) { - log('[confirmTransaction] Getting network name..'); - txData.networkName = await playwright.waitAndGetValue( - confirmPageElements.networkLabel, - notificationPage, - ); - } - // todo: handle setting of custom nonce here - log('[confirmTransaction] Getting transaction nonce..'); - txData.customNonce = await playwright.waitAndGetAttributeValue( - confirmPageElements.customNonceInput, - 'placeholder', - notificationPage, - ); - // todo: fix getting tx data on function multicall - // log('[confirmTransaction] Checking if tx data is present..'); - // if ( - // await playwright - // .metamaskNotificationWindow() - // .locator(confirmPageElements.dataButton) - // .isVisible() - // ) { - // log('[confirmTransaction] Fetching tx data..'); - // await playwright.waitAndClick( - // confirmPageElements.dataButton, - // notificationPage, - // ); - // log('[confirmTransaction] Getting origin value..'); - // txData.origin = await playwright.waitAndGetValue( - // confirmPageElements.originValue, - // notificationPage, - // ); - // log('[confirmTransaction] Getting bytes value..'); - // txData.bytes = await playwright.waitAndGetValue( - // confirmPageElements.bytesValue, - // notificationPage, - // ); - // log('[confirmTransaction] Getting hex data value..'); - // txData.hexData = await playwright.waitAndGetValue( - // confirmPageElements.hexDataValue, - // notificationPage, - // ); - // await playwright.waitAndClick( - // confirmPageElements.detailsButton, - // notificationPage, - // ); - // } - log('[confirmTransaction] Confirming transaction..'); - await playwright.waitAndClick( - confirmPageElements.confirmButton, - notificationPage, - { waitForEvent: 'close' }, - ); - txData.confirmed = true; - log('[confirmTransaction] Transaction confirmed!'); - return txData; - }, - rejectTransaction: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - confirmPageElements.rejectButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - confirmEncryptionPublicKeyRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - encryptionPublicKeyPageElements.confirmEncryptionPublicKeyButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - rejectEncryptionPublicKeyRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - encryptionPublicKeyPageElements.rejectEncryptionPublicKeyButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - confirmDecryptionRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - decryptPageElements.confirmDecryptionRequestButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - rejectDecryptionRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - decryptPageElements.rejectDecryptionRequestButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - allowToAddNetwork: async ({ waitForEvent } = {}) => { - const notificationPage = await playwright.switchToMetamaskNotification(); - if (waitForEvent) { await playwright.waitAndClick( - confirmationPageElements.footer.approveButton, - notificationPage, - { waitForEvent }, - ); - } else { - await playwright.waitAndClick( - confirmationPageElements.footer.approveButton, - notificationPage, + mainPageElements.actionableMessage.closeButton, ); } return true; }, - rejectToAddNetwork: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - confirmationPageElements.footer.cancelButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - allowToSwitchNetwork: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - confirmationPageElements.footer.approveButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - rejectToSwitchNetwork: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick( - confirmationPageElements.footer.cancelButton, - notificationPage, - { waitForEvent: 'close' }, - ); - return true; - }, - allowToAddAndSwitchNetwork: async () => { - await module.exports.allowToAddNetwork(); - await module.exports.allowToSwitchNetwork(); - return true; - }, getWalletAddress: async () => { await switchToPhantomIfNotActive(); await playwright.metamaskWindow().hover(mainPageElements.accountBar.title); @@ -1149,31 +257,23 @@ module.exports = { await module.exports.getExtensionDetails(); await module.exports.fixBlankPage(); - // password (unlock?) or new set up? if ( await playwright .metamaskWindow() .locator(firstTimeFlowPageElements.importWalletButton) .isVisible() ) { + /** + * SEED PHRASE IMPORT + */ if (secretWordsOrPrivateKey.includes(' ')) { // secret words await module.exports.importWallet(secretWordsOrPrivateKey, password); - } else { - // private key - await module.exports.createWallet(password); - await module.exports.importAccount(secretWordsOrPrivateKey); } - // await setupSettings(enableAdvancedSettings); - // if (isCustomNetwork) { - // await module.exports.addNetwork(network); - // } else { - // await module.exports.changeNetwork(network); - // } await switchToPhantomIfNotActive(); - // await module.exports.goToHome(); + // skip what's new header if ( await playwright .metamaskWindow() @@ -1187,6 +287,9 @@ module.exports = { await playwright.switchToCypressWindow(); return true; } else if ( + /** + * PASSWORD UNLOCK + */ await playwright .metamaskWindow() .locator(unlockPageElements.passwordInput) @@ -1213,6 +316,67 @@ module.exports = { } } }, + confirmSignatureRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + signaturePageElements.buttons.confirmSign, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectSignatureRequest: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + signaturePageElements.buttons.rejectSign, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmTransaction: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + transactionPageElements.buttons.rejectSign, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmIncorrectNetworkStage: async () => { + const notificationPage = await playwright.switchToMetamaskNotification(); + await playwright.waitAndClick( + incorrectModePageElements.buttons.close, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + unlock: async password => { + await playwright.waitAndType(unlockPageElements.passwordInput, password); + await playwright.waitAndClick( + unlockPageElements.unlockButton, + await playwright.metamaskWindow(), + ); + await module.exports.closePopupAndTooltips(); + return true; + }, + lock: async () => { + await playwright.waitAndClick( + menu.buttons.settings, + await playwright.metamaskWindow(), + ); + await playwright.waitAndClick( + menu.buttons.sidebar.settings, + await playwright.metamaskWindow(), + ); + await playwright.waitAndClick( + settingsPageElements.buttons.lockWallet, + await playwright.metamaskWindow(), + ); + await module.exports.closePopupAndTooltips(); + return true; + }, }; async function switchToPhantomIfNotActive() { @@ -1231,60 +395,3 @@ async function switchToCypressIfNotActive() { } return switchBackToCypressWindow; } - -async function activateAdvancedSetting( - toggleOn, - toggleOff, - skipSetup, - experimental, -) { - if (!skipSetup) { - await switchToPhantomIfNotActive(); - if (experimental) { - await module.exports.goToExperimentalSettings(); - } else { - await module.exports.goToAdvancedSettings(); - } - } - if (!(await playwright.metamaskWindow().locator(toggleOn).isVisible())) { - await playwright.waitAndClick(toggleOff); - } - if (!skipSetup) { - await playwright.waitAndClick( - settingsPageElements.closeButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); - await switchToCypressIfNotActive(); - } - return true; -} - -async function setupSettings(enableAdvancedSettings) { - await switchToPhantomIfNotActive(); - await module.exports.goToAdvancedSettings(); - await module.exports.activateAdvancedGasControl(true); - await module.exports.activateShowHexData(true); - await module.exports.activateShowTestnetNetworks(true); - await module.exports.activateCustomNonce(true); - await module.exports.activateDismissBackupReminder(true); - if (enableAdvancedSettings) { - await module.exports.activateEnhancedTokenDetection(true); - await module.exports.activateTestnetConversion(true); - } - await module.exports.goToExperimentalSettings(); - await module.exports.activateEnhancedGasFeeUI(true); - await playwright.waitAndClick( - settingsPageElements.closeButton, - await playwright.metamaskWindow(), - { - waitForEvent: 'navi', - }, - ); - await module.exports.closePopupAndTooltips(); - await switchToCypressIfNotActive(); - return true; -} diff --git a/commands/playwright.js b/commands/playwright.js index 94c43e5dc..13a133de3 100644 --- a/commands/playwright.js +++ b/commands/playwright.js @@ -7,6 +7,7 @@ const { onboardingWelcomePageElements, } = require('../pages/metamask/first-time-flow-page'); // const metamask = require('./metamask'); +const { app } = require('../pages/phantom/notification-page'); const sleep = require('util').promisify(setTimeout); let browser; @@ -14,7 +15,6 @@ let mainWindow; let metamaskWindow; let metamaskNotificationWindow; let activeTabName; - let retries = 0; module.exports = { @@ -101,13 +101,21 @@ module.exports = { switchToMetamaskWindow: async () => { if (metamaskWindow.isClosed()) { const newPage = await browser.contexts()[0].newPage(); - await Promise.all([ - newPage.waitForNavigation(), - newPage.goto( - metamaskWindow.url().replace('onboarding.html', 'popup.html'), - ), - ]); - // await newPage.waitUntilStable(); + if (process.env.PROVIDER === 'phantom') { + await Promise.all([ + newPage.waitForNavigation(), + newPage.goto( + metamaskWindow.url().replace('onboarding.html', 'popup.html'), + ), + ]); + } else { + await Promise.all([ + newPage.waitForNavigation(), + newPage.goto(metamaskWindow.url()), + ]); + await newPage.waitUntilStable(); + } + metamaskWindow = newPage; } @@ -122,16 +130,24 @@ module.exports = { }, async switchToMetamaskNotification() { let pages = await browser.contexts()[0].pages(); - for (const page of pages) { + + // loop reverse, chance is very high that it's the last window + for (let i = pages.length - 1; i >= 0; i--) { + const page = pages[i]; if (page.url().includes('notification')) { metamaskNotificationWindow = page; retries = 0; await page.bringToFront(); await module.exports.waitUntilStable(page); - await module.exports.waitFor( - notificationPageElements.notificationAppContent, - page, - ); + if (process.env.PROVIDER === 'phantom') { + await module.exports.waitFor(app.root, page); + } else { + await module.exports.waitFor( + notificationPageElements.notificationAppContent, + page, + ); + } + return page; } } diff --git a/commands/synthetix.js b/commands/synthetix.js index 8f2ce16e8..bb9d65d5e 100644 --- a/commands/synthetix.js +++ b/commands/synthetix.js @@ -1,7 +1,10 @@ const { SynthetixJs } = require('synthetix-js'); const { synthetix } = require('@synthetixio/js'); const { getNetwork } = require('../helpers'); -const metamask = require('./metamask'); +const metamask = + process.env.PROVIDER === 'phantom' + ? require('./phantom') + : require('./metamask'); const bytes32 = require('bytes32'); const sleep = require('util').promisify(setTimeout); diff --git a/pages/phantom/notification-page.js b/pages/phantom/notification-page.js index 7844b44c6..2d72c238e 100644 --- a/pages/phantom/notification-page.js +++ b/pages/phantom/notification-page.js @@ -144,3 +144,48 @@ module.exports.addTokenPageElements = { confirmAddTokenButton, rejectAddTokenButton, }; + +/** + * ADJUSTED + */ +const root = '#root'; +module.exports.app = { + root, +}; + +const primaryButton = '[data-testid="primary-button"]'; +module.exports.buttons = { + primaryButton, +}; + +/** + * NEW + */ +module.exports.signaturePageElements = { + buttons: { + confirmSign: '[data-testid="primary-button"]', + rejectSign: '[data-testid="secondary-button"]', + }, +}; + +module.exports.transactionPageElements = { + buttons: { + confirmSign: '[data-testid="primary-button"]', + rejectSign: '[data-testid="secondary-button"]', + }, +}; + +module.exports.menu = { + buttons: { + settings: '[data-testid="settings-menu-open-button"]', + sidebar: { + settings: '[data-testid="sidebar_menu-button-settings"]', + }, + }, +}; + +module.exports.incorrectModePageElements = { + buttons: { + close: '[data-testid="incorrect-mode"] button', + }, +}; diff --git a/pages/phantom/settings-page.js b/pages/phantom/settings-page.js index bcaee5b88..79cfe2903 100644 --- a/pages/phantom/settings-page.js +++ b/pages/phantom/settings-page.js @@ -1,94 +1,5 @@ -const settingsPage = '.settings-page'; -const advancedButton = `${settingsPage} button:nth-child(2)`; -const networksButton = `${settingsPage} button:nth-child(6)`; -const closeButton = `${settingsPage} .settings-page__header__title-container__close-button`; module.exports.settingsPageElements = { - settingsPage, - advancedButton, - networksButton, - closeButton, -}; - -const resetAccountButton = - '[data-testid="advanced-setting-reset-account"] button'; -const advancedGasControlToggleOn = - '[data-testid="advanced-setting-advanced-gas-inline"] .toggle-button--on'; -const advancedGasControlToggleOff = - '[data-testid="advanced-setting-advanced-gas-inline"] .toggle-button--off'; -const enhancedTokenDetectionToggleOn = - '[data-testid="advanced-setting-token-detection"] .toggle-button--on'; -const enhancedTokenDetectionToggleOff = - '[data-testid="advanced-setting-token-detection"] .toggle-button--off'; -const showHexDataToggleOn = - '[data-testid="advanced-setting-hex-data"] .toggle-button--on'; -const showHexDataToggleOff = - '[data-testid="advanced-setting-hex-data"] .toggle-button--off'; -const showTestnetConversionOn = - '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(7) .toggle-button--on'; -const showTestnetConversionOff = - '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(7) .toggle-button--off'; -const showTestnetNetworksOn = - '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(8) .toggle-button--on'; -const showTestnetNetworksOff = - '[data-testid="advanced-setting-show-testnet-conversion"]:nth-child(8) .toggle-button--off'; -const customNonceToggleOn = - '[data-testid="advanced-setting-custom-nonce"] .toggle-button--on'; -const customNonceToggleOff = - '[data-testid="advanced-setting-custom-nonce"] .toggle-button--off'; -const dismissBackupReminderOn = - '[data-testid="advanced-setting-dismiss-reminder"] .toggle-button--on'; -const dismissBackupReminderOff = - '[data-testid="advanced-setting-dismiss-reminder"] .toggle-button--off'; -module.exports.advancedPageElements = { - resetAccountButton, - advancedGasControlToggleOn, - advancedGasControlToggleOff, - enhancedTokenDetectionToggleOn, - enhancedTokenDetectionToggleOff, - showHexDataToggleOn, - showHexDataToggleOff, - showTestnetConversionOn, - showTestnetConversionOff, - showTestnetNetworksOn, - showTestnetNetworksOff, - dismissBackupReminderOn, - dismissBackupReminderOff, - customNonceToggleOn, - customNonceToggleOff, -}; - -const enhancedGasFeeUIToggleOn = - '.settings-page__content-row:nth-child(1) .toggle-button--on'; -const enhancedGasFeeUIToggleOff = - '.settings-page__content-row:nth-child(1) .toggle-button--off'; -module.exports.experimentalSettingsPageElements = { - enhancedGasFeeUIToggleOn, - enhancedGasFeeUIToggleOff, -}; - -const nevermindButton = '.modal-container .btn-secondary'; -const resetButton = '.modal-container .btn-danger-primary'; -module.exports.resetAccountModalElements = { - nevermindButton, - resetButton, -}; - -const addNetworkButton = '.networks-tab__body button'; -module.exports.networksPageElements = { addNetworkButton }; - -const addNetworkForm = '.networks-tab__add-network-form-body'; -const networkNameInput = `${addNetworkForm} .form-field:nth-child(1) input`; -const rpcUrlInput = `${addNetworkForm} .form-field:nth-child(2) input`; -const chainIdInput = `${addNetworkForm} .form-field:nth-child(3) input`; -const symbolInput = `${addNetworkForm} .form-field:nth-child(4) input`; -const blockExplorerInput = `${addNetworkForm} .form-field:nth-child(5) input`; -const saveButton = '.networks-tab__add-network-form-footer .btn-primary'; -module.exports.addNetworkPageElements = { - addNetworkForm, - networkNameInput, - rpcUrlInput, - chainIdInput, - symbolInput, - blockExplorerInput, - saveButton, + buttons: { + lockWallet: '[data-testid="lock-menu-item"]', + }, }; diff --git a/pages/phantom/unlock-page.js b/pages/phantom/unlock-page.js index 69e65f4ab..5665ea7d5 100644 --- a/pages/phantom/unlock-page.js +++ b/pages/phantom/unlock-page.js @@ -1,9 +1,4 @@ -const unlockPage = '[data-testid="unlock-page"]'; -const passwordInput = `${unlockPage} #password`; -const unlockButton = `${unlockPage} .btn-default`; - module.exports.unlockPageElements = { - unlockPage, - passwordInput, - unlockButton, + passwordInput: '[data-testid="unlock-form-password-input"]', + unlockButton: '[data-testid="unlock-form-submit-button"]', }; diff --git a/plugins/index.js b/plugins/index.js index 4bb8622ab..e366d4e34 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -1,6 +1,9 @@ const helpers = require('../helpers'); const playwright = require('../commands/playwright'); -const metamask = require('../commands/metamask'); +const provider = + process.env.PROVIDER === 'phantom' + ? require('../commands/phantom') + : require('../commands/metamask'); const synthetix = require('../commands/synthetix'); const etherscan = require('../commands/etherscan'); @@ -13,7 +16,7 @@ module.exports = (on, config) => { on('before:browser:launch', async (browser = {}, arguments_) => { if (browser.name === 'chrome') { - // metamask welcome screen blocks cypress from loading + arguments_.args.push('--window-size=1920,1080'); // optional arguments_.args.push( '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', @@ -50,7 +53,13 @@ module.exports = (on, config) => { const metamaskPath = await helpers.prepareMetamask( process.env.METAMASK_VERSION || '10.25.0', ); - arguments_.extensions.push(metamaskPath); + if (process.env.PROVIDER === 'phantom') { + arguments_.extensions.push( + metamaskPath.replace('metamask-chrome-10.21.0', 'phantom'), + ); + } else { + arguments_.extensions.push(metamaskPath); + } } return arguments_; @@ -106,24 +115,36 @@ module.exports = (on, config) => { const notificationPage = await playwright.switchToMetamaskNotification(); return notificationPage; }, + /** + * @deprecated + */ unlockMetamask: async password => { - const unlocked = await metamask.unlock(password); + return module.exports.unlock(password); + }, + unlock: async password => { + const unlocked = await provider.unlock(password); return unlocked; }, + lock: () => { + return provider.lock(); + }, + confirmIncorrectNetworkStage: () => { + return provider.confirmIncorrectNetworkStage(); + }, importMetamaskAccount: async privateKey => { - const imported = await metamask.importAccount(privateKey); + const imported = await provider.importAccount(privateKey); return imported; }, createMetamaskAccount: async accountName => { - const created = await metamask.createAccount(accountName); + const created = await provider.createAccount(accountName); return created; }, switchMetamaskAccount: async accountNameOrAccountNumber => { - const switched = await metamask.switchAccount(accountNameOrAccountNumber); + const switched = await provider.switchAccount(accountNameOrAccountNumber); return switched; }, addMetamaskNetwork: async network => { - const networkAdded = await metamask.addNetwork(network); + const networkAdded = await provider.addNetwork(network); return networkAdded; }, changeMetamaskNetwork: async network => { @@ -132,145 +153,178 @@ module.exports = (on, config) => { } else if (!network) { network = 'goerli'; } - const networkChanged = await metamask.changeNetwork(network); + const networkChanged = await provider.changeNetwork(network); return networkChanged; }, activateAdvancedGasControlInMetamask: async skipSetup => { - const activated = await metamask.activateAdvancedGasControl(skipSetup); + const activated = await provider.activateAdvancedGasControl(skipSetup); + return activated; + }, + activateEnhancedTokenDetectionInMetamask: async skipSetup => { + const activated = await provider.activateEnhancedTokenDetection( + skipSetup, + ); return activated; }, activateShowHexDataInMetamask: async skipSetup => { - const activated = await metamask.activateShowHexData(skipSetup); + const activated = await provider.activateShowHexData(skipSetup); return activated; }, activateTestnetConversionInMetamask: async skipSetup => { - const activated = await metamask.activateTestnetConversion(skipSetup); + const activated = await provider.activateTestnetConversion(skipSetup); return activated; }, activateShowTestnetNetworksInMetamask: async skipSetup => { - const activated = await metamask.activateShowTestnetNetworks(skipSetup); + const activated = await provider.activateShowTestnetNetworks(skipSetup); return activated; }, activateCustomNonceInMetamask: async skipSetup => { - const activated = await metamask.activateCustomNonce(skipSetup); + const activated = await provider.activateCustomNonce(skipSetup); return activated; }, activateDismissBackupReminderInMetamask: async skipSetup => { - const activated = await metamask.activateDismissBackupReminder(skipSetup); + const activated = await provider.activateDismissBackupReminder(skipSetup); return activated; }, - activateEthSignRequestsInMetamask: async skipSetup => { - const activated = await metamask.activateEthSignRequests(skipSetup); + activateEnhancedGasFeeUIInMetamask: async skipSetup => { + const activated = await provider.activateEnhancedGasFeeUI(skipSetup); return activated; }, - activateImprovedTokenAllowanceInMetamask: async skipSetup => { - const activated = await metamask.activateImprovedTokenAllowance( - skipSetup, - ); + activateShowCustomNetworkListInMetamask: async skipSetup => { + const activated = await provider.activateShowCustomNetworkList(skipSetup); return activated; }, resetMetamaskAccount: async () => { - const resetted = await metamask.resetAccount(); + const resetted = await provider.resetAccount(); return resetted; }, disconnectMetamaskWalletFromDapp: async () => { - const disconnected = await metamask.disconnectWalletFromDapp(); + const disconnected = await provider.disconnectWalletFromDapp(); return disconnected; }, disconnectMetamaskWalletFromAllDapps: async () => { - const disconnected = await metamask.disconnectWalletFromAllDapps(); + const disconnected = await provider.disconnectWalletFromAllDapps(); return disconnected; }, + + /** + * @deprecated + */ confirmMetamaskSignatureRequest: async () => { - const confirmed = await metamask.confirmSignatureRequest(); + return module.exports.confirmSignatureRequest(); + }, + confirmSignatureRequest: async () => { + const confirmed = await provider.confirmSignatureRequest(); return confirmed; }, + confirmMetamaskDataSignatureRequest: async () => { - const confirmed = await metamask.confirmDataSignatureRequest(); + const confirmed = await provider.confirmDataSignatureRequest(); return confirmed; }, rejectMetamaskSignatureRequest: async () => { - const rejected = await metamask.rejectSignatureRequest(); + const rejected = await provider.rejectSignatureRequest(); return rejected; }, rejectMetamaskDataSignatureRequest: async () => { - const rejected = await metamask.rejectDataSignatureRequest(); + const rejected = await provider.rejectDataSignatureRequest(); return rejected; }, confirmMetamaskEncryptionPublicKeyRequest: async () => { - const confirmed = await metamask.confirmEncryptionPublicKeyRequest(); + const confirmed = await provider.confirmEncryptionPublicKeyRequest(); return confirmed; }, rejectMetamaskEncryptionPublicKeyRequest: async () => { - const rejected = await metamask.rejectEncryptionPublicKeyRequest(); + const rejected = await provider.rejectEncryptionPublicKeyRequest(); return rejected; }, confirmMetamaskDecryptionRequest: async () => { - const confirmed = await metamask.confirmDecryptionRequest(); + const confirmed = await provider.confirmDecryptionRequest(); return confirmed; }, rejectMetamaskDecryptionRequest: async () => { - const rejected = await metamask.rejectDecryptionRequest(); + const rejected = await provider.rejectDecryptionRequest(); return rejected; }, importMetamaskToken: async tokenConfig => { - const imported = await metamask.importToken(tokenConfig); + const imported = await provider.importToken(tokenConfig); return imported; }, confirmMetamaskAddToken: async () => { - const confirmed = await metamask.confirmAddToken(); + const confirmed = await provider.confirmAddToken(); return confirmed; }, rejectMetamaskAddToken: async () => { - const rejected = await metamask.rejectAddToken(); + const rejected = await provider.rejectAddToken(); return rejected; }, - confirmMetamaskPermissionToSpend: async spendLimit => { - const confirmed = await metamask.confirmPermissionToSpend(spendLimit); + confirmMetamaskPermissionToSpend: async () => { + const confirmed = await provider.confirmPermissionToSpend(); return confirmed; }, rejectMetamaskPermissionToSpend: async () => { - const rejected = await metamask.rejectPermissionToSpend(); + const rejected = await provider.rejectPermissionToSpend(); return rejected; }, + + /** + * @deprecated + */ acceptMetamaskAccess: async options => { - const accepted = await metamask.acceptAccess(options); + return module.exports.acceptAccess(options); + }, + acceptAccess: async options => { + const accepted = await provider.acceptAccess(options); return accepted; }, + + /** + * @deprecated + */ confirmMetamaskTransaction: async gasConfig => { - const confirmed = await metamask.confirmTransaction(gasConfig); + return module.exports.confirmTransaction(gasConfig); + }, + confirmTransaction: async gasConfig => { + const confirmed = await provider.confirmTransaction(gasConfig); return confirmed; }, + rejectMetamaskTransaction: async () => { - const rejected = await metamask.rejectTransaction(); + const rejected = await provider.rejectTransaction(); return rejected; }, allowMetamaskToAddNetwork: async ({ waitForEvent }) => { - const allowed = await metamask.allowToAddNetwork({ waitForEvent }); + const allowed = await provider.allowToAddNetwork({ waitForEvent }); return allowed; }, rejectMetamaskToAddNetwork: async () => { - const rejected = await metamask.rejectToAddNetwork(); + const rejected = await provider.rejectToAddNetwork(); return rejected; }, allowMetamaskToSwitchNetwork: async () => { - const allowed = await metamask.allowToSwitchNetwork(); + const allowed = await provider.allowToSwitchNetwork(); return allowed; }, rejectMetamaskToSwitchNetwork: async () => { - const rejected = await metamask.rejectToSwitchNetwork(); + const rejected = await provider.rejectToSwitchNetwork(); return rejected; }, allowMetamaskToAddAndSwitchNetwork: async () => { - const allowed = await metamask.allowToAddAndSwitchNetwork(); + const allowed = await provider.allowToAddAndSwitchNetwork(); return allowed; }, getMetamaskWalletAddress: async () => { - const walletAddress = await metamask.getWalletAddress(); + const walletAddress = await provider.getWalletAddress(); return walletAddress; }, + /** + * @deprecated + */ fetchMetamaskWalletAddress: async () => { - return metamask.walletAddress(); + return module.exports.fetchWalletAddress(); + }, + fetchWalletAddress: async () => { + return provider.walletAddress(); }, setupMetamask: async ({ secretWordsOrPrivateKey, @@ -288,7 +342,7 @@ module.exports = (on, config) => { if (process.env.SECRET_WORDS) { secretWordsOrPrivateKey = process.env.SECRET_WORDS; } - await metamask.initialSetup(null, { + await provider.initialSetup({ secretWordsOrPrivateKey, network, password, diff --git a/support/commands.js b/support/commands.js index f6d09dd29..86913a185 100644 --- a/support/commands.js +++ b/support/commands.js @@ -2,6 +2,23 @@ import '@testing-library/cypress/add-commands'; import 'cypress-wait-until'; // playwright commands +const addCommand = (commandName, legacyCommandName, promiseFn) => { + Cypress.Commands.add(commandName, parameters => { + if (promiseFn) { + return promiseFn(cy.task(commandName, parameters)); + } + return cy.task(commandName, parameters); + }); + + if (legacyCommandName != null) { + Cypress.Commands.add(legacyCommandName, parameters => { + if (promiseFn) { + return promiseFn(cy.task(commandName, parameters)); + } + return cy.task(legacyCommandName, parameters); + }); + } +}; Cypress.Commands.add('initPlaywright', () => { return cy.task('initPlaywright'); @@ -125,9 +142,7 @@ Cypress.Commands.add('disconnectMetamaskWalletFromAllDapps', () => { return cy.task('disconnectMetamaskWalletFromAllDapps'); }); -Cypress.Commands.add('confirmMetamaskSignatureRequest', () => { - return cy.task('confirmMetamaskSignatureRequest'); -}); +addCommand('confirmSignatureRequest', 'confirmMetamaskSignatureRequest'); Cypress.Commands.add('confirmMetamaskEncryptionPublicKeyRequest', () => { return cy.task('confirmMetamaskEncryptionPublicKeyRequest'); @@ -180,13 +195,8 @@ Cypress.Commands.add('rejectMetamaskPermissionToSpend', () => { return cy.task('rejectMetamaskPermissionToSpend'); }); -Cypress.Commands.add('acceptMetamaskAccess', options => { - return cy.task('acceptMetamaskAccess', options); -}); - -Cypress.Commands.add('confirmMetamaskTransaction', gasConfig => { - return cy.task('confirmMetamaskTransaction', gasConfig); -}); +addCommand('acceptAccess', 'acceptMetamaskAccess'); +addCommand('confirmTransaction', 'confirmMetamaskTransaction'); Cypress.Commands.add('rejectMetamaskTransaction', () => { return cy.task('rejectMetamaskTransaction'); @@ -212,16 +222,24 @@ Cypress.Commands.add('allowMetamaskToAddAndSwitchNetwork', () => { return cy.task('allowMetamaskToAddAndSwitchNetwork'); }); +/** + * @deprecated + */ Cypress.Commands.add('unlockMetamask', (password = 'Tester@1234') => { return cy.task('unlockMetamask', password); }); - -Cypress.Commands.add('fetchMetamaskWalletAddress', () => { - cy.task('fetchMetamaskWalletAddress').then(address => { - return address; - }); +Cypress.Commands.add('unlock', (password = 'Tester@1234') => { + return cy.task('unlock', password); }); +addCommand('lock'); + +addCommand('fetchWalletAddress', 'fetchMetamaskWalletAddress', taskPromise => + taskPromise.then(address => address), +); + +addCommand('confirmIncorrectNetworkStage'); + Cypress.Commands.add( 'setupMetamask', ( @@ -241,6 +259,23 @@ Cypress.Commands.add( }, ); +Cypress.Commands.add( + 'setup', + ( + secretWordsOrPrivateKey = 'test test test test test test test test test test test junk', + network = 'goerli', + password = 'Tester@1234', + enableAdvancedSettings = false, + ) => { + return cy.task('setup', { + secretWordsOrPrivateKey, + network, + password, + enableAdvancedSettings, + }); + }, +); + Cypress.Commands.add('getNetwork', () => { return cy.task('getNetwork'); }); From 3a038601266811ebd08fcec37503a7cc6f278d98 Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Thu, 9 Feb 2023 16:45:24 +0100 Subject: [PATCH 03/14] chore: Adjust timings --- commands/phantom.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/commands/phantom.js b/commands/phantom.js index c5d710bf7..249a1dd99 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -176,18 +176,15 @@ module.exports = { firstTimeFlowImportPageElements.continueAfterPasswordButton, ); // shortcut confirmation - await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast + await new Promise(resolve => setTimeout(resolve, 500)); // the transitioning is too fast await playwright.waitAndClick( firstTimeFlowImportPageElements.continueOnShortcutConfirm, ); // finish - await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast + await new Promise(resolve => setTimeout(resolve, 500)); // the transitioning is too fast await playwright.waitAndClick( firstTimeFlowImportPageElements.continueOnShortcutConfirm, ); - - await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast - // await module.exports.closePopupAndTooltips(); return true; }, closePopupAndTooltips: async () => { @@ -231,7 +228,7 @@ module.exports = { getWalletAddress: async () => { await switchToPhantomIfNotActive(); await playwright.metamaskWindow().hover(mainPageElements.accountBar.title); - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise(resolve => setTimeout(resolve, 100)); await playwright.waitAndClick(mainPageElements.accountBar.ethRow); walletAddress = await playwright .metamaskWindow() From bced24c792b93e4c873866fbc6e9d7dad18e8f9d Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Tue, 14 Feb 2023 14:34:49 +0100 Subject: [PATCH 04/14] chore: Phantom download --- helpers.js | 105 ++++++++++++++++++++++++++++++++++++++++------- plugins/index.js | 13 ++---- 2 files changed, 95 insertions(+), 23 deletions(-) diff --git a/helpers.js b/helpers.js index 8c05b72fb..4d7f629fb 100644 --- a/helpers.js +++ b/helpers.js @@ -150,7 +150,62 @@ module.exports = { ); } }, - async download(url, destination) { + getPhantomReleases: async version => { + log(`Trying to find phantom version ${version} in GitHub releases..`); + let filename; + let downloadUrl; + let tagName; + let response; + + try { + if (version === 'latest' || !version) { + if (process.env.GH_USERNAME && process.env.GH_PAT) { + response = await axios.get( + 'https://api.github.com/repos/phantom-labs/phantom-wallet/releases', + { + auth: { + username: process.env.GH_USERNAME, + password: process.env.GH_PAT, + }, + }, + ); + } else { + response = await axios.get( + 'https://api.github.com/repos/phantom-labs/phantom-wallet/releases', + ); + } + filename = response.data[0].assets[0].name; + downloadUrl = response.data[0].assets[0].url; + tagName = 'phantom-chrome-latest'; + log( + `Phantom version found! Filename: ${filename}; Download url: ${downloadUrl}; Tag name: ${tagName}`, + ); + } else if (version) { + filename = `chrome-dist.zip`; + downloadUrl = `https://github.com/phantom-labs/phantom-wallet/releases/download/v${version}/chrome-dist.zip`; + tagName = `phantom-chrome-${version}`; + log( + `Phantom version found! Filename: ${filename}; Download url: ${downloadUrl}; Tag name: ${tagName}`, + ); + } + return { + filename, + downloadUrl, + tagName, + }; + } catch (e) { + if (e.response && e.response.status === 403) { + throw new Error( + `[getPhantomReleases] Unable to fetch phantom releases from GitHub because you've been rate limited! Please set GH_USERNAME and GH_PAT environment variables to avoid this issue or retry again.`, + ); + } else { + throw new Error( + `[getPhantomReleases] Unable to fetch phantom releases from GitHub with following error:\n${e}`, + ); + } + } + }, + download: async (url, destination) => { try { log( `Trying to download and extract file from: ${url} to following path: ${destination}`, @@ -159,7 +214,15 @@ module.exports = { await download(url, destination, { extract: true, auth: `${process.env.GH_USERNAME}:${process.env.GH_PAT}`, + headers: { + Accept: 'application/octet-stream', + }, }); + + /** + * Some extensions will zip their dist folder + */ + await moveFiles(`${destination}/dist`, destination); } else { await download(url, destination, { extract: true, @@ -167,31 +230,45 @@ module.exports = { } } catch (e) { throw new Error( - `[download] Unable to download metamask release from: ${url} to: ${destination} with following error:\n${e}`, + `[download] Unable to download provider release from: ${url} to: ${destination} with following error:\n${e}`, ); } }, - async prepareMetamask(version) { - const release = await module.exports.getMetamaskReleases(version); + prepareProvider: async version => { + const release = + process.env.PROVIDER === 'phantom' + ? await module.exports.getPhantomReleases(version) + : await module.exports.getMetamaskReleases(version); const downloadsDirectory = path.resolve(__dirname, 'downloads'); await module.exports.createDirIfNotExist(downloadsDirectory); - const metamaskDirectory = path.join(downloadsDirectory, release.tagName); - const metamaskDirectoryExists = await module.exports.checkDirOrFileExist( - metamaskDirectory, + const providerDirectory = path.join(downloadsDirectory, release.tagName); + const providerkDirectoryExists = await module.exports.checkDirOrFileExist( + providerDirectory, ); - const metamaskManifestFilePath = path.join( + const providerManifestFilePath = path.join( downloadsDirectory, release.tagName, 'manifest.json', ); - const metamaskManifestFileExists = await module.exports.checkDirOrFileExist( - metamaskManifestFilePath, + const providerManifestFileExists = await module.exports.checkDirOrFileExist( + providerManifestFilePath, ); - if (!metamaskDirectoryExists && !metamaskManifestFileExists) { - await module.exports.download(release.downloadUrl, metamaskDirectory); + if (!providerkDirectoryExists && !providerManifestFileExists) { + await module.exports.download(release.downloadUrl, providerDirectory); } else { - log('Metamask is already downloaded'); + log('provider is already downloaded'); } - return metamaskDirectory; + return providerDirectory; }, }; + +async function moveFiles(srcDir, destDir) { + const files = await fs.readdir(srcDir); + + return Promise.all( + files.map(function (file) { + var destFile = path.join(destDir, file); + return fs.rename(path.join(srcDir, file), destFile); + }), + ); +} diff --git a/plugins/index.js b/plugins/index.js index e366d4e34..78524d60c 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -50,16 +50,11 @@ module.exports = (on, config) => { if (!process.env.SKIP_METAMASK_INSTALL) { // NOTE: extensions cannot be loaded in headless Chrome - const metamaskPath = await helpers.prepareMetamask( - process.env.METAMASK_VERSION || '10.25.0', + const providerPath = await helpers.prepareProvider( + process.env.PROVIDER_VERSION || '10.25.0', ); - if (process.env.PROVIDER === 'phantom') { - arguments_.extensions.push( - metamaskPath.replace('metamask-chrome-10.21.0', 'phantom'), - ); - } else { - arguments_.extensions.push(metamaskPath); - } + + arguments_.extensions.push(providerPath); } return arguments_; From 5c60757651155b356929c2b6116fa888dfb7c6fd Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Tue, 21 Mar 2023 11:41:37 +0100 Subject: [PATCH 05/14] chore: Support phantom 3.6.0 --- commands/phantom.js | 38 ++------------------------------------ commands/playwright.js | 4 +++- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/commands/phantom.js b/commands/phantom.js index 249a1dd99..48b647d0e 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -2,42 +2,19 @@ const log = require('debug')('synpress:phantom'); const playwright = require('./playwright'); const { - welcomePageElements, firstTimeFlowPageElements, - metametricsPageElements, firstTimeFlowImportPageElements, - firstTimeFlowCreatePagePageElements, - secureYourWalletPageElements, - revealSeedPageElements, } = require('../pages/phantom/first-time-flow-page'); const { mainPageElements } = require('../pages/phantom/main-page'); const { unlockPageElements } = require('../pages/phantom/unlock-page'); const { - notificationPageElements, - permissionsPageElements, - confirmPageElements, signaturePageElements, - encryptionPublicKeyPageElements, - decryptPageElements, - dataSignaturePageElements, - recipientPopupElements, - addTokenPageElements, buttons, transactionPageElements, menu, incorrectModePageElements, } = require('../pages/phantom/notification-page'); -const { - settingsPageElements, - advancedPageElements, - experimentalSettingsPageElements, - resetAccountModalElements, - addNetworkPageElements, -} = require('../pages/phantom/settings-page'); -const { - confirmationPageElements, -} = require('../pages/phantom/confirmation-page'); -const { setNetwork } = require('../helpers'); +const { settingsPageElements } = require('../pages/phantom/settings-page'); let extensionInitialUrl; let extensionId; @@ -236,18 +213,7 @@ module.exports = { await switchToCypressIfNotActive(); return walletAddress; }, - initialSetup: async ({ - secretWordsOrPrivateKey, - network, - password, - enableAdvancedSettings, - }) => { - const isCustomNetwork = - (process.env.NETWORK_NAME && - process.env.RPC_URL && - process.env.CHAIN_ID) || - typeof network == 'object'; - + initialSetup: async ({ secretWordsOrPrivateKey, password }) => { await playwright.init(); await playwright.assignWindows(); await playwright.assignActiveTabName('metamask'); diff --git a/commands/playwright.js b/commands/playwright.js index 13a133de3..383122aca 100644 --- a/commands/playwright.js +++ b/commands/playwright.js @@ -204,7 +204,9 @@ module.exports = { } else { await element.click({ force: args.force }); } - await module.exports.waitUntilStable(); + if (process.env.PROVIDER !== 'phantom') { + await module.exports.waitUntilStable(); + } return element; }, async waitAndClickByText(selector, text, page = metamaskWindow) { From 8e9c10598b51bbbf52e9bab358d55dfdb1a0e442 Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Thu, 23 Mar 2023 11:48:13 +0100 Subject: [PATCH 06/14] feat: Add multi provider support --- commands/metamask.js | 378 ++++++++++++++++++++-------- commands/phantom.js | 143 ++++++++--- commands/playwright.js | 290 ++++++++++++++------- helpers.js | 18 +- pages/metamask/main-page.js | 1 + pages/phantom/select-wallet-page.js | 7 + plugins/index.js | 241 +++++++++++++----- providers.js | 11 + support/commands.js | 20 +- support/index.js | 10 +- 10 files changed, 797 insertions(+), 322 deletions(-) create mode 100644 pages/phantom/select-wallet-page.js create mode 100644 providers.js diff --git a/commands/metamask.js b/commands/metamask.js index b32c62856..b361bac60 100644 --- a/commands/metamask.js +++ b/commands/metamask.js @@ -34,6 +34,8 @@ const { } = require('../pages/metamask/confirmation-page'); const { setNetwork } = require('../helpers'); +const PROVIDER = 'metamask'; + let extensionInitialUrl; let extensionId; let extensionHomeUrl; @@ -69,10 +71,10 @@ const metamask = { }, async goTo(url) { await Promise.all([ - playwright.metamaskWindow().waitForNavigation(), - playwright.metamaskWindow().goto(url), + playwright.windows(PROVIDER).waitForNavigation(), + playwright.windows(PROVIDER).goto(url), ]); - await playwright.waitUntilStable(); + await playwright.waitUntilStable(PROVIDER); }, async goToHome() { await module.exports.goTo(extensionHomeUrl); @@ -99,7 +101,7 @@ const metamask = { await module.exports.goTo(extensionImportTokenUrl); }, async getExtensionDetails() { - extensionInitialUrl = await playwright.metamaskWindow().url(); + extensionInitialUrl = await playwright.windows(PROVIDER).url(); extensionId = extensionInitialUrl.match('//(.*?)/')[1]; extensionHomeUrl = `chrome-extension://${extensionId}/home.html`; extensionSettingsUrl = `${extensionHomeUrl}#settings`; @@ -125,36 +127,40 @@ const metamask = { async closePopupAndTooltips() { // note: this is required for fast execution of e2e tests to avoid flakiness // otherwise popup may not be detected properly and not closed - await playwright.metamaskWindow().waitForTimeout(1000); + await playwright.windows(PROVIDER).waitForTimeout(1000); if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.popup.container) .isVisible() ) { const popupBackground = playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.popup.background); const popupBackgroundBox = await popupBackground.boundingBox(); await playwright - .metamaskWindow() + .windows(PROVIDER) .mouse.click(popupBackgroundBox.x + 1, popupBackgroundBox.y + 1); } if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.tippyTooltip.closeButton) .isVisible() ) { - await playwright.waitAndClick(mainPageElements.tippyTooltip.closeButton); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.tippyTooltip.closeButton, + ); } if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.actionableMessage.closeButton) .isVisible() ) { await playwright.waitAndClick( + PROVIDER, mainPageElements.actionableMessage.closeButton, ); } @@ -163,26 +169,32 @@ const metamask = { async closeModal() { // note: this is required for fast execution of e2e tests to avoid flakiness // otherwise modal may not be detected properly and not closed - await playwright.metamaskWindow().waitForTimeout(1000); + await playwright.windows(PROVIDER).waitForTimeout(1000); if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.connectedSites.modal) .isVisible() ) { await playwright.waitAndClick( + PROVIDER, mainPageElements.connectedSites.closeButton, ); } return true; }, async unlock(password) { - await playwright.fixBlankPage(); - await playwright.fixCriticalError(); - await playwright.waitAndType(unlockPageElements.passwordInput, password); + await playwright.fixBlankPage(PROVIDER); + await playwright.fixCriticalError(PROVIDER); + await playwright.waitAndType( + PROVIDER, + unlockPageElements.passwordInput, + password, + ); await playwright.waitAndClick( + PROVIDER, unlockPageElements.unlockButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -190,10 +202,27 @@ const metamask = { await module.exports.closePopupAndTooltips(); return true; }, + async lock() { + await playwright.fixBlankPage(PROVIDER); + await playwright.fixCriticalError(PROVIDER); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.accountMenu.button, + await playwright.windows(PROVIDER), + ); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.accountMenu.lockButton, + await playwright.windows(PROVIDER), + ); + await module.exports.closePopupAndTooltips(); + return true; + }, async optOutAnalytics() { await playwright.waitAndClick( + PROVIDER, metametricsPageElements.optOutAnalyticsButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -202,8 +231,9 @@ const metamask = { }, async importWallet(secretWords, password) { await playwright.waitAndClick( + PROVIDER, onboardingWelcomePageElements.importWalletButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -212,46 +242,57 @@ const metamask = { // todo: add support for more secret words (15/18/21/24) for (const [index, word] of secretWords.split(' ').entries()) { await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.secretWordsInput(index), word, ); } await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.confirmSecretRecoverPhraseButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.passwordInput, password, ); await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.confirmPasswordInput, password, ); await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.termsCheckbox, ); await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.importButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); await playwright.waitAndClick( + PROVIDER, endOfFlowPageElements.allDoneButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); - await playwright.waitAndClick(pinExtensionPageElements.nextTabButton); await playwright.waitAndClick( + PROVIDER, + pinExtensionPageElements.nextTabButton, + ); + await playwright.waitAndClick( + PROVIDER, pinExtensionPageElements.doneButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -261,51 +302,68 @@ const metamask = { }, async createWallet(password) { await playwright.waitAndClick( + PROVIDER, onboardingWelcomePageElements.createWalletButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); await module.exports.optOutAnalytics(); await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.passwordInput, password, ); await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.confirmPasswordInput, password, ); await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.termsCheckbox, ); await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.createButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); - await playwright.waitAndClick(revealSeedPageElements.remindLaterButton); - await playwright.waitAndClick(revealSeedPageElements.skipBackupCheckbox); await playwright.waitAndClick( + PROVIDER, + revealSeedPageElements.remindLaterButton, + ); + await playwright.waitAndClick( + PROVIDER, + revealSeedPageElements.skipBackupCheckbox, + ); + await playwright.waitAndClick( + PROVIDER, revealSeedPageElements.confirmSkipBackupButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); await playwright.waitAndClick( + PROVIDER, endOfFlowPageElements.allDoneButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); - await playwright.waitAndClick(pinExtensionPageElements.nextTabButton); await playwright.waitAndClick( + PROVIDER, + pinExtensionPageElements.nextTabButton, + ); + await playwright.waitAndClick( + PROVIDER, pinExtensionPageElements.doneButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -317,12 +375,14 @@ const metamask = { await switchToMetamaskIfNotActive(); await module.exports.goToImportAccount(); await playwright.waitAndType( + PROVIDER, mainPageElements.importAccount.input, privateKey, ); await playwright.waitAndClick( + PROVIDER, mainPageElements.importAccount.importButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -339,11 +399,15 @@ const metamask = { await module.exports.goToNewAccount(); if (accountName) { await playwright.waitAndType( + PROVIDER, mainPageElements.createAccount.input, accountName, ); } - await playwright.waitAndClick(mainPageElements.createAccount.createButton); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.createAccount.createButton, + ); await module.exports.closePopupAndTooltips(); await switchToCypressIfNotActive(); return true; @@ -356,13 +420,18 @@ const metamask = { // note: closePopupAndTooltips() is required after changing createAccount() to use direct urls (popup started appearing) // ^ this change also introduced 500ms delay for closePopupAndTooltips() function await module.exports.closePopupAndTooltips(); - await playwright.waitAndClick(mainPageElements.accountMenu.button); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.accountMenu.button, + ); if (typeof accountNameOrAccountNumber === 'number') { await playwright.waitAndClick( + PROVIDER, mainPageElements.accountMenu.accountButton(accountNameOrAccountNumber), ); } else { await playwright.waitAndClickByText( + PROVIDER, mainPageElements.accountMenu.accountName, accountNameOrAccountNumber, ); @@ -373,47 +442,58 @@ const metamask = { }, async changeNetwork(network) { await switchToMetamaskIfNotActive(); - await playwright.waitAndClick(mainPageElements.networkSwitcher.button); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.networkSwitcher.button, + ); if (typeof network === 'string') { network = network.toLowerCase(); if (network === 'mainnet') { await playwright.waitAndClick( + PROVIDER, mainPageElements.networkSwitcher.mainnetNetworkItem, ); } else if (network === 'goerli') { await playwright.waitAndClick( + PROVIDER, mainPageElements.networkSwitcher.goerliNetworkItem, ); } else if (network === 'sepolia') { await playwright.waitAndClick( + PROVIDER, mainPageElements.networkSwitcher.sepoliaNetworkItem, ); } else if (network === 'localhost') { await playwright.waitAndClick( + PROVIDER, mainPageElements.networkSwitcher.localhostNetworkItem, ); } else { await playwright.waitAndClickByText( + PROVIDER, mainPageElements.networkSwitcher.dropdownMenuItem, network, ); } await playwright.waitForText( + PROVIDER, mainPageElements.networkSwitcher.networkName, network, ); } else if (typeof network === 'object') { network.networkName = network.networkName.toLowerCase(); await playwright.waitAndClickByText( + PROVIDER, mainPageElements.networkSwitcher.dropdownMenuItem, network.networkName, ); await playwright.waitForText( + PROVIDER, mainPageElements.networkSwitcher.networkName, network.networkName, ); } - await playwright.waitUntilStable(); + await playwright.waitUntilStable(PROVIDER); await module.exports.closePopupAndTooltips(); await setNetwork(network); await switchToCypressIfNotActive(); @@ -442,32 +522,38 @@ const metamask = { } await module.exports.goToAddNetwork(); await playwright.waitAndType( + PROVIDER, addNetworkPageElements.networkNameInput, network.networkName, ); await playwright.waitAndType( + PROVIDER, addNetworkPageElements.rpcUrlInput, network.rpcUrl, ); await playwright.waitAndType( + PROVIDER, addNetworkPageElements.chainIdInput, network.chainId, ); if (network.symbol) { await playwright.waitAndType( + PROVIDER, addNetworkPageElements.symbolInput, network.symbol, ); } if (network.blockExplorer) { await playwright.waitAndType( + PROVIDER, addNetworkPageElements.blockExplorerInput, network.blockExplorer, ); } await playwright.waitAndClick( + PROVIDER, addNetworkPageElements.saveButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -475,6 +561,7 @@ const metamask = { await module.exports.closePopupAndTooltips(); await setNetwork(network); await playwright.waitForText( + PROVIDER, mainPageElements.networkSwitcher.networkName, network.networkName, ); @@ -483,13 +570,17 @@ const metamask = { }, async disconnectWalletFromDapp() { await switchToMetamaskIfNotActive(); - await playwright.waitAndClick(mainPageElements.optionsMenu.button); await playwright.waitAndClick( + PROVIDER, + mainPageElements.optionsMenu.button, + ); + await playwright.waitAndClick( + PROVIDER, mainPageElements.optionsMenu.connectedSitesButton, ); if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.connectedSites.disconnectLabel) .isVisible() ) { @@ -497,9 +588,11 @@ const metamask = { '[disconnectWalletFromDapp] Wallet is connected to a dapp, disconnecting..', ); await playwright.waitAndClick( + PROVIDER, mainPageElements.connectedSites.disconnectLabel, ); await playwright.waitAndClick( + PROVIDER, mainPageElements.connectedSites.disconnectButton, ); } else { @@ -513,12 +606,16 @@ const metamask = { }, async disconnectWalletFromAllDapps() { await switchToMetamaskIfNotActive(); - await playwright.waitAndClick(mainPageElements.optionsMenu.button); await playwright.waitAndClick( + PROVIDER, + mainPageElements.optionsMenu.button, + ); + await playwright.waitAndClick( + PROVIDER, mainPageElements.optionsMenu.connectedSitesButton, ); const disconnectLabels = await playwright - .metamaskWindow() + .windows(PROVIDER) .$$(mainPageElements.connectedSites.disconnectLabel); if (disconnectLabels.length) { console.log( @@ -527,9 +624,11 @@ const metamask = { // eslint-disable-next-line no-unused-vars for (const disconnectLabel of disconnectLabels) { await playwright.waitAndClick( + PROVIDER, mainPageElements.connectedSites.disconnectLabel, ); await playwright.waitAndClick( + PROVIDER, mainPageElements.connectedSites.disconnectButton, ); } @@ -602,11 +701,18 @@ const metamask = { async resetAccount() { await switchToMetamaskIfNotActive(); await module.exports.goToAdvancedSettings(); - await playwright.waitAndClick(advancedPageElements.resetAccountButton); - await playwright.waitAndClick(resetAccountModalElements.resetButton); await playwright.waitAndClick( + PROVIDER, + advancedPageElements.resetAccountButton, + ); + await playwright.waitAndClick( + PROVIDER, + resetAccountModalElements.resetButton, + ); + await playwright.waitAndClick( + PROVIDER, settingsPageElements.closeButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -616,19 +722,21 @@ const metamask = { return true; }, async confirmSignatureRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); if ( await playwright - .metamaskNotificationWindow() + .notificationwindows(PROVIDER) .locator(signaturePageElements.signatureRequestScrollDownButton) .isVisible() ) { await playwright.waitAndClick( + PROVIDER, signaturePageElements.signatureRequestScrollDownButton, notificationPage, ); } await playwright.waitAndClick( + PROVIDER, signaturePageElements.confirmSignatureRequestButton, notificationPage, { waitForEvent: 'close' }, @@ -636,8 +744,9 @@ const metamask = { return true; }, async rejectSignatureRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, signaturePageElements.rejectSignatureRequestButton, notificationPage, { waitForEvent: 'close' }, @@ -645,19 +754,21 @@ const metamask = { return true; }, async confirmDataSignatureRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); if ( await playwright - .metamaskNotificationWindow() + .notificationwindows(PROVIDER) .locator(signaturePageElements.signatureRequestScrollDownButton) .isVisible() ) { await playwright.waitAndClick( + PROVIDER, signaturePageElements.signatureRequestScrollDownButton, notificationPage, ); } await playwright.waitAndClick( + PROVIDER, dataSignaturePageElements.confirmDataSignatureRequestButton, notificationPage, { waitForEvent: 'close' }, @@ -665,8 +776,9 @@ const metamask = { return true; }, async rejectDataSignatureRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, dataSignaturePageElements.rejectDataSignatureRequestButton, notificationPage, { waitForEvent: 'close' }, @@ -679,6 +791,7 @@ const metamask = { await module.exports.goToImportToken(); if (typeof tokenConfig === 'string') { await playwright.waitAndType( + PROVIDER, mainPageElements.importToken.tokenContractAddressInput, tokenConfig, ); @@ -688,13 +801,15 @@ const metamask = { ); } else { await playwright.waitAndType( + PROVIDER, mainPageElements.importToken.tokenContractAddressInput, tokenConfig.address, ); tokenData.tokenContractAddress = tokenConfig.address; await playwright.waitAndClick( + PROVIDER, mainPageElements.importToken.tokenEditButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { force: true, }, @@ -709,22 +824,25 @@ const metamask = { mainPageElements.importToken.tokenDecimalInput, ); await playwright.waitAndClick( + PROVIDER, mainPageElements.importToken.addCustomTokenButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); await playwright.waitAndClick( + PROVIDER, mainPageElements.importToken.importTokensButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, ); await playwright.waitAndClick( + PROVIDER, mainPageElements.asset.backButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -735,8 +853,9 @@ const metamask = { return tokenData; }, async confirmAddToken() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, addTokenPageElements.confirmAddTokenButton, notificationPage, { waitForEvent: 'close' }, @@ -744,8 +863,9 @@ const metamask = { return true; }, async rejectAddToken() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, addTokenPageElements.rejectAddTokenButton, notificationPage, { waitForEvent: 'close' }, @@ -753,11 +873,11 @@ const metamask = { return true; }, async confirmPermissionToSpend(spendLimit) { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); // experimental mode on if ( await playwright - .metamaskNotificationWindow() + .notificationwindows(PROVIDER) .locator(notificationPageElements.customSpendingLimitInput) .isVisible() ) { @@ -767,11 +887,13 @@ const metamask = { notificationPage, ); await playwright.waitAndClick( + PROVIDER, notificationPageElements.allowToSpendButton, notificationPage, ); } await playwright.waitAndClick( + PROVIDER, notificationPageElements.allowToSpendButton, notificationPage, { waitForEvent: 'close' }, @@ -779,8 +901,9 @@ const metamask = { return true; }, async rejectPermissionToSpend() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, notificationPageElements.rejectToSpendButton, notificationPage, { waitForEvent: 'close' }, @@ -788,20 +911,23 @@ const metamask = { return true; }, async acceptAccess(options) { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); if (options && options.allAccounts) { await playwright.waitAndClick( + PROVIDER, notificationPageElements.selectAllCheckbox, notificationPage, ); } await playwright.waitAndClick( + PROVIDER, notificationPageElements.nextButton, notificationPage, { waitForEvent: 'navi' }, ); if (options && options.signInSignature) { await playwright.waitAndClick( + PROVIDER, permissionsPageElements.connectButton, notificationPage, { waitForEvent: 'navi' }, @@ -809,6 +935,7 @@ const metamask = { await module.exports.confirmSignatureRequest(); } else { await playwright.waitAndClick( + PROVIDER, permissionsPageElements.connectButton, notificationPage, { waitForEvent: 'close' }, @@ -818,14 +945,14 @@ const metamask = { }, async confirmTransaction(gasConfig) { let txData = {}; - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); if (gasConfig) { log( '[confirmTransaction] gasConfig is present, determining transaction type..', ); if ( await playwright - .metamaskNotificationWindow() + .notificationwindows(PROVIDER) .locator(confirmPageElements.editGasFeeLegacyButton) .isVisible() ) { @@ -833,12 +960,13 @@ const metamask = { if (typeof gasConfig === 'object') { log('[confirmTransaction] Editing legacy tx..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.editGasFeeLegacyButton, notificationPage, ); if ( await playwright - .metamaskNotificationWindow() + .notificationwindows(PROVIDER) .locator(confirmPageElements.editGasFeeLegacyOverrideAckButton) .isVisible() ) { @@ -846,6 +974,7 @@ const metamask = { '[confirmTransaction] Override acknowledgement modal is present, closing..', ); await playwright.waitAndClick( + PROVIDER, confirmPageElements.editGasFeeLegacyOverrideAckButton, notificationPage, ); @@ -867,6 +996,7 @@ const metamask = { ); } await playwright.waitAndClick( + PROVIDER, confirmPageElements.saveCustomGasFeeButton, notificationPage, ); @@ -878,6 +1008,7 @@ const metamask = { } else { log('[confirmTransaction] Looks like eip-1559 tx'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.editGasFeeButton, notificationPage, ); @@ -885,24 +1016,28 @@ const metamask = { if (gasConfig === 'low') { log('[confirmTransaction] Changing gas fee to low..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.gasOptionLowButton, notificationPage, ); } else if (gasConfig === 'market') { log('[confirmTransaction] Changing gas fee to market..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.gasOptionMediumButton, notificationPage, ); } else if (gasConfig === 'aggressive') { log('[confirmTransaction] Changing gas fee to aggressive..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.gasOptionHighButton, notificationPage, ); } else if (gasConfig === 'site') { log('[confirmTransaction] Changing gas fee to site suggested..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.gasOptionDappSuggestedButton, notificationPage, ); @@ -910,12 +1045,14 @@ const metamask = { } else { log('[confirmTransaction] Editing eip-1559 tx..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.gasOptionCustomButton, notificationPage, ); if (gasConfig.gasLimit) { log('[confirmTransaction] Changing gas limit..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.editGasLimitButton, notificationPage, ); @@ -942,6 +1079,7 @@ const metamask = { ); } await playwright.waitAndClick( + PROVIDER, confirmPageElements.saveCustomGasFeeButton, notificationPage, ); @@ -951,20 +1089,23 @@ const metamask = { log('[confirmTransaction] Checking if recipient address is present..'); if ( await playwright - .metamaskNotificationWindow() + .notificationwindows(PROVIDER) .locator(confirmPageElements.recipientButton) .isVisible() ) { log('[confirmTransaction] Getting recipient address..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.recipientButton, notificationPage, ); txData.recipientPublicAddress = await playwright.waitAndGetValue( + PROVIDER, recipientPopupElements.recipientPublicAddress, notificationPage, ); await playwright.waitAndClick( + PROVIDER, recipientPopupElements.popupCloseButton, notificationPage, ); @@ -972,12 +1113,13 @@ const metamask = { log('[confirmTransaction] Checking if network name is present..'); if ( await playwright - .metamaskNotificationWindow() + .notificationwindows(PROVIDER) .locator(confirmPageElements.networkLabel) .isVisible() ) { log('[confirmTransaction] Getting network name..'); txData.networkName = await playwright.waitAndGetValue( + PROVIDER, confirmPageElements.networkLabel, notificationPage, ); @@ -993,37 +1135,38 @@ const metamask = { // log('[confirmTransaction] Checking if tx data is present..'); // if ( // await playwright - // .metamaskNotificationWindow() + // .notificationwindows(PROVIDER) // .locator(confirmPageElements.dataButton) // .isVisible() // ) { // log('[confirmTransaction] Fetching tx data..'); - // await playwright.waitAndClick( + // await playwright.waitAndClick(PROVIDER, // confirmPageElements.dataButton, // notificationPage, // ); // log('[confirmTransaction] Getting origin value..'); - // txData.origin = await playwright.waitAndGetValue( + // txData.origin = await playwright.waitAndGetValue(PROVIDER, // confirmPageElements.originValue, // notificationPage, // ); // log('[confirmTransaction] Getting bytes value..'); - // txData.bytes = await playwright.waitAndGetValue( + // txData.bytes = await playwright.waitAndGetValue(PROVIDER, // confirmPageElements.bytesValue, // notificationPage, // ); // log('[confirmTransaction] Getting hex data value..'); - // txData.hexData = await playwright.waitAndGetValue( + // txData.hexData = await playwright.waitAndGetValue(PROVIDER, // confirmPageElements.hexDataValue, // notificationPage, // ); - // await playwright.waitAndClick( + // await playwright.waitAndClick(PROVIDER, // confirmPageElements.detailsButton, // notificationPage, // ); // } log('[confirmTransaction] Confirming transaction..'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.confirmButton, notificationPage, { waitForEvent: 'close' }, @@ -1033,8 +1176,9 @@ const metamask = { return txData; }, async rejectTransaction() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, confirmPageElements.rejectButton, notificationPage, { waitForEvent: 'close' }, @@ -1042,8 +1186,9 @@ const metamask = { return true; }, async confirmEncryptionPublicKeyRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, encryptionPublicKeyPageElements.confirmEncryptionPublicKeyButton, notificationPage, { waitForEvent: 'close' }, @@ -1051,8 +1196,9 @@ const metamask = { return true; }, async rejectEncryptionPublicKeyRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, encryptionPublicKeyPageElements.rejectEncryptionPublicKeyButton, notificationPage, { waitForEvent: 'close' }, @@ -1060,8 +1206,9 @@ const metamask = { return true; }, async confirmDecryptionRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, decryptPageElements.confirmDecryptionRequestButton, notificationPage, { waitForEvent: 'close' }, @@ -1069,8 +1216,9 @@ const metamask = { return true; }, async rejectDecryptionRequest() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, decryptPageElements.rejectDecryptionRequestButton, notificationPage, { waitForEvent: 'close' }, @@ -1078,15 +1226,17 @@ const metamask = { return true; }, async allowToAddNetwork({ waitForEvent } = {}) { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); if (waitForEvent) { await playwright.waitAndClick( + PROVIDER, confirmationPageElements.footer.approveButton, notificationPage, { waitForEvent }, ); } else { await playwright.waitAndClick( + PROVIDER, confirmationPageElements.footer.approveButton, notificationPage, ); @@ -1094,8 +1244,9 @@ const metamask = { return true; }, async rejectToAddNetwork() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, confirmationPageElements.footer.cancelButton, notificationPage, { waitForEvent: 'close' }, @@ -1103,8 +1254,9 @@ const metamask = { return true; }, async allowToSwitchNetwork() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, confirmationPageElements.footer.approveButton, notificationPage, { waitForEvent: 'close' }, @@ -1112,8 +1264,9 @@ const metamask = { return true; }, async rejectToSwitchNetwork() { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, confirmationPageElements.footer.cancelButton, notificationPage, { waitForEvent: 'close' }, @@ -1127,45 +1280,48 @@ const metamask = { }, async getWalletAddress() { await switchToMetamaskIfNotActive(); - await playwright.waitAndClick(mainPageElements.optionsMenu.button); await playwright.waitAndClick( + PROVIDER, + mainPageElements.optionsMenu.button, + ); + await playwright.waitAndClick( + PROVIDER, mainPageElements.optionsMenu.accountDetailsButton, ); walletAddress = await playwright.waitAndGetValue( + PROVIDER, mainPageElements.accountModal.walletAddressInput, ); - await playwright.waitAndClick(mainPageElements.accountModal.closeButton); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.accountModal.closeButton, + ); await switchToCypressIfNotActive(); return walletAddress; }, - async initialSetup( - playwrightInstance, - { - secretWordsOrPrivateKey, - network, - password, - enableAdvancedSettings, - enableExperimentalSettings, - }, - ) { + async initialSetup({ + secretWordsOrPrivateKey, + network, + password, + enableAdvancedSettings, + enableExperimentalSettings, + }) { const isCustomNetwork = (process.env.NETWORK_NAME && process.env.RPC_URL && process.env.CHAIN_ID) || typeof network == 'object'; - if (playwrightInstance) { - await playwright.init(playwrightInstance); - } else { - await playwright.init(); - } - await playwright.assignWindows(); - await playwright.assignActiveTabName('metamask'); + + await playwright.init(); + await playwright.assignWindows(PROVIDER); + await playwright.assignActiveTabName(PROVIDER); await module.exports.getExtensionDetails(); - await playwright.fixBlankPage(); - await playwright.fixCriticalError(); + await playwright.fixBlankPage(PROVIDER, playwright.windows(PROVIDER)); + await playwright.switchToWindow(PROVIDER); + await playwright.fixCriticalError(PROVIDER, playwright.windows(PROVIDER)); if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(onboardingWelcomePageElements.onboardingWelcomePage) .isVisible() ) { @@ -1190,7 +1346,7 @@ const metamask = { return true; } else if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(unlockPageElements.passwordInput) .isVisible() ) { @@ -1201,7 +1357,7 @@ const metamask = { } else { if ( (await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.walletOverview) .isVisible()) && !process.env.RESET_METAMASK @@ -1219,7 +1375,7 @@ const metamask = { async function switchToMetamaskIfNotActive() { if (await playwright.isCypressWindowActive()) { - await playwright.switchToMetamaskWindow(); + await playwright.switchToWindow(PROVIDER); switchBackToCypressWindow = true; } return switchBackToCypressWindow; @@ -1247,13 +1403,14 @@ async function activateAdvancedSetting( await metamask.goToAdvancedSettings(); } } - if (!(await playwright.metamaskWindow().locator(toggleOn).isVisible())) { - await playwright.waitAndClick(toggleOff); + if (!(await playwright.windows(PROVIDER).locator(toggleOn).isVisible())) { + await playwright.waitAndClick(PROVIDER, toggleOff); } if (!skipSetup) { await playwright.waitAndClick( + PROVIDER, settingsPageElements.closeButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -1283,8 +1440,9 @@ async function setupSettings( await metamask.activateImprovedTokenAllowance(true); } await playwright.waitAndClick( + PROVIDER, settingsPageElements.closeButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, diff --git a/commands/phantom.js b/commands/phantom.js index 48b647d0e..ed3f7bfec 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -13,8 +13,12 @@ const { transactionPageElements, menu, incorrectModePageElements, + app, } = require('../pages/phantom/notification-page'); const { settingsPageElements } = require('../pages/phantom/settings-page'); +const { selectWalletElements } = require('../pages/phantom/select-wallet-page'); + +const PROVIDER = 'phantom'; let extensionInitialUrl; let extensionId; @@ -51,8 +55,8 @@ module.exports = { }, goTo: async url => { await Promise.all([ - playwright.metamaskWindow().waitForNavigation(), - playwright.metamaskWindow().goto(url), + playwright.windows(PROVIDER).waitForNavigation(), + playwright.windows(PROVIDER).goto(url), ]); await playwright.waitUntilStable(); }, @@ -81,7 +85,7 @@ module.exports = { await module.exports.goTo(extensionImportTokenUrl); }, getExtensionDetails: async () => { - extensionInitialUrl = await playwright.metamaskWindow().url(); + extensionInitialUrl = await playwright.windows(PROVIDER).url(); extensionId = extensionInitialUrl.match('//(.*?)/')[1]; extensionHomeUrl = `chrome-extension://${extensionId}/notification.html`; extensionSettingsUrl = `${extensionHomeUrl}#settings`; @@ -104,27 +108,31 @@ module.exports = { extensionImportTokenUrl, }; }, - // Phantom doesn't need this, it's well coded :) - fixBlankPage: async () => { - return; - }, acceptAccess: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); - await playwright.waitAndClick(buttons.primaryButton, notificationPage); + const notificationPage = await playwright.switchToNotification(PROVIDER); + await playwright.waitAndClick( + PROVIDER, + buttons.primaryButton, + notificationPage, + ); return true; }, importWallet: async (secretWords, password) => { - await playwright.waitAndClick(firstTimeFlowPageElements.importWalletButton); - + await playwright.waitAndClick( + PROVIDER, + firstTimeFlowPageElements.importWalletButton, + ); // STEP: Input mnemonic words and click Import // todo: add support for more secret words (15/18/21/24) for (const [index, word] of secretWords.split(' ').entries()) { await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.secretWordsInput(index), word, ); } await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.confirmWordsButton, ); @@ -132,34 +140,41 @@ module.exports = { // shortcut confirmation await new Promise(resolve => setTimeout(resolve, 200)); // the transitioning is too fast await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.confirmWordsButton, // 'button:text("Import Selected Accounts")', ); // STEP: Input password, confirm and continue await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.passwordInput, password, ); await playwright.waitAndType( + PROVIDER, firstTimeFlowImportPageElements.confirmPasswordInput, password, ); await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.termsCheckbox, ); // continue to next screen await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.continueAfterPasswordButton, ); // shortcut confirmation await new Promise(resolve => setTimeout(resolve, 500)); // the transitioning is too fast await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.continueOnShortcutConfirm, ); // finish await new Promise(resolve => setTimeout(resolve, 500)); // the transitioning is too fast await playwright.waitAndClick( + PROVIDER, firstTimeFlowImportPageElements.continueOnShortcutConfirm, ); return true; @@ -167,36 +182,40 @@ module.exports = { closePopupAndTooltips: async () => { // note: this is required for fast execution of e2e tests to avoid flakiness // otherwise popup may not be detected properly and not closed - await playwright.metamaskWindow().waitForTimeout(1000); + await playwright.windows(PROVIDER).waitForTimeout(1000); if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.popup.container) .isVisible() ) { const popupBackground = playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.popup.background); const popupBackgroundBox = await popupBackground.boundingBox(); await playwright - .metamaskWindow() + .windows(PROVIDER) .mouse.click(popupBackgroundBox.x + 1, popupBackgroundBox.y + 1); } if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.tippyTooltip.closeButton) .isVisible() ) { - await playwright.waitAndClick(mainPageElements.tippyTooltip.closeButton); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.tippyTooltip.closeButton, + ); } if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.actionableMessage.closeButton) .isVisible() ) { await playwright.waitAndClick( + PROVIDER, mainPageElements.actionableMessage.closeButton, ); } @@ -204,25 +223,30 @@ module.exports = { }, getWalletAddress: async () => { await switchToPhantomIfNotActive(); - await playwright.metamaskWindow().hover(mainPageElements.accountBar.title); + await playwright.windows(PROVIDER).hover(mainPageElements.accountBar.title); await new Promise(resolve => setTimeout(resolve, 100)); - await playwright.waitAndClick(mainPageElements.accountBar.ethRow); + await playwright.waitAndClick(PROVIDER, mainPageElements.accountBar.ethRow); walletAddress = await playwright - .metamaskWindow() + .windows(PROVIDER) .evaluate('navigator.clipboard.readText()'); await switchToCypressIfNotActive(); return walletAddress; }, initialSetup: async ({ secretWordsOrPrivateKey, password }) => { await playwright.init(); - await playwright.assignWindows(); - await playwright.assignActiveTabName('metamask'); + await playwright.assignWindows(PROVIDER); + await playwright.assignActiveTabName(PROVIDER); await module.exports.getExtensionDetails(); - await module.exports.fixBlankPage(); + await playwright.fixBlankPage( + PROVIDER, + playwright.windows(PROVIDER), + app.root, + ); + await playwright.switchToWindow(PROVIDER); if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(firstTimeFlowPageElements.importWalletButton) .isVisible() ) { @@ -239,11 +263,11 @@ module.exports = { // skip what's new header if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.whatsNew.header) ) { await playwright - .metamaskWindow() + .windows(PROVIDER) .click(mainPageElements.whatsNew.continueButton); } walletAddress = await module.exports.getWalletAddress(); @@ -254,7 +278,7 @@ module.exports = { * PASSWORD UNLOCK */ await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(unlockPageElements.passwordInput) .isVisible() ) { @@ -265,7 +289,7 @@ module.exports = { } else { if ( (await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.walletOverview) .isVisible()) && !process.env.RESET_PHANTOM @@ -280,8 +304,9 @@ module.exports = { } }, confirmSignatureRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, signaturePageElements.buttons.confirmSign, notificationPage, { waitForEvent: 'close' }, @@ -289,8 +314,9 @@ module.exports = { return true; }, rejectSignatureRequest: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, signaturePageElements.buttons.rejectSign, notificationPage, { waitForEvent: 'close' }, @@ -298,8 +324,9 @@ module.exports = { return true; }, confirmTransaction: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, transactionPageElements.buttons.rejectSign, notificationPage, { waitForEvent: 'close' }, @@ -307,8 +334,9 @@ module.exports = { return true; }, confirmIncorrectNetworkStage: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); + const notificationPage = await playwright.switchToNotification(PROVIDER); await playwright.waitAndClick( + PROVIDER, incorrectModePageElements.buttons.close, notificationPage, { waitForEvent: 'close' }, @@ -316,36 +344,71 @@ module.exports = { return true; }, unlock: async password => { - await playwright.waitAndType(unlockPageElements.passwordInput, password); + await playwright.waitAndType( + PROVIDER, + unlockPageElements.passwordInput, + password, + ); await playwright.waitAndClick( + PROVIDER, unlockPageElements.unlockButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), ); await module.exports.closePopupAndTooltips(); return true; }, lock: async () => { await playwright.waitAndClick( + PROVIDER, menu.buttons.settings, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), ); await playwright.waitAndClick( + PROVIDER, menu.buttons.sidebar.settings, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), ); await playwright.waitAndClick( + PROVIDER, settingsPageElements.buttons.lockWallet, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), ); await module.exports.closePopupAndTooltips(); return true; }, + selectWallet: async (wallet = 'metamask', mode = 'once') => { + const notificationPage = await playwright.switchToNotification(PROVIDER); + if (wallet === 'metamask') { + await playwright.waitAndClick( + PROVIDER, + selectWalletElements.buttons.continueWithMetamask, + notificationPage, + ); + return true; + } + + if (mode === 'always') { + await playwright.waitAndClick( + PROVIDER, + selectWalletElements.buttons.alwaysUsePhantom, + notificationPage, + ); + return true; + } + + await playwright.waitAndClick( + PROVIDER, + selectWalletElements.buttons.continueWithPhantom, + notificationPage, + ); + return true; + }, }; async function switchToPhantomIfNotActive() { - await playwright.switchToMetamaskWindow(); + await playwright.switchToWindow(PROVIDER); if (await playwright.isCypressWindowActive()) { - await playwright.switchToMetamaskWindow(); + await playwright.switchToWindow(PROVIDER); switchBackToCypressWindow = true; } return switchBackToCypressWindow; diff --git a/commands/playwright.js b/commands/playwright.js index 383122aca..966bd4a16 100644 --- a/commands/playwright.js +++ b/commands/playwright.js @@ -12,11 +12,13 @@ const sleep = require('util').promisify(setTimeout); let browser; let mainWindow; -let metamaskWindow; -let metamaskNotificationWindow; let activeTabName; let retries = 0; +let pageWindows = {}; +let notificationWindows = {}; +let extensions = {}; // name, id + module.exports = { browser() { return browser; @@ -24,11 +26,11 @@ module.exports = { mainWindow() { return mainWindow; }, - metamaskWindow() { - return metamaskWindow; + notificationWindow(provider) { + return notificationWindows[provider]; }, - metamaskNotificationWindow() { - return metamaskNotificationWindow; + windows(provider) { + return pageWindows[provider]; }, activeTabName() { return activeTabName; @@ -53,21 +55,47 @@ module.exports = { } else { browser = await chromium.connectOverCDP(webSocketDebuggerUrl); } + + // get extension details + const pagesResponse = await fetch('http://127.0.0.1:9222/json'); + const pages = await pagesResponse.json(); + + extensions = pages + .filter( + page => + page.url.startsWith('chrome-extension://') && + !page.url.endsWith('/_generated_background_page.html'), // cypress + ) + .map(extension => { + const matches = extension.url.match(/chrome-extension:\/\/(.*)\/.*/); + return { + name: + extension.title === 'Phantom Wallet' + ? 'phantom' + : extension.title.toLowerCase(), + id: matches[1], + }; + }) + .reduce((prev, curr) => ({ ...prev, [curr.name]: curr }), {}); + return browser.isConnected(); }, async clear() { browser = null; return true; }, - async assignWindows() { + async assignWindows(provider) { let pages = await browser.contexts()[0].pages(); for (const page of pages) { if (page.url().includes('runner')) { mainWindow = page; - } else if (page.url().includes('extension')) { - metamaskWindow = page; - } else if (page.url().includes('notification')) { - metamaskNotificationWindow = page; + } else if ( + page.url().includes(extensions[provider].id) && + page.url().includes('notification') + ) { + notificationWindows[provider] = page; + } else if (page.url().includes(extensions[provider].id)) { + pageWindows[provider] = page; } } return true; @@ -78,18 +106,18 @@ module.exports = { }, async clearWindows() { mainWindow = null; - metamaskWindow = null; - metamaskNotificationWindow = null; + pageWindows = {}; + notificationWindows = {}; return true; }, async isCypressWindowActive() { return activeTabName === 'cypress'; }, - async isMetamaskWindowActive() { - return activeTabName === 'metamask'; + async isWindowActive(provider) { + return activeTabName === provider; }, - async isMetamaskNotificationWindowActive() { - return activeTabName === 'metamask-notif'; + async isNotificationWindowActive(provider) { + return activeTabName === `${provider}-notif`; }, async switchToCypressWindow() { if (mainWindow) { @@ -98,51 +126,54 @@ module.exports = { } return true; }, - switchToMetamaskWindow: async () => { - if (metamaskWindow.isClosed()) { + switchToWindow: async provider => { + if (pageWindows[provider].isClosed()) { const newPage = await browser.contexts()[0].newPage(); - if (process.env.PROVIDER === 'phantom') { + if (provider === 'phantom') { await Promise.all([ newPage.waitForNavigation(), newPage.goto( - metamaskWindow.url().replace('onboarding.html', 'popup.html'), + pageWindows[provider] + .url() + .replace('onboarding.html', 'popup.html'), ), ]); } else { await Promise.all([ newPage.waitForNavigation(), - newPage.goto(metamaskWindow.url()), + newPage.goto(pageWindows[provider].url()), ]); - await newPage.waitUntilStable(); + await newPage.waitUntilStable(provider); } - metamaskWindow = newPage; + pageWindows[provider] = newPage; } - await metamaskWindow.bringToFront(); - await module.exports.assignActiveTabName('metamask'); + await pageWindows[provider].bringToFront(); + await module.exports.assignActiveTabName(provider); return true; }, - async switchToMetamaskNotificationWindow() { - await metamaskNotificationWindow.bringToFront(); - await module.exports.assignActiveTabName('metamask-notif'); + async switchToNotificationWindow(provider) { + await notificationWindows[provider].bringToFront(); + await module.exports.assignActiveTabName(`${provider}-notif`); return true; }, - async switchToMetamaskNotification() { + async switchToNotification(provider) { let pages = await browser.contexts()[0].pages(); // loop reverse, chance is very high that it's the last window for (let i = pages.length - 1; i >= 0; i--) { const page = pages[i]; if (page.url().includes('notification')) { - metamaskNotificationWindow = page; + notificationWindows[provider] = page; retries = 0; await page.bringToFront(); - await module.exports.waitUntilStable(page); - if (process.env.PROVIDER === 'phantom') { - await module.exports.waitFor(app.root, page); + await module.exports.waitUntilStable(provider, page); + if (provider === 'phantom') { + await module.exports.waitFor(provider, app.root, page); } else { await module.exports.waitFor( + provider, notificationPageElements.notificationAppContent, page, ); @@ -154,16 +185,16 @@ module.exports = { await sleep(200); if (retries < 50) { retries++; - return await module.exports.switchToMetamaskNotification(); + return await module.exports.switchToNotification(provider); } else if (retries >= 50) { retries = 0; throw new Error( - '[switchToMetamaskNotification] Max amount of retries to switch to metamask notification window has been reached. It was never found.', + `[switchToNotification: ${provider}] Max amount of retries to switch to metamask notification window has been reached. It was never found.`, ); } }, - async waitFor(selector, page = metamaskWindow) { - await module.exports.waitUntilStable(page); + async waitFor(provider, selector, page = pageWindows[provider]) { + await module.exports.waitUntilStable(provider, page); await page.waitForSelector(selector, { strict: false }); const element = page.locator(selector).first(); await element.waitFor(); @@ -177,8 +208,13 @@ module.exports = { } return element; }, - async waitAndClick(selector, page = metamaskWindow, args = {}) { - const element = await module.exports.waitFor(selector, page); + async waitAndClick( + provider, + selector, + page = pageWindows[provider], + args = {}, + ) { + const element = await module.exports.waitFor(provider, selector, page); if (args.numberOfClicks && !args.waitForEvent) { await element.click({ clickCount: args.numberOfClicks, @@ -205,26 +241,31 @@ module.exports = { await element.click({ force: args.force }); } if (process.env.PROVIDER !== 'phantom') { - await module.exports.waitUntilStable(); + await module.exports.waitUntilStable(provider); } return element; }, - async waitAndClickByText(selector, text, page = metamaskWindow) { - await module.exports.waitFor(selector, page); + async waitAndClickByText( + provider, + selector, + text, + page = pageWindows[provider], + ) { + await module.exports.waitFor(provider, selector, page); const element = page.locator(`text=${text}`); await element.click(); - await module.exports.waitUntilStable(); + await module.exports.waitUntilStable(provider); }, - async waitAndType(selector, value, page = metamaskWindow) { - const element = await module.exports.waitFor(selector, page); + async waitAndType(provider, selector, value, page = pageWindows[provider]) { + const element = await module.exports.waitFor(provider, selector, page); await element.type(value); - await module.exports.waitUntilStable(page); + await module.exports.waitUntilStable(provider, page); }, - async waitAndGetValue(selector, page = metamaskWindow) { + async waitAndGetValue(provider, selector, page = pageWindows[provider]) { const expect = global.expect ? global.expect : require('@playwright/test').expect; - const element = await module.exports.waitFor(selector, page); + const element = await module.exports.waitFor(provider, selector, page); await expect(element).toHaveText(/[a-zA-Z0-9]/, { ignoreCase: true, useInnerText: true, @@ -232,60 +273,93 @@ module.exports = { const value = await element.innerText(); return value; }, - async waitAndGetInputValue(selector, page = metamaskWindow) { + async waitAndGetInputValue(provider, selector, page = pageWindows[provider]) { const expect = global.expect ? global.expect : require('@playwright/test').expect; - const element = await module.exports.waitFor(selector, page); + const element = await module.exports.waitFor(provider, selector, page); await expect(element).toHaveValue(/[a-zA-Z1-9]/); const value = await element.inputValue(); return value; }, - async waitAndGetAttributeValue(selector, attribute, page = metamaskWindow) { + async waitAndGetAttributeValue( + provider, + selector, + attribute, + page = pageWindows[provider], + ) { const expect = global.expect ? global.expect : require('@playwright/test').expect; - const element = await module.exports.waitFor(selector, page); + const element = await module.exports.waitFor(provider, selector, page); await expect(element).toHaveAttribute(attribute, /[a-zA-Z0-9]/); const attrValue = await element.getAttribute(attribute); return attrValue; }, - async waitAndSetValue(text, selector, page = metamaskWindow) { - const element = await module.exports.waitFor(selector, page); + async waitAndSetValue( + provider, + text, + selector, + page = pageWindows[provider], + ) { + const element = await module.exports.waitFor(provider, selector, page); await element.fill(''); - await module.exports.waitUntilStable(page); + await module.exports.waitUntilStable(provider, page); await element.fill(text); - await module.exports.waitUntilStable(page); - }, - async waitAndClearWithBackspace(selector, page = metamaskWindow) { - await module.exports.waitFor(selector, page); + await module.exports.waitUntilStable(provider, page); + }, + async waitAndClearWithBackspace( + provider, + selector, + page = pageWindows[provider], + ) { + await module.exports.waitFor(provider, selector, page); const inputValue = await page.evaluate(selector, el => el.value); for (let i = 0; i < inputValue.length; i++) { await page.keyboard.press('Backspace'); - await module.exports.waitUntilStable(page); + await module.exports.waitUntilStable(provider, page); } }, - async waitClearAndType(text, selector, page = metamaskWindow) { - const element = await module.exports.waitAndClick(selector, page, { - numberOfClicks: 3, - }); - await module.exports.waitUntilStable(page); + async waitClearAndType( + provider, + text, + selector, + page = pageWindows[provider], + ) { + const element = await module.exports.waitAndClick( + provider, + selector, + page, + { + numberOfClicks: 3, + }, + ); + await module.exports.waitUntilStable(provider, page); await element.type(text); - await module.exports.waitUntilStable(page); - }, - async waitForText(selector, text, page = metamaskWindow) { - await module.exports.waitFor(selector, page); + await module.exports.waitUntilStable(provider, page); + }, + async waitForText( + provider, + selector, + text, + page = module.exports.windows(provider), + ) { + await module.exports.waitFor(provider, selector, page); const element = page.locator(selector, { hasText: text }); await element.waitFor(); }, - async waitToBeHidden(selector, page = metamaskWindow) { + async waitToBeHidden( + provider, + selector, + page = module.exports.windows(provider), + ) { // info: waits for 60 seconds const locator = page.locator(selector); for (const element of await locator.all()) { if ((await element.isVisible()) && retries < 300) { retries++; await page.waitForTimeout(200); - await module.exports.waitToBeHidden(selector, page); + await module.exports.waitToBeHidden(provider, selector, page); } else if (retries >= 300) { retries = 0; throw new Error( @@ -295,17 +369,19 @@ module.exports = { retries = 0; } }, - async waitUntilStable(page) { + async waitUntilStable(provider, page) { if (page && page.url().includes('notification')) { await page.waitForLoadState('load'); await page.waitForLoadState('domcontentloaded'); await page.waitForLoadState('networkidle'); - await module.exports.waitUntilNotificationWindowIsStable(); + await module.exports.waitUntilNotificationWindowIsStable(provider); + } + await module.exports.windows(provider).waitForLoadState('load'); + await module.exports.windows(provider).waitForLoadState('domcontentloaded'); + await module.exports.windows(provider).waitForLoadState('networkidle'); + if (provider != 'phantom') { + await module.exports.waitUntilWindowIsStable(provider); } - await metamaskWindow.waitForLoadState('load'); - await metamaskWindow.waitForLoadState('domcontentloaded'); - await metamaskWindow.waitForLoadState('networkidle'); - await module.exports.waitUntilMetamaskWindowIsStable(); if (mainWindow) { await mainWindow.waitForLoadState('load'); await mainWindow.waitForLoadState('domcontentloaded'); @@ -313,21 +389,42 @@ module.exports = { // await mainWindow.waitForLoadState('networkidle'); } }, - async waitUntilNotificationWindowIsStable(page = metamaskNotificationWindow) { + async waitUntilNotificationWindowIsStable( + provider, + page = module.exports.windows(provider), + ) { await module.exports.waitToBeHidden( + provider, notificationPageElements.loadingLogo, page, ); await module.exports.waitToBeHidden( + provider, notificationPageElements.loadingSpinner, page, ); }, - async waitUntilMetamaskWindowIsStable(page = metamaskWindow) { - await module.exports.waitToBeHidden(pageElements.loadingLogo, page); // shown on reload - await module.exports.waitToBeHidden(pageElements.loadingSpinner, page); // shown on reload - await module.exports.waitToBeHidden(pageElements.loadingOverlay, page); // shown on change network + async waitUntilWindowIsStable( + provider, + page = module.exports.windows(provider), + ) { + await module.exports.waitToBeHidden( + provider, + pageElements.loadingLogo, + page, + ); // shown on reload + await module.exports.waitToBeHidden( + provider, + pageElements.loadingSpinner, + page, + ); // shown on reload await module.exports.waitToBeHidden( + provider, + pageElements.loadingOverlay, + page, + ); // shown on change network + await module.exports.waitToBeHidden( + provider, pageElements.loadingOverlaySpinner, page, ); // shown on balance load @@ -336,37 +433,46 @@ module.exports = { await page.locator(pageElements.loadingOverlayErrorButtons).isVisible() ) { await module.exports.waitAndClick( + provider, pageElements.loadingOverlayErrorButtonsRetryButton, page, ); - await module.exports.waitToBeHidden(pageElements.loadingOverlay, page); + await module.exports.waitToBeHidden( + provider, + pageElements.loadingOverlay, + page, + ); } - await module.exports.fixCriticalError(); + await module.exports.fixCriticalError(provider, page); }, // workaround for metamask random blank page on first run - async fixBlankPage(page = metamaskWindow) { + async fixBlankPage( + provider, + page = module.exports.windows(provider), + appRoot = onboardingWelcomePageElements.app, + ) { + await page.waitForTimeout(1000); for (let times = 0; times < 5; times++) { - if ( - (await page.locator(onboardingWelcomePageElements.app).count()) === 0 - ) { + if ((await page.locator(appRoot).count()) === 0) { await page.reload(); - await module.exports.waitUntilMetamaskWindowIsStable(); + await module.exports.waitUntilWindowIsStable(provider, page); } else { break; } } }, - async fixCriticalError(page = metamaskWindow) { + async fixCriticalError(provider, page = module.exports.windows(provider)) { for (let times = 0; times < 5; times++) { if ((await page.locator(pageElements.criticalError).count()) > 0) { if (times < 3) { await page.reload(); } else { await module.exports.waitAndClick( + provider, pageElements.criticalErrorRestartButton, ); } - await module.exports.waitUntilMetamaskWindowIsStable(); + await module.exports.waitUntilWindowIsStable(provider); } else { break; } diff --git a/helpers.js b/helpers.js index 4d7f629fb..2225d4aff 100644 --- a/helpers.js +++ b/helpers.js @@ -57,7 +57,7 @@ module.exports = { }, getSynpressPath() { if (process.env.SYNPRESS_LOCAL_TEST) { - return '.'; + return './node_modules/@phantom/synpress'; } else { return path.dirname(require.resolve(packageJson.name)); } @@ -205,7 +205,7 @@ module.exports = { } } }, - download: async (url, destination) => { + download: async (provider, url, destination) => { try { log( `Trying to download and extract file from: ${url} to following path: ${destination}`, @@ -222,7 +222,9 @@ module.exports = { /** * Some extensions will zip their dist folder */ - await moveFiles(`${destination}/dist`, destination); + if (provider === 'phantom') { + await moveFiles(`${destination}/dist`, destination); + } } else { await download(url, destination, { extract: true, @@ -234,9 +236,9 @@ module.exports = { ); } }, - prepareProvider: async version => { + prepareProvider: async (provider, version) => { const release = - process.env.PROVIDER === 'phantom' + provider === 'phantom' ? await module.exports.getPhantomReleases(version) : await module.exports.getMetamaskReleases(version); const downloadsDirectory = path.resolve(__dirname, 'downloads'); @@ -254,7 +256,11 @@ module.exports = { providerManifestFilePath, ); if (!providerkDirectoryExists && !providerManifestFileExists) { - await module.exports.download(release.downloadUrl, providerDirectory); + await module.exports.download( + provider, + release.downloadUrl, + providerDirectory, + ); } else { log('provider is already downloaded'); } diff --git a/pages/metamask/main-page.js b/pages/metamask/main-page.js index e1c1f4864..425da3387 100644 --- a/pages/metamask/main-page.js +++ b/pages/metamask/main-page.js @@ -67,6 +67,7 @@ const accountMenu = { createAccountButton: '.account-menu__item--clickable:nth-child(6)', importAccountButton: '.account-menu__item--clickable:nth-child(7)', settingsButton: '.account-menu__item--clickable:nth-child(11)', + lockButton: '.account_menu__lock-button', }; const optionsMenu = { diff --git a/pages/phantom/select-wallet-page.js b/pages/phantom/select-wallet-page.js new file mode 100644 index 000000000..47a894818 --- /dev/null +++ b/pages/phantom/select-wallet-page.js @@ -0,0 +1,7 @@ +module.exports.selectWalletElements = { + buttons: { + continueWithPhantom: '[data-testid="select_wallet--phantom"]', + continueWithMetamask: '[data-testid="select_wallet--metamask"]', + alwaysUsePhantom: '[data-testid="metamask_override-always_use_phantom"]', + }, +}; diff --git a/plugins/index.js b/plugins/index.js index 78524d60c..b88b311ac 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -1,12 +1,19 @@ const helpers = require('../helpers'); const playwright = require('../commands/playwright'); -const provider = - process.env.PROVIDER === 'phantom' - ? require('../commands/phantom') - : require('../commands/metamask'); const synthetix = require('../commands/synthetix'); const etherscan = require('../commands/etherscan'); +const metamaskProvider = require('../commands/metamask'); +const phantomProvider = require('../commands/phantom'); + +const providersHelper = require('../providers'); + +const providerMap = { + metamask: metamaskProvider, + phantom: phantomProvider, +}; +let selectedProvider = 'metamask'; + /** * @type {Cypress.PluginConfig} */ @@ -14,6 +21,10 @@ module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config + function getProvider(providerName = selectedProvider) { + return providerMap[providerName]; + } + on('before:browser:launch', async (browser = {}, arguments_) => { if (browser.name === 'chrome') { arguments_.args.push('--window-size=1920,1080'); // optional @@ -48,14 +59,26 @@ module.exports = (on, config) => { }; } - if (!process.env.SKIP_METAMASK_INSTALL) { - // NOTE: extensions cannot be loaded in headless Chrome - const providerPath = await helpers.prepareProvider( - process.env.PROVIDER_VERSION || '10.25.0', - ); + if (process.env.PROVIDERS) { + process.env.CYPRESS_PROVIDERS = process.env.PROVIDERS; + const providers = providersHelper.getProviders(process.env.PROVIDERS); + for (const provider of providers) { + const providerPath = await helpers.prepareProvider( + provider.name, + provider.version || 'latest', + ); - arguments_.extensions.push(providerPath); + arguments_.extensions.push(providerPath); + } } + // if (!process.env.SKIP_METAMASK_INSTALL) { + // // NOTE: extensions cannot be loaded in headless Chrome + // const providerPath = await helpers.prepareProvider( + // process.env.PROVIDER_VERSION || '10.25.0', + // ); + + // arguments_.extensions.push(providerPath); + // } return arguments_; }); @@ -69,6 +92,10 @@ module.exports = (on, config) => { console.warn('\u001B[33m', 'WARNING:', message, '\u001B[0m'); return true; }, + selectProvider(providerName) { + selectedProvider = providerName; + return true; + }, // playwright commands initPlaywright: async () => { const connected = await playwright.init(); @@ -78,8 +105,8 @@ module.exports = (on, config) => { const cleared = await playwright.clear(); return cleared; }, - assignWindows: async () => { - const assigned = await playwright.assignWindows(); + assignWindows: async provider => { + const assigned = await playwright.assignWindows(provider); return assigned; }, clearWindows: async () => { @@ -90,9 +117,9 @@ module.exports = (on, config) => { const assigned = await playwright.assignActiveTabName(tabName); return assigned; }, - isMetamaskWindowActive: async () => { - const isMetamaskActive = await playwright.isMetamaskWindowActive(); - return isMetamaskActive; + isWindowActive: async provider => { + const isActive = await playwright.isWindowActive(provider); + return isActive; }, isCypressWindowActive: async () => { const isCypressActive = await playwright.isCypressWindowActive(); @@ -102,12 +129,12 @@ module.exports = (on, config) => { const switched = await playwright.switchToCypressWindow(); return switched; }, - switchToMetamaskWindow: async () => { - const switched = await playwright.switchToMetamaskWindow(); + switchToWindow: async provider => { + const switched = await playwright.switchToWindow(provider); return switched; }, - switchToMetamaskNotification: async () => { - const notificationPage = await playwright.switchToMetamaskNotification(); + switchToNotification: async provider => { + const notificationPage = await playwright.switchToNotification(provider); return notificationPage; }, /** @@ -117,29 +144,37 @@ module.exports = (on, config) => { return module.exports.unlock(password); }, unlock: async password => { - const unlocked = await provider.unlock(password); + const unlocked = await getProvider(selectedProvider).unlock(password); return unlocked; }, lock: () => { - return provider.lock(); + return getProvider(selectedProvider).lock(); }, confirmIncorrectNetworkStage: () => { - return provider.confirmIncorrectNetworkStage(); + return getProvider(selectedProvider).confirmIncorrectNetworkStage(); }, importMetamaskAccount: async privateKey => { - const imported = await provider.importAccount(privateKey); + const imported = await getProvider(selectedProvider).importAccount( + privateKey, + ); return imported; }, createMetamaskAccount: async accountName => { - const created = await provider.createAccount(accountName); + const created = await getProvider(selectedProvider).createAccount( + accountName, + ); return created; }, switchMetamaskAccount: async accountNameOrAccountNumber => { - const switched = await provider.switchAccount(accountNameOrAccountNumber); + const switched = await getProvider(selectedProvider).switchAccount( + accountNameOrAccountNumber, + ); return switched; }, addMetamaskNetwork: async network => { - const networkAdded = await provider.addNetwork(network); + const networkAdded = await getProvider(selectedProvider).addNetwork( + network, + ); return networkAdded; }, changeMetamaskNetwork: async network => { @@ -148,57 +183,79 @@ module.exports = (on, config) => { } else if (!network) { network = 'goerli'; } - const networkChanged = await provider.changeNetwork(network); + const networkChanged = await getProvider(selectedProvider).changeNetwork( + network, + ); return networkChanged; }, activateAdvancedGasControlInMetamask: async skipSetup => { - const activated = await provider.activateAdvancedGasControl(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateAdvancedGasControl(skipSetup); return activated; }, activateEnhancedTokenDetectionInMetamask: async skipSetup => { - const activated = await provider.activateEnhancedTokenDetection( - skipSetup, - ); + const activated = await getProvider( + selectedProvider, + ).activateEnhancedTokenDetection(skipSetup); return activated; }, activateShowHexDataInMetamask: async skipSetup => { - const activated = await provider.activateShowHexData(skipSetup); + const activated = await getProvider(selectedProvider).activateShowHexData( + skipSetup, + ); return activated; }, activateTestnetConversionInMetamask: async skipSetup => { - const activated = await provider.activateTestnetConversion(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateTestnetConversion(skipSetup); return activated; }, activateShowTestnetNetworksInMetamask: async skipSetup => { - const activated = await provider.activateShowTestnetNetworks(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateShowTestnetNetworks(skipSetup); return activated; }, activateCustomNonceInMetamask: async skipSetup => { - const activated = await provider.activateCustomNonce(skipSetup); + const activated = await getProvider(selectedProvider).activateCustomNonce( + skipSetup, + ); return activated; }, activateDismissBackupReminderInMetamask: async skipSetup => { - const activated = await provider.activateDismissBackupReminder(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateDismissBackupReminder(skipSetup); return activated; }, activateEnhancedGasFeeUIInMetamask: async skipSetup => { - const activated = await provider.activateEnhancedGasFeeUI(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateEnhancedGasFeeUI(skipSetup); return activated; }, activateShowCustomNetworkListInMetamask: async skipSetup => { - const activated = await provider.activateShowCustomNetworkList(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateShowCustomNetworkList(skipSetup); return activated; }, resetMetamaskAccount: async () => { - const resetted = await provider.resetAccount(); + const resetted = await getProvider(selectedProvider).resetAccount(); return resetted; }, disconnectMetamaskWalletFromDapp: async () => { - const disconnected = await provider.disconnectWalletFromDapp(); + const disconnected = await getProvider( + selectedProvider, + ).disconnectWalletFromDapp(); return disconnected; }, disconnectMetamaskWalletFromAllDapps: async () => { - const disconnected = await provider.disconnectWalletFromAllDapps(); + const disconnected = await getProvider( + selectedProvider, + ).disconnectWalletFromAllDapps(); return disconnected; }, @@ -209,56 +266,85 @@ module.exports = (on, config) => { return module.exports.confirmSignatureRequest(); }, confirmSignatureRequest: async () => { - const confirmed = await provider.confirmSignatureRequest(); + const confirmed = await getProvider( + selectedProvider, + ).confirmSignatureRequest(); return confirmed; }, confirmMetamaskDataSignatureRequest: async () => { - const confirmed = await provider.confirmDataSignatureRequest(); + const confirmed = await getProvider( + selectedProvider, + ).confirmDataSignatureRequest(); return confirmed; }, rejectMetamaskSignatureRequest: async () => { - const rejected = await provider.rejectSignatureRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectSignatureRequest(); return rejected; }, rejectMetamaskDataSignatureRequest: async () => { - const rejected = await provider.rejectDataSignatureRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectDataSignatureRequest(); return rejected; }, confirmMetamaskEncryptionPublicKeyRequest: async () => { - const confirmed = await provider.confirmEncryptionPublicKeyRequest(); + const confirmed = await getProvider( + selectedProvider, + ).confirmEncryptionPublicKeyRequest(); return confirmed; }, rejectMetamaskEncryptionPublicKeyRequest: async () => { - const rejected = await provider.rejectEncryptionPublicKeyRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectEncryptionPublicKeyRequest(); return rejected; }, confirmMetamaskDecryptionRequest: async () => { - const confirmed = await provider.confirmDecryptionRequest(); + const confirmed = await getProvider( + selectedProvider, + ).confirmDecryptionRequest(); return confirmed; }, rejectMetamaskDecryptionRequest: async () => { - const rejected = await provider.rejectDecryptionRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectDecryptionRequest(); return rejected; }, importMetamaskToken: async tokenConfig => { - const imported = await provider.importToken(tokenConfig); + const imported = await getProvider(selectedProvider).importToken( + tokenConfig, + ); return imported; }, + selectWallet: async (wallet, mode) => { + const result = await getProvider(selectedProvider).selectWallet( + wallet, + mode, + ); + return result; + }, confirmMetamaskAddToken: async () => { - const confirmed = await provider.confirmAddToken(); + const confirmed = await getProvider(selectedProvider).confirmAddToken(); return confirmed; }, rejectMetamaskAddToken: async () => { - const rejected = await provider.rejectAddToken(); + const rejected = await getProvider(selectedProvider).rejectAddToken(); return rejected; }, confirmMetamaskPermissionToSpend: async () => { - const confirmed = await provider.confirmPermissionToSpend(); + const confirmed = await getProvider( + selectedProvider, + ).confirmPermissionToSpend(); return confirmed; }, rejectMetamaskPermissionToSpend: async () => { - const rejected = await provider.rejectPermissionToSpend(); + const rejected = await getProvider( + selectedProvider, + ).rejectPermissionToSpend(); return rejected; }, @@ -269,7 +355,9 @@ module.exports = (on, config) => { return module.exports.acceptAccess(options); }, acceptAccess: async options => { - const accepted = await provider.acceptAccess(options); + const accepted = await getProvider(selectedProvider).acceptAccess( + options, + ); return accepted; }, @@ -280,36 +368,48 @@ module.exports = (on, config) => { return module.exports.confirmTransaction(gasConfig); }, confirmTransaction: async gasConfig => { - const confirmed = await provider.confirmTransaction(gasConfig); + const confirmed = await getProvider(selectedProvider).confirmTransaction( + gasConfig, + ); return confirmed; }, rejectMetamaskTransaction: async () => { - const rejected = await provider.rejectTransaction(); + const rejected = await getProvider(selectedProvider).rejectTransaction(); return rejected; }, allowMetamaskToAddNetwork: async ({ waitForEvent }) => { - const allowed = await provider.allowToAddNetwork({ waitForEvent }); + const allowed = await getProvider(selectedProvider).allowToAddNetwork({ + waitForEvent, + }); return allowed; }, rejectMetamaskToAddNetwork: async () => { - const rejected = await provider.rejectToAddNetwork(); + const rejected = await getProvider(selectedProvider).rejectToAddNetwork(); return rejected; }, allowMetamaskToSwitchNetwork: async () => { - const allowed = await provider.allowToSwitchNetwork(); + const allowed = await getProvider( + selectedProvider, + ).allowToSwitchNetwork(); return allowed; }, rejectMetamaskToSwitchNetwork: async () => { - const rejected = await provider.rejectToSwitchNetwork(); + const rejected = await getProvider( + selectedProvider, + ).rejectToSwitchNetwork(); return rejected; }, allowMetamaskToAddAndSwitchNetwork: async () => { - const allowed = await provider.allowToAddAndSwitchNetwork(); + const allowed = await getProvider( + selectedProvider, + ).allowToAddAndSwitchNetwork(); return allowed; }, getMetamaskWalletAddress: async () => { - const walletAddress = await provider.getWalletAddress(); + const walletAddress = await getProvider( + selectedProvider, + ).getWalletAddress(); return walletAddress; }, /** @@ -318,16 +418,19 @@ module.exports = (on, config) => { fetchMetamaskWalletAddress: async () => { return module.exports.fetchWalletAddress(); }, - fetchWalletAddress: async () => { - return provider.walletAddress(); + fetchWalletAddress: async ({ provider = selectedProvider } = {}) => { + return getProvider(provider).walletAddress(); }, - setupMetamask: async ({ + setup: async ({ + provider = selectedProvider, secretWordsOrPrivateKey, network, password, enableAdvancedSettings, enableExperimentalSettings, }) => { + console.log(`Setting up ${provider}...`); + if (process.env.NETWORK_NAME) { network = process.env.NETWORK_NAME; } @@ -337,7 +440,7 @@ module.exports = (on, config) => { if (process.env.SECRET_WORDS) { secretWordsOrPrivateKey = process.env.SECRET_WORDS; } - await provider.initialSetup({ + await getProvider(provider).initialSetup({ secretWordsOrPrivateKey, network, password, @@ -393,5 +496,9 @@ module.exports = (on, config) => { config.env.SKIP_METAMASK_SETUP = true; } + if (process.env.PROVIDERS) { + config.env.PROVIDERS = process.env.PROVIDERS; + } + return config; }; diff --git a/providers.js b/providers.js new file mode 100644 index 000000000..3485f7602 --- /dev/null +++ b/providers.js @@ -0,0 +1,11 @@ +module.exports = { + getProviders: providersConfig => { + const providers = []; + const tempProviders = (providersConfig ?? '').split(','); + for (const provider of tempProviders) { + const [providerName, providerVersion] = provider.split('@'); // can accept names like phantom@10.25.0 + providers.push({ name: providerName, version: providerVersion }); + } + return providers; + }, +}; diff --git a/support/commands.js b/support/commands.js index 86913a185..ebba4d3d5 100644 --- a/support/commands.js +++ b/support/commands.js @@ -20,6 +20,10 @@ const addCommand = (commandName, legacyCommandName, promiseFn) => { } }; +Cypress.Commands.add('selectProvider', providerName => { + return cy.task('selectProvider', providerName); +}); + Cypress.Commands.add('initPlaywright', () => { return cy.task('initPlaywright'); }); @@ -232,6 +236,10 @@ Cypress.Commands.add('unlock', (password = 'Tester@1234') => { return cy.task('unlock', password); }); +Cypress.Commands.add('selectWallet', (wallet = 'metamask', mode = 'always') => { + return cy.task('selectWallet', wallet, mode); +}); + addCommand('lock'); addCommand('fetchWalletAddress', 'fetchMetamaskWalletAddress', taskPromise => @@ -242,14 +250,16 @@ addCommand('confirmIncorrectNetworkStage'); Cypress.Commands.add( 'setupMetamask', - ( + ({ + provider = 'metamask', secretWordsOrPrivateKey = 'test test test test test test test test test test test junk', network = 'goerli', password = 'Tester@1234', enableAdvancedSettings = false, enableExperimentalSettings = false, - ) => { + }) => { return cy.task('setupMetamask', { + provider, secretWordsOrPrivateKey, network, password, @@ -261,13 +271,15 @@ Cypress.Commands.add( Cypress.Commands.add( 'setup', - ( + ({ + provider = 'metamask', secretWordsOrPrivateKey = 'test test test test test test test test test test test junk', network = 'goerli', password = 'Tester@1234', enableAdvancedSettings = false, - ) => { + }) => { return cy.task('setup', { + provider, secretWordsOrPrivateKey, network, password, diff --git a/support/index.js b/support/index.js index 606e21279..ebe2a7f7d 100644 --- a/support/index.js +++ b/support/index.js @@ -1,5 +1,6 @@ import './commands'; import { configure } from '@testing-library/cypress'; +const providersHelper = require('../providers'); configure({ testIdAttribute: 'data-testid' }); @@ -24,8 +25,11 @@ Cypress.on('window:before:load', win => { }); }); -before(async () => { - if (!Cypress.env('SKIP_METAMASK_SETUP')) { - await cy.setupMetamask(); +before(() => { + if (!Cypress.env('SKIP_SETUP')) { + const providers = providersHelper.getProviders(Cypress.env('PROVIDERS')); + for (const provider of providers) { + cy.setup({ provider: provider.name }); + } } }); From 025fea9bb88986fcffb957c81798fb449a2f5eda Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Thu, 23 Mar 2023 13:26:09 +0100 Subject: [PATCH 07/14] v4.0.0-alpha.1 --- package.json | 7 +++++-- providers.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ee9379d74..61badb595 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@synthetixio/synpress", - "version": "3.5.0", + "name": "@phantom/synpress", + "version": "4.0.0-alpha.1", "description": "Synpress is e2e testing framework based around Cypress.io & playwright with included MetaMask support. Test your dapps with ease.", "keywords": [ "Synpress", @@ -114,6 +114,9 @@ "**/ansi-regex": "5.0.1", "**/@testing-library/dom": "8.20.0" }, + "overrides": { + "lodash@<4.17.20": "4.17.20" + }, "engines": { "node": ">=14" }, diff --git a/providers.js b/providers.js index 3485f7602..86ff0e190 100644 --- a/providers.js +++ b/providers.js @@ -1,7 +1,7 @@ module.exports = { getProviders: providersConfig => { const providers = []; - const tempProviders = (providersConfig ?? '').split(','); + const tempProviders = (providersConfig || '').split(','); for (const provider of tempProviders) { const [providerName, providerVersion] = provider.split('@'); // can accept names like phantom@10.25.0 providers.push({ name: providerName, version: providerVersion }); From 46f0fbba185598a6ea0ec300cda7698b1736797b Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Tue, 28 Mar 2023 08:23:19 +0200 Subject: [PATCH 08/14] chore: provider leftovers --- commands/metamask.js | 3 +- commands/phantom.js | 23 ++++++----- commands/playwright.js | 61 ++++++++++++++++++++--------- package.json | 2 +- pages/phantom/select-wallet-page.js | 2 +- 5 files changed, 60 insertions(+), 31 deletions(-) diff --git a/commands/metamask.js b/commands/metamask.js index b361bac60..1f2979bab 100644 --- a/commands/metamask.js +++ b/commands/metamask.js @@ -1305,6 +1305,7 @@ const metamask = { password, enableAdvancedSettings, enableExperimentalSettings, + playwrightInstance, }) { const isCustomNetwork = (process.env.NETWORK_NAME && @@ -1312,7 +1313,7 @@ const metamask = { process.env.CHAIN_ID) || typeof network == 'object'; - await playwright.init(); + await playwright.init(playwrightInstance); await playwright.assignWindows(PROVIDER); await playwright.assignActiveTabName(PROVIDER); await module.exports.getExtensionDetails(); diff --git a/commands/phantom.js b/commands/phantom.js index ed3f7bfec..03912d0f9 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -146,6 +146,7 @@ module.exports = { ); // STEP: Input password, confirm and continue + await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast await playwright.waitAndType( PROVIDER, firstTimeFlowImportPageElements.passwordInput, @@ -166,13 +167,13 @@ module.exports = { firstTimeFlowImportPageElements.continueAfterPasswordButton, ); // shortcut confirmation - await new Promise(resolve => setTimeout(resolve, 500)); // the transitioning is too fast + await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast await playwright.waitAndClick( PROVIDER, firstTimeFlowImportPageElements.continueOnShortcutConfirm, ); // finish - await new Promise(resolve => setTimeout(resolve, 500)); // the transitioning is too fast + await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast await playwright.waitAndClick( PROVIDER, firstTimeFlowImportPageElements.continueOnShortcutConfirm, @@ -232,8 +233,12 @@ module.exports = { await switchToCypressIfNotActive(); return walletAddress; }, - initialSetup: async ({ secretWordsOrPrivateKey, password }) => { - await playwright.init(); + initialSetup: async ({ + secretWordsOrPrivateKey, + password, + playwrightInstance, + }) => { + await playwright.init(playwrightInstance); await playwright.assignWindows(PROVIDER); await playwright.assignActiveTabName(PROVIDER); await module.exports.getExtensionDetails(); @@ -378,19 +383,19 @@ module.exports = { }, selectWallet: async (wallet = 'metamask', mode = 'once') => { const notificationPage = await playwright.switchToNotification(PROVIDER); - if (wallet === 'metamask') { + + if (mode === 'always') { await playwright.waitAndClick( PROVIDER, - selectWalletElements.buttons.continueWithMetamask, + selectWalletElements.buttons.alwaysUse, notificationPage, ); - return true; } - if (mode === 'always') { + if (wallet === 'metamask') { await playwright.waitAndClick( PROVIDER, - selectWalletElements.buttons.alwaysUsePhantom, + selectWalletElements.buttons.continueWithMetamask, notificationPage, ); return true; diff --git a/commands/playwright.js b/commands/playwright.js index 966bd4a16..ae1f373d8 100644 --- a/commands/playwright.js +++ b/commands/playwright.js @@ -39,6 +39,7 @@ module.exports = { const chromium = playwrightInstance ? playwrightInstance : require('@playwright/test').chromium; + const debuggerDetails = await fetch('http://127.0.0.1:9222/json/version'); //DevSkim: ignore DS137138 const debuggerDetailsConfig = await debuggerDetails.json(); const webSocketDebuggerUrl = debuggerDetailsConfig.webSocketDebuggerUrl; @@ -127,13 +128,14 @@ module.exports = { return true; }, switchToWindow: async provider => { - if (pageWindows[provider].isClosed()) { + if (module.exports.windows(provider).isClosed()) { const newPage = await browser.contexts()[0].newPage(); if (provider === 'phantom') { await Promise.all([ newPage.waitForNavigation(), newPage.goto( - pageWindows[provider] + module.exports + .windows(provider) .url() .replace('onboarding.html', 'popup.html'), ), @@ -141,7 +143,7 @@ module.exports = { } else { await Promise.all([ newPage.waitForNavigation(), - newPage.goto(pageWindows[provider].url()), + newPage.goto(module.exports.windows(provider).url()), ]); await newPage.waitUntilStable(provider); } @@ -149,7 +151,7 @@ module.exports = { pageWindows[provider] = newPage; } - await pageWindows[provider].bringToFront(); + await module.exports.windows(provider).bringToFront(); await module.exports.assignActiveTabName(provider); return true; }, @@ -168,7 +170,9 @@ module.exports = { notificationWindows[provider] = page; retries = 0; await page.bringToFront(); - await module.exports.waitUntilStable(provider, page); + if (provider == 'metamask') { + await module.exports.waitUntilStable(provider, page); + } if (provider === 'phantom') { await module.exports.waitFor(provider, app.root, page); } else { @@ -193,8 +197,10 @@ module.exports = { ); } }, - async waitFor(provider, selector, page = pageWindows[provider]) { - await module.exports.waitUntilStable(provider, page); + async waitFor(provider, selector, page = module.exports.windows(provider)) { + if (provider == 'metamask') { + await module.exports.waitUntilStable(provider, page); + } await page.waitForSelector(selector, { strict: false }); const element = page.locator(selector).first(); await element.waitFor(); @@ -211,7 +217,7 @@ module.exports = { async waitAndClick( provider, selector, - page = pageWindows[provider], + page = module.exports.windows(provider), args = {}, ) { const element = await module.exports.waitFor(provider, selector, page); @@ -240,7 +246,7 @@ module.exports = { } else { await element.click({ force: args.force }); } - if (process.env.PROVIDER !== 'phantom') { + if (provider === 'metamask') { await module.exports.waitUntilStable(provider); } return element; @@ -249,19 +255,32 @@ module.exports = { provider, selector, text, - page = pageWindows[provider], + page = module.exports.windows(provider), ) { await module.exports.waitFor(provider, selector, page); const element = page.locator(`text=${text}`); await element.click(); - await module.exports.waitUntilStable(provider); + if (provider === 'metamask') { + await module.exports.waitUntilStable(provider); + } }, - async waitAndType(provider, selector, value, page = pageWindows[provider]) { + async waitAndType( + provider, + selector, + value, + page = module.exports.windows(provider), + ) { const element = await module.exports.waitFor(provider, selector, page); await element.type(value); - await module.exports.waitUntilStable(provider, page); + if (provider === 'metamask') { + await module.exports.waitUntilStable(provider, page); + } }, - async waitAndGetValue(provider, selector, page = pageWindows[provider]) { + async waitAndGetValue( + provider, + selector, + page = module.exports.windows(provider), + ) { const expect = global.expect ? global.expect : require('@playwright/test').expect; @@ -273,7 +292,11 @@ module.exports = { const value = await element.innerText(); return value; }, - async waitAndGetInputValue(provider, selector, page = pageWindows[provider]) { + async waitAndGetInputValue( + provider, + selector, + page = module.exports.windows(provider), + ) { const expect = global.expect ? global.expect : require('@playwright/test').expect; @@ -286,7 +309,7 @@ module.exports = { provider, selector, attribute, - page = pageWindows[provider], + page = module.exports.windows(provider), ) { const expect = global.expect ? global.expect @@ -300,7 +323,7 @@ module.exports = { provider, text, selector, - page = pageWindows[provider], + page = module.exports.windows(provider), ) { const element = await module.exports.waitFor(provider, selector, page); await element.fill(''); @@ -311,7 +334,7 @@ module.exports = { async waitAndClearWithBackspace( provider, selector, - page = pageWindows[provider], + page = module.exports.windows(provider), ) { await module.exports.waitFor(provider, selector, page); const inputValue = await page.evaluate(selector, el => el.value); @@ -324,7 +347,7 @@ module.exports = { provider, text, selector, - page = pageWindows[provider], + page = module.exports.windows(provider), ) { const element = await module.exports.waitAndClick( provider, diff --git a/package.json b/package.json index 61badb595..f767972d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@phantom/synpress", - "version": "4.0.0-alpha.1", + "version": "4.0.0-alpha.6", "description": "Synpress is e2e testing framework based around Cypress.io & playwright with included MetaMask support. Test your dapps with ease.", "keywords": [ "Synpress", diff --git a/pages/phantom/select-wallet-page.js b/pages/phantom/select-wallet-page.js index 47a894818..8c3c50a93 100644 --- a/pages/phantom/select-wallet-page.js +++ b/pages/phantom/select-wallet-page.js @@ -2,6 +2,6 @@ module.exports.selectWalletElements = { buttons: { continueWithPhantom: '[data-testid="select_wallet--phantom"]', continueWithMetamask: '[data-testid="select_wallet--metamask"]', - alwaysUsePhantom: '[data-testid="metamask_override-always_use_phantom"]', + alwaysUse: '[data-testid="select_wallet--always"]', }, }; From 8cb46f11a39429c6e5661adc66aede570867b120 Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Mon, 3 Apr 2023 10:24:39 +0200 Subject: [PATCH 09/14] chore: Add new phantom welcome screen --- commands/phantom.js | 19 ++++++++++++++++--- package.json | 2 +- pages/phantom/main-page.js | 6 ++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/commands/phantom.js b/commands/phantom.js index 03912d0f9..019e640a5 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -265,15 +265,28 @@ module.exports = { await switchToPhantomIfNotActive(); - // skip what's new header + // skip welcome page if ( await playwright .windows(PROVIDER) - .locator(mainPageElements.whatsNew.header) + .locator(mainPageElements.welcome.takeTheTourButton) ) { await playwright .windows(PROVIDER) - .click(mainPageElements.whatsNew.continueButton); + .click(mainPageElements.welcome.takeTheTourButton); + await new Promise(resolve => setTimeout(resolve, 200)); + await playwright + .windows(PROVIDER) + .click(mainPageElements.welcome.takeTheTourButtonNext); + await new Promise(resolve => setTimeout(resolve, 200)); + await playwright + .windows(PROVIDER) + .click(mainPageElements.welcome.takeTheTourButtonNext); + await new Promise(resolve => setTimeout(resolve, 200)); + await playwright + .windows(PROVIDER) + .click(mainPageElements.welcome.takeTheTourButtonNext); + await new Promise(resolve => setTimeout(resolve, 200)); } walletAddress = await module.exports.getWalletAddress(); await playwright.switchToCypressWindow(); diff --git a/package.json b/package.json index f767972d0..1c9ee4cfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@phantom/synpress", - "version": "4.0.0-alpha.6", + "version": "4.0.0-alpha.7", "description": "Synpress is e2e testing framework based around Cypress.io & playwright with included MetaMask support. Test your dapps with ease.", "keywords": [ "Synpress", diff --git a/pages/phantom/main-page.js b/pages/phantom/main-page.js index e978cb23e..0780d7fee 100644 --- a/pages/phantom/main-page.js +++ b/pages/phantom/main-page.js @@ -80,6 +80,11 @@ const whatsNew = { continueButton: '[data-testid="whats_new-continue_button"]', }; +const welcome = { + takeTheTourButton: '[data-testid="welcome-take_the_tour"]', + takeTheTourButtonNext: '[data-testid="primary-button"]', +}; + const accountBar = { title: '[data-testid="tooltip_interactive-wrapper"]', ethRow: '[data-testid="account-header-chain-eip155:1"]', @@ -150,4 +155,5 @@ module.exports.mainPageElements = { importToken, asset, whatsNew, + welcome, }; From 4f5f0f1cd737bca99793bfa9df21bb566fcd5f24 Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Fri, 7 Apr 2023 10:19:04 +0200 Subject: [PATCH 10/14] chore: revert package name change --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1c9ee4cfb..c35d57879 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@phantom/synpress", + "name": "@synthetixio/synpress", "version": "4.0.0-alpha.7", "description": "Synpress is e2e testing framework based around Cypress.io & playwright with included MetaMask support. Test your dapps with ease.", "keywords": [ From 3cb1d58d26532d68f17a90b5f5b6fce79b6ec832 Mon Sep 17 00:00:00 2001 From: Jennifer Yen Date: Wed, 12 Apr 2023 09:59:49 -0700 Subject: [PATCH 11/14] Add disconnectWalletFromDapp method and selectors --- commands/phantom.js | 52 ++++++++++++++++++++++++----- pages/phantom/main-page.js | 17 ++++------ pages/phantom/select-wallet-page.js | 1 - 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/commands/phantom.js b/commands/phantom.js index 019e640a5..2f8efffd6 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -394,32 +394,66 @@ module.exports = { await module.exports.closePopupAndTooltips(); return true; }, - selectWallet: async (wallet = 'metamask', mode = 'once') => { + selectWallet: async wallet => { const notificationPage = await playwright.switchToNotification(PROVIDER); - if (mode === 'always') { + if (wallet === 'metamask') { await playwright.waitAndClick( PROVIDER, - selectWalletElements.buttons.alwaysUse, + selectWalletElements.buttons.continueWithMetamask, notificationPage, ); + return true; } - if (wallet === 'metamask') { + if (wallet === 'phantom') { await playwright.waitAndClick( PROVIDER, - selectWalletElements.buttons.continueWithMetamask, + selectWalletElements.buttons.continueWithPhantom, notificationPage, ); return true; } - + }, + disconnectWalletFromDapp: async () => { + await switchToPhantomIfNotActive(); await playwright.waitAndClick( PROVIDER, - selectWalletElements.buttons.continueWithPhantom, - notificationPage, + mainPageElements.settingsMenu.settingsMenuButton, ); - return true; + await playwright.waitAndClick( + PROVIDER, + mainPageElements.settingsMenu.settingsSidebarButton, + ); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.settingsMenu.trustedAppsRow, + ); + + const revokeButtonLocator = await playwright + .windows(PROVIDER) + .locator(mainPageElements.connectedSites.trustedAppsRevokeButton); + await playwright + .windows(PROVIDER) + .waitForSelector(mainPageElements.connectedSites.trustedAppsRevokeButton); + const hasRevokeButton = await revokeButtonLocator.isVisible(); + + if (hasRevokeButton) { + console.log( + '[disconnectWalletFromDapp] Wallet is connected to a dapp, disconnecting...', + ); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.connectedSites.trustedAppsRevokeButton, + ); + await switchToCypressIfNotActive(); + return true; + } else { + console.log( + '[disconnectWalletFromDapp] Wallet is not connected to a dapp, skipping...', + ); + } + return false; }, }; diff --git a/pages/phantom/main-page.js b/pages/phantom/main-page.js index 0780d7fee..62e0197b7 100644 --- a/pages/phantom/main-page.js +++ b/pages/phantom/main-page.js @@ -69,10 +69,10 @@ const accountMenu = { settingsButton: '.account-menu__item--clickable:nth-child(11)', }; -const optionsMenu = { - button: '[data-testid=account-options-menu-button]', - accountDetailsButton: '[data-testid="account-options-menu__account-details"]', - connectedSitesButton: '[data-testid="account-options-menu__connected-sites"]', +const settingsMenu = { + settingsMenuButton: '[data-testid="settings-menu-open-button"]', + settingsSidebarButton: '[data-testid="sidebar_menu-button-settings"]', + trustedAppsRow: '[data-testid="settings-item-trusted-apps"]', }; const whatsNew = { @@ -90,13 +90,8 @@ const accountBar = { ethRow: '[data-testid="account-header-chain-eip155:1"]', }; -const connectedSitesSelector = '.connected-sites'; const connectedSites = { - modal: connectedSitesSelector, - disconnectLabel: `${connectedSitesSelector} .connected-sites-list__content-row-link-button`, - cancelButton: `${connectedSitesSelector} .btn-secondary`, - disconnectButton: `${connectedSitesSelector} .btn-primary`, - closeButton: `${connectedSitesSelector} [data-testid="popover-close"]`, + trustedAppsRevokeButton: '[data-testid="trusted-apps-revoke-button"]', }; const accountModal = { @@ -147,7 +142,7 @@ module.exports.mainPageElements = { actionableMessage, accountMenu, accountBar, - optionsMenu, + settingsMenu, connectedSites, accountModal, importAccount, diff --git a/pages/phantom/select-wallet-page.js b/pages/phantom/select-wallet-page.js index 8c3c50a93..1ff03fcbf 100644 --- a/pages/phantom/select-wallet-page.js +++ b/pages/phantom/select-wallet-page.js @@ -2,6 +2,5 @@ module.exports.selectWalletElements = { buttons: { continueWithPhantom: '[data-testid="select_wallet--phantom"]', continueWithMetamask: '[data-testid="select_wallet--metamask"]', - alwaysUse: '[data-testid="select_wallet--always"]', }, }; From e1a4ee0e0c2115b99a3321468be2070ec9b95a81 Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Fri, 14 Apr 2023 11:33:43 +0200 Subject: [PATCH 12/14] chore: release 4.0.0-alpha.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c35d57879..ad5d97959 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@synthetixio/synpress", - "version": "4.0.0-alpha.7", + "version": "4.0.0-alpha.8", "description": "Synpress is e2e testing framework based around Cypress.io & playwright with included MetaMask support. Test your dapps with ease.", "keywords": [ "Synpress", From e6c2beba585dfad0c3067e0115d69b335db76eaf Mon Sep 17 00:00:00 2001 From: Maxim Geerinck Date: Wed, 19 Apr 2023 18:15:37 +0200 Subject: [PATCH 13/14] wip --- commands/metamask.js | 3 ++ commands/phantom.js | 3 ++ commands/playwright.js | 71 ++++++++++++++++++++++++++++++++++++++---- package.json | 2 +- 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/commands/metamask.js b/commands/metamask.js index 1f2979bab..cb99629f2 100644 --- a/commands/metamask.js +++ b/commands/metamask.js @@ -100,6 +100,9 @@ const metamask = { async goToImportToken() { await module.exports.goTo(extensionImportTokenUrl); }, + clearExtensionData: async () => { + await playwright.clearExtensionData(PROVIDER); + }, async getExtensionDetails() { extensionInitialUrl = await playwright.windows(PROVIDER).url(); extensionId = extensionInitialUrl.match('//(.*?)/')[1]; diff --git a/commands/phantom.js b/commands/phantom.js index 2f8efffd6..950ce8241 100644 --- a/commands/phantom.js +++ b/commands/phantom.js @@ -84,6 +84,9 @@ module.exports = { goToImportToken: async () => { await module.exports.goTo(extensionImportTokenUrl); }, + clearExtensionData: async () => { + await playwright.clearExtensionData(PROVIDER); + }, getExtensionDetails: async () => { extensionInitialUrl = await playwright.windows(PROVIDER).url(); extensionId = extensionInitialUrl.match('//(.*?)/')[1]; diff --git a/commands/playwright.js b/commands/playwright.js index ae1f373d8..8e8ab0e16 100644 --- a/commands/playwright.js +++ b/commands/playwright.js @@ -62,11 +62,7 @@ module.exports = { const pages = await pagesResponse.json(); extensions = pages - .filter( - page => - page.url.startsWith('chrome-extension://') && - !page.url.endsWith('/_generated_background_page.html'), // cypress - ) + .filter(page => page.url.startsWith('chrome-extension://')) .map(extension => { const matches = extension.url.match(/chrome-extension:\/\/(.*)\/.*/); return { @@ -75,12 +71,69 @@ module.exports = { ? 'phantom' : extension.title.toLowerCase(), id: matches[1], + welcomeUrl: + extension.title === 'Phantom Wallet' + ? extension.url.replace('popup.html', 'onboarding.html') + : extension.url, }; }) .reduce((prev, curr) => ({ ...prev, [curr.name]: curr }), {}); return browser.isConnected(); }, + clearExtensionData: async provider => { + try { + // if (!mainWindow) { + // const newPage = await browser.contexts()[0].newPage(); + // mainWindow = newPage; + // } + + // await module.exports.switchToWindow(provider); + await module.exports.windows(provider).evaluate(async () => { + await new Promise((resolve, reject) => { + return chrome.storage.local.clear(() => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + } else { + resolve(true); + } + }); + }); + + await new Promise((resolve, reject) => { + return chrome.storage.sync.clear(() => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + } else { + resolve(true); + } + }); + }); + // chrome.runtime.reload(); // closes the popup + }); + // await mainWindow.waitForTimeout(1000); + // await module.exports.windows(provider).waitForTimeout(1000); + await module.exports.windows(provider).reload(); + // return module.exports.windows(provider); + // await mainWindow.waitForTimeout(1000); + // const newPagePromise = new Promise(resolve => + // browser.contexts()[0].once('page', resolve), + // ); + // await mainWindow.evaluate(async extensionWelcomeUrl => { + // window.open(extensionWelcomeUrl, '_blank').focus(); + // }, extensions[provider].welcomeUrl); + + // await new Promise(resolve => setTimeout(resolve, 20000)); + // pageWindows[provider] = await newPagePromise; + // pageWindows[provider] = newPage; + // await module.exports.assignActiveTabName(provider); + // await module.exports.windows(provider).reload(); + // await module.exports.waitUntilStable(); + // return module.exports.windows(provider); + } catch (ex) { + console.log(`[${provider}]: ${ex.message}`); + } + }, async clear() { browser = null; return true; @@ -99,6 +152,12 @@ module.exports = { pageWindows[provider] = page; } } + + // if (!mainWindow) { + // const newPage = await browser.contexts()[0].newPage(); + // mainWindow = newPage; + // } + return true; }, async assignActiveTabName(tabName) { @@ -153,7 +212,7 @@ module.exports = { await module.exports.windows(provider).bringToFront(); await module.exports.assignActiveTabName(provider); - return true; + return module.exports.windows(provider); }, async switchToNotificationWindow(provider) { await notificationWindows[provider].bringToFront(); diff --git a/package.json b/package.json index ad5d97959..6d1429b67 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@synthetixio/synpress", + "name": "@phantom/synpress", "version": "4.0.0-alpha.8", "description": "Synpress is e2e testing framework based around Cypress.io & playwright with included MetaMask support. Test your dapps with ease.", "keywords": [ From acc6b608e455f94ed890654a72f0ab448fe4bc86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:30:04 +0000 Subject: [PATCH 14/14] chore(deps): Bump yaml from 2.2.1 to 2.2.2 Bumps [yaml](https://github.com/eemeli/yaml) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](https://github.com/eemeli/yaml/compare/v2.2.1...v2.2.2) --- updated-dependencies: - dependency-name: yaml dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b48964a53..bc806cfe9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13635,9 +13635,9 @@ yallist@^4.0.0: integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.1.tgz#3014bf0482dcd15147aa8e56109ce8632cd60ce4" - integrity sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw== + version "2.2.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" + integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== yargs-parser@21.1.1: version "21.1.1"