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 +42 -5
- package/dist/index.d.mts +33 -10
- package/dist/index.d.ts +33 -10
- package/dist/index.js +151 -31
- package/dist/index.mjs +154 -32
- package/package.json +15 -3
- package/src/scripts/download.js +2 -1
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
[](https://www.npmjs.com/package/w3wallets)
|
|
5
|
+

|
|
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
|
-
|
|
17
|
+
The `Backpack` and the `Polkadot{.js}` wallets are currently supported.
|
|
17
18
|
|
|
18
|
-
#### 1. Download
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
114
|
-
import_fs.default.rmSync(userDataDir, { recursive: true });
|
|
115
|
-
}
|
|
183
|
+
cleanUserDataDir(userDataDir);
|
|
116
184
|
const extensionPaths = [];
|
|
117
|
-
if (
|
|
118
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
78
|
-
fs.rmSync(userDataDir, { recursive: true });
|
|
79
|
-
}
|
|
149
|
+
cleanUserDataDir(userDataDir);
|
|
80
150
|
const extensionPaths = [];
|
|
81
|
-
if (
|
|
82
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
62
|
+
"@playwright/test": "^1.50.1"
|
|
51
63
|
}
|
|
52
64
|
}
|
package/src/scripts/download.js
CHANGED
|
@@ -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("
|
|
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`);
|