@avalix/chroma 0.0.7 → 0.0.9
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/dist/index.d.ts +1 -0
- package/dist/index.js +67 -29
- package/package.json +3 -2
- package/src/context-playwright/index.ts +139 -0
- package/src/context-playwright/types.ts +84 -0
- package/src/context-playwright/wallet-factory.ts +140 -0
- package/src/index.ts +2 -0
- package/src/utils/download-extension.ts +68 -0
- package/src/wallets/polkadot-js.ts +135 -0
- package/src/wallets/talisman.ts +180 -0
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -9,14 +9,22 @@ const POLKADOT_JS_CONFIG = {
|
|
|
9
9
|
extensionName: "polkadot-extension-0.61.7"
|
|
10
10
|
};
|
|
11
11
|
async function findExtensionPopup$1(context, extensionId) {
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const maxAttempts = 10;
|
|
13
|
+
const retryDelay = 500;
|
|
14
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
15
|
+
const pages = context.pages();
|
|
16
|
+
for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
17
|
+
await p.waitForLoadState("domcontentloaded");
|
|
18
|
+
return p;
|
|
19
|
+
}
|
|
20
|
+
if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
21
|
+
}
|
|
14
22
|
throw new Error(`Extension popup not found for ID: ${extensionId}`);
|
|
15
23
|
}
|
|
16
24
|
async function getPolkadotJSExtensionPath() {
|
|
17
25
|
const extensionsDir = path.resolve(process.cwd(), ".chroma");
|
|
18
26
|
const extensionDir = path.join(extensionsDir, POLKADOT_JS_CONFIG.extensionName);
|
|
19
|
-
if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Polkadot-JS extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n
|
|
27
|
+
if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Polkadot-JS extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n`);
|
|
20
28
|
console.log(`✅ Found Polkadot-JS extension at: ${extensionDir}`);
|
|
21
29
|
return extensionDir;
|
|
22
30
|
}
|
|
@@ -46,7 +54,6 @@ async function importPolkadotJSAccount(page, { seed, name = "Test Account", pass
|
|
|
46
54
|
async function authorizePolkadotJS(page) {
|
|
47
55
|
const context = page.__extensionContext;
|
|
48
56
|
const extensionId = page.__extensionId;
|
|
49
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
50
57
|
const extensionPopup = await findExtensionPopup$1(context, extensionId);
|
|
51
58
|
await extensionPopup.getByText("Select all").click();
|
|
52
59
|
await extensionPopup.getByRole("button", { name: /Connect \d+ account\(s\)/ }).click();
|
|
@@ -56,12 +63,17 @@ async function approvePolkadotJSTx(page, options = {}) {
|
|
|
56
63
|
const { password = "h3llop0lkadot!" } = options;
|
|
57
64
|
const context = page.__extensionContext;
|
|
58
65
|
const extensionId = page.__extensionId;
|
|
59
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
60
66
|
const extensionPopup = await findExtensionPopup$1(context, extensionId);
|
|
61
67
|
await extensionPopup.getByRole("textbox").fill(password);
|
|
62
68
|
await extensionPopup.getByRole("button", { name: "Sign the transaction" }).click();
|
|
63
69
|
console.log("✅ Polkadot-JS transaction signed successfully");
|
|
64
70
|
}
|
|
71
|
+
async function rejectPolkadotJSTx(page) {
|
|
72
|
+
const context = page.__extensionContext;
|
|
73
|
+
const extensionId = page.__extensionId;
|
|
74
|
+
await (await findExtensionPopup$1(context, extensionId)).getByRole("link", { name: "Cancel" }).click();
|
|
75
|
+
console.log("✅ Polkadot-JS transaction rejected successfully");
|
|
76
|
+
}
|
|
65
77
|
|
|
66
78
|
//#endregion
|
|
67
79
|
//#region src/wallets/talisman.ts
|
|
@@ -70,45 +82,55 @@ const TALISMAN_CONFIG = {
|
|
|
70
82
|
extensionName: "talisman-extension-3.0.5"
|
|
71
83
|
};
|
|
72
84
|
async function findExtensionPopup(context, extensionId) {
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
for (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
const maxAttempts = 10;
|
|
86
|
+
const retryDelay = 500;
|
|
87
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
88
|
+
const pages = context.pages();
|
|
89
|
+
for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
90
|
+
await p.setViewportSize({
|
|
91
|
+
width: 400,
|
|
92
|
+
height: 600
|
|
93
|
+
});
|
|
94
|
+
await p.waitForLoadState("domcontentloaded");
|
|
95
|
+
return p;
|
|
96
|
+
}
|
|
97
|
+
if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
82
98
|
}
|
|
83
99
|
throw new Error(`Extension popup not found for ID: ${extensionId}`);
|
|
84
100
|
}
|
|
85
101
|
async function getTalismanExtensionPath() {
|
|
86
102
|
const extensionsDir = path.resolve(process.cwd(), ".chroma");
|
|
87
103
|
const extensionDir = path.join(extensionsDir, TALISMAN_CONFIG.extensionName);
|
|
88
|
-
if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Talisman extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n
|
|
104
|
+
if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Talisman extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n`);
|
|
89
105
|
console.log(`✅ Found Talisman extension at: ${extensionDir}`);
|
|
90
106
|
return extensionDir;
|
|
91
107
|
}
|
|
92
108
|
async function importEthPrivateKey(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
|
|
93
109
|
const context = page.__extensionContext;
|
|
94
110
|
const extensionId = page.__extensionId;
|
|
95
|
-
|
|
111
|
+
const maxAttempts = 20;
|
|
112
|
+
const retryDelay = 500;
|
|
96
113
|
let extensionPage = null;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
115
|
+
const pages = context.pages();
|
|
116
|
+
for (const p of pages) {
|
|
117
|
+
const url = p.url();
|
|
118
|
+
console.log(`📄 Found page: ${url}`);
|
|
119
|
+
if (url.includes("onboarding.html") || url.includes(`chrome-extension://${extensionId}/`)) {
|
|
120
|
+
extensionPage = p;
|
|
121
|
+
console.log(`✅ Found Talisman onboarding page: ${url}`);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (extensionPage) break;
|
|
126
|
+
if (attempt < maxAttempts - 1) {
|
|
127
|
+
console.log(`⏳ Attempt ${attempt + 1}/${maxAttempts}: Waiting for onboarding page...`);
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
105
129
|
}
|
|
106
130
|
}
|
|
107
|
-
if (!extensionPage) throw new Error(`Talisman onboarding page not found`);
|
|
131
|
+
if (!extensionPage) throw new Error(`Talisman onboarding page not found after ${maxAttempts} attempts`);
|
|
108
132
|
try {
|
|
109
133
|
await extensionPage.bringToFront();
|
|
110
|
-
console.log("🔄 Reloading onboarding page for fresh state...");
|
|
111
|
-
await extensionPage.reload();
|
|
112
134
|
await extensionPage.waitForLoadState("domcontentloaded");
|
|
113
135
|
await extensionPage.getByTestId("onboarding-get-started-button").click();
|
|
114
136
|
await extensionPage.getByRole("textbox", { name: "Enter password" }).fill(password);
|
|
@@ -135,7 +157,6 @@ async function authorizeTalisman(page, options = {}) {
|
|
|
135
157
|
const { accountName = "Test Account" } = options;
|
|
136
158
|
const context = page.__extensionContext;
|
|
137
159
|
const extensionId = page.__extensionId;
|
|
138
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
139
160
|
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
140
161
|
await extensionPopup.getByRole("button", { name: accountName }).click();
|
|
141
162
|
await extensionPopup.getByTestId("connection-connect-button").click();
|
|
@@ -148,9 +169,13 @@ async function authorizeTalisman(page, options = {}) {
|
|
|
148
169
|
async function approveTalismanTx(page) {
|
|
149
170
|
const context = page.__extensionContext;
|
|
150
171
|
const extensionId = page.__extensionId;
|
|
151
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
152
172
|
await (await findExtensionPopup(context, extensionId)).getByRole("button", { name: "Approve" }).click();
|
|
153
173
|
}
|
|
174
|
+
async function rejectTalismanTx(page) {
|
|
175
|
+
const context = page.__extensionContext;
|
|
176
|
+
const extensionId = page.__extensionId;
|
|
177
|
+
await (await findExtensionPopup(context, extensionId)).getByTestId("connection-reject-button").click();
|
|
178
|
+
}
|
|
154
179
|
|
|
155
180
|
//#endregion
|
|
156
181
|
//#region src/context-playwright/types.ts
|
|
@@ -206,6 +231,19 @@ function createWalletInstance(walletType, extensionId, context) {
|
|
|
206
231
|
break;
|
|
207
232
|
default: throw new Error(`Unsupported wallet type: ${walletType}`);
|
|
208
233
|
}
|
|
234
|
+
},
|
|
235
|
+
rejectTx: async () => {
|
|
236
|
+
const page = context.pages()[0] || await context.newPage();
|
|
237
|
+
const extPage = createExtendedPage(page, context, extensionId);
|
|
238
|
+
switch (walletType) {
|
|
239
|
+
case "polkadot-js":
|
|
240
|
+
await rejectPolkadotJSTx(extPage);
|
|
241
|
+
break;
|
|
242
|
+
case "talisman":
|
|
243
|
+
await rejectTalismanTx(extPage);
|
|
244
|
+
break;
|
|
245
|
+
default: throw new Error(`Unsupported wallet type: ${walletType}`);
|
|
246
|
+
}
|
|
209
247
|
}
|
|
210
248
|
};
|
|
211
249
|
switch (walletType) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avalix/chroma",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.9",
|
|
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,84 @@
|
|
|
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
|
+
rejectTx: () => Promise<void>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Polkadot-JS specific wallet instance
|
|
32
|
+
export interface PolkadotJsWalletInstance extends BaseWalletInstance {
|
|
33
|
+
type: 'polkadot-js'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Talisman specific wallet instance (with additional methods)
|
|
37
|
+
export interface TalismanWalletInstance extends BaseWalletInstance {
|
|
38
|
+
type: 'talisman'
|
|
39
|
+
importEthPrivateKey: (options: { privateKey: string, name?: string, password?: string }) => Promise<void>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Union type of all wallet instances
|
|
43
|
+
export type WalletInstance = PolkadotJsWalletInstance | TalismanWalletInstance
|
|
44
|
+
|
|
45
|
+
// Map wallet type to its instance
|
|
46
|
+
export interface WalletTypeMap {
|
|
47
|
+
'polkadot-js': PolkadotJsWalletInstance
|
|
48
|
+
'talisman': TalismanWalletInstance
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Wallets collection - all wallet types
|
|
52
|
+
export type Wallets = WalletTypeMap
|
|
53
|
+
|
|
54
|
+
// Helper type to build a wallets object based on configured wallet types
|
|
55
|
+
export type ConfiguredWallets<T extends readonly WalletConfig[]> = {
|
|
56
|
+
[K in T[number]['type']]: WalletTypeMap[K]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extended page with wallet context
|
|
60
|
+
export type ExtendedPage = Page & {
|
|
61
|
+
__extensionContext: BrowserContext
|
|
62
|
+
__walletExtensionIds: Map<string, string>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Complete test configuration - supports single and multi-wallet
|
|
66
|
+
export interface ChromaTestOptions<T extends readonly WalletConfig[] = WalletConfig[]> {
|
|
67
|
+
// Wallet configuration (single or multiple)
|
|
68
|
+
wallets?: T
|
|
69
|
+
// Common options
|
|
70
|
+
headless?: boolean
|
|
71
|
+
slowMo?: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Test fixtures (test-scoped: recreated per test)
|
|
75
|
+
export interface WalletFixtures<W = Wallets> {
|
|
76
|
+
page: ExtendedPage
|
|
77
|
+
wallets: W
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Worker fixtures (worker-scoped: persisted across tests)
|
|
81
|
+
export interface WalletWorkerFixtures {
|
|
82
|
+
walletContext: BrowserContext
|
|
83
|
+
walletExtensionIds: Map<string, string>
|
|
84
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
rejectPolkadotJSTx,
|
|
14
|
+
} from '../wallets/polkadot-js.js'
|
|
15
|
+
import {
|
|
16
|
+
approveTalismanTx,
|
|
17
|
+
authorizeTalisman,
|
|
18
|
+
importEthPrivateKey,
|
|
19
|
+
rejectTalismanTx,
|
|
20
|
+
} from '../wallets/talisman.js'
|
|
21
|
+
|
|
22
|
+
// Helper to create extended page with wallet context
|
|
23
|
+
function createExtendedPage(page: Page, context: BrowserContext, extensionId: string) {
|
|
24
|
+
const extPage = page as Page & { __extensionContext: BrowserContext, __extensionId: string }
|
|
25
|
+
extPage.__extensionContext = context
|
|
26
|
+
extPage.__extensionId = extensionId
|
|
27
|
+
return extPage
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Create wallet instance helper with proper typing
|
|
31
|
+
export function createWalletInstance(
|
|
32
|
+
walletType: string,
|
|
33
|
+
extensionId: string,
|
|
34
|
+
context: BrowserContext,
|
|
35
|
+
): WalletInstance {
|
|
36
|
+
// Store the imported account name for later use
|
|
37
|
+
let importedAccountName: string | undefined
|
|
38
|
+
|
|
39
|
+
// Common methods for all wallets
|
|
40
|
+
const baseInstance: BaseWalletInstance = {
|
|
41
|
+
extensionId,
|
|
42
|
+
importMnemonic: async (options: WalletAccount) => {
|
|
43
|
+
const page = context.pages()[0] || await context.newPage()
|
|
44
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
45
|
+
|
|
46
|
+
// Store the account name for future authorize calls
|
|
47
|
+
importedAccountName = options.name || 'Test Account'
|
|
48
|
+
|
|
49
|
+
switch (walletType) {
|
|
50
|
+
case 'polkadot-js':
|
|
51
|
+
await importPolkadotJSAccount(extPage, options)
|
|
52
|
+
break
|
|
53
|
+
case 'talisman':
|
|
54
|
+
throw new Error('Talisman importMnemonic is not yet implemented.')
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`Unsupported wallet type: ${walletType}`)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
authorize: async (options: { accountName?: string } = {}) => {
|
|
60
|
+
const page = context.pages()[0] || await context.newPage()
|
|
61
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
62
|
+
|
|
63
|
+
// Use provided account name or fall back to the imported one
|
|
64
|
+
const accountName = options.accountName || importedAccountName
|
|
65
|
+
|
|
66
|
+
switch (walletType) {
|
|
67
|
+
case 'polkadot-js':
|
|
68
|
+
await authorizePolkadotJS(extPage)
|
|
69
|
+
break
|
|
70
|
+
case 'talisman':
|
|
71
|
+
await authorizeTalisman(extPage, { accountName })
|
|
72
|
+
break
|
|
73
|
+
default:
|
|
74
|
+
throw new Error(`Unsupported wallet type: ${walletType}`)
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
approveTx: async (options: { password?: string } = {}) => {
|
|
78
|
+
const page = context.pages()[0] || await context.newPage()
|
|
79
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
80
|
+
|
|
81
|
+
switch (walletType) {
|
|
82
|
+
case 'polkadot-js':
|
|
83
|
+
await approvePolkadotJSTx(extPage, options)
|
|
84
|
+
break
|
|
85
|
+
case 'talisman':
|
|
86
|
+
await approveTalismanTx(extPage)
|
|
87
|
+
break
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`Unsupported wallet type: ${walletType}`)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
rejectTx: async () => {
|
|
93
|
+
const page = context.pages()[0] || await context.newPage()
|
|
94
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
95
|
+
|
|
96
|
+
switch (walletType) {
|
|
97
|
+
case 'polkadot-js':
|
|
98
|
+
await rejectPolkadotJSTx(extPage)
|
|
99
|
+
break
|
|
100
|
+
case 'talisman':
|
|
101
|
+
await rejectTalismanTx(extPage)
|
|
102
|
+
break
|
|
103
|
+
default:
|
|
104
|
+
throw new Error(`Unsupported wallet type: ${walletType}`)
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Return wallet-specific instance with type discriminator
|
|
110
|
+
switch (walletType) {
|
|
111
|
+
case 'polkadot-js':
|
|
112
|
+
return {
|
|
113
|
+
...baseInstance,
|
|
114
|
+
type: 'polkadot-js',
|
|
115
|
+
} as PolkadotJsWalletInstance
|
|
116
|
+
|
|
117
|
+
case 'talisman':
|
|
118
|
+
return {
|
|
119
|
+
...baseInstance,
|
|
120
|
+
type: 'talisman',
|
|
121
|
+
importEthPrivateKey: async (options: { privateKey: string, name?: string, password?: string }) => {
|
|
122
|
+
const page = context.pages()[0] || await context.newPage()
|
|
123
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
124
|
+
|
|
125
|
+
// Store the account name for future authorize calls
|
|
126
|
+
importedAccountName = options.name || 'Test Account'
|
|
127
|
+
|
|
128
|
+
// Use the seed property to pass the private key
|
|
129
|
+
await importEthPrivateKey(extPage, {
|
|
130
|
+
seed: options.privateKey,
|
|
131
|
+
name: options.name,
|
|
132
|
+
password: options.password,
|
|
133
|
+
})
|
|
134
|
+
},
|
|
135
|
+
} as TalismanWalletInstance
|
|
136
|
+
|
|
137
|
+
default:
|
|
138
|
+
throw new Error(`Unsupported wallet type: ${walletType}`)
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/index.ts
ADDED
|
@@ -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,135 @@
|
|
|
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
|
+
// Wait for extension popup to appear with retry logic
|
|
16
|
+
const maxAttempts = 10
|
|
17
|
+
const retryDelay = 500
|
|
18
|
+
|
|
19
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
20
|
+
const pages = context.pages()
|
|
21
|
+
for (const p of pages) {
|
|
22
|
+
if (p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
23
|
+
await p.waitForLoadState('domcontentloaded')
|
|
24
|
+
return p
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If not found, wait a bit before retrying
|
|
29
|
+
if (attempt < maxAttempts - 1) {
|
|
30
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new Error(`Extension popup not found for ID: ${extensionId}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Get Polkadot-JS extension path
|
|
38
|
+
export async function getPolkadotJSExtensionPath(): Promise<string> {
|
|
39
|
+
const extensionsDir = path.resolve(process.cwd(), '.chroma')
|
|
40
|
+
const extensionDir = path.join(extensionsDir, POLKADOT_JS_CONFIG.extensionName)
|
|
41
|
+
|
|
42
|
+
// Check if extension exists
|
|
43
|
+
if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Polkadot-JS extension not found at: ${extensionDir}\n\n`
|
|
46
|
+
+ `Please download the extension first by running:\n`
|
|
47
|
+
+ ` npx @avalix/chroma download-extensions\n`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`✅ Found Polkadot-JS extension at: ${extensionDir}`)
|
|
52
|
+
return extensionDir
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Polkadot-JS specific account import implementation
|
|
56
|
+
export async function importPolkadotJSAccount(
|
|
57
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
58
|
+
{ seed, name = 'Test Account', password = 'h3llop0lkadot!' }: WalletAccount,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const context = page.__extensionContext
|
|
61
|
+
const extensionId = page.__extensionId
|
|
62
|
+
|
|
63
|
+
const extensionPopupUrl = `chrome-extension://${extensionId}/index.html`
|
|
64
|
+
const extensionPage = await context.newPage()
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await extensionPage.goto(extensionPopupUrl)
|
|
68
|
+
|
|
69
|
+
// Handle "Understood, let me continue" button if it exists
|
|
70
|
+
const understoodButton = extensionPage.getByRole('button', { name: 'Understood, let me continue' })
|
|
71
|
+
if (await understoodButton.count() > 0) {
|
|
72
|
+
await understoodButton.click()
|
|
73
|
+
await extensionPage.waitForTimeout(100)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Navigate to import seed page
|
|
77
|
+
await extensionPage.goto(`${extensionPopupUrl}#/account/import-seed`)
|
|
78
|
+
|
|
79
|
+
// Fill seed phrase and account details
|
|
80
|
+
await extensionPage.locator('textarea').fill(seed)
|
|
81
|
+
await extensionPage.locator('button:has-text("Next")').click()
|
|
82
|
+
await extensionPage.locator('input[type="text"]').fill(name)
|
|
83
|
+
await extensionPage.locator('input[type="password"]').fill(password)
|
|
84
|
+
await extensionPage.locator('div').filter({ hasText: /^Repeat password for verification$/ }).getByRole('textbox').fill(password)
|
|
85
|
+
await extensionPage.getByRole('button', { name: 'Add the account with the supplied seed' }).click()
|
|
86
|
+
|
|
87
|
+
console.log(`✅ Created Polkadot-JS wallet account: ${name}`)
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
await extensionPage.close()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Polkadot-JS specific authorization implementation
|
|
95
|
+
export async function authorizePolkadotJS(
|
|
96
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
97
|
+
): Promise<void> {
|
|
98
|
+
const context = page.__extensionContext
|
|
99
|
+
const extensionId = page.__extensionId
|
|
100
|
+
|
|
101
|
+
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
102
|
+
await extensionPopup.getByText('Select all').click()
|
|
103
|
+
await extensionPopup.getByRole('button', { name: /Connect \d+ account\(s\)/ }).click()
|
|
104
|
+
|
|
105
|
+
console.log('✅ Polkadot-JS wallet connected successfully')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Polkadot-JS specific transaction approval implementation
|
|
109
|
+
export async function approvePolkadotJSTx(
|
|
110
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
111
|
+
options: { password?: string } = {},
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
const { password = 'h3llop0lkadot!' } = options
|
|
114
|
+
const context = page.__extensionContext
|
|
115
|
+
const extensionId = page.__extensionId
|
|
116
|
+
|
|
117
|
+
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
118
|
+
await extensionPopup.getByRole('textbox').fill(password)
|
|
119
|
+
await extensionPopup.getByRole('button', { name: 'Sign the transaction' }).click()
|
|
120
|
+
|
|
121
|
+
console.log('✅ Polkadot-JS transaction signed successfully')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Polkadot-JS specific transaction rejection implementation
|
|
125
|
+
export async function rejectPolkadotJSTx(
|
|
126
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
const context = page.__extensionContext
|
|
129
|
+
const extensionId = page.__extensionId
|
|
130
|
+
|
|
131
|
+
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
132
|
+
await extensionPopup.getByRole('link', { name: 'Cancel' }).click()
|
|
133
|
+
|
|
134
|
+
console.log('✅ Polkadot-JS transaction rejected successfully')
|
|
135
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
// Wait for extension popup to appear with retry logic
|
|
16
|
+
const maxAttempts = 10
|
|
17
|
+
const retryDelay = 500
|
|
18
|
+
|
|
19
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
20
|
+
const pages = context.pages()
|
|
21
|
+
for (const p of pages) {
|
|
22
|
+
if (p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
23
|
+
await p.setViewportSize({ width: 400, height: 600 })
|
|
24
|
+
await p.waitForLoadState('domcontentloaded')
|
|
25
|
+
return p
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If not found, wait a bit before retrying
|
|
30
|
+
if (attempt < maxAttempts - 1) {
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new Error(`Extension popup not found for ID: ${extensionId}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get Talisman extension path
|
|
39
|
+
export async function getTalismanExtensionPath(): Promise<string> {
|
|
40
|
+
const extensionsDir = path.resolve(process.cwd(), '.chroma')
|
|
41
|
+
const extensionDir = path.join(extensionsDir, TALISMAN_CONFIG.extensionName)
|
|
42
|
+
|
|
43
|
+
// Check if extension exists
|
|
44
|
+
if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Talisman extension not found at: ${extensionDir}\n\n`
|
|
47
|
+
+ `Please download the extension first by running:\n`
|
|
48
|
+
+ ` npx @avalix/chroma download-extensions\n`,
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(`✅ Found Talisman extension at: ${extensionDir}`)
|
|
53
|
+
return extensionDir
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Talisman specific Ethereum private key import implementation
|
|
57
|
+
export async function importEthPrivateKey(
|
|
58
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
59
|
+
{ seed, name = 'Test Account', password = 'h3llop0lkadot!' }: WalletAccount,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const context = page.__extensionContext
|
|
62
|
+
const extensionId = page.__extensionId
|
|
63
|
+
|
|
64
|
+
// Wait for Talisman to open its onboarding tab with retry logic
|
|
65
|
+
const maxAttempts = 20
|
|
66
|
+
const retryDelay = 500
|
|
67
|
+
let extensionPage: Page | null = null
|
|
68
|
+
|
|
69
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
70
|
+
const pages = context.pages()
|
|
71
|
+
|
|
72
|
+
for (const p of pages) {
|
|
73
|
+
const url = p.url()
|
|
74
|
+
console.log(`📄 Found page: ${url}`)
|
|
75
|
+
if (url.includes('onboarding.html') || url.includes(`chrome-extension://${extensionId}/`)) {
|
|
76
|
+
extensionPage = p
|
|
77
|
+
console.log(`✅ Found Talisman onboarding page: ${url}`)
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (extensionPage) {
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If not found, wait before retrying
|
|
87
|
+
if (attempt < maxAttempts - 1) {
|
|
88
|
+
console.log(`⏳ Attempt ${attempt + 1}/${maxAttempts}: Waiting for onboarding page...`)
|
|
89
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!extensionPage) {
|
|
94
|
+
throw new Error(`Talisman onboarding page not found after ${maxAttempts} attempts`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Bring the onboarding page to front
|
|
99
|
+
await extensionPage.bringToFront()
|
|
100
|
+
|
|
101
|
+
// Wait for the page to load and become interactive
|
|
102
|
+
await extensionPage.waitForLoadState('domcontentloaded')
|
|
103
|
+
|
|
104
|
+
// Click the get started button
|
|
105
|
+
await extensionPage.getByTestId('onboarding-get-started-button').click()
|
|
106
|
+
|
|
107
|
+
// Fill the password
|
|
108
|
+
await extensionPage.getByRole('textbox', { name: 'Enter password' }).fill(password!)
|
|
109
|
+
await extensionPage.getByRole('textbox', { name: 'Confirm password' }).fill(password!)
|
|
110
|
+
await extensionPage.getByTestId('onboarding-password-confirm-button').click()
|
|
111
|
+
|
|
112
|
+
// Click the no thanks button
|
|
113
|
+
await extensionPage.getByRole('button', { name: 'No thanks' }).click()
|
|
114
|
+
await extensionPage.getByTestId('onboarding-enter-talisman-button').click()
|
|
115
|
+
|
|
116
|
+
// Import Ethereum account
|
|
117
|
+
await extensionPage.getByRole('button', { name: 'Add account Create or import' }).click()
|
|
118
|
+
await extensionPage.getByRole('button', { name: 'Import Import an existing' }).click()
|
|
119
|
+
await extensionPage.getByRole('button', { name: 'Import via Private Key' }).click()
|
|
120
|
+
await extensionPage.getByRole('button', { name: 'Select account platform' }).click()
|
|
121
|
+
await extensionPage.getByRole('option', { name: 'Ethereum' }).locator('div').click()
|
|
122
|
+
await extensionPage.getByRole('textbox', { name: 'Choose a name' }).fill(name!)
|
|
123
|
+
await extensionPage.getByRole('textbox', { name: 'Enter your private key' }).fill(seed!)
|
|
124
|
+
await extensionPage.getByRole('button', { name: 'Save' }).click()
|
|
125
|
+
|
|
126
|
+
await extensionPage.close()
|
|
127
|
+
|
|
128
|
+
console.log('✅ Talisman account import completed')
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error('❌ Error during Talisman account import:', error)
|
|
132
|
+
throw error
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Talisman specific authorization implementation
|
|
137
|
+
export async function authorizeTalisman(
|
|
138
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
139
|
+
options: { accountName?: string } = {},
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
const { accountName = 'Test Account' } = options
|
|
142
|
+
const context = page.__extensionContext
|
|
143
|
+
const extensionId = page.__extensionId
|
|
144
|
+
|
|
145
|
+
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
146
|
+
|
|
147
|
+
// Authorize Talisman account
|
|
148
|
+
await extensionPopup.getByRole('button', { name: accountName }).click()
|
|
149
|
+
await extensionPopup.getByTestId('connection-connect-button').click()
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const anotherPopup = await findExtensionPopup(context, extensionId)
|
|
153
|
+
await anotherPopup.getByRole('button', { name: 'Approve' }).click()
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
console.log('No another popup found, skipping')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Talisman specific transaction approval implementation
|
|
161
|
+
export async function approveTalismanTx(
|
|
162
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
const context = page.__extensionContext
|
|
165
|
+
const extensionId = page.__extensionId
|
|
166
|
+
|
|
167
|
+
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
168
|
+
await extensionPopup.getByRole('button', { name: 'Approve' }).click()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Talisman specific transaction rejection implementation
|
|
172
|
+
export async function rejectTalismanTx(
|
|
173
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
const context = page.__extensionContext
|
|
176
|
+
const extensionId = page.__extensionId
|
|
177
|
+
|
|
178
|
+
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
179
|
+
await extensionPopup.getByTestId('connection-reject-button').click()
|
|
180
|
+
}
|