@avalix/chroma 0.0.11 → 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/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,41 +102,71 @@ 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
  }
106
- async function importEthPrivateKey(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
112
+ /* c8 ignore start */
113
+ async function findOnboardingPage(context, extensionId) {
114
+ const popupUrl = `chrome-extension://${extensionId}/dashboard.html`;
115
+ const newPage = await context.newPage();
116
+ await newPage.goto(popupUrl);
117
+ await newPage.waitForLoadState("domcontentloaded");
118
+ for (const p of context.pages()) if (p !== newPage && p.url().includes(`chrome-extension://${extensionId}/`)) await p.close();
119
+ return newPage;
120
+ }
121
+ async function completeOnboarding(extensionPage, password) {
122
+ await extensionPage.bringToFront();
123
+ await extensionPage.waitForLoadState("domcontentloaded");
124
+ if (await extensionPage.getByRole("button", { name: "Settings" }).isVisible()) {
125
+ await extensionPage.getByRole("button", { name: "Settings" }).click({ force: true });
126
+ return;
127
+ }
128
+ await extensionPage.getByTestId("onboarding-get-started-button").click();
129
+ await extensionPage.getByRole("textbox", { name: "Enter password" }).fill(password);
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
+ const extensionId = extensionPage.url().match(/chrome-extension:\/\/([^/]+)/)?.[1];
135
+ await extensionPage.goto(`chrome-extension://${extensionId}/dashboard.html#/settings/general`);
136
+ await extensionPage.waitForLoadState("domcontentloaded");
137
+ await extensionPage.getByRole("link", { name: "Security & Privacy" }).click();
138
+ await extensionPage.getByTestId("component-toggle-button").first().click();
139
+ }
140
+ async function importPolkadotMnemonic(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
107
141
  const context = page.__extensionContext;
108
142
  const extensionId = page.__extensionId;
109
- const maxAttempts = 20;
110
- const retryDelay = 500;
111
- let extensionPage = null;
112
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
113
- const pages = context.pages();
114
- for (const p of pages) {
115
- const url = p.url();
116
- if (url.includes("onboarding.html") || url.includes(`chrome-extension://${extensionId}/`)) {
117
- extensionPage = p;
118
- break;
119
- }
120
- }
121
- if (extensionPage) break;
122
- if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
143
+ const extensionPage = await findOnboardingPage(context, extensionId);
144
+ try {
145
+ await completeOnboarding(extensionPage, password);
146
+ await extensionPage.getByRole("link", { name: "Manage Accounts" }).click();
147
+ await extensionPage.getByRole("button", { name: "Get Started" }).click();
148
+ await extensionPage.getByRole("button", { name: "Add Account" }).click();
149
+ await extensionPage.getByRole("button", { name: "Import Import an existing" }).click();
150
+ await extensionPage.getByRole("button", { name: "Import via Recovery Phrase" }).click();
151
+ await extensionPage.getByRole("button", { name: "Polkadot Relay Chain, Asset" }).click();
152
+ await extensionPage.getByRole("textbox", { name: "Choose a name" }).fill(name);
153
+ await extensionPage.getByRole("textbox", { name: "Enter your 12 or 24 word" }).fill(seed);
154
+ await extensionPage.getByTestId("account-add-mnemonic-import-button").click();
155
+ await extensionPage.close();
156
+ } catch (error) {
157
+ console.error("❌ Error during Talisman Polkadot account import:", error);
158
+ throw error;
123
159
  }
124
- if (!extensionPage) throw new Error(`Talisman onboarding page not found after ${maxAttempts} attempts`);
160
+ }
161
+ async function importEthPrivateKey(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
162
+ const context = page.__extensionContext;
163
+ const extensionId = page.__extensionId;
164
+ const extensionPage = await findOnboardingPage(context, extensionId);
125
165
  try {
126
- await extensionPage.bringToFront();
127
- await extensionPage.waitForLoadState("domcontentloaded");
128
- await extensionPage.getByTestId("onboarding-get-started-button").click();
129
- await extensionPage.getByRole("textbox", { name: "Enter password" }).fill(password);
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();
166
+ await completeOnboarding(extensionPage, password);
167
+ await extensionPage.getByRole("link", { name: "Manage Accounts" }).click();
168
+ await extensionPage.getByRole("button", { name: "Get Started" }).click();
169
+ await extensionPage.getByRole("button", { name: "Add Account" }).click();
135
170
  await extensionPage.getByRole("button", { name: "Import Import an existing" }).click();
136
171
  await extensionPage.getByRole("button", { name: "Import via Private Key" }).click();
137
172
  await extensionPage.getByRole("button", { name: "Select account platform" }).click();
@@ -141,16 +176,20 @@ async function importEthPrivateKey(page, { seed, name = "Test Account", password
141
176
  await extensionPage.getByRole("button", { name: "Save" }).click();
142
177
  await extensionPage.close();
143
178
  } catch (error) {
144
- console.error("❌ Error during Talisman account import:", error);
179
+ console.error("❌ Error during Talisman Ethereum account import:", error);
145
180
  throw error;
146
181
  }
147
182
  }
148
183
  async function authorizeTalisman(page, options = {}) {
149
- const { accountName = "Test Account" } = options;
150
184
  const context = page.__extensionContext;
151
185
  const extensionId = page.__extensionId;
186
+ const { accountName = "Test Account" } = options;
152
187
  const extensionPopup = await findExtensionPopup(context, extensionId);
153
- await extensionPopup.getByRole("button", { name: accountName }).click();
188
+ await extensionPopup.waitForLoadState("domcontentloaded");
189
+ const accountButton = extensionPopup.getByRole("button", { name: accountName });
190
+ await accountButton.waitFor({ state: "visible" });
191
+ await accountButton.scrollIntoViewIfNeeded();
192
+ await accountButton.click({ force: true });
154
193
  await extensionPopup.getByTestId("connection-connect-button").click();
155
194
  try {
156
195
  await (await findExtensionPopup(context, extensionId)).getByRole("button", { name: "Approve" }).click();
@@ -160,105 +199,83 @@ async function approveTalismanTx(page) {
160
199
  const context = page.__extensionContext;
161
200
  const extensionId = page.__extensionId;
162
201
  const extensionPopup = await findExtensionPopup(context, extensionId);
163
- try {
164
- await extensionPopup.getByRole("button", { name: "Yes" }).click();
165
- } catch {
166
- console.log("No another popup found, skipping");
167
- }
202
+ if (await extensionPopup.getByRole("button", { name: "Yes" }).isVisible()) await extensionPopup.getByRole("button", { name: "Yes" }).click();
168
203
  await extensionPopup.getByRole("button", { name: "Approve" }).click();
169
204
  }
170
205
  async function rejectTalismanTx(page) {
171
206
  const context = page.__extensionContext;
172
207
  const extensionId = page.__extensionId;
173
- await (await findExtensionPopup(context, extensionId)).getByTestId("connection-reject-button").click();
208
+ const extensionPopup = await findExtensionPopup(context, extensionId);
209
+ await extensionPopup.getByTestId("connection-reject-button").or(extensionPopup.getByRole("button", { name: "Cancel" })).click();
174
210
  }
175
-
176
- //#endregion
177
- //#region src/context-playwright/types.ts
178
- const WALLET_TYPES = ["polkadot-js", "talisman"];
211
+ /* c8 ignore stop */
179
212
 
180
213
  //#endregion
181
214
  //#region src/context-playwright/wallet-factory.ts
215
+ /* c8 ignore start */
182
216
  function createExtendedPage(page, context, extensionId) {
183
217
  const extPage = page;
184
218
  extPage.__extensionContext = context;
185
219
  extPage.__extensionId = extensionId;
186
220
  return extPage;
187
221
  }
188
- function createWalletInstance(walletType, extensionId, context) {
189
- let importedAccountName;
190
- const baseInstance = {
222
+ /* c8 ignore stop */
223
+ /* c8 ignore start */
224
+ function createPolkadotJsWallet(extensionId, context) {
225
+ return {
191
226
  extensionId,
227
+ type: "polkadot-js",
192
228
  importMnemonic: async (options) => {
229
+ await importPolkadotJSAccount(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId), options);
230
+ },
231
+ authorize: async () => {
232
+ await authorizePolkadotJS(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
233
+ },
234
+ approveTx: async (options = {}) => {
235
+ await approvePolkadotJSTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId), options);
236
+ },
237
+ rejectTx: async () => {
238
+ await rejectPolkadotJSTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
239
+ }
240
+ };
241
+ }
242
+ /* c8 ignore stop */
243
+ /* c8 ignore start */
244
+ function createTalismanWallet(extensionId, context) {
245
+ let importedAccountName;
246
+ return {
247
+ extensionId,
248
+ type: "talisman",
249
+ importPolkadotMnemonic: async (options) => {
193
250
  const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
194
251
  importedAccountName = options.name || "Test Account";
195
- switch (walletType) {
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
- }
252
+ await importPolkadotMnemonic(extPage, options);
202
253
  },
203
- authorize: async (options = {}) => {
254
+ importEthPrivateKey: async (options) => {
204
255
  const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
205
- const accountName = options.accountName || importedAccountName;
206
- switch (walletType) {
207
- case "polkadot-js":
208
- await authorizePolkadotJS(extPage);
209
- break;
210
- case "talisman":
211
- await authorizeTalisman(extPage, { accountName });
212
- break;
213
- default: throw new Error(`Unsupported wallet type: ${walletType}`);
214
- }
256
+ importedAccountName = options.name || "Test Account";
257
+ await importEthPrivateKey(extPage, {
258
+ seed: options.privateKey,
259
+ name: options.name,
260
+ password: options.password
261
+ });
215
262
  },
216
- approveTx: async (options = {}) => {
217
- const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
218
- switch (walletType) {
219
- case "polkadot-js":
220
- await approvePolkadotJSTx(extPage, options);
221
- break;
222
- case "talisman":
223
- await approveTalismanTx(extPage);
224
- break;
225
- default: throw new Error(`Unsupported wallet type: ${walletType}`);
226
- }
263
+ authorize: async (options = {}) => {
264
+ await authorizeTalisman(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId), { accountName: options.accountName || importedAccountName });
265
+ },
266
+ approveTx: async () => {
267
+ await approveTalismanTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
227
268
  },
228
269
  rejectTx: async () => {
229
- const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
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
- }
270
+ await rejectTalismanTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
239
271
  }
240
272
  };
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
273
  }
274
+ /* c8 ignore stop */
275
+ const walletFactories = {
276
+ "polkadot-js": createPolkadotJsWallet,
277
+ "talisman": createTalismanWallet
278
+ };
262
279
 
263
280
  //#endregion
264
281
  //#region src/context-playwright/index.ts
@@ -274,6 +291,7 @@ function createWalletTest(options = {}) {
274
291
  const { headless = false, slowMo = 150 } = options;
275
292
  const walletConfigs = options.wallets && options.wallets.length > 0 ? options.wallets : [{ type: "polkadot-js" }];
276
293
  const isMultiWallet = walletConfigs.length > 1;
294
+ /* c8 ignore start */
277
295
  return test$1.extend({
278
296
  walletContext: [async ({}, use) => {
279
297
  const extensionPathsString = (await Promise.all(walletConfigs.map((config) => getExtensionPathForWallet(config)))).join(",");
@@ -306,10 +324,14 @@ function createWalletTest(options = {}) {
306
324
  },
307
325
  wallets: async ({ walletContext, walletExtensionIds }, use) => {
308
326
  const walletMap = {};
309
- for (const [walletType, extensionId] of walletExtensionIds) if (WALLET_TYPES.includes(walletType)) walletMap[walletType] = createWalletInstance(walletType, extensionId, walletContext);
327
+ for (const [walletType, extensionId] of walletExtensionIds) {
328
+ const factory = walletFactories[walletType];
329
+ if (factory) walletMap[walletType] = factory(extensionId, walletContext);
330
+ }
310
331
  await use(walletMap);
311
332
  }
312
333
  });
334
+ /* c8 ignore stop */
313
335
  }
314
336
  const test = createWalletTest();
315
337
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@avalix/chroma",
3
3
  "type": "module",
4
- "version": "0.0.11",
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://github.com/avalix-labs/chroma#readme",
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",
@@ -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 --ui",
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"
@@ -60,6 +61,7 @@
60
61
  "@playwright/test": "^1.57.0",
61
62
  "@types/node": "^24.10.2",
62
63
  "@types/unzipper": "^0.10.11",
64
+ "@vitest/coverage-v8": "4.0.18",
63
65
  "eslint": "^9.39.1",
64
66
  "tsdown": "^0.20.1",
65
67
  "tsx": "^4.21.0",
@@ -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('🗑️ Clearing existing .chroma directory...')
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(`🎨 Chroma v${version}\n`)
30
- console.log('🚀 Downloading wallet extensions...\n')
29
+ console.log(`\n🎨 Chroma v${version}`)
30
+ console.log('🚀 Downloading wallet extensions...')
31
31
 
32
32
  try {
33
33
  // Clear existing .chroma directory
@@ -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
+ })
@@ -4,19 +4,16 @@ 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 { WALLET_TYPES } from './types.js'
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
- async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
16
+ export async function getExtensionPathForWallet(config: WalletConfig): Promise<string> {
20
17
  const { type } = config
21
18
 
22
19
  switch (type) {
@@ -46,6 +43,15 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
46
43
  // Compute the expected wallets type
47
44
  type ExpectedWallets = T extends readonly WalletConfig[] ? ConfiguredWallets<T> : Wallets
48
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 */
49
55
  return base.extend<WalletFixtures<ExpectedWallets>, WalletWorkerFixtures>({
50
56
  // Worker-scoped: Browser context with extension(s) (persists across all tests in worker)
51
57
  // eslint-disable-next-line no-empty-pattern
@@ -117,19 +123,20 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
117
123
 
118
124
  // Wallet instances for each configured wallet
119
125
  wallets: async ({ walletContext, walletExtensionIds }, use) => {
120
- const walletMap: Partial<ExpectedWallets> = {}
126
+ const walletMap = {} as ExpectedWallets
121
127
 
122
128
  // Create wallet instance for each configured wallet
123
129
  for (const [walletType, extensionId] of walletExtensionIds) {
124
- if (WALLET_TYPES.includes(walletType as WalletType)) {
125
- const instance = createWalletInstance(walletType, extensionId, walletContext);
126
- (walletMap as Record<string, WalletInstance>)[walletType] = instance
130
+ const factory = walletFactories[walletType as keyof typeof walletFactories]
131
+ if (factory) {
132
+ walletMap[walletType as keyof ExpectedWallets] = factory(extensionId, walletContext) as ExpectedWallets[keyof ExpectedWallets]
127
133
  }
128
134
  }
129
135
 
130
- await use(walletMap as ExpectedWallets)
136
+ await use(walletMap)
131
137
  },
132
138
  })
139
+ /* c8 ignore stop */
133
140
  }
134
141
 
135
142
  // Default test with Polkadot JS wallet (with persistent wallet support via worker-scoped fixtures)
@@ -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