@avalix/chroma 0.0.12 → 0.0.13
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 +14 -8
- package/dist/index.mjs +16 -0
- package/package.json +3 -2
- package/src/context-playwright/index.test.ts +148 -0
- package/src/context-playwright/index.ts +11 -1
- package/src/context-playwright/wallet-factory.test.ts +106 -0
- package/src/context-playwright/wallet-factory.ts +14 -2
- package/src/index.test.ts +57 -0
- package/src/wallets/polkadot-js.test.ts +97 -0
- package/src/wallets/polkadot-js.ts +15 -1
- package/src/wallets/talisman.test.ts +97 -0
- package/src/wallets/talisman.ts +15 -1
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
End-to-end testing library for Polkadot wallet interactions using Playwright.
|
|
4
4
|
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
We highly recommend you take a look at the [Chroma documentation](https://chroma-docs.up.railway.app/docs) to level up. It's a great resource for learning more about the library. It covers everything from getting started to advanced topics like CI/CD integration and Docker setup.
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -82,14 +86,9 @@ test('multi-wallet test', async ({ page, wallets }) => {
|
|
|
82
86
|
})
|
|
83
87
|
```
|
|
84
88
|
|
|
85
|
-
##
|
|
86
|
-
|
|
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)
|
|
89
|
+
## Supported Wallets & Chains
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
### Supported Chains
|
|
93
92
|
|
|
94
93
|
| Chain | Status |
|
|
95
94
|
|-------|--------|
|
|
@@ -97,7 +96,7 @@ test('multi-wallet test', async ({ page, wallets }) => {
|
|
|
97
96
|
| Ethereum | ✅ Supported |
|
|
98
97
|
| Solana | ⏳ Planned |
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
### Supported Wallets
|
|
101
100
|
|
|
102
101
|
| Wallet | Status | Version |
|
|
103
102
|
|--------|--------|---------|
|
|
@@ -106,6 +105,13 @@ test('multi-wallet test', async ({ page, wallets }) => {
|
|
|
106
105
|
| SubWallet | ⏳ Planned | - |
|
|
107
106
|
| MetaMask | ⏳ Planned | - |
|
|
108
107
|
|
|
108
|
+
## Features
|
|
109
|
+
|
|
110
|
+
- **Easy Extension Setup** - Download wallet extensions with a single command
|
|
111
|
+
- **Multi-Wallet Support** - Test with multiple wallet extensions simultaneously
|
|
112
|
+
- **TypeScript Support** - Full type safety and autocomplete
|
|
113
|
+
- **VS Code Integration** - Works with [Playwright Test for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
|
|
114
|
+
|
|
109
115
|
## Requirements
|
|
110
116
|
|
|
111
117
|
- Node.js 24+
|
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ const POLKADOT_JS_CONFIG = {
|
|
|
9
9
|
downloadUrl: `https://github.com/polkadot-js/extension/releases/download/v${VERSION$1}/master-chrome-build.zip`,
|
|
10
10
|
extensionName: `polkadot-extension-${VERSION$1}`
|
|
11
11
|
};
|
|
12
|
+
/* c8 ignore start */
|
|
12
13
|
async function findExtensionPopup$1(context, extensionId) {
|
|
13
14
|
const maxAttempts = 10;
|
|
14
15
|
const retryDelay = 500;
|
|
@@ -22,12 +23,14 @@ async function findExtensionPopup$1(context, extensionId) {
|
|
|
22
23
|
}
|
|
23
24
|
throw new Error(`Extension popup not found for ID: ${extensionId}`);
|
|
24
25
|
}
|
|
26
|
+
/* c8 ignore stop */
|
|
25
27
|
async function getPolkadotJSExtensionPath() {
|
|
26
28
|
const extensionsDir = path.resolve(process.cwd(), ".chroma");
|
|
27
29
|
const extensionDir = path.join(extensionsDir, POLKADOT_JS_CONFIG.extensionName);
|
|
28
30
|
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`);
|
|
29
31
|
return extensionDir;
|
|
30
32
|
}
|
|
33
|
+
/* c8 ignore start */
|
|
31
34
|
async function importPolkadotJSAccount(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
|
|
32
35
|
const context = page.__extensionContext;
|
|
33
36
|
const extensionPopupUrl = `chrome-extension://${page.__extensionId}/index.html`;
|
|
@@ -72,6 +75,7 @@ async function rejectPolkadotJSTx(page) {
|
|
|
72
75
|
const extensionId = page.__extensionId;
|
|
73
76
|
await (await findExtensionPopup$1(context, extensionId)).getByRole("link", { name: "Cancel" }).click();
|
|
74
77
|
}
|
|
78
|
+
/* c8 ignore stop */
|
|
75
79
|
|
|
76
80
|
//#endregion
|
|
77
81
|
//#region src/wallets/talisman.ts
|
|
@@ -80,6 +84,7 @@ const TALISMAN_CONFIG = {
|
|
|
80
84
|
downloadUrl: `https://github.com/avalix-labs/polkadot-wallets/raw/refs/heads/main/talisman/talisman-${VERSION}.zip`,
|
|
81
85
|
extensionName: `talisman-extension-${VERSION}`
|
|
82
86
|
};
|
|
87
|
+
/* c8 ignore start */
|
|
83
88
|
async function findExtensionPopup(context, extensionId) {
|
|
84
89
|
const maxAttempts = 10;
|
|
85
90
|
const retryDelay = 500;
|
|
@@ -97,12 +102,14 @@ async function findExtensionPopup(context, extensionId) {
|
|
|
97
102
|
}
|
|
98
103
|
throw new Error(`Extension popup not found for ID: ${extensionId}`);
|
|
99
104
|
}
|
|
105
|
+
/* c8 ignore stop */
|
|
100
106
|
async function getTalismanExtensionPath() {
|
|
101
107
|
const extensionsDir = path.resolve(process.cwd(), ".chroma");
|
|
102
108
|
const extensionDir = path.join(extensionsDir, TALISMAN_CONFIG.extensionName);
|
|
103
109
|
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
110
|
return extensionDir;
|
|
105
111
|
}
|
|
112
|
+
/* c8 ignore start */
|
|
106
113
|
async function findOnboardingPage(context, extensionId) {
|
|
107
114
|
const popupUrl = `chrome-extension://${extensionId}/dashboard.html`;
|
|
108
115
|
const newPage = await context.newPage();
|
|
@@ -201,15 +208,19 @@ async function rejectTalismanTx(page) {
|
|
|
201
208
|
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
202
209
|
await extensionPopup.getByTestId("connection-reject-button").or(extensionPopup.getByRole("button", { name: "Cancel" })).click();
|
|
203
210
|
}
|
|
211
|
+
/* c8 ignore stop */
|
|
204
212
|
|
|
205
213
|
//#endregion
|
|
206
214
|
//#region src/context-playwright/wallet-factory.ts
|
|
215
|
+
/* c8 ignore start */
|
|
207
216
|
function createExtendedPage(page, context, extensionId) {
|
|
208
217
|
const extPage = page;
|
|
209
218
|
extPage.__extensionContext = context;
|
|
210
219
|
extPage.__extensionId = extensionId;
|
|
211
220
|
return extPage;
|
|
212
221
|
}
|
|
222
|
+
/* c8 ignore stop */
|
|
223
|
+
/* c8 ignore start */
|
|
213
224
|
function createPolkadotJsWallet(extensionId, context) {
|
|
214
225
|
return {
|
|
215
226
|
extensionId,
|
|
@@ -228,6 +239,8 @@ function createPolkadotJsWallet(extensionId, context) {
|
|
|
228
239
|
}
|
|
229
240
|
};
|
|
230
241
|
}
|
|
242
|
+
/* c8 ignore stop */
|
|
243
|
+
/* c8 ignore start */
|
|
231
244
|
function createTalismanWallet(extensionId, context) {
|
|
232
245
|
let importedAccountName;
|
|
233
246
|
return {
|
|
@@ -258,6 +271,7 @@ function createTalismanWallet(extensionId, context) {
|
|
|
258
271
|
}
|
|
259
272
|
};
|
|
260
273
|
}
|
|
274
|
+
/* c8 ignore stop */
|
|
261
275
|
const walletFactories = {
|
|
262
276
|
"polkadot-js": createPolkadotJsWallet,
|
|
263
277
|
"talisman": createTalismanWallet
|
|
@@ -277,6 +291,7 @@ function createWalletTest(options = {}) {
|
|
|
277
291
|
const { headless = false, slowMo = 150 } = options;
|
|
278
292
|
const walletConfigs = options.wallets && options.wallets.length > 0 ? options.wallets : [{ type: "polkadot-js" }];
|
|
279
293
|
const isMultiWallet = walletConfigs.length > 1;
|
|
294
|
+
/* c8 ignore start */
|
|
280
295
|
return test$1.extend({
|
|
281
296
|
walletContext: [async ({}, use) => {
|
|
282
297
|
const extensionPathsString = (await Promise.all(walletConfigs.map((config) => getExtensionPathForWallet(config)))).join(",");
|
|
@@ -316,6 +331,7 @@ function createWalletTest(options = {}) {
|
|
|
316
331
|
await use(walletMap);
|
|
317
332
|
}
|
|
318
333
|
});
|
|
334
|
+
/* c8 ignore stop */
|
|
319
335
|
}
|
|
320
336
|
const test = createWalletTest();
|
|
321
337
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avalix/chroma",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.13",
|
|
5
5
|
"description": "End-to-end testing library for Polkadot wallet interactions",
|
|
6
6
|
"author": "Avalix Labs",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"homepage": "https://
|
|
8
|
+
"homepage": "https://chroma-docs.up.railway.app/docs",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/avalix-labs/chroma.git",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"@playwright/test": "^1.57.0",
|
|
62
62
|
"@types/node": "^24.10.2",
|
|
63
63
|
"@types/unzipper": "^0.10.11",
|
|
64
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
64
65
|
"eslint": "^9.39.1",
|
|
65
66
|
"tsdown": "^0.20.1",
|
|
66
67
|
"tsx": "^4.21.0",
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { getPolkadotJSExtensionPath } from '../wallets/polkadot-js.js'
|
|
3
|
+
import { getTalismanExtensionPath } from '../wallets/talisman.js'
|
|
4
|
+
import { createWalletTest, getExtensionPathForWallet } from './index.js'
|
|
5
|
+
|
|
6
|
+
// Mock @playwright/test
|
|
7
|
+
vi.mock('@playwright/test', () => {
|
|
8
|
+
const mockExtend = vi.fn().mockReturnValue({
|
|
9
|
+
extend: vi.fn(),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
test: {
|
|
14
|
+
extend: mockExtend,
|
|
15
|
+
},
|
|
16
|
+
chromium: {
|
|
17
|
+
launchPersistentContext: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
expect: vi.fn(),
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Mock wallet extension paths
|
|
24
|
+
vi.mock('../wallets/polkadot-js.js', () => ({
|
|
25
|
+
getPolkadotJSExtensionPath: vi.fn().mockResolvedValue('/mock/path/polkadot-extension'),
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
vi.mock('../wallets/talisman.js', () => ({
|
|
29
|
+
getTalismanExtensionPath: vi.fn().mockResolvedValue('/mock/path/talisman-extension'),
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
// Mock wallet factories
|
|
33
|
+
vi.mock('./wallet-factory.js', () => ({
|
|
34
|
+
walletFactories: {
|
|
35
|
+
'polkadot-js': vi.fn().mockReturnValue({ type: 'polkadot-js' }),
|
|
36
|
+
'talisman': vi.fn().mockReturnValue({ type: 'talisman' }),
|
|
37
|
+
},
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
describe('context-playwright/index', () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
vi.clearAllMocks()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('createWalletTest', () => {
|
|
46
|
+
it('should create a test function', () => {
|
|
47
|
+
const result = createWalletTest()
|
|
48
|
+
|
|
49
|
+
expect(result).toBeDefined()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should accept empty options', () => {
|
|
53
|
+
const result = createWalletTest({})
|
|
54
|
+
|
|
55
|
+
expect(result).toBeDefined()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should accept headless option', () => {
|
|
59
|
+
const result = createWalletTest({ headless: true })
|
|
60
|
+
|
|
61
|
+
expect(result).toBeDefined()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should accept slowMo option', () => {
|
|
65
|
+
const result = createWalletTest({ slowMo: 200 })
|
|
66
|
+
|
|
67
|
+
expect(result).toBeDefined()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should accept single wallet configuration', () => {
|
|
71
|
+
const result = createWalletTest({
|
|
72
|
+
wallets: [{ type: 'polkadot-js' }],
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
expect(result).toBeDefined()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should accept talisman wallet configuration', () => {
|
|
79
|
+
const result = createWalletTest({
|
|
80
|
+
wallets: [{ type: 'talisman' }],
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
expect(result).toBeDefined()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should accept multi-wallet configuration', () => {
|
|
87
|
+
const result = createWalletTest({
|
|
88
|
+
wallets: [
|
|
89
|
+
{ type: 'polkadot-js' },
|
|
90
|
+
{ type: 'talisman' },
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
expect(result).toBeDefined()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should accept all options combined', () => {
|
|
98
|
+
const result = createWalletTest({
|
|
99
|
+
headless: true,
|
|
100
|
+
slowMo: 100,
|
|
101
|
+
wallets: [{ type: 'polkadot-js' }],
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
expect(result).toBeDefined()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should default to polkadot-js when wallets array is empty', () => {
|
|
108
|
+
const result = createWalletTest({
|
|
109
|
+
wallets: [],
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
expect(result).toBeDefined()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should handle wallets with downloadUrl option', () => {
|
|
116
|
+
const result = createWalletTest({
|
|
117
|
+
wallets: [{
|
|
118
|
+
type: 'polkadot-js',
|
|
119
|
+
downloadUrl: 'https://custom-url.com/extension.zip',
|
|
120
|
+
}],
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
expect(result).toBeDefined()
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('getExtensionPathForWallet', () => {
|
|
128
|
+
it('should return polkadot-js extension path', async () => {
|
|
129
|
+
const result = await getExtensionPathForWallet({ type: 'polkadot-js' })
|
|
130
|
+
|
|
131
|
+
expect(result).toBe('/mock/path/polkadot-extension')
|
|
132
|
+
expect(getPolkadotJSExtensionPath).toHaveBeenCalled()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should return talisman extension path', async () => {
|
|
136
|
+
const result = await getExtensionPathForWallet({ type: 'talisman' })
|
|
137
|
+
|
|
138
|
+
expect(result).toBe('/mock/path/talisman-extension')
|
|
139
|
+
expect(getTalismanExtensionPath).toHaveBeenCalled()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should throw error for unsupported wallet type', async () => {
|
|
143
|
+
await expect(
|
|
144
|
+
getExtensionPathForWallet({ type: 'unsupported' as any }),
|
|
145
|
+
).rejects.toThrow('Unsupported wallet type: unsupported')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
})
|
|
@@ -13,7 +13,7 @@ import { getTalismanExtensionPath } from '../wallets/talisman.js'
|
|
|
13
13
|
import { walletFactories } from './wallet-factory.js'
|
|
14
14
|
|
|
15
15
|
// Helper function to get extension path for a wallet config
|
|
16
|
-
async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
|
|
16
|
+
export async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
|
|
17
17
|
const { type } = config
|
|
18
18
|
|
|
19
19
|
switch (type) {
|
|
@@ -43,6 +43,15 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
|
|
|
43
43
|
// Compute the expected wallets type
|
|
44
44
|
type ExpectedWallets = T extends readonly WalletConfig[] ? ConfiguredWallets<T> : Wallets
|
|
45
45
|
|
|
46
|
+
/*
|
|
47
|
+
* Playwright Fixtures - Coverage Exclusion
|
|
48
|
+
*
|
|
49
|
+
* The fixture implementations below are excluded from unit test coverage because:
|
|
50
|
+
* 1. They require a real Chromium browser with extension support
|
|
51
|
+
* 2. They interact with Chrome's extension APIs (service workers, extension IDs)
|
|
52
|
+
* 3. They are thoroughly tested via E2E tests in the tests/ directory
|
|
53
|
+
*/
|
|
54
|
+
/* c8 ignore start */
|
|
46
55
|
return base.extend<WalletFixtures<ExpectedWallets>, WalletWorkerFixtures>({
|
|
47
56
|
// Worker-scoped: Browser context with extension(s) (persists across all tests in worker)
|
|
48
57
|
// eslint-disable-next-line no-empty-pattern
|
|
@@ -127,6 +136,7 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
|
|
|
127
136
|
await use(walletMap)
|
|
128
137
|
},
|
|
129
138
|
})
|
|
139
|
+
/* c8 ignore stop */
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
// Default test with Polkadot JS wallet (with persistent wallet support via worker-scoped fixtures)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from '@playwright/test'
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import {
|
|
4
|
+
createPolkadotJsWallet,
|
|
5
|
+
createTalismanWallet,
|
|
6
|
+
walletFactories,
|
|
7
|
+
} from './wallet-factory.js'
|
|
8
|
+
|
|
9
|
+
// Mock wallet implementations
|
|
10
|
+
vi.mock('../wallets/polkadot-js.js', () => ({
|
|
11
|
+
importPolkadotJSAccount: vi.fn(),
|
|
12
|
+
authorizePolkadotJS: vi.fn(),
|
|
13
|
+
approvePolkadotJSTx: vi.fn(),
|
|
14
|
+
rejectPolkadotJSTx: vi.fn(),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
vi.mock('../wallets/talisman.js', () => ({
|
|
18
|
+
importPolkadotMnemonic: vi.fn(),
|
|
19
|
+
importEthPrivateKey: vi.fn(),
|
|
20
|
+
authorizeTalisman: vi.fn(),
|
|
21
|
+
approveTalismanTx: vi.fn(),
|
|
22
|
+
rejectTalismanTx: vi.fn(),
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
// Create mock browser context
|
|
26
|
+
function createMockContext(): BrowserContext {
|
|
27
|
+
const mockPage = {
|
|
28
|
+
url: () => 'https://example.com',
|
|
29
|
+
__extensionContext: null,
|
|
30
|
+
__extensionId: '',
|
|
31
|
+
} as unknown as Page
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
pages: vi.fn().mockReturnValue([mockPage]),
|
|
35
|
+
newPage: vi.fn().mockResolvedValue(mockPage),
|
|
36
|
+
} as unknown as BrowserContext
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('wallet-factory', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.clearAllMocks()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('walletFactories', () => {
|
|
45
|
+
it('should have polkadot-js factory', () => {
|
|
46
|
+
expect(walletFactories['polkadot-js']).toBeDefined()
|
|
47
|
+
expect(typeof walletFactories['polkadot-js']).toBe('function')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should have talisman factory', () => {
|
|
51
|
+
expect(walletFactories.talisman).toBeDefined()
|
|
52
|
+
expect(typeof walletFactories.talisman).toBe('function')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('createPolkadotJsWallet', () => {
|
|
57
|
+
const extensionId = 'test-extension-id'
|
|
58
|
+
let mockContext: BrowserContext
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
mockContext = createMockContext()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should create wallet with correct type and extensionId', () => {
|
|
65
|
+
const wallet = createPolkadotJsWallet(extensionId, mockContext)
|
|
66
|
+
|
|
67
|
+
expect(wallet.type).toBe('polkadot-js')
|
|
68
|
+
expect(wallet.extensionId).toBe(extensionId)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should have all required methods', () => {
|
|
72
|
+
const wallet = createPolkadotJsWallet(extensionId, mockContext)
|
|
73
|
+
|
|
74
|
+
expect(typeof wallet.importMnemonic).toBe('function')
|
|
75
|
+
expect(typeof wallet.authorize).toBe('function')
|
|
76
|
+
expect(typeof wallet.approveTx).toBe('function')
|
|
77
|
+
expect(typeof wallet.rejectTx).toBe('function')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe('createTalismanWallet', () => {
|
|
82
|
+
const extensionId = 'test-extension-id'
|
|
83
|
+
let mockContext: BrowserContext
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
mockContext = createMockContext()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should create wallet with correct type and extensionId', () => {
|
|
90
|
+
const wallet = createTalismanWallet(extensionId, mockContext)
|
|
91
|
+
|
|
92
|
+
expect(wallet.type).toBe('talisman')
|
|
93
|
+
expect(wallet.extensionId).toBe(extensionId)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should have all required methods', () => {
|
|
97
|
+
const wallet = createTalismanWallet(extensionId, mockContext)
|
|
98
|
+
|
|
99
|
+
expect(typeof wallet.importPolkadotMnemonic).toBe('function')
|
|
100
|
+
expect(typeof wallet.importEthPrivateKey).toBe('function')
|
|
101
|
+
expect(typeof wallet.authorize).toBe('function')
|
|
102
|
+
expect(typeof wallet.approveTx).toBe('function')
|
|
103
|
+
expect(typeof wallet.rejectTx).toBe('function')
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -15,14 +15,20 @@ import {
|
|
|
15
15
|
} from '../wallets/talisman.js'
|
|
16
16
|
|
|
17
17
|
// Helper to create extended page with wallet context
|
|
18
|
+
/* c8 ignore start */
|
|
18
19
|
function createExtendedPage(page: Page, context: BrowserContext, extensionId: string) {
|
|
19
20
|
const extPage = page as Page & { __extensionContext: BrowserContext, __extensionId: string }
|
|
20
21
|
extPage.__extensionContext = context
|
|
21
22
|
extPage.__extensionId = extensionId
|
|
22
23
|
return extPage
|
|
23
24
|
}
|
|
25
|
+
/* c8 ignore stop */
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
/*
|
|
28
|
+
* Factory function for Polkadot-JS wallet
|
|
29
|
+
* Coverage excluded: methods interact with Chrome extension APIs via browser context.
|
|
30
|
+
*/
|
|
31
|
+
/* c8 ignore start */
|
|
26
32
|
export function createPolkadotJsWallet(extensionId: string, context: BrowserContext) {
|
|
27
33
|
return {
|
|
28
34
|
extensionId,
|
|
@@ -49,8 +55,13 @@ export function createPolkadotJsWallet(extensionId: string, context: BrowserCont
|
|
|
49
55
|
},
|
|
50
56
|
}
|
|
51
57
|
}
|
|
58
|
+
/* c8 ignore stop */
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
/*
|
|
61
|
+
* Factory function for Talisman wallet
|
|
62
|
+
* Coverage excluded: methods interact with Chrome extension APIs via browser context.
|
|
63
|
+
*/
|
|
64
|
+
/* c8 ignore start */
|
|
54
65
|
export function createTalismanWallet(extensionId: string, context: BrowserContext) {
|
|
55
66
|
let importedAccountName: string | undefined
|
|
56
67
|
|
|
@@ -91,6 +102,7 @@ export function createTalismanWallet(extensionId: string, context: BrowserContex
|
|
|
91
102
|
},
|
|
92
103
|
}
|
|
93
104
|
}
|
|
105
|
+
/* c8 ignore stop */
|
|
94
106
|
|
|
95
107
|
// Wallet factories map - auto-inferred types
|
|
96
108
|
export const walletFactories = {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
// Mock playwright before importing
|
|
4
|
+
vi.mock('@playwright/test', () => ({
|
|
5
|
+
test: {
|
|
6
|
+
extend: vi.fn().mockReturnValue({}),
|
|
7
|
+
},
|
|
8
|
+
chromium: {
|
|
9
|
+
launchPersistentContext: vi.fn(),
|
|
10
|
+
},
|
|
11
|
+
expect: vi.fn(),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
vi.mock('./wallets/polkadot-js.js', () => ({
|
|
15
|
+
getPolkadotJSExtensionPath: vi.fn().mockResolvedValue('/mock/path'),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
vi.mock('./wallets/talisman.js', () => ({
|
|
19
|
+
getTalismanExtensionPath: vi.fn().mockResolvedValue('/mock/path'),
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
vi.mock('./context-playwright/wallet-factory.js', () => ({
|
|
23
|
+
walletFactories: {
|
|
24
|
+
'polkadot-js': vi.fn(),
|
|
25
|
+
'talisman': vi.fn(),
|
|
26
|
+
},
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
describe('index exports', () => {
|
|
30
|
+
it('should export createWalletTest function', async () => {
|
|
31
|
+
const { createWalletTest } = await import('./index.js')
|
|
32
|
+
expect(createWalletTest).toBeDefined()
|
|
33
|
+
expect(typeof createWalletTest).toBe('function')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should export test function', async () => {
|
|
37
|
+
const { test } = await import('./index.js')
|
|
38
|
+
expect(test).toBeDefined()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should export expect function', async () => {
|
|
42
|
+
const { expect: playwrightExpect } = await import('./index.js')
|
|
43
|
+
expect(playwrightExpect).toBeDefined()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should be able to call createWalletTest', async () => {
|
|
47
|
+
const { createWalletTest } = await import('./index.js')
|
|
48
|
+
const result = createWalletTest()
|
|
49
|
+
expect(result).toBeDefined()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should be able to call createWalletTest with options', async () => {
|
|
53
|
+
const { createWalletTest } = await import('./index.js')
|
|
54
|
+
const result = createWalletTest({ headless: true })
|
|
55
|
+
expect(result).toBeDefined()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import {
|
|
5
|
+
getPolkadotJSExtensionPath,
|
|
6
|
+
POLKADOT_JS_CONFIG,
|
|
7
|
+
} from './polkadot-js.js'
|
|
8
|
+
|
|
9
|
+
// Mock node:fs module
|
|
10
|
+
vi.mock('node:fs', async () => {
|
|
11
|
+
const actual = await vi.importActual<typeof import('node:fs')>('node:fs')
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
default: {
|
|
15
|
+
...actual,
|
|
16
|
+
existsSync: vi.fn(),
|
|
17
|
+
readdirSync: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
existsSync: vi.fn(),
|
|
20
|
+
readdirSync: vi.fn(),
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('polkadot-js wallet', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
vi.restoreAllMocks()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('polkadot_js_config', () => {
|
|
34
|
+
it('should have correct extension name format', () => {
|
|
35
|
+
expect(POLKADOT_JS_CONFIG.extensionName).toMatch(/^polkadot-extension-\d+\.\d+\.\d+$/)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should have valid download URL', () => {
|
|
39
|
+
expect(POLKADOT_JS_CONFIG.downloadUrl).toContain('github.com')
|
|
40
|
+
expect(POLKADOT_JS_CONFIG.downloadUrl).toContain('polkadot-js/extension')
|
|
41
|
+
expect(POLKADOT_JS_CONFIG.downloadUrl.endsWith('.zip')).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('getPolkadotJSExtensionPath', () => {
|
|
46
|
+
it('should return extension path when extension exists', async () => {
|
|
47
|
+
const mockedFs = vi.mocked(fs)
|
|
48
|
+
mockedFs.existsSync.mockReturnValue(true)
|
|
49
|
+
mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
|
|
50
|
+
|
|
51
|
+
const result = await getPolkadotJSExtensionPath()
|
|
52
|
+
|
|
53
|
+
expect(result).toContain('.chroma')
|
|
54
|
+
expect(result).toContain(POLKADOT_JS_CONFIG.extensionName)
|
|
55
|
+
expect(mockedFs.existsSync).toHaveBeenCalled()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should throw error when extension directory does not exist', async () => {
|
|
59
|
+
const mockedFs = vi.mocked(fs)
|
|
60
|
+
mockedFs.existsSync.mockReturnValue(false)
|
|
61
|
+
|
|
62
|
+
await expect(getPolkadotJSExtensionPath()).rejects.toThrow(
|
|
63
|
+
'Polkadot-JS extension not found',
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should throw error when extension directory is empty', async () => {
|
|
68
|
+
const mockedFs = vi.mocked(fs)
|
|
69
|
+
mockedFs.existsSync.mockReturnValue(true)
|
|
70
|
+
mockedFs.readdirSync.mockReturnValue([])
|
|
71
|
+
|
|
72
|
+
await expect(getPolkadotJSExtensionPath()).rejects.toThrow(
|
|
73
|
+
'Polkadot-JS extension not found',
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should include download instructions in error message', async () => {
|
|
78
|
+
const mockedFs = vi.mocked(fs)
|
|
79
|
+
mockedFs.existsSync.mockReturnValue(false)
|
|
80
|
+
|
|
81
|
+
await expect(getPolkadotJSExtensionPath()).rejects.toThrow(
|
|
82
|
+
'npx @avalix/chroma download-extensions',
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should use correct path structure', async () => {
|
|
87
|
+
const mockedFs = vi.mocked(fs)
|
|
88
|
+
mockedFs.existsSync.mockReturnValue(true)
|
|
89
|
+
mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
|
|
90
|
+
|
|
91
|
+
const result = await getPolkadotJSExtensionPath()
|
|
92
|
+
|
|
93
|
+
const expectedPath = path.join(process.cwd(), '.chroma', POLKADOT_JS_CONFIG.extensionName)
|
|
94
|
+
expect(result).toBe(expectedPath)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -12,7 +12,11 @@ export const POLKADOT_JS_CONFIG = {
|
|
|
12
12
|
extensionName: `polkadot-extension-${VERSION}`,
|
|
13
13
|
} as const
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/*
|
|
16
|
+
* Helper function to find extension popup
|
|
17
|
+
* Coverage excluded: requires real browser context with Chrome extension APIs.
|
|
18
|
+
*/
|
|
19
|
+
/* c8 ignore start */
|
|
16
20
|
async function findExtensionPopup(context: BrowserContext, extensionId: string): Promise<Page> {
|
|
17
21
|
// Wait for extension popup to appear with retry logic
|
|
18
22
|
const maxAttempts = 10
|
|
@@ -35,6 +39,7 @@ async function findExtensionPopup(context: BrowserContext, extensionId: string):
|
|
|
35
39
|
|
|
36
40
|
throw new Error(`Extension popup not found for ID: ${extensionId}`)
|
|
37
41
|
}
|
|
42
|
+
/* c8 ignore stop */
|
|
38
43
|
|
|
39
44
|
// Get Polkadot-JS extension path
|
|
40
45
|
export async function getPolkadotJSExtensionPath(): Promise<string> {
|
|
@@ -53,6 +58,13 @@ export async function getPolkadotJSExtensionPath(): Promise<string> {
|
|
|
53
58
|
return extensionDir
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
/*
|
|
62
|
+
* Wallet interaction functions below are excluded from coverage because:
|
|
63
|
+
* - They require a real Chromium browser with extension support
|
|
64
|
+
* - They interact with Chrome extension popup pages
|
|
65
|
+
*/
|
|
66
|
+
/* c8 ignore start */
|
|
67
|
+
|
|
56
68
|
// Polkadot-JS specific account import implementation
|
|
57
69
|
export async function importPolkadotJSAccount(
|
|
58
70
|
page: Page & { __extensionContext: BrowserContext, __extensionId: string },
|
|
@@ -142,3 +154,5 @@ export async function rejectPolkadotJSTx(
|
|
|
142
154
|
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
143
155
|
await extensionPopup.getByRole('link', { name: 'Cancel' }).click()
|
|
144
156
|
}
|
|
157
|
+
|
|
158
|
+
/* c8 ignore stop */
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import {
|
|
5
|
+
getTalismanExtensionPath,
|
|
6
|
+
TALISMAN_CONFIG,
|
|
7
|
+
} from './talisman.js'
|
|
8
|
+
|
|
9
|
+
// Mock node:fs module
|
|
10
|
+
vi.mock('node:fs', async () => {
|
|
11
|
+
const actual = await vi.importActual<typeof import('node:fs')>('node:fs')
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
default: {
|
|
15
|
+
...actual,
|
|
16
|
+
existsSync: vi.fn(),
|
|
17
|
+
readdirSync: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
existsSync: vi.fn(),
|
|
20
|
+
readdirSync: vi.fn(),
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('talisman wallet', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
vi.clearAllMocks()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
vi.restoreAllMocks()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('talisman_config', () => {
|
|
34
|
+
it('should have correct extension name format', () => {
|
|
35
|
+
expect(TALISMAN_CONFIG.extensionName).toMatch(/^talisman-extension-\d+\.\d+\.\d+$/)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should have valid download URL', () => {
|
|
39
|
+
expect(TALISMAN_CONFIG.downloadUrl).toContain('github.com')
|
|
40
|
+
expect(TALISMAN_CONFIG.downloadUrl).toContain('talisman')
|
|
41
|
+
expect(TALISMAN_CONFIG.downloadUrl.endsWith('.zip')).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('getTalismanExtensionPath', () => {
|
|
46
|
+
it('should return extension path when extension exists', async () => {
|
|
47
|
+
const mockedFs = vi.mocked(fs)
|
|
48
|
+
mockedFs.existsSync.mockReturnValue(true)
|
|
49
|
+
mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
|
|
50
|
+
|
|
51
|
+
const result = await getTalismanExtensionPath()
|
|
52
|
+
|
|
53
|
+
expect(result).toContain('.chroma')
|
|
54
|
+
expect(result).toContain(TALISMAN_CONFIG.extensionName)
|
|
55
|
+
expect(mockedFs.existsSync).toHaveBeenCalled()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should throw error when extension directory does not exist', async () => {
|
|
59
|
+
const mockedFs = vi.mocked(fs)
|
|
60
|
+
mockedFs.existsSync.mockReturnValue(false)
|
|
61
|
+
|
|
62
|
+
await expect(getTalismanExtensionPath()).rejects.toThrow(
|
|
63
|
+
'Talisman extension not found',
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should throw error when extension directory is empty', async () => {
|
|
68
|
+
const mockedFs = vi.mocked(fs)
|
|
69
|
+
mockedFs.existsSync.mockReturnValue(true)
|
|
70
|
+
mockedFs.readdirSync.mockReturnValue([])
|
|
71
|
+
|
|
72
|
+
await expect(getTalismanExtensionPath()).rejects.toThrow(
|
|
73
|
+
'Talisman extension not found',
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should include download instructions in error message', async () => {
|
|
78
|
+
const mockedFs = vi.mocked(fs)
|
|
79
|
+
mockedFs.existsSync.mockReturnValue(false)
|
|
80
|
+
|
|
81
|
+
await expect(getTalismanExtensionPath()).rejects.toThrow(
|
|
82
|
+
'npx @avalix/chroma download-extensions',
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should use correct path structure', async () => {
|
|
87
|
+
const mockedFs = vi.mocked(fs)
|
|
88
|
+
mockedFs.existsSync.mockReturnValue(true)
|
|
89
|
+
mockedFs.readdirSync.mockReturnValue(['manifest.json'] as any)
|
|
90
|
+
|
|
91
|
+
const result = await getTalismanExtensionPath()
|
|
92
|
+
|
|
93
|
+
const expectedPath = path.join(process.cwd(), '.chroma', TALISMAN_CONFIG.extensionName)
|
|
94
|
+
expect(result).toBe(expectedPath)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
})
|
package/src/wallets/talisman.ts
CHANGED
|
@@ -12,7 +12,11 @@ export const TALISMAN_CONFIG = {
|
|
|
12
12
|
extensionName: `talisman-extension-${VERSION}`,
|
|
13
13
|
} as const
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/*
|
|
16
|
+
* Helper function to find extension popup
|
|
17
|
+
* Coverage excluded: requires real browser context with Chrome extension APIs.
|
|
18
|
+
*/
|
|
19
|
+
/* c8 ignore start */
|
|
16
20
|
async function findExtensionPopup(context: BrowserContext, extensionId: string): Promise<Page> {
|
|
17
21
|
// Wait for extension popup to appear with retry logic
|
|
18
22
|
const maxAttempts = 10
|
|
@@ -36,6 +40,7 @@ async function findExtensionPopup(context: BrowserContext, extensionId: string):
|
|
|
36
40
|
|
|
37
41
|
throw new Error(`Extension popup not found for ID: ${extensionId}`)
|
|
38
42
|
}
|
|
43
|
+
/* c8 ignore stop */
|
|
39
44
|
|
|
40
45
|
// Get Talisman extension path
|
|
41
46
|
export async function getTalismanExtensionPath(): Promise<string> {
|
|
@@ -54,6 +59,13 @@ export async function getTalismanExtensionPath(): Promise<string> {
|
|
|
54
59
|
return extensionDir
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
/*
|
|
63
|
+
* Wallet interaction functions below are excluded from coverage because:
|
|
64
|
+
* - They require a real Chromium browser with extension support
|
|
65
|
+
* - They interact with Chrome extension popup pages
|
|
66
|
+
*/
|
|
67
|
+
/* c8 ignore start */
|
|
68
|
+
|
|
57
69
|
// Helper function to find Talisman onboarding page
|
|
58
70
|
async function findOnboardingPage(
|
|
59
71
|
context: BrowserContext,
|
|
@@ -235,3 +247,5 @@ export async function rejectTalismanTx(
|
|
|
235
247
|
|
|
236
248
|
await rejectButton.click()
|
|
237
249
|
}
|
|
250
|
+
|
|
251
|
+
/* c8 ignore stop */
|