dappress 0.6.0 → 0.6.1
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 +8 -1
- package/dist/metamask-pages.js +1 -1
- package/dist/metamask.js +28 -16
- package/dist/page-helpers.d.ts +8 -0
- package/dist/page-helpers.js +17 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ What changes for the users of Dappress, version by version. The format follows [
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.1] - 2026-10-05
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `cy.confirmTransaction({ gas })` no longer confirms, now and then, with the fee MetaMask estimated in place of the one asked. MetaMask's confirmation carries the new fee a moment after the fee editor closes, and confirming meanwhile sent it the old one, which it kept. The command now waits for the "Network fee" row to change, as it did for the spending cap.
|
|
12
|
+
|
|
7
13
|
## [0.6.0] - 2026-10-05
|
|
8
14
|
|
|
9
15
|
### Fixed
|
|
@@ -92,7 +98,8 @@ What changes for the users of Dappress, version by version. The format follows [
|
|
|
92
98
|
- The wallet setup file, `cypress/wallet.setup.ts`, and the opt-in profile cache.
|
|
93
99
|
- TypeScript declarations.
|
|
94
100
|
|
|
95
|
-
[Unreleased]: https://github.com/blassaut/dappress/compare/v0.6.
|
|
101
|
+
[Unreleased]: https://github.com/blassaut/dappress/compare/v0.6.1...HEAD
|
|
102
|
+
[0.6.1]: https://github.com/blassaut/dappress/compare/v0.6.0...v0.6.1
|
|
96
103
|
[0.6.0]: https://github.com/blassaut/dappress/compare/v0.5.1...v0.6.0
|
|
97
104
|
[0.5.1]: https://github.com/blassaut/dappress/compare/v0.5.0...v0.5.1
|
|
98
105
|
[0.5.0]: https://github.com/blassaut/dappress/compare/v0.4.0...v0.5.0
|
package/dist/metamask-pages.js
CHANGED
|
@@ -153,7 +153,7 @@ async function withUsableWindow(page) {
|
|
|
153
153
|
const { windowId, bounds } = await session.send('Browser.getWindowForTarget');
|
|
154
154
|
if (bounds.windowState === 'normal' && (bounds.width ?? 0) >= 200 && (bounds.height ?? 0) >= 200)
|
|
155
155
|
return page;
|
|
156
|
-
console.log(`[dappress] MetaMask's popup is ${bounds.width}x${bounds.height} (${bounds.windowState}); resizing it to ${POPUP.width}x${POPUP.height}`);
|
|
156
|
+
console.log(`[dappress] MetaMask's popup is ${bounds.width}x${bounds.height} (${bounds.windowState}), as under Xvfb without a window manager; resizing it to ${POPUP.width}x${POPUP.height}`);
|
|
157
157
|
if (bounds.windowState !== 'normal')
|
|
158
158
|
await session.send('Browser.setWindowBounds', { windowId, bounds: { windowState: 'normal' } });
|
|
159
159
|
await session.send('Browser.setWindowBounds', { windowId, bounds: { ...POPUP } });
|
package/dist/metamask.js
CHANGED
|
@@ -137,6 +137,8 @@ const selectors = {
|
|
|
137
137
|
gasForm: testId('gas-fee-advanced-eip1559-modal'),
|
|
138
138
|
gasField: (id) => [`#${id}`, `${testId(id)} input`],
|
|
139
139
|
gasSave: [testId('gas-fee-modal-save-button'), buttonText('Save')],
|
|
140
|
+
// The "Network fee" row: its amount and the name of its estimate
|
|
141
|
+
feeSection: testId('gas-fee-section'),
|
|
140
142
|
},
|
|
141
143
|
// The older footer, still used by the permission update a network switch
|
|
142
144
|
// asks for, by "Add suggested tokens", and by template confirmations
|
|
@@ -637,7 +639,7 @@ async function setSpendingCap(page, cap) {
|
|
|
637
639
|
const s = selectors.transaction;
|
|
638
640
|
if (!(await (0, page_helpers_1.isVisible)(page, s.editSpendingCap, 10000)))
|
|
639
641
|
throw await (0, page_helpers_1.failure)(page, 'MetaMask shows no spending cap to edit: spendingCap is for an ERC-20 approval');
|
|
640
|
-
const shown = await textOf(page, s.spendingCap);
|
|
642
|
+
const shown = await (0, page_helpers_1.textOf)(page, s.spendingCap);
|
|
641
643
|
const asked = await openSpendingCap(page);
|
|
642
644
|
await type(page, s.spendingCapInput, cap, 'spending cap');
|
|
643
645
|
if (!(await pressToClose(page, s.spendingCapSave, s.spendingCapInput)))
|
|
@@ -645,9 +647,7 @@ async function setSpendingCap(page, cap) {
|
|
|
645
647
|
// The modal closes before the confirmation has the new cap, and confirming
|
|
646
648
|
// meanwhile approves the old one. Two caps may read the same once MetaMask
|
|
647
649
|
// has rounded them, so a row that stays as it was is not a failure.
|
|
648
|
-
|
|
649
|
-
while (Date.now() < deadline && (await textOf(page, s.spendingCap)) === shown)
|
|
650
|
-
await (0, page_helpers_1.sleep)(250);
|
|
650
|
+
await (0, page_helpers_1.waitForTextChange)(page, s.spendingCap, shown, Number(asked) === Number(cap) ? 0 : 5000);
|
|
651
651
|
}
|
|
652
652
|
/** Open the spending cap's modal. Yields the cap the dapp asked for, which its field starts with. */
|
|
653
653
|
async function openSpendingCap(page) {
|
|
@@ -668,11 +668,14 @@ async function setGas(page, gas) {
|
|
|
668
668
|
const s = selectors.transaction;
|
|
669
669
|
if (!(await (0, page_helpers_1.isVisible)(page, s.editGas, 10000)))
|
|
670
670
|
throw await (0, page_helpers_1.failure)(page, 'MetaMask shows no network fee to edit on this confirmation');
|
|
671
|
+
const shown = await (0, page_helpers_1.textOf)(page, s.feeSection);
|
|
671
672
|
await openFeeEditor(page);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
673
|
+
const changed = typeof gas === 'string' ? await pickGasEstimate(page, gas) : await fillGasForm(page, gas);
|
|
674
|
+
// The editor closes before the confirmation carries the new fee, and
|
|
675
|
+
// confirming meanwhile sends MetaMask the old one, which it keeps: the row
|
|
676
|
+
// is waited for to change. A fee set as it already was changes nothing.
|
|
677
|
+
if (changed)
|
|
678
|
+
await (0, page_helpers_1.waitForTextChange)(page, s.feeSection, shown, 5000);
|
|
676
679
|
}
|
|
677
680
|
async function openFeeEditor(page) {
|
|
678
681
|
const s = selectors.transaction;
|
|
@@ -687,32 +690,45 @@ async function openFeeEditor(page) {
|
|
|
687
690
|
// Picking an estimate closes the editor. MetaMask lists the estimates it has
|
|
688
691
|
// for the network: Low, Market and Aggressive where it has fee estimates,
|
|
689
692
|
// "Network suggested" where it only has a gas price, as on a local node.
|
|
693
|
+
// Yields whether the transaction had another estimate before.
|
|
690
694
|
async function pickGasEstimate(page, name) {
|
|
691
695
|
const s = selectors.transaction;
|
|
692
696
|
const option = s.gasOption(...gasEstimates[name]);
|
|
693
697
|
if (!(await (0, page_helpers_1.isVisible)(page, option, 5000)))
|
|
694
698
|
throw await (0, page_helpers_1.failure)(page, `MetaMask offers no "${name}" fee for this transaction`);
|
|
699
|
+
const selected = await hasEstimate(page, option);
|
|
695
700
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
696
701
|
await (0, page_helpers_1.click)(page, option);
|
|
697
702
|
if (await (0, page_helpers_1.isGone)(page, s.gasEstimates, 10000))
|
|
698
|
-
return;
|
|
703
|
+
return !selected;
|
|
699
704
|
}
|
|
700
705
|
throw await (0, page_helpers_1.failure)(page, `MetaMask did not take the "${name}" fee`);
|
|
701
706
|
}
|
|
707
|
+
// The estimate the transaction has is marked by a class on its row
|
|
708
|
+
async function hasEstimate(page, option) {
|
|
709
|
+
const element = await (0, page_helpers_1.waitFor)(page, option);
|
|
710
|
+
return element.evaluate((el) => el.className.includes('--selected')).catch(() => false);
|
|
711
|
+
}
|
|
712
|
+
// Yields whether a field is given another value than it had
|
|
702
713
|
async function fillGasForm(page, values) {
|
|
703
714
|
const s = selectors.transaction;
|
|
704
715
|
await openGasForm(page);
|
|
716
|
+
const asked = Object.keys(gasFields).filter((field) => values[field] !== undefined);
|
|
717
|
+
let changed = false;
|
|
718
|
+
for (const field of asked) {
|
|
719
|
+
if (Number(await valueOf(page, s.gasField(gasFields[field]))) !== Number(values[field]))
|
|
720
|
+
changed = true;
|
|
721
|
+
}
|
|
705
722
|
// The form checks each fee against the other as it stood, and keeps the old
|
|
706
723
|
// value of a fee it refused: the max base fee is typed again once the
|
|
707
724
|
// priority fee is in, for two fees that both move below or above the old ones.
|
|
708
|
-
const fields =
|
|
709
|
-
if (values.maxBaseFee !== undefined && values.priorityFee !== undefined)
|
|
710
|
-
fields.push('maxBaseFee');
|
|
725
|
+
const fields = values.maxBaseFee !== undefined && values.priorityFee !== undefined ? [...asked, 'maxBaseFee'] : asked;
|
|
711
726
|
for (const field of fields)
|
|
712
727
|
await type(page, s.gasField(gasFields[field]), values[field], field);
|
|
713
728
|
// "Save" stays disabled on a value the form refuses, and the error quotes what the form says of it
|
|
714
729
|
if (!(await pressToClose(page, s.gasSave, s.gasForm)))
|
|
715
730
|
throw await (0, page_helpers_1.failure)(page, 'MetaMask did not save the network fee');
|
|
731
|
+
return changed;
|
|
716
732
|
}
|
|
717
733
|
// "Advanced", in the list of estimates, replaces the list with the form. A
|
|
718
734
|
// transaction with a gas price gets another form, which has none of these fields.
|
|
@@ -748,10 +764,6 @@ async function valueOf(page, selector) {
|
|
|
748
764
|
const field = await (0, page_helpers_1.waitFor)(page, selector);
|
|
749
765
|
return field.evaluate((el) => el.value);
|
|
750
766
|
}
|
|
751
|
-
async function textOf(page, selector) {
|
|
752
|
-
const element = await page.$(selector);
|
|
753
|
-
return element ? element.evaluate((el) => (el.textContent ?? '').trim()).catch(() => null) : null;
|
|
754
|
-
}
|
|
755
767
|
/**
|
|
756
768
|
* Press a footer button, then wait for the confirmation to go away: the
|
|
757
769
|
* popup closes, the side panel goes back to the home screen. A click that
|
package/dist/page-helpers.d.ts
CHANGED
|
@@ -29,5 +29,13 @@ export declare function fill(page: Page, selector: Selector, text: string, optio
|
|
|
29
29
|
* words for the log and as a screenshot to look at.
|
|
30
30
|
*/
|
|
31
31
|
export declare function failure(page: Page, message: string): Promise<Error>;
|
|
32
|
+
/** The text of the first element `selector` matches, or null when there is none. */
|
|
33
|
+
export declare function textOf(page: Page, selector: string): Promise<string | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Wait for the text of `selector` to differ from `shown`, at most `timeout`
|
|
36
|
+
* ms. For a value MetaMask sets on its page a moment after a modal that
|
|
37
|
+
* edits it closes: acting on the page meanwhile acts on the old value.
|
|
38
|
+
*/
|
|
39
|
+
export declare function waitForTextChange(page: Page, selector: string, shown: string | null, timeout: number): Promise<void>;
|
|
32
40
|
export declare function sleep(ms: number): Promise<void>;
|
|
33
41
|
export {};
|
package/dist/page-helpers.js
CHANGED
|
@@ -18,6 +18,8 @@ exports.clickWhenEnabled = clickWhenEnabled;
|
|
|
18
18
|
exports.dispatchClick = dispatchClick;
|
|
19
19
|
exports.fill = fill;
|
|
20
20
|
exports.failure = failure;
|
|
21
|
+
exports.textOf = textOf;
|
|
22
|
+
exports.waitForTextChange = waitForTextChange;
|
|
21
23
|
exports.sleep = sleep;
|
|
22
24
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
23
25
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -194,6 +196,21 @@ async function pageText(page) {
|
|
|
194
196
|
return '';
|
|
195
197
|
}
|
|
196
198
|
}
|
|
199
|
+
/** The text of the first element `selector` matches, or null when there is none. */
|
|
200
|
+
async function textOf(page, selector) {
|
|
201
|
+
const element = await page.$(selector);
|
|
202
|
+
return element ? element.evaluate((el) => (el.textContent ?? '').trim()).catch(() => null) : null;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Wait for the text of `selector` to differ from `shown`, at most `timeout`
|
|
206
|
+
* ms. For a value MetaMask sets on its page a moment after a modal that
|
|
207
|
+
* edits it closes: acting on the page meanwhile acts on the old value.
|
|
208
|
+
*/
|
|
209
|
+
async function waitForTextChange(page, selector, shown, timeout) {
|
|
210
|
+
const deadline = Date.now() + timeout;
|
|
211
|
+
while (Date.now() < deadline && (await textOf(page, selector)) === shown)
|
|
212
|
+
await sleep(250);
|
|
213
|
+
}
|
|
197
214
|
function sleep(ms) {
|
|
198
215
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
199
216
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dappress",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|