@avalix/chroma 0.0.11 → 0.0.12
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/README.md +18 -225
- package/dist/index.d.mts +28 -21
- package/dist/index.mjs +107 -101
- package/package.json +3 -2
- package/scripts/download-extensions.ts +3 -3
- package/src/context-playwright/index.ts +6 -9
- package/src/context-playwright/types.ts +8 -26
- package/src/context-playwright/wallet-factory.ts +60 -96
- package/src/utils/download-extension.ts +1 -1
- package/src/wallets/talisman.ts +106 -52
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
End-to-end testing library for Polkadot wallet interactions using Playwright.
|
|
4
4
|
|
|
5
|
-
> **⚠️ Active Development**: This library is currently under active development. The API may change and breaking changes can occur between versions. Please pin your version and review changelogs carefully when updating.
|
|
6
|
-
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
7
|
```bash
|
|
@@ -27,7 +25,7 @@ This will download the wallet extensions (Polkadot JS and Talisman) to `./.chrom
|
|
|
27
25
|
```json
|
|
28
26
|
{
|
|
29
27
|
"scripts": {
|
|
30
|
-
"prepare": "chroma download-extensions"
|
|
28
|
+
"test:prepare": "chroma download-extensions"
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
31
|
```
|
|
@@ -36,258 +34,60 @@ This will download the wallet extensions (Polkadot JS and Talisman) to `./.chrom
|
|
|
36
34
|
|
|
37
35
|
## Quick Start
|
|
38
36
|
|
|
39
|
-
### Basic Usage
|
|
40
|
-
|
|
41
37
|
```typescript
|
|
42
|
-
import {
|
|
38
|
+
import { createWalletTest, expect } from '@avalix/chroma'
|
|
43
39
|
|
|
44
|
-
test
|
|
40
|
+
const test = createWalletTest({
|
|
41
|
+
wallets: [{ type: 'polkadot-js' }]
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('connect wallet and sign transaction', async ({ page, wallets }) => {
|
|
45
45
|
const polkadotJs = wallets['polkadot-js']
|
|
46
46
|
|
|
47
|
-
// Import a test account
|
|
48
47
|
await polkadotJs.importMnemonic({
|
|
49
48
|
seed: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk',
|
|
50
49
|
name: 'Test Account',
|
|
51
50
|
password: 'securePassword123'
|
|
52
51
|
})
|
|
53
52
|
|
|
54
|
-
// Navigate to your dApp
|
|
55
53
|
await page.goto('http://localhost:3000')
|
|
56
|
-
|
|
57
|
-
// Connect wallet
|
|
58
54
|
await page.click('button:has-text("Connect Wallet")')
|
|
59
55
|
await polkadotJs.authorize()
|
|
60
56
|
|
|
61
|
-
// Perform transaction
|
|
62
57
|
await page.click('button:has-text("Send Transaction")')
|
|
63
58
|
await polkadotJs.approveTx({ password: 'securePassword123' })
|
|
64
59
|
|
|
65
|
-
// Verify transaction success
|
|
66
60
|
await expect(page.locator('.transaction-success')).toBeVisible()
|
|
67
61
|
})
|
|
68
62
|
```
|
|
69
63
|
|
|
70
|
-
### Custom Configuration
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
import { createWalletTest, expect } from '@avalix/chroma'
|
|
74
|
-
|
|
75
|
-
const customTest = createWalletTest({
|
|
76
|
-
wallets: [{ type: 'polkadot-js' }],
|
|
77
|
-
headless: false,
|
|
78
|
-
slowMo: 100
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
customTest('test with custom config', async ({ page, wallets }) => {
|
|
82
|
-
const polkadotJs = wallets['polkadot-js']
|
|
83
|
-
|
|
84
|
-
await polkadotJs.importMnemonic({
|
|
85
|
-
seed: 'your seed phrase here...',
|
|
86
|
-
name: 'My Test Account'
|
|
87
|
-
})
|
|
88
|
-
await page.goto('http://localhost:3000')
|
|
89
|
-
await polkadotJs.authorize()
|
|
90
|
-
})
|
|
91
|
-
```
|
|
92
|
-
|
|
93
64
|
### Multiple Wallets
|
|
94
65
|
|
|
95
66
|
```typescript
|
|
96
|
-
import { createWalletTest
|
|
67
|
+
import { createWalletTest } from '@avalix/chroma'
|
|
97
68
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
wallets: [
|
|
101
|
-
{ type: 'polkadot-js' },
|
|
102
|
-
{ type: 'talisman' }
|
|
103
|
-
],
|
|
104
|
-
headless: false,
|
|
105
|
-
slowMo: 150
|
|
69
|
+
const test = createWalletTest({
|
|
70
|
+
wallets: [{ type: 'polkadot-js' }, { type: 'talisman' }]
|
|
106
71
|
})
|
|
107
72
|
|
|
108
|
-
|
|
73
|
+
test('multi-wallet test', async ({ page, wallets }) => {
|
|
109
74
|
const polkadotJs = wallets['polkadot-js']
|
|
110
75
|
const talisman = wallets.talisman
|
|
111
76
|
|
|
112
|
-
|
|
113
|
-
await
|
|
114
|
-
seed: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk',
|
|
115
|
-
name: 'Alice'
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// Import to Talisman using Ethereum private key
|
|
119
|
-
await talisman.importEthPrivateKey({
|
|
120
|
-
privateKey: '0x...',
|
|
121
|
-
name: 'Bob'
|
|
122
|
-
})
|
|
77
|
+
await polkadotJs.importMnemonic({ seed: '...', name: 'Alice' })
|
|
78
|
+
await talisman.importEthPrivateKey({ privateKey: '0x...', name: 'Bob' })
|
|
123
79
|
|
|
124
80
|
await page.goto('http://localhost:3000')
|
|
125
|
-
|
|
126
|
-
// Use specific wallet
|
|
127
81
|
await polkadotJs.authorize()
|
|
128
|
-
await polkadotJs.approveTx()
|
|
129
82
|
})
|
|
130
83
|
```
|
|
131
84
|
|
|
132
85
|
## Features
|
|
133
86
|
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
- 🔗 **dApp Authorization**: Connect wallet to decentralized applications
|
|
139
|
-
- 🔀 **Multi-Wallet Support**: Test with multiple wallet extensions simultaneously
|
|
140
|
-
- ⚙️ **Configurable**: Custom extension paths, headless mode, and slow motion settings
|
|
141
|
-
|
|
142
|
-
## API Reference
|
|
143
|
-
|
|
144
|
-
### Core Functions
|
|
145
|
-
|
|
146
|
-
#### `test` (Default Test Function)
|
|
147
|
-
Pre-configured test function with Polkadot JS extension.
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
import { test } from '@avalix/chroma'
|
|
151
|
-
|
|
152
|
-
test('my wallet test', async ({ page, wallets }) => {
|
|
153
|
-
const polkadotJs = wallets['polkadot-js']
|
|
154
|
-
|
|
155
|
-
await polkadotJs.importMnemonic({ seed: '...' })
|
|
156
|
-
await polkadotJs.authorize()
|
|
157
|
-
})
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
#### `createWalletTest(options?: ChromaTestOptions)`
|
|
161
|
-
Create a custom test function with specific configuration. Supports single and multi-wallet modes.
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
import { createWalletTest } from '@avalix/chroma'
|
|
165
|
-
|
|
166
|
-
// Single wallet (default)
|
|
167
|
-
const test = createWalletTest()
|
|
168
|
-
|
|
169
|
-
// Single wallet with custom config
|
|
170
|
-
const customTest = createWalletTest({
|
|
171
|
-
wallets: [{ type: 'polkadot-js' }],
|
|
172
|
-
headless: false,
|
|
173
|
-
slowMo: 150
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
// Multiple wallets
|
|
177
|
-
const multiTest = createWalletTest({
|
|
178
|
-
wallets: [
|
|
179
|
-
{ type: 'polkadot-js' },
|
|
180
|
-
{ type: 'talisman' }
|
|
181
|
-
]
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
// Usage
|
|
185
|
-
test('example', async ({ page, wallets }) => {
|
|
186
|
-
const polkadotJs = wallets['polkadot-js']
|
|
187
|
-
|
|
188
|
-
await polkadotJs.importMnemonic({ seed: '...' })
|
|
189
|
-
await polkadotJs.authorize()
|
|
190
|
-
await polkadotJs.approveTx()
|
|
191
|
-
})
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Test Fixtures
|
|
195
|
-
|
|
196
|
-
#### `page`
|
|
197
|
-
Playwright page instance with wallet extension(s) loaded.
|
|
198
|
-
|
|
199
|
-
#### `wallets`
|
|
200
|
-
Typed object containing wallet instances for each configured wallet. Provides full TypeScript autocomplete.
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
// Base wallet instance (common methods)
|
|
204
|
-
interface BaseWalletInstance {
|
|
205
|
-
extensionId: string
|
|
206
|
-
importMnemonic: (options: WalletAccount) => Promise<void>
|
|
207
|
-
authorize: (options?: { accountName?: string }) => Promise<void>
|
|
208
|
-
approveTx: (options?: { password?: string }) => Promise<void>
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Polkadot-JS wallet instance
|
|
212
|
-
interface PolkadotJsWalletInstance extends BaseWalletInstance {
|
|
213
|
-
type: 'polkadot-js'
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Talisman wallet instance (with additional methods)
|
|
217
|
-
interface TalismanWalletInstance extends BaseWalletInstance {
|
|
218
|
-
type: 'talisman'
|
|
219
|
-
importEthPrivateKey: (options: { privateKey: string, name?: string, password?: string }) => Promise<void>
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Note: Talisman currently does not support importMnemonic - use importEthPrivateKey instead
|
|
223
|
-
|
|
224
|
-
// Wallets collection - each wallet has its specific type
|
|
225
|
-
interface Wallets {
|
|
226
|
-
'polkadot-js': PolkadotJsWalletInstance
|
|
227
|
-
'talisman': TalismanWalletInstance
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
interface WalletAccount {
|
|
231
|
-
seed: string
|
|
232
|
-
name?: string // Default: 'Test Account'
|
|
233
|
-
password?: string // Default: 'h3llop0lkadot!'
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Usage:**
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
test('example', async ({ page, wallets }) => {
|
|
241
|
-
const polkadotJs = wallets['polkadot-js'] // Type: PolkadotJsWalletInstance
|
|
242
|
-
|
|
243
|
-
// Import mnemonic (available on all wallets)
|
|
244
|
-
await polkadotJs.importMnemonic({
|
|
245
|
-
seed: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk',
|
|
246
|
-
name: 'Test Account',
|
|
247
|
-
password: 'securePassword123'
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
await page.goto('http://localhost:3000')
|
|
251
|
-
await polkadotJs.authorize()
|
|
252
|
-
await polkadotJs.approveTx({ password: 'securePassword123' })
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
// Talisman-specific features
|
|
256
|
-
test('talisman example', async ({ page, wallets }) => {
|
|
257
|
-
const talisman = wallets.talisman // Type: TalismanWalletInstance
|
|
258
|
-
|
|
259
|
-
// Talisman-specific method: import Ethereum private key
|
|
260
|
-
await talisman.importEthPrivateKey({
|
|
261
|
-
privateKey: '0x...',
|
|
262
|
-
name: 'My Account',
|
|
263
|
-
password: 'mypassword'
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
// Common methods also available
|
|
267
|
-
await talisman.authorize({ accountName: 'My Account' })
|
|
268
|
-
await talisman.approveTx()
|
|
269
|
-
})
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
## Configuration
|
|
273
|
-
|
|
274
|
-
### Extension Download
|
|
275
|
-
Run the download command to get the required wallet extensions:
|
|
276
|
-
|
|
277
|
-
```bash
|
|
278
|
-
npx chroma download-extensions
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Extensions will be downloaded to `./.chroma` directory in your project root. Add this directory to your `.gitignore`:
|
|
282
|
-
|
|
283
|
-
```gitignore
|
|
284
|
-
.chroma/
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
### Browser Settings
|
|
288
|
-
- **Headless Mode**: Disabled by default for better debugging
|
|
289
|
-
- **Slow Motion**: 150ms delay between actions (configurable)
|
|
290
|
-
- **Extension Loading**: Automatically loads configured wallet extensions
|
|
87
|
+
- **Easy Extension Setup** - Download wallet extensions with a single command
|
|
88
|
+
- **Multi-Wallet Support** - Test with multiple wallet extensions simultaneously
|
|
89
|
+
- **TypeScript Support** - Full type safety and autocomplete
|
|
90
|
+
- **VS Code Integration** - Works with [Playwright Test for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
|
|
291
91
|
|
|
292
92
|
## Supported Chains
|
|
293
93
|
|
|
@@ -304,20 +104,13 @@ Extensions will be downloaded to `./.chroma` directory in your project root. Add
|
|
|
304
104
|
| Polkadot JS Extension | ✅ Supported | v0.62.6 |
|
|
305
105
|
| Talisman | ✅ Supported | v3.1.13 |
|
|
306
106
|
| SubWallet | ⏳ Planned | - |
|
|
107
|
+
| MetaMask | ⏳ Planned | - |
|
|
307
108
|
|
|
308
109
|
## Requirements
|
|
309
110
|
|
|
310
111
|
- Node.js 24+
|
|
311
112
|
- @playwright/test ^1.55.0
|
|
312
113
|
|
|
313
|
-
## Contributing
|
|
314
|
-
|
|
315
|
-
This project is in active development. Currently focusing on:
|
|
316
|
-
- Polkadot JS Extension and Talisman support
|
|
317
|
-
- Core testing fixtures
|
|
318
|
-
- Additional wallet integrations
|
|
319
|
-
- Documentation improvements
|
|
320
|
-
|
|
321
114
|
## License
|
|
322
115
|
|
|
323
116
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -9923,38 +9923,45 @@ declare namespace test_d_exports {
|
|
|
9923
9923
|
export { BlobReporterOptions, Config, Expect, ExpectMatcherState, ExpectMatcherUtils, Fixtures, FullConfig, FullProject, HtmlReporterOptions, JUnitReporterOptions, JsonReporterOptions, ListReporterOptions, Location, MatcherHintOptions, MatcherReturnType, Metadata, PageAssertionsToHaveScreenshotOptions, PlaywrightTestArgs, PlaywrightTestConfig, PlaywrightTestOptions, PlaywrightTestProject, PlaywrightWorkerArgs, PlaywrightWorkerOptions, Project, ReporterDescription, ScreenshotMode, TestAnnotation, TestDetails, TestDetailsAnnotation, TestFixture, TestInfo, TestInfoError, TestStatus, TestStepInfo, TestType, TraceMode, VideoMode, WorkerFixture, WorkerInfo, _baseTest, test$2 as default, defineConfig, expect$1 as expect, mergeExpects, mergeTests, test$2 as test };
|
|
9924
9924
|
}
|
|
9925
9925
|
//#endregion
|
|
9926
|
-
//#region src/context-playwright/
|
|
9927
|
-
|
|
9928
|
-
interface WalletAccount {
|
|
9929
|
-
seed: string;
|
|
9930
|
-
name?: string;
|
|
9931
|
-
password?: string;
|
|
9932
|
-
}
|
|
9933
|
-
interface WalletConfig {
|
|
9934
|
-
type: WalletType;
|
|
9935
|
-
downloadUrl?: string;
|
|
9936
|
-
}
|
|
9937
|
-
interface BaseWalletInstance {
|
|
9926
|
+
//#region src/context-playwright/wallet-factory.d.ts
|
|
9927
|
+
declare function createPolkadotJsWallet(extensionId: string, context: BrowserContext): {
|
|
9938
9928
|
extensionId: string;
|
|
9929
|
+
type: "polkadot-js";
|
|
9939
9930
|
importMnemonic: (options: WalletAccount) => Promise<void>;
|
|
9940
|
-
authorize: (
|
|
9941
|
-
accountName?: string;
|
|
9942
|
-
}) => Promise<void>;
|
|
9931
|
+
authorize: () => Promise<void>;
|
|
9943
9932
|
approveTx: (options?: {
|
|
9944
9933
|
password?: string;
|
|
9945
9934
|
}) => Promise<void>;
|
|
9946
9935
|
rejectTx: () => Promise<void>;
|
|
9947
|
-
}
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
type: 'talisman';
|
|
9936
|
+
};
|
|
9937
|
+
declare function createTalismanWallet(extensionId: string, context: BrowserContext): {
|
|
9938
|
+
extensionId: string;
|
|
9939
|
+
type: "talisman";
|
|
9940
|
+
importPolkadotMnemonic: (options: WalletAccount) => Promise<void>;
|
|
9953
9941
|
importEthPrivateKey: (options: {
|
|
9954
9942
|
privateKey: string;
|
|
9955
9943
|
name?: string;
|
|
9956
9944
|
password?: string;
|
|
9957
9945
|
}) => Promise<void>;
|
|
9946
|
+
authorize: (options?: {
|
|
9947
|
+
accountName?: string;
|
|
9948
|
+
}) => Promise<void>;
|
|
9949
|
+
approveTx: () => Promise<void>;
|
|
9950
|
+
rejectTx: () => Promise<void>;
|
|
9951
|
+
};
|
|
9952
|
+
type PolkadotJsWalletInstance = ReturnType<typeof createPolkadotJsWallet>;
|
|
9953
|
+
type TalismanWalletInstance = ReturnType<typeof createTalismanWallet>;
|
|
9954
|
+
//#endregion
|
|
9955
|
+
//#region src/context-playwright/types.d.ts
|
|
9956
|
+
type WalletType = 'polkadot-js' | 'talisman';
|
|
9957
|
+
interface WalletAccount {
|
|
9958
|
+
seed: string;
|
|
9959
|
+
name?: string;
|
|
9960
|
+
password?: string;
|
|
9961
|
+
}
|
|
9962
|
+
interface WalletConfig {
|
|
9963
|
+
type: WalletType;
|
|
9964
|
+
downloadUrl?: string;
|
|
9958
9965
|
}
|
|
9959
9966
|
interface WalletTypeMap {
|
|
9960
9967
|
'polkadot-js': PolkadotJsWalletInstance;
|
package/dist/index.mjs
CHANGED
|
@@ -103,35 +103,63 @@ async function getTalismanExtensionPath() {
|
|
|
103
103
|
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
104
|
return extensionDir;
|
|
105
105
|
}
|
|
106
|
-
async function
|
|
106
|
+
async function findOnboardingPage(context, extensionId) {
|
|
107
|
+
const popupUrl = `chrome-extension://${extensionId}/dashboard.html`;
|
|
108
|
+
const newPage = await context.newPage();
|
|
109
|
+
await newPage.goto(popupUrl);
|
|
110
|
+
await newPage.waitForLoadState("domcontentloaded");
|
|
111
|
+
for (const p of context.pages()) if (p !== newPage && p.url().includes(`chrome-extension://${extensionId}/`)) await p.close();
|
|
112
|
+
return newPage;
|
|
113
|
+
}
|
|
114
|
+
async function completeOnboarding(extensionPage, password) {
|
|
115
|
+
await extensionPage.bringToFront();
|
|
116
|
+
await extensionPage.waitForLoadState("domcontentloaded");
|
|
117
|
+
if (await extensionPage.getByRole("button", { name: "Settings" }).isVisible()) {
|
|
118
|
+
await extensionPage.getByRole("button", { name: "Settings" }).click({ force: true });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await extensionPage.getByTestId("onboarding-get-started-button").click();
|
|
122
|
+
await extensionPage.getByRole("textbox", { name: "Enter password" }).fill(password);
|
|
123
|
+
await extensionPage.getByRole("textbox", { name: "Confirm password" }).fill(password);
|
|
124
|
+
await extensionPage.getByTestId("onboarding-password-confirm-button").click();
|
|
125
|
+
await extensionPage.getByRole("button", { name: "No thanks" }).click();
|
|
126
|
+
await extensionPage.getByTestId("onboarding-enter-talisman-button").click();
|
|
127
|
+
const extensionId = extensionPage.url().match(/chrome-extension:\/\/([^/]+)/)?.[1];
|
|
128
|
+
await extensionPage.goto(`chrome-extension://${extensionId}/dashboard.html#/settings/general`);
|
|
129
|
+
await extensionPage.waitForLoadState("domcontentloaded");
|
|
130
|
+
await extensionPage.getByRole("link", { name: "Security & Privacy" }).click();
|
|
131
|
+
await extensionPage.getByTestId("component-toggle-button").first().click();
|
|
132
|
+
}
|
|
133
|
+
async function importPolkadotMnemonic(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
|
|
107
134
|
const context = page.__extensionContext;
|
|
108
135
|
const extensionId = page.__extensionId;
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
const extensionPage = await findOnboardingPage(context, extensionId);
|
|
137
|
+
try {
|
|
138
|
+
await completeOnboarding(extensionPage, password);
|
|
139
|
+
await extensionPage.getByRole("link", { name: "Manage Accounts" }).click();
|
|
140
|
+
await extensionPage.getByRole("button", { name: "Get Started" }).click();
|
|
141
|
+
await extensionPage.getByRole("button", { name: "Add Account" }).click();
|
|
142
|
+
await extensionPage.getByRole("button", { name: "Import Import an existing" }).click();
|
|
143
|
+
await extensionPage.getByRole("button", { name: "Import via Recovery Phrase" }).click();
|
|
144
|
+
await extensionPage.getByRole("button", { name: "Polkadot Relay Chain, Asset" }).click();
|
|
145
|
+
await extensionPage.getByRole("textbox", { name: "Choose a name" }).fill(name);
|
|
146
|
+
await extensionPage.getByRole("textbox", { name: "Enter your 12 or 24 word" }).fill(seed);
|
|
147
|
+
await extensionPage.getByTestId("account-add-mnemonic-import-button").click();
|
|
148
|
+
await extensionPage.close();
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error("❌ Error during Talisman Polkadot account import:", error);
|
|
151
|
+
throw error;
|
|
123
152
|
}
|
|
124
|
-
|
|
153
|
+
}
|
|
154
|
+
async function importEthPrivateKey(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
|
|
155
|
+
const context = page.__extensionContext;
|
|
156
|
+
const extensionId = page.__extensionId;
|
|
157
|
+
const extensionPage = await findOnboardingPage(context, extensionId);
|
|
125
158
|
try {
|
|
126
|
-
await extensionPage
|
|
127
|
-
await extensionPage.
|
|
128
|
-
await extensionPage.
|
|
129
|
-
await extensionPage.getByRole("
|
|
130
|
-
await extensionPage.getByRole("textbox", { name: "Confirm password" }).fill(password);
|
|
131
|
-
await extensionPage.getByTestId("onboarding-password-confirm-button").click();
|
|
132
|
-
await extensionPage.getByRole("button", { name: "No thanks" }).click();
|
|
133
|
-
await extensionPage.getByTestId("onboarding-enter-talisman-button").click();
|
|
134
|
-
await extensionPage.getByRole("button", { name: "Add account Create or import" }).click();
|
|
159
|
+
await completeOnboarding(extensionPage, password);
|
|
160
|
+
await extensionPage.getByRole("link", { name: "Manage Accounts" }).click();
|
|
161
|
+
await extensionPage.getByRole("button", { name: "Get Started" }).click();
|
|
162
|
+
await extensionPage.getByRole("button", { name: "Add Account" }).click();
|
|
135
163
|
await extensionPage.getByRole("button", { name: "Import Import an existing" }).click();
|
|
136
164
|
await extensionPage.getByRole("button", { name: "Import via Private Key" }).click();
|
|
137
165
|
await extensionPage.getByRole("button", { name: "Select account platform" }).click();
|
|
@@ -141,16 +169,20 @@ async function importEthPrivateKey(page, { seed, name = "Test Account", password
|
|
|
141
169
|
await extensionPage.getByRole("button", { name: "Save" }).click();
|
|
142
170
|
await extensionPage.close();
|
|
143
171
|
} catch (error) {
|
|
144
|
-
console.error("❌ Error during Talisman account import:", error);
|
|
172
|
+
console.error("❌ Error during Talisman Ethereum account import:", error);
|
|
145
173
|
throw error;
|
|
146
174
|
}
|
|
147
175
|
}
|
|
148
176
|
async function authorizeTalisman(page, options = {}) {
|
|
149
|
-
const { accountName = "Test Account" } = options;
|
|
150
177
|
const context = page.__extensionContext;
|
|
151
178
|
const extensionId = page.__extensionId;
|
|
179
|
+
const { accountName = "Test Account" } = options;
|
|
152
180
|
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
153
|
-
await extensionPopup.
|
|
181
|
+
await extensionPopup.waitForLoadState("domcontentloaded");
|
|
182
|
+
const accountButton = extensionPopup.getByRole("button", { name: accountName });
|
|
183
|
+
await accountButton.waitFor({ state: "visible" });
|
|
184
|
+
await accountButton.scrollIntoViewIfNeeded();
|
|
185
|
+
await accountButton.click({ force: true });
|
|
154
186
|
await extensionPopup.getByTestId("connection-connect-button").click();
|
|
155
187
|
try {
|
|
156
188
|
await (await findExtensionPopup(context, extensionId)).getByRole("button", { name: "Approve" }).click();
|
|
@@ -160,23 +192,16 @@ async function approveTalismanTx(page) {
|
|
|
160
192
|
const context = page.__extensionContext;
|
|
161
193
|
const extensionId = page.__extensionId;
|
|
162
194
|
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
163
|
-
|
|
164
|
-
await extensionPopup.getByRole("button", { name: "Yes" }).click();
|
|
165
|
-
} catch {
|
|
166
|
-
console.log("No another popup found, skipping");
|
|
167
|
-
}
|
|
195
|
+
if (await extensionPopup.getByRole("button", { name: "Yes" }).isVisible()) await extensionPopup.getByRole("button", { name: "Yes" }).click();
|
|
168
196
|
await extensionPopup.getByRole("button", { name: "Approve" }).click();
|
|
169
197
|
}
|
|
170
198
|
async function rejectTalismanTx(page) {
|
|
171
199
|
const context = page.__extensionContext;
|
|
172
200
|
const extensionId = page.__extensionId;
|
|
173
|
-
|
|
201
|
+
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
202
|
+
await extensionPopup.getByTestId("connection-reject-button").or(extensionPopup.getByRole("button", { name: "Cancel" })).click();
|
|
174
203
|
}
|
|
175
204
|
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/context-playwright/types.ts
|
|
178
|
-
const WALLET_TYPES = ["polkadot-js", "talisman"];
|
|
179
|
-
|
|
180
205
|
//#endregion
|
|
181
206
|
//#region src/context-playwright/wallet-factory.ts
|
|
182
207
|
function createExtendedPage(page, context, extensionId) {
|
|
@@ -185,80 +210,58 @@ function createExtendedPage(page, context, extensionId) {
|
|
|
185
210
|
extPage.__extensionId = extensionId;
|
|
186
211
|
return extPage;
|
|
187
212
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
const baseInstance = {
|
|
213
|
+
function createPolkadotJsWallet(extensionId, context) {
|
|
214
|
+
return {
|
|
191
215
|
extensionId,
|
|
216
|
+
type: "polkadot-js",
|
|
192
217
|
importMnemonic: async (options) => {
|
|
218
|
+
await importPolkadotJSAccount(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId), options);
|
|
219
|
+
},
|
|
220
|
+
authorize: async () => {
|
|
221
|
+
await authorizePolkadotJS(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
222
|
+
},
|
|
223
|
+
approveTx: async (options = {}) => {
|
|
224
|
+
await approvePolkadotJSTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId), options);
|
|
225
|
+
},
|
|
226
|
+
rejectTx: async () => {
|
|
227
|
+
await rejectPolkadotJSTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function createTalismanWallet(extensionId, context) {
|
|
232
|
+
let importedAccountName;
|
|
233
|
+
return {
|
|
234
|
+
extensionId,
|
|
235
|
+
type: "talisman",
|
|
236
|
+
importPolkadotMnemonic: async (options) => {
|
|
193
237
|
const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
|
|
194
238
|
importedAccountName = options.name || "Test Account";
|
|
195
|
-
|
|
196
|
-
case "polkadot-js":
|
|
197
|
-
await importPolkadotJSAccount(extPage, options);
|
|
198
|
-
break;
|
|
199
|
-
case "talisman": throw new Error("Talisman importMnemonic is not yet implemented.");
|
|
200
|
-
default: throw new Error(`Unsupported wallet type: ${walletType}`);
|
|
201
|
-
}
|
|
239
|
+
await importPolkadotMnemonic(extPage, options);
|
|
202
240
|
},
|
|
203
|
-
|
|
241
|
+
importEthPrivateKey: async (options) => {
|
|
204
242
|
const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
await authorizeTalisman(extPage, { accountName });
|
|
212
|
-
break;
|
|
213
|
-
default: throw new Error(`Unsupported wallet type: ${walletType}`);
|
|
214
|
-
}
|
|
243
|
+
importedAccountName = options.name || "Test Account";
|
|
244
|
+
await importEthPrivateKey(extPage, {
|
|
245
|
+
seed: options.privateKey,
|
|
246
|
+
name: options.name,
|
|
247
|
+
password: options.password
|
|
248
|
+
});
|
|
215
249
|
},
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
break;
|
|
222
|
-
case "talisman":
|
|
223
|
-
await approveTalismanTx(extPage);
|
|
224
|
-
break;
|
|
225
|
-
default: throw new Error(`Unsupported wallet type: ${walletType}`);
|
|
226
|
-
}
|
|
250
|
+
authorize: async (options = {}) => {
|
|
251
|
+
await authorizeTalisman(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId), { accountName: options.accountName || importedAccountName });
|
|
252
|
+
},
|
|
253
|
+
approveTx: async () => {
|
|
254
|
+
await approveTalismanTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
227
255
|
},
|
|
228
256
|
rejectTx: async () => {
|
|
229
|
-
|
|
230
|
-
switch (walletType) {
|
|
231
|
-
case "polkadot-js":
|
|
232
|
-
await rejectPolkadotJSTx(extPage);
|
|
233
|
-
break;
|
|
234
|
-
case "talisman":
|
|
235
|
-
await rejectTalismanTx(extPage);
|
|
236
|
-
break;
|
|
237
|
-
default: throw new Error(`Unsupported wallet type: ${walletType}`);
|
|
238
|
-
}
|
|
257
|
+
await rejectTalismanTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
239
258
|
}
|
|
240
259
|
};
|
|
241
|
-
switch (walletType) {
|
|
242
|
-
case "polkadot-js": return {
|
|
243
|
-
...baseInstance,
|
|
244
|
-
type: "polkadot-js"
|
|
245
|
-
};
|
|
246
|
-
case "talisman": return {
|
|
247
|
-
...baseInstance,
|
|
248
|
-
type: "talisman",
|
|
249
|
-
importEthPrivateKey: async (options) => {
|
|
250
|
-
const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
|
|
251
|
-
importedAccountName = options.name || "Test Account";
|
|
252
|
-
await importEthPrivateKey(extPage, {
|
|
253
|
-
seed: options.privateKey,
|
|
254
|
-
name: options.name,
|
|
255
|
-
password: options.password
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
default: throw new Error(`Unsupported wallet type: ${walletType}`);
|
|
260
|
-
}
|
|
261
260
|
}
|
|
261
|
+
const walletFactories = {
|
|
262
|
+
"polkadot-js": createPolkadotJsWallet,
|
|
263
|
+
"talisman": createTalismanWallet
|
|
264
|
+
};
|
|
262
265
|
|
|
263
266
|
//#endregion
|
|
264
267
|
//#region src/context-playwright/index.ts
|
|
@@ -306,7 +309,10 @@ function createWalletTest(options = {}) {
|
|
|
306
309
|
},
|
|
307
310
|
wallets: async ({ walletContext, walletExtensionIds }, use) => {
|
|
308
311
|
const walletMap = {};
|
|
309
|
-
for (const [walletType, extensionId] of walletExtensionIds)
|
|
312
|
+
for (const [walletType, extensionId] of walletExtensionIds) {
|
|
313
|
+
const factory = walletFactories[walletType];
|
|
314
|
+
if (factory) walletMap[walletType] = factory(extensionId, walletContext);
|
|
315
|
+
}
|
|
310
316
|
await use(walletMap);
|
|
311
317
|
}
|
|
312
318
|
});
|
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.12",
|
|
5
5
|
"description": "End-to-end testing library for Polkadot wallet interactions",
|
|
6
6
|
"author": "Avalix Labs",
|
|
7
7
|
"license": "MIT",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"lint": "eslint --fix .",
|
|
45
45
|
"prepublishOnly": "npm run build",
|
|
46
46
|
"download-extensions": "rm -rf .chroma && tsx scripts/download-extensions.ts",
|
|
47
|
-
"test": "playwright test
|
|
47
|
+
"test": "playwright test",
|
|
48
|
+
"test:ui": "playwright test --ui",
|
|
48
49
|
"test:unit": "vitest",
|
|
49
50
|
"test:unit:run": "vitest run",
|
|
50
51
|
"test:unit:coverage": "vitest run --coverage"
|
|
@@ -19,15 +19,15 @@ async function clearChromaDir(): Promise<void> {
|
|
|
19
19
|
const chromaDir = path.resolve(process.cwd(), '.chroma')
|
|
20
20
|
|
|
21
21
|
if (fs.existsSync(chromaDir)) {
|
|
22
|
-
console.log('🗑️
|
|
22
|
+
console.log('🗑️ Clearing existing .chroma directory...')
|
|
23
23
|
await fs.promises.rm(chromaDir, { recursive: true, force: true })
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async function main() {
|
|
28
28
|
const version = await getVersion()
|
|
29
|
-
console.log(
|
|
30
|
-
console.log('🚀 Downloading wallet extensions
|
|
29
|
+
console.log(`\n🎨 Chroma v${version}`)
|
|
30
|
+
console.log('🚀 Downloading wallet extensions...')
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
33
|
// Clear existing .chroma directory
|
|
@@ -4,16 +4,13 @@ import type {
|
|
|
4
4
|
ExtendedPage,
|
|
5
5
|
WalletConfig,
|
|
6
6
|
WalletFixtures,
|
|
7
|
-
WalletInstance,
|
|
8
7
|
Wallets,
|
|
9
|
-
WalletType,
|
|
10
8
|
WalletWorkerFixtures,
|
|
11
9
|
} from './types.js'
|
|
12
10
|
import { test as base, chromium } from '@playwright/test'
|
|
13
11
|
import { getPolkadotJSExtensionPath } from '../wallets/polkadot-js.js'
|
|
14
12
|
import { getTalismanExtensionPath } from '../wallets/talisman.js'
|
|
15
|
-
import {
|
|
16
|
-
import { createWalletInstance } from './wallet-factory.js'
|
|
13
|
+
import { walletFactories } from './wallet-factory.js'
|
|
17
14
|
|
|
18
15
|
// Helper function to get extension path for a wallet config
|
|
19
16
|
async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
|
|
@@ -117,17 +114,17 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
|
|
|
117
114
|
|
|
118
115
|
// Wallet instances for each configured wallet
|
|
119
116
|
wallets: async ({ walletContext, walletExtensionIds }, use) => {
|
|
120
|
-
const walletMap
|
|
117
|
+
const walletMap = {} as ExpectedWallets
|
|
121
118
|
|
|
122
119
|
// Create wallet instance for each configured wallet
|
|
123
120
|
for (const [walletType, extensionId] of walletExtensionIds) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
const factory = walletFactories[walletType as keyof typeof walletFactories]
|
|
122
|
+
if (factory) {
|
|
123
|
+
walletMap[walletType as keyof ExpectedWallets] = factory(extensionId, walletContext) as ExpectedWallets[keyof ExpectedWallets]
|
|
127
124
|
}
|
|
128
125
|
}
|
|
129
126
|
|
|
130
|
-
await use(walletMap
|
|
127
|
+
await use(walletMap)
|
|
131
128
|
},
|
|
132
129
|
})
|
|
133
130
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { BrowserContext, Page } from '@playwright/test'
|
|
2
|
+
import type {
|
|
3
|
+
PolkadotJsWalletInstance,
|
|
4
|
+
TalismanWalletInstance,
|
|
5
|
+
WalletInstance,
|
|
6
|
+
} from './wallet-factory.js'
|
|
7
|
+
|
|
8
|
+
// Re-export wallet instance types
|
|
9
|
+
export type { PolkadotJsWalletInstance, TalismanWalletInstance, WalletInstance }
|
|
2
10
|
|
|
3
11
|
// Wallet types - single source of truth
|
|
4
12
|
export type WalletType = 'polkadot-js' | 'talisman'
|
|
5
13
|
|
|
6
|
-
// Available wallet types as constant array
|
|
7
|
-
export const WALLET_TYPES: readonly WalletType[] = ['polkadot-js', 'talisman'] as const
|
|
8
|
-
|
|
9
14
|
// Wallet account configuration
|
|
10
15
|
export interface WalletAccount {
|
|
11
16
|
seed: string
|
|
@@ -19,29 +24,6 @@ export interface WalletConfig {
|
|
|
19
24
|
downloadUrl?: string
|
|
20
25
|
}
|
|
21
26
|
|
|
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
27
|
// Map wallet type to its instance
|
|
46
28
|
export interface WalletTypeMap {
|
|
47
29
|
'polkadot-js': PolkadotJsWalletInstance
|
|
@@ -1,11 +1,5 @@
|
|
|
1
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'
|
|
2
|
+
import type { WalletAccount } from './types.js'
|
|
9
3
|
import {
|
|
10
4
|
approvePolkadotJSTx,
|
|
11
5
|
authorizePolkadotJS,
|
|
@@ -16,6 +10,7 @@ import {
|
|
|
16
10
|
approveTalismanTx,
|
|
17
11
|
authorizeTalisman,
|
|
18
12
|
importEthPrivateKey,
|
|
13
|
+
importPolkadotMnemonic,
|
|
19
14
|
rejectTalismanTx,
|
|
20
15
|
} from '../wallets/talisman.js'
|
|
21
16
|
|
|
@@ -27,114 +22,83 @@ function createExtendedPage(page: Page, context: BrowserContext, extensionId: st
|
|
|
27
22
|
return extPage
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
//
|
|
31
|
-
export function
|
|
32
|
-
|
|
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 = {
|
|
25
|
+
// Factory function for Polkadot-JS wallet
|
|
26
|
+
export function createPolkadotJsWallet(extensionId: string, context: BrowserContext) {
|
|
27
|
+
return {
|
|
41
28
|
extensionId,
|
|
29
|
+
type: 'polkadot-js' as const,
|
|
42
30
|
importMnemonic: async (options: WalletAccount) => {
|
|
43
31
|
const page = context.pages()[0] || await context.newPage()
|
|
44
32
|
const extPage = createExtendedPage(page, context, extensionId)
|
|
33
|
+
await importPolkadotJSAccount(extPage, options)
|
|
34
|
+
},
|
|
35
|
+
authorize: async () => {
|
|
36
|
+
const page = context.pages()[0] || await context.newPage()
|
|
37
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
38
|
+
await authorizePolkadotJS(extPage)
|
|
39
|
+
},
|
|
40
|
+
approveTx: async (options: { password?: string } = {}) => {
|
|
41
|
+
const page = context.pages()[0] || await context.newPage()
|
|
42
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
43
|
+
await approvePolkadotJSTx(extPage, options)
|
|
44
|
+
},
|
|
45
|
+
rejectTx: async () => {
|
|
46
|
+
const page = context.pages()[0] || await context.newPage()
|
|
47
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
48
|
+
await rejectPolkadotJSTx(extPage)
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
// Factory function for Talisman wallet
|
|
54
|
+
export function createTalismanWallet(extensionId: string, context: BrowserContext) {
|
|
55
|
+
let importedAccountName: string | undefined
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
return {
|
|
58
|
+
extensionId,
|
|
59
|
+
type: 'talisman' as const,
|
|
60
|
+
importPolkadotMnemonic: async (options: WalletAccount) => {
|
|
61
|
+
const page = context.pages()[0] || await context.newPage()
|
|
62
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
63
|
+
importedAccountName = options.name || 'Test Account'
|
|
64
|
+
await importPolkadotMnemonic(extPage, options)
|
|
65
|
+
},
|
|
66
|
+
importEthPrivateKey: async (options: { privateKey: string, name?: string, password?: string }) => {
|
|
67
|
+
const page = context.pages()[0] || await context.newPage()
|
|
68
|
+
const extPage = createExtendedPage(page, context, extensionId)
|
|
69
|
+
importedAccountName = options.name || 'Test Account'
|
|
70
|
+
await importEthPrivateKey(extPage, {
|
|
71
|
+
seed: options.privateKey,
|
|
72
|
+
name: options.name,
|
|
73
|
+
password: options.password,
|
|
74
|
+
})
|
|
58
75
|
},
|
|
59
76
|
authorize: async (options: { accountName?: string } = {}) => {
|
|
60
77
|
const page = context.pages()[0] || await context.newPage()
|
|
61
78
|
const extPage = createExtendedPage(page, context, extensionId)
|
|
62
|
-
|
|
63
|
-
// Use provided account name or fall back to the imported one
|
|
64
79
|
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
|
-
}
|
|
80
|
+
await authorizeTalisman(extPage, { accountName })
|
|
76
81
|
},
|
|
77
|
-
approveTx: async (
|
|
82
|
+
approveTx: async () => {
|
|
78
83
|
const page = context.pages()[0] || await context.newPage()
|
|
79
84
|
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
|
-
}
|
|
85
|
+
await approveTalismanTx(extPage)
|
|
91
86
|
},
|
|
92
87
|
rejectTx: async () => {
|
|
93
88
|
const page = context.pages()[0] || await context.newPage()
|
|
94
89
|
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
|
-
}
|
|
90
|
+
await rejectTalismanTx(extPage)
|
|
106
91
|
},
|
|
107
92
|
}
|
|
93
|
+
}
|
|
108
94
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
}
|
|
95
|
+
// Wallet factories map - auto-inferred types
|
|
96
|
+
export const walletFactories = {
|
|
97
|
+
'polkadot-js': createPolkadotJsWallet,
|
|
98
|
+
'talisman': createTalismanWallet,
|
|
140
99
|
}
|
|
100
|
+
|
|
101
|
+
// Auto-inferred types from factory functions
|
|
102
|
+
export type PolkadotJsWalletInstance = ReturnType<typeof createPolkadotJsWallet>
|
|
103
|
+
export type TalismanWalletInstance = ReturnType<typeof createTalismanWallet>
|
|
104
|
+
export type WalletInstance = PolkadotJsWalletInstance | TalismanWalletInstance
|
|
@@ -34,7 +34,7 @@ export async function downloadAndExtractExtension(options: DownloadExtensionOpti
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
|
-
console.log(
|
|
37
|
+
console.log(`\n📥 Downloading ${extensionName}...`)
|
|
38
38
|
|
|
39
39
|
// Download the ZIP file
|
|
40
40
|
const response = await fetch(downloadUrl)
|
package/src/wallets/talisman.ts
CHANGED
|
@@ -54,65 +54,114 @@ export async function getTalismanExtensionPath(): Promise<string> {
|
|
|
54
54
|
return extensionDir
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
|
|
57
|
+
// Helper function to find Talisman onboarding page
|
|
58
|
+
async function findOnboardingPage(
|
|
59
|
+
context: BrowserContext,
|
|
60
|
+
extensionId: string,
|
|
61
|
+
): Promise<Page> {
|
|
62
|
+
// Open new dashboard page
|
|
63
|
+
const popupUrl = `chrome-extension://${extensionId}/dashboard.html`
|
|
64
|
+
const newPage = await context.newPage()
|
|
65
|
+
await newPage.goto(popupUrl)
|
|
66
|
+
await newPage.waitForLoadState('domcontentloaded')
|
|
67
|
+
|
|
68
|
+
// Close any other extension tabs that may have been opened automatically
|
|
69
|
+
for (const p of context.pages()) {
|
|
70
|
+
if (p !== newPage && p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
71
|
+
await p.close()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return newPage
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Helper function to complete Talisman onboarding flow
|
|
79
|
+
async function completeOnboarding(
|
|
80
|
+
extensionPage: Page,
|
|
81
|
+
password: string,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
// Bring the onboarding page to front
|
|
84
|
+
await extensionPage.bringToFront()
|
|
85
|
+
|
|
86
|
+
// Wait for the page to load and become interactive
|
|
87
|
+
await extensionPage.waitForLoadState('domcontentloaded')
|
|
88
|
+
|
|
89
|
+
if (await extensionPage.getByRole('button', { name: 'Settings' }).isVisible()) {
|
|
90
|
+
await extensionPage.getByRole('button', { name: 'Settings' }).click({ force: true })
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Click the get started button
|
|
95
|
+
await extensionPage.getByTestId('onboarding-get-started-button').click()
|
|
96
|
+
|
|
97
|
+
// Fill the password
|
|
98
|
+
await extensionPage.getByRole('textbox', { name: 'Enter password' }).fill(password)
|
|
99
|
+
await extensionPage.getByRole('textbox', { name: 'Confirm password' }).fill(password)
|
|
100
|
+
await extensionPage.getByTestId('onboarding-password-confirm-button').click()
|
|
101
|
+
|
|
102
|
+
// Click the no thanks button
|
|
103
|
+
await extensionPage.getByRole('button', { name: 'No thanks' }).click()
|
|
104
|
+
await extensionPage.getByTestId('onboarding-enter-talisman-button').click()
|
|
105
|
+
|
|
106
|
+
// Navigate directly to settings/general page
|
|
107
|
+
const extensionId = extensionPage.url().match(/chrome-extension:\/\/([^/]+)/)?.[1]
|
|
108
|
+
await extensionPage.goto(`chrome-extension://${extensionId}/dashboard.html#/settings/general`)
|
|
109
|
+
await extensionPage.waitForLoadState('domcontentloaded')
|
|
110
|
+
await extensionPage.getByRole('link', { name: 'Security & Privacy' }).click()
|
|
111
|
+
|
|
112
|
+
// Toggle the risk scan setting
|
|
113
|
+
await extensionPage.getByTestId('component-toggle-button').first().click()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Talisman specific Polkadot mnemonic import implementation
|
|
117
|
+
export async function importPolkadotMnemonic(
|
|
59
118
|
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
60
119
|
{ seed, name = 'Test Account', password = 'h3llop0lkadot!' }: WalletAccount,
|
|
61
120
|
): Promise<void> {
|
|
62
121
|
const context = page.__extensionContext
|
|
63
122
|
const extensionId = page.__extensionId
|
|
64
123
|
|
|
65
|
-
|
|
66
|
-
const maxAttempts = 20
|
|
67
|
-
const retryDelay = 500
|
|
68
|
-
let extensionPage: Page | null = null
|
|
69
|
-
|
|
70
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
71
|
-
const pages = context.pages()
|
|
124
|
+
const extensionPage = await findOnboardingPage(context, extensionId)
|
|
72
125
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (url.includes('onboarding.html') || url.includes(`chrome-extension://${extensionId}/`)) {
|
|
76
|
-
extensionPage = p
|
|
77
|
-
break
|
|
78
|
-
}
|
|
79
|
-
}
|
|
126
|
+
try {
|
|
127
|
+
await completeOnboarding(extensionPage, password!)
|
|
80
128
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
129
|
+
// Import Polkadot account via Recovery Phrase
|
|
130
|
+
await extensionPage.getByRole('link', { name: 'Manage Accounts' }).click()
|
|
131
|
+
await extensionPage.getByRole('button', { name: 'Get Started' }).click()
|
|
132
|
+
await extensionPage.getByRole('button', { name: 'Add Account' }).click()
|
|
133
|
+
await extensionPage.getByRole('button', { name: 'Import Import an existing' }).click()
|
|
134
|
+
await extensionPage.getByRole('button', { name: 'Import via Recovery Phrase' }).click()
|
|
135
|
+
await extensionPage.getByRole('button', { name: 'Polkadot Relay Chain, Asset' }).click()
|
|
136
|
+
await extensionPage.getByRole('textbox', { name: 'Choose a name' }).fill(name!)
|
|
137
|
+
await extensionPage.getByRole('textbox', { name: 'Enter your 12 or 24 word' }).fill(seed!)
|
|
138
|
+
await extensionPage.getByTestId('account-add-mnemonic-import-button').click()
|
|
84
139
|
|
|
85
|
-
|
|
86
|
-
if (attempt < maxAttempts - 1) {
|
|
87
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay))
|
|
88
|
-
}
|
|
140
|
+
await extensionPage.close()
|
|
89
141
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
throw
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error('❌ Error during Talisman Polkadot account import:', error)
|
|
144
|
+
throw error
|
|
93
145
|
}
|
|
146
|
+
}
|
|
94
147
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Click the get started button
|
|
103
|
-
await extensionPage.getByTestId('onboarding-get-started-button').click()
|
|
148
|
+
// Talisman specific Ethereum private key import implementation
|
|
149
|
+
export async function importEthPrivateKey(
|
|
150
|
+
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
151
|
+
{ seed, name = 'Test Account', password = 'h3llop0lkadot!' }: WalletAccount,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
const context = page.__extensionContext
|
|
154
|
+
const extensionId = page.__extensionId
|
|
104
155
|
|
|
105
|
-
|
|
106
|
-
await extensionPage.getByRole('textbox', { name: 'Enter password' }).fill(password!)
|
|
107
|
-
await extensionPage.getByRole('textbox', { name: 'Confirm password' }).fill(password!)
|
|
108
|
-
await extensionPage.getByTestId('onboarding-password-confirm-button').click()
|
|
156
|
+
const extensionPage = await findOnboardingPage(context, extensionId)
|
|
109
157
|
|
|
110
|
-
|
|
111
|
-
await extensionPage
|
|
112
|
-
await extensionPage.getByTestId('onboarding-enter-talisman-button').click()
|
|
158
|
+
try {
|
|
159
|
+
await completeOnboarding(extensionPage, password!)
|
|
113
160
|
|
|
114
|
-
// Import Ethereum account
|
|
115
|
-
await extensionPage.getByRole('
|
|
161
|
+
// Import Ethereum account via Private Key
|
|
162
|
+
await extensionPage.getByRole('link', { name: 'Manage Accounts' }).click()
|
|
163
|
+
await extensionPage.getByRole('button', { name: 'Get Started' }).click()
|
|
164
|
+
await extensionPage.getByRole('button', { name: 'Add Account' }).click()
|
|
116
165
|
await extensionPage.getByRole('button', { name: 'Import Import an existing' }).click()
|
|
117
166
|
await extensionPage.getByRole('button', { name: 'Import via Private Key' }).click()
|
|
118
167
|
await extensionPage.getByRole('button', { name: 'Select account platform' }).click()
|
|
@@ -124,7 +173,7 @@ export async function importEthPrivateKey(
|
|
|
124
173
|
await extensionPage.close()
|
|
125
174
|
}
|
|
126
175
|
catch (error) {
|
|
127
|
-
console.error('❌ Error during Talisman account import:', error)
|
|
176
|
+
console.error('❌ Error during Talisman Ethereum account import:', error)
|
|
128
177
|
throw error
|
|
129
178
|
}
|
|
130
179
|
}
|
|
@@ -134,14 +183,18 @@ export async function authorizeTalisman(
|
|
|
134
183
|
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
135
184
|
options: { accountName?: string } = {},
|
|
136
185
|
): Promise<void> {
|
|
137
|
-
const { accountName = 'Test Account' } = options
|
|
138
186
|
const context = page.__extensionContext
|
|
139
187
|
const extensionId = page.__extensionId
|
|
188
|
+
const { accountName = 'Test Account' } = options
|
|
140
189
|
|
|
141
190
|
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
191
|
+
await extensionPopup.waitForLoadState('domcontentloaded')
|
|
142
192
|
|
|
143
193
|
// Authorize Talisman account
|
|
144
|
-
|
|
194
|
+
const accountButton = extensionPopup.getByRole('button', { name: accountName })
|
|
195
|
+
await accountButton.waitFor({ state: 'visible' })
|
|
196
|
+
await accountButton.scrollIntoViewIfNeeded()
|
|
197
|
+
await accountButton.click({ force: true })
|
|
145
198
|
await extensionPopup.getByTestId('connection-connect-button').click()
|
|
146
199
|
|
|
147
200
|
try {
|
|
@@ -161,12 +214,9 @@ export async function approveTalismanTx(
|
|
|
161
214
|
|
|
162
215
|
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
163
216
|
|
|
164
|
-
|
|
217
|
+
if (await extensionPopup.getByRole('button', { name: 'Yes' }).isVisible()) {
|
|
165
218
|
await extensionPopup.getByRole('button', { name: 'Yes' }).click()
|
|
166
219
|
}
|
|
167
|
-
catch {
|
|
168
|
-
console.log('No another popup found, skipping')
|
|
169
|
-
}
|
|
170
220
|
|
|
171
221
|
await extensionPopup.getByRole('button', { name: 'Approve' }).click()
|
|
172
222
|
}
|
|
@@ -179,5 +229,9 @@ export async function rejectTalismanTx(
|
|
|
179
229
|
const extensionId = page.__extensionId
|
|
180
230
|
|
|
181
231
|
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
182
|
-
|
|
232
|
+
|
|
233
|
+
const rejectButton = extensionPopup.getByTestId('connection-reject-button')
|
|
234
|
+
.or(extensionPopup.getByRole('button', { name: 'Cancel' }))
|
|
235
|
+
|
|
236
|
+
await rejectButton.click()
|
|
183
237
|
}
|