From fb1b3cac59a833d938b792a82cd81d0bd31acd23 Mon Sep 17 00:00:00 2001 From: matstyler Date: Fri, 2 Aug 2024 00:29:35 +0200 Subject: [PATCH] feat: Test cases for network management and new tokens deployment --- .../metamask/src/cypress/configureSynpress.ts | 91 ++++++++++++++++++- .../src/cypress/support/synpressCommands.ts | 53 +++++++++++ .../pages/HomePage/actions/addNetwork.ts | 14 +-- .../src/selectors/pages/HomePage/index.ts | 2 +- wallets/metamask/src/type/Network.ts | 11 +++ .../metamask/test/cypress/addNetwork.cy.ts | 27 ++++++ .../metamask/test/cypress/addNewToken.cy.ts | 38 ++++++++ 7 files changed, 221 insertions(+), 15 deletions(-) create mode 100644 wallets/metamask/src/type/Network.ts create mode 100644 wallets/metamask/test/cypress/addNetwork.cy.ts create mode 100644 wallets/metamask/test/cypress/addNewToken.cy.ts diff --git a/wallets/metamask/src/cypress/configureSynpress.ts b/wallets/metamask/src/cypress/configureSynpress.ts index 6c5ec83da..adf7f2900 100644 --- a/wallets/metamask/src/cypress/configureSynpress.ts +++ b/wallets/metamask/src/cypress/configureSynpress.ts @@ -1,10 +1,14 @@ import type { BrowserContext, Page } from '@playwright/test' import { expect } from '@playwright/test' import { ensureRdpPort } from '@synthetixio/synpress-core' -import Selectors from '../selectors/pages/HomePage' +import HomePageSelectors from '../selectors/pages/HomePage' import getPlaywrightMetamask from './getPlaywrightMetamask' import importMetaMaskWallet from './support/importMetaMaskWallet' import { initMetaMask } from './support/initMetaMask' +import { type CreateAnvilOptions, createPool } from '@viem/anvil' +import type { Network } from '../type/Network' +import { waitFor } from '../playwright/utils/waitFor' +import Selectors from '../selectors/pages/HomePage' let metamaskInitialized = false @@ -118,7 +122,7 @@ export default function configureSynpress(on: Cypress.PluginEvents, config: Cypr await metamask.renameAccount(currentAccountName, newAccountName) - await metamaskExtensionPage.locator(Selectors.threeDotsMenu.accountDetailsCloseButton).click() + await metamaskExtensionPage.locator(HomePageSelectors.threeDotsMenu.accountDetailsCloseButton).click() await expect(metamaskExtensionPage.locator(metamask.homePage.selectors.accountMenu.accountButton)).toHaveText( newAccountName @@ -146,6 +150,76 @@ export default function configureSynpress(on: Cypress.PluginEvents, config: Cypr }) }, + async createAnvilNode(options?: CreateAnvilOptions) { + const pool = createPool() + + const nodeId = Array.from(pool.instances()).length + const anvil = await pool.start(nodeId, options) + + const rpcUrl = `http://${anvil.host}:${anvil.port}` + + const DEFAULT_ANVIL_CHAIN_ID = 31337 + const chainId = options?.chainId ?? DEFAULT_ANVIL_CHAIN_ID + + return { anvil, rpcUrl, chainId } + }, + + async connectToAnvil({ + rpcUrl, + chainId + }: { + rpcUrl: string + chainId: number + }) { + const metamask = getPlaywrightMetamask(context, metamaskExtensionPage, metamaskExtensionId) + + await metamask.addNetwork({ + name: 'Anvil', + rpcUrl, + chainId, + symbol: 'ETH', + blockExplorerUrl: 'https://etherscan.io/' + }) + }, + + async addNetwork(network: Network) { + const metamask = getPlaywrightMetamask(context, metamaskExtensionPage, metamaskExtensionId) + + await metamask.addNetwork(network) + + await waitFor( + () => metamaskExtensionPage.locator(HomePageSelectors.networkAddedPopover.switchToNetworkButton).isVisible(), + 3_000, + false + ) + + await metamaskExtensionPage.locator(HomePageSelectors.networkAddedPopover.switchToNetworkButton).click() + + return true + }, + + // Token + + async deployToken() { + const metamask = getPlaywrightMetamask(context, metamaskExtensionPage, metamaskExtensionId) + + await metamask.confirmTransaction() + + return true + }, + + async addNewToken() { + const metamask = getPlaywrightMetamask(context, metamaskExtensionPage, metamaskExtensionId) + + await metamask.addNewToken() + + await expect(metamaskExtensionPage.locator(Selectors.portfolio.singleToken).nth(1)).toContainText('TST') + + return true + }, + + // Others + async providePublicEncryptionKey() { const metamask = getPlaywrightMetamask(context, metamaskExtensionPage, metamaskExtensionId) @@ -183,6 +257,19 @@ export default function configureSynpress(on: Cypress.PluginEvents, config: Cypr .catch(() => { return false }) + }, + + async confirmTransaction() { + const metamask = getPlaywrightMetamask(context, metamaskExtensionPage, metamaskExtensionId) + + return await metamask + .confirmTransaction() + .then(() => { + return true + }) + .catch(() => { + return false + }) } }) diff --git a/wallets/metamask/src/cypress/support/synpressCommands.ts b/wallets/metamask/src/cypress/support/synpressCommands.ts index 000c60d1c..7e7e9d01f 100644 --- a/wallets/metamask/src/cypress/support/synpressCommands.ts +++ b/wallets/metamask/src/cypress/support/synpressCommands.ts @@ -9,6 +9,9 @@ // https://on.cypress.io/custom-commands // *********************************************** +import type { CreateAnvilOptions, Anvil } from '@viem/anvil' +import type { Network } from '../../type/Network' + declare global { namespace Cypress { interface Chainable { @@ -22,9 +25,21 @@ declare global { renameAccount(currentAccountName: string, newAccountName: string): Chainable switchNetwork(networkName: string, isTestnet?: boolean): Chainable + createAnvilNode(options?: CreateAnvilOptions): Chainable<{ + anvil: Anvil + rpcUrl: string + chainId: number + }> + connectToAnvil(): Chainable + addNetwork(network: Network): Chainable + + deployToken(): Chainable + addNewToken(): Chainable + providePublicEncryptionKey(): Chainable decrypt(): Chainable confirmSignature(): Chainable + confirmTransaction(): Chainable } } } @@ -41,6 +56,8 @@ export default function synpressCommands() { return cy.task('connectToDapp') }) + // Account + Cypress.Commands.add('addNewAccount', (accountName: string) => { return cy.task('addNewAccount', accountName) }) @@ -51,9 +68,42 @@ export default function synpressCommands() { return cy.task('renameAccount', { currentAccountName, newAccountName }) }) + // Network + Cypress.Commands.add('switchNetwork', (networkName: string, isTestnet = false) => { return cy.task('switchNetwork', { networkName, isTestnet }) }) + Cypress.Commands.add('createAnvilNode', (options?: CreateAnvilOptions) => { + return cy.task('createAnvilNode', options) + }) + Cypress.Commands.add('connectToAnvil', () => { + return cy.task('createAnvilNode').then((anvilNetwork) => { + const network = { + name: 'Anvil', + rpcUrl: anvilNetwork.rpcUrl, + chainId: anvilNetwork.chainId, + symbol: 'ETH', + blockExplorerUrl: 'https://etherscan.io/' + } + + return cy.task('addNetwork', network) + }) + }) + Cypress.Commands.add('addNetwork', (network: Network) => { + return cy.task('addNetwork', network) + }) + + // Token + + Cypress.Commands.add('deployToken', () => { + return cy.task('deployToken') + }) + Cypress.Commands.add('addNewToken', () => { + return cy.task('addNewToken') + }) + + // Others + Cypress.Commands.add('providePublicEncryptionKey', () => { return cy.task('providePublicEncryptionKey') }) @@ -63,4 +113,7 @@ export default function synpressCommands() { Cypress.Commands.add('confirmSignature', () => { return cy.task('confirmSignature') }) + Cypress.Commands.add('confirmTransaction', () => { + return cy.task('confirmTransaction') + }) } diff --git a/wallets/metamask/src/playwright/pages/HomePage/actions/addNetwork.ts b/wallets/metamask/src/playwright/pages/HomePage/actions/addNetwork.ts index 7a23f2cf4..d307123e4 100644 --- a/wallets/metamask/src/playwright/pages/HomePage/actions/addNetwork.ts +++ b/wallets/metamask/src/playwright/pages/HomePage/actions/addNetwork.ts @@ -1,21 +1,11 @@ import type { Page } from '@playwright/test' -import { z } from 'zod' import Selectors from '../../../../selectors/pages/HomePage' import { waitFor } from '../../../utils/waitFor' import { closeNetworkAddedPopover, closeNewNetworkInfoPopover } from './popups' - -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 +import { type Network, NetworkValidation } from '../../../../type/Network' export async function addNetwork(page: Page, network: Network) { - const { name, rpcUrl, chainId, symbol, blockExplorerUrl } = Network.parse(network) + const { name, rpcUrl, chainId, symbol, blockExplorerUrl } = NetworkValidation.parse(network) await page.locator(Selectors.networkDropdown.dropdownButton).click() await page.locator(Selectors.networkDropdown.addNetworkButton).click() diff --git a/wallets/metamask/src/selectors/pages/HomePage/index.ts b/wallets/metamask/src/selectors/pages/HomePage/index.ts index d3a8a10b1..61e04cf29 100644 --- a/wallets/metamask/src/selectors/pages/HomePage/index.ts +++ b/wallets/metamask/src/selectors/pages/HomePage/index.ts @@ -54,7 +54,7 @@ const popover = { } const networkAddedPopover = { - switchToNetworkButton: '.home__new-network-added button.btn-primary', + switchToNetworkButton: '.home__new-network-added__switch-to-button', dismissButton: '.home__new-network-added button.btn-secondary' } diff --git a/wallets/metamask/src/type/Network.ts b/wallets/metamask/src/type/Network.ts new file mode 100644 index 000000000..0621fbd00 --- /dev/null +++ b/wallets/metamask/src/type/Network.ts @@ -0,0 +1,11 @@ +import { z } from 'zod' + +export const NetworkValidation = z.object({ + name: z.string(), + rpcUrl: z.string(), + chainId: z.number(), + symbol: z.string(), + blockExplorerUrl: z.string().optional() +}) + +export type Network = z.infer diff --git a/wallets/metamask/test/cypress/addNetwork.cy.ts b/wallets/metamask/test/cypress/addNetwork.cy.ts new file mode 100644 index 000000000..e0aab1373 --- /dev/null +++ b/wallets/metamask/test/cypress/addNetwork.cy.ts @@ -0,0 +1,27 @@ +it("should add network and close network added popup", () => { + cy.createAnvilNode().then(({ rpcUrl, chainId }) => { + const network = { + name: "Anvil", + rpcUrl, + chainId, + symbol: "ETH", + blockExplorerUrl: "https://etherscan.io/", + }; + + cy.addNetwork(network).then(() => cy.getNetwork().should("eq", "Anvil")); + }); +}); + +it("should add network without block explorer", () => { + cy.createAnvilNode().then(({ rpcUrl, chainId }) => { + const network = { + name: "Anvil2", + rpcUrl, + chainId, + symbol: "ETH", + blockExplorerUrl: undefined, + }; + + cy.addNetwork(network).then(() => cy.getNetwork().should("eq", "Anvil2")); + }); +}); diff --git a/wallets/metamask/test/cypress/addNewToken.cy.ts b/wallets/metamask/test/cypress/addNewToken.cy.ts new file mode 100644 index 000000000..6815371b1 --- /dev/null +++ b/wallets/metamask/test/cypress/addNewToken.cy.ts @@ -0,0 +1,38 @@ +before(() => { + cy.connectToAnvil() + cy.getNetwork().should('eq', 'Anvil') + + cy.get('#connectButton').click() + + cy.connectToDapp() +}) + +it('should add new token to MetaMask', () => { + cy.get('#createToken').click() + + // wait for the blockchain - todo: replace with an event handler + cy.wait(5000) + + cy.deployToken().then(() => { + // wait for the blockchain - todo: replace with an event handler + cy.wait(5000) + + cy.get('#tokenAddresses').should('have.text', '0x5FbDB2315678afecb367f032d93F642f64180aa3') + + cy.get('#watchAssets').click() + + cy.addNewToken() + }) +}) + +it('should add new token using EIP747', () => { + cy.get('#eip747ContractAddress').type('0x5FbDB2315678afecb367f032d93F642f64180aa3') + cy.get('#eip747Symbol').type('TST') + cy.get('#eip747Decimals').type('4') + + cy.get('#eip747WatchButton').click() + + cy.addNewToken().then(() => { + cy.get('#eip747Status').should('have.text', 'NFT added successfully') + }) +})