-
-
Notifications
You must be signed in to change notification settings - Fork 195
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
📦️ feat(playwright-utils): Add package with the main fixture
- Loading branch information
1 parent
eefab9d
commit cc7e6e1
Showing
14 changed files
with
703 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
declare global { | ||
namespace NodeJS { | ||
interface ProcessEnv { | ||
CI: boolean | ||
HEADLESS: boolean | ||
} | ||
} | ||
} | ||
|
||
export {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "playwright-utils", | ||
"version": "1.0.0", | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./types/index.d.ts", | ||
"default": "./dist/index.js" | ||
} | ||
} | ||
}, | ||
"main": "./dist/index.js", | ||
"files": [ | ||
"dist", | ||
"src", | ||
"types" | ||
], | ||
"scripts": { | ||
"build": "pnpm run clean && pnpm run build:dist && pnpm run build:types", | ||
"build:cache": "tsx src/buildCache.ts", | ||
"build:cache:force": "tsx src/buildCache.ts --force", | ||
"build:cache:force:headless": "HEADLESS=true tsx src/buildCache.ts --force", | ||
"build:cache:headless": "HEADLESS=true tsx src/buildCache.ts", | ||
"build:dist": "tsup --tsconfig tsconfig.build.json", | ||
"build:types": "tsc --emitDeclarationOnly --project tsconfig.build.json", | ||
"clean": "rimraf dist types", | ||
"lint": "biome check . --apply", | ||
"lint:check": "biome check . --verbose", | ||
"lint:unsafe": "biome check . --apply-unsafe", | ||
"local:test:e2e": "playwright test", | ||
"local:test:e2e:headless": "HEADLESS=true playwright test", | ||
"serve:test-dapp": "serve node_modules/@metamask/test-dapp/dist -p 9011", | ||
"types:check": "tsc --noEmit" | ||
}, | ||
"dependencies": { | ||
"core": "workspace:*", | ||
"fs-extra": "^11.1.1", | ||
"metamask": "workspace:*" | ||
}, | ||
"devDependencies": { | ||
"@metamask/test-dapp": "^7.2.0", | ||
"@types/fs-extra": "^11.0.2", | ||
"@types/node": "^20.8.0", | ||
"rimraf": "^5.0.1", | ||
"serve": "^14.2.1", | ||
"tsconfig": "workspace:*", | ||
"tsup": "^7.2.0", | ||
"tsx": "^3.12.10", | ||
"typescript": "^5.2.2" | ||
}, | ||
"peerDependencies": { | ||
"@playwright/test": "1.39.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { defineConfig, devices } from '@playwright/test' | ||
|
||
/** | ||
* See https://playwright.dev/docs/test-configuration. | ||
*/ | ||
export default defineConfig({ | ||
// Look for test files in the "test" directory, relative to this configuration file. | ||
testDir: './test', | ||
|
||
// Run all tests in parallel. | ||
// TODO: Enable later once we have more tests. | ||
fullyParallel: false, | ||
|
||
// Fail the build on CI if you accidentally left test.only in the source code. | ||
forbidOnly: !!process.env.CI, | ||
|
||
// Workers to run parallel tests with. | ||
workers: 2, | ||
|
||
// Concise 'dot' for CI, default 'html' when running locally. | ||
// See https://playwright.dev/docs/test-reporters. | ||
reporter: process.env.CI ? 'list' : 'html', | ||
|
||
// Shared settings for all the projects below. | ||
// See https://playwright.dev/docs/api/class-testoptions. | ||
use: { | ||
// Collect traces when running locally. | ||
// See https://playwright.dev/docs/trace-viewer. | ||
trace: process.env.CI ? 'off' : 'on', | ||
baseURL: 'http://localhost:9011/' | ||
}, | ||
|
||
// Configure projects for major browsers. | ||
projects: [ | ||
{ | ||
name: 'chromium', | ||
use: { ...devices['Desktop Chrome'] } | ||
} | ||
], | ||
|
||
// Web server configuration. | ||
webServer: { | ||
command: 'pnpm run serve:test-dapp', | ||
url: 'http://127.0.0.1:9011', | ||
reuseExistingServer: !process.env.CI | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import path from 'node:path' | ||
import { createCache } from 'core' | ||
import { prepareExtension } from 'metamask' | ||
import { WALLET_SETUP_DIR_NAME } from './constants' | ||
|
||
const isForce = process.argv.includes('--force') | ||
const isHeadless = !!process.env.HEADLESS | ||
|
||
const walletSetupDirPath = path.join(process.cwd(), 'test', WALLET_SETUP_DIR_NAME) | ||
console.log({ walletSetupDirPath, isForce, isHeadless }) | ||
|
||
console.log('\t---- ⏳ Triggering the `createCache` function ----\n') | ||
|
||
await createCache(walletSetupDirPath, prepareExtension, isForce) | ||
|
||
console.log('\n\t---- ✅ `createCache` function finished ----\n') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const WALLET_SETUP_DIR_NAME = 'wallet-setup' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './testWithSynpress' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import path from 'node:path' | ||
import type { | ||
Fixtures, | ||
Page, | ||
PlaywrightTestArgs, | ||
PlaywrightTestOptions, | ||
PlaywrightWorkerArgs, | ||
PlaywrightWorkerOptions | ||
} from '@playwright/test' | ||
import { chromium, test as base } from '@playwright/test' | ||
import { defineWalletSetup, waitForExtensionOnLoadPage } from 'core' | ||
import { createTempContextDir, removeTempContextDir } from 'core' | ||
import { CACHE_DIR_NAME } from 'core' | ||
import fs from 'fs-extra' | ||
import { getExtensionId, prepareExtension } from 'metamask' | ||
import { unlockMetaMask } from './utils/unlockMetaMask' | ||
|
||
// Base types of the `test` fixture from Playwright. | ||
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions | ||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions | ||
|
||
type PrivateSynpressFixtures = { | ||
_contextPath: string | ||
} | ||
|
||
type PublicSynpressFixtures = { | ||
extensionId: string | ||
metamaskPage: Page | ||
} | ||
|
||
type SynpressFixtures = TestFixtures & PrivateSynpressFixtures & PublicSynpressFixtures | ||
|
||
// TODO: Bad practice. Use a store! | ||
let _extensionId: string | ||
let _metamaskPage: Page | ||
|
||
const synpressFixtures = ( | ||
walletSetup: ReturnType<typeof defineWalletSetup>, | ||
slowMo = 0 | ||
): Fixtures<SynpressFixtures, WorkerFixtures> => ({ | ||
_contextPath: async ({ browserName }, use, testInfo) => { | ||
const contextPath = await createTempContextDir(browserName, testInfo.testId) | ||
|
||
await use(contextPath) | ||
|
||
const error = await removeTempContextDir(contextPath) | ||
if (error) { | ||
console.error(error) | ||
} | ||
}, | ||
context: async ({ context: _, _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 metamaskPath = await prepareExtension() | ||
|
||
// We don't need the `--load-extension` args since it is already loaded in the cache. | ||
const browserArgs = [`--disable-extensions-except=${metamaskPath}`] | ||
|
||
if (process.env.HEADLESS) { | ||
browserArgs.push('--headless=new') | ||
|
||
if (slowMo > 0) { | ||
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 | ||
}) | ||
|
||
_metamaskPage = await waitForExtensionOnLoadPage(context) | ||
|
||
await unlockMetaMask(_metamaskPage, walletSetup.walletPassword) | ||
|
||
await use(context) | ||
|
||
await context.close() | ||
}, | ||
extensionId: async ({ context }, use) => { | ||
if (_extensionId) { | ||
await use(_extensionId) | ||
} | ||
|
||
_extensionId = await getExtensionId(context, 'MetaMask') | ||
|
||
await use(_extensionId) | ||
}, | ||
metamaskPage: async ({ context: _ }, use) => { | ||
await use(_metamaskPage) | ||
} | ||
}) | ||
|
||
export const testWithSynpress = (walletSetup: ReturnType<typeof defineWalletSetup>, slowMo?: number) => { | ||
// biome-ignore lint/suspicious/noExplicitAny: satisfying TypeScript here - this type doesn't matter since we are overriding it | ||
return base.extend<PublicSynpressFixtures>(synpressFixtures(walletSetup, slowMo) as any) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import type { Page } from '@playwright/test' | ||
import { errors as playwrightErrors } from '@playwright/test' | ||
import { CrashPageSelectors, HomePageSelectors, LoadingSelectors, unlock } from 'metamask' | ||
|
||
export async function unlockMetaMask(page: Page, password: string) { | ||
await unlock(page, password) | ||
|
||
await page.locator(LoadingSelectors.spinner).waitFor({ | ||
state: 'hidden', | ||
timeout: 3000 // TODO: Extract & Make this timeout configurable. | ||
}) | ||
|
||
await retryIfMetaMaskCrashAfterUnlock(page) | ||
} | ||
|
||
async function retryIfMetaMaskCrashAfterUnlock(page: Page) { | ||
const isHomePageVisible = await page.locator(HomePageSelectors.logo).isVisible() | ||
|
||
if (!isHomePageVisible) { | ||
if (await page.locator(CrashPageSelectors.header).isVisible()) { | ||
const errors = await page.locator(CrashPageSelectors.errors).allTextContents() | ||
|
||
console.warn(['[RetryIfMetaMaskCrashAfterUnlock] MetaMask crashed due to:', ...errors].join('\n')) | ||
|
||
console.log('[RetryIfMetaMaskCrashAfterUnlock] Reloading page...') | ||
await page.reload() | ||
|
||
try { | ||
await page.locator(HomePageSelectors.logo).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 | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { HomePageSelectors, connectToDapp, lock, unlock } from 'metamask' | ||
import { testWithSynpress } from '../src' | ||
import basicSetup from './wallet-setup/basic.setup' | ||
|
||
const test = testWithSynpress(basicSetup, 1_000) | ||
|
||
const { describe, expect } = test | ||
|
||
describe.configure({ | ||
mode: 'parallel' | ||
}) | ||
|
||
describe('testWithSynpress', () => { | ||
test('should connect wallet to MetaMask E2E Test Dapp', async ({ context, page, extensionId }) => { | ||
await page.goto('/') | ||
|
||
await page.locator('#connectButton').click() | ||
|
||
await connectToDapp(context, extensionId) | ||
|
||
await expect(page.locator('#accounts')).toHaveText('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266') | ||
}) | ||
|
||
test('should lock & unlock MetaMask', async ({ metamaskPage }) => { | ||
await metamaskPage.bringToFront() | ||
|
||
await lock(metamaskPage) | ||
|
||
await unlock(metamaskPage, basicSetup.walletPassword) | ||
|
||
await expect(metamaskPage.locator(HomePageSelectors.logo)).toBeVisible() | ||
}) | ||
}) |
13 changes: 13 additions & 0 deletions
13
packages/playwright-utils/test/wallet-setup/basic.setup.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { defineWalletSetup } from 'core' | ||
import { OnboardingPage } from 'metamask' | ||
|
||
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) => { | ||
const onboardingPage = new OnboardingPage(walletPage) | ||
|
||
await onboardingPage.importWallet(SEED_PHRASE, PASSWORD) | ||
|
||
await walletPage.getByTestId('selected-account-click').click() | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"extends": "tsconfig/base.json", | ||
"compilerOptions": { | ||
"rootDir": "src", | ||
"outDir": "types", | ||
"declaration": true, | ||
"sourceMap": true, | ||
"declarationMap": true | ||
}, | ||
"include": ["src"], | ||
"files": ["environment.d.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "./tsconfig.build.json", | ||
"compilerOptions": { | ||
"rootDir": "." | ||
}, | ||
"include": [ | ||
"src", | ||
"test" | ||
], | ||
"files": ["environment.d.ts", "playwright.config.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { defineConfig } from 'tsup' | ||
|
||
export default defineConfig({ | ||
name: 'playwright-utils', | ||
entry: ['src/index.ts'], | ||
outDir: 'dist', | ||
format: 'esm', | ||
splitting: false, | ||
sourcemap: true | ||
}) |
Oops, something went wrong.