@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/README.md +27 -228
- package/dist/index.d.mts +28 -21
- package/dist/index.mjs +123 -101
- package/package.json +5 -3
- package/scripts/download-extensions.ts +3 -3
- package/src/context-playwright/index.test.ts +148 -0
- package/src/context-playwright/index.ts +17 -10
- package/src/context-playwright/types.ts +8 -26
- package/src/context-playwright/wallet-factory.test.ts +106 -0
- package/src/context-playwright/wallet-factory.ts +72 -96
- package/src/index.test.ts +57 -0
- package/src/utils/download-extension.ts +1 -1
- 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 +121 -53
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
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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();
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
254
|
+
importEthPrivateKey: async (options) => {
|
|
204
255
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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)
|
|
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.
|
|
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",
|
|
@@ -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"
|
|
@@ -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('🗑️
|
|
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
|
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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
|