@avalix/chroma 0.0.16 ā 1.0.1
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 +104 -4
- package/dist/index.d.mts +115 -20
- package/dist/index.mjs +122 -162
- package/package.json +4 -5
- package/scripts/download-extensions.ts +17 -22
- package/src/context-playwright/index.test.ts +19 -29
- package/src/context-playwright/index.ts +50 -36
- package/src/context-playwright/types.ts +19 -17
- package/src/context-playwright/wallet-factory.test.ts +38 -0
- package/src/context-playwright/wallet-factory.ts +18 -81
- package/src/utils/download-extension.integration.test.ts +1 -1
- package/src/utils/download-extension.test.ts +16 -34
- package/src/utils/download-extension.ts +11 -7
- package/src/utils/find-extension-popup.ts +39 -0
- package/src/utils/test-defaults.ts +7 -0
- package/src/wallets/metamask.test.ts +12 -18
- package/src/wallets/metamask.ts +91 -60
- package/src/wallets/polkadot-js.test.ts +12 -18
- package/src/wallets/polkadot-js.ts +19 -54
- package/src/wallets/talisman.test.ts +12 -18
- package/src/wallets/talisman.ts +20 -49
package/dist/index.mjs
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
+
import { cp, rm } from "node:fs/promises";
|
|
2
|
+
import path, { resolve } from "node:path";
|
|
1
3
|
import { chromium, expect, test as test$1 } from "@playwright/test";
|
|
2
4
|
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
5
6
|
|
|
7
|
+
//#region src/utils/test-defaults.ts
|
|
8
|
+
/**
|
|
9
|
+
* Default password used by chroma to bootstrap wallets in tests.
|
|
10
|
+
*
|
|
11
|
+
* Wallet methods accept an explicit `password` option that overrides this
|
|
12
|
+
* value, so callers that already supply their own password are unaffected.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_TEST_PASSWORD = "h3llop0lkadot!";
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
6
17
|
//#region src/wallets/metamask.ts
|
|
7
|
-
const VERSION$2 = "13.
|
|
18
|
+
const VERSION$2 = "13.28.0";
|
|
8
19
|
const METAMASK_CONFIG = {
|
|
9
|
-
downloadUrl: `https://github.com/MetaMask/metamask-extension/releases/download/v${VERSION$2}/metamask-
|
|
20
|
+
downloadUrl: `https://github.com/MetaMask/metamask-extension/releases/download/v${VERSION$2}/metamask-chrome-${VERSION$2}.zip`,
|
|
10
21
|
extensionName: `metamask-extension-${VERSION$2}`
|
|
11
22
|
};
|
|
12
23
|
async function getMetaMaskExtensionPath() {
|
|
13
24
|
const extensionsDir = path.resolve(process.cwd(), ".chroma");
|
|
14
25
|
const extensionDir = path.join(extensionsDir, METAMASK_CONFIG.extensionName);
|
|
15
|
-
if (
|
|
26
|
+
if ((await fs.promises.readdir(extensionDir).catch(() => [])).length === 0) throw new Error(`MetaMask extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n`);
|
|
16
27
|
return extensionDir;
|
|
17
28
|
}
|
|
18
29
|
/* c8 ignore start */
|
|
@@ -23,7 +34,6 @@ async function findOnboardingPage$1(context, extensionId) {
|
|
|
23
34
|
const pages = context.pages();
|
|
24
35
|
for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
25
36
|
await p.waitForLoadState("domcontentloaded");
|
|
26
|
-
await p.getByText("I accept the risks").click();
|
|
27
37
|
return p;
|
|
28
38
|
}
|
|
29
39
|
if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
@@ -50,7 +60,6 @@ async function findExtensionPopup$2(context, extensionId) {
|
|
|
50
60
|
}
|
|
51
61
|
throw new Error(`MetaMask side panel not found for ID: ${extensionId}`);
|
|
52
62
|
}
|
|
53
|
-
const METAMASK_PASSWORD = "h3llop0lkadot!";
|
|
54
63
|
async function completeOnboarding$1(extensionPage, seedPhrase) {
|
|
55
64
|
await extensionPage.bringToFront();
|
|
56
65
|
await extensionPage.waitForLoadState("domcontentloaded");
|
|
@@ -58,18 +67,13 @@ async function completeOnboarding$1(extensionPage, seedPhrase) {
|
|
|
58
67
|
await extensionPage.getByTestId("onboarding-import-with-srp-button").click();
|
|
59
68
|
await extensionPage.getByTestId("srp-input-import__srp-note").pressSequentially(seedPhrase, { delay: 50 });
|
|
60
69
|
await extensionPage.getByTestId("import-srp-confirm").click();
|
|
61
|
-
await extensionPage.getByTestId("create-password-new-input").fill(
|
|
62
|
-
await extensionPage.getByTestId("create-password-confirm-input").fill(
|
|
70
|
+
await extensionPage.getByTestId("create-password-new-input").fill(DEFAULT_TEST_PASSWORD);
|
|
71
|
+
await extensionPage.getByTestId("create-password-confirm-input").fill(DEFAULT_TEST_PASSWORD);
|
|
63
72
|
await extensionPage.getByTestId("create-password-terms").click();
|
|
64
73
|
await extensionPage.getByTestId("create-password-submit").click();
|
|
65
74
|
await extensionPage.getByTestId("metametrics-checkbox").click();
|
|
66
75
|
await extensionPage.getByTestId("metametrics-i-agree").click();
|
|
67
76
|
await extensionPage.getByTestId("manage-default-settings").click();
|
|
68
|
-
await extensionPage.getByTestId("category-item-General").click();
|
|
69
|
-
await extensionPage.getByTestId("basic-functionality-toggle").locator(".toggle-button").click();
|
|
70
|
-
await extensionPage.getByText("I understand and want to continue").click();
|
|
71
|
-
await extensionPage.getByTestId("basic-configuration-modal-toggle-button").click();
|
|
72
|
-
await extensionPage.getByTestId("category-back-button").click();
|
|
73
77
|
await extensionPage.getByTestId("category-item-Assets").click();
|
|
74
78
|
await extensionPage.getByTestId("privacy-settings-settings").locator(".toggle-button").nth(0).click();
|
|
75
79
|
await extensionPage.getByTestId("privacy-settings-settings").locator(".toggle-button").nth(1).click();
|
|
@@ -81,40 +85,44 @@ async function completeOnboarding$1(extensionPage, seedPhrase) {
|
|
|
81
85
|
await extensionPage.getByTestId("onboarding-complete-done").click();
|
|
82
86
|
await extensionPage.close();
|
|
83
87
|
}
|
|
84
|
-
async function
|
|
85
|
-
const context = page.__extensionContext;
|
|
86
|
-
const extensionId = page.__extensionId;
|
|
88
|
+
async function approveMetaMask(context, extensionId) {
|
|
87
89
|
const extensionPopup = await findExtensionPopup$2(context, extensionId);
|
|
88
|
-
await extensionPopup.getByTestId("confirm-btn").click();
|
|
90
|
+
await extensionPopup.getByTestId("confirm-btn").or(extensionPopup.getByTestId("confirm-footer-button")).or(extensionPopup.getByTestId("confirm-sign-message-confirm-snap-footer-button")).first().click();
|
|
89
91
|
await extensionPopup.close();
|
|
90
92
|
}
|
|
91
|
-
async function
|
|
92
|
-
const context = page.__extensionContext;
|
|
93
|
-
const extensionId = page.__extensionId;
|
|
94
|
-
const extensionPopup = await findExtensionPopup$2(context, extensionId);
|
|
95
|
-
await extensionPopup.getByTestId("confirm-footer-button").click();
|
|
96
|
-
await extensionPopup.close();
|
|
97
|
-
}
|
|
98
|
-
async function rejectMetaMask(page) {
|
|
99
|
-
const context = page.__extensionContext;
|
|
100
|
-
const extensionId = page.__extensionId;
|
|
93
|
+
async function rejectMetaMask(context, extensionId) {
|
|
101
94
|
const extensionPopup = await findExtensionPopup$2(context, extensionId);
|
|
102
95
|
await extensionPopup.getByTestId("confirm-footer-cancel").or(extensionPopup.getByTestId("page-container-footer-cancel")).or(extensionPopup.getByRole("button", { name: /Reject|Cancel/i })).first().click();
|
|
103
96
|
await extensionPopup.close();
|
|
104
97
|
}
|
|
105
|
-
async function unlockMetaMask(
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
98
|
+
async function unlockMetaMask(context, extensionId) {
|
|
99
|
+
const sidePanelUrl = `chrome-extension://${extensionId}/sidepanel.html`;
|
|
100
|
+
const extensionUrlPrefix = `chrome-extension://${extensionId}/`;
|
|
101
|
+
let unlockPage;
|
|
102
|
+
const deadline = Date.now() + 1e4;
|
|
103
|
+
while (Date.now() < deadline) {
|
|
104
|
+
const extensionPages = context.pages().filter((p) => p.url().startsWith(extensionUrlPrefix));
|
|
105
|
+
unlockPage = extensionPages.find((p) => p.url().includes("unlock"));
|
|
106
|
+
if (unlockPage) break;
|
|
107
|
+
if (extensionPages.length > 0) break;
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
109
|
+
}
|
|
110
|
+
if (unlockPage) {
|
|
111
|
+
await unlockPage.bringToFront();
|
|
112
|
+
await unlockPage.waitForLoadState("domcontentloaded");
|
|
113
|
+
await unlockPage.getByTestId("unlock-password").fill(DEFAULT_TEST_PASSWORD);
|
|
114
|
+
await unlockPage.getByTestId("unlock-submit").click();
|
|
115
|
+
await unlockPage.getByTestId("onboarding-complete-done").click({ timeout: 3e3 }).catch(() => {});
|
|
116
|
+
await unlockPage.goto(sidePanelUrl);
|
|
117
|
+
await unlockPage.waitForLoadState("domcontentloaded");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (context.pages().find((p) => p.url().startsWith(sidePanelUrl))) return;
|
|
121
|
+
const sidePanel = await context.newPage();
|
|
122
|
+
await sidePanel.goto(sidePanelUrl);
|
|
123
|
+
await sidePanel.waitForLoadState("domcontentloaded");
|
|
114
124
|
}
|
|
115
|
-
async function importSeedPhrase(
|
|
116
|
-
const context = page.__extensionContext;
|
|
117
|
-
const extensionId = page.__extensionId;
|
|
125
|
+
async function importSeedPhrase(context, extensionId, { seedPhrase }) {
|
|
118
126
|
const extensionPage = await findOnboardingPage$1(context, extensionId);
|
|
119
127
|
try {
|
|
120
128
|
await completeOnboarding$1(extensionPage, seedPhrase);
|
|
@@ -126,19 +134,17 @@ async function importSeedPhrase(page, { seedPhrase }) {
|
|
|
126
134
|
/* c8 ignore stop */
|
|
127
135
|
|
|
128
136
|
//#endregion
|
|
129
|
-
//#region src/
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
};
|
|
137
|
+
//#region src/utils/find-extension-popup.ts
|
|
138
|
+
/**
|
|
139
|
+
* Poll the BrowserContext for a page whose URL points at the given Chrome
|
|
140
|
+
* extension. Returns once the page is reachable and DOM-loaded.
|
|
141
|
+
*/
|
|
135
142
|
/* c8 ignore start */
|
|
136
|
-
async function findExtensionPopup$1(context, extensionId) {
|
|
137
|
-
const maxAttempts = 10;
|
|
138
|
-
const retryDelay = 500;
|
|
143
|
+
async function findExtensionPopup$1(context, extensionId, options = {}) {
|
|
144
|
+
const { maxAttempts = 10, retryDelay = 500, viewport } = options;
|
|
139
145
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
140
|
-
const
|
|
141
|
-
|
|
146
|
+
for (const p of context.pages()) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
147
|
+
if (viewport) await p.setViewportSize(viewport);
|
|
142
148
|
await p.waitForLoadState("domcontentloaded");
|
|
143
149
|
return p;
|
|
144
150
|
}
|
|
@@ -147,25 +153,29 @@ async function findExtensionPopup$1(context, extensionId) {
|
|
|
147
153
|
throw new Error(`Extension popup not found for ID: ${extensionId}`);
|
|
148
154
|
}
|
|
149
155
|
/* c8 ignore stop */
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/wallets/polkadot-js.ts
|
|
159
|
+
const VERSION$1 = "0.62.6";
|
|
160
|
+
const POLKADOT_JS_CONFIG = {
|
|
161
|
+
downloadUrl: `https://github.com/polkadot-js/extension/releases/download/v${VERSION$1}/master-chrome-build.zip`,
|
|
162
|
+
extensionName: `polkadot-extension-${VERSION$1}`
|
|
163
|
+
};
|
|
150
164
|
async function getPolkadotJSExtensionPath() {
|
|
151
165
|
const extensionsDir = path.resolve(process.cwd(), ".chroma");
|
|
152
166
|
const extensionDir = path.join(extensionsDir, POLKADOT_JS_CONFIG.extensionName);
|
|
153
|
-
if (
|
|
167
|
+
if ((await fs.promises.readdir(extensionDir).catch(() => [])).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`);
|
|
154
168
|
return extensionDir;
|
|
155
169
|
}
|
|
156
170
|
/* c8 ignore start */
|
|
157
|
-
async function importPolkadotJSAccount(
|
|
158
|
-
const
|
|
159
|
-
const extensionPopupUrl = `chrome-extension://${page.__extensionId}/index.html`;
|
|
171
|
+
async function importPolkadotJSAccount(context, extensionId, { seed, name = "Test Account", password = DEFAULT_TEST_PASSWORD }) {
|
|
172
|
+
const extensionPopupUrl = `chrome-extension://${extensionId}/index.html`;
|
|
160
173
|
const extensionPage = await context.newPage();
|
|
161
174
|
try {
|
|
162
175
|
await extensionPage.goto(extensionPopupUrl);
|
|
163
176
|
const understoodButton = extensionPage.getByRole("button", { name: "Understood, let me continue" });
|
|
164
|
-
if (await understoodButton.count() > 0)
|
|
165
|
-
|
|
166
|
-
await extensionPage.waitForTimeout(100);
|
|
167
|
-
}
|
|
168
|
-
if (await extensionPage.getByRole("button", { name: "I Understand" }).isVisible()) await extensionPage.getByRole("button", { name: "I Understand" }).click();
|
|
177
|
+
if (await understoodButton.count() > 0) await understoodButton.click();
|
|
178
|
+
await extensionPage.getByRole("button", { name: "I Understand" }).click({ timeout: 1e3 }).catch(() => {});
|
|
169
179
|
await extensionPage.goto(`${extensionPopupUrl}#/account/import-seed`);
|
|
170
180
|
await extensionPage.locator("textarea").fill(seed);
|
|
171
181
|
await extensionPage.locator("button:has-text(\"Next\")").click();
|
|
@@ -177,25 +187,19 @@ async function importPolkadotJSAccount(page, { seed, name = "Test Account", pass
|
|
|
177
187
|
await extensionPage.close();
|
|
178
188
|
}
|
|
179
189
|
}
|
|
180
|
-
async function authorizePolkadotJS(
|
|
181
|
-
const context = page.__extensionContext;
|
|
182
|
-
const extensionId = page.__extensionId;
|
|
190
|
+
async function authorizePolkadotJS(context, extensionId) {
|
|
183
191
|
const extensionPopup = await findExtensionPopup$1(context, extensionId);
|
|
184
192
|
if (await extensionPopup.getByRole("button", { name: "I Understand" }).isVisible()) await extensionPopup.getByRole("button", { name: "I Understand" }).click();
|
|
185
193
|
if (!await extensionPopup.getByText("Select all").locator("..").locator("input[type=\"checkbox\"]").isChecked().catch(() => false)) await extensionPopup.getByText("Select all").click();
|
|
186
194
|
await extensionPopup.getByRole("button", { name: /Connect \d+ account\(s\)/ }).click();
|
|
187
195
|
}
|
|
188
|
-
async function approvePolkadotJSTx(
|
|
189
|
-
const { password =
|
|
190
|
-
const context = page.__extensionContext;
|
|
191
|
-
const extensionId = page.__extensionId;
|
|
196
|
+
async function approvePolkadotJSTx(context, extensionId, options = {}) {
|
|
197
|
+
const { password = DEFAULT_TEST_PASSWORD } = options;
|
|
192
198
|
const extensionPopup = await findExtensionPopup$1(context, extensionId);
|
|
193
199
|
await extensionPopup.getByRole("textbox").fill(password);
|
|
194
200
|
await extensionPopup.getByRole("button", { name: "Sign the transaction" }).click();
|
|
195
201
|
}
|
|
196
|
-
async function rejectPolkadotJSTx(
|
|
197
|
-
const context = page.__extensionContext;
|
|
198
|
-
const extensionId = page.__extensionId;
|
|
202
|
+
async function rejectPolkadotJSTx(context, extensionId) {
|
|
199
203
|
await (await findExtensionPopup$1(context, extensionId)).getByRole("link", { name: "Cancel" }).click();
|
|
200
204
|
}
|
|
201
205
|
/* c8 ignore stop */
|
|
@@ -208,28 +212,17 @@ const TALISMAN_CONFIG = {
|
|
|
208
212
|
extensionName: `talisman-extension-${VERSION}`
|
|
209
213
|
};
|
|
210
214
|
/* c8 ignore start */
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
|
|
217
|
-
await p.setViewportSize({
|
|
218
|
-
width: 400,
|
|
219
|
-
height: 600
|
|
220
|
-
});
|
|
221
|
-
await p.waitForLoadState("domcontentloaded");
|
|
222
|
-
return p;
|
|
223
|
-
}
|
|
224
|
-
if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
225
|
-
}
|
|
226
|
-
throw new Error(`Extension popup not found for ID: ${extensionId}`);
|
|
215
|
+
function findExtensionPopup(context, extensionId) {
|
|
216
|
+
return findExtensionPopup$1(context, extensionId, { viewport: {
|
|
217
|
+
width: 400,
|
|
218
|
+
height: 600
|
|
219
|
+
} });
|
|
227
220
|
}
|
|
228
221
|
/* c8 ignore stop */
|
|
229
222
|
async function getTalismanExtensionPath() {
|
|
230
223
|
const extensionsDir = path.resolve(process.cwd(), ".chroma");
|
|
231
224
|
const extensionDir = path.join(extensionsDir, TALISMAN_CONFIG.extensionName);
|
|
232
|
-
if (
|
|
225
|
+
if ((await fs.promises.readdir(extensionDir).catch(() => [])).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`);
|
|
233
226
|
return extensionDir;
|
|
234
227
|
}
|
|
235
228
|
/* c8 ignore start */
|
|
@@ -260,9 +253,7 @@ async function completeOnboarding(extensionPage, password) {
|
|
|
260
253
|
await extensionPage.getByRole("link", { name: "Security & Privacy" }).click();
|
|
261
254
|
await extensionPage.getByTestId("component-toggle-button").first().click();
|
|
262
255
|
}
|
|
263
|
-
async function importPolkadotMnemonic(
|
|
264
|
-
const context = page.__extensionContext;
|
|
265
|
-
const extensionId = page.__extensionId;
|
|
256
|
+
async function importPolkadotMnemonic(context, extensionId, { seed, name = "Test Account", password = DEFAULT_TEST_PASSWORD }) {
|
|
266
257
|
const extensionPage = await findOnboardingPage(context, extensionId);
|
|
267
258
|
try {
|
|
268
259
|
await completeOnboarding(extensionPage, password);
|
|
@@ -281,9 +272,7 @@ async function importPolkadotMnemonic(page, { seed, name = "Test Account", passw
|
|
|
281
272
|
throw error;
|
|
282
273
|
}
|
|
283
274
|
}
|
|
284
|
-
async function importEthPrivateKey(
|
|
285
|
-
const context = page.__extensionContext;
|
|
286
|
-
const extensionId = page.__extensionId;
|
|
275
|
+
async function importEthPrivateKey(context, extensionId, { seed, name = "Test Account", password = DEFAULT_TEST_PASSWORD }) {
|
|
287
276
|
const extensionPage = await findOnboardingPage(context, extensionId);
|
|
288
277
|
try {
|
|
289
278
|
await completeOnboarding(extensionPage, password);
|
|
@@ -303,9 +292,7 @@ async function importEthPrivateKey(page, { seed, name = "Test Account", password
|
|
|
303
292
|
throw error;
|
|
304
293
|
}
|
|
305
294
|
}
|
|
306
|
-
async function authorizeTalisman(
|
|
307
|
-
const context = page.__extensionContext;
|
|
308
|
-
const extensionId = page.__extensionId;
|
|
295
|
+
async function authorizeTalisman(context, extensionId, options = {}) {
|
|
309
296
|
const { accountName = "Test Account" } = options;
|
|
310
297
|
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
311
298
|
await extensionPopup.waitForLoadState("domcontentloaded");
|
|
@@ -318,16 +305,12 @@ async function authorizeTalisman(page, options = {}) {
|
|
|
318
305
|
await (await findExtensionPopup(context, extensionId)).getByRole("button", { name: "Approve" }).click();
|
|
319
306
|
} catch {}
|
|
320
307
|
}
|
|
321
|
-
async function approveTalismanTx(
|
|
322
|
-
const context = page.__extensionContext;
|
|
323
|
-
const extensionId = page.__extensionId;
|
|
308
|
+
async function approveTalismanTx(context, extensionId) {
|
|
324
309
|
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
325
310
|
if (await extensionPopup.getByRole("button", { name: "Yes" }).isVisible()) await extensionPopup.getByRole("button", { name: "Yes" }).click();
|
|
326
311
|
await extensionPopup.getByRole("button", { name: "Approve" }).click();
|
|
327
312
|
}
|
|
328
|
-
async function rejectTalismanTx(
|
|
329
|
-
const context = page.__extensionContext;
|
|
330
|
-
const extensionId = page.__extensionId;
|
|
313
|
+
async function rejectTalismanTx(context, extensionId) {
|
|
331
314
|
const extensionPopup = await findExtensionPopup(context, extensionId);
|
|
332
315
|
await extensionPopup.getByTestId("connection-reject-button").or(extensionPopup.getByRole("button", { name: "Cancel" })).or(extensionPopup.getByRole("button", { name: "Reject" })).click();
|
|
333
316
|
}
|
|
@@ -336,30 +319,14 @@ async function rejectTalismanTx(page) {
|
|
|
336
319
|
//#endregion
|
|
337
320
|
//#region src/context-playwright/wallet-factory.ts
|
|
338
321
|
/* c8 ignore start */
|
|
339
|
-
function createExtendedPage(page, context, extensionId) {
|
|
340
|
-
const extPage = page;
|
|
341
|
-
extPage.__extensionContext = context;
|
|
342
|
-
extPage.__extensionId = extensionId;
|
|
343
|
-
return extPage;
|
|
344
|
-
}
|
|
345
|
-
/* c8 ignore stop */
|
|
346
|
-
/* c8 ignore start */
|
|
347
322
|
function createPolkadotJsWallet(extensionId, context) {
|
|
348
323
|
return {
|
|
349
324
|
extensionId,
|
|
350
325
|
type: "polkadot-js",
|
|
351
|
-
importMnemonic:
|
|
352
|
-
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
await authorizePolkadotJS(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
356
|
-
},
|
|
357
|
-
approveTx: async (options = {}) => {
|
|
358
|
-
await approvePolkadotJSTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId), options);
|
|
359
|
-
},
|
|
360
|
-
rejectTx: async () => {
|
|
361
|
-
await rejectPolkadotJSTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
362
|
-
}
|
|
326
|
+
importMnemonic: (options) => importPolkadotJSAccount(context, extensionId, options),
|
|
327
|
+
authorize: () => authorizePolkadotJS(context, extensionId),
|
|
328
|
+
approveTx: (options = {}) => approvePolkadotJSTx(context, extensionId, options),
|
|
329
|
+
rejectTx: () => rejectPolkadotJSTx(context, extensionId)
|
|
363
330
|
};
|
|
364
331
|
}
|
|
365
332
|
/* c8 ignore stop */
|
|
@@ -369,29 +336,23 @@ function createTalismanWallet(extensionId, context) {
|
|
|
369
336
|
return {
|
|
370
337
|
extensionId,
|
|
371
338
|
type: "talisman",
|
|
372
|
-
importPolkadotMnemonic:
|
|
373
|
-
const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
|
|
339
|
+
importPolkadotMnemonic: (options) => {
|
|
374
340
|
importedAccountName = options.name || "Test Account";
|
|
375
|
-
|
|
341
|
+
return importPolkadotMnemonic(context, extensionId, options);
|
|
376
342
|
},
|
|
377
|
-
importEthPrivateKey:
|
|
378
|
-
const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
|
|
343
|
+
importEthPrivateKey: (options) => {
|
|
379
344
|
importedAccountName = options.name || "Test Account";
|
|
380
|
-
|
|
345
|
+
return importEthPrivateKey(context, extensionId, {
|
|
381
346
|
seed: options.privateKey,
|
|
382
347
|
name: options.name,
|
|
383
348
|
password: options.password
|
|
384
349
|
});
|
|
385
350
|
},
|
|
386
|
-
authorize:
|
|
387
|
-
|
|
388
|
-
},
|
|
389
|
-
approveTx: async () => {
|
|
390
|
-
await approveTalismanTx(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
351
|
+
authorize: (options = {}) => {
|
|
352
|
+
return authorizeTalisman(context, extensionId, { accountName: options.accountName || importedAccountName });
|
|
391
353
|
},
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
354
|
+
approveTx: () => approveTalismanTx(context, extensionId),
|
|
355
|
+
rejectTx: () => rejectTalismanTx(context, extensionId)
|
|
395
356
|
};
|
|
396
357
|
}
|
|
397
358
|
/* c8 ignore stop */
|
|
@@ -400,21 +361,10 @@ function createMetaMaskWallet(extensionId, context) {
|
|
|
400
361
|
return {
|
|
401
362
|
extensionId,
|
|
402
363
|
type: "metamask",
|
|
403
|
-
importSeedPhrase:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
await unlockMetaMask(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
408
|
-
},
|
|
409
|
-
authorize: async () => {
|
|
410
|
-
await authorizeMetaMask(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
411
|
-
},
|
|
412
|
-
reject: async () => {
|
|
413
|
-
await rejectMetaMask(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
414
|
-
},
|
|
415
|
-
confirm: async () => {
|
|
416
|
-
await confirmMetaMask(createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId));
|
|
417
|
-
}
|
|
364
|
+
importSeedPhrase: (options) => importSeedPhrase(context, extensionId, options),
|
|
365
|
+
unlock: () => unlockMetaMask(context, extensionId),
|
|
366
|
+
approve: () => approveMetaMask(context, extensionId),
|
|
367
|
+
reject: () => rejectMetaMask(context, extensionId)
|
|
418
368
|
};
|
|
419
369
|
}
|
|
420
370
|
/* c8 ignore stop */
|
|
@@ -438,12 +388,22 @@ async function getExtensionPathForWallet(config) {
|
|
|
438
388
|
function createWalletTest(options = {}) {
|
|
439
389
|
const { headless = false, slowMo = 150 } = options;
|
|
440
390
|
const walletConfigs = options.wallets && options.wallets.length > 0 ? options.wallets : [{ type: "polkadot-js" }];
|
|
441
|
-
const isMultiWallet = walletConfigs.length > 1;
|
|
442
391
|
/* c8 ignore start */
|
|
443
392
|
return test$1.extend({
|
|
444
|
-
walletContext: [async ({}, use) => {
|
|
393
|
+
walletContext: [async ({}, use, workerInfo) => {
|
|
445
394
|
const extensionPathsString = (await Promise.all(walletConfigs.map((config) => getExtensionPathForWallet(config)))).join(",");
|
|
446
|
-
const
|
|
395
|
+
const userDataDirOption = options.userDataDir;
|
|
396
|
+
const userDataDir = typeof userDataDirOption === "function" ? await userDataDirOption({ workerIndex: workerInfo.workerIndex }) : userDataDirOption ?? "";
|
|
397
|
+
if (options.cloneUserDataDirFrom && userDataDir) {
|
|
398
|
+
const sourceAbs = resolve(options.cloneUserDataDirFrom);
|
|
399
|
+
if (sourceAbs === resolve(userDataDir)) throw new Error(`cloneUserDataDirFrom and userDataDir must be different paths; both resolved to "${sourceAbs}"`);
|
|
400
|
+
await rm(userDataDir, {
|
|
401
|
+
recursive: true,
|
|
402
|
+
force: true
|
|
403
|
+
});
|
|
404
|
+
await cp(options.cloneUserDataDirFrom, userDataDir, { recursive: true });
|
|
405
|
+
}
|
|
406
|
+
const context = await chromium.launchPersistentContext(userDataDir, {
|
|
447
407
|
headless,
|
|
448
408
|
channel: "chromium",
|
|
449
409
|
args: [`--load-extension=${extensionPathsString}`, `--disable-extensions-except=${extensionPathsString}`],
|
|
@@ -454,21 +414,21 @@ function createWalletTest(options = {}) {
|
|
|
454
414
|
}, { scope: "worker" }],
|
|
455
415
|
walletExtensionIds: [async ({ walletContext }, use) => {
|
|
456
416
|
const extensionIds = /* @__PURE__ */ new Map();
|
|
457
|
-
|
|
458
|
-
|
|
417
|
+
const expected = walletConfigs.length;
|
|
418
|
+
const deadline = Date.now() + 1e4;
|
|
419
|
+
while (walletContext.serviceWorkers().length < expected) {
|
|
420
|
+
if (Date.now() > deadline) break;
|
|
421
|
+
await Promise.race([walletContext.waitForEvent("serviceworker", { timeout: 2e3 }).catch(() => {}), new Promise((resolve) => setTimeout(resolve, 200))]);
|
|
422
|
+
}
|
|
459
423
|
const allServiceWorkers = walletContext.serviceWorkers();
|
|
460
424
|
for (let i = 0; i < walletConfigs.length && i < allServiceWorkers.length; i++) {
|
|
461
425
|
const extensionId = allServiceWorkers[i].url().split("/")[2];
|
|
462
|
-
|
|
463
|
-
extensionIds.set(walletType, extensionId);
|
|
426
|
+
extensionIds.set(walletConfigs[i].type, extensionId);
|
|
464
427
|
}
|
|
465
428
|
await use(extensionIds);
|
|
466
429
|
}, { scope: "worker" }],
|
|
467
|
-
page: async ({ walletContext
|
|
468
|
-
|
|
469
|
-
extendedPage.__extensionContext = walletContext;
|
|
470
|
-
extendedPage.__walletExtensionIds = walletExtensionIds;
|
|
471
|
-
await use(extendedPage);
|
|
430
|
+
page: async ({ walletContext }, use) => {
|
|
431
|
+
await use(walletContext.pages()[0] || await walletContext.newPage());
|
|
472
432
|
},
|
|
473
433
|
wallets: async ({ walletContext, walletExtensionIds }, use) => {
|
|
474
434
|
const walletMap = {};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avalix/chroma",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
5
|
-
"description": "End-to-end testing library for Polkadot wallet interactions",
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"description": "End-to-end testing library for Polkadot, Ethereum, and Solana wallet interactions",
|
|
6
6
|
"author": "Avalix Labs",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"homepage": "https://chroma-docs.up.railway.app/docs",
|
|
@@ -51,16 +51,15 @@
|
|
|
51
51
|
"test:unit:coverage": "vitest run --coverage"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@playwright/test": "^1.
|
|
54
|
+
"@playwright/test": "^1.59.1"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"adm-zip": "^0.5.16"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@antfu/eslint-config": "^6.5.1",
|
|
61
|
-
"@playwright/test": "^1.
|
|
61
|
+
"@playwright/test": "^1.59.1",
|
|
62
62
|
"@types/node": "^24.10.2",
|
|
63
|
-
"@types/unzipper": "^0.10.11",
|
|
64
63
|
"@vitest/coverage-v8": "4.0.18",
|
|
65
64
|
"eslint": "^9.39.1",
|
|
66
65
|
"tsdown": "^0.20.1",
|
|
@@ -18,11 +18,8 @@ async function getVersion(): Promise<string> {
|
|
|
18
18
|
|
|
19
19
|
async function clearChromaDir(): Promise<void> {
|
|
20
20
|
const chromaDir = path.resolve(process.cwd(), '.chroma')
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.log('šļø Clearing existing .chroma directory...')
|
|
24
|
-
await fs.promises.rm(chromaDir, { recursive: true, force: true })
|
|
25
|
-
}
|
|
21
|
+
console.log('šļø Clearing existing .chroma directory...')
|
|
22
|
+
await fs.promises.rm(chromaDir, { recursive: true, force: true })
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
async function main() {
|
|
@@ -34,23 +31,21 @@ async function main() {
|
|
|
34
31
|
// Clear existing .chroma directory
|
|
35
32
|
await clearChromaDir()
|
|
36
33
|
|
|
37
|
-
// Download
|
|
38
|
-
await
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
extensionName: METAMASK_CONFIG.extensionName,
|
|
53
|
-
})
|
|
34
|
+
// Download all extensions in parallel ā they're independent network ops
|
|
35
|
+
await Promise.all([
|
|
36
|
+
downloadAndExtractExtension({
|
|
37
|
+
downloadUrl: POLKADOT_JS_CONFIG.downloadUrl,
|
|
38
|
+
extensionName: POLKADOT_JS_CONFIG.extensionName,
|
|
39
|
+
}),
|
|
40
|
+
downloadAndExtractExtension({
|
|
41
|
+
downloadUrl: TALISMAN_CONFIG.downloadUrl,
|
|
42
|
+
extensionName: TALISMAN_CONFIG.extensionName,
|
|
43
|
+
}),
|
|
44
|
+
downloadAndExtractExtension({
|
|
45
|
+
downloadUrl: METAMASK_CONFIG.downloadUrl,
|
|
46
|
+
extensionName: METAMASK_CONFIG.extensionName,
|
|
47
|
+
}),
|
|
48
|
+
])
|
|
54
49
|
|
|
55
50
|
console.log('\nā
All extensions downloaded successfully!')
|
|
56
51
|
console.log('You can now run your Playwright tests.')
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
import { getPolkadotJSExtensionPath } from '../wallets/polkadot-js.js'
|
|
4
|
-
import { getTalismanExtensionPath } from '../wallets/talisman.js'
|
|
5
|
-
import { createWalletTest, getExtensionPathForWallet } from './index.js'
|
|
2
|
+
import { createWalletTest } from './index.js'
|
|
6
3
|
|
|
7
4
|
// Mock @playwright/test
|
|
8
5
|
vi.mock('@playwright/test', () => {
|
|
@@ -118,44 +115,37 @@ describe('context-playwright/index', () => {
|
|
|
118
115
|
expect(result).toBeDefined()
|
|
119
116
|
})
|
|
120
117
|
|
|
121
|
-
it('should
|
|
118
|
+
it('should accept userDataDir as a string', () => {
|
|
122
119
|
const result = createWalletTest({
|
|
123
|
-
|
|
124
|
-
type: 'polkadot-js',
|
|
125
|
-
downloadUrl: 'https://custom-url.com/extension.zip',
|
|
126
|
-
}],
|
|
120
|
+
userDataDir: '.cache/wallet-setup',
|
|
127
121
|
})
|
|
128
122
|
|
|
129
123
|
expect(result).toBeDefined()
|
|
130
124
|
})
|
|
131
|
-
})
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
it('should accept userDataDir as a per-worker function', () => {
|
|
127
|
+
const result = createWalletTest({
|
|
128
|
+
userDataDir: ({ workerIndex }) => `.cache/wallet-w${workerIndex}`,
|
|
129
|
+
})
|
|
136
130
|
|
|
137
|
-
expect(result).
|
|
138
|
-
expect(getPolkadotJSExtensionPath).toHaveBeenCalled()
|
|
131
|
+
expect(result).toBeDefined()
|
|
139
132
|
})
|
|
140
133
|
|
|
141
|
-
it('should
|
|
142
|
-
const result =
|
|
134
|
+
it('should accept userDataDir as an async function', () => {
|
|
135
|
+
const result = createWalletTest({
|
|
136
|
+
userDataDir: async ({ workerIndex }) => `.cache/wallet-w${workerIndex}`,
|
|
137
|
+
})
|
|
143
138
|
|
|
144
|
-
expect(result).
|
|
145
|
-
expect(getTalismanExtensionPath).toHaveBeenCalled()
|
|
139
|
+
expect(result).toBeDefined()
|
|
146
140
|
})
|
|
147
141
|
|
|
148
|
-
it('should
|
|
149
|
-
const result =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
})
|
|
142
|
+
it('should accept cloneUserDataDirFrom alongside userDataDir', () => {
|
|
143
|
+
const result = createWalletTest({
|
|
144
|
+
userDataDir: ({ workerIndex }) => `.cache/wallet-w${workerIndex}`,
|
|
145
|
+
cloneUserDataDirFrom: '.cache/wallet-setup',
|
|
146
|
+
})
|
|
154
147
|
|
|
155
|
-
|
|
156
|
-
await expect(
|
|
157
|
-
getExtensionPathForWallet({ type: 'unsupported' as any }),
|
|
158
|
-
).rejects.toThrow('Unsupported wallet type: unsupported')
|
|
148
|
+
expect(result).toBeDefined()
|
|
159
149
|
})
|
|
160
150
|
})
|
|
161
151
|
})
|