Skip to content

Commit 0dbf2b2

Browse files
javiergarciaveraorestisethHowardBrahamseaonaDDDDDanica
authored
test: Mmqa 1101 extension performance e2e tests (#37350)
## **Description** Added different test scenarios for performance purposes: - Asset details - Solana asset details (skipped until #37339 gets merged) - Onboarding import SRP (with account list loaded check) - Onboarding create account Each test will generate a json file with all the loading times for each interaction. This is an example of the output for Onboarding import SRP scenario: ```{ "testName": "Import an existing wallet and completes the onboarding process", "testFile": "onboarding-import-wallet.spec.ts", "timestamp": "2025-11-05T10:58:34.031Z", "timers": [ { "id": "Time since the user clicks on \"Import wallet\" button until \"Social\" screen is visible", "start": 1762340307892, "end": 1762340308117, "duration": 225 }, { "id": "Time since the user clicks on \"use SRP\" button until \"SRP\" form is visible", "start": 1762340308463, "end": 1762340308474, "duration": 11 }, { "id": "Time since the user clicks on \"Confirm\" button until \"Password\" form is visible", "start": 1762340309260, "end": 1762340309267, "duration": 7 }, { "id": "Time since the user clicks on \"Continue\" button until \"Onboarding Success\" screen is visible", "start": 1762340309508, "end": 1762340309517, "duration": 9 }, { "id": "Time since the user clicks on \"Done\" button until \"Home\" screen is visible", "start": 1762340309724, "end": 1762340313442, "duration": 3718 }, { "id": "Time since the user opens \"account list\" until the account list is loaded", "start": 1762340313518, "end": 1762340313528, "duration": 10 } ] } ``` ## **Changelog** CHANGELOG entry: null ## **Related issues** Closes: MetaMask/MetaMask-planning#5453 ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] 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] > Adds power-user performance E2E tests (asset details, Solana, onboarding) with timer reporting to JSON, plus page-object and fixture tweaks to support them. > > - **E2E Performance Tests**: > - Add `asset-details-power-user.spec.ts` and `solana-asset-details-power-user.spec.ts` to measure time to render price charts after asset click. > - Add onboarding flows: `onboarding-import-wallet.spec.ts` and `onboarding-new-wallet.spec.ts`, capturing timings across key steps and verifying asset list readiness. > - **Timing Utilities**: > - Introduce `test/timers/Timer.ts` and `test/timers/Timers.ts` for start/stop timers. > - Add `tolerate-failure/utils/testSetup.ts` to auto-generate per-test JSON reports and console summaries. > - Add `tolerate-failure/utils/commonMocks.ts` for external service mocks (Sentry, Segment, subscription APIs). > - **Page Object Enhancements**: > - `home/asset-list.ts`: simplify `clickOnAsset`, improve network filter open condition, add `checkPriceChartLoaded`, `checkTokenListIsDisplayed`, `waitForTokenToBeDisplayed`, and `checkConversionRateDisplayed`. > - `onboarding/start-onboarding-page.ts`: add SRP action helpers (`clickCreateWithSrpButton`, `clickImportWithSrpButton`), visibility checks, and refactor flows. > - `account-list-page.ts`: add selectors for "Add account" and "Syncing..."; update load checks for multichain state. > - **Fixtures**: > - `with-networks.js`: import `MultichainNetworks` and enable Solana via `ALL_POPULAR_NETWORKS.solana`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 272b07e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: orestis <[email protected]> Co-authored-by: Howard Braham <[email protected]> Co-authored-by: seaona <[email protected]> Co-authored-by: Danica Shen <[email protected]>
1 parent e1e4ba5 commit 0dbf2b2

File tree

13 files changed

+781
-25
lines changed

13 files changed

+781
-25
lines changed

app/scripts/fixtures/with-networks.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { MultichainNetworks } from '../../../shared/constants/multichain/networks';
12
import {
23
ARBITRUM_DISPLAY_NAME,
34
AVALANCHE_DISPLAY_NAME,
@@ -144,7 +145,6 @@ export const FIXTURES_NETWORKS = {
144145
imageUrl: './images/sei.svg',
145146
},
146147
},
147-
148148
localhost: {
149149
id: 'localhost',
150150
rpcUrl: 'http://localhost:8545',
@@ -172,4 +172,7 @@ export const ALL_POPULAR_NETWORKS = {
172172
[CHAIN_IDS.ZKSYNC_ERA]: true,
173173
[CHAIN_IDS.SEI]: true,
174174
},
175+
solana: {
176+
[MultichainNetworks.SOLANA]: true,
177+
},
175178
};

test/e2e/page-objects/pages/account-list-page.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,16 @@ class AccountListPage {
217217
tag: 'p',
218218
};
219219

220+
private readonly addAccountButton = {
221+
text: 'Add account',
222+
tag: 'p',
223+
};
224+
225+
private readonly syncingMessage = {
226+
text: 'Syncing...',
227+
tag: 'p',
228+
};
229+
220230
constructor(driver: Driver) {
221231
this.driver = driver;
222232
}
@@ -226,13 +236,7 @@ class AccountListPage {
226236
}): Promise<void> {
227237
try {
228238
const selectorsToWaitFor = options?.isMultichainAccountsState2Enabled
229-
? [
230-
{
231-
css: this.createMultichainAccountButton,
232-
text: 'Add account',
233-
},
234-
this.multichainAccountOptionsMenuButton,
235-
]
239+
? [this.addAccountButton, this.multichainAccountOptionsMenuButton]
236240
: [this.createAccountButton, this.accountOptionsMenuButton];
237241
await this.driver.waitForMultipleSelectors(selectorsToWaitFor);
238242
} catch (e) {
@@ -242,10 +246,7 @@ class AccountListPage {
242246

243247
if (options?.isMultichainAccountsState2Enabled) {
244248
console.log(`Check that account syncing not displayed in account list`);
245-
await this.driver.assertElementNotPresent({
246-
css: this.createMultichainAccountButton,
247-
text: 'Syncing',
248-
});
249+
await this.driver.assertElementNotPresent(this.syncingMessage);
249250
}
250251

251252
console.log('Account list is loaded');

test/e2e/page-objects/pages/home/asset-list.ts

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ class AssetListPage {
143143
private readonly tokenIncreaseDecreaseValue =
144144
'[data-testid="token-increase-decrease-value"]';
145145

146+
private readonly noPriceAvailableMessage = {
147+
css: 'p',
148+
text: 'No conversion rate available',
149+
};
150+
151+
private readonly modalCloseButton =
152+
'[data-testid="modal-header-close-button"]';
153+
146154
constructor(driver: Driver) {
147155
this.driver = driver;
148156
}
@@ -178,15 +186,10 @@ class AssetListPage {
178186
}
179187

180188
async clickOnAsset(assetName: string): Promise<void> {
181-
const buttons = await this.driver.findElements(this.tokenListItem);
182-
for (const button of buttons) {
183-
const text = await button.getText();
184-
if (text.includes(assetName)) {
185-
await button.click();
186-
return;
187-
}
188-
}
189-
throw new Error(`${assetName} button not found`);
189+
await this.driver.clickElement({
190+
css: this.tokenListItem,
191+
text: assetName,
192+
});
190193
}
191194

192195
async clickSendButton(): Promise<void> {
@@ -355,7 +358,7 @@ class AssetListPage {
355358
await this.driver.clickElement(this.networksToggle);
356359
await this.driver.waitUntil(
357360
async () => {
358-
return Boolean(await this.driver.findElement(this.allNetworksOption));
361+
return Boolean(await this.driver.findElement(this.modalCloseButton));
359362
},
360363
{
361364
timeout: 5000,
@@ -508,6 +511,11 @@ class AssetListPage {
508511
);
509512
}
510513

514+
async checkPriceChartLoaded(assetAddress: string): Promise<void> {
515+
console.log(`Verify the price chart is loaded`);
516+
await this.driver.waitForSelector(this.tokenPercentage(assetAddress));
517+
}
518+
511519
/**
512520
* Checks if the specified token amount is displayed in the token list.
513521
*
@@ -733,6 +741,47 @@ class AssetListPage {
733741
});
734742
console.log(`Token details verified successfully for ${symbol}`);
735743
}
744+
745+
/**
746+
* Checks if the token list is displayed
747+
*
748+
* @throws Error if the token list is not displayed
749+
*/
750+
async checkTokenListIsDisplayed(): Promise<void> {
751+
try {
752+
await this.driver.waitForSelector(this.tokenListItem, {
753+
timeout: 300000,
754+
});
755+
} catch (e) {
756+
console.log('Token list is not displayed', e);
757+
throw e;
758+
}
759+
}
760+
761+
/**
762+
* Waits for a token to be displayed in the token list
763+
* This is done due to the snap delay.
764+
*
765+
* @param tokenName - The name of the token to wait for
766+
*/
767+
async waitForTokenToBeDisplayed(tokenName: string): Promise<void> {
768+
await this.driver.waitForSelector(
769+
{
770+
css: this.tokenListItem,
771+
text: tokenName,
772+
},
773+
{ timeout: 30000 },
774+
);
775+
}
776+
777+
/**
778+
* Checks if the token list prices are displayed and no "No conversion rate available" message is displayed
779+
*
780+
* @throws Error if a "No conversion rate available" message is displayed
781+
*/
782+
async checkConversionRateDisplayed(): Promise<void> {
783+
await this.driver.assertElementNotPresent(this.noPriceAvailableMessage);
784+
}
736785
}
737786

738787
export default AssetListPage;

test/e2e/page-objects/pages/home/homepage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class HomePage {
9999
private readonly shieldEntryModalSkip =
100100
'[data-testid="shield-entry-modal-close-button"]';
101101

102+
private readonly multichainTokenListButton = `[data-testid="multichain-token-list-button"]`;
103+
102104
private readonly emptyBalance =
103105
'[data-testid="coin-overview-balance-empty-state"]';
104106

test/e2e/page-objects/pages/onboarding/start-onboarding-page.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,29 @@ class StartOnboardingPage {
9494
async createWalletWithSrp(socialLoginEnabled = true): Promise<void> {
9595
await this.driver.clickElement(this.createWalletButton);
9696
if (socialLoginEnabled) {
97-
await this.driver.clickElement(this.onboardingCreateWithSrpButton);
97+
await this.clickCreateWithSrpButton();
9898
}
9999
}
100100

101-
async importWallet(): Promise<void> {
102-
await this.driver.clickElement(this.importWalletButton);
101+
async clickCreateWithSrpButton(): Promise<void> {
102+
await this.driver.clickElement(this.onboardingCreateWithSrpButton);
103+
}
104+
105+
async clickImportWithSrpButton(): Promise<void> {
103106
await this.driver.clickElement(this.onboardingImportWithSrpButton);
104107
}
105108

109+
async checkUserSrpButtonIsVisible(): Promise<void> {
110+
await this.driver.waitForSelector(this.onboardingImportWithSrpButton);
111+
}
112+
113+
async importWallet(withSrpButton = true): Promise<void> {
114+
await this.driver.clickElement(this.importWalletButton);
115+
if (withSrpButton) {
116+
await this.driver.clickElement(this.onboardingImportWithSrpButton);
117+
}
118+
}
119+
106120
async createWalletWithSocialLogin(
107121
authConnection = AuthConnection.Google,
108122
): Promise<void> {
@@ -130,6 +144,19 @@ class StartOnboardingPage {
130144
await this.driver.waitForSelector(socialLoginButton);
131145
await this.driver.clickElement(socialLoginButton);
132146
}
147+
148+
async checkSocialSignUpFormIsVisible(): Promise<void> {
149+
try {
150+
await this.driver.waitForSelector(this.onboardingCreateWithGoogleButton);
151+
} catch (e) {
152+
console.log(
153+
'Timeout while waiting for social sign up form to be loaded',
154+
e,
155+
);
156+
throw e;
157+
}
158+
console.log('Social sign up form is loaded');
159+
}
133160
}
134161

135162
export default StartOnboardingPage;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { generateWalletState } from '../../../../../app/scripts/fixtures/generate-wallet-state';
2+
import { WITH_STATE_POWER_USER } from '../../../benchmarks/constants';
3+
import { withFixtures } from '../../../helpers';
4+
import { loginWithoutBalanceValidation } from '../../../page-objects/flows/login.flow';
5+
import AssetListPage from '../../../page-objects/pages/home/asset-list';
6+
import HomePage from '../../../page-objects/pages/home/homepage';
7+
import NetworkManager from '../../../page-objects/pages/network-manager';
8+
import { Driver } from '../../../webdriver/driver';
9+
import { setupTimerReporting } from '../utils/testSetup';
10+
import Timers from '../../../../timers/Timers';
11+
12+
const USDC_TOKEN_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
13+
describe('Power user persona', function () {
14+
// Setup timer reporting for all tests in this describe block
15+
setupTimerReporting();
16+
17+
it('Check asset details page load time', async function () {
18+
if (!process.env.INFURA_PROJECT_ID) {
19+
throw new Error(
20+
'Running this E2E test requires a valid process.env.INFURA_PROJECT_ID',
21+
);
22+
}
23+
24+
await withFixtures(
25+
{
26+
title: this.test?.fullTitle(),
27+
fixtures: (
28+
await generateWalletState(WITH_STATE_POWER_USER, true)
29+
).build(),
30+
manifestFlags: {
31+
testing: {
32+
disableSync: true,
33+
infuraProjectId: process.env.INFURA_PROJECT_ID,
34+
},
35+
},
36+
useMockingPassThrough: true,
37+
disableServerMochaToBackground: true,
38+
},
39+
async ({ driver }: { driver: Driver }) => {
40+
await loginWithoutBalanceValidation(driver);
41+
const homePage = new HomePage(driver);
42+
await homePage.checkPageIsLoaded();
43+
const assetListPage = new AssetListPage(driver);
44+
await assetListPage.checkTokenListIsDisplayed();
45+
await assetListPage.checkConversionRateDisplayed();
46+
await assetListPage.openNetworksFilter();
47+
const networkManager = new NetworkManager(driver);
48+
await networkManager.selectNetworkByNameWithWait('Ethereum');
49+
await homePage.checkPageIsLoaded();
50+
await assetListPage.checkTokenListIsDisplayed();
51+
await assetListPage.checkConversionRateDisplayed();
52+
await assetListPage.clickOnAsset('USDC');
53+
const timer1 = Timers.createTimer(
54+
'Time since the user clicks on the asset until the price chart is shown',
55+
);
56+
timer1.startTimer();
57+
await assetListPage.checkPriceChartIsShown();
58+
await assetListPage.checkPriceChartLoaded(USDC_TOKEN_ADDRESS);
59+
timer1.stopTimer();
60+
},
61+
);
62+
});
63+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { generateWalletState } from '../../../../../app/scripts/fixtures/generate-wallet-state';
2+
import { ALL_POPULAR_NETWORKS } from '../../../../../app/scripts/fixtures/with-networks';
3+
import { WITH_STATE_POWER_USER } from '../../../benchmarks/constants';
4+
import { withFixtures } from '../../../helpers';
5+
import { loginWithoutBalanceValidation } from '../../../page-objects/flows/login.flow';
6+
import AssetListPage from '../../../page-objects/pages/home/asset-list';
7+
import HomePage from '../../../page-objects/pages/home/homepage';
8+
import NetworkManager from '../../../page-objects/pages/network-manager';
9+
import { Driver } from '../../../webdriver/driver';
10+
11+
import { setupTimerReporting } from '../utils/testSetup';
12+
import Timers from '../../../../timers/Timers';
13+
14+
const SOL_TOKEN_ADDRESS = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501';
15+
describe('Power user persona', function () {
16+
// Setup timer reporting for all tests in this describe block
17+
setupTimerReporting();
18+
it('Check Solana asset details page load time', async function () {
19+
if (!process.env.INFURA_PROJECT_ID) {
20+
throw new Error(
21+
'Running this E2E test requires a valid process.env.INFURA_PROJECT_ID',
22+
);
23+
}
24+
25+
await withFixtures(
26+
{
27+
title: this.test?.fullTitle(),
28+
fixtures: (await generateWalletState(WITH_STATE_POWER_USER, true))
29+
.withEnabledNetworks(ALL_POPULAR_NETWORKS)
30+
.build(),
31+
manifestFlags: {
32+
testing: {
33+
disableSync: true,
34+
infuraProjectId: process.env.INFURA_PROJECT_ID,
35+
},
36+
},
37+
useMockingPassThrough: true,
38+
disableServerMochaToBackground: true,
39+
},
40+
async ({ driver }: { driver: Driver }) => {
41+
await loginWithoutBalanceValidation(driver);
42+
const homePage = new HomePage(driver);
43+
await homePage.checkPageIsLoaded();
44+
const assetListPage = new AssetListPage(driver);
45+
await assetListPage.checkTokenListIsDisplayed();
46+
await assetListPage.checkConversionRateDisplayed();
47+
await assetListPage.openNetworksFilter();
48+
const networkManager = new NetworkManager(driver);
49+
await networkManager.selectNetworkByNameWithWait('Solana');
50+
await homePage.checkPageIsLoaded();
51+
await assetListPage.checkTokenListIsDisplayed();
52+
await assetListPage.checkConversionRateDisplayed();
53+
await assetListPage.clickOnAsset('Solana');
54+
const timer1 = Timers.createTimer(
55+
'Time since the user clicks on the asset until the price chart is shown',
56+
);
57+
timer1.startTimer();
58+
await assetListPage.checkPriceChartIsShown();
59+
await assetListPage.checkPriceChartLoaded(SOL_TOKEN_ADDRESS); // SOL address
60+
timer1.stopTimer();
61+
},
62+
);
63+
});
64+
});

0 commit comments

Comments
 (0)