Skip to content

Commit 16ee0cd

Browse files
authored
test: Add e2e test for gas less swap (MetaMask#23507)
<!-- 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** Add e2e test for the gas less swap <!-- 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? --> ## **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** Fixes: https://consensyssoftware.atlassian.net/browse/MMQA-1178 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **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 Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/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 a gasless ETH→MUSD swap e2e test verifying the "Included" network fee, with mocks and QuoteView/selectors support for Max and Included. > > - **Tests**: > - Add `e2e/specs/swaps/gasless-swap.spec.ts` to validate gasless ETH→MUSD swap shows `Included` under network fee and uses `Max` amount. > - **Mocks/Constants**: > - Add `GASLESS_SWAP_QUOTES_ETH_MUSD` in `e2e/specs/swaps/helpers/constants.ts` and wire via API mock. > - **Page Objects**: > - Update `e2e/pages/swaps/QuoteView.ts` with `maxLink`, `includedLabel`, and `tapMax()` helpers. > - **Selectors**: > - Extend `QuoteViewSelectorText` with `MAX` and `INCLUDED`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 36da9e1. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent c8c5ac4 commit 16ee0cd

4 files changed

Lines changed: 199 additions & 0 deletions

File tree

e2e/pages/swaps/QuoteView.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ class QuoteView {
4444
return Matchers.getElementByText(QuoteViewSelectorText.NETWORK_FEE);
4545
}
4646

47+
get maxLink(): DetoxElement {
48+
return Matchers.getElementByText(QuoteViewSelectorText.MAX);
49+
}
50+
51+
get includedLabel(): DetoxElement {
52+
return Matchers.getElementByText(QuoteViewSelectorText.INCLUDED);
53+
}
54+
4755
token(chainId: string, symbol: string): Detox.NativeElement {
4856
const elementId = `asset-${chainId}-${symbol}`;
4957
return element(by.id(elementId)).atIndex(0);
@@ -136,6 +144,12 @@ class QuoteView {
136144
elemDescription: 'Cancel swap',
137145
});
138146
}
147+
148+
async tapMax(): Promise<void> {
149+
await Gestures.waitAndTap(this.maxLink, {
150+
elemDescription: 'Tap Max link to use maximum balance',
151+
});
152+
}
139153
}
140154

141155
export default new QuoteView();

e2e/selectors/swaps/QuoteView.selectors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export const QuoteViewSelectorText = {
99
SELECT_ALL: enContent.bridge.see_all,
1010
CANCEL: 'Cancel',
1111
FEE_DISCLAIMER: enContent.bridge.fee_disclaimer,
12+
MAX: enContent.bridge.max,
13+
INCLUDED: enContent.bridge.included,
1214
};
1315

1416
export const QuoteViewSelectorIDs = {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { withFixtures } from '../../framework/fixtures/FixtureHelper';
2+
import { LocalNode, LocalNodeType } from '../../framework/types';
3+
import FixtureBuilder from '../../framework/fixtures/FixtureBuilder';
4+
import Assertions from '../../framework/Assertions';
5+
import WalletView from '../../pages/wallet/WalletView';
6+
import { SmokeTrade } from '../../tags';
7+
import { loginToApp } from '../../viewHelper';
8+
import { logger } from '../../framework/logger';
9+
import { AnvilPort } from '../../framework/fixtures/FixtureUtils';
10+
import { AnvilManager } from '../../seeder/anvil-manager';
11+
import QuoteView from '../../pages/swaps/QuoteView';
12+
import { setupMockRequest } from '../../api-mocking/helpers/mockHelpers';
13+
import { GASLESS_SWAP_QUOTES_ETH_MUSD } from './helpers/constants';
14+
15+
describe(SmokeTrade('Gasless Swap - '), (): void => {
16+
const chainId = '0x1';
17+
18+
beforeEach(async (): Promise<void> => {
19+
jest.setTimeout(120000);
20+
});
21+
22+
it('displays included label for gasless ETH to MUSD swap quote', async (): Promise<void> => {
23+
await withFixtures(
24+
{
25+
fixture: ({ localNodes }: { localNodes?: LocalNode[] }) => {
26+
const node = localNodes?.[0] as unknown as AnvilManager;
27+
const rpcPort =
28+
node instanceof AnvilManager
29+
? (node.getPort() ?? AnvilPort())
30+
: undefined;
31+
32+
return new FixtureBuilder()
33+
.withNetworkController({
34+
providerConfig: {
35+
chainId,
36+
rpcUrl: `http://localhost:${rpcPort ?? AnvilPort()}`,
37+
type: 'custom',
38+
nickname: 'Localhost',
39+
ticker: 'ETH',
40+
},
41+
})
42+
.withMetaMetricsOptIn()
43+
.withPreferencesController({
44+
smartTransactionsOptInStatus: true,
45+
})
46+
.build();
47+
},
48+
localNodeOptions: [
49+
{
50+
type: LocalNodeType.anvil,
51+
options: {
52+
chainId: 1,
53+
},
54+
},
55+
],
56+
testSpecificMock: async (mockServer) => {
57+
// Mock ETH->MUSD quote (gasless swap)
58+
await setupMockRequest(mockServer, {
59+
requestMethod: 'GET',
60+
url: /getQuote.*destTokenAddress=0xacA92E438df0B2401fF60dA7E4337B687a2435DA/i,
61+
response: GASLESS_SWAP_QUOTES_ETH_MUSD,
62+
responseCode: 200,
63+
});
64+
},
65+
restartDevice: true,
66+
endTestfn: async () => {
67+
logger.debug('Gasless swap test completed');
68+
},
69+
},
70+
async () => {
71+
await loginToApp();
72+
await WalletView.tapWalletSwapButton();
73+
await device.disableSynchronization();
74+
await Assertions.expectElementToBeVisible(QuoteView.selectAmountLabel, {
75+
description: 'Swap amount selection visible',
76+
});
77+
78+
// Tap Max to use maximum balance
79+
await QuoteView.tapMax();
80+
81+
// Verify network fee shows "Included" for gasless swap
82+
await Assertions.expectElementToBeVisible(QuoteView.networkFeeLabel, {
83+
timeout: 60000,
84+
description: 'Network fee label visible',
85+
});
86+
87+
await Assertions.expectElementToBeVisible(QuoteView.includedLabel, {
88+
timeout: 10000,
89+
description: 'Gas included in quote',
90+
});
91+
},
92+
);
93+
});
94+
});

e2e/specs/swaps/helpers/constants.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,3 +711,92 @@ export const GET_TOP_ASSETS_BASE_RESPONSE = [
711711
{ address: '0x0000000000000000000000000000000000000000', symbol: 'ETH' },
712712
{ address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', symbol: 'USDC' },
713713
];
714+
715+
export const GASLESS_SWAP_QUOTES_ETH_MUSD = [
716+
{
717+
quote: {
718+
requestId:
719+
'0xf75136205d474cdedb32d1c6f6811fe289b95678aa74679b9abb69252ed0b266',
720+
bridgeId: 'openocean',
721+
srcChainId: 1,
722+
destChainId: 1,
723+
aggregator: 'openocean',
724+
aggregatorType: 'AGG',
725+
srcAsset: {
726+
address: '0x0000000000000000000000000000000000000000',
727+
chainId: 1,
728+
assetId: 'eip155:1/slip44:60',
729+
symbol: 'ETH',
730+
decimals: 18,
731+
name: 'Ethereum',
732+
coingeckoId: 'ethereum',
733+
aggregators: [],
734+
occurrences: 100,
735+
iconUrl:
736+
'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/slip44/60.png',
737+
metadata: {},
738+
},
739+
srcTokenAmount: '991250000000000000',
740+
destAsset: {
741+
address: '0xacA92E438df0B2401fF60dA7E4337B687a2435DA',
742+
chainId: 1,
743+
assetId: 'eip155:1/erc20:0xaca92e438df0b2401ff60da7e4337b687a2435da',
744+
symbol: 'MUSD',
745+
decimals: 6,
746+
name: 'MetaMask USD',
747+
coingeckoId: 'metamask-usd',
748+
aggregators: ['metamask', 'liFi', 'socket', 'rubic', 'rango'],
749+
occurrences: 5,
750+
iconUrl:
751+
'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/erc20/0xaca92e438df0b2401ff60da7e4337b687a2435da.png',
752+
metadata: { storage: {} },
753+
},
754+
destTokenAmount: '3839447765',
755+
minDestTokenAmount: '3762658809',
756+
walletAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3',
757+
destWalletAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3',
758+
gasIncluded: true,
759+
gasIncluded7702: false,
760+
feeData: {
761+
metabridge: {
762+
amount: '8750000000000000',
763+
asset: {
764+
address: '0x0000000000000000000000000000000000000000',
765+
chainId: 1,
766+
assetId: 'eip155:1/slip44:60',
767+
symbol: 'ETH',
768+
decimals: 18,
769+
name: 'Ethereum',
770+
coingeckoId: 'ethereum',
771+
aggregators: [],
772+
occurrences: 100,
773+
iconUrl:
774+
'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/slip44/60.png',
775+
metadata: {},
776+
},
777+
quoteBpsFee: 87.5,
778+
baseBpsFee: 87.5,
779+
},
780+
},
781+
bridges: ['openocean'],
782+
protocols: ['openocean'],
783+
steps: [],
784+
slippage: 2,
785+
priceData: {
786+
totalFromAmountUsd: '3865.21',
787+
totalToAmountUsd: '3832.3211880033778',
788+
priceImpact: '0.008508932760864812',
789+
totalFeeAmountUsd: '33.8205875',
790+
},
791+
},
792+
trade: {
793+
chainId: 1,
794+
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
795+
from: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3',
796+
value: '0xde0b6b3a7640000',
797+
data: '0x5f575529000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f70656e4f6365616e46656544796e616d6963000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000aca92e438df0b2401ff60da7e4337b687a2435da0000000000000000000000000000000000000000000000000dc1a09f859b200000000000000000000000000000000000000000000000000000000000e0123b42',
798+
gasLimit: 448721,
799+
},
800+
estimatedProcessingTimeInSeconds: 0,
801+
},
802+
];

0 commit comments

Comments
 (0)