Skip to content

Commit e054b7e

Browse files
authored
test: Fix flask multichain api wallet_invokeMethod e2e tests (#37471)
## **Description** Fix these 2 flaky tests: - Multichain API Calling `wallet_invokeMethod` on the same dapp across three different connected chains Write operations: calling `eth_sendTransaction` on each connected scope should have less balance due to gas after transaction is sent - Multichain API Calling `wallet_invokeMethod` on the same dapp across three different connected chains Write operations: calling `eth_sendTransaction` on each connected scope should match chosen addresses in each chain to the selected address per scope in extension window The 2 flaky tests share same reason of flakiness: as we invoke all methods the same time, so the confirmation screen order is not always the same when we run test, so the displayed order of account and network can be different https://github.com/user-attachments/assets/5d5a2104-1dea-4315-83d9-02d787ccf602 The test was flaky because it assumed a fixed order of confirmation screens. The new logic no longer depends on a strict order. Instead, it: - Iterates through each confirmation screen dynamically. - Collects the displayed account name and/or network name for each confirmation. - Verifies that all expected combinations appear, regardless of order. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/31396?quickstart=1) ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** ## **Manual testing steps** Tests should pass and be robust ## **Screenshots/Recordings** ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Makes multichain wallet_invokeMethod e2e tests order-agnostic, enhances confirmation page object APIs, and adds a data-testid to the network name used by tests. > > - **E2E (Multichain `wallet_invokeMethod`)**: > - Refactor write-ops tests to be order-agnostic: dynamically iterate confirmations, collect `account`/`network`, and assert all expected combinations; validate unique/expected networks across dialogs. > - Add `SCOPE_TO_NETWORK_NAME` map; update balance checks to verify post-gas changes per network. > - **Page Object (`TransactionConfirmation`)**: > - Add selectors for header account and network name (`confirmation__details-network-name`). > - New helpers: `getNetworkName`, `getSenderAccountName`, `checkNetworkIsNotDisplayed`, `checkHeaderAccountNameIsDisplayed`; rename `checkIsSenderAccountDisplayed` → `checkSenderAccountIsDisplayed`. > - **Metrics Test**: > - Switch to `checkHeaderAccountNameIsDisplayed('Account 1')` before advanced details. > - **UI (Confirm Info Network Row)**: > - Add `data-testid="confirmation__details-network-name"` to network name; update related snapshots. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit a246399. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0a1d33f commit e054b7e

File tree

9 files changed

+192
-64
lines changed

9 files changed

+192
-64
lines changed

test/e2e/flask/multichain-api/evm/wallet_invokeMethod.spec.ts

Lines changed: 107 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ describe('Multichain API', function () {
2929
const DEFAULT_INITIAL_BALANCE_HEX = convertETHToHexGwei(
3030
DEFAULT_LOCAL_NODE_ETH_BALANCE_DEC,
3131
);
32+
const SCOPE_TO_NETWORK_NAME: Record<string, string> = {
33+
'eip155:1337': 'Localhost 8545',
34+
'eip155:1338': 'Localhost 8546',
35+
'eip155:1000': 'Localhost 7777',
36+
};
3237

3338
describe('Calling `wallet_invokeMethod` with permissions granted from EIP-1193 provider', function () {
3439
it('should allow the request to be made', async function () {
@@ -175,48 +180,66 @@ describe('Multichain API', function () {
175180
}
176181
await testDapp.clickInvokeAllMethodsButton();
177182

178-
// first confirmation page should display Account 1 as sender account
179-
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
180-
const confirmation = new TransactionConfirmation(driver);
181-
await confirmation.checkPageIsLoaded();
182-
assert.equal(
183-
await confirmation.checkIsSenderAccountDisplayed('Account 1'),
184-
true,
185-
);
186-
await confirmation.clickFooterConfirmButton();
183+
// Build expected confirmations
184+
const expectedConfirmations: {
185+
account: string;
186+
network: string;
187+
}[] = [];
188+
for (const [i, scope] of GANACHE_SCOPES.entries()) {
189+
expectedConfirmations.push({
190+
account:
191+
i === INDEX_FOR_ALTERNATE_ACCOUNT ? 'Account 2' : 'Account 1',
192+
network: SCOPE_TO_NETWORK_NAME[scope],
193+
});
194+
}
187195

188-
// check which account confirmation page is displayed on second screen
196+
const resultConfirmations: { account: string; network: string }[] =
197+
[];
189198
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
190-
await confirmation.checkPageIsLoaded();
191-
const screenForAccount2 =
192-
await confirmation.checkIsSenderAccountDisplayed('Account 2');
193-
if (screenForAccount2) {
194-
await confirmation.checkNetworkIsDisplayed('Localhost 8546');
195-
await confirmation.clickFooterConfirmButton();
196-
197-
// third confirmation page should display Account 1 as sender account
198-
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
199-
await confirmation.checkPageIsLoaded();
200-
assert.equal(
201-
await confirmation.checkIsSenderAccountDisplayed('Account 1'),
202-
true,
203-
);
204-
await confirmation.checkNetworkIsDisplayed('Localhost 7777');
205-
await confirmation.clickFooterConfirmButtonAndAndWaitForWindowToClose();
206-
} else {
207-
await confirmation.checkNetworkIsDisplayed('Localhost 7777');
208-
await confirmation.clickFooterConfirmButton();
199+
const firstConfirmation = new TransactionConfirmation(driver);
200+
await firstConfirmation.checkPageIsLoaded();
201+
let currentAccount = await firstConfirmation.getSenderAccountName();
202+
let currentNetwork = await firstConfirmation.getNetworkName();
203+
await firstConfirmation.clickFooterConfirmButton();
204+
resultConfirmations.push({
205+
account: currentAccount,
206+
network: currentNetwork,
207+
});
209208

210-
// third confirmation page should display Account 2 as sender account
209+
// Collect actual confirmations from each confirmation screen
210+
for (let i = 0; i < GANACHE_SCOPES.length - 1; i++) {
211211
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
212+
const confirmation = new TransactionConfirmation(driver);
212213
await confirmation.checkPageIsLoaded();
213-
assert.equal(
214-
await confirmation.checkIsSenderAccountDisplayed('Account 2'),
215-
true,
216-
);
217-
await confirmation.checkNetworkIsDisplayed('Localhost 8546');
218-
await confirmation.clickFooterConfirmButtonAndAndWaitForWindowToClose();
214+
await confirmation.checkNetworkIsNotDisplayed(currentNetwork);
215+
currentAccount = await confirmation.getSenderAccountName();
216+
currentNetwork = await confirmation.getNetworkName();
217+
resultConfirmations.push({
218+
account: currentAccount,
219+
network: currentNetwork,
220+
});
221+
222+
// Confirm the transaction except for the last one
223+
if (i < GANACHE_SCOPES.length - 2) {
224+
await confirmation.clickFooterConfirmButton();
225+
} else {
226+
await confirmation.clickFooterConfirmButtonAndAndWaitForWindowToClose();
227+
}
219228
}
229+
230+
// Verify all expected confirmations were found
231+
const hasAllExpectedConfirmations = expectedConfirmations.every(
232+
(expectedConf) =>
233+
resultConfirmations.find(
234+
(resultConf) =>
235+
resultConf.account === expectedConf.account &&
236+
resultConf.network === expectedConf.network,
237+
),
238+
);
239+
assert.ok(
240+
hasAllExpectedConfirmations,
241+
'Not all expected confirmation screens were found',
242+
);
220243
},
221244
);
222245
});
@@ -260,24 +283,64 @@ describe('Multichain API', function () {
260283

261284
await testDapp.clickInvokeAllMethodsButton();
262285
const totalNumberOfScopes = GANACHE_SCOPES.length;
263-
for (let i = 0; i < totalNumberOfScopes; i++) {
286+
const expectedNetworks = [
287+
'Localhost 8545',
288+
'Localhost 8546',
289+
'Localhost 7777',
290+
];
291+
const currentNetworks = new Set<string>();
292+
293+
// Get the network name from the first confirmation screen
294+
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
295+
const firstConfirmation = new TransactionConfirmation(driver);
296+
await firstConfirmation.checkPageIsLoaded();
297+
let currentNetworkName = await firstConfirmation.getNetworkName();
298+
await firstConfirmation.checkPageNumbers(1, totalNumberOfScopes);
299+
await firstConfirmation.clickFooterConfirmButton();
300+
currentNetworks.add(currentNetworkName);
301+
302+
for (let i = 0; i < totalNumberOfScopes - 1; i++) {
264303
await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog);
265304
const confirmation = new TransactionConfirmation(driver);
266305
await confirmation.checkPageIsLoaded();
267-
if (i < totalNumberOfScopes - 1) {
268-
// if pending tx's, verify navigation and confirm
269-
await confirmation.checkPageNumbers(1, totalNumberOfScopes - i);
270-
await confirmation.checkNetworkIsDisplayed(
271-
`Localhost ${8545 + i}`,
306+
307+
// Check that the network is different from the previous confirmation screen to ensure that we’ve switched to a new confirmation screen
308+
await confirmation.checkNetworkIsNotDisplayed(currentNetworkName);
309+
310+
// Get the network name from the current confirmation screen
311+
currentNetworkName = await confirmation.getNetworkName();
312+
313+
// Verify this network hasn't been seen before and is expected
314+
assert(
315+
!currentNetworks.has(currentNetworkName),
316+
`Network ${currentNetworkName} appeared more than once`,
317+
);
318+
assert(
319+
expectedNetworks.includes(currentNetworkName),
320+
`Unexpected network: ${currentNetworkName}`,
321+
);
322+
currentNetworks.add(currentNetworkName);
323+
324+
if (i < totalNumberOfScopes - 2) {
325+
// First 2 confirmations: verify navigation and confirm
326+
await confirmation.checkPageNumbers(
327+
1,
328+
totalNumberOfScopes - i - 1,
272329
);
273330
await confirmation.clickFooterConfirmButton();
274331
} else {
275-
await confirmation.checkNetworkIsDisplayed('Localhost 7777');
276-
// if no pending tx's, confirm and wait for window to close
332+
// Last confirmation: confirm and wait for window to close
277333
await confirmation.clickFooterConfirmButtonAndAndWaitForWindowToClose();
278334
}
279335
}
280336

337+
// Verify all expected networks were seen
338+
assert.equal(
339+
currentNetworks.size,
340+
expectedNetworks.length,
341+
'Not all networks were confirmed',
342+
);
343+
281344
await driver.switchToWindowWithTitle(
282345
WINDOW_TITLES.ExtensionInFullScreenView,
283346
);

test/e2e/page-objects/pages/confirmations/redesign/transaction-confirmation.ts

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ class TransactionConfirmation extends Confirmation {
7070
private readonly gasLimitInput: RawLocator =
7171
'[data-testid="gas-limit-input"]';
7272

73+
private readonly headerAccountName: RawLocator =
74+
'[data-testid="header-account-name"]';
75+
76+
private readonly networkName: RawLocator =
77+
'[data-testid="confirmation__details-network-name"]';
78+
7379
private readonly saveButton: RawLocator = { tag: 'button', text: 'Save' };
7480

7581
private readonly senderAccount: RawLocator = '[data-testid="sender-address"]';
7682

77-
private readonly transactionDetails: RawLocator =
78-
'[data-testid="confirmation__token-details-section"]';
79-
8083
private readonly walletInitiatedHeadingTitle: RawLocator = {
8184
css: 'h4',
8285
text: tEn('review') as string,
@@ -215,6 +218,16 @@ class TransactionConfirmation extends Confirmation {
215218
});
216219
}
217220

221+
async checkHeaderAccountNameIsDisplayed(account: string): Promise<void> {
222+
console.log(
223+
`Checking header account name ${account} on transaction confirmation page.`,
224+
);
225+
await this.driver.waitForSelector({
226+
css: this.headerAccountName,
227+
text: account,
228+
});
229+
}
230+
218231
async checkPaidByMetaMask() {
219232
await this.driver.findElement({
220233
css: this.paidByMetaMaskNotice,
@@ -223,26 +236,18 @@ class TransactionConfirmation extends Confirmation {
223236
}
224237

225238
/**
226-
* Checks if the sender account is displayed in the transaction confirmation page.
239+
* Checks that the sender account is displayed on the transaction confirmation page.
227240
*
228241
* @param account - The sender account to check.
229242
*/
230-
async checkIsSenderAccountDisplayed(account: string): Promise<boolean> {
243+
async checkSenderAccountIsDisplayed(account: string): Promise<void> {
231244
console.log(
232245
`Checking sender account ${account} on transaction confirmation page.`,
233246
);
234-
try {
235-
await this.driver.waitForSelector({
236-
css: this.senderAccount,
237-
text: account,
238-
});
239-
return true;
240-
} catch (err) {
241-
console.log(
242-
`Sender account ${account} is not displayed on transaction confirmation page.`,
243-
);
244-
return false;
245-
}
247+
await this.driver.waitForSelector({
248+
css: this.senderAccount,
249+
text: account,
250+
});
246251
}
247252

248253
/**
@@ -259,11 +264,23 @@ class TransactionConfirmation extends Confirmation {
259264
`Checking network ${network} is displayed on transaction confirmation page.`,
260265
);
261266
await this.driver.waitForSelector({
262-
css: this.transactionDetails,
267+
css: this.networkName,
263268
text: network,
264269
});
265270
}
266271

272+
async checkNetworkIsNotDisplayed(network: string): Promise<void> {
273+
console.log(
274+
`Checking network ${network} is not displayed on transaction confirmation page.`,
275+
);
276+
await this.driver.assertElementNotPresent(
277+
{ css: this.networkName, text: network },
278+
{
279+
waitAtLeastGuard: 1000,
280+
},
281+
);
282+
}
283+
267284
async checkNoAlertMessageIsDisplayed() {
268285
console.log(
269286
`Checking no alert message is displayed on transaction confirmation page.`,
@@ -355,6 +372,45 @@ class TransactionConfirmation extends Confirmation {
355372
await this.driver.fill(this.customNonceInput, nonce);
356373
}
357374

375+
/**
376+
* Gets the network name displayed on the transaction confirmation page.
377+
*
378+
* IMPORTANT: Make sure the transaction confirmation screen is fully loaded
379+
* before calling this method to avoid race conditions, as the network name element
380+
* might not be present or updated correctly immediately after navigation.
381+
*
382+
* @returns The network name.
383+
*/
384+
async getNetworkName(): Promise<string> {
385+
const networkNameElement = await this.driver.findElement(this.networkName);
386+
const networkName = await networkNameElement.getText();
387+
console.log(
388+
'Current network name displayed on transaction confirmation page: ',
389+
networkName,
390+
);
391+
return networkName;
392+
}
393+
394+
/**
395+
* Gets the sender account name displayed on the transaction confirmation page.
396+
*
397+
* IMPORTANT: Make sure the transaction confirmation screen is fully loaded
398+
* before calling this method to avoid race conditions.
399+
*
400+
* @returns The sender account name.
401+
*/
402+
async getSenderAccountName(): Promise<string> {
403+
const senderAccountElement = await this.driver.findElement(
404+
this.senderAccount,
405+
);
406+
const senderAccountName = await senderAccountElement.getText();
407+
console.log(
408+
'Current sender account name displayed on transaction confirmation page: ',
409+
senderAccountName,
410+
);
411+
return senderAccountName;
412+
}
413+
358414
async setCustomNonce(nonce: string) {
359415
await this.clickCustomNonceButton();
360416
await this.fillCustomNonce(nonce);

test/e2e/tests/confirmations/transactions/metrics.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('Metrics', function () {
6969
const transactionConfirmation = new TransactionConfirmation(driver);
7070
// verify UI before clicking advanced details to give time for the Transaction Added event to be emitted without Advanced Details being displayed
7171
await transactionConfirmation.checkPageIsLoaded();
72-
await transactionConfirmation.checkIsSenderAccountDisplayed(
72+
await transactionConfirmation.checkHeaderAccountNameIsDisplayed(
7373
'Account 1',
7474
);
7575
await transactionConfirmation.checkGasFeeSymbol('ETH');

ui/pages/confirmations/components/confirm/info/__snapshots__/info.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,7 @@ exports[`Info renders info section for typed sign request with permission 1`] =
18831883
</div>
18841884
<p
18851885
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
1886+
data-testid="confirmation__details-network-name"
18861887
>
18871888
Goerli
18881889
</p>

ui/pages/confirmations/components/confirm/info/native-transfer/__snapshots__/native-transfer.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ exports[`NativeTransferInfo renders correctly 1`] = `
204204
</div>
205205
<p
206206
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
207+
data-testid="confirmation__details-network-name"
207208
>
208209
Goerli
209210
</p>

ui/pages/confirmations/components/confirm/info/nft-token-transfer/__snapshots__/nft-token-transfer.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ exports[`NFTTokenTransferInfo renders correctly 1`] = `
234234
</div>
235235
<p
236236
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
237+
data-testid="confirmation__details-network-name"
237238
>
238239
Goerli
239240
</p>

ui/pages/confirmations/components/confirm/info/shared/network-row/network-row.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ export const NetworkRow = ({
6464
name={networkName}
6565
style={{ borderWidth: 0 }}
6666
/>
67-
<Text variant={TextVariant.bodyMd} color={TextColor.textDefault}>
67+
<Text
68+
variant={TextVariant.bodyMd}
69+
color={TextColor.textDefault}
70+
data-testid="confirmation__details-network-name"
71+
>
6872
{networkName}
6973
</Text>
7074
</Box>

ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-details-section.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ exports[`TokenDetailsSection renders correctly 1`] = `
3434
</div>
3535
<p
3636
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
37+
data-testid="confirmation__details-network-name"
3738
>
3839
Goerli
3940
</p>

ui/pages/confirmations/components/confirm/info/token-transfer/__snapshots__/token-transfer.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ exports[`TokenTransferInfo renders correctly 1`] = `
215215
</div>
216216
<p
217217
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
218+
data-testid="confirmation__details-network-name"
218219
>
219220
Goerli
220221
</p>

0 commit comments

Comments
 (0)