Skip to content

Commit 389ef05

Browse files
zgz2020drptbl
andauthored
Phantom support for Playwright (#1271)
* Phantom support for Playwright * Update typedoc-sidebar.json * Update typedoc-sidebar.json * Phantom support for Playwright * chore: build:cache:metamask and build:cache:phantom Signed-off-by: drptbl <[email protected]> * fix: lint errors Signed-off-by: drptbl <[email protected]> * chore: add playwright-report to gitignore Signed-off-by: drptbl <[email protected]> * Improvements * pnpm-lock updates and selector update * chore: update pnpm-lock.yaml dependencies * refactor: update sign button selector to use ActionFooter component * Fixes for waitForExtensionOnLoadPage * Fixes for Unit and Phantom tests --------- Signed-off-by: drptbl <[email protected]> Co-authored-by: drptbl <[email protected]>
1 parent 3fe2cd1 commit 389ef05

File tree

113 files changed

+3703
-694
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

113 files changed

+3703
-694
lines changed

.github/workflows/test.yml

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727
- name: Run tests
2828
run: pnpm run test
2929

30-
test-e2e-headful:
31-
name: Run E2E tests (headful)
30+
test-e2e-headful-wallet-mock-and-metamask:
31+
name: Run E2E tests - Wallet mock & Metamask (headful)
3232
runs-on: ubuntu-latest
3333
steps:
3434
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # [email protected]
@@ -61,19 +61,70 @@ jobs:
6161
run: |
6262
pnpm run serve:test-dapp &
6363
64-
- name: Build cache
64+
- name: Build cache (metamask)
6565
run: |
66-
xvfb-run pnpm run build:cache
66+
xvfb-run pnpm run build:cache:metamask
6767
6868
- name: Run E2E tests (headful)
6969
run: |
70-
xvfb-run pnpm run test:playwright:headful
70+
xvfb-run pnpm run test:playwright:wallet-mock-and-metamask:headful
7171
72-
- name: Archive Playwright report
72+
- name: Archive Playwright report (metamask)
7373
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # [email protected]
7474
if: success() || failure()
7575
with:
76-
name: playwright-report-headful
76+
name: playwright-report-headful-metamask
7777
path: |
7878
wallets/metamask/playwright-report-headful/
7979
if-no-files-found: error
80+
81+
test-e2e-headful-phantom:
82+
name: Run E2E tests - Phantom (headful)
83+
runs-on: ubuntu-latest
84+
steps:
85+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # [email protected]
86+
87+
- name: Setup Node & Install dependencies
88+
uses: ./.github/actions/setup
89+
90+
- name: Install Foundry
91+
uses: foundry-rs/foundry-toolchain@de808b1eea699e761c404bda44ba8f21aba30b2c # [email protected]
92+
93+
- name: Install Playwright dependencies
94+
run: pnpm dlx [email protected] install-deps
95+
96+
# For now, we only need Chromium.
97+
- name: Install browsers for Playwright
98+
run: pnpm dlx [email protected] install chromium
99+
100+
- name: Build project
101+
run: pnpm run build
102+
103+
- name: Install linux dependencies
104+
run: |
105+
sudo apt-get install --no-install-recommends -y \
106+
xvfb
107+
108+
- name: Build project
109+
run: pnpm run build
110+
111+
- name: Serve MetaMask Test Dapp
112+
run: |
113+
pnpm run serve:test-dapp &
114+
115+
- name: Build cache (phantom)
116+
run: |
117+
xvfb-run pnpm run build:cache:phantom
118+
119+
- name: Run E2E tests (headful)
120+
run: |
121+
xvfb-run pnpm run test:playwright:phantom:headful
122+
123+
- name: Archive Playwright report (phantom)
124+
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # [email protected]
125+
if: success() || failure()
126+
with:
127+
name: playwright-report-headful-phantom
128+
path: |
129+
wallets/phantom/playwright-report-headful/
130+
if-no-files-found: error

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ playwright/.cache
5757
output
5858

5959
synpress-source.txt
60+
61+
playwright-report*

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
"license": "MIT",
99
"scripts": {
1010
"build": "turbo build",
11-
"build:cache": "turbo build:cache --filter=@synthetixio/synpress-metamask",
12-
"docs:build": "turbo docs:build --filter=docs",
11+
"build:cache:metamask": "turbo build:cache --filter=@synthetixio/synpress-metamask",
12+
"build:cache:phantom": "turbo build:cache --filter=@synthetixio/synpress-phantom",
1313
"format": "biome format . --write",
1414
"format:check": "biome format . --error-on-warnings",
1515
"preinstall": "npx only-allow pnpm",
@@ -21,8 +21,10 @@
2121
"sort-package-json": "sort-package-json 'package.json' '{packages,wallets,examples}/*/package.json'",
2222
"sort-package-json:check": "sort-package-json 'package.json' '{packages,wallets,examples}/*/package.json' --check",
2323
"test": "turbo test",
24-
"test:playwright:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock",
25-
"test:playwright:headless": "turbo test:playwright:headless --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock",
24+
"test:playwright:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock --filter=@synthetixio/synpress-phantom",
25+
"test:playwright:headless": "turbo test:playwright:headless --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock --filter=@synthetixio/synpress-phantom",
26+
"test:playwright:phantom:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-phantom",
27+
"test:playwright:wallet-mock-and-metamask:headful": "turbo test:playwright:headful --filter=@synthetixio/synpress-metamask --filter=@synthetixio/ethereum-wallet-mock",
2628
"update:deps": "ncu -u -ws --root"
2729
},
2830
"lint-staged": {

packages/cache/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"gradient-string": "2.0.2",
4545
"progress": "2.0.3",
4646
"tsup": "8.0.2",
47+
"unzip-crx-3": "0.2.0",
4748
"unzipper": "0.10.14",
4849
"zod": "3.22.4"
4950
},

packages/cache/src/cli/cliEntrypoint.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import { rimraf } from 'rimraf'
77
import { WALLET_SETUP_DIR_NAME } from '../constants'
88
import { createCache } from '../createCache'
99
import { prepareExtension } from '../prepareExtension'
10+
import { prepareExtensionPhantom } from '../prepareExtensionPhantom'
1011
import { compileWalletSetupFunctions } from './compileWalletSetupFunctions'
1112
import { footer } from './footer'
1213

1314
interface CliFlags {
1415
headless: boolean
1516
force: boolean
1617
debug: boolean
18+
phantom: boolean
1719
}
1820

1921
// Helper function to check if running in WSL
@@ -41,6 +43,7 @@ export const cliEntrypoint = async () => {
4143
)
4244
.option('-f, --force', 'Force the creation of cache even if it already exists', false)
4345
.option('-d, --debug', 'If this flag is present, the compilation files are not going to be deleted', false)
46+
.option('-p, --phantom', 'If this flag is present, Phantom extension will be installed instead of Metamask', false)
4447
.helpOption(undefined, 'Display help for command')
4548
.addHelpText('afterAll', `\n${footer}\n`)
4649
.parse(process.argv)
@@ -88,8 +91,12 @@ export const cliEntrypoint = async () => {
8891
flags.debug
8992
)
9093

91-
// TODO: We should be using `prepareExtension` function from the wallet itself!
92-
await createCache(compiledWalletSetupDirPath, setupFunctionHashes, prepareExtension, flags.force)
94+
// TODO: We should be using `prepareExtension` functions from the wallet itself!
95+
if (flags.phantom) {
96+
await createCache(compiledWalletSetupDirPath, setupFunctionHashes, prepareExtensionPhantom, flags.force)
97+
} else {
98+
await createCache(compiledWalletSetupDirPath, setupFunctionHashes, prepareExtension, flags.force)
99+
}
93100

94101
if (!flags.debug) {
95102
await rimraf(compiledWalletSetupDirPath)

packages/cache/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './utils/createTempContextDir'
88
export * from './utils/removeTempContextDir'
99
export * from './prepareExtension'
1010
export * from './cli/cliEntrypoint'
11+
export * from './prepareExtensionPhantom'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { downloadFile, ensureCacheDirExists, unzipArchivePhantom } from '.'
2+
3+
export const PHANTOM_EXTENSION_DOWNLOAD_URL = 'https://crx-backup.phantom.dev/latest.crx'
4+
5+
// NOTE: This function is copied from `wallets/phantom/src/prepareExtensionPhantom.ts` only TEMPORARILY!
6+
export async function prepareExtensionPhantom() {
7+
const cacheDirPath = ensureCacheDirExists()
8+
9+
const downloadResult = await downloadFile({
10+
url: PHANTOM_EXTENSION_DOWNLOAD_URL,
11+
outputDir: cacheDirPath,
12+
fileName: 'phantom-chrome-latest.crx'
13+
})
14+
15+
const unzipResult = await unzipArchivePhantom({
16+
archivePath: downloadResult.filePath
17+
})
18+
19+
return unzipResult.outputPath
20+
}

packages/cache/src/unzipArchive.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'node:path'
22
import fs from 'fs-extra'
3+
import unzipCrx from 'unzip-crx-3'
34
import unzippper from 'unzipper'
45

56
type UnzipArchiveOptions = {
@@ -73,3 +74,27 @@ export async function unzipArchive(options: UnzipArchiveOptions) {
7374
throw new Error(`[UnzipFile] Error unzipping the file - ${error.message}`)
7475
})
7576
}
77+
78+
export async function unzipArchivePhantom(options: UnzipArchiveOptions) {
79+
const { archivePath, overwrite } = options
80+
81+
const archiveFileExtension = archivePath.split('.').slice(-1)
82+
const outputPath = archivePath.replace(`.${archiveFileExtension}`, '')
83+
84+
const fileExists = fs.existsSync(outputPath)
85+
if (fileExists && !overwrite) {
86+
return {
87+
outputPath,
88+
unzipSkipped: true
89+
}
90+
}
91+
92+
// Creates the output directory
93+
fs.mkdirSync(outputPath, { recursive: true })
94+
95+
await unzipCrx(archivePath, outputPath)
96+
97+
// TODO: Handle errors
98+
99+
return { outputPath }
100+
}

packages/cache/src/utils/createCacheForWalletSetupFunction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export async function createCacheForWalletSetupFunction(
2323
args: browserArgs
2424
})
2525

26-
const extensionPage = await waitForExtensionOnLoadPage(context)
26+
const extensionPage = await waitForExtensionOnLoadPage(context, extensionPath)
2727

2828
try {
2929
await walletSetup(context, extensionPage)

packages/cache/src/utils/waitForExtensionOnLoadPage.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,34 @@ const RELOAD_DELAY = 1000
1515
// Polling interval for finding extension page
1616
const POLLING_INTERVAL = 500
1717

18-
// MetaMask specific selectors
19-
const METAMASK_SELECTORS = {
20-
APP: '#app-content .app'
18+
const APP_SELECTORS = {
19+
METAMASK: '#app-content .app',
20+
PHANTOM: '#root'
2121
}
2222

2323
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
2424

2525
/**
26-
* Checks if a page is blank (no MetaMask app content)
26+
* Checks if a page is blank (no MetaMask or Phantom app content)
2727
*/
28-
async function isPageBlank(page: Page): Promise<boolean> {
28+
async function isPageBlank(page: Page, extensionPath: string): Promise<boolean> {
29+
const app = (extensionPath: string) => {
30+
if (extensionPath.includes('metamask')) {
31+
return 'METAMASK'
32+
} else {
33+
return 'PHANTOM'
34+
}
35+
}
36+
2937
try {
30-
// Check specifically for MetaMask app element
38+
// Check for specific app element
3139
const appElementCount = await page
32-
.locator(METAMASK_SELECTORS.APP)
40+
.locator(APP_SELECTORS[app(extensionPath)])
3341
.count()
3442
.catch(() => 0)
3543

3644
if (appElementCount === 0) {
37-
console.log('[WaitForExtensionOnLoadPage] MetaMask app element not found, page appears to be blank')
45+
console.log(`[WaitForExtensionOnLoadPage] ${app(extensionPath)} app element not found, page appears to be blank`)
3846
return true
3947
}
4048

@@ -50,11 +58,11 @@ async function isPageBlank(page: Page): Promise<boolean> {
5058
/**
5159
* Attempts to fix a blank page by reloading
5260
*/
53-
async function fixBlankPage(page: Page): Promise<boolean> {
61+
async function fixBlankPage(page: Page, extensionPath: string): Promise<boolean> {
5462
for (let attempt = 0; attempt < MAX_BLANK_PAGE_RETRIES; attempt++) {
5563
console.log(`[WaitForExtensionOnLoadPage] Checking page state (attempt ${attempt + 1}/${MAX_BLANK_PAGE_RETRIES})`)
5664

57-
const isBlank = await isPageBlank(page)
65+
const isBlank = await isPageBlank(page, extensionPath)
5866

5967
if (isBlank) {
6068
console.log('[WaitForExtensionOnLoadPage] Page is blank, reloading...')
@@ -90,7 +98,7 @@ async function findExtensionPage(context: BrowserContext): Promise<Page | null>
9098
/**
9199
* Waits for the extension page to load and ensures it's not blank or has errors
92100
*/
93-
export async function waitForExtensionOnLoadPage(context: BrowserContext): Promise<Page> {
101+
export async function waitForExtensionOnLoadPage(context: BrowserContext, extensionPath: string): Promise<Page> {
94102
let retries = 0
95103
console.log(
96104
`[WaitForExtensionOnLoadPage] Starting with timeout: ${EXTENSION_LOAD_TIMEOUT}ms and max retries: ${MAX_RETRIES}`
@@ -136,7 +144,7 @@ export async function waitForExtensionOnLoadPage(context: BrowserContext): Promi
136144

137145
// Now check if the page is blank or has errors and fix if needed
138146
console.log('[WaitForExtensionOnLoadPage] Checking if extension page is properly loaded...')
139-
const isFixed = await fixBlankPage(extensionPage)
147+
const isFixed = await fixBlankPage(extensionPage, extensionPath)
140148

141149
if (isFixed) {
142150
console.log('[WaitForExtensionOnLoadPage] Extension page is properly loaded and ready')

0 commit comments

Comments
 (0)