From 0f09e664461b5976b1d4db1cb8efb954528e02d1 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 17 Feb 2026 18:30:52 +0300 Subject: [PATCH 1/5] feat: implement awaiting of the wallet action page from the side of the wtm package --- .../browser-service/src/browser.constants.ts | 6 +++ .../browser-service/src/browser.service.ts | 4 +- packages/wallets/src/bitget/bitgetPage.ts | 10 ++-- packages/wallets/src/coin98/coin98.page.ts | 17 +++++-- .../wallets/src/coinbase/coinbase.page.ts | 23 +++++++-- packages/wallets/src/ctrl/ctrl.page.ts | 10 ++-- packages/wallets/src/exodus/exodus.page.ts | 16 ++++-- .../metamask/metamask-latest/metamask.page.ts | 50 +++++++++++++++---- .../metamask/metamask-stable/metamask.page.ts | 50 +++++++++++++++---- packages/wallets/src/okx/okx.page.ts | 49 ++++++++++++++---- packages/wallets/src/safe/pages/setup.page.ts | 28 +++++------ .../src/safe/pages/transaction.page.ts | 23 +++------ packages/wallets/src/safe/safeIframe.page.ts | 26 ++++++++-- packages/wallets/src/safe/safeWc.page.ts | 12 ++++- .../src/trustwallet/trustWallet.page.ts | 42 +++++++++++++--- packages/wallets/src/wallet.page.ts | 25 +++++----- packages/wallets/utils/helper.ts | 47 +++++++++++++++++ .../config/config.ethereumWidget.ts | 2 +- wallets-testing/pages/iframeWidget.page.ts | 34 +++++-------- wallets-testing/pages/standWidget.page.ts | 30 ++++------- wallets-testing/pages/widget.page.ts | 2 +- 21 files changed, 350 insertions(+), 156 deletions(-) create mode 100644 packages/wallets/utils/helper.ts diff --git a/packages/browser-service/src/browser.constants.ts b/packages/browser-service/src/browser.constants.ts index 5b0d6b3b..dfe6feab 100644 --- a/packages/browser-service/src/browser.constants.ts +++ b/packages/browser-service/src/browser.constants.ts @@ -11,6 +11,7 @@ import { SafeWcPage, TrustWalletPage, } from '@lidofinance/wallets-testing-wallets'; +import { z } from 'zod'; export const WALLET_PAGES = { metamaskStable: MetamaskStablePage, @@ -27,3 +28,8 @@ export const WALLET_PAGES = { }; export const DEFAULT_BROWSER_CONTEXT_DIR_NAME = '.browser_context'; + +export const BrowserExtension = z.object({ + id: z.string(), + name: z.string(), +}); diff --git a/packages/browser-service/src/browser.service.ts b/packages/browser-service/src/browser.service.ts index 9a58c12f..0bdd555e 100644 --- a/packages/browser-service/src/browser.service.ts +++ b/packages/browser-service/src/browser.service.ts @@ -52,9 +52,7 @@ type BrowserServiceOptions = { */ export class BrowserService { private logger = new ConsoleLogger(BrowserService.name); - private walletPage: WalletPage< - WalletConnectTypes.WC | WalletConnectTypes.EOA | WalletConnectTypes.IFRAME - >; + private walletPage: WalletPage; private browserContextService: BrowserContextService; public ethereumNodeService: EthereumNodeService; diff --git a/packages/wallets/src/bitget/bitgetPage.ts b/packages/wallets/src/bitget/bitgetPage.ts index 1522aa94..a10961d6 100644 --- a/packages/wallets/src/bitget/bitgetPage.ts +++ b/packages/wallets/src/bitget/bitgetPage.ts @@ -1,9 +1,9 @@ -import { WalletConnectTypes } from '../wallets.constants'; import { WalletPage, WalletPageOptions } from '../wallet.page'; import { test, Page } from '@playwright/test'; import { ConsoleLogger } from '@nestjs/common'; +import { getNotificationPage } from '../../utils/helper'; -export class BitgetPage implements WalletPage { +export class BitgetPage implements WalletPage { logger = new ConsoleLogger(BitgetPage.name); page: Page | undefined; @@ -82,8 +82,12 @@ export class BitgetPage implements WalletPage { }); } - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await page.waitForTimeout(1000); await page.click("button:has-text('Connect')"); await page.close(); diff --git a/packages/wallets/src/coin98/coin98.page.ts b/packages/wallets/src/coin98/coin98.page.ts index 7ee40989..e5706efa 100644 --- a/packages/wallets/src/coin98/coin98.page.ts +++ b/packages/wallets/src/coin98/coin98.page.ts @@ -1,9 +1,9 @@ -import { WalletConnectTypes } from '../wallets.constants'; import { WalletPage, WalletPageOptions } from '../wallet.page'; import { test, Page } from '@playwright/test'; import { ConsoleLogger } from '@nestjs/common'; +import { getNotificationPage } from '../../utils/helper'; -export class Coin98 implements WalletPage { +export class Coin98 implements WalletPage { logger = new ConsoleLogger(Coin98.name); page: Page | undefined; @@ -94,8 +94,12 @@ export class Coin98 implements WalletPage { }); } - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await this.unlock(page); const selectAllBtn = page.getByText('Select all', { exact: true }); // for polygon network there is no account selection preview @@ -108,6 +112,7 @@ export class Coin98 implements WalletPage { await page.click('button:has-text("Connect")'); }); } + async closePopover(popUpPage: Page) { //popUpPage param required since noisy pop-up can appear in confirmation pages try { @@ -137,8 +142,12 @@ export class Coin98 implements WalletPage { throw new Error('Method not implemented.'); } - async confirmTx(page: Page) { + async confirmTx() { await test.step('Confirm TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await page.click('button:has-text("Confirm")'); }); } diff --git a/packages/wallets/src/coinbase/coinbase.page.ts b/packages/wallets/src/coinbase/coinbase.page.ts index 1b01ab6f..4faaebc4 100644 --- a/packages/wallets/src/coinbase/coinbase.page.ts +++ b/packages/wallets/src/coinbase/coinbase.page.ts @@ -1,10 +1,11 @@ -import { NetworkConfig, WalletConnectTypes } from '../wallets.constants'; +import { NetworkConfig } from '../wallets.constants'; import { WalletPage, WalletPageOptions } from '../wallet.page'; import expect from 'expect'; import { test, Page } from '@playwright/test'; import { ConsoleLogger } from '@nestjs/common'; +import { getNotificationPage } from '../../utils/helper'; -export class CoinbasePage implements WalletPage { +export class CoinbasePage implements WalletPage { logger = new ConsoleLogger(CoinbasePage.name); page: Page | undefined; @@ -120,22 +121,34 @@ export class CoinbasePage implements WalletPage { }); } - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await page.click('button:has-text("Connect")'); }); } - async assertTxAmount(page: Page, expectedAmount: string) { + async assertTxAmount(expectedAmount: string) { await test.step('Assert TX Amount', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); expect(await page.textContent('.currency-display-component__text')).toBe( expectedAmount, ); }); } - async confirmTx(page: Page) { + async confirmTx() { await test.step('Confirm TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await this.closeTransactionPopover(); await page.click('button[data-testid="request-confirm-button"]'); }); diff --git a/packages/wallets/src/ctrl/ctrl.page.ts b/packages/wallets/src/ctrl/ctrl.page.ts index 2952783d..be504145 100644 --- a/packages/wallets/src/ctrl/ctrl.page.ts +++ b/packages/wallets/src/ctrl/ctrl.page.ts @@ -1,11 +1,11 @@ import { WalletPage } from '../wallet.page'; import { test, Page } from '@playwright/test'; -import { WalletConnectTypes } from '../wallets.constants'; import { LoginPage, OnboardingPage, WalletOperations } from './pages'; import { WalletPageOptions } from '../wallet.page'; import { ConsoleLogger } from '@nestjs/common'; +import { getNotificationPage } from '../../utils/helper'; -export class CtrlPage implements WalletPage { +export class CtrlPage implements WalletPage { logger = new ConsoleLogger(CtrlPage.name); page: Page | undefined; onboardingPage: OnboardingPage; @@ -53,8 +53,12 @@ export class CtrlPage implements WalletPage { } /** Click `Connect` button */ - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect Ctrl wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const operationPage = new WalletOperations(page); await operationPage.connectBtn.waitFor({ state: 'visible', diff --git a/packages/wallets/src/exodus/exodus.page.ts b/packages/wallets/src/exodus/exodus.page.ts index bd2b41ab..286ac9bb 100644 --- a/packages/wallets/src/exodus/exodus.page.ts +++ b/packages/wallets/src/exodus/exodus.page.ts @@ -1,10 +1,10 @@ -import { WalletConnectTypes } from '../wallets.constants'; import { WalletPage, WalletPageOptions } from '../wallet.page'; import { test, Page } from '@playwright/test'; import { OnboardingPage } from './pages'; import { ConsoleLogger } from '@nestjs/common'; +import { getNotificationPage } from '../../utils/helper'; -export class ExodusPage implements WalletPage { +export class ExodusPage implements WalletPage { logger = new ConsoleLogger(ExodusPage.name); page: Page | undefined; onboardingPage: OnboardingPage; @@ -63,8 +63,12 @@ export class ExodusPage implements WalletPage { } /** Click `Connect` button on the transaction `page` */ - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const connectWalletBtn = page.getByText('Connect').nth(2); // the connect button is disabled by default, and it will be enabled after hover with awaiting await connectWalletBtn.hover(); @@ -74,8 +78,12 @@ export class ExodusPage implements WalletPage { } /** Click `Confirm` button on the transaction `page` */ - async confirmTx(page: Page) { + async confirmTx() { await test.step('Confirm TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await page.getByText('Confirm').click(); }); } diff --git a/packages/wallets/src/metamask/metamask-latest/metamask.page.ts b/packages/wallets/src/metamask/metamask-latest/metamask.page.ts index a80e1542..ae94a7b2 100644 --- a/packages/wallets/src/metamask/metamask-latest/metamask.page.ts +++ b/packages/wallets/src/metamask/metamask-latest/metamask.page.ts @@ -1,4 +1,4 @@ -import { NetworkConfig, WalletConnectTypes } from '../../wallets.constants'; +import { NetworkConfig } from '../../wallets.constants'; import { WalletPage, WalletPageOptions } from '../../wallet.page'; import { expect } from '@playwright/test'; import { test, Page } from '@playwright/test'; @@ -18,8 +18,9 @@ import { } from './pages/elements'; import { getAddress } from 'viem'; import { isPopularMainnetNetwork, isPopularTestnetNetwork } from './helper'; +import { getNotificationPage } from '../../../utils/helper'; -export class MetamaskPage implements WalletPage { +export class MetamaskPage implements WalletPage { page: Page | undefined; header: Header; homePage: HomePage; @@ -109,6 +110,7 @@ export class MetamaskPage implements WalletPage { async setupNetwork(networkConfig: NetworkConfig) { await test.step(`Setup "${networkConfig.chainName}" Network`, async () => { + if (this.page.isClosed()) await this.navigate(); await this.settingsMenu.openNetworksSettings(); if ( await this.networkList.isNetworkExist( @@ -154,16 +156,24 @@ export class MetamaskPage implements WalletPage { }); } - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect Metamask wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const operationPage = new WalletOperationPage(page); await operationPage.connectBtn.click(); // "Confirm" button to give permission await operationPage.page.close(); }); } - async assertTxAmount(page: Page, expectedAmount: string) { + async assertTxAmount(expectedAmount: string) { await test.step('Assert TX Amount', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const txAmount = await new WalletOperationPage(page).getTxAmount(); if (txAmount) { expect(txAmount).toBe(expectedAmount); @@ -171,9 +181,13 @@ export class MetamaskPage implements WalletPage { }); } - async confirmAddTokenToWallet(confirmPage: Page) { + async confirmAddTokenToWallet() { await test.step('Confirm add token to wallet', async () => { - await new WalletOperationPage(confirmPage).addTokenButton.click(); + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); + await new WalletOperationPage(page).addTokenButton.click(); }); } @@ -212,26 +226,42 @@ export class MetamaskPage implements WalletPage { }); } - async confirmTx(page: Page, setAggressiveGas?: boolean) { + async confirmTx(setAggressiveGas?: boolean) { await test.step('Confirm TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperationPage(page).confirmTransaction(setAggressiveGas); }); } - async cancelTx(page: Page) { + async cancelTx() { await test.step('Reject TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperationPage(page).cancelTransaction(); }); } - async approveTokenTx(page: Page) { + async approveTokenTx() { await test.step('Approve token TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperationPage(page).confirmTransactionOfTokenApproval(); }); } - async assertReceiptAddress(page: Page, expectedAddress: string) { + async assertReceiptAddress(expectedAddress: string) { await test.step('Assert receiptAddress/Contract', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const recipientAddress = await new WalletOperationPage( page, ).getReceiptAddress(); diff --git a/packages/wallets/src/metamask/metamask-stable/metamask.page.ts b/packages/wallets/src/metamask/metamask-stable/metamask.page.ts index 945170c4..d6fd0ed1 100644 --- a/packages/wallets/src/metamask/metamask-stable/metamask.page.ts +++ b/packages/wallets/src/metamask/metamask-stable/metamask.page.ts @@ -1,4 +1,4 @@ -import { NetworkConfig, WalletConnectTypes } from '../../wallets.constants'; +import { NetworkConfig } from '../../wallets.constants'; import { WalletPage, WalletPageOptions } from '../../wallet.page'; import { expect } from '@playwright/test'; import { test, Page } from '@playwright/test'; @@ -22,8 +22,9 @@ import { isPopularTestnetNetwork, } from './helper'; import { privateKeyToAccount } from 'viem/accounts'; +import { getNotificationPage } from '../../../utils/helper'; -export class MetamaskStablePage implements WalletPage { +export class MetamaskStablePage implements WalletPage { page: Page | undefined; header: Header; homePage: HomePage; @@ -107,6 +108,7 @@ export class MetamaskStablePage implements WalletPage { } async setupNetwork(networkConfig: NetworkConfig) { + if (this.page.isClosed()) await this.navigate(); const correctNetworkName = getCorrectNetworkName(networkConfig.chainName); await test.step(`Setup "${correctNetworkName}" Network`, async () => { await this.header.networkListButton.click(); @@ -175,16 +177,24 @@ export class MetamaskStablePage implements WalletPage { }); } - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect Metamask wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const operationPage = new WalletOperationPage(page); await operationPage.connectBtn.click(); // "Confirm" button to give permission await operationPage.page.close(); }); } - async assertTxAmount(page: Page, expectedAmount: string) { + async assertTxAmount(expectedAmount: string) { await test.step('Assert TX Amount', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const txAmount = await new WalletOperationPage(page).getTxAmount(); if (txAmount) { expect(txAmount).toBe(expectedAmount); @@ -192,9 +202,13 @@ export class MetamaskStablePage implements WalletPage { }); } - async confirmAddTokenToWallet(confirmPage: Page) { + async confirmAddTokenToWallet() { await test.step('Confirm add token to wallet', async () => { - await new WalletOperationPage(confirmPage).addTokenButton.click(); + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); + await new WalletOperationPage(page).addTokenButton.click(); }); } @@ -233,26 +247,42 @@ export class MetamaskStablePage implements WalletPage { }); } - async confirmTx(page: Page, setAggressiveGas?: boolean) { + async confirmTx(setAggressiveGas?: boolean) { await test.step('Confirm TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperationPage(page).confirmTransaction(setAggressiveGas); }); } - async cancelTx(page: Page) { + async cancelTx() { await test.step('Reject TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperationPage(page).cancelTransaction(); }); } - async approveTokenTx(page: Page) { + async approveTokenTx() { await test.step('Approve token TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperationPage(page).confirmTransactionOfTokenApproval(); }); } - async assertReceiptAddress(page: Page, expectedAddress: string) { + async assertReceiptAddress(expectedAddress: string) { await test.step('Assert receiptAddress/Contract', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const recipientAddress = await new WalletOperationPage( page, ).getReceiptAddress(); diff --git a/packages/wallets/src/okx/okx.page.ts b/packages/wallets/src/okx/okx.page.ts index 3b50bedc..9594e483 100644 --- a/packages/wallets/src/okx/okx.page.ts +++ b/packages/wallets/src/okx/okx.page.ts @@ -1,4 +1,4 @@ -import { NetworkConfig, WalletConnectTypes } from '../wallets.constants'; +import { NetworkConfig } from '../wallets.constants'; import { WalletPage } from '../wallet.page'; import { test, Page, expect } from '@playwright/test'; import { @@ -17,8 +17,9 @@ import { } from './helper'; import { ConsoleLogger } from '@nestjs/common'; import { WalletPageOptions } from '../wallet.page'; +import { getNotificationPage } from '../../utils/helper'; -export class OkxPage implements WalletPage { +export class OkxPage implements WalletPage { logger = new ConsoleLogger(OkxPage.name); page: Page | undefined; homePage: HomePage; @@ -161,8 +162,12 @@ export class OkxPage implements WalletPage { } /** Click `Connect` button on the transaction `page` */ - async connectWallet(page: Page) { + async connectWallet() { await test.step('Connect OKX wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const operationPage = new WalletOperations(page); await operationPage.connectButton.waitFor({ state: 'visible', @@ -179,8 +184,12 @@ export class OkxPage implements WalletPage { } /** Get the `amount` from transaction and comply with the `expectedAmount` */ - async assertTxAmount(page: Page, expectedAmount: string) { + async assertTxAmount(expectedAmount: string) { await test.step('Assert TX Amount', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const txAmount = await new WalletOperations(page).getTxAmount(); if (txAmount) { expect(txAmount).toBe(expectedAmount); @@ -189,15 +198,23 @@ export class OkxPage implements WalletPage { } /** Cancel transaction */ - async cancelTx(page: Page) { + async cancelTx() { await test.step('Cancel TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperations(page).cancelTxButton.click(); }); } /** Confirm transaction */ - async confirmTx(page: Page) { + async confirmTx() { await test.step('Confirm TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperations(page).confirmTxButton.click({ timeout: 30000, // sometimes button is disabled awaits rpc }); @@ -205,16 +222,24 @@ export class OkxPage implements WalletPage { } /** Approve token transaction */ - async approveTokenTx(page: Page) { + async approveTokenTx() { await test.step('Approve token tx', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const walletOperations = new WalletOperations(page); await walletOperations.confirmTxButton.click(); }); } /** Get the `address` from transaction and comply with the `expectedAddress` */ - async assertReceiptAddress(page: Page, expectedAddress: string) { + async assertReceiptAddress(expectedAddress: string) { await test.step('Assert receiptAddress/Contract', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const recipientAddress = await new WalletOperations( page, ).getReceiptAddress(); @@ -223,9 +248,13 @@ export class OkxPage implements WalletPage { } /** Confirm tx to add token to wallet */ - async confirmAddTokenToWallet(confirmPage: Page) { + async confirmAddTokenToWallet() { await test.step('Confirm add token to wallet', async () => { - await new WalletOperations(confirmPage).confirmTxButton.click(); + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); + await new WalletOperations(page).confirmTxButton.click(); }); } diff --git a/packages/wallets/src/safe/pages/setup.page.ts b/packages/wallets/src/safe/pages/setup.page.ts index bf543cf1..9f723656 100644 --- a/packages/wallets/src/safe/pages/setup.page.ts +++ b/packages/wallets/src/safe/pages/setup.page.ts @@ -1,6 +1,5 @@ import { Locator, Page, test } from '@playwright/test'; import { WalletPage } from '../../wallet.page'; -import { WalletConnectTypes } from '../../wallets.constants'; import { ConsoleLogger } from '@nestjs/common'; export class SetupPage { @@ -18,7 +17,7 @@ export class SetupPage { constructor( public page: Page, - public extensionPage: WalletPage, + public extensionPage: WalletPage, public chainId: number, ) { this.setupUrl = @@ -76,10 +75,11 @@ export class SetupPage { await test.step('Add trusted Safes', async () => { try { // If the button is visible, the trusted Safe is selected - await this.manageTrustedSafesBtn.waitFor({ - state: 'visible', - timeout: 3000, - }); + if (this.chainId === 1) + await this.manageTrustedSafesBtn.waitFor({ + state: 'visible', + timeout: 3000, + }); } catch { await this.addSafesBtn.click(); @@ -152,16 +152,12 @@ export class SetupPage { } try { - const [connectWalletPage] = await Promise.all([ - this.page.context().waitForEvent('page', { timeout: 5000 }), - - this.page - .getByText( - this.extensionPage.options.walletConfig.EXTENSION_WALLET_NAME, - ) - .click(), - ]); - await this.extensionPage.connectWallet(connectWalletPage); + await this.page + .getByText( + this.extensionPage.options.walletConfig.EXTENSION_WALLET_NAME, + ) + .click(); + await this.extensionPage.connectWallet(); } catch (er) { // Expect the wallet is connected } diff --git a/packages/wallets/src/safe/pages/transaction.page.ts b/packages/wallets/src/safe/pages/transaction.page.ts index f4d6933a..1c12664b 100644 --- a/packages/wallets/src/safe/pages/transaction.page.ts +++ b/packages/wallets/src/safe/pages/transaction.page.ts @@ -1,7 +1,6 @@ import { Locator, Page, test, expect } from '@playwright/test'; import { ConsoleLogger } from '@nestjs/common'; import { WalletPage } from '../../wallet.page'; -import { WalletConnectTypes } from '../../wallets.constants'; import { Big } from 'big.js'; export class TransactionPage { @@ -20,10 +19,7 @@ export class TransactionPage { contentLoader: Locator; transactionFailBanner: Locator; - constructor( - public page: Page, - public extensionPage: WalletPage, - ) { + constructor(public page: Page, public extensionPage: WalletPage) { this.transactionCardContent = this.page.getByTestId('card-content').first(); this.contractExplorerUrl = this.transactionCardContent .getByTestId('explorer-btn') @@ -122,11 +118,8 @@ export class TransactionPage { await test.step('Switch network if needed', async () => { if (await this.switchNetworkBtn.isVisible()) { - const [extensionTxPage] = await Promise.all([ - this.page.context().waitForEvent('page', { timeout: 10000 }), - await this.switchNetworkBtn.click(), - ]); - await this.extensionPage.confirmTx(extensionTxPage); + await this.switchNetworkBtn.click(); + await this.extensionPage.confirmTx(); } }); @@ -149,12 +142,10 @@ export class TransactionPage { await this.selectOptionToExecuteTx(); }); - const [extensionTxPage] = await Promise.all([ - this.executeTxBtnClick(), - this.executeTxBtn.click(), - ]); - await this.extensionPage.confirmTx(extensionTxPage, true); - return extensionTxPage; + await this.executeTxBtn.click(); + await this.executeTxBtnClick(); + + await this.extensionPage.confirmTx(true); } private async executeTxBtnClick(maxAttempts = 3) { diff --git a/packages/wallets/src/safe/safeIframe.page.ts b/packages/wallets/src/safe/safeIframe.page.ts index 213c3741..f0721580 100644 --- a/packages/wallets/src/safe/safeIframe.page.ts +++ b/packages/wallets/src/safe/safeIframe.page.ts @@ -1,10 +1,10 @@ import { Page, test } from '@playwright/test'; import { WalletPage, WalletPageOptions } from '../wallet.page'; -import { NetworkConfig, WalletConnectTypes } from '../wallets.constants'; +import { NetworkConfig } from '../wallets.constants'; import { ConsoleLogger } from '@nestjs/common'; import { SetupPage, SettingPage, TransactionPage } from './pages'; -export class SafeIframePage implements WalletPage { +export class SafeIframePage implements WalletPage { logger = new ConsoleLogger(SafeIframePage.name); page: Page; safeUrl: URL; @@ -35,6 +35,13 @@ export class SafeIframePage implements WalletPage { */ async setup() { await this.options.extensionPage.setup(); + // close all pages + await Promise.all( + this.options.browserContext + .pages() + .slice(1) + .map((page) => page.close()), + ); await test.step('Init Safe wallet', async () => { await this.initLocators(); @@ -105,8 +112,11 @@ export class SafeIframePage implements WalletPage { }); } - async assertReceiptAddress(page: Page, expectedAddress: string) { + async assertReceiptAddress(expectedAddress: string) { await test.step('Assert of receipt address', async () => { + const page = this.options.browserContext + .pages() + .find((page) => page.url().includes(this.safeUrl.hostname)); await new TransactionPage( page, this.options.extensionPage, @@ -114,8 +124,11 @@ export class SafeIframePage implements WalletPage { }); } - async confirmTx(page: Page) { + async confirmTx() { await test.step('Confirm transaction', async () => { + const page = this.options.browserContext + .pages() + .find((page) => page.url().includes(this.safeUrl.hostname)); const transactionPage = new TransactionPage( page, this.options.extensionPage, @@ -128,8 +141,11 @@ export class SafeIframePage implements WalletPage { }); } - async assertTxAmount(page: Page, expectedAmount: string) { + async assertTxAmount(expectedAmount: string) { await test.step('Assert of transaction amount', async () => { + const page = this.options.browserContext + .pages() + .find((page) => page.url().includes(this.safeUrl.hostname)); await new TransactionPage( page, this.options.extensionPage, diff --git a/packages/wallets/src/safe/safeWc.page.ts b/packages/wallets/src/safe/safeWc.page.ts index f47351b4..53d0ae63 100644 --- a/packages/wallets/src/safe/safeWc.page.ts +++ b/packages/wallets/src/safe/safeWc.page.ts @@ -2,9 +2,9 @@ import { Page, test } from '@playwright/test'; import { ConsoleLogger } from '@nestjs/common'; import { HomePage, SetupPage } from './pages'; import { WalletPage, WalletPageOptions } from '../wallet.page'; -import { NetworkConfig, WalletConnectTypes } from '../wallets.constants'; +import { NetworkConfig } from '../wallets.constants'; -export class SafeWcPage implements WalletPage { +export class SafeWcPage implements WalletPage { logger = new ConsoleLogger(SafeWcPage.name); page: Page; setupPage: SetupPage; @@ -26,6 +26,14 @@ export class SafeWcPage implements WalletPage { async setup() { await this.options.extensionPage.setup(); + // close all pages + await Promise.all( + this.options.browserContext + .pages() + .slice(1) + .map((page) => page.close()), + ); + await this.initLocators(); this.safeAccountUrl = await this.setupPage.firstTimeSetupWallet(); } diff --git a/packages/wallets/src/trustwallet/trustWallet.page.ts b/packages/wallets/src/trustwallet/trustWallet.page.ts index 03e93445..9a3062ba 100644 --- a/packages/wallets/src/trustwallet/trustWallet.page.ts +++ b/packages/wallets/src/trustwallet/trustWallet.page.ts @@ -1,4 +1,4 @@ -import { NetworkConfig, WalletConnectTypes } from '../wallets.constants'; +import { NetworkConfig } from '../wallets.constants'; import { WalletPage, WalletPageOptions } from '../wallet.page'; import { test, Page, expect } from '@playwright/test'; import { ConsoleLogger } from '@nestjs/common'; @@ -10,8 +10,9 @@ import { LoginPage, } from './pages'; import { closeUnnecessaryPages } from '../okx/helper'; +import { getNotificationPage } from '../../utils/helper'; -export class TrustWalletPage implements WalletPage { +export class TrustWalletPage implements WalletPage { logger = new ConsoleLogger(TrustWalletPage.name); page?: Page; @@ -105,10 +106,15 @@ export class TrustWalletPage implements WalletPage { } /** Click `Connect` button on the transaction `page` */ - async connectWallet(page: Page) { + async connectWallet() { // [High risk connection] Need to research before connecting the Trust wallet methods to widget tests // https://linear.app/lidofi/issue/QA-3382/high-risk-popup-before-connect-to-trust-wallet await test.step('Connect wallet', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); + await new LoginPage(page, this.options.accountConfig).unlock(); const txPage = new WalletOperations(page); @@ -122,8 +128,12 @@ export class TrustWalletPage implements WalletPage { } /** Get the `amount` from transaction and comply with the `expectedAmount` */ - async assertTxAmount(page: Page, expectedAmount: string) { + async assertTxAmount(expectedAmount: string) { await test.step('Assert TX Amount', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const txPage = new WalletOperations(page); expect(parseFloat(await txPage.txAmountValue.textContent())).toBe( expectedAmount, @@ -132,8 +142,12 @@ export class TrustWalletPage implements WalletPage { } /** Confirm transaction */ - async confirmTx(page: Page) { + async confirmTx() { await test.step('Confirm TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const txPage = new WalletOperations(page); await expect(txPage.confirmBtn).toBeEnabled(); await txPage.confirmBtn.click(); @@ -141,8 +155,12 @@ export class TrustWalletPage implements WalletPage { } /** Reject transaction */ - async cancelTx(page: Page) { + async cancelTx() { await test.step('Cancel TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const txPage = new WalletOperations(page); await expect(txPage.rejectBtn).toBeEnabled(); await txPage.rejectBtn.click(); @@ -150,8 +168,12 @@ export class TrustWalletPage implements WalletPage { } /** Confirm token approval transaction */ - async approveTokenTx(page: Page) { + async approveTokenTx() { await test.step('Approve token TX', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); const txPage = new WalletOperations(page); await expect(txPage.confirmBtn).toBeEnabled(); await txPage.confirmBtn.click(); @@ -159,8 +181,12 @@ export class TrustWalletPage implements WalletPage { } /** Get the `address` from transaction and comply with the `expectedAddress` */ - async assertReceiptAddress(page: Page, expectedAddress: string) { + async assertReceiptAddress(expectedAddress: string) { await test.step('Assert receiptAddress/Contract', async () => { + const page = await getNotificationPage( + this.options.browserContext, + this.options.extensionUrl, + ); await new WalletOperations(page).viewDetailsBtn.click(); await page.getByText(expectedAddress).isVisible(); }); diff --git a/packages/wallets/src/wallet.page.ts b/packages/wallets/src/wallet.page.ts index a485e668..c0022e49 100644 --- a/packages/wallets/src/wallet.page.ts +++ b/packages/wallets/src/wallet.page.ts @@ -4,8 +4,6 @@ import { CommonWalletConfig, NetworkConfig, StandConfig, - WalletConnectType, - WalletConnectTypes, } from './wallets.constants'; /** Required options to manage wallet */ @@ -14,7 +12,7 @@ export interface WalletPageOptions { walletConfig: CommonWalletConfig; accountConfig?: AccountConfig; extensionUrl?: string; - extensionPage?: WalletPage; + extensionPage?: WalletPage; standConfig?: StandConfig; } @@ -42,7 +40,7 @@ export interface WalletPageOptions { * - extensionPage * - standConfig * */ -export interface WalletPage { +export interface WalletPage { options: WalletPageOptions; page?: Page; @@ -50,25 +48,24 @@ export interface WalletPage { importKey(secretKey: string, withChecks?: boolean): Promise; - connectWallet( - param?: T extends WalletConnectTypes.EOA ? Page : string, - ): Promise; + /** @param param is url walletConnect */ + connectWallet(param?: string): Promise; - assertTxAmount(page: Page, expectedAmount: string): Promise; + assertTxAmount(expectedAmount: string): Promise; - confirmTx(page: Page, setAggressiveGas?: boolean): Promise; + confirmTx(setAggressiveGas?: boolean): Promise; - cancelTx(page: Page): Promise; + cancelTx(): Promise; - approveTokenTx?(page: Page): Promise; + approveTokenTx?(): Promise; openLastTxInEthplorer?(txIndex?: number): Promise; getTokenBalance?(tokenName: string): Promise; - confirmAddTokenToWallet?(page: Page): Promise; + confirmAddTokenToWallet?(): Promise; - assertReceiptAddress(page: Page, expectedAddress: string): Promise; + assertReceiptAddress(expectedAddress: string): Promise; getWalletAddress?(): Promise; @@ -82,9 +79,11 @@ export interface WalletPage { accountName: string, isClosePage?: boolean, ): Promise; + changeWalletAccountByAddress?( address: string, isClosePage?: boolean, ): Promise; + isWalletAddressExist?(address: string): Promise; } diff --git a/packages/wallets/utils/helper.ts b/packages/wallets/utils/helper.ts new file mode 100644 index 00000000..e04b03ee --- /dev/null +++ b/packages/wallets/utils/helper.ts @@ -0,0 +1,47 @@ +import { BrowserContext, Page, test } from '@playwright/test'; +import { ConsoleLogger } from '@nestjs/common'; + +/** This function helps to find the wallet page with the transaction or any wallet's notification */ +export async function getNotificationPage( + context: BrowserContext, + extensionUrl: string, + timeout = 10000, +) { + const logger = new ConsoleLogger('PageWaiter'); + const isWalletPage = (page: Page) => page.url().includes(extensionUrl); + + let walletPage: Page | undefined; + await test.step('Looking for the wallet page', async () => { + const attempts = 3; + + for (let i = 1; i <= attempts; i++) { + walletPage = context.pages().find(isWalletPage); + if (!walletPage) { + walletPage = await context.waitForEvent('page', { + predicate: isWalletPage, + timeout, + }); + } + + if (walletPage) break; + logger.debug(`wallet is not opened [attempt ${i}]`); + } + }); + + await test.step('Wait for page loaded', async () => { + try { + await Promise.all([ + walletPage.waitForLoadState('domcontentloaded', { + timeout: 10000, + }), + walletPage.waitForLoadState('networkidle', { + timeout: 10000, + }), + ]); + } catch (er) { + logger.debug('Page loading timeout'); + } + }); + + return walletPage; +} diff --git a/wallets-testing/config/config.ethereumWidget.ts b/wallets-testing/config/config.ethereumWidget.ts index 293f9d2c..39bf0183 100644 --- a/wallets-testing/config/config.ethereumWidget.ts +++ b/wallets-testing/config/config.ethereumWidget.ts @@ -19,7 +19,7 @@ export interface WidgetConfig { const ETHEREUM_WIDGET_CONFIG: WidgetConfig = { url: 'https://stake.lido.fi', - rpcUrlToMock: '**/api/rpc?chainId=1', + rpcUrlToMock: '.*/api/rpc\\?chainId=1', name: 'ethereum', network: { ...NETWORKS_CONFIG.mainnet.ETHEREUM, diff --git a/wallets-testing/pages/iframeWidget.page.ts b/wallets-testing/pages/iframeWidget.page.ts index aca9c5aa..a1d7d895 100644 --- a/wallets-testing/pages/iframeWidget.page.ts +++ b/wallets-testing/pages/iframeWidget.page.ts @@ -1,16 +1,13 @@ import { expect, FrameLocator, Locator, Page, test } from '@playwright/test'; import { ConsoleLogger } from '@nestjs/common'; -import { - WalletPage, - WalletConnectTypes, -} from '@lidofinance/wallets-testing-wallets'; +import { WalletPage } from '@lidofinance/wallets-testing-wallets'; import { BrowserService } from '@lidofinance/browser-service'; import { tokenToWithdraw, tokenToWrap, WidgetPage } from './widget.page'; import { WidgetConfig } from '../config'; export class IframeWidgetPage implements WidgetPage { logger = new ConsoleLogger(IframeWidgetPage.name); - walletPage: WalletPage; + walletPage: WalletPage; app: FrameLocator; page: Page; @@ -149,16 +146,13 @@ export class IframeWidgetPage implements WidgetPage { await this.stakeSubmitBtn.click(); }); - await this.walletPage.assertTxAmount(this.page, txAmount); - await this.walletPage.assertReceiptAddress( - this.page, - this.widgetConfig.stakeContract, - ); + await this.walletPage.assertTxAmount(txAmount); + await this.walletPage.assertReceiptAddress(this.widgetConfig.stakeContract); // Skip next steps, because on the mainnet we need to check only tx initialization (without execution) if (this.widgetConfig.network.chainId == 1) return; - await this.walletPage.confirmTx(this.page, true); + await this.walletPage.confirmTx(true); await this.app .getByText('Staking operation was successful.') .waitFor({ state: 'visible', timeout: 90000 }); @@ -184,16 +178,15 @@ export class IframeWidgetPage implements WidgetPage { await this.wrapSubmitBtn.click(); }); - await this.walletPage.assertTxAmount(this.page, txAmount); + await this.walletPage.assertTxAmount(txAmount); await this.walletPage.assertReceiptAddress( - this.page, this.widgetConfig.wrapContract[token], ); // Skip next steps, because on the mainnet we need to check only tx initialization (without execution) if (this.widgetConfig.network.chainId == 1) return; - await this.walletPage.confirmTx(this.page, true); + await this.walletPage.confirmTx(true); await this.app .getByText('Wrapping operation was successful.') .waitFor({ state: 'visible', timeout: 90000 }); @@ -215,16 +208,15 @@ export class IframeWidgetPage implements WidgetPage { await this.unwrapSubmitBtn.click(); }); - await this.walletPage.assertTxAmount(this.page, txAmount); + await this.walletPage.assertTxAmount(txAmount); await this.walletPage.assertReceiptAddress( - this.page, this.widgetConfig.wrapContract.stETH, ); // Skip next steps, because on the mainnet we need to check only tx initialization (without execution) if (this.widgetConfig.network.chainId == 1) return; - await this.walletPage.confirmTx(this.page, true); + await this.walletPage.confirmTx(true); await this.app .getByText('Unwrapping operation was successful.') .waitFor({ state: 'visible', timeout: 90000 }); @@ -250,16 +242,15 @@ export class IframeWidgetPage implements WidgetPage { await this.requestSubmitBtn.click(); }); - await this.walletPage.assertTxAmount(this.page, txAmount); + await this.walletPage.assertTxAmount(txAmount); await this.walletPage.assertReceiptAddress( - this.page, this.widgetConfig.withdrawalContract, ); // Skip next steps, because on the mainnet we need to check only tx initialization (without execution) if (this.widgetConfig.network.chainId == 1) return; - await this.walletPage.confirmTx(this.page, true); + await this.walletPage.confirmTx(true); await this.app .getByText('Withdrawal request successfully sent') .waitFor({ state: 'visible', timeout: 90000 }); @@ -289,14 +280,13 @@ export class IframeWidgetPage implements WidgetPage { }); await this.walletPage.assertReceiptAddress( - this.page, this.widgetConfig.withdrawalContract, ); // Skip next steps, because on the mainnet we need to check only tx initialization (without execution) if (this.widgetConfig.network.chainId == 1) return; - await this.walletPage.confirmTx(this.page, true); + await this.walletPage.confirmTx(true); await this.app .getByText('Claiming operation was successful') .waitFor({ state: 'visible', timeout: 90000 }); diff --git a/wallets-testing/pages/standWidget.page.ts b/wallets-testing/pages/standWidget.page.ts index 2e39f4ca..3a753b9d 100644 --- a/wallets-testing/pages/standWidget.page.ts +++ b/wallets-testing/pages/standWidget.page.ts @@ -9,7 +9,7 @@ import { WidgetConfig } from '../config'; export class StandWidgetPage implements WidgetPage { page: Page; - walletPage: WalletPage; + walletPage: WalletPage; connectBtn: Locator; stakeInput: Locator; @@ -82,11 +82,8 @@ export class StandWidgetPage implements WidgetPage { switch (this.walletPage.options.walletConfig.WALLET_TYPE) { case WalletConnectTypes.EOA: { - const [connectWalletPage] = await Promise.all([ - this.waitForPage(), - walletButton.dblclick(), - ]); - await this.walletPage.connectWallet(connectWalletPage); + await walletButton.dblclick(); + await this.walletPage.connectWallet(); break; } case WalletConnectTypes.WC: { @@ -110,20 +107,13 @@ export class StandWidgetPage implements WidgetPage { await this.enabledStakeSubmitBtn.waitFor({ timeout: 15000 }); }); - const [walletSignPage] = - await test.step('Click to staking button', async () => { - return await Promise.all([ - this.waitForPage(180000), - this.stakeSubmitBtn.click(), - ]); - }); - - await this.walletPage.assertTxAmount(walletSignPage, txAmount); - await this.walletPage.assertReceiptAddress( - walletSignPage, - this.widgetConfig.stakeContract, - ); - await this.walletPage.confirmTx(walletSignPage, true); + await test.step('Click to staking button', async () => { + await this.stakeSubmitBtn.click(); + }); + + await this.walletPage.assertTxAmount(txAmount); + await this.walletPage.assertReceiptAddress(this.widgetConfig.stakeContract); + await this.walletPage.confirmTx(true); await this.page.waitForSelector( 'text="Staking operation was successful."', { timeout: 90000 }, diff --git a/wallets-testing/pages/widget.page.ts b/wallets-testing/pages/widget.page.ts index 24c01b53..1921286b 100644 --- a/wallets-testing/pages/widget.page.ts +++ b/wallets-testing/pages/widget.page.ts @@ -14,7 +14,7 @@ export enum tokenToWithdraw { export interface WidgetPage { page: Page; - walletPage: WalletPage; + walletPage: WalletPage; widgetConfig: WidgetConfig; connectBtn: Locator; From 77455551b7bd2903bccb72b6afdc50246f582085 Mon Sep 17 00:00:00 2001 From: jake Date: Wed, 18 Feb 2026 21:39:20 +0300 Subject: [PATCH 2/5] fix: many fixes --- package.json | 6 +-- packages/wallets/src/bitget/bitgetPage.ts | 4 -- packages/wallets/src/ctrl/ctrl.page.ts | 4 -- .../metamask/metamask-latest/metamask.page.ts | 19 ++++----- .../pages/walletOperations.page.ts | 18 +++----- .../metamask/metamask-stable/metamask.page.ts | 19 ++++----- .../pages/walletOperations.page.ts | 18 +++----- packages/wallets/src/okx/okx.page.ts | 12 ------ packages/wallets/src/safe/safeIframe.page.ts | 4 -- packages/wallets/src/safe/safeWc.page.ts | 4 -- .../src/trustwallet/trustWallet.page.ts | 13 ------ packages/wallets/src/wallet.page.ts | 2 - packages/wallets/utils/helper.ts | 42 ++++++++++++++++--- 13 files changed, 65 insertions(+), 100 deletions(-) diff --git a/package.json b/package.json index 828a1189..09f0d32e 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "branches": [ "main", { - "name": "develop", - "channel": "alpha", - "prerelease": "alpha" + "name": "feat/wait-for-tx", + "channel": "wait-for-tx-alpha", + "prerelease": "wait-for-tx-alpha" } ] }, diff --git a/packages/wallets/src/bitget/bitgetPage.ts b/packages/wallets/src/bitget/bitgetPage.ts index a10961d6..12cff4bf 100644 --- a/packages/wallets/src/bitget/bitgetPage.ts +++ b/packages/wallets/src/bitget/bitgetPage.ts @@ -126,10 +126,6 @@ export class BitgetPage implements WalletPage { throw new Error('Method not implemented.'); } - async approveTokenTx() { - throw new Error('Method not implemented.'); - } - async assertReceiptAddress() { throw new Error('Method not implemented.'); } diff --git a/packages/wallets/src/ctrl/ctrl.page.ts b/packages/wallets/src/ctrl/ctrl.page.ts index be504145..11f4bcdd 100644 --- a/packages/wallets/src/ctrl/ctrl.page.ts +++ b/packages/wallets/src/ctrl/ctrl.page.ts @@ -98,10 +98,6 @@ export class CtrlPage implements WalletPage { throw new Error('Method not implemented.'); } - approveTokenTx(): Promise { - throw new Error('Method not implemented.'); - } - openLastTxInEthplorer(): Promise { throw new Error('Method not implemented.'); } diff --git a/packages/wallets/src/metamask/metamask-latest/metamask.page.ts b/packages/wallets/src/metamask/metamask-latest/metamask.page.ts index ae94a7b2..42b1ab82 100644 --- a/packages/wallets/src/metamask/metamask-latest/metamask.page.ts +++ b/packages/wallets/src/metamask/metamask-latest/metamask.page.ts @@ -18,7 +18,10 @@ import { } from './pages/elements'; import { getAddress } from 'viem'; import { isPopularMainnetNetwork, isPopularTestnetNetwork } from './helper'; -import { getNotificationPage } from '../../../utils/helper'; +import { + getNotificationPage, + waitForWalletPageClosed, +} from '../../../utils/helper'; export class MetamaskPage implements WalletPage { page: Page | undefined; @@ -232,7 +235,9 @@ export class MetamaskPage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); + const pageTitle = await page.locator('h2').textContent(); await new WalletOperationPage(page).confirmTransaction(setAggressiveGas); + await waitForWalletPageClosed(page, pageTitle); }); } @@ -242,17 +247,9 @@ export class MetamaskPage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); + const pageTitle = await page.locator('h2').textContent(); await new WalletOperationPage(page).cancelTransaction(); - }); - } - - async approveTokenTx() { - await test.step('Approve token TX', async () => { - const page = await getNotificationPage( - this.options.browserContext, - this.options.extensionUrl, - ); - await new WalletOperationPage(page).confirmTransactionOfTokenApproval(); + await waitForWalletPageClosed(page, pageTitle); }); } diff --git a/packages/wallets/src/metamask/metamask-latest/pages/walletOperations.page.ts b/packages/wallets/src/metamask/metamask-latest/pages/walletOperations.page.ts index ff5bf204..008fb3cf 100644 --- a/packages/wallets/src/metamask/metamask-latest/pages/walletOperations.page.ts +++ b/packages/wallets/src/metamask/metamask-latest/pages/walletOperations.page.ts @@ -46,9 +46,9 @@ export class WalletOperationPage { 'button[aria-label="Close"]', ); this.txDetailBlock = this.page.getByTestId('simulation-details-layout'); - this.txDetailAmount = this.txDetailBlock.getByTestId( - 'simulation-details-amount-pill', - ); + this.txDetailAmount = this.txDetailBlock + .getByTestId('simulation-details-amount-pill') + .first(); } async cancelAllTxInQueue() { @@ -79,15 +79,6 @@ export class WalletOperationPage { } } - async confirmTransactionOfTokenApproval() { - await test.step('Click "Use default" button in case if it exist', async () => { - // todo: im not sure this step is needed now - if (await this.page.locator('text=Use default').isVisible()) - await this.page.click('text=Use default'); - }); - await this.confirmButton.click(); - } - async confirmTransaction(setAggressiveGas?: boolean) { if (setAggressiveGas) { await this.editGasFeeButton.click(); @@ -110,7 +101,8 @@ export class WalletOperationPage { async getTxAmount() { if (await this.txDetailBlock.isVisible()) { - return await this.txDetailAmount.textContent(); + const amount = await this.txDetailAmount.textContent(); + return amount.match(/\d+\.\d*/)[0]; } return null; } diff --git a/packages/wallets/src/metamask/metamask-stable/metamask.page.ts b/packages/wallets/src/metamask/metamask-stable/metamask.page.ts index d6fd0ed1..2208ccb3 100644 --- a/packages/wallets/src/metamask/metamask-stable/metamask.page.ts +++ b/packages/wallets/src/metamask/metamask-stable/metamask.page.ts @@ -22,7 +22,10 @@ import { isPopularTestnetNetwork, } from './helper'; import { privateKeyToAccount } from 'viem/accounts'; -import { getNotificationPage } from '../../../utils/helper'; +import { + getNotificationPage, + waitForWalletPageClosed, +} from '../../../utils/helper'; export class MetamaskStablePage implements WalletPage { page: Page | undefined; @@ -253,7 +256,9 @@ export class MetamaskStablePage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); + const pageTitle = await page.locator('h2').textContent(); await new WalletOperationPage(page).confirmTransaction(setAggressiveGas); + await waitForWalletPageClosed(page, pageTitle); }); } @@ -263,17 +268,9 @@ export class MetamaskStablePage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); + const pageTitle = await page.locator('h2').textContent(); await new WalletOperationPage(page).cancelTransaction(); - }); - } - - async approveTokenTx() { - await test.step('Approve token TX', async () => { - const page = await getNotificationPage( - this.options.browserContext, - this.options.extensionUrl, - ); - await new WalletOperationPage(page).confirmTransactionOfTokenApproval(); + await waitForWalletPageClosed(page, pageTitle); }); } diff --git a/packages/wallets/src/metamask/metamask-stable/pages/walletOperations.page.ts b/packages/wallets/src/metamask/metamask-stable/pages/walletOperations.page.ts index ff5bf204..008fb3cf 100644 --- a/packages/wallets/src/metamask/metamask-stable/pages/walletOperations.page.ts +++ b/packages/wallets/src/metamask/metamask-stable/pages/walletOperations.page.ts @@ -46,9 +46,9 @@ export class WalletOperationPage { 'button[aria-label="Close"]', ); this.txDetailBlock = this.page.getByTestId('simulation-details-layout'); - this.txDetailAmount = this.txDetailBlock.getByTestId( - 'simulation-details-amount-pill', - ); + this.txDetailAmount = this.txDetailBlock + .getByTestId('simulation-details-amount-pill') + .first(); } async cancelAllTxInQueue() { @@ -79,15 +79,6 @@ export class WalletOperationPage { } } - async confirmTransactionOfTokenApproval() { - await test.step('Click "Use default" button in case if it exist', async () => { - // todo: im not sure this step is needed now - if (await this.page.locator('text=Use default').isVisible()) - await this.page.click('text=Use default'); - }); - await this.confirmButton.click(); - } - async confirmTransaction(setAggressiveGas?: boolean) { if (setAggressiveGas) { await this.editGasFeeButton.click(); @@ -110,7 +101,8 @@ export class WalletOperationPage { async getTxAmount() { if (await this.txDetailBlock.isVisible()) { - return await this.txDetailAmount.textContent(); + const amount = await this.txDetailAmount.textContent(); + return amount.match(/\d+\.\d*/)[0]; } return null; } diff --git a/packages/wallets/src/okx/okx.page.ts b/packages/wallets/src/okx/okx.page.ts index 9594e483..fbe95f94 100644 --- a/packages/wallets/src/okx/okx.page.ts +++ b/packages/wallets/src/okx/okx.page.ts @@ -221,18 +221,6 @@ export class OkxPage implements WalletPage { }); } - /** Approve token transaction */ - async approveTokenTx() { - await test.step('Approve token tx', async () => { - const page = await getNotificationPage( - this.options.browserContext, - this.options.extensionUrl, - ); - const walletOperations = new WalletOperations(page); - await walletOperations.confirmTxButton.click(); - }); - } - /** Get the `address` from transaction and comply with the `expectedAddress` */ async assertReceiptAddress(expectedAddress: string) { await test.step('Assert receiptAddress/Contract', async () => { diff --git a/packages/wallets/src/safe/safeIframe.page.ts b/packages/wallets/src/safe/safeIframe.page.ts index f0721580..8898f166 100644 --- a/packages/wallets/src/safe/safeIframe.page.ts +++ b/packages/wallets/src/safe/safeIframe.page.ts @@ -182,10 +182,6 @@ export class SafeIframePage implements WalletPage { throw new Error('Method not implemented.'); } - approveTokenTx?(): Promise { - throw new Error('Method not implemented.'); - } - getTokenBalance?(): Promise { throw new Error('Method not implemented.'); } diff --git a/packages/wallets/src/safe/safeWc.page.ts b/packages/wallets/src/safe/safeWc.page.ts index 53d0ae63..1f9b0d1a 100644 --- a/packages/wallets/src/safe/safeWc.page.ts +++ b/packages/wallets/src/safe/safeWc.page.ts @@ -88,10 +88,6 @@ export class SafeWcPage implements WalletPage { throw new Error('Method not implemented.'); } - approveTokenTx?(): Promise { - throw new Error('Method not implemented.'); - } - getTokenBalance?(): Promise { throw new Error('Method not implemented.'); } diff --git a/packages/wallets/src/trustwallet/trustWallet.page.ts b/packages/wallets/src/trustwallet/trustWallet.page.ts index 9a3062ba..776bb167 100644 --- a/packages/wallets/src/trustwallet/trustWallet.page.ts +++ b/packages/wallets/src/trustwallet/trustWallet.page.ts @@ -167,19 +167,6 @@ export class TrustWalletPage implements WalletPage { }); } - /** Confirm token approval transaction */ - async approveTokenTx() { - await test.step('Approve token TX', async () => { - const page = await getNotificationPage( - this.options.browserContext, - this.options.extensionUrl, - ); - const txPage = new WalletOperations(page); - await expect(txPage.confirmBtn).toBeEnabled(); - await txPage.confirmBtn.click(); - }); - } - /** Get the `address` from transaction and comply with the `expectedAddress` */ async assertReceiptAddress(expectedAddress: string) { await test.step('Assert receiptAddress/Contract', async () => { diff --git a/packages/wallets/src/wallet.page.ts b/packages/wallets/src/wallet.page.ts index c0022e49..a9dc323e 100644 --- a/packages/wallets/src/wallet.page.ts +++ b/packages/wallets/src/wallet.page.ts @@ -57,8 +57,6 @@ export interface WalletPage { cancelTx(): Promise; - approveTokenTx?(): Promise; - openLastTxInEthplorer?(txIndex?: number): Promise; getTokenBalance?(tokenName: string): Promise; diff --git a/packages/wallets/utils/helper.ts b/packages/wallets/utils/helper.ts index e04b03ee..36787a09 100644 --- a/packages/wallets/utils/helper.ts +++ b/packages/wallets/utils/helper.ts @@ -5,7 +5,7 @@ import { ConsoleLogger } from '@nestjs/common'; export async function getNotificationPage( context: BrowserContext, extensionUrl: string, - timeout = 10000, + timeout = 20000, ) { const logger = new ConsoleLogger('PageWaiter'); const isWalletPage = (page: Page) => page.url().includes(extensionUrl); @@ -17,10 +17,14 @@ export async function getNotificationPage( for (let i = 1; i <= attempts; i++) { walletPage = context.pages().find(isWalletPage); if (!walletPage) { - walletPage = await context.waitForEvent('page', { - predicate: isWalletPage, - timeout, - }); + try { + walletPage = await context.waitForEvent('page', { + predicate: isWalletPage, + timeout, + }); + } catch (er) { + // page isn't opened + } } if (walletPage) break; @@ -42,6 +46,32 @@ export async function getNotificationPage( logger.debug('Page loading timeout'); } }); - return walletPage; } + +/** + * This function waits for the wallet transaction page will be closed or changed after the confirmTx() or cancelTx() + */ +export async function waitForWalletPageClosed(txPage: Page, pageTitle: string) { + await test.step('Wait for walletPage closed', async () => { + while (!txPage.isClosed()) { + try { + await txPage.waitForEvent('close', { timeout: 5000 }); + break; + } catch (er) { + if ( + pageTitle !== + (await txPage + .locator('h2') + .textContent() + .catch(() => '')) + ) { + new ConsoleLogger('PageWaiter').debug( + 'The next tx page opened in the same window page', + ); + break; + } + } + } + }); +} From 14ce462d38bcc6353584951f11be9cff40e010a3 Mon Sep 17 00:00:00 2001 From: jake Date: Thu, 19 Feb 2026 16:26:31 +0300 Subject: [PATCH 3/5] fix: some fixes after run tests --- .../metamask/metamask-latest/metamask.page.ts | 10 ++++++++-- .../metamask/metamask-stable/metamask.page.ts | 10 ++++++++-- packages/wallets/src/wallets.constants.ts | 2 +- packages/wallets/utils/helper.ts | 17 +++++++++++------ 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/wallets/src/metamask/metamask-latest/metamask.page.ts b/packages/wallets/src/metamask/metamask-latest/metamask.page.ts index 42b1ab82..bd47ce3b 100644 --- a/packages/wallets/src/metamask/metamask-latest/metamask.page.ts +++ b/packages/wallets/src/metamask/metamask-latest/metamask.page.ts @@ -235,7 +235,10 @@ export class MetamaskPage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); - const pageTitle = await page.locator('h2').textContent(); + const pageTitle = await page + .locator('h2') + .textContent() + .catch(() => undefined); await new WalletOperationPage(page).confirmTransaction(setAggressiveGas); await waitForWalletPageClosed(page, pageTitle); }); @@ -247,7 +250,10 @@ export class MetamaskPage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); - const pageTitle = await page.locator('h2').textContent(); + const pageTitle = await page + .locator('h2') + .textContent() + .catch(() => undefined); await new WalletOperationPage(page).cancelTransaction(); await waitForWalletPageClosed(page, pageTitle); }); diff --git a/packages/wallets/src/metamask/metamask-stable/metamask.page.ts b/packages/wallets/src/metamask/metamask-stable/metamask.page.ts index 2208ccb3..52137392 100644 --- a/packages/wallets/src/metamask/metamask-stable/metamask.page.ts +++ b/packages/wallets/src/metamask/metamask-stable/metamask.page.ts @@ -256,7 +256,10 @@ export class MetamaskStablePage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); - const pageTitle = await page.locator('h2').textContent(); + const pageTitle = await page + .locator('h2') + .textContent() + .catch(() => undefined); await new WalletOperationPage(page).confirmTransaction(setAggressiveGas); await waitForWalletPageClosed(page, pageTitle); }); @@ -268,7 +271,10 @@ export class MetamaskStablePage implements WalletPage { this.options.browserContext, this.options.extensionUrl, ); - const pageTitle = await page.locator('h2').textContent(); + const pageTitle = await page + .locator('h2') + .textContent() + .catch(() => undefined); await new WalletOperationPage(page).cancelTransaction(); await waitForWalletPageClosed(page, pageTitle); }); diff --git a/packages/wallets/src/wallets.constants.ts b/packages/wallets/src/wallets.constants.ts index d1edfa4b..bb795d33 100644 --- a/packages/wallets/src/wallets.constants.ts +++ b/packages/wallets/src/wallets.constants.ts @@ -165,7 +165,7 @@ export const NETWORKS_CONFIG: { chainId: 48900, chainName: 'Zircuit', tokenSymbol: 'ETH', - rpcUrl: 'https://zircuit1-mainnet.p2pify.com', + rpcUrl: 'https://mainnet.zircuit.com', scan: '', }, LISK: { diff --git a/packages/wallets/utils/helper.ts b/packages/wallets/utils/helper.ts index 36787a09..2ef1e8af 100644 --- a/packages/wallets/utils/helper.ts +++ b/packages/wallets/utils/helper.ts @@ -52,19 +52,24 @@ export async function getNotificationPage( /** * This function waits for the wallet transaction page will be closed or changed after the confirmTx() or cancelTx() */ -export async function waitForWalletPageClosed(txPage: Page, pageTitle: string) { +export async function waitForWalletPageClosed( + txPage: Page, + pageTitle?: string, +) { await test.step('Wait for walletPage closed', async () => { - while (!txPage.isClosed()) { + const maxAttempts = 5; + for (let i = 1; i <= maxAttempts && !txPage.isClosed(); i++) { try { await txPage.waitForEvent('close', { timeout: 5000 }); break; } catch (er) { if ( + pageTitle && pageTitle !== - (await txPage - .locator('h2') - .textContent() - .catch(() => '')) + (await txPage + .locator('h2') + .textContent() + .catch(() => '')) ) { new ConsoleLogger('PageWaiter').debug( 'The next tx page opened in the same window page', From 7be024d83ded59fe0bdb2592f06a21e14e384f6d Mon Sep 17 00:00:00 2001 From: jake Date: Fri, 20 Feb 2026 12:11:48 +0300 Subject: [PATCH 4/5] chore: roll back release branch --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 09f0d32e..828a1189 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "branches": [ "main", { - "name": "feat/wait-for-tx", - "channel": "wait-for-tx-alpha", - "prerelease": "wait-for-tx-alpha" + "name": "develop", + "channel": "alpha", + "prerelease": "alpha" } ] }, From d61fe5ab3fa8efb86d63883110ba0a0381cd1fa2 Mon Sep 17 00:00:00 2001 From: jake Date: Fri, 20 Feb 2026 12:15:11 +0300 Subject: [PATCH 5/5] chore: remove unnecessary code --- packages/browser-service/src/browser.constants.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/browser-service/src/browser.constants.ts b/packages/browser-service/src/browser.constants.ts index dfe6feab..5b0d6b3b 100644 --- a/packages/browser-service/src/browser.constants.ts +++ b/packages/browser-service/src/browser.constants.ts @@ -11,7 +11,6 @@ import { SafeWcPage, TrustWalletPage, } from '@lidofinance/wallets-testing-wallets'; -import { z } from 'zod'; export const WALLET_PAGES = { metamaskStable: MetamaskStablePage, @@ -28,8 +27,3 @@ export const WALLET_PAGES = { }; export const DEFAULT_BROWSER_CONTEXT_DIR_NAME = '.browser_context'; - -export const BrowserExtension = z.object({ - id: z.string(), - name: z.string(), -});