Skip to content

Commit

Permalink
✨ feat: Optional caching for tests and enable tests for windows (#1155)
Browse files Browse the repository at this point in the history
* optional tagging setuo

* add cache check in fixture

* do prep for cacheless download

* do prep for cacheless download

* cacheless fixture testing

* gitignore

* load extension zip correctly

* done

* cacheless import working

* make compatible with windows

* clean up, cache/cli message

* linting

* usecache setup

* cleaning up expanding fixture flow for cacheless setup

* fix an issue with async unzip

* clean up and solve most other text

* skip broken tests

* resolve sec issue, clean up

* merge

* merge

* useCache

* imports

* fix issues for review

* review changes

* lint

* lint

* irg

* console r
  • Loading branch information
Seroxdesign authored Jul 9, 2024
1 parent 02d451e commit af4405a
Show file tree
Hide file tree
Showing 23 changed files with 301 additions and 65 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ playwright/.cache

**/ethereum-wallet-mock/cypress
**/metamask/cypress

### Cacheless

**/metamask/downloads
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"**/test-results",
"**/playwright-report",
"**/.cache-synpress",
"**/.vitepress/cache"
"**/.vitepress/cache",
"**/downloads"
]
},
"formatter": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cache/src/defineWalletSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getWalletSetupFuncHash } from './utils/getWalletSetupFuncHash'
// TODO: Should we export this type in the `release` package?
export type WalletSetupFunction = (context: BrowserContext, walletPage: Page) => Promise<void>

// TODO: This runs at least twice. Should we cache it somehow?
// This runs once for each setup file on building cache and setting up fixtures, then it runs once for each worker on e2e:test. Should we cache it somehow?
/**
* This function is used to define how a wallet should be set up.
* Based on the contents of this function, a browser with the wallet extension is set up and cached so that it can be used by the tests later.
Expand Down
45 changes: 45 additions & 0 deletions pnpm-lock.yaml

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

1 change: 1 addition & 0 deletions wallets/metamask/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ declare global {
interface ProcessEnv {
CI: boolean
HEADLESS: boolean
USE_CACHE: string
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions wallets/metamask/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,20 @@
"@synthetixio/synpress-cache": "workspace:*",
"@synthetixio/synpress-core": "workspace:*",
"@viem/anvil": "0.0.7",
"app-root-path": "3.1.0",
"axios": "1.6.7",
"dotenv": "16.4.2",
"find-config": "1.0.0",
"fs-extra": "11.2.0",
"unzipper": "0.10.14",
"zod": "3.22.4"
},
"devDependencies": {
"@synthetixio/synpress-tsconfig": "0.0.1-alpha.7",
"@types/find-config": "1.0.4",
"@types/fs-extra": "11.0.4",
"@types/node": "20.11.17",
"@types/unzipper": "0.10.9",
"@vitest/coverage-v8": "1.2.2",
"rimraf": "5.0.5",
"tsup": "8.0.2",
Expand Down
14 changes: 14 additions & 0 deletions wallets/metamask/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import dotenv from 'dotenv'
import findConfig from 'find-config'

export const loadEnv = () => {
const envFiles = ['.env', '.env.e2e', '.env.local', '.env.dev']
envFiles.find((envFile) => {
const config = findConfig(envFile)
if (config) {
dotenv.config({ path: config })
return true
}
return false
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Page } from '@playwright/test'
import { MetaMask } from '..'
import { retryIfMetaMaskCrashAfterUnlock } from '..'
import { closePopover } from '../pages/HomePage/actions'

export async function importAndConnectForFixtures(
page: Page,
seedPhrase: string,
password: string,
extensionId: string
) {
const metamask = new MetaMask(page.context(), page, password, extensionId)

await metamask.importWallet(seedPhrase)

await metamask.openSettings()

const SidebarMenus = metamask.homePage.selectors.settings.SettingsSidebarMenus

await metamask.openSidebarMenu(SidebarMenus.Advanced)

await metamask.toggleDismissSecretRecoveryPhraseReminder()

await page.goto(`chrome-extension://${extensionId}/home.html`)

await retryIfMetaMaskCrashAfterUnlock(page)

await closePopover(page)

const newPage = await page.context().newPage()

await newPage.goto('http://localhost:9999')

await newPage.locator('#connectButton').click()

await metamask.connectToDapp()

await newPage.close()
}
89 changes: 89 additions & 0 deletions wallets/metamask/src/fixture-actions/noCacheMetaMaskSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import path from 'node:path'
import { type BrowserContext, chromium } from '@playwright/test'
import appRoot from 'app-root-path'
import axios from 'axios'
import fs from 'fs-extra'
import unzipper from 'unzipper'
import { DEFAULT_METAMASK_VERSION, EXTENSION_DOWNLOAD_URL } from '../utils/constants'

async function prepareDownloadDirectory(version: string = DEFAULT_METAMASK_VERSION): Promise<string> {
const downloadsDirectory =
process.platform === 'win32' ? appRoot.resolve('/node_modules') : path.join(process.cwd(), 'downloads')
await fs.ensureDir(downloadsDirectory)

const metamaskDirectory = path.join(downloadsDirectory, `metamask-chrome-${version}.zip`)
const archiveFileExtension = path.extname(metamaskDirectory)
const outputPath = metamaskDirectory.replace(archiveFileExtension, '')
const metamaskManifestPath = path.join(outputPath, 'manifest.json')

if (!fs.existsSync(metamaskManifestPath)) {
await downloadAndExtract(EXTENSION_DOWNLOAD_URL, metamaskDirectory)
}

return outputPath
}

async function downloadAndExtract(url: string, destination: string): Promise<void> {
const response = await axios.get(url, { responseType: 'stream' })
const writer = fs.createWriteStream(destination)
response.data.pipe(writer)
await new Promise((resolve) => writer.on('finish', resolve))

await unzipArchive(destination)
}

async function unzipArchive(archivePath: string): Promise<void> {
const archiveFileExtension = path.extname(archivePath)
const outputPath = archivePath.replace(archiveFileExtension, '')

await fs.ensureDir(outputPath)

try {
await new Promise<void>((resolve, reject) => {
const stream = fs.createReadStream(archivePath).pipe(unzipper.Parse())

stream.on(
'entry',
async (entry: { path: string; type: string; pipe: (arg: unknown) => void; autodrain: () => void }) => {
const fileName = entry.path
const type = entry.type as 'Directory' | 'File'

if (type === 'Directory') {
await fs.mkdir(path.join(outputPath, fileName), { recursive: true })
entry.autodrain()
return
}

if (type === 'File') {
const writeStream = fs.createWriteStream(path.join(outputPath, fileName))
entry.pipe(writeStream)

await new Promise<void>((res, rej) => {
writeStream.on('finish', res)
writeStream.on('error', rej)
})
}
}
)
stream.on('finish', resolve)
stream.on('error', reject)
})
} catch (error: unknown) {
console.error(`[unzipArchive] Error unzipping archive: ${(error as { message: string }).message}`)
throw error
}
}

export async function cachelessSetupMetaMask(metamaskVersion?: string): Promise<BrowserContext> {
const metamaskPath = await prepareDownloadDirectory(metamaskVersion || DEFAULT_METAMASK_VERSION)
const browserArgs = [`--load-extension=${metamaskPath}`, `--disable-extensions-except=${metamaskPath}`]

if (process.env.HEADLESS) {
browserArgs.push('--headless=new')
}
const context = await chromium.launchPersistentContext('', {
headless: false,
args: browserArgs
})
return context
}
4 changes: 1 addition & 3 deletions wallets/metamask/src/fixture-actions/prepareExtension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { downloadFile, ensureCacheDirExists, unzipArchive } from '@synthetixio/synpress-cache'

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`
import { DEFAULT_METAMASK_VERSION, EXTENSION_DOWNLOAD_URL } from '../utils/constants'

export async function prepareExtension() {
const cacheDirPath = ensureCacheDirExists()
Expand Down
2 changes: 1 addition & 1 deletion wallets/metamask/src/fixture-actions/unlockForFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async function unlockWalletButReloadIfSpinnerDoesNotVanish(metamask: MetaMask) {
}
}

async function retryIfMetaMaskCrashAfterUnlock(page: Page) {
export async function retryIfMetaMaskCrashAfterUnlock(page: Page) {
const homePageLogoLocator = page.locator(HomePage.selectors.logo)

const isHomePageLogoVisible = await homePageLogoLocator.isVisible()
Expand Down
Loading

0 comments on commit af4405a

Please sign in to comment.