diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b670c45b2..422edb3e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,6 +46,10 @@ jobs: - name: Build project run: pnpm run build + - name: Build cache + run: | + xvfb-run pnpm run build:cache + - name: Run E2E tests (headful) run: | xvfb-run pnpm run test:e2e:headful diff --git a/package.json b/package.json index fb1c2385c..7a68fd060 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "private": true, "scripts": { "build": "turbo build", + "build:cache": "turbo build:cache", "format": "biome format . --write", "format:check": "biome format . --error-on-warnings", "preinstall": "npx only-allow pnpm", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61fe5d34f..2d1208a4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: core: specifier: workspace:* version: link:../../packages/core + fixtures: + specifier: workspace:* + version: link:../../packages/fixtures zod: specifier: ^3.22.4 version: 3.22.4 diff --git a/turbo.json b/turbo.json index 43c2f4c0d..9c81da875 100644 --- a/turbo.json +++ b/turbo.json @@ -8,11 +8,11 @@ "test": { "dependsOn": ["build"] }, - "test:e2e:headless": { + "build:cache": { "dependsOn": ["build"] }, "test:e2e:headful": { - "dependsOn": ["build"] + "dependsOn": ["build", "build:cache"] } } } diff --git a/wallets/metamask/package.json b/wallets/metamask/package.json index 1807a72b3..4a6b38e2e 100644 --- a/wallets/metamask/package.json +++ b/wallets/metamask/package.json @@ -18,6 +18,9 @@ ], "scripts": { "build": "pnpm run clean && pnpm run build:dist && pnpm run build:types", + "build:cache": "core test/e2e/wallet-setup", + "build:cache:headless": "core test/e2e/wallet-setup --headless", + "build:cache:headless:force": "core test/e2e/wallet-setup --headless --force", "build:dist": "tsup --tsconfig tsconfig.build.json", "build:types": "tsc --emitDeclarationOnly --project tsconfig.build.json", "clean": "rimraf dist types", @@ -30,6 +33,7 @@ }, "dependencies": { "core": "workspace:*", + "fixtures": "workspace:*", "zod": "^3.22.4" }, "devDependencies": { diff --git a/wallets/metamask/playwright.config.ts b/wallets/metamask/playwright.config.ts index c12d17cba..0ee01292d 100644 --- a/wallets/metamask/playwright.config.ts +++ b/wallets/metamask/playwright.config.ts @@ -8,13 +8,12 @@ export default defineConfig({ testDir: './test/e2e', // Run all tests in parallel. - // TODO: Enable later once we have more tests. - fullyParallel: false, + fullyParallel: true, // Fail the build on CI if you accidentally left test.only in the source code. forbidOnly: !!process.env.CI, - // Opt out of parallel tests on CI. + // Opt out of parallel tests on CI since it supports only 1 worker. workers: process.env.CI ? 1 : undefined, // Concise 'dot' for CI, default 'html' when running locally. diff --git a/wallets/metamask/test/e2e/connectToDapp.spec.ts b/wallets/metamask/test/e2e/connectToDapp.spec.ts new file mode 100644 index 000000000..1f62492cf --- /dev/null +++ b/wallets/metamask/test/e2e/connectToDapp.spec.ts @@ -0,0 +1,22 @@ +import { testWithSynpress } from 'fixtures' +import { connectToDapp, getExtensionId, unlockForFixture } from '../../src' + +import basicSetup from './wallet-setup/basic.setup' + +const test = testWithSynpress(basicSetup, unlockForFixture) + +const { describe, expect } = test + +describe('connectToDapp', () => { + test('should connect wallet to dapp', async ({ context, page }) => { + const extensionId = await getExtensionId(context, 'MetaMask') + + await page.goto('https://metamask.github.io/test-dapp/') + + await page.locator('#connectButton').click() + + await connectToDapp(context, extensionId) + + await expect(page.locator('#accounts')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') + }) +}) diff --git a/wallets/metamask/test/e2e/getExtensionId.spec.ts b/wallets/metamask/test/e2e/getExtensionId.spec.ts new file mode 100644 index 000000000..0277aad47 --- /dev/null +++ b/wallets/metamask/test/e2e/getExtensionId.spec.ts @@ -0,0 +1,15 @@ +import { testWithSynpress } from 'fixtures' +import { getExtensionId, unlockForFixture } from '../../src' + +import basicSetup from './wallet-setup/basic.setup' + +const test = testWithSynpress(basicSetup, unlockForFixture) + +const { describe, expect } = test + +describe('getExtensionId', () => { + test('should return the extension id', async ({ context }) => { + const extensionId = await getExtensionId(context, 'MetaMask') + expect(extensionId).toMatch(/^[a-z]{32}$/) + }) +}) diff --git a/wallets/metamask/test/e2e/importWallet.spec.ts b/wallets/metamask/test/e2e/importWallet.spec.ts new file mode 100644 index 000000000..f2b141f85 --- /dev/null +++ b/wallets/metamask/test/e2e/importWallet.spec.ts @@ -0,0 +1,62 @@ +import { type BrowserContext, type Page, chromium, test as base } from '@playwright/test' +import { OnboardingPage, prepareExtension } from '../../src' + +const SEED_PHRASE = 'test test test test test test test test test test test junk' +const PASSWORD = 'Tester@1234' + +let sharedContext: BrowserContext | undefined + +const test = base.extend<{ + metamaskPage: Page +}>({ + context: async ({ context: _ }, use) => { + if (sharedContext) { + await use(sharedContext) + + return + } + + const metamaskPath = await prepareExtension() + + // biome-ignore format: the array should not be formatted + const browserArgs = [ + `--disable-extensions-except=${metamaskPath}`, + `--load-extension=${metamaskPath}` + ] + + if (process.env.HEADLESS) { + browserArgs.push('--headless=new') + } + + const context = await chromium.launchPersistentContext('', { + headless: false, + args: browserArgs + }) + + try { + await context.waitForEvent('page', { timeout: 5000 }) + } catch { + throw new Error('[FIXTURE] MetaMask extension did not load in time') + } + + sharedContext = context + await use(context) + }, + metamaskPage: async ({ context }, use) => { + const metamaskOnboardingPage = context.pages()[1] as Page + await use(metamaskOnboardingPage) + } +}) + +const { describe, expect } = test + +describe('importWallet', () => { + test('should go through the onboarding flow and import wallet from seed phrase', async ({ metamaskPage }) => { + const onboardingPage = new OnboardingPage(metamaskPage) + + await onboardingPage.importWallet(SEED_PHRASE, PASSWORD) + + await expect(metamaskPage.getByText('Account 1')).toBeVisible() + await expect(metamaskPage.getByText('0xf39...2266')).toBeVisible() + }) +}) diff --git a/wallets/metamask/test/e2e/lock.spec.ts b/wallets/metamask/test/e2e/lock.spec.ts new file mode 100644 index 000000000..8304caacb --- /dev/null +++ b/wallets/metamask/test/e2e/lock.spec.ts @@ -0,0 +1,16 @@ +import { testWithSynpress } from 'fixtures' +import { UnlockPageSelectors, lock, unlockForFixture } from '../../src' + +import basicSetup from './wallet-setup/basic.setup' + +const test = testWithSynpress(basicSetup, unlockForFixture) + +const { describe, expect } = test + +describe('lock', () => { + test('should lock the wallet', async ({ metamaskPage }) => { + await lock(metamaskPage) + + await expect(metamaskPage.locator(UnlockPageSelectors.submitButton)).toBeVisible() + }) +}) diff --git a/wallets/metamask/test/e2e/metamask.spec.ts b/wallets/metamask/test/e2e/metamask.spec.ts deleted file mode 100644 index a249692be..000000000 --- a/wallets/metamask/test/e2e/metamask.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { type BrowserContext, type Page, chromium, test as base } from '@playwright/test' -import { connectToDapp } from '../../src/actions/connectToDapp' -import { lock } from '../../src/actions/lock' -import { unlock } from '../../src/actions/unlock' -import { OnboardingPage } from '../../src/pages' -import { prepareExtension } from '../../src/prepareExtension' -import { HomePageSelectors, UnlockPageSelectors } from '../../src/selectors' -import { getExtensionId } from '../../src/utils/getExtensionId' - -const DEFAULT_SEED_PHRASE = 'test test test test test test test test test test test junk' -const DEFAULT_PASSWORD = 'Tester@1234' - -let sharedContext: BrowserContext | undefined - -// Fixture for the test. -const test = base.extend<{ - metamaskPage: Page -}>({ - context: async ({ context: _ }, use) => { - if (sharedContext) { - await use(sharedContext) - - return - } - - const metamaskPath = await prepareExtension() - - // biome-ignore format: the array should not be formatted - const browserArgs = [ - `--disable-extensions-except=${metamaskPath}`, - `--load-extension=${metamaskPath}` - ] - - if (process.env.HEADLESS) { - browserArgs.push('--headless=new') - } - - const context = await chromium.launchPersistentContext('', { - headless: false, - args: browserArgs - }) - - try { - await context.waitForEvent('page', { timeout: 5000 }) - } catch { - throw new Error('[FIXTURE] MetaMask extension did not load in time') - } - - sharedContext = context - await use(context) - }, - metamaskPage: async ({ context }, use) => { - const metamaskOnboardingPage = context.pages()[1] as Page - await use(metamaskOnboardingPage) - } -}) - -const { describe, expect } = test - -// Currently testing only happy paths until we have proper setup for parallel tests. -describe('MetaMask', () => { - describe('importWallet', () => { - test('should go through the onboarding flow and import wallet from seed phrase', async ({ metamaskPage }) => { - const onboardingPage = new OnboardingPage(metamaskPage) - - await onboardingPage.importWallet(DEFAULT_SEED_PHRASE, DEFAULT_PASSWORD) - - await expect(metamaskPage.getByText('Account 1')).toBeVisible() - await expect(metamaskPage.getByText('0xf39...2266')).toBeVisible() - }) - }) - - describe('getExtensionId', () => { - test('should return the extension id', async ({ context }) => { - const extensionId = await getExtensionId(context, 'MetaMask') - expect(extensionId).toMatch(/^[a-z]{32}$/) - }) - }) - - describe('connectToDapp', () => { - test('should connect wallet to dapp', async ({ context, page }) => { - const extensionId = await getExtensionId(context, 'MetaMask') - - await page.goto('https://metamask.github.io/test-dapp/') - - await page.locator('#connectButton').click() - - await connectToDapp(context, extensionId) - - await expect(page.locator('#accounts')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') - }) - }) - - describe('lock', () => { - test('should lock the wallet', async ({ metamaskPage }) => { - await metamaskPage.bringToFront() - - await lock(metamaskPage) - - await expect(metamaskPage.locator(UnlockPageSelectors.submitButton)).toBeVisible() - }) - }) - - describe('unlock', () => { - test('should unlock the wallet', async ({ metamaskPage }) => { - await unlock(metamaskPage, DEFAULT_PASSWORD) - - await expect(metamaskPage.locator(HomePageSelectors.logo)).toBeVisible() - }) - }) -}) diff --git a/wallets/metamask/test/e2e/unlock.spec.ts b/wallets/metamask/test/e2e/unlock.spec.ts new file mode 100644 index 000000000..6bae01f54 --- /dev/null +++ b/wallets/metamask/test/e2e/unlock.spec.ts @@ -0,0 +1,18 @@ +import { testWithSynpress } from 'fixtures' +import { HomePageSelectors, lock, unlock, unlockForFixture } from '../../src' + +import basicSetup from './wallet-setup/basic.setup' + +const test = testWithSynpress(basicSetup, unlockForFixture) + +const { describe, expect } = test + +describe('unlock', () => { + test('should unlock the wallet', async ({ metamaskPage }) => { + await lock(metamaskPage) + + await unlock(metamaskPage, basicSetup.walletPassword) + + await expect(metamaskPage.locator(HomePageSelectors.logo)).toBeVisible() + }) +}) diff --git a/wallets/metamask/test/e2e/wallet-setup/basic.setup.ts b/wallets/metamask/test/e2e/wallet-setup/basic.setup.ts new file mode 100644 index 000000000..3a1bd34f7 --- /dev/null +++ b/wallets/metamask/test/e2e/wallet-setup/basic.setup.ts @@ -0,0 +1,10 @@ +import { defineWalletSetup } from 'core' +import { importWallet } from './utils/importWallet' + +const SEED_PHRASE = 'test test test test test test test test test test test junk' + +const PASSWORD = 'Tester@1234' + +export default defineWalletSetup(PASSWORD, async (_, walletPage) => { + await importWallet(walletPage, SEED_PHRASE, PASSWORD) +}) diff --git a/wallets/metamask/test/e2e/wallet-setup/utils/importWallet.ts b/wallets/metamask/test/e2e/wallet-setup/utils/importWallet.ts new file mode 100644 index 000000000..46273ab84 --- /dev/null +++ b/wallets/metamask/test/e2e/wallet-setup/utils/importWallet.ts @@ -0,0 +1,8 @@ +import type { Page } from '@playwright/test' +import { OnboardingPage } from '../../../../src' + +export async function importWallet(walletPage: Page, seedPhrase: string, password: string) { + const onboardingPage = new OnboardingPage(walletPage) + + await onboardingPage.importWallet(seedPhrase, password) +}