From 196b7b73aaface37c3e30fdd011c84c51dfdd19d Mon Sep 17 00:00:00 2001 From: Martin Vere Cihlar Date: Thu, 27 Feb 2025 15:35:55 +0100 Subject: [PATCH 1/3] refactor(e2e): Refactor fixtures Split it to suiteBase and pageObjects + mocks fixure file slimmed down metadataProviderMock.stop() moved to fixture teardown untangle page fixture --- .../suite-desktop-core/e2e/support/common.ts | 9 +- .../e2e/support/fixtures.ts | 197 ++---------------- .../e2e/support/pageActions/marketActions.ts | 2 +- .../pageActions/onboarding/backupActions.ts | 8 +- .../onboarding/onboardingActions.ts | 8 +- .../pageActions/settings/coinActions.ts | 2 +- .../pageActions/settings/settingsActions.ts | 2 +- .../support/pageActions/trezorInputActions.ts | 2 +- .../{ => testExtends}/customMatchers.ts | 2 +- .../support/testExtends/suiteBaseFixture.ts | 165 +++++++++++++++ .../e2e/tests/bridge-tor/spawn-bridge.test.ts | 3 - .../tests/metadata/account-metadata.test.ts | 4 - .../tests/metadata/address-metadata.test.ts | 4 - .../tests/metadata/dropbox-api-errors.test.ts | 4 - .../tests/metadata/google-api-errors.test.ts | 4 - .../tests/metadata/interval-fetching.test.ts | 4 - .../tests/metadata/output-labeling.test.ts | 4 - .../tests/metadata/remembered-device.test.ts | 4 - .../tests/metadata/wallet-metadata.test.ts | 4 - 19 files changed, 208 insertions(+), 224 deletions(-) rename packages/suite-desktop-core/e2e/support/{ => testExtends}/customMatchers.ts (98%) create mode 100644 packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts diff --git a/packages/suite-desktop-core/e2e/support/common.ts b/packages/suite-desktop-core/e2e/support/common.ts index ce54bf41418..d3f9bf9b0db 100644 --- a/packages/suite-desktop-core/e2e/support/common.ts +++ b/packages/suite-desktop-core/e2e/support/common.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -import test, { TestInfo, _electron as electron } from '@playwright/test'; +import test, { ElectronApplication, Page, TestInfo, _electron as electron } from '@playwright/test'; import { readdirSync, removeSync } from 'fs-extra'; import { isEqual, omit } from 'lodash'; import path from 'path'; @@ -25,6 +25,11 @@ type LaunchSuiteParams = { viewport: { width: number; height: number }; }; +export type Suite = { + electronApp: ElectronApplication; + window: Page; +}; + const formatErrorLogMessage = (data: string) => { const red = '\x1b[31m'; const reset = '\x1b[0m'; @@ -101,7 +106,7 @@ export const launchSuiteElectronApp = async (params: LaunchSuiteParams) => { return electronApp; }; -export const launchSuite = async (params: LaunchSuiteParams) => { +export const launchSuite = async (params: LaunchSuiteParams): Promise => { const electronApp = await launchSuiteElectronApp(params); const window = await electronApp.firstWindow(); diff --git a/packages/suite-desktop-core/e2e/support/fixtures.ts b/packages/suite-desktop-core/e2e/support/fixtures.ts index ad210b9310d..eaa27598abe 100644 --- a/packages/suite-desktop-core/e2e/support/fixtures.ts +++ b/packages/suite-desktop-core/e2e/support/fixtures.ts @@ -1,21 +1,5 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { Page, test as base } from '@playwright/test'; - -import { - type Model, - type SetupEmu, - type StartEmu, - type TrezorUserEnvLinkClass, -} from '@trezor/trezor-user-env-link'; - import { AnalyticsFixture } from './analytics'; -import { - TrezorUserEnvLinkProxy, - getElectronVideoPath, - getUrl, - isDesktopProject, - launchSuite, -} from './common'; import { IndexedDbFixture } from './indexedDb'; import { BlockbookMock } from './mocks/blockBookMock'; import { MetadataProviderMock } from './mocks/metadataProviderMock'; @@ -32,18 +16,9 @@ import { SettingsActions } from './pageActions/settings/settingsActions'; import { SuiteGuide } from './pageActions/suiteGuideActions'; import { TrezorInputActions } from './pageActions/trezorInputActions'; import { WalletActions } from './pageActions/walletActions'; - -type StartEmuModelRequired = StartEmu & { model: Model }; +import { suiteBaseTest } from './testExtends/suiteBaseFixture'; type Fixtures = { - startEmulator: boolean; - setupEmulator: boolean; - emulatorStartConf: StartEmuModelRequired; - emulatorSetupConf: SetupEmu; - url: string; - trezorUserEnvLink: TrezorUserEnvLinkClass; - electronWindow: Page | undefined; - page: Page; dashboardPage: DashboardActions; settingsPage: SettingsActions; suiteGuidePage: SuiteGuide; @@ -61,164 +36,55 @@ type Fixtures = { metadataProviderMock: MetadataProviderMock; blockbookMock: BlockbookMock; tradingMock: TradingMock; - exceptionLogger: void; }; -const test = base.extend({ - startEmulator: true, - setupEmulator: true, - emulatorStartConf: { model: 'T3T1', wipe: true }, - emulatorSetupConf: {}, - /* eslint-disable-next-line no-empty-pattern */ - url: async ({}, use, testInfo) => { - await use(getUrl(testInfo)); - }, - /* eslint-disable-next-line no-empty-pattern */ - trezorUserEnvLink: async ({}, use) => { - await use(TrezorUserEnvLinkProxy); - }, - electronWindow: async ( - { - trezorUserEnvLink, - startEmulator, - setupEmulator, - emulatorStartConf, - emulatorSetupConf, - locale, - colorScheme, - }, - use, - testInfo, - ) => { - // We need to ensure emulator is running before launching the suite - await trezorUserEnvLink.logTestDetails( - ` - - - STARTING TEST ${testInfo.titlePath.join(' - ')}`, - ); - await trezorUserEnvLink.stopBridge(); - await trezorUserEnvLink.stopEmu(); - await trezorUserEnvLink.connect(); - if (startEmulator) { - await trezorUserEnvLink.startEmu(emulatorStartConf); - } - - if (startEmulator && setupEmulator) { - await trezorUserEnvLink.setupEmu(emulatorSetupConf); - } - - if (isDesktopProject(testInfo)) { - const suite = await launchSuite({ - locale, - colorScheme, - videoFolder: testInfo.outputDir, - viewport: testInfo.project.use.viewport!, - }); - await use(suite.window); - await suite.electronApp.close(); // Ensure cleanup after tests - } else { - if (startEmulator) { - await trezorUserEnvLink.startBridge(); - } - await use(undefined); - } - await trezorUserEnvLink.logTestDetails( - ` - - - FINISHING TEST ${testInfo.titlePath.join(' - ')}`, - ); - }, - page: async ({ electronWindow, page: webPage }, use, testInfo) => { - if (electronWindow) { - await webPage.close(); // Close the default chromium page - await electronWindow.context().tracing.start({ screenshots: true, snapshots: true }); - await use(electronWindow); - const tracePath = `${testInfo.outputDir}/trace.electron.zip`; - await electronWindow.context().tracing.stop({ path: tracePath }); - testInfo.attachments.push({ - name: 'trace', - path: tracePath, - contentType: 'application/zip', - }); - testInfo.attachments.push({ - name: 'video', - path: getElectronVideoPath(testInfo.outputDir), - contentType: 'video/webm', - }); - } else { - await webPage.context().addInitScript(() => { - // Tells the app to attach Redux Store to window object. packages/suite-web/src/support/usePlaywright.ts - window.Playwright = true; - }); - await webPage.goto('./'); - await use(webPage); - } - }, +const test = suiteBaseTest.extend({ dashboardPage: async ({ page, devicePrompt }, use) => { - const dashboardPage = new DashboardActions(page, devicePrompt); - await use(dashboardPage); + await use(new DashboardActions(page, devicePrompt)); }, settingsPage: async ({ page, url }, use) => { - const settingsPage = new SettingsActions(page, url); - await use(settingsPage); + await use(new SettingsActions(page, url)); }, suiteGuidePage: async ({ page }, use) => { - const suiteGuidePage = new SuiteGuide(page); - await use(suiteGuidePage); + await use(new SuiteGuide(page)); }, walletPage: async ({ page }, use) => { - const walletPage = new WalletActions(page); - await use(walletPage); - }, - onboardingPage: async ( - { page, analyticsPage, devicePrompt, emulatorStartConf }, - use, - testInfo, - ) => { - const onboardingPage = new OnboardingActions( - page, - analyticsPage, - devicePrompt, - emulatorStartConf.model, - testInfo, - ); - await use(onboardingPage); + await use(new WalletActions(page)); + }, + onboardingPage: async ({ page, emulatorStartConf }, use, testInfo) => { + await use(new OnboardingActions(page, emulatorStartConf.model, testInfo)); }, analyticsPage: async ({ page }, use) => { - const analyticsPage = new AnalyticsActions(page); - await use(analyticsPage); + await use(new AnalyticsActions(page)); }, devicePrompt: async ({ page }, use) => { - const devicePromptActions = new DevicePromptActions(page); - await use(devicePromptActions); + await use(new DevicePromptActions(page)); }, recoveryPage: async ({ page }, use) => { - const recoveryPage = new RecoveryActions(page); - await use(recoveryPage); + await use(new RecoveryActions(page)); }, marketPage: async ({ page }, use) => { - const marketPage = new MarketActions(page); - await use(marketPage); + await use(new MarketActions(page)); }, assetsPage: async ({ page }, use) => { - const assetPage = new AssetsActions(page); - await use(assetPage); + await use(new AssetsActions(page)); }, metadataPage: async ({ page, devicePrompt }, use) => { - const metadataPage = new MetadataActions(page, devicePrompt); - await use(metadataPage); + await use(new MetadataActions(page, devicePrompt)); }, trezorInput: async ({ page }, use) => { - const trezorInput = new TrezorInputActions(page); - await use(trezorInput); + await use(new TrezorInputActions(page)); }, analytics: async ({ page }, use) => { - const analytics = new AnalyticsFixture(page); - await use(analytics); + await use(new AnalyticsFixture(page)); }, indexedDb: async ({ page }, use) => { - const indexedDb = new IndexedDbFixture(page); - await use(indexedDb); + await use(new IndexedDbFixture(page)); }, metadataProviderMock: async ({ page }, use) => { const metadataProviderMock = new MetadataProviderMock(page); await use(metadataProviderMock); + await metadataProviderMock.stop(); }, /* eslint-disable-next-line no-empty-pattern */ blockbookMock: async ({}, use) => { @@ -227,28 +93,9 @@ const test = base.extend({ blockbookMock.stop(); }, tradingMock: async ({ page }, use) => { - const tradingMock = new TradingMock(page); - await use(tradingMock); - }, - exceptionLogger: [ - async ({ page }, use) => { - const errors: Error[] = []; - page.on('pageerror', error => { - errors.push(error); - }); - - await use(); - - if (errors.length > 0) { - throw new Error( - `There was a JS exception during test run. - \n${errors.map(error => `${error.message}\n${error.stack}`).join('\n-----\n')}`, - ); - } - }, - { auto: true }, - ], + await use(new TradingMock(page)); + }, }); export { test }; -export { expect } from './customMatchers'; +export { expect } from './testExtends/customMatchers'; diff --git a/packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts index 5d3191cf100..081f8e5293d 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts @@ -6,9 +6,9 @@ import { NetworkSymbol } from '@suite-common/wallet-config'; import { buyQuotesBTC, invityEndpoint } from '../../fixtures/invity'; import { TrezorUserEnvLinkProxy, step } from '../common'; -import { expect } from '../customMatchers'; import { DevicePromptActions } from './devicePromptActions'; import { solanaUrlPattern } from '../mocks/tradingMock'; +import { expect } from '../testExtends/customMatchers'; const quoteProviderLocator = '@trading/offers/quote/provider'; const quoteAmountLocator = '@trading/offers/quote/crypto-amount'; diff --git a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/backupActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/backupActions.ts index 7b94a6eeb81..6bd0cbc66d3 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/backupActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/backupActions.ts @@ -4,6 +4,8 @@ import { TrezorUserEnvLinkProxy, step } from '../../common'; import { DevicePromptActions } from '../devicePromptActions'; export class BackupActions { + private devicePrompt: DevicePromptActions; + readonly startButton: Locator; readonly undertandWhatSeedIsCheckbox: Locator; readonly hasEnoughTimeCheckbox: Locator; @@ -13,10 +15,8 @@ export class BackupActions { readonly willHideSeedCheckbox: Locator; readonly closeButton: Locator; - constructor( - private page: Page, - private devicePrompt: DevicePromptActions, - ) { + constructor(private page: Page) { + this.devicePrompt = new DevicePromptActions(page); this.startButton = page.getByTestId('@backup/start-button'); this.undertandWhatSeedIsCheckbox = page.getByTestId( '@backup/check-item/understands-what-seed-is', diff --git a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts index b2dc7461171..d5724520cc3 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts @@ -17,6 +17,8 @@ export class OnboardingActions { readonly firmware: FirmwareActions; readonly pin: PinActions; readonly tutorial: TutorialActions; + private readonly devicePrompt: DevicePromptActions; + private readonly analyticsPage: AnalyticsActions; readonly welcomeBody: Locator; readonly onboardingContinueButton: Locator; @@ -42,12 +44,12 @@ export class OnboardingActions { constructor( public page: Page, - private analyticsPage: AnalyticsActions, - private readonly devicePrompt: DevicePromptActions, private readonly model: Model, private readonly testInfo: TestInfo, ) { - this.backup = new BackupActions(page, devicePrompt); + this.analyticsPage = new AnalyticsActions(page); + this.devicePrompt = new DevicePromptActions(page); + this.backup = new BackupActions(page); this.firmware = new FirmwareActions(page); this.tutorial = new TutorialActions(page); this.pin = new PinActions(page); diff --git a/packages/suite-desktop-core/e2e/support/pageActions/settings/coinActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/settings/coinActions.ts index 34a3e6c9e2c..c4bb2b34c11 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/settings/coinActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/settings/coinActions.ts @@ -3,7 +3,7 @@ import { Locator, Page } from '@playwright/test'; import { BackendType, NetworkSymbol } from '@suite-common/wallet-config'; import { step } from '../../common'; -import { expect } from '../../customMatchers'; +import { expect } from '../../testExtends/customMatchers'; export class CoinsActions { readonly networkButton = (symbol: NetworkSymbol) => diff --git a/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts index 99e519082b7..0bcc6122e36 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/settings/settingsActions.ts @@ -5,7 +5,7 @@ import { capitalizeFirstLetter } from '@trezor/utils'; import { CoinsActions } from './coinActions'; import { DeviceActions } from './deviceActions'; import { TrezorUserEnvLinkProxy, step } from '../../common'; -import { expect } from '../../customMatchers'; +import { expect } from '../../testExtends/customMatchers'; export enum Theme { System = 'system', diff --git a/packages/suite-desktop-core/e2e/support/pageActions/trezorInputActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/trezorInputActions.ts index 8b52d1d5cba..3b7d444b2a4 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/trezorInputActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/trezorInputActions.ts @@ -1,7 +1,7 @@ import { Locator, Page, test } from '@playwright/test'; import { TrezorUserEnvLinkProxy, step } from '../common'; -import { expect } from '../customMatchers'; +import { expect } from '../testExtends/customMatchers'; export class TrezorInputActions { readonly wordSelectInput: Locator; diff --git a/packages/suite-desktop-core/e2e/support/customMatchers.ts b/packages/suite-desktop-core/e2e/support/testExtends/customMatchers.ts similarity index 98% rename from packages/suite-desktop-core/e2e/support/customMatchers.ts rename to packages/suite-desktop-core/e2e/support/testExtends/customMatchers.ts index 84259668908..a556e1a81f3 100644 --- a/packages/suite-desktop-core/e2e/support/customMatchers.ts +++ b/packages/suite-desktop-core/e2e/support/testExtends/customMatchers.ts @@ -1,7 +1,7 @@ import { Locator, Request, expect as baseExpect } from '@playwright/test'; import { diff } from 'jest-diff'; -import { isEqualWithOmit } from './common'; +import { isEqualWithOmit } from '../common'; const compareTextAndNumber = async ( locator: Locator, diff --git a/packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts b/packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts new file mode 100644 index 00000000000..beaba0d1133 --- /dev/null +++ b/packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts @@ -0,0 +1,165 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { Browser, Page, TestInfo, test as base } from '@playwright/test'; + +import { Model, SetupEmu, StartEmu, TrezorUserEnvLinkClass } from '@trezor/trezor-user-env-link'; + +import { + Suite, + TrezorUserEnvLinkProxy, + getElectronVideoPath, + getUrl, + isDesktopProject, + launchSuite, +} from '../common'; + +type StartEmuModelRequired = StartEmu & { model: Model }; + +type suiteBaseFixture = { + startEmulator: boolean; + setupEmulator: boolean; + emulatorStartConf: StartEmuModelRequired; + emulatorSetupConf: SetupEmu; + url: string; + trezorUserEnvLink: TrezorUserEnvLinkClass; + page: Page; + exceptionLogger: void; +}; + +const electronSetup = async (testInfo: TestInfo, locale: string | undefined, colorScheme: any) => { + const suite = await launchSuite({ + locale, + colorScheme, + videoFolder: testInfo.outputDir, + viewport: testInfo.project.use.viewport!, + }); + + await suite.window.context().tracing.start({ screenshots: true, snapshots: true }); + + return suite; +}; + +const electronTeardown = async (suite: Suite, testInfo: TestInfo) => { + const tracePath = `${testInfo.outputDir}/trace.electron.zip`; + await suite.window.context().tracing.stop({ path: tracePath }); + testInfo.attachments.push({ + name: 'trace', + path: tracePath, + contentType: 'application/zip', + }); + testInfo.attachments.push({ + name: 'video', + path: getElectronVideoPath(testInfo.outputDir), + contentType: 'video/webm', + }); + await suite.electronApp.close(); +}; + +const webSetup = async (browser: Browser) => { + await TrezorUserEnvLinkProxy.startBridge(); + const context = await browser.newContext(); + const page = await context.newPage(); + // Tells the app to attach Redux Store to window object. packages/suite-web/src/support/usePlaywright.ts + // Which is needed for methods manupalating Redux store like onboardingActions.disableFirmwareHashCheck + await page.context().addInitScript(() => { + (window as any).Playwright = true; + }); + await page.goto('./'); + + return page; +}; + +const trezorEnvSetup = async ( + testInfo: TestInfo, + startEmulator: boolean, + setupEmulator: boolean, + emulatorStartConf: StartEmu, + emulatorSetupConf: SetupEmu, +) => { + await TrezorUserEnvLinkProxy.logTestDetails( + ` - - - STARTING TEST ${testInfo.titlePath.join(' - ')}`, + ); + // We cannot rely on that previous teardown was done correctly + await TrezorUserEnvLinkProxy.stopBridge(); + await TrezorUserEnvLinkProxy.stopEmu(); + await TrezorUserEnvLinkProxy.connect(); + if (startEmulator) { + await TrezorUserEnvLinkProxy.startEmu(emulatorStartConf); + } + if (startEmulator && setupEmulator) { + await TrezorUserEnvLinkProxy.setupEmu(emulatorSetupConf); + } +}; + +// This is the base Suite text fixture containing all the necessary setup and core page object +// Depending on the project type (desktop or web) it will launch the appropriate environment +// and provide the necessary page object which is either electron window or web page +const suiteBaseTest = base.extend({ + startEmulator: true, + setupEmulator: true, + emulatorStartConf: { model: 'T3T1', wipe: true }, + emulatorSetupConf: {}, + /* eslint-disable-next-line no-empty-pattern */ + url: async ({}, use, testInfo) => { + await use(getUrl(testInfo)); + }, + /* eslint-disable-next-line no-empty-pattern */ + trezorUserEnvLink: async ({}, use) => { + await use(TrezorUserEnvLinkProxy); + }, + page: async ( + { + locale, + colorScheme, + browser, + startEmulator, + setupEmulator, + emulatorStartConf, + emulatorSetupConf, + }, + use, + testInfo, + ) => { + // This Trezor env setup needs to happen before electron or web page are launched + await trezorEnvSetup( + testInfo, + startEmulator, + setupEmulator, + emulatorStartConf, + emulatorSetupConf, + ); + + if (isDesktopProject(testInfo)) { + const suite = await electronSetup(testInfo, locale, colorScheme); + await use(suite.window); + await electronTeardown(suite, testInfo); + } else { + await TrezorUserEnvLinkProxy.startBridge(); + const page = await webSetup(browser); + await use(page); + } + + await TrezorUserEnvLinkProxy.logTestDetails( + ` - - - FINISHING TEST ${testInfo.titlePath.join(' - ')}`, + ); + }, + exceptionLogger: [ + async ({ page }, use) => { + const errors: Error[] = []; + page.on('pageerror', error => { + errors.push(error); + }); + + await use(); + + if (errors.length > 0) { + throw new Error( + `There was a JS exception during test run. + \n${errors.map(error => `${error.message}\n${error.stack}`).join('\n-----\n')}`, + ); + } + }, + { auto: true }, + ], +}); + +export { suiteBaseTest }; diff --git a/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-bridge.test.ts b/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-bridge.test.ts index 839e307a27f..bd198c6417d 100644 --- a/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-bridge.test.ts +++ b/packages/suite-desktop-core/e2e/tests/bridge-tor/spawn-bridge.test.ts @@ -6,7 +6,6 @@ import { } from '../../support/bridge'; import { LEGACY_BRIDGE_VERSION, launchSuite, skipFixture } from '../../support/common'; import { expect, test } from '../../support/fixtures'; -import { AnalyticsActions } from '../../support/pageActions/analyticsActions'; import { DevicePromptActions } from '../../support/pageActions/devicePromptActions'; import { OnboardingActions } from '../../support/pageActions/onboarding/onboardingActions'; @@ -67,8 +66,6 @@ test.describe.serial('Bridge', { tag: ['@group=suite', '@desktopOnly'] }, () => const onboardingPage = new OnboardingActions( suite.window, - new AnalyticsActions(suite.window), - devicePrompt, trezorUserEnvLink.defaultModel, testInfo, ); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/account-metadata.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/account-metadata.test.ts index 9c8e2cf5fb4..45999d9c54d 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/account-metadata.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/account-metadata.test.ts @@ -112,8 +112,4 @@ test.describe('Account metadata', { tag: ['@group=metadata1', '@webOnly'] }, () 'adding label to a newly added account. does it work?', ); }); - - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); }); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/address-metadata.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/address-metadata.test.ts index e4229c929c6..10c03924df1 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/address-metadata.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/address-metadata.test.ts @@ -39,8 +39,4 @@ test.describe('Metadata - address labeling', { tag: ['@group=metadata1', '@webOn await page.keyboard.press('Enter'); await expect(page.getByTestId(metadataEl)).toHaveText('meow meow'); }); - - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); }); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/dropbox-api-errors.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/dropbox-api-errors.test.ts index 145a9640fed..12b6e5beb51 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/dropbox-api-errors.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/dropbox-api-errors.test.ts @@ -152,9 +152,5 @@ test.describe('Dropbox API errors', { tag: ['@group=metadata1', '@webOnly'] }, ( await metadataPage.account.editLabel(AccountLabelId.BitcoinDefault1, 'Kvooo'); }); - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); - // TODO: Add tests for more possible errors }); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/google-api-errors.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/google-api-errors.test.ts index b9dd267aeac..8f22ce068a9 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/google-api-errors.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/google-api-errors.test.ts @@ -58,10 +58,6 @@ test.describe('Google API errors', { tag: ['@group=metadata1', '@webOnly'] }, () ); }); - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); - // TODO: Add tests for more possible errors // Reference: https://developers.google.com/drive/api/v3/handle-errors }); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/interval-fetching.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/interval-fetching.test.ts index 43d50b26e4f..ebea16223c5 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/interval-fetching.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/interval-fetching.test.ts @@ -63,8 +63,4 @@ test.describe('Account metadata', { tag: ['@group=metadata1', '@webOnly'] }, () ); }); }); - - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); }); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/output-labeling.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/output-labeling.test.ts index f40f01a2b7c..2158a72de76 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/output-labeling.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/output-labeling.test.ts @@ -88,8 +88,4 @@ test.describe('Metadata - Output labeling', { tag: ['@group=metadata1', '@webOnl expect(fileContent).toContain(expectedSubstr); expect(typeof fileContent).toBe('string'); }); - - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); }); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/remembered-device.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/remembered-device.test.ts index b700f850c7c..3f23c5dfafa 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/remembered-device.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/remembered-device.test.ts @@ -116,8 +116,4 @@ test.describe('Remembered device', { tag: ['@group=metadata2', '@webOnly'] }, () metadataPage.account.addLabelButton(AccountLabelId.BitcoinDefault1), ).not.toBeVisible(); }); - - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); }); diff --git a/packages/suite-desktop-core/e2e/tests/metadata/wallet-metadata.test.ts b/packages/suite-desktop-core/e2e/tests/metadata/wallet-metadata.test.ts index 8097f226616..f7dddcac340 100644 --- a/packages/suite-desktop-core/e2e/tests/metadata/wallet-metadata.test.ts +++ b/packages/suite-desktop-core/e2e/tests/metadata/wallet-metadata.test.ts @@ -116,8 +116,4 @@ test.describe('Metadata - wallet labeling', { tag: ['@group=metadata2', '@webOnl 'still works, metadata enabled for currently not selected device', ); }); - - test.afterEach(async ({ metadataProviderMock }) => { - await metadataProviderMock.stop(); - }); }); From a4a74aa2d857b9de7a6fcb2d1759f69103c967bf Mon Sep 17 00:00:00 2001 From: Martin Vere Cihlar Date: Thu, 27 Feb 2025 17:32:51 +0100 Subject: [PATCH 2/3] fix(e2e): Fix handling of browser context also adjust multiple-session tests --- .../pageActions/onboarding/onboardingActions.ts | 2 +- .../e2e/support/testExtends/suiteBaseFixture.ts | 15 ++++++++------- .../e2e/tests/suite/multiple-sessions.test.ts | 16 ++++++++++++++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts index d5724520cc3..adf93c2f42e 100644 --- a/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts +++ b/packages/suite-desktop-core/e2e/support/pageActions/onboarding/onboardingActions.ts @@ -44,7 +44,7 @@ export class OnboardingActions { constructor( public page: Page, - private readonly model: Model, + readonly model: Model, private readonly testInfo: TestInfo, ) { this.analyticsPage = new AnalyticsActions(page); diff --git a/packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts b/packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts index beaba0d1133..0915a9ea62c 100644 --- a/packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts +++ b/packages/suite-desktop-core/e2e/support/testExtends/suiteBaseFixture.ts @@ -1,5 +1,5 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { Browser, Page, TestInfo, test as base } from '@playwright/test'; +import { BrowserContext, Page, TestInfo, test as base } from '@playwright/test'; import { Model, SetupEmu, StartEmu, TrezorUserEnvLinkClass } from '@trezor/trezor-user-env-link'; @@ -54,14 +54,13 @@ const electronTeardown = async (suite: Suite, testInfo: TestInfo) => { await suite.electronApp.close(); }; -const webSetup = async (browser: Browser) => { +const webSetup = async (browserContext: BrowserContext) => { await TrezorUserEnvLinkProxy.startBridge(); - const context = await browser.newContext(); - const page = await context.newPage(); + const page = await browserContext.newPage(); // Tells the app to attach Redux Store to window object. packages/suite-web/src/support/usePlaywright.ts // Which is needed for methods manupalating Redux store like onboardingActions.disableFirmwareHashCheck await page.context().addInitScript(() => { - (window as any).Playwright = true; + window.Playwright = true; }); await page.goto('./'); @@ -133,9 +132,11 @@ const suiteBaseTest = base.extend({ await use(suite.window); await electronTeardown(suite, testInfo); } else { - await TrezorUserEnvLinkProxy.startBridge(); - const page = await webSetup(browser); + const browserContext = await browser.newContext(); + const page = await webSetup(browserContext); await use(page); + await page.close(); + await browserContext.close(); } await TrezorUserEnvLinkProxy.logTestDetails( diff --git a/packages/suite-desktop-core/e2e/tests/suite/multiple-sessions.test.ts b/packages/suite-desktop-core/e2e/tests/suite/multiple-sessions.test.ts index e9c91d2e6e1..4ba00f12d4d 100644 --- a/packages/suite-desktop-core/e2e/tests/suite/multiple-sessions.test.ts +++ b/packages/suite-desktop-core/e2e/tests/suite/multiple-sessions.test.ts @@ -3,6 +3,7 @@ import { BridgeTransport } from '@trezor/transport'; import { expect, test } from '../../support/fixtures'; import { DashboardActions } from '../../support/pageActions/dashboardActions'; +import { OnboardingActions } from '../../support/pageActions/onboarding/onboardingActions'; const stealBridgeSession = async () => { await test.step('Steal Bridge session', async () => { @@ -84,16 +85,27 @@ test.describe('Multiple sessions', { tag: ['@group=suite'] }, () => { test( 'Overtake session by opening suite new tab', { tag: ['@webOnly'] }, - async ({ context, onboardingPage, dashboardPage, devicePrompt }) => { + async ({ context, onboardingPage, dashboardPage, devicePrompt }, testInfo) => { await onboardingPage.completeOnboarding(); await dashboardPage.discoveryShouldFinish(); const pageTwo = await context.newPage(); - await pageTwo.goto(''); + await pageTwo.context().addInitScript(() => { + window.Playwright = true; + }); + await pageTwo.goto('./'); + const onboardingPageTwo = new OnboardingActions( + pageTwo, + onboardingPage.model, + testInfo, + ); + await onboardingPageTwo.completeOnboarding(); const dashboardPageTwo = new DashboardActions(pageTwo, devicePrompt); await dashboardPageTwo.discoveryShouldFinish(); await expect(dashboardPageTwo.deviceStatus).toHaveText('Connected'); await expect(dashboardPage.deviceStatus).toHaveText('Refresh'); + + await pageTwo.close(); }, ); From 0a29d6c6f48f8499dd35ddbf9592647958e715de Mon Sep 17 00:00:00 2001 From: Martin Vere Cihlar Date: Thu, 27 Feb 2025 18:18:33 +0100 Subject: [PATCH 3/3] refactor(e2e): Add timeout throw Error to emulator press action on API lvl --- packages/trezor-user-env-link/src/api.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/trezor-user-env-link/src/api.ts b/packages/trezor-user-env-link/src/api.ts index 9d667a0c8f8..4890aa24e10 100644 --- a/packages/trezor-user-env-link/src/api.ts +++ b/packages/trezor-user-env-link/src/api.ts @@ -79,6 +79,20 @@ interface ReadAndConfirmShamirMnemonicEmu { type StartBridgeVersion = '2.0.32' | '2.0.33' | 'node-bridge'; +// Currently, emulator can timeout on press action, suite does not have any way to know about it +// Without this promiseWithTimeout, suite would fail on next step which complicates debugging +export function promiseWithTimeout( + promise: Promise, + errorMessage: string, + timeout = 4_500, +): Promise { + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error(errorMessage)), timeout), + ); + + return Promise.race([promise, timeoutPromise]); +} + export const MNEMONICS = { mnemonic_all: 'all all all all all all all all all all all all', mnemonic_12: 'alcohol woman abuse must during monitor noble actual mixed trade anger aisle', @@ -243,12 +257,18 @@ export class TrezorUserEnvLinkClass extends TypedEmitter return null; } async pressYes() { - await this.client.send({ type: 'emulator-press-yes' }); + await promiseWithTimeout( + this.client.send({ type: 'emulator-press-yes' }), + 'Emulator did not respond to press command in time', + ); return null; } async pressNo() { - await this.client.send({ type: 'emulator-press-no' }); + await promiseWithTimeout( + this.client.send({ type: 'emulator-press-no' }), + 'Emulator did not respond to press command in time', + ); return null; }