Skip to content

Commit

Permalink
✨ feat(metamask): Add support for adding networks on demand (#1006)
Browse files Browse the repository at this point in the history
  • Loading branch information
duckception authored Nov 21, 2023
1 parent 64297af commit ced0a9d
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 12 deletions.
5 changes: 5 additions & 0 deletions wallets/metamask/src/metamask.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BrowserContext, Page } from '@playwright/test'
import { CrashPage, HomePage, LockPage, NotificationPage, OnboardingPage } from './pages'
import type { Network } from './pages/HomePage/actions'
import { SettingsSidebarMenus } from './pages/HomePage/selectors/settings'

const NO_EXTENSION_ID_ERROR = new Error('MetaMask extensionId is not set')
Expand Down Expand Up @@ -37,6 +38,10 @@ export class MetaMask {
await this.homePage.switchAccount(accountName)
}

async addNetwork(network: Network) {
await this.homePage.addNetwork(network)
}

async switchNetwork(networkName: string) {
await this.homePage.switchNetwork(networkName)
}
Expand Down
51 changes: 51 additions & 0 deletions wallets/metamask/src/pages/HomePage/actions/addNetwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Page } from '@playwright/test'
import { z } from 'zod'
import { waitFor } from '../../../utils/waitFor'
import Selectors from '../selectors'

const Network = z.object({
name: z.string(),
rpcUrl: z.string(),
chainId: z.number(),
symbol: z.string(),
blockExplorerUrl: z.string().optional()
})

export type Network = z.infer<typeof Network>

export async function addNetwork(page: Page, network: Network) {
const { name, rpcUrl, chainId, symbol, blockExplorerUrl } = Network.parse(network)

await page.locator(Selectors.networkDropdown.dropdownButton).click()
await page.locator(Selectors.networkDropdown.addNetworkButton).click()

await page.locator(Selectors.settings.networks.addNetworkManuallyButton).click()

await page.locator(Selectors.settings.networks.newNetworkForm.networkNameInput).fill(name)

await page.locator(Selectors.settings.networks.newNetworkForm.rpcUrlInput).fill(rpcUrl)

// We have to wait for the RPC URL error to appear.
const rpcUrlErrorLocator = page.locator(Selectors.settings.networks.newNetworkForm.rpcUrlError)
if (await waitFor(() => rpcUrlErrorLocator.isVisible(), 1_000, false)) {
const rpcUrlErrorText = await rpcUrlErrorLocator.textContent({ timeout: 1_000 })
throw new Error(`[AddNetwork] RPC URL error: ${rpcUrlErrorText}`)
}

await page.locator(Selectors.settings.networks.newNetworkForm.chainIdInput).fill(chainId.toString())

// We have to wait for the Chain ID error to appear.
const chainIdErrorLocator = page.locator(Selectors.settings.networks.newNetworkForm.chainIdError)
if (await waitFor(() => chainIdErrorLocator.isVisible(), 1_000, false)) {
const chainIdErrorText = await chainIdErrorLocator.textContent({ timeout: 1_000 })
throw new Error(`[AddNetwork] Chain ID error: ${chainIdErrorText}`)
}

await page.locator(Selectors.settings.networks.newNetworkForm.symbolInput).fill(symbol)

if (blockExplorerUrl) {
await page.locator(Selectors.settings.networks.newNetworkForm.blockExplorerUrlInput).fill(blockExplorerUrl)
}

await page.locator(Selectors.settings.networks.newNetworkForm.saveButton).click()
}
1 change: 1 addition & 0 deletions wallets/metamask/src/pages/HomePage/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './importWalletFromPrivateKey'
export * from './switchAccount'
export * from './settings'
export * from './switchNetwork'
export * from './addNetwork'
7 changes: 6 additions & 1 deletion wallets/metamask/src/pages/HomePage/page.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Page } from '@playwright/test'
import { importWalletFromPrivateKey, lock, settings, switchAccount, switchNetwork } from './actions'
import { addNetwork, importWalletFromPrivateKey, lock, settings, switchAccount, switchNetwork } from './actions'
import type { Network } from './actions'
import Selectors from './selectors'
import type { SettingsSidebarMenus } from './selectors/settings'

Expand Down Expand Up @@ -44,4 +45,8 @@ export class HomePage {
async switchNetwork(networkName: string) {
await switchNetwork(this.page, networkName)
}

async addNetwork(network: Network) {
await addNetwork(this.page, network)
}
}
3 changes: 2 additions & 1 deletion wallets/metamask/src/pages/HomePage/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const recoveryPhraseReminder = {

const networkDropdown = {
dropdownButton: createDataTestSelector('network-display'),
networks: `${createDataTestSelector('network-droppo')} .network-dropdown-list li > span`
networks: `${createDataTestSelector('network-droppo')} .network-dropdown-list li > span`,
addNetworkButton: `${createDataTestSelector('network-droppo')} .network__add-network-button > button`
}

export default {
Expand Down
21 changes: 20 additions & 1 deletion wallets/metamask/src/pages/HomePage/selectors/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,27 @@ const advanced = {
showTestNetworksToggle: `${createDataTestSelector('advanced-setting-show-testnet-conversion')} .toggle-button`
}

const newNetworkFormContainer = '.networks-tab__add-network-form'
const newNetworkForm = {
networkNameInput: `${newNetworkFormContainer} .form-field:nth-child(1) input`,
rpcUrlInput: `${newNetworkFormContainer} .form-field:nth-child(2) input`,
rpcUrlError: `${newNetworkFormContainer} .form-field:nth-child(2) .form-field__error`,
chainIdInput: `${newNetworkFormContainer} .form-field:nth-child(3) input`,
chainIdError: `${newNetworkFormContainer} .form-field:nth-child(3) .form-field__error`,
symbolInput: `${newNetworkFormContainer} .form-field:nth-child(4) input`,
blockExplorerUrlInput: `${newNetworkFormContainer} .form-field:nth-child(5) input`,
cancelButton: `${newNetworkFormContainer} .networks-tab__add-network-form-footer button.btn-secondary`,
saveButton: `${newNetworkFormContainer} .networks-tab__add-network-form-footer button.btn-primary`
}

const networks = {
addNetworkManuallyButton: `${createDataTestSelector('add-network-manually')}`,
newNetworkForm
}

export default {
SettingsSidebarMenus,
sidebarMenu,
advanced
advanced,
networks
}
69 changes: 69 additions & 0 deletions wallets/metamask/test/e2e/metamask/addNetwork.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { testWithSynpress } from 'fixtures'
import { MetaMask, unlockForFixture } from '../../../src'

import { z } from 'zod'
import basicSetup from '../wallet-setup/basic.setup'

const test = testWithSynpress(basicSetup, unlockForFixture)

const { expect } = test

const network = {
name: 'OP Mainnet',
rpcUrl: 'https://mainnet.optimism.io',
chainId: 10,
symbol: 'ETH',
blockExplorerUrl: 'https://optimistic.etherscan.io'
}

test('should add network', async ({ context, metamaskPage }) => {
const metamask = new MetaMask(context, metamaskPage, basicSetup.walletPassword)

await metamask.addNetwork(network)

await expect(metamaskPage.locator(metamask.homePage.selectors.currentNetwork)).toHaveText(network.name)
})

test('should add network without block explorer', async ({ context, metamaskPage }) => {
const metamask = new MetaMask(context, metamaskPage, basicSetup.walletPassword)

await metamask.addNetwork({
...network,
blockExplorerUrl: undefined
})

await expect(metamaskPage.locator(metamask.homePage.selectors.currentNetwork)).toHaveText(network.name)
})

test('should validate the network object with Zod', async ({ context, metamaskPage }) => {
const metamask = new MetaMask(context, metamaskPage, basicSetup.walletPassword)

// @ts-ignore
await expect(metamask.addNetwork({})).rejects.toThrowError(z.ZodError)
})

test('should throw if there is an issue with rpc url', async ({ context, metamaskPage }) => {
const metamask = new MetaMask(context, metamaskPage, basicSetup.walletPassword)

const promise = metamask.addNetwork({
...network,
rpcUrl: 'hps://mainnet.optimism.io' // Incorrect.
})

await expect(promise).rejects.toThrowError(
'[AddNetwork] RPC URL error: URLs require the appropriate HTTP/HTTPS prefix.'
)
})

test('should throw if there is an issue with chain id', async ({ context, metamaskPage }) => {
const metamask = new MetaMask(context, metamaskPage, basicSetup.walletPassword)

const promise = metamask.addNetwork({
...network,
chainId: 0x42069 // Incorrect.
})

await expect(promise).rejects.toThrowError(
'[AddNetwork] Chain ID error: The RPC URL you have entered returned a different chain ID (10). Please update the Chain ID to match the RPC URL of the network you are trying to add.'
)
})
11 changes: 2 additions & 9 deletions wallets/metamask/test/e2e/metamask/switchNetwork.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ const test = testWithSynpress(basicSetup, unlockForFixture)

const { expect } = test

test.use({
permissions: ['clipboard-read']
})

test('should switch network', async ({ context, metamaskPage }) => {
const metamask = new MetaMask(context, metamaskPage, basicSetup.walletPassword)

Expand All @@ -21,15 +17,12 @@ test('should switch network', async ({ context, metamaskPage }) => {

await metamask.toggleShowTestNetworks()

const networkBefore = await metamaskPage.locator(metamask.homePage.selectors.currentNetwork).textContent()

expect(networkBefore).toEqual('Ethereum Mainnet')
await expect(metamaskPage.locator(metamask.homePage.selectors.currentNetwork)).toHaveText('Ethereum Mainnet')

const targetNetwork = 'Sepolia test network'
await metamask.switchNetwork(targetNetwork)

const networkAfter = await metamaskPage.locator(metamask.homePage.selectors.currentNetwork).textContent()
expect(networkAfter).toEqual(targetNetwork)
await expect(metamaskPage.locator(metamask.homePage.selectors.currentNetwork)).toHaveText(targetNetwork)
})

test('should throw an error if there is no account with target name', async ({ context, metamaskPage }) => {
Expand Down

0 comments on commit ced0a9d

Please sign in to comment.