w3wallets 0.10.2 → 1.0.0-beta.10
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 +128 -21
- package/dist/index.d.mts +172 -54
- package/dist/index.d.ts +172 -54
- package/dist/index.js +679 -301
- package/dist/index.mjs +676 -300
- package/dist/scripts/cache.js +317 -0
- package/package.json +17 -12
- package/src/scripts/download.js +426 -65
package/dist/index.js
CHANGED
|
@@ -30,126 +30,697 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
Metamask: () => Metamask,
|
|
34
|
+
PolkadotJS: () => PolkadotJS,
|
|
35
|
+
config: () => config,
|
|
36
|
+
createWallet: () => createWallet,
|
|
37
|
+
debug: () => debug,
|
|
38
|
+
isCachedConfig: () => isCachedConfig,
|
|
39
|
+
metamask: () => metamask,
|
|
40
|
+
polkadotJS: () => polkadotJS,
|
|
41
|
+
prepareWallet: () => prepareWallet,
|
|
33
42
|
withWallets: () => withWallets
|
|
34
43
|
});
|
|
35
44
|
module.exports = __toCommonJS(index_exports);
|
|
36
45
|
|
|
37
46
|
// src/withWallets.ts
|
|
47
|
+
var import_path3 = __toESM(require("path"));
|
|
48
|
+
var import_fs3 = __toESM(require("fs"));
|
|
49
|
+
var import_test2 = require("@playwright/test");
|
|
50
|
+
|
|
51
|
+
// src/cache/types.ts
|
|
52
|
+
function isCachedConfig(config2) {
|
|
53
|
+
return "__cached" in config2 && config2.__cached === true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/cache/buildCache.ts
|
|
57
|
+
var import_path2 = __toESM(require("path"));
|
|
58
|
+
var import_fs2 = __toESM(require("fs"));
|
|
59
|
+
var import_test = require("@playwright/test");
|
|
60
|
+
|
|
61
|
+
// src/cache/constants.ts
|
|
62
|
+
var CACHE_DIR = ".w3wallets/cache";
|
|
63
|
+
|
|
64
|
+
// src/core/utils.ts
|
|
38
65
|
var import_path = __toESM(require("path"));
|
|
39
66
|
var import_fs = __toESM(require("fs"));
|
|
67
|
+
var import_crypto = __toESM(require("crypto"));
|
|
68
|
+
function sleep(ms) {
|
|
69
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
70
|
+
}
|
|
71
|
+
function getExtensionId(extensionPath) {
|
|
72
|
+
const absolutePath = import_path.default.resolve(extensionPath);
|
|
73
|
+
const manifestPath = import_path.default.join(absolutePath, "manifest.json");
|
|
74
|
+
const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
|
|
75
|
+
let dataToHash;
|
|
76
|
+
if (manifest.key) {
|
|
77
|
+
dataToHash = Buffer.from(manifest.key, "base64");
|
|
78
|
+
} else {
|
|
79
|
+
dataToHash = Buffer.from(absolutePath);
|
|
80
|
+
}
|
|
81
|
+
const hash = import_crypto.default.createHash("sha256").update(dataToHash).digest();
|
|
82
|
+
const ALPHABET = "abcdefghijklmnop";
|
|
83
|
+
let extensionId = "";
|
|
84
|
+
for (let i = 0; i < 16; i++) {
|
|
85
|
+
const byte = hash[i];
|
|
86
|
+
extensionId += ALPHABET[byte >> 4 & 15];
|
|
87
|
+
extensionId += ALPHABET[byte & 15];
|
|
88
|
+
}
|
|
89
|
+
return extensionId;
|
|
90
|
+
}
|
|
40
91
|
|
|
41
|
-
//
|
|
42
|
-
var
|
|
92
|
+
// src/timeouts.ts
|
|
93
|
+
var SERVICE_WORKER_TIMEOUT = 3e4;
|
|
94
|
+
var SERVICE_WORKER_POLL_INTERVAL = 500;
|
|
95
|
+
var POPUP_VISIBILITY_TIMEOUT = 2e3;
|
|
96
|
+
var SHIELD_CLOSE_TIMEOUT = 1e3;
|
|
97
|
+
var ARIA_CLOSE_TIMEOUT = 500;
|
|
98
|
+
var POPUP_HIDDEN_TIMEOUT = 3e3;
|
|
99
|
+
var POST_UNLOCK_TIMEOUT = 3e4;
|
|
100
|
+
var NOTIFICATION_CHECK_TIMEOUT = 5e3;
|
|
101
|
+
var POST_CLICK_TIMEOUT = 1e4;
|
|
102
|
+
var BUTTON_OR_POPUP_TIMEOUT = 3e4;
|
|
103
|
+
var LAST_RESORT_CLICK_TIMEOUT = 1e4;
|
|
104
|
+
var LOCK_SCREEN_TIMEOUT = 3e4;
|
|
105
|
+
var MENU_BUTTON_TIMEOUT = 3e4;
|
|
106
|
+
var ONBOARD_VISIBLE_TIMEOUT = 3e4;
|
|
107
|
+
var ROUTE_RETRY_TIMEOUT = 5e3;
|
|
108
|
+
var MAX_ROUTE_ATTEMPTS = 5;
|
|
109
|
+
var MNEMONIC_KEY_DELAY = 5;
|
|
110
|
+
var MNEMONIC_WORD_DELAY = 100;
|
|
111
|
+
|
|
112
|
+
// src/debug.ts
|
|
113
|
+
var isDebug = () => process.env.W3WALLETS_DEBUG === "true";
|
|
114
|
+
function debug(message) {
|
|
115
|
+
if (!isDebug()) return;
|
|
116
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
117
|
+
console.log(`[w3wallets ${ts}] ${message}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/cache/buildCache.ts
|
|
121
|
+
function findCacheDir(walletName) {
|
|
122
|
+
const cacheRoot = import_path2.default.join(process.cwd(), CACHE_DIR);
|
|
123
|
+
if (!import_fs2.default.existsSync(cacheRoot)) return null;
|
|
124
|
+
const entries = import_fs2.default.readdirSync(cacheRoot, { withFileTypes: true });
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
127
|
+
const metaPath = import_path2.default.join(cacheRoot, entry.name, ".meta.json");
|
|
128
|
+
if (!import_fs2.default.existsSync(metaPath)) continue;
|
|
129
|
+
try {
|
|
130
|
+
const meta = JSON.parse(import_fs2.default.readFileSync(metaPath, "utf-8"));
|
|
131
|
+
if (meta.name === walletName) {
|
|
132
|
+
return import_path2.default.join(cacheRoot, entry.name);
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
43
140
|
|
|
44
141
|
// src/withWallets.ts
|
|
45
|
-
var
|
|
142
|
+
var W3WALLETS_DIR = ".w3wallets";
|
|
143
|
+
var MIN_PLAYWRIGHT_VERSION = "1.57.0";
|
|
144
|
+
function checkPlaywrightVersion() {
|
|
145
|
+
try {
|
|
146
|
+
const pkgPath = require.resolve("@playwright/test/package.json");
|
|
147
|
+
const { version } = require(pkgPath);
|
|
148
|
+
const [minMajor, minMinor, minPatch] = MIN_PLAYWRIGHT_VERSION.split(".").map(Number);
|
|
149
|
+
const [curMajor, curMinor, curPatch] = version.split(".").map(Number);
|
|
150
|
+
const isBelow = curMajor < minMajor || curMajor === minMajor && curMinor < minMinor || curMajor === minMajor && curMinor === minMinor && curPatch < minPatch;
|
|
151
|
+
if (isBelow) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`w3wallets requires @playwright/test >= ${MIN_PLAYWRIGHT_VERSION}, but found ${version}.
|
|
154
|
+
Upgrade: npm install -D @playwright/test@latest`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err instanceof Error && err.message.startsWith("w3wallets requires")) {
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function withWallets(test, ...wallets) {
|
|
164
|
+
checkPlaywrightVersion();
|
|
165
|
+
const cachedCount = wallets.filter((w) => isCachedConfig(w)).length;
|
|
166
|
+
if (cachedCount > 0 && cachedCount < wallets.length) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
"Mixing cached and non-cached wallet configs is not supported. All wallets must be either cached (via prepareWallet) or non-cached."
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const useCachedContext = cachedCount > 0;
|
|
172
|
+
debug(`withWallets: ${wallets.length} wallet(s), cached=${useCachedContext}`);
|
|
173
|
+
const extensionInfo = wallets.map((w) => {
|
|
174
|
+
const extPath = import_path3.default.join(process.cwd(), W3WALLETS_DIR, w.extensionDir);
|
|
175
|
+
ensureWalletExtensionExists(extPath, w.name);
|
|
176
|
+
const extensionId = w.extensionId ?? getExtensionId(extPath);
|
|
177
|
+
return { path: extPath, id: extensionId, name: w.name };
|
|
178
|
+
});
|
|
179
|
+
const extensionPaths = extensionInfo.map((e) => e.path);
|
|
180
|
+
const fixtures = {
|
|
181
|
+
context: async ({}, use, testInfo) => {
|
|
182
|
+
const userDataDir = import_path3.default.join(
|
|
183
|
+
process.cwd(),
|
|
184
|
+
W3WALLETS_DIR,
|
|
185
|
+
".context",
|
|
186
|
+
testInfo.testId
|
|
187
|
+
);
|
|
188
|
+
cleanUserDataDir(userDataDir);
|
|
189
|
+
if (useCachedContext) {
|
|
190
|
+
const wallet = wallets[0];
|
|
191
|
+
const cacheDir = findCacheDir(wallet.name);
|
|
192
|
+
if (!cacheDir) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`Cache not found for wallet "${wallet.name}".
|
|
195
|
+
Searched: ${import_path3.default.join(process.cwd(), CACHE_DIR)}/
|
|
196
|
+
Rebuild: npx w3wallets cache --force <your-cache-dir>
|
|
197
|
+
Ensure your *.cache.ts setup file exports prepareWallet(...).`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
import_fs3.default.cpSync(cacheDir, userDataDir, { recursive: true });
|
|
201
|
+
}
|
|
202
|
+
debug(`Launching persistent context: ${userDataDir}`);
|
|
203
|
+
const context = await import_test2.chromium.launchPersistentContext(userDataDir, {
|
|
204
|
+
headless: testInfo.project.use.headless ?? true,
|
|
205
|
+
channel: "chromium",
|
|
206
|
+
args: [
|
|
207
|
+
`--disable-extensions-except=${extensionPaths.join(",")}`,
|
|
208
|
+
`--load-extension=${extensionPaths.join(",")}`
|
|
209
|
+
]
|
|
210
|
+
});
|
|
211
|
+
debug(`Waiting for ${extensionPaths.length} service worker(s)...`);
|
|
212
|
+
const swDeadline = Date.now() + SERVICE_WORKER_TIMEOUT;
|
|
213
|
+
while (context.serviceWorkers().length < extensionPaths.length) {
|
|
214
|
+
if (Date.now() > swDeadline) {
|
|
215
|
+
const found = context.serviceWorkers().length;
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Service worker initialization timed out after ${SERVICE_WORKER_TIMEOUT / 1e3}s.
|
|
218
|
+
Expected: ${extensionPaths.length} extension(s), found: ${found} service worker(s).
|
|
219
|
+
Extension paths: ${extensionPaths.map((p) => import_path3.default.relative(process.cwd(), p)).join(", ")}
|
|
220
|
+
Suggestions:
|
|
221
|
+
- Check extension path exists and contains manifest.json
|
|
222
|
+
- Try headed mode to see what's happening: headless: false
|
|
223
|
+
- Ensure extension is compatible with the installed Chromium version`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
await Promise.race([
|
|
227
|
+
context.waitForEvent("serviceworker", {
|
|
228
|
+
timeout: SERVICE_WORKER_TIMEOUT
|
|
229
|
+
}),
|
|
230
|
+
sleep(SERVICE_WORKER_POLL_INTERVAL)
|
|
231
|
+
]);
|
|
232
|
+
}
|
|
233
|
+
debug(`All ${extensionPaths.length} service worker(s) detected`);
|
|
234
|
+
await use(context);
|
|
235
|
+
await context.close();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
239
|
+
const wallet = wallets[i];
|
|
240
|
+
const info = extensionInfo[i];
|
|
241
|
+
fixtures[wallet.name] = async ({ context }, use) => {
|
|
242
|
+
if (isCachedConfig(wallet)) {
|
|
243
|
+
debug(`Initializing cached wallet: ${wallet.name} (ID: ${info.id})`);
|
|
244
|
+
const instance = await findCachedExtension(
|
|
245
|
+
context,
|
|
246
|
+
wallet.WalletClass,
|
|
247
|
+
info.id,
|
|
248
|
+
wallet.name,
|
|
249
|
+
wallet.homeUrl
|
|
250
|
+
);
|
|
251
|
+
await use(instance);
|
|
252
|
+
} else {
|
|
253
|
+
debug(`Initializing fresh wallet: ${wallet.name} (ID: ${info.id})`);
|
|
254
|
+
const instance = await initializeExtension(
|
|
255
|
+
context,
|
|
256
|
+
wallet.WalletClass,
|
|
257
|
+
info.id,
|
|
258
|
+
wallet.name
|
|
259
|
+
);
|
|
260
|
+
await use(instance);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return test.extend(fixtures);
|
|
265
|
+
}
|
|
266
|
+
function cleanUserDataDir(userDataDir) {
|
|
267
|
+
if (import_fs3.default.existsSync(userDataDir)) {
|
|
268
|
+
import_fs3.default.rmSync(userDataDir, { recursive: true });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function ensureWalletExtensionExists(walletPath, walletName) {
|
|
272
|
+
const manifestPath = import_path3.default.join(walletPath, "manifest.json");
|
|
273
|
+
if (!import_fs3.default.existsSync(manifestPath)) {
|
|
274
|
+
const cliAlias = walletName.toLowerCase();
|
|
275
|
+
throw new Error(
|
|
276
|
+
`Cannot find ${walletName} extension.
|
|
277
|
+
Checked: ${manifestPath}
|
|
278
|
+
Download it: npx w3wallets ${cliAlias}
|
|
279
|
+
Custom dir: npx w3wallets -o <dir> ${cliAlias}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function findCachedExtension(context, ExtensionClass, expectedExtensionId, walletName, homeUrl) {
|
|
284
|
+
const expectedUrl = `chrome-extension://${expectedExtensionId}/`;
|
|
285
|
+
const worker = context.serviceWorkers().find((w) => w.url().startsWith(expectedUrl));
|
|
286
|
+
if (!worker) {
|
|
287
|
+
const availableIds = context.serviceWorkers().map((w) => w.url().split("/")[2]).filter(Boolean);
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Service worker for ${walletName} (ID: ${expectedExtensionId}) not found in cached context.
|
|
290
|
+
Available IDs: [${availableIds.join(", ")}]
|
|
291
|
+
The cache may be stale. Rebuild: npx w3wallets cache --force
|
|
292
|
+
Also check extension path: ${W3WALLETS_DIR}/${walletName}/`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
const page = await context.newPage();
|
|
296
|
+
if (homeUrl) {
|
|
297
|
+
await page.goto(`chrome-extension://${expectedExtensionId}/${homeUrl}`);
|
|
298
|
+
}
|
|
299
|
+
const extension = new ExtensionClass(page, expectedExtensionId);
|
|
300
|
+
return extension;
|
|
301
|
+
}
|
|
302
|
+
async function initializeExtension(context, ExtensionClass, expectedExtensionId, walletName) {
|
|
303
|
+
const expectedUrl = `chrome-extension://${expectedExtensionId}/`;
|
|
304
|
+
const worker = context.serviceWorkers().find((w) => w.url().startsWith(expectedUrl));
|
|
305
|
+
if (!worker) {
|
|
306
|
+
const availableIds = context.serviceWorkers().map((w) => w.url().split("/")[2]).filter(Boolean);
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Service worker for ${walletName} (ID: ${expectedExtensionId}) not found. Available extension IDs: [${availableIds.join(", ")}]`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
const page = await context.newPage();
|
|
312
|
+
const extension = new ExtensionClass(page, expectedExtensionId);
|
|
313
|
+
return extension;
|
|
314
|
+
}
|
|
46
315
|
|
|
47
|
-
// src/
|
|
48
|
-
|
|
316
|
+
// src/core/types.ts
|
|
317
|
+
function createWallet(config2) {
|
|
318
|
+
return config2;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/wallets/metamask/metamask.ts
|
|
322
|
+
var import_test3 = require("@playwright/test");
|
|
323
|
+
|
|
324
|
+
// src/config.ts
|
|
325
|
+
var config = {
|
|
326
|
+
/**
|
|
327
|
+
* Timeout for actions like click, fill, waitFor, goto.
|
|
328
|
+
* Set via W3WALLETS_ACTION_TIMEOUT env variable.
|
|
329
|
+
* @default 30000 (30 seconds)
|
|
330
|
+
*/
|
|
331
|
+
get actionTimeout() {
|
|
332
|
+
const value = process.env.W3WALLETS_ACTION_TIMEOUT;
|
|
333
|
+
return value ? parseInt(value, 10) : void 0;
|
|
334
|
+
},
|
|
335
|
+
/**
|
|
336
|
+
* Timeout for expect assertions like toBeVisible, toContainText.
|
|
337
|
+
* Set via W3WALLETS_EXPECT_TIMEOUT env variable.
|
|
338
|
+
* @default undefined (uses Playwright's default of 5000ms)
|
|
339
|
+
*/
|
|
340
|
+
get expectTimeout() {
|
|
341
|
+
const value = process.env.W3WALLETS_EXPECT_TIMEOUT;
|
|
342
|
+
return value ? parseInt(value, 10) : void 0;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
49
345
|
|
|
50
|
-
// src/wallet.ts
|
|
346
|
+
// src/core/wallet.ts
|
|
51
347
|
var Wallet = class {
|
|
52
348
|
constructor(page, extensionId) {
|
|
53
349
|
this.page = page;
|
|
54
350
|
this.extensionId = extensionId;
|
|
351
|
+
if (config.actionTimeout) {
|
|
352
|
+
page.setDefaultTimeout(config.actionTimeout);
|
|
353
|
+
}
|
|
55
354
|
}
|
|
56
355
|
};
|
|
57
356
|
|
|
58
|
-
// src/
|
|
59
|
-
var
|
|
60
|
-
defaultPassword = "
|
|
61
|
-
currentAccountId = 0;
|
|
62
|
-
maxAccountId = 0;
|
|
357
|
+
// src/wallets/metamask/metamask.ts
|
|
358
|
+
var Metamask = class extends Wallet {
|
|
359
|
+
defaultPassword = "TestPassword123!";
|
|
63
360
|
async gotoOnboardPage() {
|
|
361
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
362
|
+
await (0, import_test3.expect)(
|
|
363
|
+
this.page.getByRole("button", { name: "I have an existing wallet" })
|
|
364
|
+
).toBeVisible({ timeout: ONBOARD_VISIBLE_TIMEOUT });
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Onboard MetaMask with a mnemonic phrase
|
|
368
|
+
* @param mnemonic - 12 or 24 word recovery phrase
|
|
369
|
+
* @param password - Optional password (defaults to TestPassword123!)
|
|
370
|
+
*/
|
|
371
|
+
async onboard(mnemonic, password) {
|
|
372
|
+
debug("metamask.onboard: starting");
|
|
373
|
+
const pwd = password ?? this.defaultPassword;
|
|
374
|
+
await this.gotoOnboardPage();
|
|
375
|
+
await this.page.getByRole("button", { name: "I have an existing wallet" }).click();
|
|
376
|
+
await this.page.getByRole("button", { name: "Import using Secret Recovery Phrase" }).click();
|
|
377
|
+
const srpTextarea = this.page.getByTestId("srp-input-import__srp-note");
|
|
378
|
+
await srpTextarea.click();
|
|
379
|
+
const words = mnemonic.split(" ");
|
|
380
|
+
for (let i = 0; i < words.length; i++) {
|
|
381
|
+
await this.page.keyboard.type(words[i], { delay: MNEMONIC_KEY_DELAY });
|
|
382
|
+
if (i < words.length - 1) {
|
|
383
|
+
await this.page.keyboard.press("Space");
|
|
384
|
+
await this.page.waitForTimeout(MNEMONIC_WORD_DELAY);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const continueBtn = this.page.getByTestId("import-srp-confirm");
|
|
388
|
+
await (0, import_test3.expect)(continueBtn).toBeEnabled({ timeout: config.expectTimeout });
|
|
389
|
+
await continueBtn.click();
|
|
390
|
+
const passwordInputs = this.page.locator('input[type="password"]');
|
|
391
|
+
await passwordInputs.nth(0).fill(pwd);
|
|
392
|
+
await passwordInputs.nth(1).fill(pwd);
|
|
393
|
+
await this.page.getByRole("checkbox").click({ force: true });
|
|
394
|
+
await this.page.getByRole("button", { name: "Create password" }).click();
|
|
395
|
+
const metametricsBtn = this.page.getByTestId("metametrics-i-agree");
|
|
396
|
+
await metametricsBtn.click();
|
|
397
|
+
const openWalletBtn = this.page.getByRole("button", {
|
|
398
|
+
name: /open wallet/i
|
|
399
|
+
});
|
|
400
|
+
await openWalletBtn.click();
|
|
401
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
64
402
|
await this.page.goto(
|
|
65
|
-
`chrome-extension://${this.extensionId}/
|
|
403
|
+
`chrome-extension://${this.extensionId}/sidepanel.html`
|
|
66
404
|
);
|
|
67
|
-
|
|
405
|
+
debug("metamask.onboard: complete");
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Dismiss MetaMask promotional popups (e.g., "Transaction Shield")
|
|
409
|
+
* that may overlay the confirmation UI.
|
|
410
|
+
*/
|
|
411
|
+
async dismissPopups() {
|
|
412
|
+
debug("metamask.dismissPopups: checking for popups");
|
|
413
|
+
const popup = this.page.getByText(/Transaction Shield|free trial/i);
|
|
414
|
+
if (!await popup.first().isVisible({ timeout: POPUP_VISIBILITY_TIMEOUT }).catch(() => false)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const shieldClose = this.page.getByTestId(
|
|
418
|
+
"shield-entry-modal-close-button"
|
|
419
|
+
);
|
|
420
|
+
if (await shieldClose.isVisible({ timeout: SHIELD_CLOSE_TIMEOUT }).catch(() => false)) {
|
|
421
|
+
await shieldClose.click();
|
|
422
|
+
if (await this.waitForPopupHidden(popup)) return;
|
|
423
|
+
}
|
|
424
|
+
const closeByAria = this.page.locator('button[aria-label="close"]').first();
|
|
425
|
+
if (await closeByAria.isVisible({ timeout: ARIA_CLOSE_TIMEOUT }).catch(() => false)) {
|
|
426
|
+
await closeByAria.click();
|
|
427
|
+
if (await this.waitForPopupHidden(popup)) return;
|
|
428
|
+
}
|
|
429
|
+
await this.page.keyboard.press("Escape");
|
|
430
|
+
await this.waitForPopupHidden(popup);
|
|
68
431
|
}
|
|
69
|
-
async
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
432
|
+
async waitForPopupHidden(popup) {
|
|
433
|
+
try {
|
|
434
|
+
await popup.first().waitFor({ state: "hidden", timeout: POPUP_HIDDEN_TIMEOUT });
|
|
435
|
+
return true;
|
|
436
|
+
} catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
73
439
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
440
|
+
/**
|
|
441
|
+
* After unlock, MetaMask may show onboarding screens, queued
|
|
442
|
+
* notifications, or go straight to the wallet UI. Race all possible
|
|
443
|
+
* states in a single wait to avoid sequential timeout penalties.
|
|
444
|
+
*/
|
|
445
|
+
async stabilizePostUnlock() {
|
|
446
|
+
debug("metamask.stabilizePostUnlock: racing post-unlock states");
|
|
447
|
+
const metametricsBtn = this.page.getByTestId("metametrics-i-agree");
|
|
448
|
+
const openWalletBtn = this.page.getByRole("button", {
|
|
449
|
+
name: /open wallet/i
|
|
450
|
+
});
|
|
451
|
+
const readyIndicator = this.page.getByTestId("account-options-menu-button");
|
|
452
|
+
const rejectAllBtn = this.page.getByText("Reject all");
|
|
453
|
+
const notificationCancelBtn = this.page.getByTestId(
|
|
454
|
+
"confirmation-cancel-button"
|
|
79
455
|
);
|
|
80
|
-
await
|
|
456
|
+
const state = await Promise.race([
|
|
457
|
+
metametricsBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "metametrics"),
|
|
458
|
+
openWalletBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "openWallet"),
|
|
459
|
+
rejectAllBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "rejectAll"),
|
|
460
|
+
notificationCancelBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "notification"),
|
|
461
|
+
readyIndicator.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "ready")
|
|
462
|
+
]).catch(() => "timeout");
|
|
463
|
+
debug(`metamask.stabilizePostUnlock: state=${state}`);
|
|
464
|
+
if (state === "timeout") {
|
|
465
|
+
debug(
|
|
466
|
+
`metamask.stabilizePostUnlock: timeout after ${POST_UNLOCK_TIMEOUT}ms. URL: ${this.page.url()}. Checked: metametrics-i-agree, open-wallet button, Reject all, confirmation-cancel-button, account-options-menu-button`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if (state === "metametrics") {
|
|
470
|
+
await metametricsBtn.click();
|
|
471
|
+
if (await openWalletBtn.isVisible({ timeout: POPUP_HIDDEN_TIMEOUT }).catch(() => false)) {
|
|
472
|
+
await openWalletBtn.click();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (state === "openWallet") {
|
|
476
|
+
await openWalletBtn.click();
|
|
477
|
+
}
|
|
478
|
+
if (state !== "ready") {
|
|
479
|
+
await this.dismissQueuedNotifications();
|
|
480
|
+
}
|
|
81
481
|
}
|
|
82
482
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
483
|
+
* Dismiss all queued MetaMask notifications (e.g., Solana/Tron account
|
|
484
|
+
* removal). These use the templated confirmation flow at #/confirmation/...
|
|
485
|
+
* If multiple are queued, "Reject all" appears; if only one, just
|
|
486
|
+
* confirmation-cancel-button is available.
|
|
85
487
|
*/
|
|
86
|
-
async
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
await this.page.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
488
|
+
async dismissQueuedNotifications() {
|
|
489
|
+
const homeUrl = `chrome-extension://${this.extensionId}/home.html`;
|
|
490
|
+
const readyIndicator = this.page.getByTestId("account-options-menu-button");
|
|
491
|
+
const rejectAllBtn = this.page.getByText("Reject all");
|
|
492
|
+
const notificationCancelBtn = this.page.getByTestId(
|
|
493
|
+
"confirmation-cancel-button"
|
|
494
|
+
);
|
|
495
|
+
await this.page.goto(homeUrl);
|
|
496
|
+
for (let i = 0; i < 10; i++) {
|
|
497
|
+
const state = await Promise.race([
|
|
498
|
+
readyIndicator.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "ready"),
|
|
499
|
+
rejectAllBtn.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "rejectAll"),
|
|
500
|
+
notificationCancelBtn.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "notification")
|
|
501
|
+
]).catch(() => "timeout");
|
|
502
|
+
if (state === "ready") return;
|
|
503
|
+
if (state === "rejectAll") {
|
|
504
|
+
await rejectAllBtn.click();
|
|
505
|
+
await this.page.goto(homeUrl);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (state === "notification") {
|
|
509
|
+
await notificationCancelBtn.click();
|
|
510
|
+
await this.page.goto(homeUrl);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
debug(
|
|
514
|
+
`metamask.dismissQueuedNotifications: timeout at iteration ${i}. URL: ${this.page.url()}`
|
|
515
|
+
);
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
await (0, import_test3.expect)(readyIndicator).toBeVisible({ timeout: POST_UNLOCK_TIMEOUT });
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Wait for a target button while handling the Transaction Shield popup.
|
|
522
|
+
* Always navigates to sidepanel.html fresh so MetaMask's
|
|
523
|
+
* ConfirmationHandler can route to the pending approval.
|
|
524
|
+
*/
|
|
525
|
+
async waitAndClickButton(btnLocator) {
|
|
526
|
+
debug(`metamask.waitAndClickButton: navigating to sidepanel`);
|
|
527
|
+
const popup = this.page.getByText(/Transaction Shield|free trial/i);
|
|
528
|
+
const sidepanelUrl = `chrome-extension://${this.extensionId}/sidepanel.html`;
|
|
529
|
+
const confirmRoutePattern = /#\/(confirm-transaction|connect|confirmation)\b/;
|
|
530
|
+
const waitForButtonOrPopup = (timeout) => Promise.race([
|
|
531
|
+
btnLocator.first().waitFor({ state: "visible", timeout }).then(() => "button"),
|
|
532
|
+
popup.first().waitFor({ state: "visible", timeout }).then(() => "popup")
|
|
533
|
+
]).catch(() => "timeout");
|
|
534
|
+
const handlePopupAndClick = async () => {
|
|
535
|
+
await this.dismissPopups();
|
|
536
|
+
await btnLocator.first().waitFor({ state: "visible", timeout: BUTTON_OR_POPUP_TIMEOUT });
|
|
537
|
+
await btnLocator.first().click();
|
|
538
|
+
};
|
|
539
|
+
let routeFound = false;
|
|
540
|
+
for (let attempt = 0; attempt < MAX_ROUTE_ATTEMPTS; attempt++) {
|
|
541
|
+
await this.page.goto(sidepanelUrl);
|
|
542
|
+
try {
|
|
543
|
+
await this.page.waitForURL(confirmRoutePattern, {
|
|
544
|
+
timeout: ROUTE_RETRY_TIMEOUT
|
|
545
|
+
});
|
|
546
|
+
routeFound = true;
|
|
547
|
+
break;
|
|
548
|
+
} catch {
|
|
549
|
+
debug(
|
|
550
|
+
`metamask.waitAndClickButton: route attempt ${attempt + 1}/${MAX_ROUTE_ATTEMPTS} failed. URL: ${this.page.url()}`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (!routeFound) {
|
|
555
|
+
console.warn(
|
|
556
|
+
`[w3wallets] confirmation route not found after ${MAX_ROUTE_ATTEMPTS} attempts. URL: ${this.page.url()}`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const result = await waitForButtonOrPopup(BUTTON_OR_POPUP_TIMEOUT);
|
|
560
|
+
debug(
|
|
561
|
+
`metamask.waitAndClickButton: result=${result}, URL=${this.page.url()}`
|
|
562
|
+
);
|
|
563
|
+
if (result === "button") {
|
|
564
|
+
await btnLocator.first().click();
|
|
565
|
+
await this.page.waitForURL((url) => !confirmRoutePattern.test(url.toString()), {
|
|
566
|
+
timeout: POST_CLICK_TIMEOUT
|
|
567
|
+
}).catch(() => {
|
|
568
|
+
console.warn(
|
|
569
|
+
`[w3wallets] still on confirmation route after click. URL: ${this.page.url()}`
|
|
570
|
+
);
|
|
571
|
+
});
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (result === "popup") {
|
|
575
|
+
await handlePopupAndClick();
|
|
576
|
+
await this.page.waitForURL((url) => !confirmRoutePattern.test(url.toString()), {
|
|
577
|
+
timeout: POST_CLICK_TIMEOUT
|
|
578
|
+
}).catch(() => {
|
|
579
|
+
console.warn(
|
|
580
|
+
`[w3wallets] still on confirmation route after popup dismiss. URL: ${this.page.url()}`
|
|
581
|
+
);
|
|
582
|
+
});
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const isOnConfirmRoute = confirmRoutePattern.test(this.page.url());
|
|
586
|
+
debug(
|
|
587
|
+
`metamask.waitAndClickButton: timeout after ${BUTTON_OR_POPUP_TIMEOUT}ms. URL: ${this.page.url()}, onConfirmRoute: ${isOnConfirmRoute}`
|
|
588
|
+
);
|
|
589
|
+
console.warn(
|
|
590
|
+
`[w3wallets] no button or popup found after ${BUTTON_OR_POPUP_TIMEOUT / 1e3}s. URL: ${this.page.url()}`
|
|
591
|
+
);
|
|
592
|
+
await btnLocator.first().click({ timeout: LAST_RESORT_CLICK_TIMEOUT });
|
|
107
593
|
}
|
|
108
594
|
async approve() {
|
|
109
|
-
|
|
595
|
+
debug("metamask.approve: starting");
|
|
596
|
+
const confirmBtn = this.page.getByTestId("confirm-btn").or(this.page.getByTestId("confirm-footer-button")).or(this.page.getByTestId("page-container-footer-next")).or(this.page.getByRole("button", { name: /^confirm$/i }));
|
|
597
|
+
await this.waitAndClickButton(confirmBtn);
|
|
110
598
|
}
|
|
111
599
|
async deny() {
|
|
112
|
-
|
|
600
|
+
debug("metamask.deny: starting");
|
|
601
|
+
const cancelBtn = this.page.getByTestId("cancel-btn").or(this.page.getByTestId("confirm-footer-cancel-button")).or(this.page.getByTestId("page-container-footer-cancel")).or(this.page.getByRole("button", { name: /^cancel$/i })).or(this.page.getByRole("button", { name: /^reject$/i }));
|
|
602
|
+
await this.waitAndClickButton(cancelBtn);
|
|
113
603
|
}
|
|
114
|
-
|
|
115
|
-
|
|
604
|
+
/**
|
|
605
|
+
* Lock the MetaMask wallet
|
|
606
|
+
*/
|
|
607
|
+
async lock() {
|
|
608
|
+
debug("metamask.lock: starting");
|
|
609
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
610
|
+
const menuBtn = this.page.getByTestId("account-options-menu-button");
|
|
611
|
+
await menuBtn.waitFor({ state: "visible", timeout: MENU_BUTTON_TIMEOUT });
|
|
612
|
+
await menuBtn.click({ force: true });
|
|
613
|
+
await this.page.locator("text=Log out").click();
|
|
116
614
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await this.page.
|
|
127
|
-
|
|
128
|
-
await
|
|
129
|
-
await this.page.
|
|
130
|
-
await this.page.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
615
|
+
/**
|
|
616
|
+
* Unlock MetaMask with password.
|
|
617
|
+
* After unlocking, stabilizes the wallet UI by handling post-unlock
|
|
618
|
+
* screens (metametrics, onboarding completion) and dismissing queued
|
|
619
|
+
* notifications. Ends on home.html with the wallet UI ready.
|
|
620
|
+
*/
|
|
621
|
+
async unlock(password) {
|
|
622
|
+
debug("metamask.unlock: starting");
|
|
623
|
+
const pwd = password ?? this.defaultPassword;
|
|
624
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
625
|
+
const passwordInput = this.page.getByTestId("unlock-password");
|
|
626
|
+
await passwordInput.fill(pwd);
|
|
627
|
+
await this.page.getByTestId("unlock-submit").click();
|
|
628
|
+
await this.page.waitForSelector('[data-testid="unlock-password"]', {
|
|
629
|
+
state: "hidden",
|
|
630
|
+
timeout: LOCK_SCREEN_TIMEOUT
|
|
631
|
+
});
|
|
632
|
+
await this.stabilizePostUnlock();
|
|
633
|
+
debug("metamask.unlock: complete");
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Switch to an existing network in MetaMask
|
|
637
|
+
* @param networkName - Name of the network to switch to (e.g., "Ethereum Mainnet", "Sepolia")
|
|
638
|
+
*/
|
|
639
|
+
async switchNetwork(networkName, networkType = "Popular") {
|
|
640
|
+
debug(`metamask.switchNetwork: ${networkName} (${networkType})`);
|
|
641
|
+
await this.page.getByTestId("sort-by-networks").click();
|
|
642
|
+
if (networkType === "Custom") {
|
|
643
|
+
await this.page.getByRole("tab", { name: "Custom" }).click({ force: true });
|
|
136
644
|
}
|
|
137
|
-
await this.page.
|
|
138
|
-
await this.page.getByTestId("
|
|
645
|
+
await this.page.getByText(networkName).click();
|
|
646
|
+
await (0, import_test3.expect)(this.page.getByTestId("sort-by-networks")).toHaveText(
|
|
647
|
+
networkName,
|
|
648
|
+
{ timeout: config.expectTimeout }
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
async switchAccount(accountName) {
|
|
652
|
+
await this.page.getByTestId("account-menu-icon").click();
|
|
653
|
+
await this.page.getByText(accountName, { exact: true }).click();
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Add a custom network to MetaMask
|
|
657
|
+
*/
|
|
658
|
+
async addNetwork(network) {
|
|
659
|
+
await this.page.goto(
|
|
660
|
+
`chrome-extension://${this.extensionId}/home.html#settings/networks/add-network`
|
|
661
|
+
);
|
|
662
|
+
await this.page.getByTestId("network-form-network-name").fill(network.name);
|
|
663
|
+
await this.page.getByTestId("network-form-rpc-url").fill(network.rpc);
|
|
664
|
+
await this.page.getByTestId("network-form-chain-id").fill(network.chainId.toString());
|
|
665
|
+
await this.page.getByTestId("network-form-ticker-input").fill(network.currencySymbol);
|
|
666
|
+
await this.page.getByRole("button", { name: /save/i }).click();
|
|
667
|
+
}
|
|
668
|
+
async addCustomNetwork(settings) {
|
|
669
|
+
await this.page.getByTestId("account-options-menu-button").click({ force: true });
|
|
670
|
+
await this.page.getByTestId("global-menu-networks").click();
|
|
671
|
+
await this.page.getByRole("button", { name: "Add a custom network" }).click();
|
|
672
|
+
await this.page.getByTestId("network-form-network-name").fill(settings.name);
|
|
673
|
+
await this.page.getByTestId("network-form-chain-id").fill(settings.chainId.toString());
|
|
674
|
+
await this.page.getByTestId("network-form-ticker-input").fill(settings.currencySymbol);
|
|
675
|
+
await this.page.getByTestId("test-add-rpc-drop-down").click();
|
|
676
|
+
await this.page.getByRole("button", { name: "Add RPC URL" }).click();
|
|
677
|
+
await this.page.getByTestId("rpc-url-input-test").fill(settings.rpc);
|
|
678
|
+
await this.page.getByRole("button", { name: "Add URL" }).click();
|
|
679
|
+
await this.page.getByRole("button", { name: "Save" }).click();
|
|
680
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
681
|
+
await this.page.waitForLoadState("domcontentloaded");
|
|
682
|
+
}
|
|
683
|
+
async enableTestNetworks() {
|
|
684
|
+
await this.page.getByTestId("account-options-menu-button").click({ force: true });
|
|
685
|
+
await this.page.getByTestId("global-menu-networks").click();
|
|
686
|
+
const toggle = this.page.locator(
|
|
687
|
+
"text=Show test networks >> xpath=following-sibling::label"
|
|
688
|
+
);
|
|
689
|
+
await (0, import_test3.expect)(toggle).toBeVisible({ timeout: config.expectTimeout });
|
|
690
|
+
await toggle.click();
|
|
691
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
692
|
+
}
|
|
693
|
+
async importAccount(privateKey) {
|
|
694
|
+
debug("metamask.importAccount: starting");
|
|
695
|
+
await this.page.getByTestId("account-menu-icon").click();
|
|
696
|
+
await this.page.getByTestId("account-list-add-wallet-button").click();
|
|
697
|
+
await this.page.getByTestId("choose-wallet-type-import-account").click();
|
|
698
|
+
await this.page.locator("#private-key-box").fill(privateKey);
|
|
699
|
+
await this.page.getByTestId("import-account-confirm-button").click();
|
|
700
|
+
await this.page.getByTestId("back-button").click();
|
|
701
|
+
await this.page.locator('[data-testid^="multichain-account-cell-keyring:"]').first().click();
|
|
702
|
+
}
|
|
703
|
+
async accountNameIs(accountName) {
|
|
704
|
+
await (0, import_test3.expect)(this.page.getByTestId("account-menu-icon")).toContainText(
|
|
705
|
+
accountName,
|
|
706
|
+
{ timeout: config.expectTimeout }
|
|
707
|
+
);
|
|
139
708
|
}
|
|
140
709
|
};
|
|
141
710
|
|
|
142
|
-
// src/
|
|
143
|
-
var
|
|
711
|
+
// src/wallets/polkadot-js/polkadot-js.ts
|
|
712
|
+
var import_test4 = require("@playwright/test");
|
|
144
713
|
var PolkadotJS = class extends Wallet {
|
|
145
714
|
defaultPassword = "11111111";
|
|
146
715
|
async gotoOnboardPage() {
|
|
147
716
|
await this.page.goto(`chrome-extension://${this.extensionId}/index.html`);
|
|
148
|
-
await (0,
|
|
717
|
+
await (0, import_test4.expect)(
|
|
149
718
|
this.page.getByText("Before we start, just a couple of notes")
|
|
150
|
-
).toBeVisible();
|
|
719
|
+
).toBeVisible({ timeout: config.expectTimeout });
|
|
151
720
|
}
|
|
152
721
|
async onboard(seed, password, name) {
|
|
722
|
+
debug("polkadotJS.onboard: starting");
|
|
723
|
+
await this.gotoOnboardPage();
|
|
153
724
|
await this.page.getByRole("button", { name: "Understood, let me continue" }).click();
|
|
154
725
|
await this.page.getByRole("button", { name: "I Understand" }).click();
|
|
155
726
|
await this.page.locator(".popupToggle").first().click();
|
|
@@ -166,12 +737,13 @@ var PolkadotJS = class extends Wallet {
|
|
|
166
737
|
password ?? this.defaultPassword
|
|
167
738
|
);
|
|
168
739
|
await this.page.getByRole("button", { name: "Add the account with the supplied seed" }).click();
|
|
740
|
+
debug("polkadotJS.onboard: complete");
|
|
169
741
|
}
|
|
170
742
|
async selectAllAccounts() {
|
|
171
743
|
await this.page.getByText("Select all").click();
|
|
172
744
|
}
|
|
173
745
|
async selectAccount(accountId) {
|
|
174
|
-
await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").
|
|
746
|
+
await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").click();
|
|
175
747
|
}
|
|
176
748
|
async enterPassword(password) {
|
|
177
749
|
await this._getLabeledInput("Password for this account").fill(
|
|
@@ -179,6 +751,7 @@ var PolkadotJS = class extends Wallet {
|
|
|
179
751
|
);
|
|
180
752
|
}
|
|
181
753
|
async approve() {
|
|
754
|
+
debug("polkadotJS.approve: starting");
|
|
182
755
|
const connect = this.page.getByRole("button", { name: "Connect" });
|
|
183
756
|
const signTransaction = this.page.getByRole("button", {
|
|
184
757
|
name: "Sign the transaction"
|
|
@@ -186,6 +759,7 @@ var PolkadotJS = class extends Wallet {
|
|
|
186
759
|
await connect.or(signTransaction).click();
|
|
187
760
|
}
|
|
188
761
|
async deny() {
|
|
762
|
+
debug("polkadotJS.deny: starting");
|
|
189
763
|
const reject = this.page.getByRole("button", { name: "Reject" });
|
|
190
764
|
const cancel = this.page.getByRole("link", { name: "Cancel" });
|
|
191
765
|
await reject.or(cancel).click();
|
|
@@ -197,234 +771,38 @@ var PolkadotJS = class extends Wallet {
|
|
|
197
771
|
}
|
|
198
772
|
};
|
|
199
773
|
|
|
200
|
-
// src/
|
|
201
|
-
var
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
await this.page.getByTestId("onboarding-import-with-srp-button").click();
|
|
214
|
-
await this.page.getByTestId("srp-input-import__srp-note").pressSequentially(mnemonic, { delay: 5 });
|
|
215
|
-
await this.page.getByRole("button", { name: "Continue" }).click();
|
|
216
|
-
await this.page.getByTestId("create-password-new-input").fill(password);
|
|
217
|
-
await this.page.getByTestId("create-password-confirm-input").fill(password);
|
|
218
|
-
await this.page.getByTestId("create-password-terms").click();
|
|
219
|
-
await this.page.getByTestId("create-password-submit").click();
|
|
220
|
-
await this.page.getByTestId("metametrics-i-agree").click();
|
|
221
|
-
await this.page.getByTestId("onboarding-complete-done").click();
|
|
222
|
-
await this.clickTopRightCornerToCloseAllTheMarketingBullshit();
|
|
223
|
-
}
|
|
224
|
-
// async switchAccount(accountAddress: { address: string }): Promise<void>;
|
|
225
|
-
async switchAccount(accountNameOrAddress) {
|
|
226
|
-
await this.page.getByTestId("account-menu-icon").click();
|
|
227
|
-
await this.page.getByText(accountNameOrAddress.name, { exact: true }).click();
|
|
228
|
-
}
|
|
229
|
-
async importAccount(privateKey) {
|
|
230
|
-
await this.page.getByTestId("account-menu-icon").click();
|
|
231
|
-
await this.page.getByTestId("account-list-add-wallet-button").click();
|
|
232
|
-
await this.page.getByTestId("add-wallet-modal-import-account").click();
|
|
233
|
-
await this.page.locator("#private-key-box").fill(privateKey);
|
|
234
|
-
await this.page.getByTestId("import-account-confirm-button").click();
|
|
235
|
-
await this.page.getByRole("button", { name: "Back" }).click();
|
|
236
|
-
}
|
|
237
|
-
async addAccount(accountName) {
|
|
238
|
-
await this.page.getByTestId("account-menu-icon").click();
|
|
239
|
-
await this.page.getByTestId("multichain-account-menu-popover-action-button").click();
|
|
240
|
-
await this.page.getByTestId("multichain-account-menu-popover-add-account").click();
|
|
241
|
-
if (accountName) {
|
|
242
|
-
await this.page.locator("#account-name").fill(accountName);
|
|
243
|
-
}
|
|
244
|
-
await this.page.getByTestId("submit-add-account-with-name").click();
|
|
245
|
-
}
|
|
246
|
-
async getAccountName() {
|
|
247
|
-
const accountSelect = this.page.getByTestId("account-menu-icon");
|
|
248
|
-
await (0, import_test3.expect)(accountSelect).toBeVisible();
|
|
249
|
-
const text = await accountSelect.textContent();
|
|
250
|
-
if (!text) throw Error("Cannot get account name");
|
|
251
|
-
return text;
|
|
252
|
-
}
|
|
253
|
-
async connectToNetwork(networkName, networkType = "Popular") {
|
|
254
|
-
await this.page.getByTestId("sort-by-networks").click();
|
|
255
|
-
await this.page.getByRole("tab", { name: networkType, exact: true }).click();
|
|
256
|
-
const additionalNetwork = this.page.getByTestId("additional-network-item").getByText(networkName);
|
|
257
|
-
await this.page.getByText(networkName).click();
|
|
258
|
-
}
|
|
259
|
-
async addCustomNetwork(settings) {
|
|
260
|
-
await this.page.getByTestId("account-options-menu-button").click();
|
|
261
|
-
await this.page.getByTestId("global-menu-networks").click();
|
|
262
|
-
await this.page.getByRole("button", { name: "Add a custom network" }).click();
|
|
263
|
-
await this.page.getByTestId("network-form-network-name").fill(settings.name);
|
|
264
|
-
await this.page.getByTestId("network-form-chain-id").fill(settings.chainId.toString());
|
|
265
|
-
await this.page.getByTestId("network-form-ticker-input").fill(settings.currencySymbol);
|
|
266
|
-
await this.page.getByTestId("test-add-rpc-drop-down").click();
|
|
267
|
-
await this.page.getByRole("button", { name: "Add RPC URL" }).click();
|
|
268
|
-
await this.page.getByTestId("rpc-url-input-test").fill(settings.rpc);
|
|
269
|
-
await this.page.getByRole("button", { name: "Add URL" }).click();
|
|
270
|
-
await this.page.getByRole("button", { name: "Save" }).click();
|
|
271
|
-
}
|
|
272
|
-
async enableTestNetworks() {
|
|
273
|
-
await this.page.getByTestId("account-options-menu-button").click();
|
|
274
|
-
await this.page.getByTestId("global-menu-networks").click();
|
|
275
|
-
await this.page.locator("text=Show test networks >> xpath=following-sibling::label").click();
|
|
276
|
-
await this.page.keyboard.press("Escape");
|
|
277
|
-
}
|
|
278
|
-
async approve() {
|
|
279
|
-
const p = await this.page.context().newPage();
|
|
280
|
-
await p.goto(`chrome-extension://${this.extensionId}/notification.html`);
|
|
281
|
-
await p.locator(
|
|
282
|
-
'[data-testid="confirm-footer-button"], [data-testid="confirm-btn"], [data-testid="page-container-footer-next"], [data-testid="confirmation-submit-button"]'
|
|
283
|
-
).click();
|
|
284
|
-
await p.waitForSelector(".multichain-app-header", {
|
|
285
|
-
timeout: 1e4
|
|
286
|
-
});
|
|
287
|
-
await p.close();
|
|
288
|
-
}
|
|
289
|
-
async deny() {
|
|
290
|
-
return this.usingNotificationPage(
|
|
291
|
-
(p) => p.getByTestId("cancel-btn").click()
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
async usingNotificationPage(action) {
|
|
295
|
-
const p = await this.page.context().newPage();
|
|
296
|
-
await p.goto(`chrome-extension://${this.extensionId}/notification.html`);
|
|
297
|
-
await action(p);
|
|
298
|
-
await p.close();
|
|
299
|
-
}
|
|
300
|
-
async clickTopRightCornerToCloseAllTheMarketingBullshit() {
|
|
301
|
-
await this.page.waitForTimeout(500);
|
|
302
|
-
await this.page.keyboard.press("Escape");
|
|
303
|
-
await this.page.mouse.click(1e3, 10);
|
|
304
|
-
}
|
|
305
|
-
};
|
|
774
|
+
// src/wallets/index.ts
|
|
775
|
+
var metamask = createWallet({
|
|
776
|
+
name: "metamask",
|
|
777
|
+
extensionDir: "metamask",
|
|
778
|
+
WalletClass: Metamask,
|
|
779
|
+
homeUrl: "home.html"
|
|
780
|
+
});
|
|
781
|
+
var polkadotJS = createWallet({
|
|
782
|
+
name: "polkadotJS",
|
|
783
|
+
extensionDir: "polkadotjs",
|
|
784
|
+
WalletClass: PolkadotJS,
|
|
785
|
+
homeUrl: "index.html"
|
|
786
|
+
});
|
|
306
787
|
|
|
307
|
-
// src/
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const polkadotJSPath = import_path.default.join(process.cwd(), w3walletsDir, "polkadotJS");
|
|
315
|
-
const metamaskPath = import_path.default.join(process.cwd(), w3walletsDir, "metamask");
|
|
316
|
-
return test.extend({
|
|
317
|
-
/**
|
|
318
|
-
* Sets up a persistent browser context with the requested extensions loaded.
|
|
319
|
-
*/
|
|
320
|
-
context: async ({}, use, testInfo) => {
|
|
321
|
-
const userDataDir = import_path.default.join(
|
|
322
|
-
process.cwd(),
|
|
323
|
-
".w3wallets",
|
|
324
|
-
".context",
|
|
325
|
-
testInfo.testId
|
|
326
|
-
);
|
|
327
|
-
cleanUserDataDir(userDataDir);
|
|
328
|
-
const extensionPaths = [];
|
|
329
|
-
if (withBackpack) {
|
|
330
|
-
ensureWalletExtensionExists(backpackPath, "backpack");
|
|
331
|
-
extensionPaths.push(backpackPath);
|
|
332
|
-
}
|
|
333
|
-
if (withPolkadotJS) {
|
|
334
|
-
ensureWalletExtensionExists(polkadotJSPath, "polkadotJS");
|
|
335
|
-
extensionPaths.push(polkadotJSPath);
|
|
336
|
-
}
|
|
337
|
-
if (withMetamask) {
|
|
338
|
-
ensureWalletExtensionExists(metamaskPath, "metamask");
|
|
339
|
-
extensionPaths.push(metamaskPath);
|
|
340
|
-
}
|
|
341
|
-
const context = await import_test4.chromium.launchPersistentContext(userDataDir, {
|
|
342
|
-
headless: testInfo.project.use.headless ?? true,
|
|
343
|
-
channel: "chromium",
|
|
344
|
-
args: [
|
|
345
|
-
`--disable-extensions-except=${extensionPaths.join(",")}`,
|
|
346
|
-
`--load-extension=${extensionPaths.join(",")}`
|
|
347
|
-
]
|
|
348
|
-
});
|
|
349
|
-
while (context.serviceWorkers().length < extensionPaths.length) {
|
|
350
|
-
await sleep(1e3);
|
|
351
|
-
}
|
|
352
|
-
await use(context);
|
|
353
|
-
await context.close();
|
|
354
|
-
},
|
|
355
|
-
backpack: async ({ context }, use) => {
|
|
356
|
-
if (!withBackpack) {
|
|
357
|
-
throw Error(
|
|
358
|
-
"The Backpack wallet hasn't been loaded. Add it to the withWallets function."
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
const backpack = await initializeExtension(
|
|
362
|
-
context,
|
|
363
|
-
Backpack,
|
|
364
|
-
"Backpack is not initialized"
|
|
365
|
-
);
|
|
366
|
-
await use(backpack);
|
|
367
|
-
},
|
|
368
|
-
polkadotJS: async ({ context }, use) => {
|
|
369
|
-
if (!withPolkadotJS) {
|
|
370
|
-
throw Error(
|
|
371
|
-
"The Polkadot{.js} wallet hasn't been loaded. Add it to the withWallets function."
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
const polkadotJS = await initializeExtension(
|
|
375
|
-
context,
|
|
376
|
-
PolkadotJS,
|
|
377
|
-
"Polkadot{.js} is not initialized"
|
|
378
|
-
);
|
|
379
|
-
await use(polkadotJS);
|
|
380
|
-
},
|
|
381
|
-
metamask: async ({ context }, use) => {
|
|
382
|
-
if (!withMetamask) {
|
|
383
|
-
throw Error(
|
|
384
|
-
"The Metamask wallet hasn't been loaded. Add it to the withWallets function."
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
const metamask = await initializeExtension(
|
|
388
|
-
context,
|
|
389
|
-
Metamask,
|
|
390
|
-
"Metamask is not initialized"
|
|
391
|
-
);
|
|
392
|
-
await use(metamask);
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
function cleanUserDataDir(userDataDir) {
|
|
397
|
-
if (import_fs.default.existsSync(userDataDir)) {
|
|
398
|
-
import_fs.default.rmSync(userDataDir, { recursive: true });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
function ensureWalletExtensionExists(walletPath, walletName) {
|
|
402
|
-
if (!import_fs.default.existsSync(import_path.default.join(walletPath, "manifest.json"))) {
|
|
403
|
-
throw new Error(
|
|
404
|
-
`Cannot find ${walletName}. Please download it via 'npx w3wallets ${walletName}'.`
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
async function initializeExtension(context, ExtensionClass, notInitializedErrorMessage) {
|
|
409
|
-
const serviceWorkers = context.serviceWorkers();
|
|
410
|
-
let page = await context.newPage();
|
|
411
|
-
for (const worker of serviceWorkers) {
|
|
412
|
-
const extensionId = worker.url().split("/")[2];
|
|
413
|
-
if (!extensionId) {
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
const extension = new ExtensionClass(page, extensionId);
|
|
417
|
-
try {
|
|
418
|
-
await extension.gotoOnboardPage();
|
|
419
|
-
return extension;
|
|
420
|
-
} catch {
|
|
421
|
-
await page.close();
|
|
422
|
-
page = await context.newPage();
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
throw new Error(notInitializedErrorMessage);
|
|
788
|
+
// src/cache/prepareWallet.ts
|
|
789
|
+
function prepareWallet(walletConfig, setupFn) {
|
|
790
|
+
return {
|
|
791
|
+
...walletConfig,
|
|
792
|
+
setupFn,
|
|
793
|
+
__cached: true
|
|
794
|
+
};
|
|
426
795
|
}
|
|
427
796
|
// Annotate the CommonJS export names for ESM import in node:
|
|
428
797
|
0 && (module.exports = {
|
|
798
|
+
Metamask,
|
|
799
|
+
PolkadotJS,
|
|
800
|
+
config,
|
|
801
|
+
createWallet,
|
|
802
|
+
debug,
|
|
803
|
+
isCachedConfig,
|
|
804
|
+
metamask,
|
|
805
|
+
polkadotJS,
|
|
806
|
+
prepareWallet,
|
|
429
807
|
withWallets
|
|
430
808
|
});
|