dappress 0.5.1 → 0.6.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/CHANGELOG.md +9 -1
- package/dist/actions.js +19 -8
- package/dist/browser.js +2 -2
- package/dist/metamask-pages.d.ts +10 -0
- package/dist/metamask-pages.js +30 -3
- package/dist/metamask.d.ts +7 -0
- package/dist/metamask.js +24 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ What changes for the users of Dappress, version by version. The format follows [
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.0] - 2026-10-05
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- The peer dependency on Cypress asks for 15.10 or later, the first version with `Cypress.expose()`, which the commands read their options with. Earlier versions were accepted at install, then failed as soon as the support file loaded.
|
|
12
|
+
- A command sent right after another was answered in the popup no longer fails on "Timed out waiting for confirm-footer-button". The popup closes a moment after its button goes, and the next command could find it, still listed on the request just answered, then lose it. A decision now waits for the popup to close, and a confirmation that closes before it is acted on is looked for again.
|
|
13
|
+
|
|
7
14
|
## [0.5.1] - 2026-10-05
|
|
8
15
|
|
|
9
16
|
### Changed
|
|
@@ -85,7 +92,8 @@ What changes for the users of Dappress, version by version. The format follows [
|
|
|
85
92
|
- The wallet setup file, `cypress/wallet.setup.ts`, and the opt-in profile cache.
|
|
86
93
|
- TypeScript declarations.
|
|
87
94
|
|
|
88
|
-
[Unreleased]: https://github.com/blassaut/dappress/compare/v0.
|
|
95
|
+
[Unreleased]: https://github.com/blassaut/dappress/compare/v0.6.0...HEAD
|
|
96
|
+
[0.6.0]: https://github.com/blassaut/dappress/compare/v0.5.1...v0.6.0
|
|
89
97
|
[0.5.1]: https://github.com/blassaut/dappress/compare/v0.5.0...v0.5.1
|
|
90
98
|
[0.5.0]: https://github.com/blassaut/dappress/compare/v0.4.0...v0.5.0
|
|
91
99
|
[0.4.0]: https://github.com/blassaut/dappress/compare/v0.3.3...v0.4.0
|
package/dist/actions.js
CHANGED
|
@@ -86,16 +86,27 @@ function createTasks(options) {
|
|
|
86
86
|
await metamask.leaveUnlockForm(page, options);
|
|
87
87
|
return state;
|
|
88
88
|
}
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Find the confirmation and act on it. One that closes before it is acted
|
|
91
|
+
* on was the popup of the request before, found as it closed: the request's
|
|
92
|
+
* own is looked for once more.
|
|
93
|
+
*/
|
|
94
|
+
async function onConfirmation(browser, act) {
|
|
95
|
+
for (let attempt = 0;; attempt++) {
|
|
96
|
+
const page = await (0, metamask_pages_1.getConfirmationPage)(browser, await metamaskId(browser), options.timeout);
|
|
97
|
+
try {
|
|
98
|
+
return await act(page);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (attempt > 0 || !(error instanceof metamask.ConfirmationClosed))
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
93
105
|
}
|
|
106
|
+
/** Approve the prompt a network change raised: "Add network" or the permission to switch. */
|
|
107
|
+
const approveNetworkChange = (browser) => onConfirmation(browser, (page) => metamask.approveNetworkChange(page, options.timeout));
|
|
94
108
|
/** The task for a decision: find the confirmation, press its button. */
|
|
95
|
-
const decide = (decision) =>
|
|
96
|
-
const page = await (0, metamask_pages_1.getConfirmationPage)(browser, await metamaskId(browser), options.timeout);
|
|
97
|
-
await metamask.decide(decision, page, options.timeout, argument);
|
|
98
|
-
};
|
|
109
|
+
const decide = (decision) => (browser, argument) => onConfirmation(browser, (page) => metamask.decide(decision, page, options.timeout, argument));
|
|
99
110
|
const actions = {
|
|
100
111
|
setupWallet,
|
|
101
112
|
approveNetworkChange,
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Connects Puppeteer to the browser Cypress launched, through the debugging
|
|
3
|
-
// URL Cypress hands out in after:browser:launch (Cypress
|
|
3
|
+
// URL Cypress hands out in after:browser:launch (Cypress 15.10+).
|
|
4
4
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
5
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
6
|
};
|
|
@@ -20,7 +20,7 @@ function captureDebuggerUrl(on) {
|
|
|
20
20
|
*/
|
|
21
21
|
async function withBrowser(action) {
|
|
22
22
|
if (!debuggerUrl) {
|
|
23
|
-
throw new Error('[dappress] No browser to connect to. Is configureDappress() called in setupNodeEvents, with Cypress
|
|
23
|
+
throw new Error('[dappress] No browser to connect to. Is configureDappress() called in setupNodeEvents, with Cypress 15.10 or later?');
|
|
24
24
|
}
|
|
25
25
|
const browser = await puppeteer_core_1.default.connect({ browserWSEndpoint: debuggerUrl, defaultViewport: null });
|
|
26
26
|
try {
|
package/dist/metamask-pages.d.ts
CHANGED
|
@@ -12,6 +12,16 @@ export declare function getRequestPages(browser: Browser, extensionId: string):
|
|
|
12
12
|
* The page a dapp request is shown on. Waits for the popup to open, or takes
|
|
13
13
|
* the side panel. A popup still on its home route is one closing after the
|
|
14
14
|
* previous request, or one that hasn't routed to the request yet: skipped.
|
|
15
|
+
* The route is read from the page, which follows a change of hash at once,
|
|
16
|
+
* where the target can still name the request just answered.
|
|
15
17
|
*/
|
|
16
18
|
export declare function getConfirmationPage(browser: Browser, extensionId: string, timeout: number): Promise<Page>;
|
|
19
|
+
/**
|
|
20
|
+
* Once the request shown on a popup is answered, wait for the popup to close,
|
|
21
|
+
* at most `timeout` ms. MetaMask closes it a moment after its button goes, and
|
|
22
|
+
* left to close on its own, the next command could find it, still listed on
|
|
23
|
+
* `request`, and act on a page that vanishes under it. A popup that goes on
|
|
24
|
+
* to another request instead is left to it; so is the side panel, which stays.
|
|
25
|
+
*/
|
|
26
|
+
export declare function waitForDismissal(page: Page, request: string, timeout: number): Promise<void>;
|
|
17
27
|
export declare function withUsableWindow(page: Page): Promise<Page>;
|
package/dist/metamask-pages.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.findExtensionId = findExtensionId;
|
|
|
7
7
|
exports.getHomePage = getHomePage;
|
|
8
8
|
exports.getRequestPages = getRequestPages;
|
|
9
9
|
exports.getConfirmationPage = getConfirmationPage;
|
|
10
|
+
exports.waitForDismissal = waitForDismissal;
|
|
10
11
|
exports.withUsableWindow = withUsableWindow;
|
|
11
12
|
const page_helpers_1 = require("./page-helpers");
|
|
12
13
|
const HOME_PATH = '/home.html';
|
|
@@ -97,14 +98,20 @@ async function getRequestPages(browser, extensionId) {
|
|
|
97
98
|
* The page a dapp request is shown on. Waits for the popup to open, or takes
|
|
98
99
|
* the side panel. A popup still on its home route is one closing after the
|
|
99
100
|
* previous request, or one that hasn't routed to the request yet: skipped.
|
|
101
|
+
* The route is read from the page, which follows a change of hash at once,
|
|
102
|
+
* where the target can still name the request just answered.
|
|
100
103
|
*/
|
|
101
104
|
async function getConfirmationPage(browser, extensionId, timeout) {
|
|
102
105
|
const deadline = Date.now() + timeout;
|
|
103
106
|
while (Date.now() < deadline) {
|
|
104
107
|
for (const pathname of CONFIRMATION_PATHS) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
for (const target of pagesOf(browser, extensionId, pathname)) {
|
|
109
|
+
// A target closing while its page is taken has none: not a confirmation either
|
|
110
|
+
const page = await target.page().catch(() => null);
|
|
111
|
+
if (!page || page.isClosed() || isHomeRoute(page.url()))
|
|
112
|
+
continue;
|
|
113
|
+
return pathname === '/notification.html' ? withUsableWindow(page) : page;
|
|
114
|
+
}
|
|
108
115
|
}
|
|
109
116
|
await (0, page_helpers_1.sleep)(250);
|
|
110
117
|
}
|
|
@@ -115,6 +122,26 @@ async function getConfirmationPage(browser, extensionId, timeout) {
|
|
|
115
122
|
.join(', ');
|
|
116
123
|
throw new Error(`[dappress] MetaMask showed no confirmation within ${timeout}ms. Did the dapp send a request? MetaMask pages seen: ${seen || 'none'}`);
|
|
117
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Once the request shown on a popup is answered, wait for the popup to close,
|
|
127
|
+
* at most `timeout` ms. MetaMask closes it a moment after its button goes, and
|
|
128
|
+
* left to close on its own, the next command could find it, still listed on
|
|
129
|
+
* `request`, and act on a page that vanishes under it. A popup that goes on
|
|
130
|
+
* to another request instead is left to it; so is the side panel, which stays.
|
|
131
|
+
*/
|
|
132
|
+
async function waitForDismissal(page, request, timeout) {
|
|
133
|
+
if (!request.includes('/notification.html'))
|
|
134
|
+
return;
|
|
135
|
+
const deadline = Date.now() + timeout;
|
|
136
|
+
while (Date.now() < deadline) {
|
|
137
|
+
if (page.isClosed())
|
|
138
|
+
return;
|
|
139
|
+
const url = page.url();
|
|
140
|
+
if (url !== request && !isHomeRoute(url))
|
|
141
|
+
return;
|
|
142
|
+
await (0, page_helpers_1.sleep)(100);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
118
145
|
// MetaMask asks Chrome for a 400x620 popup. Under Xvfb on a GitHub runner the
|
|
119
146
|
// window it gets is 1x1: nothing is laid out, the pane of a confirmation that
|
|
120
147
|
// must be read to the end has no height, so it can never be scrolled there and
|
package/dist/metamask.d.ts
CHANGED
|
@@ -48,6 +48,13 @@ export declare function disconnectSite(page: Page, origin: string): Promise<void
|
|
|
48
48
|
type CommandOptions = ConnectOptions & TransactionOptions;
|
|
49
49
|
/** Press the button of `decision` on the confirmation shown on `page`, once it is set as `options` ask. */
|
|
50
50
|
export declare function decide(decision: Decision, page: Page, timeout: number, options?: CommandOptions): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when the confirmation page closed before its button was pressed: it
|
|
53
|
+
* was the popup of the request before, found as it closed. The request's own
|
|
54
|
+
* popup is there to be looked for.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ConfirmationClosed extends Error {
|
|
57
|
+
}
|
|
51
58
|
/** wallet_addEthereumChain on a network MetaMask knows behaves like a switch: either prompt may show. */
|
|
52
59
|
export declare function approveNetworkChange(page: Page, timeout: number): Promise<void>;
|
|
53
60
|
export {};
|
package/dist/metamask.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// first, then a fallback on the button's English text. When a MetaMask release
|
|
8
8
|
// moves something, this is the file to fix.
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.decisions = void 0;
|
|
10
|
+
exports.ConfirmationClosed = exports.decisions = void 0;
|
|
11
11
|
exports.walletState = walletState;
|
|
12
12
|
exports.onboard = onboard;
|
|
13
13
|
exports.unlock = unlock;
|
|
@@ -20,6 +20,7 @@ exports.disconnectSite = disconnectSite;
|
|
|
20
20
|
exports.decide = decide;
|
|
21
21
|
exports.approveNetworkChange = approveNetworkChange;
|
|
22
22
|
const page_helpers_1 = require("./page-helpers");
|
|
23
|
+
const metamask_pages_1 = require("./metamask-pages");
|
|
23
24
|
const testId = (id) => `[data-testid="${id}"]`;
|
|
24
25
|
// XPath rather than Puppeteer's ::-p-text(): the latter needs MutationObserver,
|
|
25
26
|
// which MetaMask's sandbox (LavaMoat) blocks in its pages.
|
|
@@ -553,11 +554,30 @@ async function decide(decision, page, timeout, options) {
|
|
|
553
554
|
const adjust = adjustments[decision];
|
|
554
555
|
if (!adjust)
|
|
555
556
|
throw new Error(`[dappress] ${decision} takes no options`);
|
|
556
|
-
await (
|
|
557
|
+
await waitForButton(page, exports.decisions[decision], timeout);
|
|
557
558
|
await adjust(page, options, timeout);
|
|
558
559
|
}
|
|
559
560
|
return pressAndWaitForDismissal(page, exports.decisions[decision], timeout);
|
|
560
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* Thrown when the confirmation page closed before its button was pressed: it
|
|
564
|
+
* was the popup of the request before, found as it closed. The request's own
|
|
565
|
+
* popup is there to be looked for.
|
|
566
|
+
*/
|
|
567
|
+
class ConfirmationClosed extends Error {
|
|
568
|
+
}
|
|
569
|
+
exports.ConfirmationClosed = ConfirmationClosed;
|
|
570
|
+
// The button of a confirmation, or ConfirmationClosed if the page went meanwhile
|
|
571
|
+
async function waitForButton(page, button, timeout) {
|
|
572
|
+
try {
|
|
573
|
+
await (0, page_helpers_1.waitFor)(page, button, { timeout });
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
if (page.isClosed())
|
|
577
|
+
throw new ConfirmationClosed(`[dappress] The confirmation closed before "${(0, page_helpers_1.describe)(button)}" was pressed`);
|
|
578
|
+
throw error;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
561
581
|
/** wallet_addEthereumChain on a network MetaMask knows behaves like a switch: either prompt may show. */
|
|
562
582
|
function approveNetworkChange(page, timeout) {
|
|
563
583
|
return pressAndWaitForDismissal(page, [...selectors.confirmation.confirm, ...selectors.pageContainer.confirm], timeout);
|
|
@@ -741,7 +761,7 @@ async function textOf(page, selector) {
|
|
|
741
761
|
* what MetaMask logged.
|
|
742
762
|
*/
|
|
743
763
|
async function pressAndWaitForDismissal(page, button, timeout) {
|
|
744
|
-
await (
|
|
764
|
+
await waitForButton(page, button, timeout);
|
|
745
765
|
await dismissModal(page);
|
|
746
766
|
const logged = recordErrors(page);
|
|
747
767
|
const request = page.url();
|
|
@@ -751,7 +771,7 @@ async function pressAndWaitForDismissal(page, button, timeout) {
|
|
|
751
771
|
if (await (0, page_helpers_1.isVisible)(page, selectors.alert.acknowledge, 500))
|
|
752
772
|
await (0, page_helpers_1.click)(page, selectors.alert.acknowledge);
|
|
753
773
|
if (await (0, page_helpers_1.isGone)(page, button, 3000))
|
|
754
|
-
return;
|
|
774
|
+
return (0, metamask_pages_1.waitForDismissal)(page, request, 3000);
|
|
755
775
|
await dismissModal(page);
|
|
756
776
|
await (0, page_helpers_1.dispatchClick)(page, button, { timeout: 3000 }).catch(() => { });
|
|
757
777
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dappress",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "MetaMask automation for Cypress: load the real extension, import a test wallet, answer dapp requests with cy.* commands. Tracks the latest MetaMask release.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Benjamin Lassaut",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"format:check": "prettier --check ."
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"cypress": ">=
|
|
64
|
+
"cypress": ">=15.10.0"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@scure/bip39": "^1.6.0",
|