w3wallets 0.1.3 → 0.2.0

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 CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  ![License](https://img.shields.io/badge/License-MIT-yellow.svg)
4
4
  [![npm version](https://img.shields.io/npm/v/w3wallets.svg)](https://www.npmjs.com/package/w3wallets)
5
+ ![CodeQL](https://github.com/Maksandre/w3wallets/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)
5
6
 
6
7
  Web3 wallets for Playwright.
7
8
 
@@ -13,15 +14,15 @@ npm install -D w3wallets
13
14
 
14
15
  ## Getting Started
15
16
 
16
- Only the `Backpack` wallet is supported at this point.
17
+ The `Backpack` and the `Polkadot{.js}` wallets are currently supported.
17
18
 
18
- #### 1. Download Backpack
19
+ #### 1. Download wallets
19
20
 
20
21
  ```sh
21
- npx w3wallets backpack
22
+ npx w3wallets backpack polkadotJS
22
23
  ```
23
24
 
24
- The unzipped files should be stored in the `wallets/backpack` directory. Add them to `.gitignore`.
25
+ The unzipped files should be stored in the `.w3wallets/<wallet-name>` directory. Add them to `.gitignore`.
25
26
 
26
27
  #### 2. Wrap your fixture `withWallets`
27
28
 
@@ -29,7 +30,8 @@ The unzipped files should be stored in the `wallets/backpack` directory. Add the
29
30
  import { test as base } from "@playwright/test";
30
31
  import { withWallets } from "../src/withWallets";
31
32
 
32
- const test = withWallets(base, { backpack: true });
33
+ // Specify one or many wallets that should be installed in the browser
34
+ const test = withWallets(base, "backpack", "polkadotJS");
33
35
 
34
36
  test("has title", async ({ page, backpack }) => {
35
37
  await page.goto("https://playwright.dev/");
@@ -40,3 +42,38 @@ test("has title", async ({ page, backpack }) => {
40
42
  await backpack.onboard("Eclipse", privateKey);
41
43
  });
42
44
  ```
45
+
46
+ ## Run tests
47
+
48
+ To work on this project in VS Code, make sure you open the project's root directory.
49
+
50
+ 0. Create the `.env` using `.env.example` as a reference.
51
+ 1. Install dependencies
52
+
53
+ ```sh
54
+ yarn
55
+ ```
56
+
57
+ 2. Install Chrome browser
58
+
59
+ ```sh
60
+ npx playwright install chromium
61
+ ```
62
+
63
+ 3. Download wallet extensions
64
+
65
+ ```sh
66
+ npx w3wallets backpack polkadotJS
67
+ ```
68
+
69
+ 4. Start UI
70
+
71
+ ```sh
72
+ yarn start:ui
73
+ ```
74
+
75
+ 5. Run Tests with Playwright
76
+
77
+ ```sh
78
+ yarn test
79
+ ```
package/dist/index.d.mts CHANGED
@@ -1,13 +1,27 @@
1
1
  import * as playwright_test from 'playwright/test';
2
2
  import { Page, test, BrowserContext } from '@playwright/test';
3
3
 
4
- type BackPackNetwork = "Eclipse" | "Ethereum";
4
+ /**
5
+ * Represents the supported network types for the BackPack application.
6
+ */
7
+ type BackPackNetwork = "Solana" | "Eclipse" | "Ethereum" | "Polygon" | "Base" | "Arbitrum" | "Optimism";
5
8
 
6
- declare class Backpack {
7
- private page;
8
- private extensionId;
9
- private defaultPassword;
9
+ type WalletName = "backpack" | "polkadotJS";
10
+ type NoDuplicates<T extends readonly unknown[], Acc extends readonly unknown[] = []> = T extends [infer Head, ...infer Tail] ? Head extends Acc[number] ? never : [Head, ...NoDuplicates<Tail, [...Acc, Head]>] : T;
11
+ interface IWallet {
12
+ gotoOnboardPage(): Promise<void>;
13
+ }
14
+
15
+ declare abstract class Wallet implements IWallet {
16
+ protected page: Page;
17
+ protected extensionId: string;
10
18
  constructor(page: Page, extensionId: string);
19
+ abstract gotoOnboardPage(): Promise<void>;
20
+ }
21
+
22
+ declare class Backpack extends Wallet {
23
+ private defaultPassword;
24
+ gotoOnboardPage(): Promise<void>;
11
25
  onboard(network: BackPackNetwork, privateKey: string): Promise<void>;
12
26
  unlock(): Promise<void>;
13
27
  goToSettings(accountName?: string): Promise<void>;
@@ -17,13 +31,22 @@ declare class Backpack {
17
31
  deny(): Promise<void>;
18
32
  }
19
33
 
20
- type Config = {
21
- backpack?: boolean;
22
- };
23
- declare function withWallets(test: typeof test, config: Config): playwright_test.TestType<playwright_test.PlaywrightTestArgs & playwright_test.PlaywrightTestOptions & {
34
+ declare class PolkadotJS extends Wallet {
35
+ private defaultPassword;
36
+ gotoOnboardPage(): Promise<void>;
37
+ onboard(seed: string, password?: string, name?: string): Promise<void>;
38
+ selectAllAccounts(): Promise<void>;
39
+ selectAccount(accountId: string): Promise<void>;
40
+ enterPassword(password?: string): Promise<void>;
41
+ approve(): Promise<void>;
42
+ deny(): Promise<void>;
43
+ private _getLabeledInput;
44
+ }
45
+
46
+ declare function withWallets<T extends readonly WalletName[]>(test: typeof test, ...config: NoDuplicates<T>): playwright_test.TestType<playwright_test.PlaywrightTestArgs & playwright_test.PlaywrightTestOptions & {
24
47
  context: BrowserContext;
25
48
  backpack: Backpack;
26
- extensionId: string;
49
+ polkadotJS: PolkadotJS;
27
50
  }, playwright_test.PlaywrightWorkerArgs & playwright_test.PlaywrightWorkerOptions>;
28
51
 
29
52
  export { withWallets };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,27 @@
1
1
  import * as playwright_test from 'playwright/test';
2
2
  import { Page, test, BrowserContext } from '@playwright/test';
3
3
 
4
- type BackPackNetwork = "Eclipse" | "Ethereum";
4
+ /**
5
+ * Represents the supported network types for the BackPack application.
6
+ */
7
+ type BackPackNetwork = "Solana" | "Eclipse" | "Ethereum" | "Polygon" | "Base" | "Arbitrum" | "Optimism";
5
8
 
6
- declare class Backpack {
7
- private page;
8
- private extensionId;
9
- private defaultPassword;
9
+ type WalletName = "backpack" | "polkadotJS";
10
+ type NoDuplicates<T extends readonly unknown[], Acc extends readonly unknown[] = []> = T extends [infer Head, ...infer Tail] ? Head extends Acc[number] ? never : [Head, ...NoDuplicates<Tail, [...Acc, Head]>] : T;
11
+ interface IWallet {
12
+ gotoOnboardPage(): Promise<void>;
13
+ }
14
+
15
+ declare abstract class Wallet implements IWallet {
16
+ protected page: Page;
17
+ protected extensionId: string;
10
18
  constructor(page: Page, extensionId: string);
19
+ abstract gotoOnboardPage(): Promise<void>;
20
+ }
21
+
22
+ declare class Backpack extends Wallet {
23
+ private defaultPassword;
24
+ gotoOnboardPage(): Promise<void>;
11
25
  onboard(network: BackPackNetwork, privateKey: string): Promise<void>;
12
26
  unlock(): Promise<void>;
13
27
  goToSettings(accountName?: string): Promise<void>;
@@ -17,13 +31,22 @@ declare class Backpack {
17
31
  deny(): Promise<void>;
18
32
  }
19
33
 
20
- type Config = {
21
- backpack?: boolean;
22
- };
23
- declare function withWallets(test: typeof test, config: Config): playwright_test.TestType<playwright_test.PlaywrightTestArgs & playwright_test.PlaywrightTestOptions & {
34
+ declare class PolkadotJS extends Wallet {
35
+ private defaultPassword;
36
+ gotoOnboardPage(): Promise<void>;
37
+ onboard(seed: string, password?: string, name?: string): Promise<void>;
38
+ selectAllAccounts(): Promise<void>;
39
+ selectAccount(accountId: string): Promise<void>;
40
+ enterPassword(password?: string): Promise<void>;
41
+ approve(): Promise<void>;
42
+ deny(): Promise<void>;
43
+ private _getLabeledInput;
44
+ }
45
+
46
+ declare function withWallets<T extends readonly WalletName[]>(test: typeof test, ...config: NoDuplicates<T>): playwright_test.TestType<playwright_test.PlaywrightTestArgs & playwright_test.PlaywrightTestOptions & {
24
47
  context: BrowserContext;
25
48
  backpack: Backpack;
26
- extensionId: string;
49
+ polkadotJS: PolkadotJS;
27
50
  }, playwright_test.PlaywrightWorkerArgs & playwright_test.PlaywrightWorkerOptions>;
28
51
 
29
52
  export { withWallets };
package/dist/index.js CHANGED
@@ -37,17 +37,33 @@ module.exports = __toCommonJS(index_exports);
37
37
  // src/withWallets.ts
38
38
  var import_path = __toESM(require("path"));
39
39
  var import_fs = __toESM(require("fs"));
40
- var import_test2 = require("@playwright/test");
40
+
41
+ // tests/utils/sleep.ts
42
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
43
+
44
+ // src/withWallets.ts
41
45
  var import_test3 = require("@playwright/test");
42
46
 
43
47
  // src/backpack/backpack.ts
44
48
  var import_test = require("@playwright/test");
45
- var Backpack = class {
49
+
50
+ // src/wallet.ts
51
+ var Wallet = class {
46
52
  constructor(page, extensionId) {
47
53
  this.page = page;
48
54
  this.extensionId = extensionId;
49
55
  }
56
+ };
57
+
58
+ // src/backpack/backpack.ts
59
+ var Backpack = class extends Wallet {
50
60
  defaultPassword = "11111111";
61
+ async gotoOnboardPage() {
62
+ await this.page.goto(
63
+ `chrome-extension://${this.extensionId}/options.html?onboarding=true`
64
+ );
65
+ await (0, import_test.expect)(this.page.getByText("Welcome to Backpack")).toBeVisible();
66
+ }
51
67
  async onboard(network, privateKey) {
52
68
  await this.page.getByRole("button", { name: "Import wallet" }).click();
53
69
  await this.page.getByRole("button", { name: network }).click();
@@ -89,39 +105,91 @@ var Backpack = class {
89
105
  }
90
106
  };
91
107
 
108
+ // src/polkadotJS/polkadotJS.ts
109
+ var import_test2 = require("@playwright/test");
110
+ var PolkadotJS = class extends Wallet {
111
+ defaultPassword = "11111111";
112
+ async gotoOnboardPage() {
113
+ await this.page.goto(`chrome-extension://${this.extensionId}/index.html`);
114
+ await (0, import_test2.expect)(
115
+ this.page.getByText("Before we start, just a couple of notes")
116
+ ).toBeVisible();
117
+ }
118
+ async onboard(seed, password, name) {
119
+ await this.page.getByRole("button", { name: "Understood, let me continue" }).click();
120
+ await this.page.locator(".popupToggle").first().click();
121
+ await this.page.getByText("Import account from pre-existing seed").click();
122
+ await this.page.locator(".seedInput").getByRole("textbox").fill(seed);
123
+ await this.page.getByRole("button", { name: "Next" }).click();
124
+ await this._getLabeledInput("A descriptive name for your account").fill(
125
+ name ?? "Test"
126
+ );
127
+ await this._getLabeledInput("A new password for this account").fill(
128
+ password ?? this.defaultPassword
129
+ );
130
+ await this._getLabeledInput("Repeat password for verification").fill(
131
+ password ?? this.defaultPassword
132
+ );
133
+ await this.page.getByRole("button", { name: "Add the account with the supplied seed" }).click();
134
+ }
135
+ async selectAllAccounts() {
136
+ await this.page.getByText("Select all").click();
137
+ }
138
+ async selectAccount(accountId) {
139
+ await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").locator("span").check();
140
+ }
141
+ async enterPassword(password) {
142
+ await this._getLabeledInput("Password for this account").fill(
143
+ password ?? this.defaultPassword
144
+ );
145
+ }
146
+ async approve() {
147
+ const connect = this.page.getByRole("button", { name: "Connect" });
148
+ const signTransaction = this.page.getByRole("button", {
149
+ name: "Sign the transaction"
150
+ });
151
+ await connect.or(signTransaction).click();
152
+ }
153
+ async deny() {
154
+ const reject = this.page.getByRole("button", { name: "Reject" });
155
+ const cancel = this.page.getByRole("link", { name: "Cancel" });
156
+ await reject.or(cancel).click();
157
+ }
158
+ _getLabeledInput(label) {
159
+ return this.page.locator(
160
+ `//label[text()="${label}"]/following-sibling::input`
161
+ );
162
+ }
163
+ };
164
+
92
165
  // src/withWallets.ts
93
- function withWallets(test, config) {
94
- const backpackPath = import_path.default.join(process.cwd(), "wallets", "backpack");
95
- const metamaskPath = import_path.default.join(process.cwd(), "wallets", "metamask");
166
+ var w3walletsDir = ".w3wallets";
167
+ function withWallets(test, ...config) {
168
+ const withBackpack = config.includes("backpack");
169
+ const withPolkadotJS = config.includes("polkadotJS");
170
+ const backpackPath = import_path.default.join(process.cwd(), w3walletsDir, "backpack");
171
+ const polkadotJSPath = import_path.default.join(process.cwd(), w3walletsDir, "polkadotJS");
96
172
  return test.extend({
97
- backpack: async ({ context, extensionId }, use) => {
98
- const page = context.pages()[0];
99
- if (!page) throw Error("No pages in context");
100
- const backpack = new Backpack(page, extensionId);
101
- await page.goto(
102
- `chrome-extension://${extensionId}/options.html?onboarding=true`
103
- );
104
- await use(backpack);
105
- },
106
- // Browser context fixture
173
+ /**
174
+ * Sets up a persistent browser context with the requested extensions loaded.
175
+ */
107
176
  context: async ({}, use, testInfo) => {
108
177
  const userDataDir = import_path.default.join(
109
178
  process.cwd(),
110
179
  ".w3wallets",
180
+ ".context",
111
181
  testInfo.testId
112
182
  );
113
- if (import_fs.default.existsSync(userDataDir)) {
114
- import_fs.default.rmSync(userDataDir, { recursive: true });
115
- }
183
+ cleanUserDataDir(userDataDir);
116
184
  const extensionPaths = [];
117
- if (config.backpack) {
118
- if (!import_fs.default.existsSync(import_path.default.join(backpackPath, "manifest.json"))) {
119
- throw Error(
120
- "Cannot find Backpack. Please download it via `npx w3wallets`"
121
- );
122
- }
185
+ if (withBackpack) {
186
+ ensureWalletExtensionExists(backpackPath, "backpack");
123
187
  extensionPaths.push(backpackPath);
124
188
  }
189
+ if (withPolkadotJS) {
190
+ ensureWalletExtensionExists(polkadotJSPath, "polkadotJS");
191
+ extensionPaths.push(polkadotJSPath);
192
+ }
125
193
  const context = await import_test3.chromium.launchPersistentContext(userDataDir, {
126
194
  headless: false,
127
195
  args: [
@@ -129,20 +197,72 @@ function withWallets(test, config) {
129
197
  `--load-extension=${extensionPaths.join(",")}`
130
198
  ]
131
199
  });
200
+ await context.waitForEvent("serviceworker");
201
+ while (context.serviceWorkers().length < extensionPaths.length) {
202
+ await sleep(1e3);
203
+ }
132
204
  await use(context);
133
205
  await context.close();
134
206
  },
135
- extensionId: async ({ context }, use) => {
136
- let [background] = context.serviceWorkers();
137
- if (!background) {
138
- background = await context.waitForEvent("serviceworker");
207
+ backpack: async ({ context }, use) => {
208
+ if (!withBackpack) {
209
+ throw Error(
210
+ "The Backpack wallet hasn't been loaded. Add it to the withWallets function."
211
+ );
139
212
  }
140
- const extensionId = background.url().split("/")[2];
141
- if (!extensionId) throw Error("No extension id");
142
- await use(extensionId);
213
+ const backpack = await initializeExtension(
214
+ context,
215
+ Backpack,
216
+ "Backpack is not initialized"
217
+ );
218
+ await use(backpack);
219
+ },
220
+ polkadotJS: async ({ context }, use) => {
221
+ if (!withPolkadotJS) {
222
+ throw Error(
223
+ "The Polkadot{.js} wallet hasn't been loaded. Add it to the withWallets function."
224
+ );
225
+ }
226
+ const polkadotJS = await initializeExtension(
227
+ context,
228
+ PolkadotJS,
229
+ "Polkadot{.js} is not initialized"
230
+ );
231
+ await use(polkadotJS);
143
232
  }
144
233
  });
145
234
  }
235
+ function cleanUserDataDir(userDataDir) {
236
+ if (import_fs.default.existsSync(userDataDir)) {
237
+ import_fs.default.rmSync(userDataDir, { recursive: true });
238
+ }
239
+ }
240
+ function ensureWalletExtensionExists(walletPath, walletName) {
241
+ if (!import_fs.default.existsSync(import_path.default.join(walletPath, "manifest.json"))) {
242
+ throw new Error(
243
+ `Cannot find ${walletName}. Please download it via 'npx w3wallets ${walletName}'.`
244
+ );
245
+ }
246
+ }
247
+ async function initializeExtension(context, ExtensionClass, notInitializedErrorMessage) {
248
+ const serviceWorkers = context.serviceWorkers();
249
+ let page = await context.newPage();
250
+ for (const worker of serviceWorkers) {
251
+ const extensionId = worker.url().split("/")[2];
252
+ if (!extensionId) {
253
+ continue;
254
+ }
255
+ const extension = new ExtensionClass(page, extensionId);
256
+ try {
257
+ await extension.gotoOnboardPage();
258
+ return extension;
259
+ } catch {
260
+ await page.close();
261
+ page = await context.newPage();
262
+ }
263
+ }
264
+ throw new Error(notInitializedErrorMessage);
265
+ }
146
266
  // Annotate the CommonJS export names for ESM import in node:
147
267
  0 && (module.exports = {
148
268
  withWallets
package/dist/index.mjs CHANGED
@@ -1,17 +1,35 @@
1
1
  // src/withWallets.ts
2
2
  import path from "path";
3
3
  import fs from "fs";
4
- import "@playwright/test";
5
- import { chromium } from "@playwright/test";
4
+
5
+ // tests/utils/sleep.ts
6
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
7
+
8
+ // src/withWallets.ts
9
+ import {
10
+ chromium
11
+ } from "@playwright/test";
6
12
 
7
13
  // src/backpack/backpack.ts
8
14
  import { expect } from "@playwright/test";
9
- var Backpack = class {
15
+
16
+ // src/wallet.ts
17
+ var Wallet = class {
10
18
  constructor(page, extensionId) {
11
19
  this.page = page;
12
20
  this.extensionId = extensionId;
13
21
  }
22
+ };
23
+
24
+ // src/backpack/backpack.ts
25
+ var Backpack = class extends Wallet {
14
26
  defaultPassword = "11111111";
27
+ async gotoOnboardPage() {
28
+ await this.page.goto(
29
+ `chrome-extension://${this.extensionId}/options.html?onboarding=true`
30
+ );
31
+ await expect(this.page.getByText("Welcome to Backpack")).toBeVisible();
32
+ }
15
33
  async onboard(network, privateKey) {
16
34
  await this.page.getByRole("button", { name: "Import wallet" }).click();
17
35
  await this.page.getByRole("button", { name: network }).click();
@@ -53,39 +71,91 @@ var Backpack = class {
53
71
  }
54
72
  };
55
73
 
74
+ // src/polkadotJS/polkadotJS.ts
75
+ import { expect as expect2 } from "@playwright/test";
76
+ var PolkadotJS = class extends Wallet {
77
+ defaultPassword = "11111111";
78
+ async gotoOnboardPage() {
79
+ await this.page.goto(`chrome-extension://${this.extensionId}/index.html`);
80
+ await expect2(
81
+ this.page.getByText("Before we start, just a couple of notes")
82
+ ).toBeVisible();
83
+ }
84
+ async onboard(seed, password, name) {
85
+ await this.page.getByRole("button", { name: "Understood, let me continue" }).click();
86
+ await this.page.locator(".popupToggle").first().click();
87
+ await this.page.getByText("Import account from pre-existing seed").click();
88
+ await this.page.locator(".seedInput").getByRole("textbox").fill(seed);
89
+ await this.page.getByRole("button", { name: "Next" }).click();
90
+ await this._getLabeledInput("A descriptive name for your account").fill(
91
+ name ?? "Test"
92
+ );
93
+ await this._getLabeledInput("A new password for this account").fill(
94
+ password ?? this.defaultPassword
95
+ );
96
+ await this._getLabeledInput("Repeat password for verification").fill(
97
+ password ?? this.defaultPassword
98
+ );
99
+ await this.page.getByRole("button", { name: "Add the account with the supplied seed" }).click();
100
+ }
101
+ async selectAllAccounts() {
102
+ await this.page.getByText("Select all").click();
103
+ }
104
+ async selectAccount(accountId) {
105
+ await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").locator("span").check();
106
+ }
107
+ async enterPassword(password) {
108
+ await this._getLabeledInput("Password for this account").fill(
109
+ password ?? this.defaultPassword
110
+ );
111
+ }
112
+ async approve() {
113
+ const connect = this.page.getByRole("button", { name: "Connect" });
114
+ const signTransaction = this.page.getByRole("button", {
115
+ name: "Sign the transaction"
116
+ });
117
+ await connect.or(signTransaction).click();
118
+ }
119
+ async deny() {
120
+ const reject = this.page.getByRole("button", { name: "Reject" });
121
+ const cancel = this.page.getByRole("link", { name: "Cancel" });
122
+ await reject.or(cancel).click();
123
+ }
124
+ _getLabeledInput(label) {
125
+ return this.page.locator(
126
+ `//label[text()="${label}"]/following-sibling::input`
127
+ );
128
+ }
129
+ };
130
+
56
131
  // src/withWallets.ts
57
- function withWallets(test, config) {
58
- const backpackPath = path.join(process.cwd(), "wallets", "backpack");
59
- const metamaskPath = path.join(process.cwd(), "wallets", "metamask");
132
+ var w3walletsDir = ".w3wallets";
133
+ function withWallets(test, ...config) {
134
+ const withBackpack = config.includes("backpack");
135
+ const withPolkadotJS = config.includes("polkadotJS");
136
+ const backpackPath = path.join(process.cwd(), w3walletsDir, "backpack");
137
+ const polkadotJSPath = path.join(process.cwd(), w3walletsDir, "polkadotJS");
60
138
  return test.extend({
61
- backpack: async ({ context, extensionId }, use) => {
62
- const page = context.pages()[0];
63
- if (!page) throw Error("No pages in context");
64
- const backpack = new Backpack(page, extensionId);
65
- await page.goto(
66
- `chrome-extension://${extensionId}/options.html?onboarding=true`
67
- );
68
- await use(backpack);
69
- },
70
- // Browser context fixture
139
+ /**
140
+ * Sets up a persistent browser context with the requested extensions loaded.
141
+ */
71
142
  context: async ({}, use, testInfo) => {
72
143
  const userDataDir = path.join(
73
144
  process.cwd(),
74
145
  ".w3wallets",
146
+ ".context",
75
147
  testInfo.testId
76
148
  );
77
- if (fs.existsSync(userDataDir)) {
78
- fs.rmSync(userDataDir, { recursive: true });
79
- }
149
+ cleanUserDataDir(userDataDir);
80
150
  const extensionPaths = [];
81
- if (config.backpack) {
82
- if (!fs.existsSync(path.join(backpackPath, "manifest.json"))) {
83
- throw Error(
84
- "Cannot find Backpack. Please download it via `npx w3wallets`"
85
- );
86
- }
151
+ if (withBackpack) {
152
+ ensureWalletExtensionExists(backpackPath, "backpack");
87
153
  extensionPaths.push(backpackPath);
88
154
  }
155
+ if (withPolkadotJS) {
156
+ ensureWalletExtensionExists(polkadotJSPath, "polkadotJS");
157
+ extensionPaths.push(polkadotJSPath);
158
+ }
89
159
  const context = await chromium.launchPersistentContext(userDataDir, {
90
160
  headless: false,
91
161
  args: [
@@ -93,20 +163,72 @@ function withWallets(test, config) {
93
163
  `--load-extension=${extensionPaths.join(",")}`
94
164
  ]
95
165
  });
166
+ await context.waitForEvent("serviceworker");
167
+ while (context.serviceWorkers().length < extensionPaths.length) {
168
+ await sleep(1e3);
169
+ }
96
170
  await use(context);
97
171
  await context.close();
98
172
  },
99
- extensionId: async ({ context }, use) => {
100
- let [background] = context.serviceWorkers();
101
- if (!background) {
102
- background = await context.waitForEvent("serviceworker");
173
+ backpack: async ({ context }, use) => {
174
+ if (!withBackpack) {
175
+ throw Error(
176
+ "The Backpack wallet hasn't been loaded. Add it to the withWallets function."
177
+ );
178
+ }
179
+ const backpack = await initializeExtension(
180
+ context,
181
+ Backpack,
182
+ "Backpack is not initialized"
183
+ );
184
+ await use(backpack);
185
+ },
186
+ polkadotJS: async ({ context }, use) => {
187
+ if (!withPolkadotJS) {
188
+ throw Error(
189
+ "The Polkadot{.js} wallet hasn't been loaded. Add it to the withWallets function."
190
+ );
103
191
  }
104
- const extensionId = background.url().split("/")[2];
105
- if (!extensionId) throw Error("No extension id");
106
- await use(extensionId);
192
+ const polkadotJS = await initializeExtension(
193
+ context,
194
+ PolkadotJS,
195
+ "Polkadot{.js} is not initialized"
196
+ );
197
+ await use(polkadotJS);
107
198
  }
108
199
  });
109
200
  }
201
+ function cleanUserDataDir(userDataDir) {
202
+ if (fs.existsSync(userDataDir)) {
203
+ fs.rmSync(userDataDir, { recursive: true });
204
+ }
205
+ }
206
+ function ensureWalletExtensionExists(walletPath, walletName) {
207
+ if (!fs.existsSync(path.join(walletPath, "manifest.json"))) {
208
+ throw new Error(
209
+ `Cannot find ${walletName}. Please download it via 'npx w3wallets ${walletName}'.`
210
+ );
211
+ }
212
+ }
213
+ async function initializeExtension(context, ExtensionClass, notInitializedErrorMessage) {
214
+ const serviceWorkers = context.serviceWorkers();
215
+ let page = await context.newPage();
216
+ for (const worker of serviceWorkers) {
217
+ const extensionId = worker.url().split("/")[2];
218
+ if (!extensionId) {
219
+ continue;
220
+ }
221
+ const extension = new ExtensionClass(page, extensionId);
222
+ try {
223
+ await extension.gotoOnboardPage();
224
+ return extension;
225
+ } catch {
226
+ await page.close();
227
+ page = await context.newPage();
228
+ }
229
+ }
230
+ throw new Error(notInitializedErrorMessage);
231
+ }
110
232
  export {
111
233
  withWallets
112
234
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "w3wallets",
3
3
  "description": "browser wallets for playwright",
4
- "version": "0.1.3",
4
+ "version": "0.2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "homepage": "https://github.com/Maksandre/w3wallets",
@@ -13,6 +13,15 @@
13
13
  "type": "git",
14
14
  "url": "git+https://github.com/Maksandre/w3wallets.git"
15
15
  },
16
+ "keywords": [
17
+ "e2e",
18
+ "playwright",
19
+ "backpack",
20
+ "testing",
21
+ "ethereum",
22
+ "polkadot",
23
+ "eclipse"
24
+ ],
16
25
  "license": "MIT",
17
26
  "publishConfig": {
18
27
  "access": "public"
@@ -22,6 +31,8 @@
22
31
  ],
23
32
  "bin": "./src/scripts/download.js",
24
33
  "scripts": {
34
+ "start:ui": "yarn workspace @w3wallets/test-app dev",
35
+ "test": "npx playwright test",
25
36
  "build": "tsup",
26
37
  "clean": "rm -rf dist",
27
38
  "check-format": "prettier --check .",
@@ -39,14 +50,15 @@
39
50
  "devDependencies": {
40
51
  "@arethetypeswrong/cli": "^0.17.2",
41
52
  "@changesets/cli": "^2.27.11",
42
- "@playwright/test": "^1.49.1",
53
+ "@playwright/test": "^1.50.1",
43
54
  "@types/node": "^22.10.5",
55
+ "dotenv": "^16.4.7",
44
56
  "prettier": "^3.4.2",
45
57
  "standard-version": "^9.5.0",
46
58
  "tsup": "^8.3.5",
47
59
  "typescript": "^5.7.2"
48
60
  },
49
61
  "peerDependencies": {
50
- "@playwright/test": "^1.49.1"
62
+ "@playwright/test": "^1.50.1"
51
63
  }
52
64
  }
@@ -22,6 +22,7 @@ const zlib = require("zlib");
22
22
  const ALIASES = {
23
23
  backpack: "aflkmfhebedbjioipglgcbcmnbpgliof",
24
24
  metamask: "nkbihfbeogaeaoehlefnkodbefgpgknn",
25
+ polkadotJS: "mopnmbcafieddcagagdcbnhejhlodfdd",
25
26
  };
26
27
 
27
28
  // ---------------------------------------------------------------------
@@ -58,7 +59,7 @@ for (const alias of inputAliases) {
58
59
  console.log(`Got CRX data for "${alias}"! ${crxBuffer.length} bytes`);
59
60
 
60
61
  // 2) Save raw CRX to disk
61
- const outDir = path.join("wallets", alias);
62
+ const outDir = path.join(".w3wallets", alias);
62
63
  fs.mkdirSync(outDir, { recursive: true });
63
64
 
64
65
  const debugPath = path.join(outDir, `debug-${alias}.crx`);