diff --git a/.gitignore b/.gitignore index cfd72b71a..94d16f61e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,8 @@ playwright/.cache ### Vercel .vercel + +### Cypress + +**/ethereum-wallet-mock/cypress +**/metamask/cypress diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ade8d9488..d7e53cacc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -227,9 +227,15 @@ importers: wallets/ethereum-wallet-mock: dependencies: + '@depay/web3-client': + specifier: 10.18.6 + version: 10.18.6(@depay/solana-web3.js@1.26.0)(@depay/web3-blockchains@9.3.6)(ethers@5.7.2) '@depay/web3-mock': specifier: 14.17.0 version: 14.17.0 + '@depay/web3-mock-evm': + specifier: ^14.17.0 + version: 14.17.0 '@playwright/test': specifier: 1.44.0 version: 1.44.0 @@ -849,6 +855,35 @@ packages: engines: {node: '>=10'} dev: false + /@depay/web3-blockchains@9.3.6: + resolution: {integrity: sha512-PdNxxR9w8rY+vMjZyx/oBFiJpSBrCNKJT9beBB7LSvBdtNxrcFO6h0xBpDQc+jHTsjDHWxmn3k6RxuTYNp3N0Q==} + engines: {node: '>=10'} + dev: false + + /@depay/web3-client@10.18.6(@depay/solana-web3.js@1.26.0)(@depay/web3-blockchains@9.3.6)(ethers@5.7.2): + resolution: {integrity: sha512-JeUAZ04/dsIra1ao3mvqAdVhRf4U1YxSoH0mE+XxhcXPgLC7KQTzH6oCLp07tAmoxholEL2cf5Oo20n6q1fZ/w==} + engines: {node: '>=16'} + peerDependencies: + '@depay/solana-web3.js': ^1.25.1 + '@depay/web3-blockchains': ^9.3.6 + ethers: ^5.7.1 + dependencies: + '@depay/solana-web3.js': 1.26.0 + '@depay/web3-blockchains': 9.3.6 + ethers: 5.7.2 + dev: false + + /@depay/web3-mock-evm@14.17.0: + resolution: {integrity: sha512-f/GPKWY8roW0n8TfaV5p+EHXw7+H1pCwTHTcHUZFUMYoqUCxTS1zCbRa1i9vx4sdS4TQ7VeRcyg8+t6u/kAxqg==} + engines: {node: '>=16'} + dependencies: + '@depay/web3-blockchains': 9.3.5 + ethers: 5.7.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@depay/web3-mock@14.17.0: resolution: {integrity: sha512-0WCIpHqGUTPmOb5l3iN+4wCY+P3nHnGWd3uyWB+Wrt5DygS6MWI2b50gwtSCgYUCfgmEv9KlRuCnHDC4TDKCeA==} engines: {node: '>=16'} diff --git a/wallets/ethereum-wallet-mock/package.json b/wallets/ethereum-wallet-mock/package.json index 5296f1131..d51116ad3 100644 --- a/wallets/ethereum-wallet-mock/package.json +++ b/wallets/ethereum-wallet-mock/package.json @@ -27,7 +27,9 @@ "types:check": "tsc --noEmit" }, "dependencies": { + "@depay/web3-client": "10.18.6", "@depay/web3-mock": "14.17.0", + "@depay/web3-mock-evm": "^14.17.0", "@synthetixio/synpress-core": "0.0.1-alpha.7", "viem": "2.9.9" }, diff --git a/wallets/ethereum-wallet-mock/src/constants.ts b/wallets/ethereum-wallet-mock/src/constants.ts new file mode 100644 index 000000000..56efa00aa --- /dev/null +++ b/wallets/ethereum-wallet-mock/src/constants.ts @@ -0,0 +1,11 @@ +// Wallet +export const SEED_PHRASE = 'test test test test test test test test test test test junk' + +export const BLOCKCHAIN = 'ethereum' + +export const ACCOUNT_MOCK = '0xd73b04b0e696b0945283defa3eee453814758f1a' + +// Anvil +export const ANVIL_CHAIN_ID = 31337 + +export const ANVIL_URL_URL = 'http://anvil:5000' diff --git a/wallets/ethereum-wallet-mock/src/cypress/EthereumWalletMock.ts b/wallets/ethereum-wallet-mock/src/cypress/EthereumWalletMock.ts new file mode 100644 index 000000000..0c8820367 --- /dev/null +++ b/wallets/ethereum-wallet-mock/src/cypress/EthereumWalletMock.ts @@ -0,0 +1,180 @@ +import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts' +import { ACCOUNT_MOCK, BLOCKCHAIN } from '../constants' +import { EthereumWalletMockAbstract } from '../type/EthereumWalletMockAbstract' +import type { Network } from '../type/Network' +import type { WalletMock } from '../type/WalletMock' + +export default class EthereumWalletMock extends EthereumWalletMockAbstract { + constructor(wallet: WalletMock = 'metamask') { + super(wallet) + this.wallet = wallet + } + + // /** + // * Imports a wallet using the given seed phrase. + // * + // * @param seedPhrase - The seed phrase to import. + // */ + importWallet(seedPhrase: string) { + this.seedPhrase = seedPhrase + + cy.window().then((cypressWindow) => { + cypressWindow.Web3Mock.mock({ + blockchain: BLOCKCHAIN, + wallet: this.wallet, + accounts: { return: [ACCOUNT_MOCK] } + }) + }) + } + + // /** + // * Retrieves the current account address. + // */ + getAllAccounts(): Cypress.Chainable<`0x${string}`[]> { + return cy.window().then((cypressWindow) => { + return cypressWindow.ethereum.request({ + method: 'eth_requestAccounts' + }) + }) + } + + // /** + // * Adds a new account. This account is based on the initially imported seed phrase. + // */ + addNewAccount() { + this.getAllAccounts().then((accounts) => { + const newAccount = mnemonicToAccount(this.seedPhrase || '', { + accountIndex: accounts?.length + }) + + cy.window().then((cypressWindow) => { + cypressWindow.Web3Mock.mock({ + blockchain: BLOCKCHAIN, + wallet: this.wallet, + accounts: { + return: [newAccount.address, ...accounts] + } + }) + }) + }) + } + + // /** + // * Imports a wallet using the given private key. + // * + // * @param privateKey - The private key to import. + // */ + importWalletFromPrivateKey(privateKey: `0x${string}`) { + const newAccount = privateKeyToAccount(privateKey) + + cy.window().then((cypressWindow) => { + cypressWindow.Web3Mock.mock({ + blockchain: BLOCKCHAIN, + wallet: this.wallet, + accounts: { return: [newAccount.address] } + }) + }) + } + + /** + // * Switches to the account with the given name. + // * + // * @param accountAddress - The name of the account to switch to. + // */ + switchAccount(accountAddress: string) { + cy.window().then((cypressWindow) => { + cypressWindow.Web3Mock.mock({ + blockchain: BLOCKCHAIN, + wallet: this.wallet, + accounts: { return: [accountAddress] } + }) + }) + } + + /** + // * Adds a new network. + // * + // * @param network - The network object to use for adding the new network. + // */ + async addNetwork(network: Network) { + const networkInfo = { + chainId: network.chainId, + chainName: network.name, + nativeCurrency: network.nativeCurrency, + rpcUrls: [network.rpcUrl], + blockExplorerUrls: [network.blockExplorerUrl] + } + + cy.window().then((cypressWindow) => { + cypressWindow.Web3Mock.mock({ + blockchain: BLOCKCHAIN, + wallet: this.wallet, + network: { + add: networkInfo + } + }) + }) + } + + // /** + // * Retrieves the current account address. + // */ + getAccountAddress() { + return this.getAllAccounts().then((accounts) => { + return accounts[0] as `0x${string}` + }) + } + + // /** + // * Switches to the network with the given name. + // * + // * @param networkName - The name of the network to switch to. + // */ + async switchNetwork(networkName: string) { + cy.window().then((cypressWindow) => { + cypressWindow.Web3Mock.mock({ + blockchain: BLOCKCHAIN, + wallet: this.wallet, + network: { + add: { + chainId: 1, + chainName: networkName, + nativeCurrency: { + name: 'ETH', + symbol: 'ETH', + decimals: 18 + }, + rpcUrls: ['http://localhost:8545'] + } + } + }) + }) + } + + // /** + // * Connects wallet to the dapp. + // * + // * @param wallet - The wallet to connect to the dapp. + // */ + async connectToDapp(wallet: WalletMock = 'metamask') { + this.wallet = wallet + + cy.window().then((cypressWindow) => { + class WalletConnectStub {} + + let connector: WalletConnectStub | undefined + + if (wallet === 'walletconnect') { + connector = WalletConnectStub + } + + cypressWindow.Web3Mock.mock({ + blockchain: BLOCKCHAIN, + wallet, + accounts: { return: [ACCOUNT_MOCK] }, + // @ts-ignore + connector + }) + }) + } +} diff --git a/wallets/ethereum-wallet-mock/src/cypress/errors.ts b/wallets/ethereum-wallet-mock/src/cypress/constants/errors.ts similarity index 100% rename from wallets/ethereum-wallet-mock/src/cypress/errors.ts rename to wallets/ethereum-wallet-mock/src/cypress/constants/errors.ts diff --git a/wallets/ethereum-wallet-mock/src/cypress/index.ts b/wallets/ethereum-wallet-mock/src/cypress/index.ts index c1cf1560a..8360764af 100644 --- a/wallets/ethereum-wallet-mock/src/cypress/index.ts +++ b/wallets/ethereum-wallet-mock/src/cypress/index.ts @@ -1 +1,2 @@ -export { default as installSynpress } from './installSynpress' +export { default as installSynpress } from './utils/installSynpress' +export { default as EthereumWalletMock } from './EthereumWalletMock' diff --git a/wallets/ethereum-wallet-mock/src/cypress/setupTasks.ts b/wallets/ethereum-wallet-mock/src/cypress/setupTasks.ts deleted file mode 100644 index 6a801b90a..000000000 --- a/wallets/ethereum-wallet-mock/src/cypress/setupTasks.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { getEthereumWalletMock } from './initEthereumWalletMock' - -export default function setupTasks(on: Cypress.PluginEvents) { - on('task', { - importWallet: async function (seedPhrase: string) { - const ethereumWalletMock = getEthereumWalletMock() - if (ethereumWalletMock) { - await ethereumWalletMock.importWallet(seedPhrase) - } - return true - }, - addNewAccount: async function () { - const ethereumWalletMock = getEthereumWalletMock() - if (ethereumWalletMock) { - await ethereumWalletMock.addNewAccount() - } - return true - }, - getAllAccounts: async function () { - const ethereumWalletMock = getEthereumWalletMock() - if (ethereumWalletMock) { - return await ethereumWalletMock.getAllAccounts() - } - return [] - } - }) -} diff --git a/wallets/ethereum-wallet-mock/src/cypress/support/commands.ts b/wallets/ethereum-wallet-mock/src/cypress/support/commands.ts index 88021a178..bccdc15eb 100644 --- a/wallets/ethereum-wallet-mock/src/cypress/support/commands.ts +++ b/wallets/ethereum-wallet-mock/src/cypress/support/commands.ts @@ -9,8 +9,10 @@ // https://on.cypress.io/custom-commands // *********************************************** -import type { WalletMock } from '../../EthereumWalletMock' -import type { Network } from '../../network/Network' +import type { Network } from '../../type/Network' +import type { WalletMock } from '../../type/WalletMock' +// import type { Network } from "../../type/Network"; +import getEthereumWalletMock from '../utils/getEthereumWalletMock' declare global { namespace Cypress { @@ -19,21 +21,81 @@ declare global { importWalletFromPrivateKey(privateKey: `0x${string}`): Chainable addNewAccount(): Chainable getAllAccounts(): Chainable> + getAccountAddress(): Chainable<`0x${string}`> switchAccount(accountAddress: string): Chainable addNetwork(network: Network): Chainable - getAccountAddress(): Chainable<`0x${string}`> switchNetwork(networkName: string): Chainable connectToDapp(wallet?: WalletMock): Chainable } + + interface ApplicationWindow { + Web3Mock: { + mock: (options: { + blockchain: string + wallet: string + accounts?: { return: Array } + network?: { + add: { + chainId: number + chainName: string + nativeCurrency: + | { + name: string + symbol: string + decimals: number + } + | undefined + rpcUrls: Array + } + } + }) => void + } + } } } -Cypress.Commands.add('importWallet', (seedPhrase) => cy.task('importWallet', seedPhrase)) -Cypress.Commands.add('importWalletFromPrivateKey', (privateKey) => cy.task('importWalletFromPrivateKey', privateKey)) -Cypress.Commands.add('addNewAccount', () => cy.task('addNewAccount')) -Cypress.Commands.add('getAllAccounts', () => cy.task('getAllAccounts')) -Cypress.Commands.add('switchAccount', (accountAddress) => cy.task('switchAccount', accountAddress)) -Cypress.Commands.add('addNetwork', (network) => cy.task('addNetwork', network)) -Cypress.Commands.add('getAccountAddress', () => cy.task('getAccountAddress')) -Cypress.Commands.add('switchNetwork', (networkName) => cy.task('switchNetwork', networkName)) -Cypress.Commands.add('connectToDapp', (wallet) => cy.task('connectToDapp', wallet)) +Cypress.Commands.add('importWallet', (seedPhrase) => { + const ethereumWalletMock = getEthereumWalletMock() + ethereumWalletMock.importWallet(seedPhrase) +}) + +Cypress.Commands.add('importWalletFromPrivateKey', (privateKey) => { + const ethereumWalletMock = getEthereumWalletMock() + ethereumWalletMock.importWalletFromPrivateKey(privateKey) +}) + +Cypress.Commands.add('addNewAccount', () => { + const ethereumWalletMock = getEthereumWalletMock() + return ethereumWalletMock.addNewAccount() +}) + +Cypress.Commands.add('getAllAccounts', () => { + const ethereumWalletMock = getEthereumWalletMock() + + return ethereumWalletMock.getAllAccounts() +}) + +Cypress.Commands.add('getAccountAddress', () => { + const ethereumWalletMock = getEthereumWalletMock() + return ethereumWalletMock.getAccountAddress() +}) + +Cypress.Commands.add('switchAccount', (accountAddress) => { + const ethereumWalletMock = getEthereumWalletMock() + ethereumWalletMock.switchAccount(accountAddress) +}) + +Cypress.Commands.add('addNetwork', (network) => { + const ethereumWalletMock = getEthereumWalletMock() + ethereumWalletMock.addNetwork(network) +}) + +Cypress.Commands.add('switchNetwork', (networkName) => { + const ethereumWalletMock = getEthereumWalletMock() + ethereumWalletMock.switchNetwork(networkName) +}) + +Cypress.Commands.add('connectToDapp', (wallet) => { + const ethereumWalletMock = getEthereumWalletMock() + ethereumWalletMock.connectToDapp(wallet) +}) diff --git a/wallets/ethereum-wallet-mock/src/cypress/support/e2e.ts b/wallets/ethereum-wallet-mock/src/cypress/support/e2e.ts index c90b6b6d3..6c7c31871 100644 --- a/wallets/ethereum-wallet-mock/src/cypress/support/e2e.ts +++ b/wallets/ethereum-wallet-mock/src/cypress/support/e2e.ts @@ -15,3 +15,9 @@ // Import commands.js using ES2015 syntax: import './commands' +import './mockEthereum' + +Cypress.on('uncaught:exception', () => { + // failing the test + return false +}) diff --git a/wallets/ethereum-wallet-mock/src/cypress/support/mockEthereum.ts b/wallets/ethereum-wallet-mock/src/cypress/support/mockEthereum.ts new file mode 100644 index 000000000..886d06fcf --- /dev/null +++ b/wallets/ethereum-wallet-mock/src/cypress/support/mockEthereum.ts @@ -0,0 +1,10 @@ +before(() => { + cy.visit('/', { + onBeforeLoad: (window) => { + window.Web3Mock.mock({ + blockchain: 'ethereum', + wallet: 'metamask' + }) + } + }) +}) diff --git a/wallets/ethereum-wallet-mock/src/cypress/utils/getEthereumWalletMock.ts b/wallets/ethereum-wallet-mock/src/cypress/utils/getEthereumWalletMock.ts new file mode 100644 index 000000000..5f83a044a --- /dev/null +++ b/wallets/ethereum-wallet-mock/src/cypress/utils/getEthereumWalletMock.ts @@ -0,0 +1,13 @@ +import { SEED_PHRASE } from '../../constants' +import EthereumWalletMock from '../EthereumWalletMock' + +let ethereumWalletMock: EthereumWalletMock | undefined + +export default function getEthereumWalletMock() { + if (ethereumWalletMock) return ethereumWalletMock + + ethereumWalletMock = new EthereumWalletMock() + ethereumWalletMock.importWallet(SEED_PHRASE) + + return ethereumWalletMock +} diff --git a/wallets/ethereum-wallet-mock/src/cypress/initEthereumWalletMock.ts b/wallets/ethereum-wallet-mock/src/cypress/utils/initEthereumWalletMock.ts similarity index 58% rename from wallets/ethereum-wallet-mock/src/cypress/initEthereumWalletMock.ts rename to wallets/ethereum-wallet-mock/src/cypress/utils/initEthereumWalletMock.ts index 3eae9cb19..366a33975 100644 --- a/wallets/ethereum-wallet-mock/src/cypress/initEthereumWalletMock.ts +++ b/wallets/ethereum-wallet-mock/src/cypress/utils/initEthereumWalletMock.ts @@ -1,15 +1,11 @@ import { readFileSync } from 'fs' import { type BrowserContext, type Page, chromium } from '@playwright/test' -import { EthereumWalletMock } from '../EthereumWalletMock' -import { SEED_PHRASE, mockEthereum, web3MockPath } from '../utils' -import { MISSING_INIT, NO_CONTEXT, NO_PAGE } from './errors' +import { mockEthereum, web3MockPath } from '../../playwright/utils' +import { NO_CONTEXT, NO_PAGE } from '../constants/errors' let context: BrowserContext | undefined let cypressPage: Page | undefined -let ethereumWalletMock: EthereumWalletMock | undefined - -let ethereumObjectLoaded = false const getCypressPage = async () => { if (!context) { @@ -54,24 +50,4 @@ export async function initEthereumWalletMock(port: number) { await context.addInitScript({ content: `${readFileSync(web3MockPath, 'utf-8')}\n(${mockEthereum.toString()})();` }) - - // As we want to refresh the page after mocking the ethereum object - if (!ethereumObjectLoaded) { - await cypressPage.reload() - ethereumObjectLoaded = true - } - - ethereumWalletMock = new EthereumWalletMock(cypressPage) - await ethereumWalletMock.importWallet(SEED_PHRASE) -} - -export function getEthereumWalletMock() { - if (!context || !cypressPage || !ethereumWalletMock) { - console.error(MISSING_INIT) - return - } - - if (ethereumWalletMock) return ethereumWalletMock - - return new EthereumWalletMock(cypressPage) } diff --git a/wallets/ethereum-wallet-mock/src/cypress/installSynpress.ts b/wallets/ethereum-wallet-mock/src/cypress/utils/installSynpress.ts similarity index 93% rename from wallets/ethereum-wallet-mock/src/cypress/installSynpress.ts rename to wallets/ethereum-wallet-mock/src/cypress/utils/installSynpress.ts index 71c6f884c..95dea8fd4 100644 --- a/wallets/ethereum-wallet-mock/src/cypress/installSynpress.ts +++ b/wallets/ethereum-wallet-mock/src/cypress/utils/installSynpress.ts @@ -1,7 +1,6 @@ import { ensureRdpPort } from '@synthetixio/synpress-core' import { initEthereumWalletMock } from './initEthereumWalletMock' -import setupTasks from './setupTasks' let port: number @@ -21,8 +20,6 @@ export default function installSynpress(on: Cypress.PluginEvents, config: Cypres await initEthereumWalletMock(port) }) - setupTasks(on) - return { ...config, browsers diff --git a/wallets/ethereum-wallet-mock/src/index.ts b/wallets/ethereum-wallet-mock/src/index.ts index 57da40041..24e2d27cc 100644 --- a/wallets/ethereum-wallet-mock/src/index.ts +++ b/wallets/ethereum-wallet-mock/src/index.ts @@ -1,3 +1,3 @@ -export * from './EthereumWalletMock' -export * from './utils' -export * from './fixtures/ethereumWalletMockFixtures' +export { default as EthereumWalletMock } from './playwright/EthereumWalletMock' +export * from './playwright/utils' +export * from './playwright/fixtures/ethereumWalletMockFixtures' diff --git a/wallets/ethereum-wallet-mock/src/EthereumWalletMock.ts b/wallets/ethereum-wallet-mock/src/playwright/EthereumWalletMock.ts similarity index 79% rename from wallets/ethereum-wallet-mock/src/EthereumWalletMock.ts rename to wallets/ethereum-wallet-mock/src/playwright/EthereumWalletMock.ts index 5e518c54c..d0bb3b047 100644 --- a/wallets/ethereum-wallet-mock/src/EthereumWalletMock.ts +++ b/wallets/ethereum-wallet-mock/src/playwright/EthereumWalletMock.ts @@ -1,16 +1,18 @@ import type { Page } from '@playwright/test' import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts' -import type { Network } from './network/Network' -import { ACCOUNT_MOCK, BLOCKCHAIN, OPTIMISM_NETWORK_ID } from './utils' +import { ACCOUNT_MOCK, BLOCKCHAIN } from '../constants' +import { EthereumWalletMockAbstract } from '../type/EthereumWalletMockAbstract' +import type { Network } from '../type/Network' +import type { WalletMock } from '../type/WalletMock' +import { OPTIMISM_NETWORK_ID } from './utils' -export type WalletMock = 'metamask' | 'coinbase' | 'phantom' | 'walletconnect' | 'walletlink' +export default class EthereumWalletMock extends EthereumWalletMockAbstract { + page: Page -export class EthereumWalletMock { - seedPhrase = '' - wallet: WalletMock = 'metamask' - - constructor(readonly page: Page) { + constructor(page: Page, wallet: WalletMock = 'metamask') { + super(wallet) this.page = page + this.wallet = wallet } /** @@ -23,12 +25,21 @@ export class EthereumWalletMock { return this.page.evaluate( ([blockchain, wallet, accounts]) => { + class WalletConnectStub {} + + let connector: WalletConnectStub | undefined + + if (wallet === 'walletconnect') { + connector = WalletConnectStub + } + return Web3Mock.mock({ blockchain, wallet, accounts: { return: accounts - } + }, + connector }) }, [BLOCKCHAIN, this.wallet, [ACCOUNT_MOCK]] @@ -38,7 +49,7 @@ export class EthereumWalletMock { /** * Retrieves the current account address. */ - async getAllAccounts(): Promise { + async getAllAccounts(): Promise<`0x${string}`[] | undefined> { return this.page.evaluate(() => { return window.ethereum.request({ method: 'eth_requestAccounts' }) }) @@ -50,8 +61,8 @@ export class EthereumWalletMock { async addNewAccount() { const accounts = await this.getAllAccounts() - const newAccount = mnemonicToAccount(this.seedPhrase, { - accountIndex: accounts.length + const newAccount = mnemonicToAccount(this.seedPhrase || '', { + accountIndex: accounts?.length }) return this.page.evaluate( @@ -64,7 +75,7 @@ export class EthereumWalletMock { } }) }, - [BLOCKCHAIN, this.wallet, [newAccount.address, ...accounts]] + [BLOCKCHAIN, this.wallet, [newAccount.address, ...(accounts || [])]] ) } @@ -141,8 +152,8 @@ export class EthereumWalletMock { /** * Retrieves the current account address. */ - async getAccountAddress() { - return (await this.getAllAccounts())[0] + async getAccountAddress(): Promise<`0x${string}` | undefined> { + return (await this.getAllAccounts())?.[0] } /** @@ -176,8 +187,9 @@ export class EthereumWalletMock { * * @param wallet - The wallet to connect to the dapp. */ - async connectToDapp(wallet: WalletMock = 'metamask') { + connectToDapp(wallet: WalletMock = 'metamask') { this.wallet = wallet + return this.page.evaluate( ([blockchain, accounts, wallet]) => { // Cannot pass custom class as an argument to `page.evaluate` diff --git a/wallets/ethereum-wallet-mock/src/utils/constants.ts b/wallets/ethereum-wallet-mock/src/playwright/constants.ts similarity index 63% rename from wallets/ethereum-wallet-mock/src/utils/constants.ts rename to wallets/ethereum-wallet-mock/src/playwright/constants.ts index 8ec553d44..cefae7d5d 100644 --- a/wallets/ethereum-wallet-mock/src/utils/constants.ts +++ b/wallets/ethereum-wallet-mock/src/playwright/constants.ts @@ -1,12 +1,6 @@ import { createRequire } from 'node:module' const require = createRequire(import.meta.url) -export const BLOCKCHAIN = 'ethereum' - -export const ACCOUNT_MOCK = '0xd73b04b0e696b0945283defa3eee453814758f1a' - -export const SEED_PHRASE = 'test test test test test test test test test test test junk' - export const PRIVATE_KEY = 'ea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a' export const OPTIMISM_NETWORK_ID = '0xa' diff --git a/wallets/ethereum-wallet-mock/src/fixtures/ethereumWalletMockFixtures.ts b/wallets/ethereum-wallet-mock/src/playwright/fixtures/ethereumWalletMockFixtures.ts similarity index 82% rename from wallets/ethereum-wallet-mock/src/fixtures/ethereumWalletMockFixtures.ts rename to wallets/ethereum-wallet-mock/src/playwright/fixtures/ethereumWalletMockFixtures.ts index bb2b53853..b83fca70b 100644 --- a/wallets/ethereum-wallet-mock/src/fixtures/ethereumWalletMockFixtures.ts +++ b/wallets/ethereum-wallet-mock/src/playwright/fixtures/ethereumWalletMockFixtures.ts @@ -1,10 +1,8 @@ import { readFileSync } from 'fs' import { test as base } from '@playwright/test' -import { EthereumWalletMock } from '../EthereumWalletMock' -import { SEED_PHRASE, mockEthereum, web3MockPath } from '../utils' - -export const ANVIL_CHAIN_ID = 31337 -export const ANVIL_URL_URL = 'http://anvil:5000' +import { SEED_PHRASE } from '../../constants' +import EthereumWalletMock from '../EthereumWalletMock' +import { mockEthereum, web3MockPath } from '../utils' type EthereumWalletMockFixtures = { ethereumWalletMock: EthereumWalletMock diff --git a/wallets/ethereum-wallet-mock/src/playwright/index.ts b/wallets/ethereum-wallet-mock/src/playwright/index.ts new file mode 100644 index 000000000..320f5f6d5 --- /dev/null +++ b/wallets/ethereum-wallet-mock/src/playwright/index.ts @@ -0,0 +1,2 @@ +export { default as EthereumWalletMock } from './EthereumWalletMock' +export { default as synpress } from './synpress' diff --git a/wallets/ethereum-wallet-mock/test/synpress.ts b/wallets/ethereum-wallet-mock/src/playwright/synpress.ts similarity index 69% rename from wallets/ethereum-wallet-mock/test/synpress.ts rename to wallets/ethereum-wallet-mock/src/playwright/synpress.ts index db00d8534..f54d1ce1b 100644 --- a/wallets/ethereum-wallet-mock/test/synpress.ts +++ b/wallets/ethereum-wallet-mock/src/playwright/synpress.ts @@ -1,4 +1,4 @@ import { testWithSynpress } from '@synthetixio/synpress-core' -import { ethereumWalletMockFixtures } from '../src' +import { ethereumWalletMockFixtures } from '../index' export default testWithSynpress(ethereumWalletMockFixtures) diff --git a/wallets/ethereum-wallet-mock/src/utils/index.ts b/wallets/ethereum-wallet-mock/src/playwright/utils/index.ts similarity index 66% rename from wallets/ethereum-wallet-mock/src/utils/index.ts rename to wallets/ethereum-wallet-mock/src/playwright/utils/index.ts index 2813a058c..77d5358b2 100644 --- a/wallets/ethereum-wallet-mock/src/utils/index.ts +++ b/wallets/ethereum-wallet-mock/src/playwright/utils/index.ts @@ -1,2 +1,2 @@ export { default as mockEthereum } from './mockEthereum' -export * from './constants' +export * from '../constants' diff --git a/wallets/ethereum-wallet-mock/src/utils/mockEthereum.ts b/wallets/ethereum-wallet-mock/src/playwright/utils/mockEthereum.ts similarity index 100% rename from wallets/ethereum-wallet-mock/src/utils/mockEthereum.ts rename to wallets/ethereum-wallet-mock/src/playwright/utils/mockEthereum.ts diff --git a/wallets/ethereum-wallet-mock/src/type/EthereumWalletMockAbstract.ts b/wallets/ethereum-wallet-mock/src/type/EthereumWalletMockAbstract.ts new file mode 100644 index 000000000..06c218259 --- /dev/null +++ b/wallets/ethereum-wallet-mock/src/type/EthereumWalletMockAbstract.ts @@ -0,0 +1,68 @@ +import type { Network } from './Network' +import type { WalletMock } from './WalletMock' + +export abstract class EthereumWalletMockAbstract { + seedPhrase: string | undefined + wallet: WalletMock + + protected constructor(wallet: WalletMock = 'metamask') { + this.wallet = wallet + } + + /** + * Imports a wallet using the given seed phrase. + * + * @param seedPhrase - The seed phrase to import. + */ + abstract importWallet(seedPhrase: string): void + + /** + * Retrieves the current account address. + */ + abstract getAllAccounts(): Cypress.Chainable<`0x${string}`[]> | Promise<`0x${string}`[] | undefined> + + /** + * Adds a new account. This account is based on the initially imported seed phrase. + */ + abstract addNewAccount(): void + + /** + * Imports a wallet using the given private key. + * + * @param privateKey - The private key to import. + */ + abstract importWalletFromPrivateKey(privateKey: `0x${string}`): void + + /** + * Switches to the account with the given name. + * + * @param accountAddress - The name of the account to switch to. + */ + abstract switchAccount(accountAddress: string): void + + /** + * Adds a new network. + * + * @param network - The network object to use for adding the new network. + */ + abstract addNetwork(network: Network): void + + /** + * Retrieves the current account address. + */ + abstract getAccountAddress(): Cypress.Chainable<`0x${string}`> | Promise<`0x${string}` | undefined> + + /** + * Switches to the network with the given name. + * + * @param networkName - The name of the network to switch to. + */ + abstract switchNetwork(networkName: string): void + + /** + * Connects wallet to the dapp. + * + * @param wallet - The wallet to connect to the dapp. + */ + abstract connectToDapp(wallet: WalletMock): void +} diff --git a/wallets/ethereum-wallet-mock/src/network/Network.ts b/wallets/ethereum-wallet-mock/src/type/Network.ts similarity index 100% rename from wallets/ethereum-wallet-mock/src/network/Network.ts rename to wallets/ethereum-wallet-mock/src/type/Network.ts diff --git a/wallets/ethereum-wallet-mock/src/type/WalletMock.ts b/wallets/ethereum-wallet-mock/src/type/WalletMock.ts new file mode 100644 index 000000000..e8564f3e7 --- /dev/null +++ b/wallets/ethereum-wallet-mock/src/type/WalletMock.ts @@ -0,0 +1 @@ +export type WalletMock = 'metamask' | 'coinbase' | 'phantom' | 'walletconnect' | 'walletlink' diff --git a/wallets/ethereum-wallet-mock/test/cypress/metamask/addNewAccount.cy.ts b/wallets/ethereum-wallet-mock/test/cypress/metamask/addNewAccount.cy.ts new file mode 100644 index 000000000..b3c9817b5 --- /dev/null +++ b/wallets/ethereum-wallet-mock/test/cypress/metamask/addNewAccount.cy.ts @@ -0,0 +1,19 @@ +it('should add a new accounts', () => { + cy.getAllAccounts().then((accounts) => { + expect(accounts).to.have.length(1) + }) + + cy.addNewAccount().then(() => { + cy.getAllAccounts().then((accounts) => { + expect(accounts).to.have.length(2) + }) + }) + + cy.addNewAccount().then(() => { + cy.addNewAccount().then(() => { + cy.getAllAccounts().then((accounts) => { + expect(accounts).to.have.length(4) + }) + }) + }) +}) diff --git a/wallets/ethereum-wallet-mock/test/cypress/metamask/importWalletFromPrivateKey.cy.ts b/wallets/ethereum-wallet-mock/test/cypress/metamask/importWalletFromPrivateKey.cy.ts new file mode 100644 index 000000000..06c8a3fd5 --- /dev/null +++ b/wallets/ethereum-wallet-mock/test/cypress/metamask/importWalletFromPrivateKey.cy.ts @@ -0,0 +1,7 @@ +it('should import account using private key', () => { + cy.importWalletFromPrivateKey('0xea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a') + + cy.get('#getAccounts').click() + + cy.get('#getAccountsResult').should('have.text', '0xa2ce797cA71d0EaE1be5a7EffD27Fd6C38126801') +}) diff --git a/wallets/ethereum-wallet-mock/test/cypress/metamask/setupMetaMask.cy.ts b/wallets/ethereum-wallet-mock/test/cypress/metamask/setupMetaMask.cy.ts deleted file mode 100644 index 8938da0ae..000000000 --- a/wallets/ethereum-wallet-mock/test/cypress/metamask/setupMetaMask.cy.ts +++ /dev/null @@ -1,7 +0,0 @@ -it('should add new MetaMask account', () => { - cy.getAllAccounts().should('have.length', 1) - - cy.addNewAccount() - - cy.getAllAccounts().should('have.length', 2) -}) diff --git a/wallets/ethereum-wallet-mock/test/cypress/metamask/switchAccount.cy.ts b/wallets/ethereum-wallet-mock/test/cypress/metamask/switchAccount.cy.ts new file mode 100644 index 000000000..e5fe072c8 --- /dev/null +++ b/wallets/ethereum-wallet-mock/test/cypress/metamask/switchAccount.cy.ts @@ -0,0 +1,7 @@ +it('should switch account', () => { + cy.switchAccount('0x4444797cA71d0EaE1be5a7EffD27Fd6C38126801') + + cy.get('#getAccounts').click() + + cy.get('#getAccountsResult').should('have.text', '0x4444797cA71d0EaE1be5a7EffD27Fd6C38126801') +}) diff --git a/wallets/ethereum-wallet-mock/test/cypress/metamask/switchNetwork.cy.ts b/wallets/ethereum-wallet-mock/test/cypress/metamask/switchNetwork.cy.ts new file mode 100644 index 000000000..3dd0ec712 --- /dev/null +++ b/wallets/ethereum-wallet-mock/test/cypress/metamask/switchNetwork.cy.ts @@ -0,0 +1,29 @@ +import { ANVIL_CHAIN_ID, ANVIL_URL_URL } from '../../../src/constants' + +function createAnvilNetwork() { + return { + name: 'Anvil', + rpcUrl: ANVIL_URL_URL, + chainId: ANVIL_CHAIN_ID, + blockExplorerUrl: 'https://etherscan.io/', + nativeCurrency: { + decimals: 18, + name: 'Anvil', + symbol: 'ETH' + } + } +} + +it('should switch network', () => { + const network = createAnvilNetwork() + + cy.addNetwork(network) + + cy.switchNetwork(network.name) + + cy.window().then((cypressWindow) => { + cypressWindow.ethereum.request({ method: 'eth_chainId' }).then((chainId: string) => { + expect(chainId).to.equal('0xa') + }) + }) +}) diff --git a/wallets/ethereum-wallet-mock/test/cypress/mock/mockEthereum.cy.ts b/wallets/ethereum-wallet-mock/test/cypress/mock/mockEthereum.cy.ts new file mode 100644 index 000000000..e8bf9b258 --- /dev/null +++ b/wallets/ethereum-wallet-mock/test/cypress/mock/mockEthereum.cy.ts @@ -0,0 +1,40 @@ +it('should be able to access ethereum API', () => { + cy.window().then((window) => { + expect(!!window.ethereum).to.be.true + }) +}) + +it('should be connected to metamask by default', () => { + cy.window().then((window) => { + expect(window.ethereum.isMetaMask).to.be.true + }) +}) + +it('should connect to ethereum', () => { + cy.window().then((window) => { + window.ethereum + .request({ + method: 'eth_chainId' + }) + .then((currentChainId: string) => { + expect(currentChainId).to.equal('0x1') + }) + }) +}) + +it('should be able to connect to every supported ethereum wallet', () => { + cy.window().then((window) => { + expect(!!window.ethereum.isMetaMask).to.be.true + expect(window.ethereum.isCoinbaseWallet).to.be.undefined + }) + + cy.connectToDapp('coinbase') + cy.window().then((window) => { + expect(!!window.ethereum.isCoinbaseWallet).to.be.true + }) + + cy.connectToDapp('walletconnect') + cy.window().then((window) => { + expect(!!window.ethereum.isWalletLink).to.be.true + }) +}) diff --git a/wallets/ethereum-wallet-mock/test/playwright/addNewAccount.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/addNewAccount.spec.ts deleted file mode 100644 index 9baf68fd4..000000000 --- a/wallets/ethereum-wallet-mock/test/playwright/addNewAccount.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import test from '../synpress' - -const { expect } = test - -test('should add a new account with specified name', async ({ ethereumWalletMock }) => { - // Imported wallet includes 1 account - expect(await ethereumWalletMock.getAllAccounts()).toHaveLength(1) - - await ethereumWalletMock.addNewAccount() - - expect(await ethereumWalletMock.getAllAccounts()).toHaveLength(2) - - await ethereumWalletMock.addNewAccount() - await ethereumWalletMock.addNewAccount() - - expect(await ethereumWalletMock.getAllAccounts()).toHaveLength(4) -}) diff --git a/wallets/ethereum-wallet-mock/test/playwright/importWalletFromPrivateKey.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/importWalletFromPrivateKey.spec.ts deleted file mode 100644 index 345c6b8a6..000000000 --- a/wallets/ethereum-wallet-mock/test/playwright/importWalletFromPrivateKey.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import test from '../synpress' - -const { expect } = test - -test('should import account using private key', async ({ page, ethereumWalletMock }) => { - await ethereumWalletMock.importWalletFromPrivateKey( - '0xea084c575a01e2bbefcca3db101eaeab1d8af15554640a510c73692db24d0a6a' - ) - - await page.locator('#getAccounts').click() - await expect(page.locator('#getAccountsResult')).toHaveText('0xa2ce797cA71d0EaE1be5a7EffD27Fd6C38126801') -}) diff --git a/wallets/ethereum-wallet-mock/test/playwright/metamask/addNewAccount.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/metamask/addNewAccount.spec.ts index dfc402e49..63763b1ac 100644 --- a/wallets/ethereum-wallet-mock/test/playwright/metamask/addNewAccount.spec.ts +++ b/wallets/ethereum-wallet-mock/test/playwright/metamask/addNewAccount.spec.ts @@ -1,4 +1,4 @@ -import test from '../../synpress' +import test from '../../../src/playwright/synpress' const { expect } = test diff --git a/wallets/ethereum-wallet-mock/test/playwright/metamask/importWalletFromPrivateKey.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/metamask/importWalletFromPrivateKey.spec.ts index b9a35e9a2..f0efb6cbd 100644 --- a/wallets/ethereum-wallet-mock/test/playwright/metamask/importWalletFromPrivateKey.spec.ts +++ b/wallets/ethereum-wallet-mock/test/playwright/metamask/importWalletFromPrivateKey.spec.ts @@ -1,4 +1,4 @@ -import test from '../../synpress' +import test from '../../../src/playwright/synpress' const { expect } = test diff --git a/wallets/ethereum-wallet-mock/test/playwright/metamask/mock/mockEthereum.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/metamask/mock/mockEthereum.spec.ts deleted file mode 100644 index 6b8ccf5e8..000000000 --- a/wallets/ethereum-wallet-mock/test/playwright/metamask/mock/mockEthereum.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import synpress from '../../../synpress' - -const test = synpress - -const { expect } = test - -test('should be able to access ethereum API', async ({ page }) => { - const ethereum = await page.evaluate(() => window.ethereum) - expect(ethereum).toBeTruthy() -}) - -test('should be connected to metamask by default', async ({ page }) => { - const ethereum = await page.evaluate(() => window.ethereum) - expect(ethereum.isMetaMask).toBe(true) -}) - -test('should connect to ethereum', async ({ page }) => { - const currentChainId = await page.evaluate(() => - window.ethereum.request({ - method: 'eth_chainId' - }) - ) - - expect(currentChainId).toEqual('0x1') -}) - -test('should be able to connect to every supported ethereum wallet', async ({ page, ethereumWalletMock }) => { - // Metamask - let ethereum = await page.evaluate(() => window.ethereum) - expect(ethereum.isMetaMask).toBe(true) - expect(ethereum.isCoinbaseWallet).toBe(undefined) - - // Coinbase wallet - await ethereumWalletMock.connectToDapp('coinbase') - ethereum = await page.evaluate(() => window.ethereum) - expect(ethereum.isCoinbaseWallet).toBe(true) - - // Walletconnect - await ethereumWalletMock.connectToDapp('walletconnect') - ethereum = await page.evaluate(() => window.ethereum) - expect(ethereum.isWalletLink).toBe(true) -}) diff --git a/wallets/ethereum-wallet-mock/test/playwright/metamask/switchAccount.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/metamask/switchAccount.spec.ts index e2cbfebc5..75fba0087 100644 --- a/wallets/ethereum-wallet-mock/test/playwright/metamask/switchAccount.spec.ts +++ b/wallets/ethereum-wallet-mock/test/playwright/metamask/switchAccount.spec.ts @@ -1,4 +1,4 @@ -import test from '../../synpress' +import test from '../../../src/playwright/synpress' const { expect } = test diff --git a/wallets/ethereum-wallet-mock/test/playwright/metamask/switchNetwork.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/metamask/switchNetwork.spec.ts index baf2b240b..9ea22c47b 100644 --- a/wallets/ethereum-wallet-mock/test/playwright/metamask/switchNetwork.spec.ts +++ b/wallets/ethereum-wallet-mock/test/playwright/metamask/switchNetwork.spec.ts @@ -1,5 +1,5 @@ -import { ANVIL_CHAIN_ID, ANVIL_URL_URL } from '../../../src' -import test from '../../synpress' +import { ANVIL_CHAIN_ID, ANVIL_URL_URL } from '../../../src/constants' +import test from '../../../src/playwright/synpress' const { expect } = test diff --git a/wallets/ethereum-wallet-mock/test/playwright/mock/mockEthereum.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/mock/mockEthereum.spec.ts index 788492ad3..d9a1deed6 100644 --- a/wallets/ethereum-wallet-mock/test/playwright/mock/mockEthereum.spec.ts +++ b/wallets/ethereum-wallet-mock/test/playwright/mock/mockEthereum.spec.ts @@ -1,4 +1,4 @@ -import test from '../../synpress' +import test from '../../../src/playwright/synpress' const { expect } = test diff --git a/wallets/ethereum-wallet-mock/test/playwright/switchAccount.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/switchAccount.spec.ts deleted file mode 100644 index d24721557..000000000 --- a/wallets/ethereum-wallet-mock/test/playwright/switchAccount.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import test from '../synpress' - -const { expect } = test - -test('should switch account', async ({ page, ethereumWalletMock }) => { - await ethereumWalletMock.switchAccount('0x4444797cA71d0EaE1be5a7EffD27Fd6C38126801') - - await page.locator('#getAccounts').click() - await expect(page.locator('#getAccountsResult')).toHaveText('0x4444797cA71d0EaE1be5a7EffD27Fd6C38126801') -}) diff --git a/wallets/ethereum-wallet-mock/test/playwright/switchNetwork.spec.ts b/wallets/ethereum-wallet-mock/test/playwright/switchNetwork.spec.ts deleted file mode 100644 index 039fe2aef..000000000 --- a/wallets/ethereum-wallet-mock/test/playwright/switchNetwork.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ANVIL_CHAIN_ID, ANVIL_URL_URL } from '../../src' - -import test from '../synpress' - -const { expect } = test - -function createAnvilNetwork() { - return { - name: 'Anvil', - rpcUrl: ANVIL_URL_URL, - chainId: ANVIL_CHAIN_ID, - blockExplorerUrl: 'https://etherscan.io/', - nativeCurrency: { - decimals: 18, - name: 'Anvil', - symbol: 'ETH' - } - } -} - -test('should switch network', async ({ ethereumWalletMock, page }) => { - const network = createAnvilNetwork() - - await ethereumWalletMock.addNetwork(network) - - await ethereumWalletMock.switchNetwork(network.name) - - const chainId = await page.evaluate(async () => { - return await window.ethereum.request({ method: 'eth_chainId' }) - }) - - // Mocked Optimism chain id due to https://github.com/DePayFi/web3-mock/issues/33 - expect(chainId).toBe('0xa') -})