Skip to content

Commit b14624d

Browse files
authored
test: fix ramp unified buy e2e test (MetaMask#27818)
<!-- 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** A stuck “loading tokens” screen in the onramp-unified-buy E2E test was resolved by correcting RAMPS_COUNTRIES_RESPONSE to use supported instead of support, aligning with what ramps-controller’s `RampsService.getCountries()` filters on. The incorrect key caused all countries to be silently dropped, which prevented `RampsController.init()` from completing and left tokens stuck loading. Two Detox synchronization issues caused by animations were also resolved. The first involved a blinking cursor on the amount page (useNativeDriver: true loop), and the second involved the <Cursor> component and a 60-second cooldown timer on the OTP page (recurring setInterval/setTimeout chains). Both are now gated behind isE2E in `app/util/test/utils.js` ## **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: ## **Related issues** Fixes: ## **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] > **Low Risk** > Primarily test/mocking changes plus E2E-only gating of UI animations/timers; low production risk, but could affect OTP resend UX if `isE2E` is mis-set in non-test builds. > > **Overview** > Fixes Ramp Unified Buy E2E stability by **aligning mocked countries responses with the actual client contracts**: `/v2/regions/countries` now uses `supported`, while legacy aggregator and native deposit flows get their own country payload shapes, selected dynamically in `RAMPS_CATALOG_MOCKS`. > > Improves Detox reliability by **disabling non-idle animations/timers in E2E builds** (OTP resend countdown timer, OTP `Cursor`, and the ramp amount blinking cursor). > > Updates ramp E2E specs/fixtures accordingly (region-aware mocks for sell deeplink regression, fiat string formatting in unified buy smoke) and re-enables the previously skipped `onramp-unified-buy` smoke suite. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c946c46. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 4efb704 commit b14624d

9 files changed

Lines changed: 264 additions & 37 deletions

File tree

app/components/UI/Ramp/Views/NativeFlow/OtpCode.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { useTransakRouting } from '../../hooks/useTransakRouting';
4141
import { useRampsController } from '../../hooks/useRampsController';
4242
import { parseUserFacingError } from '../../utils/parseUserFacingError';
4343
import { OtpCodeSelectorsIDs } from './OtpCode.testIds';
44+
import { isE2E } from '../../../../../util/test/utils';
4445

4546
export interface V2OtpCodeParams {
4647
email: string;
@@ -148,6 +149,10 @@ const V2OtpCode = () => {
148149
}, [inputRef]);
149150

150151
useEffect(() => {
152+
// Skip the countdown timer in E2E: the recurring setTimeout keeps the JS
153+
// thread non-idle and causes Detox synchronization to stall indefinitely.
154+
if (isE2E) return;
155+
151156
if (resendButtonState === 'cooldown' && cooldownSeconds > 0) {
152157
timerRef.current = setTimeout(() => {
153158
setCooldownSeconds((prev) => prev - 1);
@@ -366,7 +371,9 @@ const V2OtpCode = () => {
366371
style={[styles.cellRoot, isFocused && styles.focusCell]}
367372
>
368373
<Text style={styles.cellText}>
369-
{symbol || (isFocused ? <Cursor /> : null)}
374+
{/* Cursor uses setInterval which keeps the JS thread non-idle,
375+
stalling Detox synchronization. Omit it in E2E builds. */}
376+
{symbol || (isFocused && !isE2E ? <Cursor /> : null)}
370377
</Text>
371378
</View>
372379
)}

app/components/UI/Ramp/hooks/useBlinkingCursor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useRef } from 'react';
22
import { Animated, Easing } from 'react-native';
3+
import { isE2E } from '../../../../util/test/utils';
34

45
const BLINK_DURATION = 800;
56
const INITIAL_OPACITY = 0.6;
@@ -13,7 +14,7 @@ export function useBlinkingCursor(enabled = true): Animated.Value {
1314
const cursorOpacity = useRef(new Animated.Value(INITIAL_OPACITY)).current;
1415

1516
useEffect(() => {
16-
if (process.env.NODE_ENV === 'test' || !enabled) {
17+
if (process.env.NODE_ENV === 'test' || isE2E || !enabled) {
1718
return;
1819
}
1920

tests/api-mocking/mock-responses/ramps/ramps-mocks.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { setupMockRequest } from '../../helpers/mockHelpers.ts';
33
import { MockApiEndpoint, RampsRegion } from '../../../framework/types.ts';
44
import { getDecodedProxiedURL } from '../../../smoke/notifications/utils/helpers.ts';
55
import { RAMPS_NETWORKS_RESPONSE } from './responses/ramps-networks-response.ts';
6-
import { RAMPS_COUNTRIES_RESPONSE } from './responses/ramps-countries-response.ts';
6+
import { getCountryResponseForUrl } from './responses/countries-contracts.ts';
77
import {
88
RAMPS_REGION_CONFIG_RESPONSE,
99
RAMPS_AMOUNT_RESPONSE,
@@ -82,13 +82,6 @@ export const RAMPS_CATALOG_MOCKS = async (mockServer: Mockttp) => {
8282
responseCode: 200,
8383
response: RAMPS_NETWORKS_RESPONSE,
8484
},
85-
// Countries (V1 + V2)
86-
{
87-
urlEndpoint:
88-
/^https:\/\/on-ramp-cache\.uat-api\.cx\.metamask\.io(\/v2)?\/regions\/countries\?.*$/,
89-
responseCode: 200,
90-
response: RAMPS_COUNTRIES_RESPONSE,
91-
},
9285
// Region config (payment methods, crypto/fiat options, limits)
9386
{
9487
urlEndpoint:
@@ -139,6 +132,32 @@ export const RAMPS_CATALOG_MOCKS = async (mockServer: Mockttp) => {
139132
response: RAMPS_TOP_TOKENS_RESPONSE,
140133
},
141134
]);
135+
136+
// Countries split by actual client contract:
137+
// - /v2/regions/countries => @metamask/ramps-controller Country.supported
138+
// - /regions/countries?action=deposit => NativeRampsSdk DepositRegion.supported:boolean
139+
// - /regions/countries => legacy aggregator flow (sell): OnRampSdk Country.support
140+
await mockServer
141+
.forGet('/proxy')
142+
.matching((request) => {
143+
const url = getDecodedProxiedURL(request.url);
144+
const parsedUrl = new URL(url);
145+
146+
return (
147+
parsedUrl.hostname === 'on-ramp-cache.uat-api.cx.metamask.io' &&
148+
(parsedUrl.pathname === '/v2/regions/countries' ||
149+
parsedUrl.pathname === '/regions/countries')
150+
);
151+
})
152+
.asPriority(999)
153+
.thenCallback((request) => {
154+
const url = getDecodedProxiedURL(request.url);
155+
156+
return {
157+
statusCode: 200,
158+
json: getCountryResponseForUrl(url),
159+
};
160+
});
142161
};
143162

144163
/**
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { RAMPS_COUNTRIES_RESPONSE } from './ramps-countries-response.ts';
2+
import { RAMPS_AGGREGATOR_COUNTRIES_RESPONSE } from './ramps-aggregator-countries-response.ts';
3+
import { RAMPS_DEPOSIT_COUNTRIES_RESPONSE } from './ramps-deposit-countries-response.ts';
4+
5+
export function isDepositCountriesRequest(url: string): boolean {
6+
const parsedUrl = new URL(url);
7+
8+
return (
9+
parsedUrl.pathname === '/regions/countries' &&
10+
parsedUrl.searchParams.get('action') === 'deposit'
11+
);
12+
}
13+
14+
export function getCountryResponseForUrl(url: string) {
15+
const parsedUrl = new URL(url);
16+
17+
if (parsedUrl.pathname === '/v2/regions/countries') {
18+
return RAMPS_COUNTRIES_RESPONSE;
19+
}
20+
21+
if (isDepositCountriesRequest(url)) {
22+
return RAMPS_DEPOSIT_COUNTRIES_RESPONSE;
23+
}
24+
25+
return RAMPS_AGGREGATOR_COUNTRIES_RESPONSE;
26+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Legacy Aggregator SDK countries response for GET .../regions/countries.
2+
// The old @consensys/on-ramp-sdk Country shape uses `support`, `unsupported`,
3+
// and `enableSell`, not `supported`.
4+
export const RAMPS_AGGREGATOR_COUNTRIES_RESPONSE = [
5+
{
6+
id: '/regions/pt',
7+
name: 'Portugal',
8+
emoji: '🇵🇹',
9+
currencies: ['/currencies/fiat/eur'],
10+
unsupported: false,
11+
hidden: false,
12+
states: [],
13+
support: {
14+
buy: true,
15+
sell: true,
16+
recurringBuy: true,
17+
},
18+
recommended: false,
19+
enableSell: true,
20+
detected: false,
21+
},
22+
{
23+
id: '/regions/fr',
24+
name: 'France',
25+
emoji: '🇫🇷',
26+
currencies: ['/currencies/fiat/eur'],
27+
unsupported: false,
28+
hidden: false,
29+
states: [],
30+
support: {
31+
buy: true,
32+
sell: true,
33+
recurringBuy: true,
34+
},
35+
recommended: false,
36+
enableSell: true,
37+
detected: false,
38+
},
39+
{
40+
id: '/regions/us',
41+
name: 'United States of America',
42+
emoji: '🇺🇸',
43+
currencies: ['/currencies/fiat/usd'],
44+
unsupported: false,
45+
hidden: false,
46+
states: [
47+
{
48+
id: '/regions/us-ca',
49+
name: 'California',
50+
stateId: 'CA',
51+
emoji: '🇺🇸',
52+
unsupported: false,
53+
support: {
54+
buy: true,
55+
sell: true,
56+
recurringBuy: true,
57+
},
58+
detected: false,
59+
},
60+
],
61+
support: {
62+
buy: false,
63+
sell: false,
64+
recurringBuy: false,
65+
},
66+
recommended: false,
67+
enableSell: false,
68+
detected: false,
69+
},
70+
{
71+
id: '/regions/es',
72+
name: 'Spain',
73+
emoji: '🇪🇸',
74+
currencies: ['/currencies/fiat/eur'],
75+
unsupported: false,
76+
hidden: false,
77+
states: [],
78+
support: {
79+
buy: true,
80+
sell: true,
81+
recurringBuy: true,
82+
},
83+
recommended: false,
84+
enableSell: true,
85+
detected: false,
86+
},
87+
{
88+
id: '/regions/lc',
89+
name: 'Saint Lucia',
90+
emoji: '🇱🇨',
91+
currencies: ['/currencies/fiat/xcd'],
92+
unsupported: false,
93+
hidden: false,
94+
states: [],
95+
support: {
96+
buy: true,
97+
sell: true,
98+
recurringBuy: true,
99+
},
100+
recommended: false,
101+
enableSell: true,
102+
detected: false,
103+
},
104+
];

tests/api-mocking/mock-responses/ramps/responses/ramps-countries-response.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
// Countries response — used by both V1 and V2 endpoints.
2-
// Must include `currencies` array (SDK's getDefaultFiatCurrencySync iterates it)
3-
// and `support` object (not `supported` — the aggregator SDK checks `support.buy/sell`).
1+
// Controller countries response for GET .../v2/regions/countries.
2+
// Must include `currencies` and `supported` on countries/states because
3+
// @metamask/ramps-controller RampsService.getCountries() filters on
4+
// `country.supported.buy || country.supported.sell`.
5+
// Legacy aggregator and native deposit use separate response files.
46
export const RAMPS_COUNTRIES_RESPONSE = [
57
{
68
isoCode: 'PT',
@@ -10,7 +12,7 @@ export const RAMPS_COUNTRIES_RESPONSE = [
1012
phone: { prefix: '+351', placeholder: '', template: '' },
1113
currency: 'EUR',
1214
currencies: ['/currencies/fiat/eur'],
13-
support: {
15+
supported: {
1416
buy: true,
1517
sell: true,
1618
},
@@ -25,7 +27,7 @@ export const RAMPS_COUNTRIES_RESPONSE = [
2527
phone: { prefix: '+33', placeholder: '', template: '' },
2628
currency: 'EUR',
2729
currencies: ['/currencies/fiat/eur'],
28-
support: {
30+
supported: {
2931
buy: true,
3032
sell: true,
3133
},
@@ -45,13 +47,13 @@ export const RAMPS_COUNTRIES_RESPONSE = [
4547
id: '/regions/us-ca',
4648
name: 'California',
4749
stateId: 'ca',
48-
support: {
50+
supported: {
4951
buy: true,
5052
sell: true,
5153
},
5254
},
5355
],
54-
support: {
56+
supported: {
5557
buy: false,
5658
sell: false,
5759
},
@@ -66,7 +68,22 @@ export const RAMPS_COUNTRIES_RESPONSE = [
6668
phone: { prefix: '+34', placeholder: '', template: '' },
6769
currency: 'EUR',
6870
currencies: ['/currencies/fiat/eur'],
69-
support: {
71+
supported: {
72+
buy: true,
73+
sell: true,
74+
},
75+
defaultAmount: 100,
76+
quickAmounts: [50, 100, 200, 400],
77+
},
78+
{
79+
isoCode: 'LC',
80+
id: '/regions/lc',
81+
emoji: '🇱🇨',
82+
name: 'Saint Lucia',
83+
phone: { prefix: '+1-758', placeholder: '', template: '' },
84+
currency: 'XCD',
85+
currencies: ['/currencies/fiat/xcd'],
86+
supported: {
7087
buy: true,
7188
sell: true,
7289
},
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Native Deposit SDK countries response for GET .../regions/countries?action=deposit.
2+
// DepositRegion uses a boolean `supported` field.
3+
export const RAMPS_DEPOSIT_COUNTRIES_RESPONSE = [
4+
{
5+
isoCode: 'PT',
6+
flag: '🇵🇹',
7+
name: 'Portugal',
8+
phone: { prefix: '+351', placeholder: '', template: '' },
9+
currency: 'EUR',
10+
supported: true,
11+
},
12+
{
13+
isoCode: 'FR',
14+
flag: '🇫🇷',
15+
name: 'France',
16+
phone: { prefix: '+33', placeholder: '', template: '' },
17+
currency: 'EUR',
18+
supported: true,
19+
},
20+
{
21+
isoCode: 'US',
22+
flag: '🇺🇸',
23+
name: 'United States',
24+
phone: { prefix: '+1', placeholder: '', template: '' },
25+
currency: 'USD',
26+
supported: true,
27+
},
28+
{
29+
isoCode: 'ES',
30+
flag: '🇪🇸',
31+
name: 'Spain',
32+
phone: { prefix: '+34', placeholder: '', template: '' },
33+
currency: 'EUR',
34+
supported: true,
35+
},
36+
{
37+
isoCode: 'LC',
38+
flag: '🇱🇨',
39+
name: 'Saint Lucia',
40+
phone: { prefix: '+1-758', placeholder: '', template: '' },
41+
currency: 'XCD',
42+
supported: true,
43+
},
44+
];

0 commit comments

Comments
 (0)