diff --git a/wallets/keplr/src/fixtureActions/unlockForFixture.ts b/wallets/keplr/src/fixtureActions/unlockForFixture.ts new file mode 100644 index 000000000..1ee43aa3b --- /dev/null +++ b/wallets/keplr/src/fixtureActions/unlockForFixture.ts @@ -0,0 +1,77 @@ +import type { Page } from '@playwright/test' +import { errors as playwrightErrors } from '@playwright/test' +import { KeplrWallet } from '..' +import { CrashPage, HomePage } from '../pages' +import { closePopover, closeRecoveryPhraseReminder } from '../pages/HomePage/actions' +import { waitForSpinnerToVanish } from '../utils/waitForSpinnerToVanish' + +/** + * A more advanced version of the `MetaMask.unlock()` function that incorporates various workarounds for MetaMask issues, among other things. + * This function should be used instead of the `MetaMask.unlock()` when passing it to the `testWithSynpress` function. + * + * @param page - The MetaMask tab page. + * @param password - The password of the MetaMask wallet. + */ +export async function unlockForFixture(page: Page, password: string) { + const metamask = new MetaMask(page.context(), page, password) + + await unlockWalletButReloadIfSpinnerDoesNotVanish(metamask) + + await retryIfMetaMaskCrashAfterUnlock(page) + + await closePopover(page) + await closeRecoveryPhraseReminder(page) +} + +async function unlockWalletButReloadIfSpinnerDoesNotVanish(metamask: MetaMask) { + try { + await metamask.unlock() + } catch (e) { + if (e instanceof playwrightErrors.TimeoutError) { + console.warn('[UnlockWalletButReloadIfSpinnerDoesNotVanish] Unlocking MetaMask timed out. Reloading page...') + + const page = metamask.page + + await page.reload() + await waitForSpinnerToVanish(page) + } else { + throw e + } + } +} + +async function retryIfMetaMaskCrashAfterUnlock(page: Page) { + const homePageLogoLocator = page.locator(HomePage.selectors.logo) + + const isHomePageLogoVisible = await homePageLogoLocator.isVisible() + const isPopoverVisible = await page.locator(HomePage.selectors.popover.closeButton).isVisible() + + if (!isHomePageLogoVisible && !isPopoverVisible) { + if (await page.locator(CrashPage.selectors.header).isVisible()) { + const errors = await page.locator(CrashPage.selectors.errors).allTextContents() + + console.warn(['[RetryIfMetaMaskCrashAfterUnlock] MetaMask crashed due to:', ...errors].join('\n')) + + console.log('[RetryIfMetaMaskCrashAfterUnlock] Reloading page...') + await page.reload() + + try { + await homePageLogoLocator.waitFor({ + state: 'visible', + timeout: 10_000 // TODO: Extract & Make this timeout configurable. + }) + console.log('[RetryIfMetaMaskCrashAfterUnlock] Successfully restored MetaMask!') + } catch (e) { + if (e instanceof playwrightErrors.TimeoutError) { + throw new Error( + ['[RetryIfMetaMaskCrashAfterUnlock] Reload did not help. Throwing with the crash cause:', ...errors].join( + '\n' + ) + ) + } + + throw e + } + } + } +} diff --git a/wallets/keplr/src/fixtures/keplrFixtures.ts b/wallets/keplr/src/fixtures/keplrFixtures.ts index d381a0122..b479ffae6 100644 --- a/wallets/keplr/src/fixtures/keplrFixtures.ts +++ b/wallets/keplr/src/fixtures/keplrFixtures.ts @@ -3,7 +3,7 @@ import { type Page, chromium } from '@playwright/test' import { test as base } from '@playwright/test' import { KeplrWallet } from '../KeplrWallet' -import { SEED_PHRASE } from '../utils' +import { PASSWORD, SEED_PHRASE } from '../utils' import { CACHE_DIR_NAME, createTempContextDir, @@ -13,9 +13,75 @@ import { import { type Anvil, type CreateAnvilOptions, createPool } from '@viem/anvil' import fs from 'fs-extra' import { persistLocalStorage } from '../fixtureActions/persistLocalStorage' +import { prepareExtension, getExtensionId } from '../fixtureActions' -export const keplrFixtures = () => { - return base.extend<{ keplrWallet: KeplrWallet }>({ +type KeplrFixtures = { + _contextPath: string + keplr: KeplrWallet + extensionId: string + keplrPage: Page + createAnvilNode: (options?: CreateAnvilOptions) => Promise<{ anvil: Anvil; rpcUrl: string; chainId: number }> + connectToAnvil: () => Promise +} + +let _keplrPage: Page + +export const keplrFixtures = (walletSetup: ReturnType, slowMo = 0) => { + return base.extend({ + _contextPath: async ({ browserName }, use, testInfo) => { + const contextDir = await createTempContextDir(browserName, testInfo.testId) + await use(contextDir) + await removeTempContextDir(contextDir) + }, + context: async ({ context: currentContext, _contextPath }, use) => { + const cacheDirPath = path.join(process.cwd(), CACHE_DIR_NAME, walletSetup.hash) + if (!(await fs.exists(cacheDirPath))) { + throw new Error(`Cache for ${walletSetup.hash} does not exist. Create it first!`) + } + + // Copying the cache to the temporary context directory. + await fs.copy(cacheDirPath, _contextPath) + + const keplrPath = await prepareExtension() + // We don't need the `--load-extension` arg since the extension is already loaded in the cache. + const browserArgs = [`--disable-extensions-except=${keplrPath}`] + + if (process.env.HEADLESS) { + browserArgs.push('--headless=new') + + if (slowMo) { + console.warn('[WARNING] Slow motion makes no sense in headless mode. It will be ignored!') + } + } + + const context = await chromium.launchPersistentContext(_contextPath, { + headless: false, + args: browserArgs, + slowMo: process.env.HEADLESS ? 0 : slowMo + }) + + + const { cookies, origins } = await currentContext.storageState() + + if (cookies) { + await context.addCookies(cookies) + } + if (origins && origins.length > 0) { + await persistLocalStorage(origins, context) + } + + const extensionId = await getExtensionId(context, 'keplr') + + _keplrPage = await context.newPage()[0] as Page + + await _keplrPage.goto('chrome-extension://' + extensionId + '/popup.html') + + await unlockForFixture(_keplrPage, PASSWORD) + + await use(context) + + await context.close() + }, page: async ({ context }, use) => { const page = await context.newPage() @@ -24,7 +90,7 @@ export const keplrFixtures = () => { await use(page) }, keplrWallet: async ({ page }, use) => { - const keplrWallet = new KeplrWallet(page) + const keplrWallet = new KeplrWallet(page, context, extensionId, PASSWORD) await keplrWallet.importWallet(SEED_PHRASE, 'password') await use(keplrWallet) }, diff --git a/wallets/keplr/test/synpress.ts b/wallets/keplr/test/synpress.ts index 76b171ebd..ff4389063 100644 --- a/wallets/keplr/test/synpress.ts +++ b/wallets/keplr/test/synpress.ts @@ -1,4 +1,5 @@ import { testWithSynpress } from "@synthetixio/synpress-core"; import { keplrFixtures } from "../src"; +import connectedSetup from "./wallet-setup/connected.setup"; -export default testWithSynpress(keplrFixtures) +export default testWithSynpress(keplrFixtures(connectedSetup))