Skip to content

Commit bdfd06e

Browse files
authored
fix: reset bridge page inputs after submitting transaction (#41222)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Fixes a bug in which the submitted swap parameters don't get reset after tx submission Adds e2e tests to verify - default quoteRequest is set on load (ETH->mUSD) - quote request is preserved when navigating to and from the asset page - quote request is reset after tx submission - getQuote request polling is aborted after navigating out of the Swap page - refactored metrics spec for better readability <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/41222?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: fix: reset bridge page inputs after submitting transaction ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/SWAPS-4307 ## **Manual testing steps** 1. Submit a swap/bridge transaction (BNB->USDT) 2. Click Swap from homepage 3. Verify that inputs are reset to ETH->MUSD ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> <img width="816" height="310" alt="Screenshot 2026-03-30 at 3 55 03 PM" src="https://github.com/user-attachments/assets/1307d44d-c6e5-4c95-9410-0db8bca5ce91" /> ### **After** <!-- [screenshots/recordings] --> <img width="815" height="318" alt="Screenshot 2026-03-30 at 3 55 19 PM" src="https://github.com/user-attachments/assets/df8c9e90-b092-47e3-aee9-bd76faab4394" /> ## **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] > **Medium Risk** > Touches bridge navigation and reset semantics (controller state vs UI inputs/cache) which can affect user-visible swap/bridge defaults and polling behavior; changes are covered by expanded unit/e2e tests but still impact core flow state management. > > **Overview** > Fixes swap/bridge state not resetting after leaving or completing a transaction by **splitting controller reset from UI/input reset**: `resetBridgeController` now only resets background state, while `resetInputFields` clears bridge cache and resets the Redux input slice. > > Updates bridge entry/prefill/navigation hooks to **avoid unintended resets while preserving state when returning from asset pages**, and to only restore from an active quote in *popup* context. Adds `data-testid` to the asset-info icon to enable stable navigation-to-asset-page testing. > > Expands e2e and metrics tests to validate default `ETH → mUSD` initialization, state preservation across asset-page navigation/back, state reset when reopening the swap page, and that quote polling stops after navigating away; also adds helpers/mocks (historical prices, refresh-rate constant, input-change event assertions). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b83d2e6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 806ac67 commit bdfd06e

21 files changed

+668
-150
lines changed

test/e2e/page-objects/pages/bridge/quote-page.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { strict as assert } from 'assert';
2+
import { Hex } from '@metamask/utils';
3+
import { toAssetId } from '../../../../../shared/lib/asset-utils';
4+
import { ASSET_ROUTE } from '../../../../../shared/lib/deep-links/routes/route';
5+
import { toChecksumHexAddress } from '../../../../../shared/lib/hexstring-utils';
26
import { Driver } from '../../../webdriver/driver';
7+
import TokenOverviewPage from '../token-overview-page';
38

49
export type BridgeQuote = {
510
amount: string;
@@ -15,7 +20,7 @@ class BridgeQuotePage {
1520

1621
public sourceAssetPickerButton = '[data-testid="bridge-source-button"]';
1722

18-
private destinationAssetPickerButton =
23+
public destinationAssetPickerButton =
1924
'[data-testid="bridge-destination-button"]';
2025

2126
private mutlichainAssetPicker =
@@ -148,6 +153,69 @@ class BridgeQuotePage {
148153
);
149154
};
150155

156+
searchForAsset = async (
157+
token: string,
158+
assetPicker = this.sourceAssetPickerButton,
159+
) => {
160+
console.log(`Opening asset picker`);
161+
await this.driver.clickElement(assetPicker);
162+
await this.driver.fill(this.assetPrickerSearchInput, token);
163+
console.log(`Filled search input with ${token}`);
164+
const assetElement = await this.driver.findElement({
165+
css: this.tokenButton,
166+
text: token,
167+
});
168+
return assetElement;
169+
};
170+
171+
goToAssetPage = async (
172+
token: string,
173+
chainId: Hex,
174+
address: string,
175+
assetPicker = this.sourceAssetPickerButton,
176+
) => {
177+
const expectedAssetId = toAssetId(address, chainId)?.toLowerCase();
178+
const expectedUrl = `${ASSET_ROUTE}/${chainId}/${encodeURIComponent(toChecksumHexAddress(address))}`;
179+
180+
console.log(`Opening asset picker`);
181+
await this.driver.clickElement(assetPicker);
182+
await this.driver.fill(this.assetPrickerSearchInput, token);
183+
console.log(`Filled search input with ${token}`);
184+
const assetElement = await this.driver.findElement({
185+
tag: 'button',
186+
testId: `bridge-asset-info-icon-${expectedAssetId}`,
187+
});
188+
console.log(`Clicked link to the asset page`);
189+
await assetElement.click();
190+
await this.driver.waitForUrlContaining({
191+
url: expectedUrl,
192+
});
193+
const assetPage = new TokenOverviewPage(this.driver);
194+
await assetPage.checkPageIsLoaded();
195+
};
196+
197+
checkAssetsAreSelected = async (sourceToken: string, destToken: string) => {
198+
await this.driver.waitForSelector({
199+
css: this.sourceAssetPickerButton,
200+
text: sourceToken,
201+
});
202+
console.log(`Expected source asset ${sourceToken} is selected`);
203+
await this.driver.waitForSelector({
204+
css: this.destinationAssetPickerButton,
205+
text: destToken,
206+
});
207+
console.log(`Expected dest asset ${destToken} is selected`);
208+
};
209+
210+
checkAssetPickerModalIsReopened = async () => {
211+
await this.driver.waitForSelector({
212+
testId: 'bridge-asset-picker-modal',
213+
});
214+
console.log('Asset picker modal is visible');
215+
await this.driver.clickElementAndWaitToDisappear('[aria-label="Close"]');
216+
console.log('Asset picker modal closed');
217+
};
218+
151219
waitForQuote = async () => {
152220
await this.driver.waitForSelector(this.submitButton, { timeout: 30000 });
153221
};

test/e2e/page-objects/pages/token-overview-page.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class TokenOverviewPage {
2525
tag: 'div',
2626
};
2727

28+
private readonly backButton = '.asset-page__back-button';
29+
2830
constructor(driver: Driver) {
2931
this.driver = driver;
3032
}
@@ -67,6 +69,10 @@ class TokenOverviewPage {
6769
this.viewAssetInExplorerButton,
6870
);
6971
}
72+
73+
async clickBack(): Promise<void> {
74+
await this.driver.clickElement(this.backButton);
75+
}
7076
}
7177

7278
export default TokenOverviewPage;

test/e2e/tests/bridge/bridge-positive-cases.spec.ts

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import { login } from '../../page-objects/flows/login.flow';
44
import HomePage from '../../page-objects/pages/home/homepage';
55
import BridgeQuotePage from '../../page-objects/pages/bridge/quote-page';
66
import NetworkManager from '../../page-objects/pages/network-manager';
7+
import TokenOverviewPage from '../../page-objects/pages/token-overview-page';
78
import {
89
BRIDGE_FEATURE_FLAGS_WITH_SSE_ENABLED,
910
DEFAULT_BRIDGE_FEATURE_FLAGS,
1011
} from './constants';
11-
import { bridgeTransaction, getBridgeFixtures } from './bridge-test-utils';
12+
import {
13+
bridgeTransaction,
14+
checkQuoteRequestsAreNotMadeAfterTimestamp,
15+
getBridgeFixtures,
16+
} from './bridge-test-utils';
1217

1318
describe('Bridge tests', function (this: Suite) {
1419
this.timeout(160000); // This test is very long, so we need an unusually high timeout
@@ -92,7 +97,7 @@ describe('Bridge tests', function (this: Suite) {
9297
DEFAULT_BRIDGE_FEATURE_FLAGS,
9398
false,
9499
),
95-
async ({ driver }) => {
100+
async ({ driver, mockedEndpoint }) => {
96101
await login(driver, { expectedBalance: '$225,730.11' });
97102
const networkManager = new NetworkManager(driver);
98103

@@ -108,8 +113,14 @@ describe('Bridge tests', function (this: Suite) {
108113
fromChain: 'Linea',
109114
toChain: 'Ethereum',
110115
});
116+
const finalQuoteRequestTimestamp = Date.now();
111117

112118
await bridgePage.goBack();
119+
await checkQuoteRequestsAreNotMadeAfterTimestamp(
120+
driver,
121+
finalQuoteRequestTimestamp,
122+
mockedEndpoint,
123+
);
113124

114125
// check if the Linea network is selected
115126
await networkManager.openNetworkManager();
@@ -127,7 +138,7 @@ describe('Bridge tests', function (this: Suite) {
127138
BRIDGE_FEATURE_FLAGS_WITH_SSE_ENABLED,
128139
false,
129140
),
130-
async ({ driver }) => {
141+
async ({ driver, mockedEndpoint }) => {
131142
await login(driver, { expectedBalance: '$225,730.11' });
132143

133144
const homePage = new HomePage(driver);
@@ -148,6 +159,167 @@ describe('Bridge tests', function (this: Suite) {
148159
expectedTransactionsCount: 2,
149160
expectedDestAmount: '9.9',
150161
});
162+
const finalQuoteRequestTimestamp = Date.now();
163+
const bridgePage = new BridgeQuotePage(driver);
164+
await checkQuoteRequestsAreNotMadeAfterTimestamp(
165+
driver,
166+
finalQuoteRequestTimestamp,
167+
mockedEndpoint,
168+
);
169+
170+
console.log('Navigating back to Swap page');
171+
await homePage.startSwapFlow();
172+
await bridgePage.checkAssetsAreSelected('ETH', 'mUSD');
173+
console.log('Checked that assets have been reset to defaults');
174+
},
175+
);
176+
});
177+
178+
it('Preserves bridge state when navigating from the source asset page', async function () {
179+
await withFixtures(
180+
getBridgeFixtures(
181+
this.test?.fullTitle(),
182+
DEFAULT_BRIDGE_FEATURE_FLAGS,
183+
false,
184+
),
185+
async ({ driver }) => {
186+
await login(driver, { expectedBalance: '$225,730.11' });
187+
188+
// Navigate to Bridge page
189+
const homePage = new HomePage(driver);
190+
await homePage.startSwapFlow();
191+
const bridgePage = new BridgeQuotePage(driver);
192+
193+
console.log('Checking that source asset is selected');
194+
await bridgePage.goToAssetPage(
195+
'DAI',
196+
'0x1',
197+
'0x6B175474E89094C44Da98b954EedeAC495271d0F',
198+
);
199+
200+
const tokenOverviewPage = new TokenOverviewPage(driver);
201+
await tokenOverviewPage.clickSwap();
202+
console.log('Clicked Swap button from source asset page');
203+
await bridgePage.checkAssetsAreSelected('DAI', 'mUSD');
204+
},
205+
);
206+
});
207+
208+
it('Preserves bridge state when navigating from the dest asset page', async function () {
209+
await withFixtures(
210+
getBridgeFixtures(
211+
this.test?.fullTitle(),
212+
DEFAULT_BRIDGE_FEATURE_FLAGS,
213+
false,
214+
),
215+
async ({ driver }) => {
216+
await login(driver, { expectedBalance: '$225,730.11' });
217+
218+
// Navigate to Bridge page
219+
const homePage = new HomePage(driver);
220+
await homePage.startSwapFlow();
221+
const bridgePage = new BridgeQuotePage(driver);
222+
223+
await (await bridgePage.searchForAsset('DAI')).click();
224+
console.log('Selected source asset DAI');
225+
226+
const tokenOverviewPage = new TokenOverviewPage(driver);
227+
console.log('Checking that dest asset is selected');
228+
await bridgePage.goToAssetPage(
229+
'USDC',
230+
'0xe708',
231+
'0x176211869cA2b568f2A7D4EE941E073a821EE1ff',
232+
bridgePage.destinationAssetPickerButton,
233+
);
234+
await tokenOverviewPage.clickSwap();
235+
console.log('Clicked Swap button from dest asset page');
236+
await bridgePage.checkAssetsAreSelected('DAI', 'USDC');
237+
},
238+
);
239+
});
240+
241+
it('Preserves bridge state when navigating back from the asset page', async function () {
242+
await withFixtures(
243+
getBridgeFixtures(
244+
this.test?.fullTitle(),
245+
DEFAULT_BRIDGE_FEATURE_FLAGS,
246+
false,
247+
),
248+
async ({ driver }) => {
249+
await login(driver, { expectedBalance: '$225,730.11' });
250+
251+
// Navigate to Bridge page
252+
const homePage = new HomePage(driver);
253+
await homePage.startSwapFlow();
254+
const bridgePage = new BridgeQuotePage(driver);
255+
256+
const tokenOverviewPage = new TokenOverviewPage(driver);
257+
258+
const srcAssetElement = await bridgePage.searchForAsset('mUSD');
259+
await srcAssetElement.click();
260+
console.log('Selected source asset mUSD');
261+
262+
console.log('Checking that asset picker is visible');
263+
await bridgePage.goToAssetPage(
264+
'USDC',
265+
'0xe708',
266+
'0x176211869cA2b568f2A7D4EE941E073a821EE1ff',
267+
bridgePage.destinationAssetPickerButton,
268+
);
269+
await tokenOverviewPage.clickBack();
270+
console.log('Navigated back to Swap page from asset page');
271+
272+
await bridgePage.checkAssetPickerModalIsReopened();
273+
await bridgePage.checkAssetsAreSelected('mUSD', 'ETH');
274+
},
275+
);
276+
});
277+
278+
it('Resets bridge state when reopening the page', async function () {
279+
await withFixtures(
280+
getBridgeFixtures(
281+
this.test?.fullTitle(),
282+
DEFAULT_BRIDGE_FEATURE_FLAGS,
283+
false,
284+
),
285+
async ({ driver }) => {
286+
await login(driver, { expectedBalance: '$225,730.11' });
287+
288+
// Navigate to Bridge page
289+
const homePage = new HomePage(driver);
290+
await homePage.startSwapFlow();
291+
const bridgePage = new BridgeQuotePage(driver);
292+
293+
const tokenOverviewPage = new TokenOverviewPage(driver);
294+
295+
await (await bridgePage.searchForAsset('DAI')).click();
296+
console.log('Selected source asset DAI');
297+
298+
await (
299+
await bridgePage.searchForAsset(
300+
'USDC',
301+
bridgePage.destinationAssetPickerButton,
302+
)
303+
).click();
304+
console.log('Selected dest asset USDC');
305+
306+
await bridgePage.goToAssetPage(
307+
'DAI',
308+
'0x1',
309+
'0x6B175474E89094C44Da98b954EedeAC495271d0F',
310+
bridgePage.destinationAssetPickerButton,
311+
);
312+
await tokenOverviewPage.clickBack();
313+
314+
await bridgePage.checkAssetPickerModalIsReopened();
315+
await bridgePage.checkAssetsAreSelected('DAI', 'USDC');
316+
317+
console.log(
318+
'Checking that selected assets are reset after reopening Swap page',
319+
);
320+
await bridgePage.goBack();
321+
await homePage.startSwapFlow();
322+
await bridgePage.checkAssetsAreSelected('ETH', 'mUSD');
151323
},
152324
);
153325
});

0 commit comments

Comments
 (0)