diff --git a/package.json b/package.json index 46b4c3ea..ba06c0ac 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "branches": [ "main", { - "name": "develop", - "channel": "alpha", - "prerelease": "alpha" + "name": "feat/mm-change-network", + "channel": "alpha-mm-change-network", + "prerelease": "alpha-mm-change-network" } ] } diff --git a/packages/browser-service/package.json b/packages/browser-service/package.json index 523e5b74..42253ecf 100644 --- a/packages/browser-service/package.json +++ b/packages/browser-service/package.json @@ -36,4 +36,4 @@ "peerDependencies": { "@playwright/test": "^1.51.1" } -} \ No newline at end of file +} diff --git a/packages/browser-service/src/browser.service.ts b/packages/browser-service/src/browser.service.ts index 61648f31..0476f19b 100644 --- a/packages/browser-service/src/browser.service.ts +++ b/packages/browser-service/src/browser.service.ts @@ -31,7 +31,7 @@ type BrowserServiceOptions = { accountConfig: AccountConfig; walletConfig: CommonWalletConfig; nodeConfig?: NodeConfig; - standUrl?: string; + standUrl: string; browserOptions?: BrowserOptions; }; @@ -86,7 +86,6 @@ export class BrowserService { } else { await this.setup(); await this.walletPage.setupNetwork(this.options.networkConfig); - await this.walletPage.changeNetwork(this.options.networkConfig.chainName); await this.browserContextService.closePages(); } } @@ -162,6 +161,9 @@ export class BrowserService { extensionUrl: extension.url, accountConfig: this.options.accountConfig, walletConfig: this.options.walletConfig, + standConfig: { + standUrl: this.options.standUrl, + }, }); switch (this.options.walletConfig.WALLET_TYPE) { diff --git a/packages/wallets/src/metamask/helper.ts b/packages/wallets/src/metamask/helper.ts index 5ee6c1f5..e3785a18 100644 --- a/packages/wallets/src/metamask/helper.ts +++ b/packages/wallets/src/metamask/helper.ts @@ -1,4 +1,5 @@ const MMPopularNetworks = [ + 'Ethereum Mainnet', 'zkSync Era Mainnet', 'OP Mainnet', 'Arbitrum One', diff --git a/packages/wallets/src/metamask/metamask.page.ts b/packages/wallets/src/metamask/metamask.page.ts index dcb10a06..45cf8304 100644 --- a/packages/wallets/src/metamask/metamask.page.ts +++ b/packages/wallets/src/metamask/metamask.page.ts @@ -12,8 +12,9 @@ import { AccountMenu, NetworkList, } from './pages/elements'; -import { getAddress } from 'viem'; import { isPopularMainnetNetwork, isPopularTestnetNetwork } from './helper'; +import { EditNetworksTab } from './pages/navBarMenu'; +import { AllPermissionsPage } from './pages/navBarMenu'; export class MetamaskPage implements WalletPage { page: Page | undefined; @@ -53,7 +54,9 @@ export class MetamaskPage implements WalletPage { await test.step('Navigate to metamask Home page', async () => { await this.initLocators(); await this.homePage.goto(); - await this.header.appHeaderLogo.waitFor({ state: 'visible' }); + await this.header.appHeaderLogo + .or(this.loginPage.passwordInput) + .waitFor({ state: 'visible' }); await this.popoverElements.closeConnectingProblemPopover(); await this.loginPage.unlock(); @@ -78,14 +81,14 @@ export class MetamaskPage implements WalletPage { }); } - async changeNetwork(networkName: string) { - await test.step(`Change Metamask network to ${networkName}`, async () => { + // should be used only after connection to dapp after v12.10.4 + async changeNetwork(networkName: string, standUrl?: string) { + await test.step(`Change network to ${networkName}`, async () => { await this.navigate(); - await this.settingsMenu.openNetworksSettings(); - await this.networkList.clickToNetworkItemButton(networkName); - if (networkName === 'Linea') { - await this.popoverElements.closePopover(); //Linea network require additional confirmation - } + await this.changeNetworkForDapp( + standUrl || this.options.standConfig.standUrl, + networkName, + ); await this.page.close(); }); } @@ -123,12 +126,30 @@ export class MetamaskPage implements WalletPage { await this.networkList.addPopularTestnetNetwork(networkConfig); } else { await this.networkList.addNetworkManually(networkConfig); - await this.changeNetwork(networkConfig.chainName); } if (isClosePage) await this.page.close(); }); } + async changeNetworkForDapp(standUrl: string, networkName: string) { + await test.step(`Change network for ${standUrl} to ${networkName}`, async () => { + const allPermissionsPage = new AllPermissionsPage( + this.page, + this.options.extensionUrl, + this.options.walletConfig, + ); + + await allPermissionsPage.openAllPermissions(); + await allPermissionsPage.openEditNetworksForWebsite(standUrl); + await allPermissionsPage.openEditNetworksPage(); + + const editNetworksPage = new EditNetworksTab(this.page); + await editNetworksPage.uncheckAllNetworks(); + await editNetworksPage.selectNetwork(networkName); + await editNetworksPage.updateNetworks(); + }); + } + async importKey(key: string) { await test.step('Import key', async () => { await this.navigate(); @@ -234,10 +255,14 @@ export class MetamaskPage implements WalletPage { return await test.step('Get current wallet address', async () => { await this.navigate(); await this.settingsMenu.openAccountSettings(); - const address = - await this.popoverElements.accountDetailAddressLabel.textContent(); + await this.page.getByTestId('account-details-row-address').click(); + const address = await this.page + .getByTestId('address-copy-button-text') + .last() + .locator('..') + .textContent(); await this.page.close(); - return getAddress(address).toLowerCase(); + return address.replace(/account \d/, '').toLowerCase(); }); } diff --git a/packages/wallets/src/metamask/pages/elements/networkList.element.ts b/packages/wallets/src/metamask/pages/elements/networkList.element.ts index a79c832e..da510d9b 100644 --- a/packages/wallets/src/metamask/pages/elements/networkList.element.ts +++ b/packages/wallets/src/metamask/pages/elements/networkList.element.ts @@ -3,6 +3,7 @@ import { NetworkSetting } from './networkSetting.element'; import { NetworkConfig } from '../../../wallets.constants'; import { ConsoleLogger } from '@nestjs/common'; import { SettingsElement } from './settings.element'; +import { isPopularMainnetNetwork, isPopularTestnetNetwork } from '../../helper'; export class NetworkList { logger = new ConsoleLogger(`MetaMask. ${NetworkList.name}`); @@ -59,13 +60,7 @@ export class NetworkList { } async openModalNetworkEdit(chainId: any) { - const hexChainId = chainId.toString(16); - const testIdPrefix = 'network-list-item-options-button-'; - // or locator used for different MM versions from latest and LATEST_STABLE_DOWNLOAD_LINK - const modalNetworkEditButton = this.dialogSection - .getByTestId(`${testIdPrefix}0x${hexChainId}`) // old stable version - .or(this.dialogSection.getByTestId(`${testIdPrefix}eip155:${chainId}`)); // new stable version - await modalNetworkEditButton.click(); + await this.getNetworkEditButton(chainId).click(); await this.editNetworkButton.click(); } @@ -74,19 +69,25 @@ export class NetworkList { rpcUrl: string, chainId: number, ): Promise { - const existNetworkByName = this.dialogSection.getByTestId(networkName); - if (await existNetworkByName.isHidden()) { - return false; - } - - try { - // By default no rpc label below network Name - const elements = this.page.getByTestId( - `network-rpc-name-button-0x${chainId.toString(16)}`, - ); - return rpcUrl.includes(await elements.textContent({ timeout: 1000 })); - } catch (Error) { - return false; + if ( + (await isPopularMainnetNetwork(networkName)) || + (await isPopularTestnetNetwork(networkName)) + ) { + // If the network is popular, we need to check if the network has additional installed our rpc + // if our rpc is not installed - we install rpc later + try { + // By default no rpc label below network Name + return rpcUrl.includes( + await this.getNetworkRpcDropdown(chainId).textContent({ + timeout: 1000, + }), + ); + } catch (Error) { + return false; + } + } else { + // If the network is not popular, we need to check only the network exists + return this.getNetworkEditButton(chainId).isVisible(); } } @@ -158,4 +159,19 @@ export class NetworkList { } await this.page.close(); } + + private getNetworkEditButton(chainId: number) { + const testIdPrefix = 'network-list-item-options-button-'; + // or locator used for different MM versions from latest and LATEST_STABLE_DOWNLOAD_LINK + return this.dialogSection + .getByTestId(`${testIdPrefix}0x${chainId.toString(16)}`) // old stable version + .or(this.dialogSection.getByTestId(`${testIdPrefix}eip155:${chainId}`)); // last MM version + } + + private getNetworkRpcDropdown(chainId: number) { + const buttonPrefix = 'network-rpc-name-button-'; + return this.page + .getByTestId(`${buttonPrefix}0x${chainId.toString(16)}`) // old stable version + .or(this.page.getByTestId(`${buttonPrefix}eip155:${chainId}`)); // last MM version + } } diff --git a/packages/wallets/src/metamask/pages/index.ts b/packages/wallets/src/metamask/pages/index.ts index 8e60e1f0..1861426e 100644 --- a/packages/wallets/src/metamask/pages/index.ts +++ b/packages/wallets/src/metamask/pages/index.ts @@ -1,3 +1,3 @@ export * from './home.page'; -export * from './settings.page'; + export * from './login.page'; diff --git a/packages/wallets/src/metamask/pages/navBarMenu/index.ts b/packages/wallets/src/metamask/pages/navBarMenu/index.ts new file mode 100644 index 00000000..0d171ac3 --- /dev/null +++ b/packages/wallets/src/metamask/pages/navBarMenu/index.ts @@ -0,0 +1,3 @@ +export * from './settings.page'; +export * from './permissions/allPermissions.page'; +export * from './permissions/editNetworks.page'; diff --git a/packages/wallets/src/metamask/pages/navBarMenu/permissions/allPermissions.page.ts b/packages/wallets/src/metamask/pages/navBarMenu/permissions/allPermissions.page.ts new file mode 100644 index 00000000..282c9e8a --- /dev/null +++ b/packages/wallets/src/metamask/pages/navBarMenu/permissions/allPermissions.page.ts @@ -0,0 +1,40 @@ +import { Locator, Page, test } from '@playwright/test'; +import { CommonWalletConfig } from '../../../../wallets.constants'; + +export class AllPermissionsPage { + tabBarMenu: Locator; + networkRow: Locator; + + constructor( + public page: Page, + private extensionUrl: string, + public walletConfig: CommonWalletConfig, + ) { + this.tabBarMenu = this.page.locator('.tab-bar'); + this.networkRow = this.page + .getByTestId('site-cell-connection-list-item') + .last(); + } + async openAllPermissions() { + await test.step('Open All permissions page', async () => { + await this.page.goto( + this.extensionUrl + + this.walletConfig.EXTENSION_START_PATH + + '#permissions', + ); + }); + } + + async openEditNetworksForWebsite(url: string) { + await test.step('Open edit networks for website', async () => { + const domain = new URL(url).host; + await this.page.locator(`button:has-text('${domain}')`).click(); + }); + } + + async openEditNetworksPage() { + await test.step('Open edit networks permission page', async () => { + await this.networkRow.getByTestId('edit').click(); + }); + } +} diff --git a/packages/wallets/src/metamask/pages/navBarMenu/permissions/editNetworks.page.ts b/packages/wallets/src/metamask/pages/navBarMenu/permissions/editNetworks.page.ts new file mode 100644 index 00000000..df5ee21b --- /dev/null +++ b/packages/wallets/src/metamask/pages/navBarMenu/permissions/editNetworks.page.ts @@ -0,0 +1,42 @@ +import { Locator, Page, test } from '@playwright/test'; + +export class EditNetworksTab { + private editNetworkDialog: Locator; + private selectAllButtonCheckBox: Locator; + private updateNetworksButton: Locator; + + constructor(public page: Page) { + this.editNetworkDialog = page.locator('section[role="dialog"]'); + this.selectAllButtonCheckBox = this.editNetworkDialog.getByRole( + 'checkbox', + { name: 'Select all' }, + ); + this.updateNetworksButton = this.editNetworkDialog.getByTestId( + 'connect-more-chains-button', + ); + } + + async uncheckAllNetworks() { + await test.step('Uncheck all networks', async () => { + while ( + (await this.selectAllButtonCheckBox.getAttribute('class')).match( + /mm-checkbox__input--(checked|indeterminate)/, + ) + ) { + await this.selectAllButtonCheckBox.click(); + } + }); + } + + async selectNetwork(networkName: string) { + await test.step(`Select network: ${networkName}`, async () => { + await this.editNetworkDialog.getByTestId(networkName).click(); + }); + } + + async updateNetworks() { + await test.step('Click update networks button', async () => { + await this.updateNetworksButton.click(); + }); + } +} diff --git a/packages/wallets/src/metamask/pages/settings.page.ts b/packages/wallets/src/metamask/pages/navBarMenu/settings.page.ts similarity index 78% rename from packages/wallets/src/metamask/pages/settings.page.ts rename to packages/wallets/src/metamask/pages/navBarMenu/settings.page.ts index f374ca62..55535073 100644 --- a/packages/wallets/src/metamask/pages/settings.page.ts +++ b/packages/wallets/src/metamask/pages/navBarMenu/settings.page.ts @@ -1,5 +1,5 @@ import { Locator, Page, test } from '@playwright/test'; -import { CommonWalletConfig } from '../../wallets.constants'; +import { CommonWalletConfig } from '../../../wallets.constants'; export class SettingsPage { tabBarMenu: Locator; @@ -18,10 +18,6 @@ export class SettingsPage { .getByRole('button') .getByText('Experimental'); - // Experimental page locators - this.inputNetworksForEachSiteToggle = this.page - .getByTestId('experimental-setting-toggle-request-queue') - .locator('input'); this.selectNetworksForEachSiteToggle = this.inputNetworksForEachSiteToggle.locator('..'); } diff --git a/packages/wallets/src/metamask/pages/onboarding.page.ts b/packages/wallets/src/metamask/pages/onboarding.page.ts index 1e0b01fd..ab63f85f 100644 --- a/packages/wallets/src/metamask/pages/onboarding.page.ts +++ b/packages/wallets/src/metamask/pages/onboarding.page.ts @@ -54,7 +54,7 @@ export class OnboardingPage { } async firstTimeSetup() { - await test.step('First time wallet setup (v2)', async () => { + await test.step('First time wallet setup', async () => { await this.getStartedButton.click(); await this.confirmTermsOfOnboarding(); await this.iHaveExistingWalletButton.click(); diff --git a/packages/wallets/src/metamask/pages/walletOperations.page.ts b/packages/wallets/src/metamask/pages/walletOperations.page.ts index ff5bf204..c7aabed3 100644 --- a/packages/wallets/src/metamask/pages/walletOperations.page.ts +++ b/packages/wallets/src/metamask/pages/walletOperations.page.ts @@ -52,6 +52,7 @@ export class WalletOperationPage { } async cancelAllTxInQueue() { + await this.rejectMultiplyRequestNotice(); test.step('Cancel all tx in queue', async () => { //Is there is any tx in queue. try { @@ -71,7 +72,20 @@ export class WalletOperationPage { }); } + async rejectMultiplyRequestNotice() { + try { + await this.page.getByTestId("We've noticed multiple requests").waitFor({ + state: 'visible', + timeout: 1000, + }); + } catch (er) { + return; + } + await this.page.locator('button:has-text("Cancel")').click(); + } + async cancelTransaction() { + await this.rejectMultiplyRequestNotice(); try { await this.cancelButton.click(); } catch { @@ -80,6 +94,7 @@ export class WalletOperationPage { } async confirmTransactionOfTokenApproval() { + await this.rejectMultiplyRequestNotice(); await test.step('Click "Use default" button in case if it exist', async () => { // todo: im not sure this step is needed now if (await this.page.locator('text=Use default').isVisible()) @@ -89,6 +104,7 @@ export class WalletOperationPage { } async confirmTransaction(setAggressiveGas?: boolean) { + await this.rejectMultiplyRequestNotice(); if (setAggressiveGas) { await this.editGasFeeButton.click(); await this.page.mouse.move(1, 1); diff --git a/packages/wallets/src/wallet.page.ts b/packages/wallets/src/wallet.page.ts index 153fbc33..0396d5eb 100644 --- a/packages/wallets/src/wallet.page.ts +++ b/packages/wallets/src/wallet.page.ts @@ -79,7 +79,7 @@ export interface WalletPage { isClosePage?: boolean, ): Promise; - changeNetwork?(networkName: string): Promise; + changeNetwork?(networkName: string, standUrl?: string): Promise; changeWalletAccountByName?( accountName: string, diff --git a/yarn.lock b/yarn.lock index 581784de..66c77169 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8063,7 +8063,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8095,7 +8104,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8848,7 +8864,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -8866,6 +8882,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"