Skip to content

Commit

Permalink
test: ensure bridge button handles clicks according to feature flags (#…
Browse files Browse the repository at this point in the history
…25812)

<!--
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**

Adds e2e tests for new cross-chain swaps routes

<!--
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/25793?quickstart=1)

## **Related issues**

Follow-up to #25811

## **Manual testing steps**

1. New tests should run successfully

## **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**

- [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/develop/.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/develop/.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.
  • Loading branch information
micaelae authored Aug 14, 2024
1 parent c5c13d5 commit 2707dc3
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 96 deletions.
4 changes: 2 additions & 2 deletions shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [
CHAIN_IDS.BASE,
];

const BRIDGE_DEV_API_BASE_URL = 'https://bridge.dev-api.cx.metamask.io';
const BRIDGE_PROD_API_BASE_URL = 'https://bridge.api.cx.metamask.io';
export const BRIDGE_DEV_API_BASE_URL = 'https://bridge.dev-api.cx.metamask.io';
export const BRIDGE_PROD_API_BASE_URL = 'https://bridge.api.cx.metamask.io';
export const BRIDGE_API_BASE_URL = process.env.BRIDGE_USE_DEV_APIS
? BRIDGE_DEV_API_BASE_URL
: BRIDGE_PROD_API_BASE_URL;
Expand Down
57 changes: 57 additions & 0 deletions test/e2e/tests/bridge/bridge-button-opens-portfolio.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Suite } from 'mocha';
import { withFixtures, logInWithBalanceValidation } from '../../helpers';
import { Ganache } from '../../seeder/ganache';
import { Driver } from '../../webdriver/driver';
import GanacheContractAddressRegistry from '../../seeder/ganache-contract-address-registry';
import { BridgePage, getBridgeFixtures } from './bridge-test-utils';

describe('Click bridge button @no-mmi', function (this: Suite) {
it('loads portfolio tab from wallet overview when flag is turned off', async function () {
await withFixtures(
getBridgeFixtures(this.test?.fullTitle()),
async ({
driver,
ganacheServer,
}: {
driver: Driver;
ganacheServer: Ganache;
}) => {
const bridgePage = new BridgePage(driver);
await logInWithBalanceValidation(driver, ganacheServer);
await bridgePage.navigateToBridgePage();
await bridgePage.verifyPortfolioTab(2);
},
);
});

it('loads portfolio tab from asset overview when flag is turned off', async function () {
await withFixtures(
// withErc20 param is false, as we test it manually below
getBridgeFixtures(this.test?.fullTitle(), undefined, false),
async ({
driver,
ganacheServer,
contractRegistry,
}: {
driver: Driver;
ganacheServer: Ganache;
contractRegistry: GanacheContractAddressRegistry;
}) => {
const bridgePage = new BridgePage(driver);
await logInWithBalanceValidation(driver, ganacheServer);

// ETH
await bridgePage.navigateToAssetPage(contractRegistry, 'ETH', false);
await bridgePage.navigateToBridgePage('coin-overview');
await bridgePage.verifyPortfolioTab(2);

await bridgePage.reloadHome();

// TST
await bridgePage.navigateToAssetPage(contractRegistry, 'TST');
await bridgePage.navigateToBridgePage('token-overview');
await bridgePage.verifyPortfolioTab(3);
},
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { Ganache } from '../../seeder/ganache';
import { Driver } from '../../webdriver/driver';
import { BridgePage, getBridgeFixtures } from './bridge-test-utils';

describe('Click bridge button from wallet overview @no-mmi', function (this: Suite) {
it('loads portfolio tab when flag is turned off', async function () {
describe('Click bridge button @no-mmi', function (this: Suite) {
it('loads placeholder swap route from wallet overview when flag is turned on', async function () {
await withFixtures(
getBridgeFixtures(this.test?.fullTitle()),
getBridgeFixtures(this.test?.fullTitle(), { 'extension-support': true }),
async ({
driver,
ganacheServer,
Expand All @@ -17,10 +17,8 @@ describe('Click bridge button from wallet overview @no-mmi', function (this: Sui
}) => {
const bridgePage = new BridgePage(driver);
await logInWithBalanceValidation(driver, ganacheServer);
await bridgePage.load();
await bridgePage.verifyPortfolioTab(
'https://portfolio.metamask.io/bridge?metametricsId=null&metricsEnabled=false&marketingEnabled=false',
);
await bridgePage.navigateToBridgePage();
await bridgePage.verifySwapPage(1);
},
);
});
Expand Down
41 changes: 0 additions & 41 deletions test/e2e/tests/bridge/bridge-click-from-asset-overview.spec.ts

This file was deleted.

142 changes: 97 additions & 45 deletions test/e2e/tests/bridge/bridge-test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { strict as assert } from 'assert';
import { Mockttp } from 'mockttp';
import { Browser } from 'selenium-webdriver';
import FixtureBuilder from '../../fixture-builder';
import { clickNestedButton, generateGanacheOptions } from '../../helpers';
import {
WINDOW_TITLES,
clickNestedButton,
generateGanacheOptions,
} from '../../helpers';
BRIDGE_CLIENT_ID,
BRIDGE_DEV_API_BASE_URL,
BRIDGE_PROD_API_BASE_URL,
} from '../../../../shared/constants/bridge';
import { SMART_CONTRACTS } from '../../seeder/smart-contracts';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import GanacheContractAddressRegistry from '../../seeder/ganache-contract-address-registry';
import { Driver } from '../../webdriver/driver';
import { FeatureFlagResponse } from '../../../../ui/pages/bridge/bridge.util';
import { isManifestV3 } from '../../../../shared/modules/mv3.utils';
import {
DEFAULT_FEATURE_FLAGS_RESPONSE,
ETH_CONVERSION_RATE_USD,
LOCATOR,
MOCK_CURRENCY_RATES,
} from './constants';

const IS_FIREFOX = process.env.SELENIUM_BROWSER === Browser.FIREFOX;

export class BridgePage {
driver: Driver;
Expand All @@ -18,7 +30,11 @@ export class BridgePage {
this.driver = driver;
}

load = async (
reloadHome = async () => {
await this.driver.navigate();
};

navigateToBridgePage = async (
location:
| 'wallet-overview'
| 'coin-overview'
Expand All @@ -43,78 +59,113 @@ export class BridgePage {
);
};

reloadHome = async (shouldCloseWindow = true) => {
if (shouldCloseWindow) {
await this.driver.closeWindow();
await this.driver.delay(2000);
await this.driver.switchToWindowWithTitle(
WINDOW_TITLES.ExtensionInFullScreenView,
);
}
await this.driver.navigate();
};

loadAssetPage = async (
navigateToAssetPage = async (
contractRegistry: GanacheContractAddressRegistry,
symbol?: string,
symbol: string,
shouldImportToken = true,
) => {
let tokenListItem;

if (symbol) {
if (shouldImportToken) {
// Import token
const contractAddress = await contractRegistry.getContractAddress(
SMART_CONTRACTS.HST,
);
await this.driver.clickElement({
text: 'Import tokens',
tag: 'button',
});
await clickNestedButton(this.driver, 'Import tokens');
await clickNestedButton(this.driver, 'Custom token');
await this.driver.fill(
'[data-testid="import-tokens-modal-custom-address"]',
LOCATOR.MM_IMPORT_TOKENS_MODAL('custom-address'),
contractAddress,
);
await this.driver.fill(
LOCATOR.MM_IMPORT_TOKENS_MODAL('custom-symbol'),
symbol,
);
await this.driver.waitForSelector(
'[data-testid="import-tokens-modal-custom-decimals"]',
LOCATOR.MM_IMPORT_TOKENS_MODAL('custom-decimals'),
);
await this.driver.clickElement({
text: 'Next',
tag: 'button',
});
await clickNestedButton(this.driver, 'Next');
await this.driver.clickElement(
LOCATOR.MM_IMPORT_TOKENS_MODAL('import-button'),
);
await this.driver.assertElementNotPresent(
'[data-testid="import-tokens-modal-import-button"]',
);
await this.driver.delay(2000);
tokenListItem = await this.driver.findElement({ text: symbol });
await this.driver.clickElement({ text: symbol });
} else {
tokenListItem = await this.driver.findElement(
'[data-testid="multichain-token-list-button"]',
);
await this.driver.clickElement({
css: '[data-testid="multichain-token-list-button"]',
text: symbol,
});
}
await tokenListItem.click();
await this.driver.delay(2000);
assert.ok((await this.driver.getCurrentUrl()).includes('asset'));
};

verifyPortfolioTab = async (url: string) => {
verifyPortfolioTab = async (expectedHandleCount: number) => {
await this.driver.delay(4000);
await this.driver.switchToWindowWithTitle('MetaMask Portfolio - Bridge');
assert.equal(await this.driver.getCurrentUrl(), url);
assert.equal(
(await this.driver.getAllWindowHandles()).length,
IS_FIREFOX || !isManifestV3
? expectedHandleCount
: expectedHandleCount + 1,
);
assert.match(
await this.driver.getCurrentUrl(),
/^https:\/\/portfolio\.metamask\.io\/bridge/u,
);
};

verifySwapPage = async () => {
const currentUrl = await this.driver.getCurrentUrl();
assert.ok(currentUrl.includes('cross-chain/swaps'));
verifySwapPage = async (expectedHandleCount: number) => {
await this.driver.delay(4000);
await this.driver.waitForSelector({
css: '.bridge__title',
text: 'Bridge',
});
assert.equal(
(await this.driver.getAllWindowHandles()).length,
IS_FIREFOX || !isManifestV3
? expectedHandleCount
: expectedHandleCount + 1,
);
assert.match(await this.driver.getCurrentUrl(), /.+cross-chain\/swaps/u);
};
}

const mockServer =
(featureFlagOverrides: Partial<FeatureFlagResponse>) =>
async (mockServer_: Mockttp) => {
const featureFlagMocks = [
`${BRIDGE_DEV_API_BASE_URL}/getAllFeatureFlags`,
`${BRIDGE_PROD_API_BASE_URL}/getAllFeatureFlags`,
].map(
async (url) =>
await mockServer_
.forGet(url)
.withHeaders({ 'X-Client-Id': BRIDGE_CLIENT_ID })
.always()
.thenCallback(() => {
return {
statusCode: 200,
json: {
...DEFAULT_FEATURE_FLAGS_RESPONSE,
...featureFlagOverrides,
},
};
}),
);
return Promise.all(featureFlagMocks);
};

export const getBridgeFixtures = (
title?: string,
testSpecificMock?: (server: Mockttp) => Promise<void>,
featureFlags: Partial<FeatureFlagResponse> = {},
withErc20: boolean = true,
) => {
const fixtureBuilder = new FixtureBuilder({
inputChainId: CHAIN_IDS.MAINNET,
}).withNetworkControllerOnMainnet();
})
.withNetworkControllerOnMainnet()
.withCurrencyController(MOCK_CURRENCY_RATES);

if (withErc20) {
fixtureBuilder.withTokensControllerERC20();
Expand All @@ -125,8 +176,9 @@ export const getBridgeFixtures = (
// openDevToolsForTabs: true,
},
fixtures: fixtureBuilder.build(),
testSpecificMock,
testSpecificMock: mockServer(featureFlags),
smartContract: SMART_CONTRACTS.HST,
ethConversionInUsd: ETH_CONVERSION_RATE_USD,
ganacheOptions: generateGanacheOptions({
hardfork: 'london',
chain: { chainId: CHAIN_IDS.MAINNET },
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/tests/bridge/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FeatureFlagResponse } from '../../../../ui/pages/bridge/bridge.util';

export const DEFAULT_FEATURE_FLAGS_RESPONSE: FeatureFlagResponse = {
'extension-support': false,
};

export const LOCATOR = {
MM_IMPORT_TOKENS_MODAL: (suffix: string) =>
`[data-testid="import-tokens-modal-${suffix}"]`,
};

export const ETH_CONVERSION_RATE_USD = 3010;
export const MOCK_CURRENCY_RATES = {
currencyRates: {
ETH: {
conversionDate: 1665507609.0,
conversionRate: ETH_CONVERSION_RATE_USD,
usdConversionRate: ETH_CONVERSION_RATE_USD,
},
},
};
2 changes: 1 addition & 1 deletion ui/pages/bridge/bridge.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ enum BridgeFlag {
EXTENSION_SUPPORT = 'extension-support',
}

type FeatureFlagResponse = {
export type FeatureFlagResponse = {
[BridgeFlag.EXTENSION_SUPPORT]: boolean;
};
// End of copied types
Expand Down

0 comments on commit 2707dc3

Please sign in to comment.