w3wallets 1.0.0-beta.1 → 1.0.0-beta.11
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 +85 -10
- package/dist/index.d.mts +75 -33
- package/dist/index.d.ts +75 -33
- package/dist/index.js +554 -167
- package/dist/index.mjs +563 -171
- package/dist/scripts/cache.js +317 -0
- package/package.json +17 -12
- package/src/scripts/download.js +75 -13
package/dist/index.js
CHANGED
|
@@ -30,29 +30,148 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
Backpack: () => Backpack,
|
|
34
33
|
Metamask: () => Metamask,
|
|
35
34
|
PolkadotJS: () => PolkadotJS,
|
|
36
|
-
|
|
35
|
+
config: () => config,
|
|
37
36
|
createWallet: () => createWallet,
|
|
37
|
+
debug: () => debug,
|
|
38
|
+
isCachedConfig: () => isCachedConfig,
|
|
38
39
|
metamask: () => metamask,
|
|
39
40
|
polkadotJS: () => polkadotJS,
|
|
41
|
+
prepareWallet: () => prepareWallet,
|
|
40
42
|
withWallets: () => withWallets
|
|
41
43
|
});
|
|
42
44
|
module.exports = __toCommonJS(index_exports);
|
|
43
45
|
|
|
44
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
|
|
45
65
|
var import_path = __toESM(require("path"));
|
|
46
66
|
var import_fs = __toESM(require("fs"));
|
|
47
67
|
var import_crypto = __toESM(require("crypto"));
|
|
48
|
-
var import_test = require("@playwright/test");
|
|
49
|
-
var W3WALLETS_DIR = ".w3wallets";
|
|
50
68
|
function sleep(ms) {
|
|
51
69
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
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
|
+
}
|
|
91
|
+
|
|
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
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/withWallets.ts
|
|
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
|
+
}
|
|
53
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}`);
|
|
54
173
|
const extensionInfo = wallets.map((w) => {
|
|
55
|
-
const extPath =
|
|
174
|
+
const extPath = import_path3.default.join(process.cwd(), W3WALLETS_DIR, w.extensionDir);
|
|
56
175
|
ensureWalletExtensionExists(extPath, w.name);
|
|
57
176
|
const extensionId = w.extensionId ?? getExtensionId(extPath);
|
|
58
177
|
return { path: extPath, id: extensionId, name: w.name };
|
|
@@ -60,14 +179,28 @@ function withWallets(test, ...wallets) {
|
|
|
60
179
|
const extensionPaths = extensionInfo.map((e) => e.path);
|
|
61
180
|
const fixtures = {
|
|
62
181
|
context: async ({}, use, testInfo) => {
|
|
63
|
-
const userDataDir =
|
|
182
|
+
const userDataDir = import_path3.default.join(
|
|
64
183
|
process.cwd(),
|
|
65
184
|
W3WALLETS_DIR,
|
|
66
185
|
".context",
|
|
67
186
|
testInfo.testId
|
|
68
187
|
);
|
|
69
188
|
cleanUserDataDir(userDataDir);
|
|
70
|
-
|
|
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, {
|
|
71
204
|
headless: testInfo.project.use.headless ?? true,
|
|
72
205
|
channel: "chromium",
|
|
73
206
|
args: [
|
|
@@ -75,10 +208,29 @@ function withWallets(test, ...wallets) {
|
|
|
75
208
|
`--load-extension=${extensionPaths.join(",")}`
|
|
76
209
|
]
|
|
77
210
|
});
|
|
211
|
+
debug(`Waiting for ${extensionPaths.length} service worker(s)...`);
|
|
212
|
+
const swDeadline = Date.now() + SERVICE_WORKER_TIMEOUT;
|
|
78
213
|
while (context.serviceWorkers().length < extensionPaths.length) {
|
|
79
|
-
|
|
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
|
+
]);
|
|
80
232
|
}
|
|
81
|
-
|
|
233
|
+
debug(`All ${extensionPaths.length} service worker(s) detected`);
|
|
82
234
|
await use(context);
|
|
83
235
|
await context.close();
|
|
84
236
|
}
|
|
@@ -87,49 +239,65 @@ function withWallets(test, ...wallets) {
|
|
|
87
239
|
const wallet = wallets[i];
|
|
88
240
|
const info = extensionInfo[i];
|
|
89
241
|
fixtures[wallet.name] = async ({ context }, use) => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
}
|
|
97
262
|
};
|
|
98
263
|
}
|
|
99
264
|
return test.extend(fixtures);
|
|
100
265
|
}
|
|
101
266
|
function cleanUserDataDir(userDataDir) {
|
|
102
|
-
if (
|
|
103
|
-
|
|
267
|
+
if (import_fs3.default.existsSync(userDataDir)) {
|
|
268
|
+
import_fs3.default.rmSync(userDataDir, { recursive: true });
|
|
104
269
|
}
|
|
105
270
|
}
|
|
106
271
|
function ensureWalletExtensionExists(walletPath, walletName) {
|
|
107
|
-
|
|
272
|
+
const manifestPath = import_path3.default.join(walletPath, "manifest.json");
|
|
273
|
+
if (!import_fs3.default.existsSync(manifestPath)) {
|
|
108
274
|
const cliAlias = walletName.toLowerCase();
|
|
109
275
|
throw new Error(
|
|
110
|
-
`Cannot find ${walletName}.
|
|
276
|
+
`Cannot find ${walletName} extension.
|
|
277
|
+
Checked: ${manifestPath}
|
|
278
|
+
Download it: npx w3wallets ${cliAlias}
|
|
279
|
+
Custom dir: npx w3wallets -o <dir> ${cliAlias}`
|
|
111
280
|
);
|
|
112
281
|
}
|
|
113
282
|
}
|
|
114
|
-
function
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
);
|
|
123
294
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for (let i = 0; i < 16; i++) {
|
|
128
|
-
const byte = hash[i];
|
|
129
|
-
extensionId += ALPHABET[byte >> 4 & 15];
|
|
130
|
-
extensionId += ALPHABET[byte & 15];
|
|
295
|
+
const page = await context.newPage();
|
|
296
|
+
if (homeUrl) {
|
|
297
|
+
await page.goto(`chrome-extension://${expectedExtensionId}/${homeUrl}`);
|
|
131
298
|
}
|
|
132
|
-
|
|
299
|
+
const extension = new ExtensionClass(page, expectedExtensionId);
|
|
300
|
+
return extension;
|
|
133
301
|
}
|
|
134
302
|
async function initializeExtension(context, ExtensionClass, expectedExtensionId, walletName) {
|
|
135
303
|
const expectedUrl = `chrome-extension://${expectedExtensionId}/`;
|
|
@@ -142,121 +310,58 @@ async function initializeExtension(context, ExtensionClass, expectedExtensionId,
|
|
|
142
310
|
}
|
|
143
311
|
const page = await context.newPage();
|
|
144
312
|
const extension = new ExtensionClass(page, expectedExtensionId);
|
|
145
|
-
await extension.gotoOnboardPage();
|
|
146
313
|
return extension;
|
|
147
314
|
}
|
|
148
315
|
|
|
149
316
|
// src/core/types.ts
|
|
150
|
-
function createWallet(
|
|
151
|
-
return
|
|
317
|
+
function createWallet(config2) {
|
|
318
|
+
return config2;
|
|
152
319
|
}
|
|
153
320
|
|
|
154
|
-
// src/wallets/
|
|
155
|
-
var
|
|
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
|
+
};
|
|
156
345
|
|
|
157
346
|
// src/core/wallet.ts
|
|
158
347
|
var Wallet = class {
|
|
159
348
|
constructor(page, extensionId) {
|
|
160
349
|
this.page = page;
|
|
161
350
|
this.extensionId = extensionId;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// src/wallets/backpack/backpack.ts
|
|
166
|
-
var Backpack = class extends Wallet {
|
|
167
|
-
defaultPassword = "11111111";
|
|
168
|
-
currentAccountId = 0;
|
|
169
|
-
maxAccountId = 0;
|
|
170
|
-
async gotoOnboardPage() {
|
|
171
|
-
await this.page.goto(
|
|
172
|
-
`chrome-extension://${this.extensionId}/onboarding.html`
|
|
173
|
-
);
|
|
174
|
-
await (0, import_test2.expect)(this.page.getByText("Welcome to Backpack")).toBeVisible();
|
|
175
|
-
}
|
|
176
|
-
async onboard(network, privateKey) {
|
|
177
|
-
this.currentAccountId++;
|
|
178
|
-
this.maxAccountId++;
|
|
179
|
-
return this._importAccount(network, privateKey, true);
|
|
180
|
-
}
|
|
181
|
-
async addAccount(network, privateKey) {
|
|
182
|
-
this.maxAccountId++;
|
|
183
|
-
this.currentAccountId = this.maxAccountId;
|
|
184
|
-
await this.page.goto(
|
|
185
|
-
`chrome-extension://${this.extensionId}/onboarding.html?add-user-account=true`
|
|
186
|
-
);
|
|
187
|
-
await this._importAccount(network, privateKey, false);
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Switch account
|
|
191
|
-
* @param id The first added account has id 1, the second – 2, and so on
|
|
192
|
-
*/
|
|
193
|
-
async switchAccount(id) {
|
|
194
|
-
await this.page.getByRole("button", { name: `A${this.currentAccountId}` }).click();
|
|
195
|
-
await this.page.getByRole("button", { name: `Account ${id}` }).click();
|
|
196
|
-
this.currentAccountId = id;
|
|
197
|
-
}
|
|
198
|
-
async unlock() {
|
|
199
|
-
await this.page.getByPlaceholder("Password").fill(this.defaultPassword);
|
|
200
|
-
await this.page.getByRole("button", { name: "Unlock" }).click();
|
|
201
|
-
}
|
|
202
|
-
async setRPC(network, rpc) {
|
|
203
|
-
await this._clickOnAccount();
|
|
204
|
-
await this.page.getByRole("button", { name: "Settings" }).click();
|
|
205
|
-
await this.page.getByRole("button", { name: network }).click();
|
|
206
|
-
await this.page.getByRole("button", { name: "RPC Connection" }).click();
|
|
207
|
-
await this.page.getByRole("button", { name: "Custom" }).click();
|
|
208
|
-
await this.page.getByPlaceholder("RPC URL").fill(rpc);
|
|
209
|
-
await this.page.keyboard.press("Enter");
|
|
210
|
-
}
|
|
211
|
-
async ignoreAndProceed() {
|
|
212
|
-
const ignoreButton = this.page.getByText("Ignore and proceed anyway.");
|
|
213
|
-
await ignoreButton.click();
|
|
214
|
-
}
|
|
215
|
-
async approve() {
|
|
216
|
-
await this.page.getByText("Approve", { exact: true }).click();
|
|
217
|
-
}
|
|
218
|
-
async deny() {
|
|
219
|
-
await this.page.getByText("Deny", { exact: true }).click();
|
|
220
|
-
}
|
|
221
|
-
async _clickOnAccount() {
|
|
222
|
-
return this.page.getByRole("button", { name: `A${this.currentAccountId}`, exact: true }).click();
|
|
223
|
-
}
|
|
224
|
-
async _importAccount(network, privateKey, isOnboard) {
|
|
225
|
-
{
|
|
226
|
-
await this.page.waitForTimeout(2e3);
|
|
227
|
-
if (await this.page.getByText("You're all good!").isVisible()) {
|
|
228
|
-
await this.page.getByLabel("Go back").click();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
await this.page.getByTestId("terms-of-service-checkbox").click();
|
|
232
|
-
await this.page.getByText("I already have a wallet").click();
|
|
233
|
-
await this.page.getByText(network).click();
|
|
234
|
-
await this.page.getByText("Private key").click();
|
|
235
|
-
await this.page.getByPlaceholder("Private key").fill(privateKey);
|
|
236
|
-
await this.page.waitForTimeout(1e3);
|
|
237
|
-
await this.page.getByText("Import", { exact: true }).click();
|
|
238
|
-
if (isOnboard) {
|
|
239
|
-
await this.page.getByRole("textbox").nth(0).fill(this.defaultPassword);
|
|
240
|
-
await this.page.getByRole("textbox").nth(1).fill(this.defaultPassword);
|
|
241
|
-
await this.page.getByText("Next", { exact: true }).click();
|
|
242
|
-
await (0, import_test2.expect)(this.page.getByText("You're all good!")).toBeVisible();
|
|
351
|
+
if (config.actionTimeout) {
|
|
352
|
+
page.setDefaultTimeout(config.actionTimeout);
|
|
243
353
|
}
|
|
244
|
-
await this.page.goto(`chrome-extension://${this.extensionId}/popup.html`);
|
|
245
|
-
await this.page.getByTestId("__CAROUSEL_ITEM_0__").waitFor({ state: "visible" });
|
|
246
354
|
}
|
|
247
355
|
};
|
|
248
356
|
|
|
249
357
|
// src/wallets/metamask/metamask.ts
|
|
250
|
-
var import_test3 = require("@playwright/test");
|
|
251
358
|
var Metamask = class extends Wallet {
|
|
252
359
|
defaultPassword = "TestPassword123!";
|
|
253
360
|
async gotoOnboardPage() {
|
|
254
|
-
await this.page.goto(
|
|
255
|
-
`chrome-extension://${this.extensionId}/home.html#onboarding/welcome`
|
|
256
|
-
);
|
|
361
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
257
362
|
await (0, import_test3.expect)(
|
|
258
363
|
this.page.getByRole("button", { name: "I have an existing wallet" })
|
|
259
|
-
).toBeVisible();
|
|
364
|
+
).toBeVisible({ timeout: ONBOARD_VISIBLE_TIMEOUT });
|
|
260
365
|
}
|
|
261
366
|
/**
|
|
262
367
|
* Onboard MetaMask with a mnemonic phrase
|
|
@@ -264,68 +369,328 @@ var Metamask = class extends Wallet {
|
|
|
264
369
|
* @param password - Optional password (defaults to TestPassword123!)
|
|
265
370
|
*/
|
|
266
371
|
async onboard(mnemonic, password) {
|
|
372
|
+
debug("metamask.onboard: starting");
|
|
267
373
|
const pwd = password ?? this.defaultPassword;
|
|
374
|
+
await this.gotoOnboardPage();
|
|
268
375
|
await this.page.getByRole("button", { name: "I have an existing wallet" }).click();
|
|
269
376
|
await this.page.getByRole("button", { name: "Import using Secret Recovery Phrase" }).click();
|
|
270
|
-
const
|
|
271
|
-
await
|
|
272
|
-
|
|
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
|
+
}
|
|
273
387
|
const continueBtn = this.page.getByTestId("import-srp-confirm");
|
|
388
|
+
await (0, import_test3.expect)(continueBtn).toBeEnabled({ timeout: config.expectTimeout });
|
|
274
389
|
await continueBtn.click();
|
|
275
390
|
const passwordInputs = this.page.locator('input[type="password"]');
|
|
276
391
|
await passwordInputs.nth(0).fill(pwd);
|
|
277
392
|
await passwordInputs.nth(1).fill(pwd);
|
|
278
|
-
await this.page.getByRole("checkbox").click();
|
|
393
|
+
await this.page.getByRole("checkbox").click({ force: true });
|
|
279
394
|
await this.page.getByRole("button", { name: "Create password" }).click();
|
|
395
|
+
const passkeyMaybeLater = this.page.getByTestId(
|
|
396
|
+
"passkey-maybe-later-button"
|
|
397
|
+
);
|
|
280
398
|
const metametricsBtn = this.page.getByTestId("metametrics-i-agree");
|
|
281
|
-
await
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
399
|
+
const postPasswordState = await Promise.race([
|
|
400
|
+
passkeyMaybeLater.waitFor({ state: "visible", timeout: ONBOARD_VISIBLE_TIMEOUT }).then(() => "passkey"),
|
|
401
|
+
metametricsBtn.waitFor({ state: "visible", timeout: ONBOARD_VISIBLE_TIMEOUT }).then(() => "metametrics")
|
|
402
|
+
]);
|
|
403
|
+
if (postPasswordState === "passkey") {
|
|
404
|
+
await passkeyMaybeLater.click();
|
|
405
|
+
}
|
|
406
|
+
await metametricsBtn.waitFor({
|
|
407
|
+
state: "visible",
|
|
408
|
+
timeout: ONBOARD_VISIBLE_TIMEOUT
|
|
285
409
|
});
|
|
286
|
-
await
|
|
410
|
+
await metametricsBtn.click();
|
|
411
|
+
const openWalletBtn = this.page.getByTestId("onboarding-complete-done");
|
|
287
412
|
await openWalletBtn.click();
|
|
413
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
288
414
|
await this.page.goto(
|
|
289
415
|
`chrome-extension://${this.extensionId}/sidepanel.html`
|
|
290
416
|
);
|
|
291
|
-
|
|
417
|
+
debug("metamask.onboard: complete");
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Dismiss MetaMask promotional popups (e.g., "Transaction Shield")
|
|
421
|
+
* that may overlay the confirmation UI.
|
|
422
|
+
*/
|
|
423
|
+
async dismissPopups() {
|
|
424
|
+
debug("metamask.dismissPopups: checking for popups");
|
|
425
|
+
const popup = this.page.getByText(/Transaction Shield|free trial/i);
|
|
426
|
+
if (!await popup.first().isVisible({ timeout: POPUP_VISIBILITY_TIMEOUT }).catch(() => false)) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const shieldClose = this.page.getByTestId(
|
|
430
|
+
"shield-entry-modal-close-button"
|
|
431
|
+
);
|
|
432
|
+
if (await shieldClose.isVisible({ timeout: SHIELD_CLOSE_TIMEOUT }).catch(() => false)) {
|
|
433
|
+
await shieldClose.click();
|
|
434
|
+
if (await this.waitForPopupHidden(popup)) return;
|
|
435
|
+
}
|
|
436
|
+
const closeByAria = this.page.locator('button[aria-label="close"]').first();
|
|
437
|
+
if (await closeByAria.isVisible({ timeout: ARIA_CLOSE_TIMEOUT }).catch(() => false)) {
|
|
438
|
+
await closeByAria.click();
|
|
439
|
+
if (await this.waitForPopupHidden(popup)) return;
|
|
440
|
+
}
|
|
441
|
+
await this.page.keyboard.press("Escape");
|
|
442
|
+
await this.waitForPopupHidden(popup);
|
|
443
|
+
}
|
|
444
|
+
async waitForPopupHidden(popup) {
|
|
445
|
+
try {
|
|
446
|
+
await popup.first().waitFor({ state: "hidden", timeout: POPUP_HIDDEN_TIMEOUT });
|
|
447
|
+
return true;
|
|
448
|
+
} catch {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* MetaMask's MV3 service worker can fail to come up on cold-start
|
|
454
|
+
* (especially in fresh-extension CI runs), showing an error screen
|
|
455
|
+
* with a "Restart MetaMask" button. Detect and click it so the
|
|
456
|
+
* normal post-unlock flow can proceed.
|
|
457
|
+
*/
|
|
458
|
+
async recoverFromStartupError() {
|
|
459
|
+
const restartBtn = this.page.getByRole("button", {
|
|
460
|
+
name: "Restart MetaMask"
|
|
461
|
+
});
|
|
462
|
+
if (!await restartBtn.isVisible({ timeout: POPUP_VISIBILITY_TIMEOUT }).catch(() => false)) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
debug("metamask.recoverFromStartupError: clicking Restart MetaMask");
|
|
466
|
+
await restartBtn.click();
|
|
467
|
+
await restartBtn.waitFor({
|
|
468
|
+
state: "hidden",
|
|
469
|
+
timeout: POST_UNLOCK_TIMEOUT
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* After unlock, MetaMask may show onboarding screens, queued
|
|
474
|
+
* notifications, or go straight to the wallet UI. Race all possible
|
|
475
|
+
* states in a single wait to avoid sequential timeout penalties.
|
|
476
|
+
*/
|
|
477
|
+
async stabilizePostUnlock() {
|
|
478
|
+
debug("metamask.stabilizePostUnlock: racing post-unlock states");
|
|
479
|
+
await this.recoverFromStartupError();
|
|
480
|
+
const passkeyMaybeLater = this.page.getByTestId(
|
|
481
|
+
"passkey-maybe-later-button"
|
|
482
|
+
);
|
|
483
|
+
const metametricsBtn = this.page.getByTestId("metametrics-i-agree");
|
|
484
|
+
const openWalletBtn = this.page.getByTestId("onboarding-complete-done");
|
|
485
|
+
const readyIndicator = this.page.getByTestId("account-options-menu-button");
|
|
486
|
+
const rejectAllBtn = this.page.getByText("Reject all");
|
|
487
|
+
const notificationCancelBtn = this.page.getByTestId(
|
|
488
|
+
"confirmation-cancel-button"
|
|
489
|
+
);
|
|
490
|
+
const state = await Promise.race([
|
|
491
|
+
passkeyMaybeLater.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "passkey"),
|
|
492
|
+
metametricsBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "metametrics"),
|
|
493
|
+
openWalletBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "openWallet"),
|
|
494
|
+
rejectAllBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "rejectAll"),
|
|
495
|
+
notificationCancelBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "notification"),
|
|
496
|
+
readyIndicator.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "ready")
|
|
497
|
+
]).catch(() => "timeout");
|
|
498
|
+
debug(`metamask.stabilizePostUnlock: state=${state}`);
|
|
499
|
+
if (state === "timeout") {
|
|
500
|
+
debug(
|
|
501
|
+
`metamask.stabilizePostUnlock: timeout after ${POST_UNLOCK_TIMEOUT}ms. URL: ${this.page.url()}. Checked: passkey-maybe-later-button, metametrics-i-agree, onboarding-complete-done, Reject all, confirmation-cancel-button, account-options-menu-button`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
if (state === "passkey") {
|
|
505
|
+
await passkeyMaybeLater.click();
|
|
506
|
+
if (await metametricsBtn.isVisible({ timeout: POST_UNLOCK_TIMEOUT }).catch(() => false)) {
|
|
507
|
+
await metametricsBtn.click();
|
|
508
|
+
}
|
|
509
|
+
if (await openWalletBtn.isVisible({ timeout: POPUP_HIDDEN_TIMEOUT }).catch(() => false)) {
|
|
510
|
+
await openWalletBtn.click();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (state === "metametrics") {
|
|
514
|
+
await metametricsBtn.click();
|
|
515
|
+
if (await openWalletBtn.isVisible({ timeout: POPUP_HIDDEN_TIMEOUT }).catch(() => false)) {
|
|
516
|
+
await openWalletBtn.click();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (state === "openWallet") {
|
|
520
|
+
await openWalletBtn.click();
|
|
521
|
+
}
|
|
522
|
+
if (state !== "ready") {
|
|
523
|
+
await this.dismissQueuedNotifications();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Dismiss all queued MetaMask notifications (e.g., Solana/Tron account
|
|
528
|
+
* removal). These use the templated confirmation flow at #/confirmation/...
|
|
529
|
+
* If multiple are queued, "Reject all" appears; if only one, just
|
|
530
|
+
* confirmation-cancel-button is available.
|
|
531
|
+
*/
|
|
532
|
+
async dismissQueuedNotifications() {
|
|
533
|
+
const homeUrl = `chrome-extension://${this.extensionId}/home.html`;
|
|
534
|
+
const readyIndicator = this.page.getByTestId("account-options-menu-button");
|
|
535
|
+
const rejectAllBtn = this.page.getByText("Reject all");
|
|
536
|
+
const notificationCancelBtn = this.page.getByTestId(
|
|
537
|
+
"confirmation-cancel-button"
|
|
538
|
+
);
|
|
539
|
+
await this.page.goto(homeUrl);
|
|
540
|
+
await this.recoverFromStartupError();
|
|
541
|
+
for (let i = 0; i < 10; i++) {
|
|
542
|
+
const state = await Promise.race([
|
|
543
|
+
readyIndicator.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "ready"),
|
|
544
|
+
rejectAllBtn.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "rejectAll"),
|
|
545
|
+
notificationCancelBtn.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "notification")
|
|
546
|
+
]).catch(() => "timeout");
|
|
547
|
+
if (state === "ready") return;
|
|
548
|
+
if (state === "rejectAll") {
|
|
549
|
+
await rejectAllBtn.click();
|
|
550
|
+
await this.page.goto(homeUrl);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (state === "notification") {
|
|
554
|
+
await notificationCancelBtn.click();
|
|
555
|
+
await this.page.goto(homeUrl);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
debug(
|
|
559
|
+
`metamask.dismissQueuedNotifications: timeout at iteration ${i}. URL: ${this.page.url()}`
|
|
560
|
+
);
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
await (0, import_test3.expect)(readyIndicator).toBeVisible({ timeout: POST_UNLOCK_TIMEOUT });
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Wait for a target button while handling the Transaction Shield popup.
|
|
567
|
+
* Always navigates to sidepanel.html fresh so MetaMask's
|
|
568
|
+
* ConfirmationHandler can route to the pending approval.
|
|
569
|
+
*/
|
|
570
|
+
async waitAndClickButton(btnLocator) {
|
|
571
|
+
debug(`metamask.waitAndClickButton: navigating to sidepanel`);
|
|
572
|
+
const popup = this.page.getByText(/Transaction Shield|free trial/i);
|
|
573
|
+
const sidepanelUrl = `chrome-extension://${this.extensionId}/sidepanel.html`;
|
|
574
|
+
const confirmRoutePattern = /#\/(confirm-transaction|connect|confirmation)\b/;
|
|
575
|
+
const waitForButtonOrPopup = (timeout) => Promise.race([
|
|
576
|
+
btnLocator.first().waitFor({ state: "visible", timeout }).then(() => "button"),
|
|
577
|
+
popup.first().waitFor({ state: "visible", timeout }).then(() => "popup")
|
|
578
|
+
]).catch(() => "timeout");
|
|
579
|
+
const handlePopupAndClick = async () => {
|
|
580
|
+
await this.dismissPopups();
|
|
581
|
+
await btnLocator.first().waitFor({ state: "visible", timeout: BUTTON_OR_POPUP_TIMEOUT });
|
|
582
|
+
await btnLocator.first().click();
|
|
583
|
+
};
|
|
584
|
+
let routeFound = false;
|
|
585
|
+
for (let attempt = 0; attempt < MAX_ROUTE_ATTEMPTS; attempt++) {
|
|
586
|
+
try {
|
|
587
|
+
await this.page.goto(sidepanelUrl, { timeout: ROUTE_RETRY_TIMEOUT });
|
|
588
|
+
await this.page.waitForURL(confirmRoutePattern, {
|
|
589
|
+
timeout: ROUTE_RETRY_TIMEOUT
|
|
590
|
+
});
|
|
591
|
+
routeFound = true;
|
|
592
|
+
break;
|
|
593
|
+
} catch {
|
|
594
|
+
debug(
|
|
595
|
+
`metamask.waitAndClickButton: route attempt ${attempt + 1}/${MAX_ROUTE_ATTEMPTS} failed. URL: ${this.page.url()}`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (!routeFound) {
|
|
600
|
+
console.warn(
|
|
601
|
+
`[w3wallets] confirmation route not found after ${MAX_ROUTE_ATTEMPTS} attempts. URL: ${this.page.url()}`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const result = await waitForButtonOrPopup(BUTTON_OR_POPUP_TIMEOUT);
|
|
605
|
+
debug(
|
|
606
|
+
`metamask.waitAndClickButton: result=${result}, URL=${this.page.url()}`
|
|
607
|
+
);
|
|
608
|
+
if (result === "button") {
|
|
609
|
+
await btnLocator.first().click();
|
|
610
|
+
await this.page.waitForURL((url) => !confirmRoutePattern.test(url.toString()), {
|
|
611
|
+
timeout: POST_CLICK_TIMEOUT
|
|
612
|
+
}).catch(() => {
|
|
613
|
+
console.warn(
|
|
614
|
+
`[w3wallets] still on confirmation route after click. URL: ${this.page.url()}`
|
|
615
|
+
);
|
|
616
|
+
});
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (result === "popup") {
|
|
620
|
+
await handlePopupAndClick();
|
|
621
|
+
await this.page.waitForURL((url) => !confirmRoutePattern.test(url.toString()), {
|
|
622
|
+
timeout: POST_CLICK_TIMEOUT
|
|
623
|
+
}).catch(() => {
|
|
624
|
+
console.warn(
|
|
625
|
+
`[w3wallets] still on confirmation route after popup dismiss. URL: ${this.page.url()}`
|
|
626
|
+
);
|
|
627
|
+
});
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const isOnConfirmRoute = confirmRoutePattern.test(this.page.url());
|
|
631
|
+
debug(
|
|
632
|
+
`metamask.waitAndClickButton: timeout after ${BUTTON_OR_POPUP_TIMEOUT}ms. URL: ${this.page.url()}, onConfirmRoute: ${isOnConfirmRoute}`
|
|
633
|
+
);
|
|
634
|
+
console.warn(
|
|
635
|
+
`[w3wallets] no button or popup found after ${BUTTON_OR_POPUP_TIMEOUT / 1e3}s. URL: ${this.page.url()}`
|
|
636
|
+
);
|
|
637
|
+
await btnLocator.first().click({ timeout: LAST_RESORT_CLICK_TIMEOUT });
|
|
292
638
|
}
|
|
293
639
|
async approve() {
|
|
294
|
-
|
|
295
|
-
|
|
640
|
+
debug("metamask.approve: starting");
|
|
641
|
+
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 }));
|
|
642
|
+
await this.waitAndClickButton(confirmBtn);
|
|
296
643
|
}
|
|
297
644
|
async deny() {
|
|
298
|
-
|
|
299
|
-
|
|
645
|
+
debug("metamask.deny: starting");
|
|
646
|
+
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 }));
|
|
647
|
+
await this.waitAndClickButton(cancelBtn);
|
|
300
648
|
}
|
|
301
649
|
/**
|
|
302
650
|
* Lock the MetaMask wallet
|
|
303
651
|
*/
|
|
304
652
|
async lock() {
|
|
305
|
-
|
|
306
|
-
await this.page.
|
|
653
|
+
debug("metamask.lock: starting");
|
|
654
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
655
|
+
const menuBtn = this.page.getByTestId("account-options-menu-button");
|
|
656
|
+
await menuBtn.waitFor({ state: "visible", timeout: MENU_BUTTON_TIMEOUT });
|
|
657
|
+
await menuBtn.click({ force: true });
|
|
658
|
+
await this.page.getByTestId("global-menu-lock").click();
|
|
307
659
|
}
|
|
308
660
|
/**
|
|
309
|
-
* Unlock MetaMask with password
|
|
661
|
+
* Unlock MetaMask with password.
|
|
662
|
+
* After unlocking, stabilizes the wallet UI by handling post-unlock
|
|
663
|
+
* screens (metametrics, onboarding completion) and dismissing queued
|
|
664
|
+
* notifications. Ends on home.html with the wallet UI ready.
|
|
310
665
|
*/
|
|
311
666
|
async unlock(password) {
|
|
667
|
+
debug("metamask.unlock: starting");
|
|
312
668
|
const pwd = password ?? this.defaultPassword;
|
|
669
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
313
670
|
const passwordInput = this.page.getByTestId("unlock-password");
|
|
314
671
|
await passwordInput.fill(pwd);
|
|
315
672
|
await this.page.getByTestId("unlock-submit").click();
|
|
673
|
+
await this.page.waitForSelector('[data-testid="unlock-password"]', {
|
|
674
|
+
state: "hidden",
|
|
675
|
+
timeout: LOCK_SCREEN_TIMEOUT
|
|
676
|
+
});
|
|
677
|
+
await this.stabilizePostUnlock();
|
|
678
|
+
debug("metamask.unlock: complete");
|
|
316
679
|
}
|
|
317
680
|
/**
|
|
318
681
|
* Switch to an existing network in MetaMask
|
|
319
682
|
* @param networkName - Name of the network to switch to (e.g., "Ethereum Mainnet", "Sepolia")
|
|
320
683
|
*/
|
|
321
684
|
async switchNetwork(networkName, networkType = "Popular") {
|
|
685
|
+
debug(`metamask.switchNetwork: ${networkName} (${networkType})`);
|
|
322
686
|
await this.page.getByTestId("sort-by-networks").click();
|
|
323
687
|
if (networkType === "Custom") {
|
|
324
|
-
await this.page.getByRole("tab", { name: "Custom" }).click();
|
|
688
|
+
await this.page.getByRole("tab", { name: "Custom" }).click({ force: true });
|
|
325
689
|
}
|
|
326
690
|
await this.page.getByText(networkName).click();
|
|
327
691
|
await (0, import_test3.expect)(this.page.getByTestId("sort-by-networks")).toHaveText(
|
|
328
|
-
networkName
|
|
692
|
+
networkName,
|
|
693
|
+
{ timeout: config.expectTimeout }
|
|
329
694
|
);
|
|
330
695
|
}
|
|
331
696
|
async switchAccount(accountName) {
|
|
@@ -346,7 +711,7 @@ var Metamask = class extends Wallet {
|
|
|
346
711
|
await this.page.getByRole("button", { name: /save/i }).click();
|
|
347
712
|
}
|
|
348
713
|
async addCustomNetwork(settings) {
|
|
349
|
-
await this.page.getByTestId("account-options-menu-button").click();
|
|
714
|
+
await this.page.getByTestId("account-options-menu-button").click({ force: true });
|
|
350
715
|
await this.page.getByTestId("global-menu-networks").click();
|
|
351
716
|
await this.page.getByRole("button", { name: "Add a custom network" }).click();
|
|
352
717
|
await this.page.getByTestId("network-form-network-name").fill(settings.name);
|
|
@@ -357,24 +722,33 @@ var Metamask = class extends Wallet {
|
|
|
357
722
|
await this.page.getByTestId("rpc-url-input-test").fill(settings.rpc);
|
|
358
723
|
await this.page.getByRole("button", { name: "Add URL" }).click();
|
|
359
724
|
await this.page.getByRole("button", { name: "Save" }).click();
|
|
725
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
726
|
+
await this.page.waitForLoadState("domcontentloaded");
|
|
360
727
|
}
|
|
361
728
|
async enableTestNetworks() {
|
|
362
|
-
await this.page.getByTestId("account-options-menu-button").click();
|
|
729
|
+
await this.page.getByTestId("account-options-menu-button").click({ force: true });
|
|
363
730
|
await this.page.getByTestId("global-menu-networks").click();
|
|
364
|
-
|
|
365
|
-
|
|
731
|
+
const toggle = this.page.locator(
|
|
732
|
+
"text=Show test networks >> xpath=following-sibling::label"
|
|
733
|
+
);
|
|
734
|
+
await (0, import_test3.expect)(toggle).toBeVisible({ timeout: config.expectTimeout });
|
|
735
|
+
await toggle.click();
|
|
736
|
+
await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
|
|
366
737
|
}
|
|
367
738
|
async importAccount(privateKey) {
|
|
739
|
+
debug("metamask.importAccount: starting");
|
|
368
740
|
await this.page.getByTestId("account-menu-icon").click();
|
|
369
741
|
await this.page.getByTestId("account-list-add-wallet-button").click();
|
|
370
|
-
await this.page.getByTestId("
|
|
742
|
+
await this.page.getByTestId("choose-wallet-type-import-account").click();
|
|
371
743
|
await this.page.locator("#private-key-box").fill(privateKey);
|
|
372
744
|
await this.page.getByTestId("import-account-confirm-button").click();
|
|
373
|
-
await this.page.
|
|
745
|
+
await this.page.getByTestId("back-button").click();
|
|
746
|
+
await this.page.locator('[data-testid^="multichain-account-cell-keyring:"]').first().click();
|
|
374
747
|
}
|
|
375
748
|
async accountNameIs(accountName) {
|
|
376
749
|
await (0, import_test3.expect)(this.page.getByTestId("account-menu-icon")).toContainText(
|
|
377
|
-
accountName
|
|
750
|
+
accountName,
|
|
751
|
+
{ timeout: config.expectTimeout }
|
|
378
752
|
);
|
|
379
753
|
}
|
|
380
754
|
};
|
|
@@ -387,9 +761,11 @@ var PolkadotJS = class extends Wallet {
|
|
|
387
761
|
await this.page.goto(`chrome-extension://${this.extensionId}/index.html`);
|
|
388
762
|
await (0, import_test4.expect)(
|
|
389
763
|
this.page.getByText("Before we start, just a couple of notes")
|
|
390
|
-
).toBeVisible();
|
|
764
|
+
).toBeVisible({ timeout: config.expectTimeout });
|
|
391
765
|
}
|
|
392
766
|
async onboard(seed, password, name) {
|
|
767
|
+
debug("polkadotJS.onboard: starting");
|
|
768
|
+
await this.gotoOnboardPage();
|
|
393
769
|
await this.page.getByRole("button", { name: "Understood, let me continue" }).click();
|
|
394
770
|
await this.page.getByRole("button", { name: "I Understand" }).click();
|
|
395
771
|
await this.page.locator(".popupToggle").first().click();
|
|
@@ -406,12 +782,13 @@ var PolkadotJS = class extends Wallet {
|
|
|
406
782
|
password ?? this.defaultPassword
|
|
407
783
|
);
|
|
408
784
|
await this.page.getByRole("button", { name: "Add the account with the supplied seed" }).click();
|
|
785
|
+
debug("polkadotJS.onboard: complete");
|
|
409
786
|
}
|
|
410
787
|
async selectAllAccounts() {
|
|
411
788
|
await this.page.getByText("Select all").click();
|
|
412
789
|
}
|
|
413
790
|
async selectAccount(accountId) {
|
|
414
|
-
await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").
|
|
791
|
+
await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").click();
|
|
415
792
|
}
|
|
416
793
|
async enterPassword(password) {
|
|
417
794
|
await this._getLabeledInput("Password for this account").fill(
|
|
@@ -419,6 +796,7 @@ var PolkadotJS = class extends Wallet {
|
|
|
419
796
|
);
|
|
420
797
|
}
|
|
421
798
|
async approve() {
|
|
799
|
+
debug("polkadotJS.approve: starting");
|
|
422
800
|
const connect = this.page.getByRole("button", { name: "Connect" });
|
|
423
801
|
const signTransaction = this.page.getByRole("button", {
|
|
424
802
|
name: "Sign the transaction"
|
|
@@ -426,6 +804,7 @@ var PolkadotJS = class extends Wallet {
|
|
|
426
804
|
await connect.or(signTransaction).click();
|
|
427
805
|
}
|
|
428
806
|
async deny() {
|
|
807
|
+
debug("polkadotJS.deny: starting");
|
|
429
808
|
const reject = this.page.getByRole("button", { name: "Reject" });
|
|
430
809
|
const cancel = this.page.getByRole("link", { name: "Cancel" });
|
|
431
810
|
await reject.or(cancel).click();
|
|
@@ -438,29 +817,37 @@ var PolkadotJS = class extends Wallet {
|
|
|
438
817
|
};
|
|
439
818
|
|
|
440
819
|
// src/wallets/index.ts
|
|
441
|
-
var backpack = createWallet({
|
|
442
|
-
name: "backpack",
|
|
443
|
-
extensionDir: "backpack",
|
|
444
|
-
WalletClass: Backpack
|
|
445
|
-
});
|
|
446
820
|
var metamask = createWallet({
|
|
447
821
|
name: "metamask",
|
|
448
822
|
extensionDir: "metamask",
|
|
449
|
-
WalletClass: Metamask
|
|
823
|
+
WalletClass: Metamask,
|
|
824
|
+
homeUrl: "home.html"
|
|
450
825
|
});
|
|
451
826
|
var polkadotJS = createWallet({
|
|
452
827
|
name: "polkadotJS",
|
|
453
828
|
extensionDir: "polkadotjs",
|
|
454
|
-
WalletClass: PolkadotJS
|
|
829
|
+
WalletClass: PolkadotJS,
|
|
830
|
+
homeUrl: "index.html"
|
|
455
831
|
});
|
|
832
|
+
|
|
833
|
+
// src/cache/prepareWallet.ts
|
|
834
|
+
function prepareWallet(walletConfig, setupFn) {
|
|
835
|
+
return {
|
|
836
|
+
...walletConfig,
|
|
837
|
+
setupFn,
|
|
838
|
+
__cached: true
|
|
839
|
+
};
|
|
840
|
+
}
|
|
456
841
|
// Annotate the CommonJS export names for ESM import in node:
|
|
457
842
|
0 && (module.exports = {
|
|
458
|
-
Backpack,
|
|
459
843
|
Metamask,
|
|
460
844
|
PolkadotJS,
|
|
461
|
-
|
|
845
|
+
config,
|
|
462
846
|
createWallet,
|
|
847
|
+
debug,
|
|
848
|
+
isCachedConfig,
|
|
463
849
|
metamask,
|
|
464
850
|
polkadotJS,
|
|
851
|
+
prepareWallet,
|
|
465
852
|
withWallets
|
|
466
853
|
});
|