Skip to content

test: Adding e2e tests for bridge #15067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions e2e/fixtures/fixture-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ class FixtureBuilder {
return this;
}

withGanacheNetwork() {
withGanacheNetwork(chainId = '0x539') {
const fixtures = this.fixture.state.engine.backgroundState;

// Generate a unique key for the new network client ID
Expand All @@ -865,7 +865,7 @@ class FixtureBuilder {

// Define the Ganache network configuration
const ganacheNetworkConfig = {
chainId: '0x539',
chainId,
rpcEndpoints: [
{
networkClientId: newNetworkClientId,
Expand All @@ -882,7 +882,7 @@ class FixtureBuilder {
};

// Add the new Ganache network configuration
fixtures.NetworkController.networkConfigurationsByChainId['0x539'] =
fixtures.NetworkController.networkConfigurationsByChainId[chainId] =
ganacheNetworkConfig;

// Update selectedNetworkClientId to the new network client ID
Expand Down
1 change: 1 addition & 0 deletions e2e/jest.e2e.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
testMatch: [
'<rootDir>/e2e/specs/*.spec.js',
'<rootDir>/e2e/specs/*/*.spec.js',
'<rootDir>/e2e/specs/*/*.spec.ts',
'<rootDir>/e2e/specs/*/*/*.spec.js',
'<rootDir>/e2e/specs/*/*/*/*.spec.js',
],
Expand Down
64 changes: 64 additions & 0 deletions e2e/pages/Bridge/QuoteView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Matchers from '../../utils/Matchers';
import Gestures from '../../utils/Gestures';
import {
QuoteViewSelectorIDs,
QuoteViewSelectorText,
} from '../../selectors/Bridge/QuoteView.selectors';

class QuoteView {
get continueButton() {
return Matchers.getElementByText(QuoteViewSelectorText.CONTINUE);
}

get bridgeTo() {
return Matchers.getElementByText(QuoteViewSelectorText.BRIDGE_TO);
}

get searchToken() {
return Matchers.getElementByID(QuoteViewSelectorIDs.TOKEN_SEARCH_INPUT);
}

get quotesLabel() {
return Matchers.getElementByID(QuoteViewSelectorText.QUOTES);
}

token(symbol) {
return Matchers.getElementByID(`asset-${symbol}`);
}


async enterBridgeAmount(amount) {
for (let idx = 0; idx < amount.length; idx++) {
const element = Matchers.getElementByText(amount[idx]);
await Gestures.waitAndTap(element);
}
}

async tapSearchToken() {
await Gestures.waitAndTap(this.searchToken);
}

async tapBridgeTo() {
await Gestures.waitAndTap(this.bridgeTo);
}

async selectNetwork(network) {
const element = Matchers.getElementByText(network);
await Gestures.waitAndTap(element);
}

async typeSearchToken(symbol) {
await Gestures.typeTextAndHideKeyboard(this.searchToken, symbol);
}

async selectToken(symbol) {
const element = await this.token(symbol);
await Gestures.waitAndTap(element);
}

async tapContinue() {
await Gestures.waitAndTap(this.continueButton);
}
}

export default new QuoteView();
11 changes: 11 additions & 0 deletions e2e/pages/Transactions/ActivitiesView.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class ActivitiesView {
return title;
}

generateBridgeActivityLabel(destNetwork) {
let title = ActivitiesViewSelectorsText.BRIDGE;
title = title.replace('{{chainName}}', destNetwork);
return title;
}

generateApprovedTokenActivityLabel(sourceToken) {
let title = ActivitiesViewSelectorsText.APPROVE;
title = title.replace('{{sourceToken}}', sourceToken);
Expand All @@ -60,6 +66,11 @@ class ActivitiesView {
);
}

bridgeActivityTitle(destNetwork) {
return Matchers.getElementByText(
this.generateBridgeActivityLabel(destNetwork),
);
}
tokenApprovalActivity(sourceToken) {
return Matchers.getElementByText(
this.generateApprovedTokenActivityLabel(sourceToken),
Expand Down
10 changes: 10 additions & 0 deletions e2e/pages/wallet/AddAccountBottomSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ class AddAccountBottomSheet {
);
}

get createSolanaAccountButton() {
return Matchers.getElementByID(
AddAccountBottomSheetSelectorsIDs.ADD_SOLANA_ACCOUNT_BUTTON,
);
}

get importSrpButton() {
return Matchers.getElementByID(
AddAccountBottomSheetSelectorsIDs.IMPORT_SRP_BUTTON,
Expand All @@ -32,6 +38,10 @@ class AddAccountBottomSheet {
async tapImportSrp() {
await Gestures.waitAndTap(this.importSrpButton);
}

async tapAddSolanaAccount() {
await Gestures.waitAndTap(this.createSolanaAccountButton);
}
}

export default new AddAccountBottomSheet();
8 changes: 8 additions & 0 deletions e2e/pages/wallet/TokenOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class TokenOverview {
return Matchers.getElementByID(TokenOverviewSelectorsIDs.SWAP_BUTTON);
}

get bridgeButton() {
return Matchers.getElementByID(TokenOverviewSelectorsIDs.BRIDGE_BUTTON);
}

get claimButton() {
return Matchers.getElementByID(TokenOverviewSelectorsIDs.CLAIM_BUTTON);
}
Expand Down Expand Up @@ -93,6 +97,10 @@ class TokenOverview {
await Gestures.waitAndTap(this.actionSheetSendButton);
}

async tapBridgeButton() {
await Gestures.waitAndTap(this.bridgeButton);
}

async tapSwapButton() {
await Gestures.waitAndTap(this.swapButton);
}
Expand Down
8 changes: 8 additions & 0 deletions e2e/pages/wallet/WalletActionsBottomSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class WalletActionsBottomSheet {
return Matchers.getElementByID(WalletActionsBottomSheetSelectorsIDs.SWAP_BUTTON);
}

get bridgeButton() {
return Matchers.getElementByID(WalletActionsBottomSheetSelectorsIDs.BRIDGE_BUTTON);
}

get buyButton() {
return Matchers.getElementByID(WalletActionsBottomSheetSelectorsIDs.BUY_BUTTON);
}
Expand All @@ -37,6 +41,10 @@ class WalletActionsBottomSheet {
await Gestures.waitAndTap(this.swapButton);
}

async tapBridgeButton() {
await Gestures.waitAndTap(this.bridgeButton);
}

async tapBuyButton() {
await Gestures.waitAndTap(this.buyButton);
}
Expand Down
12 changes: 12 additions & 0 deletions e2e/selectors/Bridge/QuoteView.selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import enContent from '../../../locales/languages/en.json';

export const QuoteViewSelectorText = {
QUOTES: enContent.bridge.quote,
BRIDGE_TO: 'Bridge to',
CONTINUE: 'Continue',
};

export const QuoteViewSelectorIDs = {
TOKEN_SEARCH_INPUT: 'bridge-token-search-input',
EXPAND_QUOTE_DETAILS: 'expand-quote-details',
};
1 change: 1 addition & 0 deletions e2e/selectors/Transactions/ActivitiesView.selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ActivitiesViewSelectorsText = {
SENT_TOKENS_MESSAGE_TEXT: (unit) => getSentUnitMessage(unit),
SET_APPROVAL_FOR_ALL_METHOD: enContent.transactions.set_approval_for_all,
SWAP: enContent.swaps.transaction_label.swap,
BRIDGE: enContent.bridge_transaction_details.bridge_to_chain,
APPROVE: enContent.swaps.transaction_label.approve,
TITLE: enContent.transactions_view.title,
STAKE_DEPOSIT: enContent.transactions.tx_review_staking_deposit,
Expand Down
171 changes: 171 additions & 0 deletions e2e/specs/bridge/bridge-functionality.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
'use strict';
import { loginToApp } from '../../viewHelper';
import TabBarComponent from '../../pages/wallet/TabBarComponent';
import {
loadFixture,
startFixtureServer,
stopFixtureServer,
} from '../../fixtures/fixture-helper';
import QuoteView from '../../pages/Bridge/QuoteView';
import FixtureBuilder from '../../fixtures/fixture-builder';
import FixtureServer from '../../fixtures/fixture-server';
import WalletView from '../../pages/wallet/WalletView';
import TestHelpers from '../../helpers';
import { SmokeTrade } from '../../tags';
import Assertions from '../../utils/Assertions';
import Ganache from '../../../app/util/test/ganache';
import AdvancedSettingsView from '../../pages/Settings/AdvancedView';
import SettingsView from '../../pages/Settings/SettingsView';
import WalletActionsBottomSheet from '../../pages/wallet/WalletActionsBottomSheet';
import ActivitiesView from '../../pages/Transactions/ActivitiesView';
import { ActivitiesViewSelectorsText } from '../../selectors/Transactions/ActivitiesView.selectors';
import AddNewHdAccountComponent from '../../pages/wallet/MultiSrp/AddAccountToSrp/AddNewHdAccountComponent';
import AccountListBottomSheet from '../../pages/wallet/AccountListBottomSheet.js';
import AddAccountBottomSheet from '../../pages/wallet/AddAccountBottomSheet';
import NetworkEducationModal from '../../pages/Network/NetworkEducationModal.js';
import NetworkListModal from '../../pages/Network/NetworkListModal.js';
import { getFixturesServerPort, getMockServerPort } from '../../fixtures/utils';
import { startMockServer } from './bridge-mocks';
import { stopMockServer } from '../../api-mocking/mock-server';
import { localNodeOptions, testSpecificMock } from './constants';
import { Mockttp } from 'mockttp';

const fixtureServer = new FixtureServer();

describe(SmokeTrade('Bridge functionality'), () => {
const FIRST_ROW = 0;
let mockServer: Mockttp;
let localNode: Ganache;

beforeAll(async () => {
jest.setTimeout(120000);
localNode = new Ganache();
await localNode.start(localNodeOptions);
await TestHelpers.reverseServerPort();
const fixture = new FixtureBuilder().withGanacheNetwork('0x1').build();
await startFixtureServer(fixtureServer);
await loadFixture(fixtureServer, { fixture });
const mockServerPort = getMockServerPort();
mockServer = await startMockServer(testSpecificMock, mockServerPort);
await TestHelpers.launchApp({
permissions: { notifications: 'YES' },
launchArgs: {
fixtureServerPort: `${getFixturesServerPort()}`,
mockServerPort: `${mockServerPort}`,
},
});

await TestHelpers.delay(5000);
await loginToApp();
});

afterAll(async () => {
if (mockServer) await stopMockServer(mockServer);
await stopFixtureServer(fixtureServer);
if (localNode) await localNode.quit();
});

it('should bridge ETH (Mainnet) to SOL (Solana)', async () => {
await TabBarComponent.tapWallet();
await WalletView.tapIdenticon();
await Assertions.checkIfVisible(AccountListBottomSheet.accountList);
await AccountListBottomSheet.tapAddAccountButton();
await AddAccountBottomSheet.tapAddSolanaAccount();
await AddNewHdAccountComponent.tapConfirm();
await Assertions.checkIfVisible(NetworkEducationModal.container);
await NetworkEducationModal.tapGotItButton();
await Assertions.checkIfNotVisible(NetworkEducationModal.container as Promise<Detox.IndexableNativeElement>);
await Assertions.checkIfVisible(WalletView.container);

await WalletView.tapNetworksButtonOnNavBar();
await NetworkListModal.changeNetworkTo('Localhost', false);
await Assertions.checkIfVisible(NetworkEducationModal.container);
await NetworkEducationModal.tapGotItButton();
await Assertions.checkIfNotVisible(NetworkEducationModal.container as Promise<Detox.IndexableNativeElement>);
await Assertions.checkIfVisible(WalletView.container);

await TabBarComponent.tapActions();
await TestHelpers.delay(500);
await WalletActionsBottomSheet.tapBridgeButton();
await device.disableSynchronization();
await QuoteView.enterBridgeAmount('1');
await QuoteView.tapBridgeTo();
await TestHelpers.delay(1000);
await QuoteView.selectNetwork('Solana');
await Assertions.checkIfVisible(QuoteView.token('SOL'));
await QuoteView.selectToken('SOL');
await Assertions.checkIfVisible(QuoteView.quotesLabel);
await Assertions.checkIfVisible(QuoteView.continueButton);
await QuoteView.tapContinue();

// Check the bridge activity completed
await TabBarComponent.tapActivity();
await Assertions.checkIfVisible(ActivitiesView.title);
await Assertions.checkIfVisible(
ActivitiesView.bridgeActivityTitle('Solana'),
);
await Assertions.checkIfElementToHaveText(
ActivitiesView.transactionStatus(FIRST_ROW) as Promise<Detox.IndexableNativeElement>,
ActivitiesViewSelectorsText.CONFIRM_TEXT,
30000,
);
});

it('should bridge ETH (Mainnet) to ETH (Base Network)', async () => {
await Assertions.checkIfVisible(WalletView.container);

await TabBarComponent.tapActions();
await WalletActionsBottomSheet.tapBridgeButton();
await device.disableSynchronization();
await QuoteView.enterBridgeAmount('1');
await QuoteView.tapBridgeTo();
await TestHelpers.delay(1000);
await QuoteView.selectNetwork('Base');
await Assertions.checkIfVisible(QuoteView.token('ETH'));
await QuoteView.selectToken('ETH');
await Assertions.checkIfVisible(QuoteView.quotesLabel);
await Assertions.checkIfVisible(QuoteView.continueButton);
await QuoteView.tapContinue();

// Check the bridge activity completed
await TabBarComponent.tapActivity();
await Assertions.checkIfVisible(ActivitiesView.title);
await Assertions.checkIfVisible(ActivitiesView.bridgeActivityTitle('Base'));
await Assertions.checkIfElementToHaveText(
ActivitiesView.transactionStatus(FIRST_ROW) as Promise<Detox.IndexableNativeElement>,
ActivitiesViewSelectorsText.CONFIRM_TEXT,
30000,
);
});

it('should bridge ETH (Mainnet) to ETH (BNB Smart Chain Mainnet)', async () => {
await Assertions.checkIfVisible(WalletView.container);
await TabBarComponent.tapSettings();
await SettingsView.tapAdvancedTitle();
await AdvancedSettingsView.tapSmartTransactionSwitch();
await TabBarComponent.tapWallet();

await TabBarComponent.tapActions();
await WalletActionsBottomSheet.tapBridgeButton();
await device.disableSynchronization();
await QuoteView.enterBridgeAmount('1');
await QuoteView.tapBridgeTo();
await TestHelpers.delay(1000);
await QuoteView.selectNetwork('OP Mainnet');
await Assertions.checkIfVisible(QuoteView.token('ETH'));
await QuoteView.selectToken('ETH');
await Assertions.checkIfVisible(QuoteView.quotesLabel);
await Assertions.checkIfVisible(QuoteView.continueButton);
await QuoteView.tapContinue();

// Check the bridge activity completed
await TabBarComponent.tapActivity();
await Assertions.checkIfVisible(ActivitiesView.title);
await Assertions.checkIfVisible(ActivitiesView.bridgeActivityTitle('Optimism'));
await Assertions.checkIfElementToHaveText(
ActivitiesView.transactionStatus(FIRST_ROW) as Promise<Detox.IndexableNativeElement>,
ActivitiesViewSelectorsText.CONFIRM_TEXT,
30000,
);
});
});
Loading
Loading