Skip to content

Commit

Permalink
feat: MetaMask setup in Cypress part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
matstyler committed Jul 4, 2024
1 parent c13f03b commit 953c425
Show file tree
Hide file tree
Showing 132 changed files with 542 additions and 194 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ test-results
playwright-report
playwright/.cache

### Cypress
cypress/downloads

### Synpress

.cache-synpress
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions wallets/metamask/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'cypress'
import installSynpress from './src/cypress/installSynpress'

export default defineConfig({
chromeWebSecurity: false,
e2e: {
baseUrl: 'http://localhost:9999',
specPattern: 'test/cypress/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'src/cypress/support/e2e.{js,jsx,ts,tsx}',
testIsolation: false,
async setupNodeEvents(on, config) {
return installSynpress(on, config)
}
}
})
8 changes: 5 additions & 3 deletions wallets/metamask/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
],
"scripts": {
"build": "pnpm run clean && pnpm run build:dist && pnpm run build:types",
"build:cache": "synpress-cache test/wallet-setup",
"build:cache:headless": "synpress-cache test/wallet-setup --headless",
"build:cache:headless:force": "synpress-cache test/wallet-setup --headless --force",
"build:cache": "synpress-cache test/playwright/wallet-setup",
"build:cache:headless": "synpress-cache test/playwright/wallet-setup --headless",
"build:cache:headless:force": "synpress-cache test/playwright/wallet-setup --headless --force",
"build:dist": "tsup --tsconfig tsconfig.build.json",
"build:types": "tsc --emitDeclarationOnly --project tsconfig.build.json",
"clean": "rimraf dist types",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:e2e:headful": "playwright test",
"test:e2e:headful:cypress": "cypress run --browser chrome --headed",
"test:e2e:headless": "HEADLESS=true playwright test",
"test:e2e:headless:ui": "HEADLESS=true playwright test --ui",
"test:watch": "vitest watch",
Expand All @@ -41,6 +42,7 @@
"@types/fs-extra": "11.0.4",
"@types/node": "20.11.17",
"@vitest/coverage-v8": "1.2.2",
"cypress": "13.9.0",
"rimraf": "5.0.5",
"tsup": "8.0.2",
"typescript": "5.3.3",
Expand Down
9 changes: 9 additions & 0 deletions wallets/metamask/src/cypress/MetaMask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MetaMaskAbstract } from '../type/MetaMaskAbstract'
import Selectors from '../playwright/pages/LockPage/selectors'

export class MetaMask extends MetaMaskAbstract {
async unlock() {
cy.get(Selectors.passwordInput).type(this.password)
cy.get(Selectors.submitButton).click()
}
}
3 changes: 3 additions & 0 deletions wallets/metamask/src/cypress/constants/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const NO_CONTEXT = 'No browser context found. Connect Playwright first - connectPlaywright()'
export const NO_PAGE = 'No page found. Use getPage()'
export const MISSING_INIT = 'EthereumWalletMock not initialized. Use initEthereumWalletMock()'
1 change: 1 addition & 0 deletions wallets/metamask/src/cypress/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MetaMask'
26 changes: 26 additions & 0 deletions wallets/metamask/src/cypress/installSynpress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ensureRdpPort } from '@synthetixio/synpress-core'
import { initMetaMask } from './support/initMetaMask'

let port: number

export default function installSynpress(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
const browsers = config.browsers.filter((b) => b.name === 'chrome')
if (browsers.length === 0) {
throw new Error('No Chrome browser found in the configuration')
}

on('before:browser:launch', async (_, launchOptions) => {
// Enable debug mode to establish playwright connection
const args = Array.isArray(launchOptions) ? launchOptions : launchOptions.args
port = ensureRdpPort(args)

args.push(...(await initMetaMask(port)))

return launchOptions
})

return {
...config,
browsers
}
}
26 changes: 26 additions & 0 deletions wallets/metamask/src/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// import mockEthereum from './mockEthereum'
// Import commands.js using ES2015 syntax:
// import synpressCommands from './synpressCommands'

// synpressCommands()
// mockEthereum()
//
// Cypress.on('uncaught:exception', () => {
// // failing the test
// return false
// })
64 changes: 64 additions & 0 deletions wallets/metamask/src/cypress/support/initMetaMask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { type BrowserContext, type Page, chromium } from '@playwright/test'

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import chromium.

import { NO_CONTEXT, NO_PAGE } from '../constants/errors'

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import NO_PAGE.
// import { getExtensionId, unlockForFixture } from '../../playwright';
import { prepareExtension } from '../../extensionSetup/prepareExtension';
import { unlockForCypress } from './unlockForCypress';
import basicSetup from '../../../test/playwright/wallet-setup/basic.setup';

let context: BrowserContext | undefined
let cypressPage: Page | undefined

const getCypressPage = async () => {

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable getCypressPage.
if (!context) {

Check warning

Code scanning / CodeQL

Useless conditional Warning

This negation always evaluates to true.
console.error(NO_CONTEXT)
return
}

cypressPage = context.pages()[0]

return cypressPage
}
export async function initMetaMask(port: number) {
// await connectPlaywrightToChrome(port)

// if (!context) {
// console.error(NO_CONTEXT)
// return []
// }
//
// await getCypressPage()
//
// if (!cypressPage) {
// console.error(NO_PAGE)
// return []
// }

const metamaskPath = await prepareExtension(false)

const browserArgs = [
`--disable-extensions-except=${metamaskPath}`,
`--load-extension=${metamaskPath}`
]


if (process.env.HEADLESS) {
browserArgs.push('--headless=new')
}

// await chromium.launchPersistentContext('', {
// headless: false,
// args: browserArgs
// })

// const extensionId = await getExtensionId(context, 'MetaMask')
// cypressPage = context.pages()[0] as Page
//
// await cypressPage.goto(`chrome-extension://${extensionId}/home.html`)
//
await unlockForCypress(basicSetup.walletPassword)

return browserArgs
//
// await context.close()
}
77 changes: 77 additions & 0 deletions wallets/metamask/src/cypress/support/synpressCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************

// import type { Network } from '../../type/Network'
// import type { WalletMock } from '../../type/WalletMock'
declare global {
namespace Cypress {
interface Chainable {

// importWallet(seedPhrase: string): Chainable<void>
// importWalletFromPrivateKey(privateKey: `0x${string}`): Chainable<void>
// addNewAccount(): Chainable<void>
// getAllAccounts(): Chainable<Array<`0x${string}`>>
// getAccountAddress(): Chainable<`0x${string}`>
// switchAccount(accountAddress: string): Chainable<void>
// addNetwork(network: Network): Chainable<void>
// switchNetwork(networkName: string): Chainable<void>
// connectToDapp(wallet?: WalletMock): Chainable<void>
}
}
}

export default function synpressCommands() {
// 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)
// })
}
15 changes: 15 additions & 0 deletions wallets/metamask/src/cypress/support/unlockForCypress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MetaMask } from '../MetaMask'
// import { closePopover, closeRecoveryPhraseReminder } from '../../playwright/pages/HomePage/actions';

export async function unlockForCypress(password: string) {
const metamask = new MetaMask(password)

await metamask.unlock()

// await unlockWalletButReloadIfSpinnerDoesNotVanish(metamask)
//
// await retryIfMetaMaskCrashAfterUnlock(page)
//
// await closePopover(page)
// await closeRecoveryPhraseReminder(page)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import path from 'node:path'
import { downloadFile, ensureCacheDirExists, unzipArchive } from '@synthetixio/synpress-cache'
import fs from 'fs-extra'

export const DEFAULT_METAMASK_VERSION = '11.9.1'
export const EXTENSION_DOWNLOAD_URL = `https://github.com/MetaMask/metamask-extension/releases/download/v${DEFAULT_METAMASK_VERSION}/metamask-chrome-${DEFAULT_METAMASK_VERSION}.zip`

export async function prepareExtension() {
const cacheDirPath = ensureCacheDirExists()
export async function prepareExtension(forceCache = true) {
let outputDir = ''
if (forceCache) {
outputDir = ensureCacheDirExists()
} else {
outputDir = path.resolve("./", 'downloads')

if (!(await fs.exists(outputDir))) {
fs.mkdirSync(outputDir)
}
}

const downloadResult = await downloadFile({
url: EXTENSION_DOWNLOAD_URL,
outputDir: cacheDirPath,
outputDir,
fileName: `metamask-chrome-${DEFAULT_METAMASK_VERSION}.zip`
})

Expand Down
Loading

0 comments on commit 953c425

Please sign in to comment.