Skip to content

Commit

Permalink
Feat e2e convert to pw suite coinmarket (#16065)
Browse files Browse the repository at this point in the history
* feat(e2e): Convert to playwright new suite: buy-coins.tests.ts

adds commented invity data mocking that needs to be fixed
first visual comparison in tests

* refactor(e2e): Cleans up paramaeters of playwright fixtures

* refactor(e2e): Cleans up the new buy-coins.test.ts

Remove invity unfunctional mocks
improve visual verification
minor other refactoring

* feat(e2e): Enable @other group and nightly web PW test runs

* fix(e2e): Fix proper mask of visual comparison in buy-coins.test.ts

* fix(e2e): Wrong test group

* fix(e2e): Sets default resolution and reverts one locator needed by cy tests

refactor waiting for offer sync

* fix(e2e): Solves breakdown problems of electron

improves waits for offer sync

* fix(e2e): Disable snapshots for Desktop project

minor refactoring and tuning of buy-coin.tests.ts

* chore(e2e): Removes refactored test from cy

* fix(e2e): Solve default currency issue in buy-coin.tests.ts

improves stability of settings tests

* fix(e2e): Solves delayed best offer and geolocation

* fix(e2e): Rework waiting for offer sync and disables snapshots for web

* fix(e2e): Attempt to enable snapshots on web tests
  • Loading branch information
Vere-Grey authored Dec 28, 2024
1 parent 490d1af commit 2121c5e
Show file tree
Hide file tree
Showing 26 changed files with 298 additions and 240 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test-suite-desktop-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ jobs:
# CONTAINERS: "trezor-user-env-unix"
# - TEST_GROUP: "@group=passphrase"
# CONTAINERS: "trezor-user-env-unix"
# - TEST_GROUP: "@group=other"
# CONTAINERS: "trezor-user-env-unix"
- TEST_GROUP: "@group=other"
CONTAINERS: "trezor-user-env-unix"
- TEST_GROUP: "@group=wallet"
CONTAINERS: "trezor-user-env-unix"

Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/test-suite-web-e2e-pw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ on:
- ".github/workflows/release*"
- ".github/workflows/template*"
- ".github/actions/release*/**"
schedule:
- cron: "0 0 * * *"
workflow_dispatch:

env:
Expand Down Expand Up @@ -103,8 +105,8 @@ jobs:
# CONTAINERS: "trezor-user-env-unix"
# - TEST_GROUP: "@group=passphrase"
# CONTAINERS: "trezor-user-env-unix"
# - TEST_GROUP: "@group=other"
# CONTAINERS: "trezor-user-env-unix"
- TEST_GROUP: "@group=other"
CONTAINERS: "trezor-user-env-unix"
- TEST_GROUP: "@group=wallet"
CONTAINERS: "trezor-user-env-unix bitcoin-regtest"

Expand Down
10 changes: 9 additions & 1 deletion packages/suite-desktop-core/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,32 @@ const config: PlaywrightTestConfig = {
name: PlaywrightProjects.Desktop,
use: {},
grepInvert: /@webOnly/,
//TODO: #16073 We cannot set resolution for Electron. Once solved, remove ignoreSnapshots
ignoreSnapshots: true,
},
],
testDir: 'tests',
workers: 1, // to disable parallelism between test files
use: {
viewport: { width: 1280, height: 720 },
headless: process.env.HEADLESS === 'true',
trace: 'on',
video: 'on',
screenshot: 'on',
testIdAttribute: 'data-testid',
actionTimeout: 30000,
},
reportSlowTests: null,
reporter: process.env.GITHUB_ACTION
? [['list'], ['@currents/playwright'], ['html', { open: 'never' }]]
: [['list'], ['html', { open: 'never' }]],
timeout: process.env.GITHUB_ACTION ? timeoutCIRun : timeoutLocalRun,
outputDir: path.join(__dirname, 'test-results'),
snapshotPathTemplate: 'snapshots/{projectName}/{testFilePath}/{arg}{ext}',
expect: {
toHaveScreenshot: {
maxDiffPixelRatio: 0.001,
},
},
};

// eslint-disable-next-line import/no-default-export
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions packages/suite-desktop-core/e2e/support/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,15 @@ export const launchSuite = async (params: LaunchSuiteParams = {}) => {
return { electronApp, window };
};

export const isDesktopProject = (testInfo: TestInfo) =>
testInfo.project.name === PlaywrightProjects.Desktop;

export const isWebProject = (testInfo: TestInfo) =>
testInfo.project.name === PlaywrightProjects.Web;

export const getApiUrl = (webBaseUrl: string | undefined, testInfo: TestInfo) => {
const electronApiURL = 'file:///';
const apiURL =
testInfo.project.name === PlaywrightProjects.Desktop ? electronApiURL : webBaseUrl;
const apiURL = isDesktopProject(testInfo) ? electronApiURL : webBaseUrl;
if (!apiURL) {
throw new Error('apiURL is not defined');
}
Expand Down
11 changes: 8 additions & 3 deletions packages/suite-desktop-core/e2e/support/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import {
} from '@trezor/trezor-user-env-link';

import { DashboardActions } from './pageActions/dashboardActions';
import { getApiUrl, getElectronVideoPath, launchSuite } from './common';
import { getApiUrl, getElectronVideoPath, isDesktopProject, launchSuite } from './common';
import { SettingsActions } from './pageActions/settingsActions';
import { SuiteGuide } from './pageActions/suiteGuideActions';
import { WalletActions } from './pageActions/walletActions';
import { OnboardingActions } from './pageActions/onboardingActions';
import { PlaywrightProjects } from '../playwright.config';
import { AnalyticsFixture } from './analytics';
import { BackupActions } from './pageActions/backupActions';
import { DevicePromptActions } from './pageActions/devicePromptActions';
import { AnalyticsActions } from './pageActions/analyticsActions';
import { IndexedDbFixture } from './indexedDb';
import { RecoverActions } from './pageActions/recoverActions';
import { WordInputActions } from './pageActions/wordInputActions';
import { MarketActions } from './pageActions/marketActions';

type Fixtures = {
startEmulator: boolean;
Expand All @@ -44,6 +44,7 @@ type Fixtures = {
wordInputPage: WordInputActions;
analytics: AnalyticsFixture;
indexedDb: IndexedDbFixture;
marketPage: MarketActions;
};

const test = base.extend<Fixtures>({
Expand Down Expand Up @@ -83,7 +84,7 @@ const test = base.extend<Fixtures>({
await trezorUserEnvLink.setupEmu(emulatorSetupConf);
}

if (testInfo.project.name === PlaywrightProjects.Desktop) {
if (isDesktopProject(testInfo)) {
const suite = await launchSuite({
locale,
colorScheme,
Expand Down Expand Up @@ -177,6 +178,10 @@ const test = base.extend<Fixtures>({
const indexedDb = new IndexedDbFixture(page);
await use(indexedDb);
},
marketPage: async ({ page }, use) => {
const marketPage = new MarketActions(page);
await use(marketPage);
},
});

export { test };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Locator, Page, expect } from '@playwright/test';
import { NetworkSymbol } from '@suite-common/wallet-config';

export class DashboardActions {
private readonly page: Page;
readonly dashboardMenuButton: Locator;
readonly discoveryHeader: Locator;
readonly discoveryBar: Locator;
Expand All @@ -19,8 +18,7 @@ export class DashboardActions {
readonly balanceOfNetwork = (symbol: NetworkSymbol) =>
this.page.getByTestId(`@wallet/coin-balance/value-${symbol}`);

constructor(page: Page) {
this.page = page;
constructor(private readonly page: Page) {
this.dashboardMenuButton = this.page.getByTestId('@suite/menu/suite-index');
this.discoveryHeader = this.page.getByRole('heading', { name: 'Dashboard' });
this.discoveryBar = this.page.getByTestId('@wallet/discovery-progress-bar');
Expand Down
149 changes: 149 additions & 0 deletions packages/suite-desktop-core/e2e/support/pageActions/marketActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Locator, Page, expect } from '@playwright/test';

import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link';
import { FiatCurrencyCode } from '@suite-common/suite-config';
import regional from '@trezor/suite/src/constants/wallet/coinmarket/regional';

const getCountryLabel = (country: string) => {
const labelWithFlag = regional.countriesMap.get(country);
if (!labelWithFlag) {
throw new Error(`Country ${country} not found in the countries map`);
}

return labelWithFlag.substring(labelWithFlag.indexOf(' ') + 1);
};

export class MarketActions {
readonly offerSpinner: Locator;
readonly layout: Locator;
readonly form: Locator;
readonly bestOfferProvider: Locator;
readonly bestOfferYouGet: Locator;
readonly bestOfferAmount: Locator;
readonly buyBestOfferButton: Locator;
readonly youPayInput: Locator;
readonly youPayCurrencyDropdown: Locator;
readonly youPayCurrencyOption = (currency: FiatCurrencyCode) =>
this.page.getByTestId(`@coinmarket/form/fiat-currency-select/option/${currency}`);
readonly countryOfResidenceDropdown: Locator;
readonly buyOffersPage: Locator;
readonly compareButton: Locator;
readonly quotes: Locator;
readonly quoteOfProvider = (provider: string) =>
this.page.getByTestId(`@coinmarket/offers/quote-${provider}`);
readonly quoteProvider: Locator;
readonly quoteAmount: Locator;
readonly selectThisQuoteButton: Locator;
readonly modal: Locator;
readonly buyTermsConfirmButton: Locator;
readonly confirmOnTrezorButton: Locator;
readonly confirmOnDevicePrompt: Locator;
readonly tradeConfirmation: Locator;
readonly tradeConfirmationCryptoAmount: Locator;
readonly tradeConfirmationProvider: Locator;
readonly tradeConfirmationContinueButton: Locator;

constructor(private page: Page) {
this.offerSpinner = this.page.getByTestId('@coinmarket/offers/loading-spinner');
this.layout = this.page.getByTestId('@coinmarket');
this.form = this.page.getByTestId('@coinmarket/form');
this.bestOfferProvider = this.page.getByTestId('@coinmarket/offers/quote/provider');
this.bestOfferYouGet = this.page.getByTestId('@coinmarket/best-offer/amount');
this.bestOfferAmount = this.page.getByTestId('@coinmarket/form/offer/crypto-amount');
this.buyBestOfferButton = this.page.getByTestId('@coinmarket/form/buy-button');
this.youPayInput = this.page.getByTestId('@coinmarket/form/fiat-input');
this.youPayCurrencyDropdown = this.page.getByTestId(
'@coinmarket/form/fiat-currency-select/input',
);
this.countryOfResidenceDropdown = this.page.getByTestId(
'@coinmarket/form/country-select/input',
);
this.buyOffersPage = this.page.getByTestId('@coinmarket/buy-offers');
this.compareButton = this.page.getByTestId('@coinmarket/form/compare-button');
this.quotes = this.page.getByTestId('@coinmarket/offers/quote');
this.quoteProvider = this.page.getByTestId('@coinmarket/offers/quote/provider');
this.quoteAmount = this.page.getByTestId('@coinmarket/offers/quote/crypto-amount');
this.selectThisQuoteButton = this.page.getByTestId(
'@coinmarket/offers/get-this-deal-button',
);
this.modal = this.page.getByTestId('@modal');
this.buyTermsConfirmButton = this.page.getByTestId(
'@coinmarket/buy/offers/buy-terms-confirm-button',
);
this.confirmOnTrezorButton = this.page.getByTestId(
'@coinmarket/offer/confirm-on-trezor-button',
);
this.confirmOnDevicePrompt = this.page.getByTestId('@prompts/confirm-on-device');
this.tradeConfirmation = this.page.getByTestId('@coinmarket/selected-offer');
this.tradeConfirmationCryptoAmount = this.page.getByTestId(
'@coinmarket/form/info/crypto-amount',
);
this.tradeConfirmationProvider = this.page.getByTestId('@coinmarket/form/info/provider');
this.tradeConfirmationContinueButton = this.page.getByTestId(
'@coinmarket/offer/continue-transaction-button',
);
}

waitForOffersSyncToFinish = async () => {
await expect(this.offerSpinner).toBeHidden({ timeout: 30000 });
//Even though the offer sync is finished, the best offer might not be displayed correctly yet and show 0 BTC
await expect(this.bestOfferAmount).not.toHaveText('0 BTC');
await expect(this.buyBestOfferButton).toBeEnabled();
};

selectCountryOfResidence = async (country: string) => {
const countryLabel = getCountryLabel(country);
const currentCountry = await this.countryOfResidenceDropdown.textContent();
if (currentCountry === countryLabel) {
return;
}
await this.countryOfResidenceDropdown.click();
await this.countryOfResidenceDropdown.getByRole('combobox').fill(countryLabel);
await this.page.getByTestId(`@coinmarket/form/country-select/option/${country}`).click();
};

selectFiatCurrency = async (currency: FiatCurrencyCode) => {
const currentCurrency = await this.youPayCurrencyDropdown.textContent();
if (currentCurrency === currency.toUpperCase()) {
return;
}
await this.youPayCurrencyDropdown.click();
await this.youPayCurrencyOption(currency).click();
};

setYouPayAmount = async (
amount: string,
currency: FiatCurrencyCode = 'czk',
country: string = 'CZ',
) => {
//Warning: the field is initialized empty and gets default value after the first offer sync
await expect(this.youPayInput).not.toHaveValue('');
await this.selectCountryOfResidence(country);
await this.selectFiatCurrency(currency);
await this.youPayInput.fill(amount);
//Warning: Bug #16054, as a workaround we wait for offer sync after setting the amount
await this.waitForOffersSyncToFinish();
};

confirmTrade = async () => {
await expect(this.modal).toBeVisible();
await this.buyTermsConfirmButton.click();
await this.confirmOnTrezorButton.click();
await expect(this.confirmOnDevicePrompt).toBeVisible();
await TrezorUserEnvLink.pressYes();
await expect(this.confirmOnDevicePrompt).not.toBeVisible();
};

readBestOfferValues = async () => {
await expect(this.bestOfferAmount).not.toHaveText('0 BTC');
const amount = await this.bestOfferAmount.textContent();
const provider = await this.bestOfferProvider.textContent();
if (!amount || !provider) {
throw new Error(
`Test was not able to extract amount or provider from the page. Amount: ${amount}, Provider: ${provider}`,
);
}

return { amount, provider };
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import { Locator, Page, TestInfo, expect } from '@playwright/test';
import { Model, TrezorUserEnvLink } from '@trezor/trezor-user-env-link';
import { SUITE as SuiteActions } from '@trezor/suite/src/actions/suite/constants';

import { PlaywrightProjects } from '../../playwright.config';
import { AnalyticsActions } from './analyticsActions';
import { isWebProject } from '../common';

export class OnboardingActions {
readonly model: Model;
readonly testInfo: TestInfo;
readonly welcomeTitle: Locator;
readonly onboardingContinueButton: Locator;
readonly onboardingViewOnlySkipButton: Locator;
Expand All @@ -34,11 +32,9 @@ export class OnboardingActions {
constructor(
public page: Page,
private analyticsPage: AnalyticsActions,
model: Model,
testInfo: TestInfo,
private readonly model: Model,
private readonly testInfo: TestInfo,
) {
this.model = model;
this.testInfo = testInfo;
this.welcomeTitle = this.page.getByTestId('@welcome/title');
this.onboardingContinueButton = this.page.getByTestId('@onboarding/exit-app-button');
this.onboardingViewOnlySkipButton = this.page.getByTestId('@onboarding/viewOnly/skip');
Expand Down Expand Up @@ -90,7 +86,7 @@ export class OnboardingActions {

async disableFirmwareHashCheck() {
// Desktop starts with already disabled firmware hash check. Web needs to disable it.
if (this.testInfo.project.name !== PlaywrightProjects.Web) {
if (!isWebProject(this.testInfo)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ const backgroundImages = {
};

export class SettingsActions {
private readonly page: Page;
private readonly apiURL: string;
private readonly TIMES_CLICK_TO_SET_DEBUG_MODE = 5;
readonly settingsMenuButton: Locator;
readonly settingsHeader: Locator;
Expand Down Expand Up @@ -75,9 +73,10 @@ export class SettingsActions {
this.page.getByTestId(`@settings/language-select/option/${language}`);
readonly pinInput = (index: number) => this.page.getByTestId(`@pin/input/${index}`);

constructor(page: Page, apiURL: string) {
this.page = page;
this.apiURL = apiURL;
constructor(
private readonly page: Page,
private readonly apiURL: string,
) {
this.settingsMenuButton = this.page.getByTestId('@suite/menu/settings');
this.settingsHeader = this.page.getByTestId('@settings/menu/title');
this.debugTabButton = this.page.getByTestId('@settings/menu/debug');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { capitalizeFirstLetter } from '@trezor/utils';
const anyTestIdEndingWithClose = '[data-testid$="close"]';

export class SuiteGuide {
private readonly page: Page;
readonly guideButton: Locator;
readonly supportAndFeedbackButton: Locator;
readonly bugFormButton: Locator;
Expand All @@ -27,8 +26,7 @@ export class SuiteGuide {
readonly feedbackSuccessToast: Locator;
readonly articleHeader: Locator;

constructor(page: Page) {
this.page = page;
constructor(private readonly page: Page) {
this.guideButton = this.page.getByTestId('@guide/button-open');
this.supportAndFeedbackButton = this.page.getByTestId('@guide/button-feedback');
this.bugFormButton = this.page.getByTestId('@guide/feedback/bug');
Expand Down
Loading

0 comments on commit 2121c5e

Please sign in to comment.