diff --git a/commands/metamask.js b/commands/metamask.js index b32c62856..cb99629f2 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); @@ -98,8 +100,11 @@ const metamask = { async goToImportToken() { await module.exports.goTo(extensionImportTokenUrl); }, + clearExtensionData: async () => { + await playwright.clearExtensionData(PROVIDER); + }, 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 +130,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 +172,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 +205,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 +234,9 @@ const metamask = { }, async importWallet(secretWords, password) { await playwright.waitAndClick( + PROVIDER, onboardingWelcomePageElements.importWalletButton, - await playwright.metamaskWindow(), + await playwright.windows(PROVIDER), { waitForEvent: 'navi', }, @@ -212,46 +245,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 +305,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 +378,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 +402,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 +423,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 +445,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 +525,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 +564,7 @@ const metamask = { await module.exports.closePopupAndTooltips(); await setNetwork(network); await playwright.waitForText( + PROVIDER, mainPageElements.networkSwitcher.networkName, network.networkName, ); @@ -483,13 +573,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 +591,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 +609,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 +627,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 +704,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 +725,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 +747,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 +757,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 +779,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 +794,7 @@ const metamask = { await module.exports.goToImportToken(); if (typeof tokenConfig === 'string') { await playwright.waitAndType( + PROVIDER, mainPageElements.importToken.tokenContractAddressInput, tokenConfig, ); @@ -688,13 +804,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 +827,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 +856,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 +866,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 +876,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 +890,13 @@ const metamask = { notificationPage, ); await playwright.waitAndClick( + PROVIDER, notificationPageElements.allowToSpendButton, notificationPage, ); } await playwright.waitAndClick( + PROVIDER, notificationPageElements.allowToSpendButton, notificationPage, { waitForEvent: 'close' }, @@ -779,8 +904,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 +914,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 +938,7 @@ const metamask = { await module.exports.confirmSignatureRequest(); } else { await playwright.waitAndClick( + PROVIDER, permissionsPageElements.connectButton, notificationPage, { waitForEvent: 'close' }, @@ -818,14 +948,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 +963,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 +977,7 @@ const metamask = { '[confirmTransaction] Override acknowledgement modal is present, closing..', ); await playwright.waitAndClick( + PROVIDER, confirmPageElements.editGasFeeLegacyOverrideAckButton, notificationPage, ); @@ -867,6 +999,7 @@ const metamask = { ); } await playwright.waitAndClick( + PROVIDER, confirmPageElements.saveCustomGasFeeButton, notificationPage, ); @@ -878,6 +1011,7 @@ const metamask = { } else { log('[confirmTransaction] Looks like eip-1559 tx'); await playwright.waitAndClick( + PROVIDER, confirmPageElements.editGasFeeButton, notificationPage, ); @@ -885,24 +1019,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 +1048,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 +1082,7 @@ const metamask = { ); } await playwright.waitAndClick( + PROVIDER, confirmPageElements.saveCustomGasFeeButton, notificationPage, ); @@ -951,20 +1092,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 +1116,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 +1138,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 +1179,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 +1189,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 +1199,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 +1209,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 +1219,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 +1229,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 +1247,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 +1257,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 +1267,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 +1283,49 @@ 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( + async initialSetup({ + secretWordsOrPrivateKey, + network, + password, + enableAdvancedSettings, + enableExperimentalSettings, playwrightInstance, - { - 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(playwrightInstance); + 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 +1350,7 @@ const metamask = { return true; } else if ( await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(unlockPageElements.passwordInput) .isVisible() ) { @@ -1201,7 +1361,7 @@ const metamask = { } else { if ( (await playwright - .metamaskWindow() + .windows(PROVIDER) .locator(mainPageElements.walletOverview) .isVisible()) && !process.env.RESET_METAMASK @@ -1219,7 +1379,7 @@ const metamask = { async function switchToMetamaskIfNotActive() { if (await playwright.isCypressWindowActive()) { - await playwright.switchToMetamaskWindow(); + await playwright.switchToWindow(PROVIDER); switchBackToCypressWindow = true; } return switchBackToCypressWindow; @@ -1247,13 +1407,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 +1444,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 new file mode 100644 index 000000000..950ce8241 --- /dev/null +++ b/commands/phantom.js @@ -0,0 +1,478 @@ +const log = require('debug')('synpress:phantom'); +const playwright = require('./playwright'); + +const { + firstTimeFlowPageElements, + firstTimeFlowImportPageElements, +} = require('../pages/phantom/first-time-flow-page'); +const { mainPageElements } = require('../pages/phantom/main-page'); +const { unlockPageElements } = require('../pages/phantom/unlock-page'); +const { + signaturePageElements, + buttons, + 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; +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.windows(PROVIDER).waitForNavigation(), + playwright.windows(PROVIDER).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); + }, + clearExtensionData: async () => { + await playwright.clearExtensionData(PROVIDER); + }, + getExtensionDetails: async () => { + extensionInitialUrl = await playwright.windows(PROVIDER).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, + }; + }, + acceptAccess: async () => { + const notificationPage = await playwright.switchToNotification(PROVIDER); + await playwright.waitAndClick( + PROVIDER, + buttons.primaryButton, + notificationPage, + ); + return true; + }, + importWallet: async (secretWords, password) => { + 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, + ); + + // STEP: Wait for confirm input + // 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 new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast + 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, 1000)); // the transitioning is too fast + await playwright.waitAndClick( + PROVIDER, + firstTimeFlowImportPageElements.continueOnShortcutConfirm, + ); + // finish + await new Promise(resolve => setTimeout(resolve, 1000)); // the transitioning is too fast + await playwright.waitAndClick( + PROVIDER, + firstTimeFlowImportPageElements.continueOnShortcutConfirm, + ); + 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 + await playwright.windows(PROVIDER).waitForTimeout(1000); + if ( + await playwright + .windows(PROVIDER) + .locator(mainPageElements.popup.container) + .isVisible() + ) { + const popupBackground = playwright + .windows(PROVIDER) + .locator(mainPageElements.popup.background); + const popupBackgroundBox = await popupBackground.boundingBox(); + await playwright + .windows(PROVIDER) + .mouse.click(popupBackgroundBox.x + 1, popupBackgroundBox.y + 1); + } + if ( + await playwright + .windows(PROVIDER) + .locator(mainPageElements.tippyTooltip.closeButton) + .isVisible() + ) { + await playwright.waitAndClick( + PROVIDER, + mainPageElements.tippyTooltip.closeButton, + ); + } + if ( + await playwright + .windows(PROVIDER) + .locator(mainPageElements.actionableMessage.closeButton) + .isVisible() + ) { + await playwright.waitAndClick( + PROVIDER, + mainPageElements.actionableMessage.closeButton, + ); + } + return true; + }, + getWalletAddress: async () => { + await switchToPhantomIfNotActive(); + await playwright.windows(PROVIDER).hover(mainPageElements.accountBar.title); + await new Promise(resolve => setTimeout(resolve, 100)); + await playwright.waitAndClick(PROVIDER, mainPageElements.accountBar.ethRow); + walletAddress = await playwright + .windows(PROVIDER) + .evaluate('navigator.clipboard.readText()'); + await switchToCypressIfNotActive(); + return walletAddress; + }, + initialSetup: async ({ + secretWordsOrPrivateKey, + password, + playwrightInstance, + }) => { + await playwright.init(playwrightInstance); + await playwright.assignWindows(PROVIDER); + await playwright.assignActiveTabName(PROVIDER); + await module.exports.getExtensionDetails(); + await playwright.fixBlankPage( + PROVIDER, + playwright.windows(PROVIDER), + app.root, + ); + await playwright.switchToWindow(PROVIDER); + + if ( + await playwright + .windows(PROVIDER) + .locator(firstTimeFlowPageElements.importWalletButton) + .isVisible() + ) { + /** + * SEED PHRASE IMPORT + */ + if (secretWordsOrPrivateKey.includes(' ')) { + // secret words + await module.exports.importWallet(secretWordsOrPrivateKey, password); + } + + await switchToPhantomIfNotActive(); + + // skip welcome page + if ( + await playwright + .windows(PROVIDER) + .locator(mainPageElements.welcome.takeTheTourButton) + ) { + await playwright + .windows(PROVIDER) + .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(); + return true; + } else if ( + /** + * PASSWORD UNLOCK + */ + await playwright + .windows(PROVIDER) + .locator(unlockPageElements.passwordInput) + .isVisible() + ) { + await module.exports.unlock(password); + walletAddress = await module.exports.getWalletAddress(); + await playwright.switchToCypressWindow(); + return true; + } else { + if ( + (await playwright + .windows(PROVIDER) + .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 + } + } + }, + confirmSignatureRequest: async () => { + const notificationPage = await playwright.switchToNotification(PROVIDER); + await playwright.waitAndClick( + PROVIDER, + signaturePageElements.buttons.confirmSign, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + rejectSignatureRequest: async () => { + const notificationPage = await playwright.switchToNotification(PROVIDER); + await playwright.waitAndClick( + PROVIDER, + signaturePageElements.buttons.rejectSign, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmTransaction: async () => { + const notificationPage = await playwright.switchToNotification(PROVIDER); + await playwright.waitAndClick( + PROVIDER, + transactionPageElements.buttons.rejectSign, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + confirmIncorrectNetworkStage: async () => { + const notificationPage = await playwright.switchToNotification(PROVIDER); + await playwright.waitAndClick( + PROVIDER, + incorrectModePageElements.buttons.close, + notificationPage, + { waitForEvent: 'close' }, + ); + return true; + }, + unlock: async password => { + await playwright.waitAndType( + PROVIDER, + unlockPageElements.passwordInput, + password, + ); + await playwright.waitAndClick( + PROVIDER, + unlockPageElements.unlockButton, + await playwright.windows(PROVIDER), + ); + await module.exports.closePopupAndTooltips(); + return true; + }, + lock: async () => { + await playwright.waitAndClick( + PROVIDER, + menu.buttons.settings, + await playwright.windows(PROVIDER), + ); + await playwright.waitAndClick( + PROVIDER, + menu.buttons.sidebar.settings, + await playwright.windows(PROVIDER), + ); + await playwright.waitAndClick( + PROVIDER, + settingsPageElements.buttons.lockWallet, + await playwright.windows(PROVIDER), + ); + await module.exports.closePopupAndTooltips(); + return true; + }, + selectWallet: async wallet => { + const notificationPage = await playwright.switchToNotification(PROVIDER); + + if (wallet === 'metamask') { + await playwright.waitAndClick( + PROVIDER, + selectWalletElements.buttons.continueWithMetamask, + notificationPage, + ); + return true; + } + + if (wallet === 'phantom') { + await playwright.waitAndClick( + PROVIDER, + selectWalletElements.buttons.continueWithPhantom, + notificationPage, + ); + return true; + } + }, + disconnectWalletFromDapp: async () => { + await switchToPhantomIfNotActive(); + await playwright.waitAndClick( + PROVIDER, + mainPageElements.settingsMenu.settingsMenuButton, + ); + 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; + }, +}; + +async function switchToPhantomIfNotActive() { + await playwright.switchToWindow(PROVIDER); + if (await playwright.isCypressWindowActive()) { + await playwright.switchToWindow(PROVIDER); + switchBackToCypressWindow = true; + } + return switchBackToCypressWindow; +} + +async function switchToCypressIfNotActive() { + if (switchBackToCypressWindow) { + await playwright.switchToCypressWindow(); + switchBackToCypressWindow = false; + } + return switchBackToCypressWindow; +} diff --git a/commands/playwright.js b/commands/playwright.js index 7867f3e3c..8e8ab0e16 100644 --- a/commands/playwright.js +++ b/commands/playwright.js @@ -7,16 +7,18 @@ 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; 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; @@ -37,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; @@ -53,23 +56,108 @@ 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://')) + .map(extension => { + const matches = extension.url.match(/chrome-extension:\/\/(.*)\/.*/); + return { + name: + extension.title === 'Phantom Wallet' + ? '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; }, - 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; } } + + // if (!mainWindow) { + // const newPage = await browser.contexts()[0].newPage(); + // mainWindow = newPage; + // } + return true; }, async assignActiveTabName(tabName) { @@ -78,18 +166,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,44 +186,80 @@ module.exports = { } return true; }, - async switchToMetamaskWindow() { - await metamaskWindow.bringToFront(); - await module.exports.assignActiveTabName('metamask'); - return true; + switchToWindow: async provider => { + if (module.exports.windows(provider).isClosed()) { + const newPage = await browser.contexts()[0].newPage(); + if (provider === 'phantom') { + await Promise.all([ + newPage.waitForNavigation(), + newPage.goto( + module.exports + .windows(provider) + .url() + .replace('onboarding.html', 'popup.html'), + ), + ]); + } else { + await Promise.all([ + newPage.waitForNavigation(), + newPage.goto(module.exports.windows(provider).url()), + ]); + await newPage.waitUntilStable(provider); + } + + pageWindows[provider] = newPage; + } + + await module.exports.windows(provider).bringToFront(); + await module.exports.assignActiveTabName(provider); + return module.exports.windows(provider); }, - 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(); - 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; + notificationWindows[provider] = page; retries = 0; await page.bringToFront(); - await module.exports.waitUntilStable(page); - await module.exports.waitFor( - notificationPageElements.notificationAppContent, - page, - ); + if (provider == 'metamask') { + 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, + ); + } + return page; } } 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 = 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(); @@ -149,8 +273,13 @@ module.exports = { } return element; }, - async waitAndClick(selector, page = metamaskWindow, args = {}) { - const element = await module.exports.waitFor(selector, page); + async waitAndClick( + provider, + selector, + page = module.exports.windows(provider), + args = {}, + ) { + const element = await module.exports.waitFor(provider, selector, page); if (args.numberOfClicks && !args.waitForEvent) { await element.click({ clickCount: args.numberOfClicks, @@ -176,25 +305,45 @@ module.exports = { } else { await element.click({ force: args.force }); } - await module.exports.waitUntilStable(); + if (provider === 'metamask') { + 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 = module.exports.windows(provider), + ) { + await module.exports.waitFor(provider, selector, page); const element = page.locator(`text=${text}`); await element.click(); - await module.exports.waitUntilStable(); + if (provider === 'metamask') { + 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 = module.exports.windows(provider), + ) { + const element = await module.exports.waitFor(provider, selector, page); await element.type(value); - await module.exports.waitUntilStable(page); + if (provider === 'metamask') { + await module.exports.waitUntilStable(provider, page); + } }, - async waitAndGetValue(selector, page = metamaskWindow) { + async waitAndGetValue( + provider, + selector, + page = module.exports.windows(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, @@ -202,60 +351,97 @@ module.exports = { const value = await element.innerText(); return value; }, - async waitAndGetInputValue(selector, page = metamaskWindow) { + async waitAndGetInputValue( + provider, + selector, + page = module.exports.windows(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 = module.exports.windows(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 = module.exports.windows(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 = module.exports.windows(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 = module.exports.windows(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( @@ -265,17 +451,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'); @@ -283,21 +471,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 @@ -306,37 +515,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/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/helpers.js b/helpers.js index 8c05b72fb..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)); } @@ -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 (provider, url, destination) => { try { log( `Trying to download and extract file from: ${url} to following path: ${destination}`, @@ -159,7 +214,17 @@ 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 + */ + if (provider === 'phantom') { + await moveFiles(`${destination}/dist`, destination); + } } else { await download(url, destination, { extract: true, @@ -167,31 +232,49 @@ 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 (provider, version) => { + const release = + 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( + provider, + 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/package.json b/package.json index ee9379d74..6d1429b67 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.8", "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/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/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..62e0197b7 --- /dev/null +++ b/pages/phantom/main-page.js @@ -0,0 +1,154 @@ +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 settingsMenu = { + settingsMenuButton: '[data-testid="settings-menu-open-button"]', + settingsSidebarButton: '[data-testid="sidebar_menu-button-settings"]', + trustedAppsRow: '[data-testid="settings-item-trusted-apps"]', +}; + +const whatsNew = { + header: '[data-testid="whats_new-header"]', + 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"]', +}; + +const connectedSites = { + trustedAppsRevokeButton: '[data-testid="trusted-apps-revoke-button"]', +}; + +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, + settingsMenu, + connectedSites, + accountModal, + importAccount, + createAccount, + importToken, + asset, + whatsNew, + welcome, +}; diff --git a/pages/phantom/notification-page.js b/pages/phantom/notification-page.js new file mode 100644 index 000000000..2d72c238e --- /dev/null +++ b/pages/phantom/notification-page.js @@ -0,0 +1,191 @@ +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, +}; + +/** + * 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/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/select-wallet-page.js b/pages/phantom/select-wallet-page.js new file mode 100644 index 000000000..1ff03fcbf --- /dev/null +++ b/pages/phantom/select-wallet-page.js @@ -0,0 +1,6 @@ +module.exports.selectWalletElements = { + buttons: { + continueWithPhantom: '[data-testid="select_wallet--phantom"]', + continueWithMetamask: '[data-testid="select_wallet--metamask"]', + }, +}; diff --git a/pages/phantom/settings-page.js b/pages/phantom/settings-page.js new file mode 100644 index 000000000..79cfe2903 --- /dev/null +++ b/pages/phantom/settings-page.js @@ -0,0 +1,5 @@ +module.exports.settingsPageElements = { + buttons: { + lockWallet: '[data-testid="lock-menu-item"]', + }, +}; diff --git a/pages/phantom/unlock-page.js b/pages/phantom/unlock-page.js new file mode 100644 index 000000000..5665ea7d5 --- /dev/null +++ b/pages/phantom/unlock-page.js @@ -0,0 +1,4 @@ +module.exports.unlockPageElements = { + 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 fc1d70371..b88b311ac 100644 --- a/plugins/index.js +++ b/plugins/index.js @@ -1,9 +1,19 @@ const helpers = require('../helpers'); const playwright = require('../commands/playwright'); -const metamask = 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} */ @@ -11,9 +21,13 @@ 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') { - // 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', @@ -29,15 +43,42 @@ 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) { - // NOTE: extensions cannot be loaded in headless Chrome - const metamaskPath = await helpers.prepareMetamask( - process.env.METAMASK_VERSION || '10.25.0', - ); - arguments_.extensions.push(metamaskPath); + 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); + } } + // 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_; }); @@ -51,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(); @@ -60,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 () => { @@ -72,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(); @@ -84,32 +129,52 @@ 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; }, + /** + * @deprecated + */ unlockMetamask: async password => { - const unlocked = await metamask.unlock(password); + return module.exports.unlock(password); + }, + unlock: async password => { + const unlocked = await getProvider(selectedProvider).unlock(password); return unlocked; }, + lock: () => { + return getProvider(selectedProvider).lock(); + }, + confirmIncorrectNetworkStage: () => { + return getProvider(selectedProvider).confirmIncorrectNetworkStage(); + }, importMetamaskAccount: async privateKey => { - const imported = await metamask.importAccount(privateKey); + const imported = await getProvider(selectedProvider).importAccount( + privateKey, + ); return imported; }, createMetamaskAccount: async accountName => { - const created = await metamask.createAccount(accountName); + const created = await getProvider(selectedProvider).createAccount( + accountName, + ); return created; }, switchMetamaskAccount: async accountNameOrAccountNumber => { - const switched = await metamask.switchAccount(accountNameOrAccountNumber); + const switched = await getProvider(selectedProvider).switchAccount( + accountNameOrAccountNumber, + ); return switched; }, addMetamaskNetwork: async network => { - const networkAdded = await metamask.addNetwork(network); + const networkAdded = await getProvider(selectedProvider).addNetwork( + network, + ); return networkAdded; }, changeMetamaskNetwork: async network => { @@ -118,153 +183,254 @@ module.exports = (on, config) => { } else if (!network) { network = 'goerli'; } - const networkChanged = await metamask.changeNetwork(network); + const networkChanged = await getProvider(selectedProvider).changeNetwork( + network, + ); return networkChanged; }, activateAdvancedGasControlInMetamask: async skipSetup => { - const activated = await metamask.activateAdvancedGasControl(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateAdvancedGasControl(skipSetup); + return activated; + }, + activateEnhancedTokenDetectionInMetamask: async skipSetup => { + const activated = await getProvider( + selectedProvider, + ).activateEnhancedTokenDetection(skipSetup); return activated; }, activateShowHexDataInMetamask: async skipSetup => { - const activated = await metamask.activateShowHexData(skipSetup); + const activated = await getProvider(selectedProvider).activateShowHexData( + skipSetup, + ); return activated; }, activateTestnetConversionInMetamask: async skipSetup => { - const activated = await metamask.activateTestnetConversion(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateTestnetConversion(skipSetup); return activated; }, activateShowTestnetNetworksInMetamask: async skipSetup => { - const activated = await metamask.activateShowTestnetNetworks(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateShowTestnetNetworks(skipSetup); return activated; }, activateCustomNonceInMetamask: async skipSetup => { - const activated = await metamask.activateCustomNonce(skipSetup); + const activated = await getProvider(selectedProvider).activateCustomNonce( + skipSetup, + ); return activated; }, activateDismissBackupReminderInMetamask: async skipSetup => { - const activated = await metamask.activateDismissBackupReminder(skipSetup); + const activated = await getProvider( + selectedProvider, + ).activateDismissBackupReminder(skipSetup); return activated; }, - activateEthSignRequestsInMetamask: async skipSetup => { - const activated = await metamask.activateEthSignRequests(skipSetup); + activateEnhancedGasFeeUIInMetamask: async skipSetup => { + const activated = await getProvider( + selectedProvider, + ).activateEnhancedGasFeeUI(skipSetup); return activated; }, - activateImprovedTokenAllowanceInMetamask: async skipSetup => { - const activated = await metamask.activateImprovedTokenAllowance( - skipSetup, - ); + activateShowCustomNetworkListInMetamask: async skipSetup => { + const activated = await getProvider( + selectedProvider, + ).activateShowCustomNetworkList(skipSetup); return activated; }, resetMetamaskAccount: async () => { - const resetted = await metamask.resetAccount(); + const resetted = await getProvider(selectedProvider).resetAccount(); return resetted; }, disconnectMetamaskWalletFromDapp: async () => { - const disconnected = await metamask.disconnectWalletFromDapp(); + const disconnected = await getProvider( + selectedProvider, + ).disconnectWalletFromDapp(); return disconnected; }, disconnectMetamaskWalletFromAllDapps: async () => { - const disconnected = await metamask.disconnectWalletFromAllDapps(); + const disconnected = await getProvider( + selectedProvider, + ).disconnectWalletFromAllDapps(); return disconnected; }, + + /** + * @deprecated + */ confirmMetamaskSignatureRequest: async () => { - const confirmed = await metamask.confirmSignatureRequest(); + return module.exports.confirmSignatureRequest(); + }, + confirmSignatureRequest: async () => { + const confirmed = await getProvider( + selectedProvider, + ).confirmSignatureRequest(); return confirmed; }, + confirmMetamaskDataSignatureRequest: async () => { - const confirmed = await metamask.confirmDataSignatureRequest(); + const confirmed = await getProvider( + selectedProvider, + ).confirmDataSignatureRequest(); return confirmed; }, rejectMetamaskSignatureRequest: async () => { - const rejected = await metamask.rejectSignatureRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectSignatureRequest(); return rejected; }, rejectMetamaskDataSignatureRequest: async () => { - const rejected = await metamask.rejectDataSignatureRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectDataSignatureRequest(); return rejected; }, confirmMetamaskEncryptionPublicKeyRequest: async () => { - const confirmed = await metamask.confirmEncryptionPublicKeyRequest(); + const confirmed = await getProvider( + selectedProvider, + ).confirmEncryptionPublicKeyRequest(); return confirmed; }, rejectMetamaskEncryptionPublicKeyRequest: async () => { - const rejected = await metamask.rejectEncryptionPublicKeyRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectEncryptionPublicKeyRequest(); return rejected; }, confirmMetamaskDecryptionRequest: async () => { - const confirmed = await metamask.confirmDecryptionRequest(); + const confirmed = await getProvider( + selectedProvider, + ).confirmDecryptionRequest(); return confirmed; }, rejectMetamaskDecryptionRequest: async () => { - const rejected = await metamask.rejectDecryptionRequest(); + const rejected = await getProvider( + selectedProvider, + ).rejectDecryptionRequest(); return rejected; }, importMetamaskToken: async tokenConfig => { - const imported = await metamask.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 metamask.confirmAddToken(); + const confirmed = await getProvider(selectedProvider).confirmAddToken(); return confirmed; }, rejectMetamaskAddToken: async () => { - const rejected = await metamask.rejectAddToken(); + const rejected = await getProvider(selectedProvider).rejectAddToken(); return rejected; }, - confirmMetamaskPermissionToSpend: async spendLimit => { - const confirmed = await metamask.confirmPermissionToSpend(spendLimit); + confirmMetamaskPermissionToSpend: async () => { + const confirmed = await getProvider( + selectedProvider, + ).confirmPermissionToSpend(); return confirmed; }, rejectMetamaskPermissionToSpend: async () => { - const rejected = await metamask.rejectPermissionToSpend(); + const rejected = await getProvider( + selectedProvider, + ).rejectPermissionToSpend(); return rejected; }, + + /** + * @deprecated + */ acceptMetamaskAccess: async options => { - const accepted = await metamask.acceptAccess(options); + return module.exports.acceptAccess(options); + }, + acceptAccess: async options => { + const accepted = await getProvider(selectedProvider).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 getProvider(selectedProvider).confirmTransaction( + gasConfig, + ); return confirmed; }, + rejectMetamaskTransaction: async () => { - const rejected = await metamask.rejectTransaction(); + const rejected = await getProvider(selectedProvider).rejectTransaction(); return rejected; }, allowMetamaskToAddNetwork: async ({ waitForEvent }) => { - const allowed = await metamask.allowToAddNetwork({ waitForEvent }); + const allowed = await getProvider(selectedProvider).allowToAddNetwork({ + waitForEvent, + }); return allowed; }, rejectMetamaskToAddNetwork: async () => { - const rejected = await metamask.rejectToAddNetwork(); + const rejected = await getProvider(selectedProvider).rejectToAddNetwork(); return rejected; }, allowMetamaskToSwitchNetwork: async () => { - const allowed = await metamask.allowToSwitchNetwork(); + const allowed = await getProvider( + selectedProvider, + ).allowToSwitchNetwork(); return allowed; }, rejectMetamaskToSwitchNetwork: async () => { - const rejected = await metamask.rejectToSwitchNetwork(); + const rejected = await getProvider( + selectedProvider, + ).rejectToSwitchNetwork(); return rejected; }, allowMetamaskToAddAndSwitchNetwork: async () => { - const allowed = await metamask.allowToAddAndSwitchNetwork(); + const allowed = await getProvider( + selectedProvider, + ).allowToAddAndSwitchNetwork(); return allowed; }, getMetamaskWalletAddress: async () => { - const walletAddress = await metamask.getWalletAddress(); + const walletAddress = await getProvider( + selectedProvider, + ).getWalletAddress(); return walletAddress; }, + /** + * @deprecated + */ fetchMetamaskWalletAddress: async () => { - return metamask.walletAddress(); + return module.exports.fetchWalletAddress(); + }, + 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; } @@ -274,7 +440,7 @@ module.exports = (on, config) => { if (process.env.SECRET_WORDS) { secretWordsOrPrivateKey = process.env.SECRET_WORDS; } - await metamask.initialSetup(null, { + await getProvider(provider).initialSetup({ secretWordsOrPrivateKey, network, password, @@ -330,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..86ff0e190 --- /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 f6d09dd29..ebba4d3d5 100644 --- a/support/commands.js +++ b/support/commands.js @@ -2,6 +2,27 @@ 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('selectProvider', providerName => { + return cy.task('selectProvider', providerName); +}); Cypress.Commands.add('initPlaywright', () => { return cy.task('initPlaywright'); @@ -125,9 +146,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 +199,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,26 +226,40 @@ Cypress.Commands.add('allowMetamaskToAddAndSwitchNetwork', () => { return cy.task('allowMetamaskToAddAndSwitchNetwork'); }); +/** + * @deprecated + */ Cypress.Commands.add('unlockMetamask', (password = 'Tester@1234') => { return cy.task('unlockMetamask', password); }); +Cypress.Commands.add('unlock', (password = 'Tester@1234') => { + return cy.task('unlock', password); +}); -Cypress.Commands.add('fetchMetamaskWalletAddress', () => { - cy.task('fetchMetamaskWalletAddress').then(address => { - return address; - }); +Cypress.Commands.add('selectWallet', (wallet = 'metamask', mode = 'always') => { + return cy.task('selectWallet', wallet, mode); }); +addCommand('lock'); + +addCommand('fetchWalletAddress', 'fetchMetamaskWalletAddress', taskPromise => + taskPromise.then(address => address), +); + +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, @@ -241,6 +269,25 @@ 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, + enableAdvancedSettings, + }); + }, +); + Cypress.Commands.add('getNetwork', () => { return cy.task('getNetwork'); }); 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 }); + } } }); 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"