@avalix/chroma 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@avalix/chroma",
3
3
  "type": "module",
4
- "version": "0.0.7",
4
+ "version": "0.0.8",
5
5
  "description": "End-to-end testing library for Polkadot wallet interactions",
6
6
  "author": "Avalix Labs",
7
7
  "license": "MIT",
@@ -35,7 +35,8 @@
35
35
  },
36
36
  "files": [
37
37
  "dist",
38
- "scripts"
38
+ "scripts",
39
+ "src"
39
40
  ],
40
41
  "scripts": {
41
42
  "build": "tsdown",
@@ -0,0 +1,139 @@
1
+ import type {
2
+ ChromaTestOptions,
3
+ ConfiguredWallets,
4
+ ExtendedPage,
5
+ WalletConfig,
6
+ WalletFixtures,
7
+ WalletInstance,
8
+ Wallets,
9
+ WalletType,
10
+ WalletWorkerFixtures,
11
+ } from './types.js'
12
+ import { test as base, chromium } from '@playwright/test'
13
+ import { getPolkadotJSExtensionPath } from '../wallets/polkadot-js.js'
14
+ import { getTalismanExtensionPath } from '../wallets/talisman.js'
15
+ import { WALLET_TYPES } from './types.js'
16
+ import { createWalletInstance } from './wallet-factory.js'
17
+
18
+ // Helper function to get extension path for a wallet config
19
+ async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
20
+ const { type } = config
21
+
22
+ switch (type) {
23
+ case 'polkadot-js':
24
+ return await getPolkadotJSExtensionPath()
25
+ case 'talisman':
26
+ return await getTalismanExtensionPath()
27
+ default:
28
+ throw new Error(`Unsupported wallet type: ${type}`)
29
+ }
30
+ }
31
+
32
+ // Create a test function with wallet configuration
33
+ // Supports single and multi-wallet modes
34
+ export function createWalletTest<const T extends readonly WalletConfig[]>(
35
+ options: ChromaTestOptions<T> = {} as ChromaTestOptions<T>,
36
+ ) {
37
+ const { headless = false, slowMo = 150 } = options
38
+
39
+ // Default to polkadot-js if no wallets specified
40
+ const walletConfigs: readonly WalletConfig[] = options.wallets && options.wallets.length > 0
41
+ ? options.wallets
42
+ : [{ type: 'polkadot-js' }]
43
+
44
+ const isMultiWallet = walletConfigs.length > 1
45
+
46
+ // Compute the expected wallets type
47
+ type ExpectedWallets = T extends readonly WalletConfig[] ? ConfiguredWallets<T> : Wallets
48
+
49
+ return base.extend<WalletFixtures<ExpectedWallets>, WalletWorkerFixtures>({
50
+ // Worker-scoped: Browser context with extension(s) (persists across all tests in worker)
51
+ // eslint-disable-next-line no-empty-pattern
52
+ walletContext: [async ({}, use) => {
53
+ // Get all extension paths
54
+ const extensionPaths = await Promise.all(
55
+ walletConfigs.map(config => getExtensionPathForWallet(config)),
56
+ )
57
+
58
+ // Join paths with comma for Chrome args
59
+ const extensionPathsString = extensionPaths.join(',')
60
+
61
+ const context = await chromium.launchPersistentContext('', {
62
+ headless,
63
+ channel: 'chromium',
64
+ args: [
65
+ `--load-extension=${extensionPathsString}`,
66
+ `--disable-extensions-except=${extensionPathsString}`,
67
+ ],
68
+ slowMo,
69
+ })
70
+
71
+ await use(context)
72
+ await context.close()
73
+ }, { scope: 'worker' }],
74
+
75
+ // Worker-scoped: Map of wallet type to extension ID
76
+ walletExtensionIds: [async ({ walletContext }, use) => {
77
+ const extensionIds = new Map<string, string>()
78
+
79
+ // Wait for all service workers to load
80
+ const serviceWorkers = walletContext.serviceWorkers()
81
+ if (serviceWorkers.length === 0) {
82
+ // Wait for at least one service worker
83
+ await walletContext.waitForEvent('serviceworker')
84
+ }
85
+
86
+ // Give some time for all extensions to load
87
+ if (isMultiWallet) {
88
+ await new Promise(resolve => setTimeout(resolve, 1000))
89
+ }
90
+
91
+ // Get all service workers (one per extension)
92
+ const allServiceWorkers = walletContext.serviceWorkers()
93
+
94
+ // Map service workers to wallet types
95
+ // Note: The order should match the walletConfigs order
96
+ for (let i = 0; i < walletConfigs.length && i < allServiceWorkers.length; i++) {
97
+ const extensionId = allServiceWorkers[i].url().split('/')[2]
98
+ const walletType = walletConfigs[i].type
99
+ extensionIds.set(walletType, extensionId)
100
+ console.log(`✅ Loaded ${walletType} extension with ID: ${extensionId}`)
101
+ }
102
+
103
+ await use(extensionIds)
104
+ }, { scope: 'worker' }],
105
+
106
+ // Main page with extension context (uses worker-scoped context)
107
+ page: async ({ walletContext, walletExtensionIds }, use) => {
108
+ const page = walletContext.pages()[0] || await walletContext.newPage()
109
+
110
+ // Store context and extension IDs on page
111
+ const extendedPage = page as ExtendedPage
112
+ extendedPage.__extensionContext = walletContext
113
+ extendedPage.__walletExtensionIds = walletExtensionIds
114
+
115
+ await use(extendedPage)
116
+ // Note: Don't close the page or context here since they're worker-scoped
117
+ },
118
+
119
+ // Wallet instances for each configured wallet
120
+ wallets: async ({ walletContext, walletExtensionIds }, use) => {
121
+ const walletMap: Partial<ExpectedWallets> = {}
122
+
123
+ // Create wallet instance for each configured wallet
124
+ for (const [walletType, extensionId] of walletExtensionIds) {
125
+ if (WALLET_TYPES.includes(walletType as WalletType)) {
126
+ const instance = createWalletInstance(walletType, extensionId, walletContext);
127
+ (walletMap as Record<string, WalletInstance>)[walletType] = instance
128
+ }
129
+ }
130
+
131
+ await use(walletMap as ExpectedWallets)
132
+ },
133
+ })
134
+ }
135
+
136
+ // Default test with Polkadot JS wallet (with persistent wallet support via worker-scoped fixtures)
137
+ export const test: ReturnType<typeof createWalletTest> = createWalletTest()
138
+
139
+ export { expect } from '@playwright/test'
@@ -0,0 +1,83 @@
1
+ import type { BrowserContext, Page } from '@playwright/test'
2
+
3
+ // Wallet types - single source of truth
4
+ export type WalletType = 'polkadot-js' | 'talisman'
5
+
6
+ // Available wallet types as constant array
7
+ export const WALLET_TYPES: readonly WalletType[] = ['polkadot-js', 'talisman'] as const
8
+
9
+ // Wallet account configuration
10
+ export interface WalletAccount {
11
+ seed: string
12
+ name?: string
13
+ password?: string
14
+ }
15
+
16
+ // Configuration for a single wallet
17
+ export interface WalletConfig {
18
+ type: WalletType
19
+ downloadUrl?: string
20
+ }
21
+
22
+ // Base wallet instance - common methods for all wallets
23
+ export interface BaseWalletInstance {
24
+ extensionId: string
25
+ importMnemonic: (options: WalletAccount) => Promise<void>
26
+ authorize: (options?: { accountName?: string }) => Promise<void>
27
+ approveTx: (options?: { password?: string }) => Promise<void>
28
+ }
29
+
30
+ // Polkadot-JS specific wallet instance
31
+ export interface PolkadotJsWalletInstance extends BaseWalletInstance {
32
+ type: 'polkadot-js'
33
+ }
34
+
35
+ // Talisman specific wallet instance (with additional methods)
36
+ export interface TalismanWalletInstance extends BaseWalletInstance {
37
+ type: 'talisman'
38
+ importEthPrivateKey: (options: { privateKey: string, name?: string, password?: string }) => Promise<void>
39
+ }
40
+
41
+ // Union type of all wallet instances
42
+ export type WalletInstance = PolkadotJsWalletInstance | TalismanWalletInstance
43
+
44
+ // Map wallet type to its instance
45
+ export interface WalletTypeMap {
46
+ 'polkadot-js': PolkadotJsWalletInstance
47
+ 'talisman': TalismanWalletInstance
48
+ }
49
+
50
+ // Wallets collection - all wallet types
51
+ export type Wallets = WalletTypeMap
52
+
53
+ // Helper type to build a wallets object based on configured wallet types
54
+ export type ConfiguredWallets<T extends readonly WalletConfig[]> = {
55
+ [K in T[number]['type']]: WalletTypeMap[K]
56
+ }
57
+
58
+ // Extended page with wallet context
59
+ export type ExtendedPage = Page & {
60
+ __extensionContext: BrowserContext
61
+ __walletExtensionIds: Map<string, string>
62
+ }
63
+
64
+ // Complete test configuration - supports single and multi-wallet
65
+ export interface ChromaTestOptions<T extends readonly WalletConfig[] = WalletConfig[]> {
66
+ // Wallet configuration (single or multiple)
67
+ wallets?: T
68
+ // Common options
69
+ headless?: boolean
70
+ slowMo?: number
71
+ }
72
+
73
+ // Test fixtures (test-scoped: recreated per test)
74
+ export interface WalletFixtures<W = Wallets> {
75
+ page: ExtendedPage
76
+ wallets: W
77
+ }
78
+
79
+ // Worker fixtures (worker-scoped: persisted across tests)
80
+ export interface WalletWorkerFixtures {
81
+ walletContext: BrowserContext
82
+ walletExtensionIds: Map<string, string>
83
+ }
@@ -0,0 +1,123 @@
1
+ import type { BrowserContext, Page } from '@playwright/test'
2
+ import type {
3
+ BaseWalletInstance,
4
+ PolkadotJsWalletInstance,
5
+ TalismanWalletInstance,
6
+ WalletAccount,
7
+ WalletInstance,
8
+ } from './types.js'
9
+ import {
10
+ approvePolkadotJSTx,
11
+ authorizePolkadotJS,
12
+ importPolkadotJSAccount,
13
+ } from '../wallets/polkadot-js.js'
14
+ import {
15
+ approveTalismanTx,
16
+ authorizeTalisman,
17
+ importEthPrivateKey,
18
+ } from '../wallets/talisman.js'
19
+
20
+ // Helper to create extended page with wallet context
21
+ function createExtendedPage(page: Page, context: BrowserContext, extensionId: string) {
22
+ const extPage = page as Page & { __extensionContext: BrowserContext, __extensionId: string }
23
+ extPage.__extensionContext = context
24
+ extPage.__extensionId = extensionId
25
+ return extPage
26
+ }
27
+
28
+ // Create wallet instance helper with proper typing
29
+ export function createWalletInstance(
30
+ walletType: string,
31
+ extensionId: string,
32
+ context: BrowserContext,
33
+ ): WalletInstance {
34
+ // Store the imported account name for later use
35
+ let importedAccountName: string | undefined
36
+
37
+ // Common methods for all wallets
38
+ const baseInstance: BaseWalletInstance = {
39
+ extensionId,
40
+ importMnemonic: async (options: WalletAccount) => {
41
+ const page = context.pages()[0] || await context.newPage()
42
+ const extPage = createExtendedPage(page, context, extensionId)
43
+
44
+ // Store the account name for future authorize calls
45
+ importedAccountName = options.name || 'Test Account'
46
+
47
+ switch (walletType) {
48
+ case 'polkadot-js':
49
+ await importPolkadotJSAccount(extPage, options)
50
+ break
51
+ case 'talisman':
52
+ throw new Error('Talisman importMnemonic is not yet implemented.')
53
+ default:
54
+ throw new Error(`Unsupported wallet type: ${walletType}`)
55
+ }
56
+ },
57
+ authorize: async (options: { accountName?: string } = {}) => {
58
+ const page = context.pages()[0] || await context.newPage()
59
+ const extPage = createExtendedPage(page, context, extensionId)
60
+
61
+ // Use provided account name or fall back to the imported one
62
+ const accountName = options.accountName || importedAccountName
63
+
64
+ switch (walletType) {
65
+ case 'polkadot-js':
66
+ await authorizePolkadotJS(extPage)
67
+ break
68
+ case 'talisman':
69
+ await authorizeTalisman(extPage, { accountName })
70
+ break
71
+ default:
72
+ throw new Error(`Unsupported wallet type: ${walletType}`)
73
+ }
74
+ },
75
+ approveTx: async (options: { password?: string } = {}) => {
76
+ const page = context.pages()[0] || await context.newPage()
77
+ const extPage = createExtendedPage(page, context, extensionId)
78
+
79
+ switch (walletType) {
80
+ case 'polkadot-js':
81
+ await approvePolkadotJSTx(extPage, options)
82
+ break
83
+ case 'talisman':
84
+ await approveTalismanTx(extPage)
85
+ break
86
+ default:
87
+ throw new Error(`Unsupported wallet type: ${walletType}`)
88
+ }
89
+ },
90
+ }
91
+
92
+ // Return wallet-specific instance with type discriminator
93
+ switch (walletType) {
94
+ case 'polkadot-js':
95
+ return {
96
+ ...baseInstance,
97
+ type: 'polkadot-js',
98
+ } as PolkadotJsWalletInstance
99
+
100
+ case 'talisman':
101
+ return {
102
+ ...baseInstance,
103
+ type: 'talisman',
104
+ importEthPrivateKey: async (options: { privateKey: string, name?: string, password?: string }) => {
105
+ const page = context.pages()[0] || await context.newPage()
106
+ const extPage = createExtendedPage(page, context, extensionId)
107
+
108
+ // Store the account name for future authorize calls
109
+ importedAccountName = options.name || 'Test Account'
110
+
111
+ // Use the seed property to pass the private key
112
+ await importEthPrivateKey(extPage, {
113
+ seed: options.privateKey,
114
+ name: options.name,
115
+ password: options.password,
116
+ })
117
+ },
118
+ } as TalismanWalletInstance
119
+
120
+ default:
121
+ throw new Error(`Unsupported wallet type: ${walletType}`)
122
+ }
123
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Main entry point for chroma package
2
+ export { createWalletTest, expect, test } from './context-playwright'
@@ -0,0 +1,68 @@
1
+ import fs, { createWriteStream } from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+ import { pipeline } from 'node:stream/promises'
5
+ import { Extract } from 'unzipper'
6
+
7
+ export interface DownloadExtensionOptions {
8
+ downloadUrl: string
9
+ extensionName: string
10
+ targetDir?: string
11
+ }
12
+
13
+ export async function downloadAndExtractExtension(options: DownloadExtensionOptions): Promise<string> {
14
+ const { downloadUrl, extensionName, targetDir } = options
15
+
16
+ // Default to a directory in the user's project, not relative to this package
17
+ const extensionsDir = targetDir || path.resolve(process.cwd(), '.chroma')
18
+ const extensionDir = path.join(extensionsDir, extensionName)
19
+ const zipPath = path.join(extensionsDir, `${extensionName}.zip`)
20
+
21
+ // Create extensions directory if it doesn't exist
22
+ await fs.promises.mkdir(extensionsDir, { recursive: true })
23
+
24
+ // Check if extension is already downloaded and extracted
25
+ if (fs.existsSync(extensionDir) && fs.readdirSync(extensionDir).length > 0) {
26
+ console.log(`✅ ${extensionName} already exists at:`, extensionDir)
27
+ return extensionDir
28
+ }
29
+
30
+ try {
31
+ console.log(`📥 Downloading ${extensionName}...`)
32
+
33
+ // Download the ZIP file
34
+ const response = await fetch(downloadUrl)
35
+ if (!response.ok) {
36
+ throw new Error(`Failed to download extension: ${response.status} ${response.statusText}`)
37
+ }
38
+
39
+ // Save ZIP file
40
+ const writeStream = createWriteStream(zipPath)
41
+ await pipeline(response.body!, writeStream)
42
+
43
+ console.log('📦 Extracting extension...')
44
+
45
+ // Standard zip extraction
46
+ await pipeline(
47
+ fs.createReadStream(zipPath),
48
+ Extract({ path: extensionDir }),
49
+ )
50
+
51
+ // Clean up ZIP file
52
+ await fs.promises.unlink(zipPath)
53
+
54
+ console.log(`✅ ${extensionName} downloaded and extracted to:`, extensionDir)
55
+ return extensionDir
56
+ }
57
+ catch (error) {
58
+ // Clean up on error
59
+ if (fs.existsSync(zipPath)) {
60
+ await fs.promises.unlink(zipPath).catch(() => {})
61
+ }
62
+ if (fs.existsSync(extensionDir)) {
63
+ await fs.promises.rm(extensionDir, { recursive: true, force: true }).catch(() => {})
64
+ }
65
+
66
+ throw new Error(`Failed to download/extract ${extensionName}: ${error instanceof Error ? error.message : String(error)}`)
67
+ }
68
+ }
@@ -0,0 +1,114 @@
1
+ import type { BrowserContext, Page } from '@playwright/test'
2
+ import type { WalletAccount } from '../context-playwright/types.js'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import process from 'node:process'
6
+
7
+ // Polkadot-JS specific configuration
8
+ export const POLKADOT_JS_CONFIG = {
9
+ downloadUrl: 'https://github.com/polkadot-js/extension/releases/download/v0.61.7/master-chrome-build.zip',
10
+ extensionName: 'polkadot-extension-0.61.7',
11
+ } as const
12
+
13
+ // Helper function to find extension popup
14
+ async function findExtensionPopup(context: BrowserContext, extensionId: string): Promise<Page> {
15
+ const pages = context.pages()
16
+ for (const p of pages) {
17
+ if (p.url().includes(`chrome-extension://${extensionId}/`)) {
18
+ return p
19
+ }
20
+ }
21
+ throw new Error(`Extension popup not found for ID: ${extensionId}`)
22
+ }
23
+
24
+ // Get Polkadot-JS extension path
25
+ export async function getPolkadotJSExtensionPath(): Promise<string> {
26
+ const extensionsDir = path.resolve(process.cwd(), '.chroma')
27
+ const extensionDir = path.join(extensionsDir, POLKADOT_JS_CONFIG.extensionName)
28
+
29
+ // Check if extension exists
30
+ if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) {
31
+ throw new Error(
32
+ `Polkadot-JS extension not found at: ${extensionDir}\n\n`
33
+ + `Please download the extension first by running:\n`
34
+ + ` npx @avalix/chroma download-extensions\n\n`
35
+ + `Or if you're using this as a dependency:\n`
36
+ + ` npm run chroma:download\n`,
37
+ )
38
+ }
39
+
40
+ console.log(`✅ Found Polkadot-JS extension at: ${extensionDir}`)
41
+ return extensionDir
42
+ }
43
+
44
+ // Polkadot-JS specific account import implementation
45
+ export async function importPolkadotJSAccount(
46
+ page: Page & { __extensionContext: BrowserContext, __extensionId: string },
47
+ { seed, name = 'Test Account', password = 'h3llop0lkadot!' }: WalletAccount,
48
+ ): Promise<void> {
49
+ const context = page.__extensionContext
50
+ const extensionId = page.__extensionId
51
+
52
+ const extensionPopupUrl = `chrome-extension://${extensionId}/index.html`
53
+ const extensionPage = await context.newPage()
54
+
55
+ try {
56
+ await extensionPage.goto(extensionPopupUrl)
57
+
58
+ // Handle "Understood, let me continue" button if it exists
59
+ const understoodButton = extensionPage.getByRole('button', { name: 'Understood, let me continue' })
60
+ if (await understoodButton.count() > 0) {
61
+ await understoodButton.click()
62
+ await extensionPage.waitForTimeout(100)
63
+ }
64
+
65
+ // Navigate to import seed page
66
+ await extensionPage.goto(`${extensionPopupUrl}#/account/import-seed`)
67
+
68
+ // Fill seed phrase and account details
69
+ await extensionPage.locator('textarea').fill(seed)
70
+ await extensionPage.locator('button:has-text("Next")').click()
71
+ await extensionPage.locator('input[type="text"]').fill(name)
72
+ await extensionPage.locator('input[type="password"]').fill(password)
73
+ await extensionPage.locator('div').filter({ hasText: /^Repeat password for verification$/ }).getByRole('textbox').fill(password)
74
+ await extensionPage.getByRole('button', { name: 'Add the account with the supplied seed' }).click()
75
+
76
+ console.log(`✅ Created Polkadot-JS wallet account: ${name}`)
77
+ }
78
+ finally {
79
+ await extensionPage.close()
80
+ }
81
+ }
82
+
83
+ // Polkadot-JS specific authorization implementation
84
+ export async function authorizePolkadotJS(
85
+ page: Page & { __extensionContext: BrowserContext, __extensionId: string },
86
+ ): Promise<void> {
87
+ const context = page.__extensionContext
88
+ const extensionId = page.__extensionId
89
+ await new Promise(resolve => setTimeout(resolve, 1000))
90
+
91
+ const extensionPopup = await findExtensionPopup(context, extensionId)
92
+ await extensionPopup.getByText('Select all').click()
93
+ await extensionPopup.getByRole('button', { name: /Connect \d+ account\(s\)/ }).click()
94
+
95
+ console.log('✅ Polkadot-JS wallet connected successfully')
96
+ }
97
+
98
+ // Polkadot-JS specific transaction approval implementation
99
+ export async function approvePolkadotJSTx(
100
+ page: Page & { __extensionContext: BrowserContext, __extensionId: string },
101
+ options: { password?: string } = {},
102
+ ): Promise<void> {
103
+ const { password = 'h3llop0lkadot!' } = options
104
+ const context = page.__extensionContext
105
+ const extensionId = page.__extensionId
106
+
107
+ await new Promise(resolve => setTimeout(resolve, 1000))
108
+ const extensionPopup = await findExtensionPopup(context, extensionId)
109
+
110
+ await extensionPopup.getByRole('textbox').fill(password)
111
+ await extensionPopup.getByRole('button', { name: 'Sign the transaction' }).click()
112
+
113
+ console.log('✅ Polkadot-JS transaction signed successfully')
114
+ }
@@ -0,0 +1,159 @@
1
+ import type { BrowserContext, Page } from '@playwright/test'
2
+ import type { WalletAccount } from '../context-playwright/types.js'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import process from 'node:process'
6
+
7
+ // Talisman specific configuration
8
+ export const TALISMAN_CONFIG = {
9
+ downloadUrl: 'https://github.com/avalix-labs/polkadot-wallets/raw/refs/heads/main/talisman/talisman-3.0.5.zip',
10
+ extensionName: 'talisman-extension-3.0.5',
11
+ } as const
12
+
13
+ // Helper function to find extension popup
14
+ async function findExtensionPopup(context: BrowserContext, extensionId: string): Promise<Page> {
15
+ // delay for 1 second
16
+ await new Promise(resolve => setTimeout(resolve, 1000))
17
+
18
+ const pages = context.pages()
19
+ for (const p of pages) {
20
+ if (p.url().includes(`chrome-extension://${extensionId}/`)) {
21
+ p.setViewportSize({ width: 400, height: 600 })
22
+ p.waitForLoadState('domcontentloaded')
23
+ return p
24
+ }
25
+ }
26
+
27
+ throw new Error(`Extension popup not found for ID: ${extensionId}`)
28
+ }
29
+
30
+ // Get Talisman extension path
31
+ export async function getTalismanExtensionPath(): Promise<string> {
32
+ const extensionsDir = path.resolve(process.cwd(), '.chroma')
33
+ const extensionDir = path.join(extensionsDir, TALISMAN_CONFIG.extensionName)
34
+
35
+ // Check if extension exists
36
+ if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) {
37
+ throw new Error(
38
+ `Talisman extension not found at: ${extensionDir}\n\n`
39
+ + `Please download the extension first by running:\n`
40
+ + ` npx @avalix/chroma download-extensions\n\n`
41
+ + `Or if you're using this as a dependency:\n`
42
+ + ` npm run chroma:download\n`,
43
+ )
44
+ }
45
+
46
+ console.log(`✅ Found Talisman extension at: ${extensionDir}`)
47
+ return extensionDir
48
+ }
49
+
50
+ // Talisman specific Ethereum private key import implementation
51
+ export async function importEthPrivateKey(
52
+ page: Page & { __extensionContext: BrowserContext, __extensionId: string },
53
+ { seed, name = 'Test Account', password = 'h3llop0lkadot!' }: WalletAccount,
54
+ ): Promise<void> {
55
+ const context = page.__extensionContext
56
+ const extensionId = page.__extensionId
57
+
58
+ // Wait for Talisman to open its onboarding tab
59
+ await new Promise(resolve => setTimeout(resolve, 2000))
60
+
61
+ // Find the onboarding tab
62
+ let extensionPage: Page | null = null
63
+ const pages = context.pages()
64
+
65
+ for (const page of pages) {
66
+ const url = page.url()
67
+ console.log(`📄 Found page: ${url}`)
68
+ if (url.includes('onboarding.html') || url.includes(`chrome-extension://${extensionId}/`)) {
69
+ extensionPage = page
70
+ console.log(`✅ Found Talisman onboarding page: ${url}`)
71
+ break
72
+ }
73
+ }
74
+
75
+ if (!extensionPage) {
76
+ throw new Error(`Talisman onboarding page not found`)
77
+ }
78
+
79
+ try {
80
+ // Bring the onboarding page to front
81
+ await extensionPage.bringToFront()
82
+
83
+ // Reload the onboarding page to ensure fresh state
84
+ console.log('🔄 Reloading onboarding page for fresh state...')
85
+ await extensionPage.reload()
86
+
87
+ // Wait for the page to load and become interactive
88
+ await extensionPage.waitForLoadState('domcontentloaded')
89
+ // await extensionPage.waitForTimeout(20000) // Give Talisman more time to initialize
90
+
91
+ // Click the get started button
92
+ await extensionPage.getByTestId('onboarding-get-started-button').click()
93
+
94
+ // Fill the password
95
+ await extensionPage.getByRole('textbox', { name: 'Enter password' }).fill(password!)
96
+ await extensionPage.getByRole('textbox', { name: 'Confirm password' }).fill(password!)
97
+ await extensionPage.getByTestId('onboarding-password-confirm-button').click()
98
+
99
+ // Click the no thanks button
100
+ await extensionPage.getByRole('button', { name: 'No thanks' }).click()
101
+ await extensionPage.getByTestId('onboarding-enter-talisman-button').click()
102
+
103
+ // Import Ethereum account
104
+ await extensionPage.getByRole('button', { name: 'Add account Create or import' }).click()
105
+ await extensionPage.getByRole('button', { name: 'Import Import an existing' }).click()
106
+ await extensionPage.getByRole('button', { name: 'Import via Private Key' }).click()
107
+ await extensionPage.getByRole('button', { name: 'Select account platform' }).click()
108
+ await extensionPage.getByRole('option', { name: 'Ethereum' }).locator('div').click()
109
+ await extensionPage.getByRole('textbox', { name: 'Choose a name' }).fill(name!)
110
+ await extensionPage.getByRole('textbox', { name: 'Enter your private key' }).fill(seed!)
111
+ await extensionPage.getByRole('button', { name: 'Save' }).click()
112
+
113
+ await extensionPage.close()
114
+
115
+ console.log('✅ Talisman account import completed')
116
+ }
117
+ catch (error) {
118
+ console.error('❌ Error during Talisman account import:', error)
119
+ throw error
120
+ }
121
+ }
122
+
123
+ // Talisman specific authorization implementation
124
+ export async function authorizeTalisman(
125
+ page: Page & { __extensionContext: BrowserContext, __extensionId: string },
126
+ options: { accountName?: string } = {},
127
+ ): Promise<void> {
128
+ const { accountName = 'Test Account' } = options
129
+ const context = page.__extensionContext
130
+ const extensionId = page.__extensionId
131
+ await new Promise(resolve => setTimeout(resolve, 1000))
132
+
133
+ const extensionPopup = await findExtensionPopup(context, extensionId)
134
+
135
+ // Authorize Talisman account
136
+ await extensionPopup.getByRole('button', { name: accountName }).click()
137
+ await extensionPopup.getByTestId('connection-connect-button').click()
138
+
139
+ try {
140
+ const anotherPopup = await findExtensionPopup(context, extensionId)
141
+ await anotherPopup.getByRole('button', { name: 'Approve' }).click()
142
+ }
143
+ catch {
144
+ console.log('No another popup found, skipping')
145
+ }
146
+ }
147
+
148
+ // Talisman specific transaction approval implementation
149
+ export async function approveTalismanTx(
150
+ page: Page & { __extensionContext: BrowserContext, __extensionId: string },
151
+ ): Promise<void> {
152
+ const context = page.__extensionContext
153
+ const extensionId = page.__extensionId
154
+
155
+ await new Promise(resolve => setTimeout(resolve, 1000))
156
+ const extensionPopup = await findExtensionPopup(context, extensionId)
157
+
158
+ await extensionPopup.getByRole('button', { name: 'Approve' }).click()
159
+ }