Skip to content

Commit

Permalink
feat(e2e): Converted account metadata test (#16253)
Browse files Browse the repository at this point in the history
  • Loading branch information
HajekOndrej authored Jan 8, 2025
1 parent 20fd4d6 commit cdc8607
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 139 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test-suite-web-e2e-pw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ jobs:
CONTAINERS: "trezor-user-env-unix"
- TEST_GROUP: "@group=settings"
CONTAINERS: "trezor-user-env-unix"
# - TEST_GROUP: "@group=metadata"
# CONTAINERS: "trezor-user-env-unix"
- TEST_GROUP: "@group=metadata"
CONTAINERS: "trezor-user-env-unix"
# - TEST_GROUP: "@group=passphrase"
# CONTAINERS: "trezor-user-env-unix"
- TEST_GROUP: "@group=other"
Expand Down
20 changes: 19 additions & 1 deletion packages/e2e-utils/src/mocks/dropbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ export class DropboxMock {
app.use(express.json());

app.use((req, res, next) => {
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, dropbox-api-arg',
);
res.setHeader('Access-Control-Allow-Credentials', 'true');

return res.status(200).end();
}

// Handle normal requests
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, dropbox-api-arg',
);
this.requests.push(req.url);

if (this.nextResponse.length) {
Expand All @@ -45,7 +64,6 @@ export class DropboxMock {
// https://api.dropboxapi.com/oauth2/token
app.post('/oauth2/token', (req, res) => {
console.log('[dropbox]: token');

const { grant_type } = req.query;
if (grant_type === 'authorization_code') {
return res.send({
Expand Down
30 changes: 21 additions & 9 deletions packages/suite-desktop-core/e2e/support/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { RecoveryActions } from './pageActions/recoveryActions';
import { TrezorInputActions } from './pageActions/trezorInputActions';
import { MarketActions } from './pageActions/marketActions';
import { AssetsActions } from './pageActions/assetsActions';
import { MetadataProviderMocks } from './metadataProviderMocks';
import { MetadataActions } from './pageActions/metadataActions';

type Fixtures = {
startEmulator: boolean;
Expand All @@ -46,11 +48,13 @@ type Fixtures = {
analyticsPage: AnalyticsActions;
devicePrompt: DevicePromptActions;
recoveryPage: RecoveryActions;
marketPage: MarketActions;
assetsPage: AssetsActions;
metadataPage: MetadataActions;
trezorInput: TrezorInputActions;
analytics: AnalyticsFixture;
indexedDb: IndexedDbFixture;
marketPage: MarketActions;
assetsPage: AssetsActions;
metadataProviderMocks: MetadataProviderMocks;
};

const test = base.extend<Fixtures>({
Expand Down Expand Up @@ -173,6 +177,18 @@ const test = base.extend<Fixtures>({
const recoveryPage = new RecoveryActions(page);
await use(recoveryPage);
},
marketPage: async ({ page }, use) => {
const marketPage = new MarketActions(page);
await use(marketPage);
},
assetsPage: async ({ page }, use) => {
const assetPage = new AssetsActions(page);
await use(assetPage);
},
metadataPage: async ({ page, devicePrompt }, use) => {
const metadataPage = new MetadataActions(page, devicePrompt);
await use(metadataPage);
},
trezorInput: async ({ page }, use) => {
const trezorInput = new TrezorInputActions(page);
await use(trezorInput);
Expand All @@ -185,13 +201,9 @@ 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);
},
assetsPage: async ({ page }, use) => {
const assetPage = new AssetsActions(page);
await use(assetPage);
metadataProviderMocks: async ({ page }, use) => {
const metadataProviderMocks = new MetadataProviderMocks(page);
await use(metadataProviderMocks);
},
});

Expand Down
77 changes: 77 additions & 0 deletions packages/suite-desktop-core/e2e/support/metadataProviderMocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Page } from '@playwright/test';

import { DropboxMock } from '../../../e2e-utils/src/mocks/dropbox';
import { GoogleMock } from '../../../e2e-utils/src/mocks/google';
import { step } from './common';

export enum MetadataProvider {
DROPBOX = 'dropbox',
GOOGLE = 'google',
}

export type MetadataProviderMock = DropboxMock | GoogleMock;

const stubOpen = `
// Override Math.random for deterministic behavior
Math.random = () => 0.4;
window.open = (url, target, features) => {
console.log('Intercepted window.open call:', url);
window.postMessage(
{ search: '?code=chicken-cho-cha&state=YYYYYYYYYY', key: 'trezor-oauth' });
};
`;

const rerouteFetch = `
const originalFetch = window.fetch;
window.fetch = async (uri, options) => {
let url;
try {
url = new URL(uri);
} catch {
const baseUrl = window.location.origin;
uri = new URL(uri, baseUrl).href;
return originalFetch(uri, options);
}
const dropboxOrigins = ['https://content.dropboxapi.com', 'https://api.dropboxapi.com'];
const googleOrigins = ['https://www.googleapis.com', 'https://oauth2.googleapis.com'];
if (dropboxOrigins.some(o => uri.includes(o))) {
const modifiedUrl = url.href.replace(url.origin, 'http://localhost:30002');
return originalFetch(modifiedUrl, options);
}
if (googleOrigins.some(o => uri.includes(o))) {
const modifiedUrl = url.href.replace(url.origin, 'http://localhost:30001');
return originalFetch(modifiedUrl, options);
}
return originalFetch(uri, options);
};
`;

export class MetadataProviderMocks {
private readonly dropBoxMock = new DropboxMock();
private readonly googleMock = new GoogleMock();
constructor(private readonly page: Page) {}

getMetadataProvider(provider: MetadataProvider): MetadataProviderMock {
switch (provider) {
case MetadataProvider.DROPBOX:
return this.dropBoxMock;
case MetadataProvider.GOOGLE:
return this.googleMock;
default:
throw new Error(`Provider ${provider} not supported`);
}
}

@step()
async initializeProviderMocking() {
await this.page.evaluate(rerouteFetch);
await this.page.evaluate(stubOpen);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Locator, Page, expect } from '@playwright/test';

import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link';

import { MetadataProvider } from '../metadataProviderMocks';
import { DevicePromptActions } from './devicePromptActions';
import { step } from '../common';

export class MetadataActions {
private readonly metadataSubmitButton: Locator;
readonly metadataInput: Locator;
readonly editLabelButton = (accountId: string) =>
this.page.getByTestId(`${this.getAccountLabelTestId(accountId)}/edit-label-button`);
readonly successLabel = (accountId: string) =>
this.page.getByTestId(`${this.getAccountLabelTestId(accountId)}/success`);
readonly accountLabel = (accountId: string) =>
this.page.getByTestId(this.getAccountLabelTestId(accountId));

constructor(
private readonly page: Page,
private readonly devicePrompt: DevicePromptActions,
) {
this.metadataSubmitButton = page.getByTestId('@metadata/submit');
this.metadataInput = page.getByTestId('@metadata/input');
}

private getAccountLabelTestId(accountId: string): string {
return `@metadata/accountLabel/${accountId}`;
}

@step()
async passThroughInitMetadata(provider: MetadataProvider) {
await this.devicePrompt.confirmOnDevicePromptIsShown();
await TrezorUserEnvLink.pressYes();
await this.page.getByTestId(`@modal/metadata-provider/${provider}-button`).click();
await expect(this.page.getByTestId('@modal/metadata-provider')).not.toBeVisible({
timeout: 30000,
});
}

@step()
async hoverAccountLabel(accountId: string) {
await this.page
.getByTestId(`${this.getAccountLabelTestId(accountId)}/hover-container`)
.hover();
}

@step()
async editLabel(accountId: string, newLabel: string) {
await this.accountLabel(accountId).click();
await this.editLabelButton(accountId).click();
await this.metadataInput.fill(newLabel);
await this.metadataSubmitButton.click();
}

@step()
async clickAddLabelButton(accountId: string) {
await this.hoverAccountLabel(accountId);
await this.page
.getByTestId(`${this.getAccountLabelTestId(accountId)}/add-label-button`)
.click();
}

@step()
async addLabel(accountId: string, label: string) {
await this.clickAddLabelButton(accountId);
await this.metadataInput.fill(label);
await this.page.keyboard.press('Enter');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { test, expect } from '../../support/fixtures';
import { MetadataProvider, MetadataProviderMock } from '../../support/metadataProviderMocks';

// Metadata is by default disabled, this means, that application does not try to generate master key and connect to cloud.
// Hovering over fields that may be labeled shows "add label" button upon which is clicked, Suite initiates metadata flow
test.describe('Account metadata', { tag: ['@group=metadata', '@webOnly'] }, () => {
test.use({
emulatorStartConf: { wipe: true },
emulatorSetupConf: { mnemonic: 'mnemonic_all' },
});

let provider: MetadataProviderMock;
test.beforeEach(async ({ metadataProviderMocks }) => {
metadataProviderMocks.initializeProviderMocking();
provider = metadataProviderMocks.getMetadataProvider(MetadataProvider.DROPBOX);
await provider.start();
});
test('dropbox provider', async ({
page,
onboardingPage,
dashboardPage,
metadataPage,
settingsPage,
}) => {
await onboardingPage.completeOnboarding();

// Finish discovery process
// Discovery process completed
await dashboardPage.discoveryShouldFinish();

await expect(page.getByTestId('@account-menu/btc/normal/0')).toBeVisible();

// Interact with accounts and metadata
// Clicking "Bitcoin" label in account menu is not possible, click triggers metadata flow
await page.getByTestId('@account-menu/btc/normal/0/label').click();
await expect(page.getByTestId('@account-menu/btc/normal/0/label')).toContainText('Bitcoin');

// Metadata flow
await metadataPage.clickAddLabelButton("m/84'/0'/0'");
await metadataPage.passThroughInitMetadata(MetadataProvider.DROPBOX);

// Edit label
await metadataPage.metadataInput.fill('cool new label');
await page.keyboard.press('Enter');
await expect(page.getByTestId('@account-menu/btc/normal/0/label')).toContainText(
'cool new label',
);

// Submit label changes via button
await metadataPage.editLabel("m/84'/0'/0'", 'even cooler');
await expect(page.getByTestId('@account-menu/btc/normal/0/label')).toContainText(
'even cooler',
);

await expect(metadataPage.successLabel("m/84'/0'/0'")).toBeVisible();
await expect(metadataPage.successLabel("m/84'/0'/0'")).not.toBeVisible();

// Discard changes via escape
await metadataPage.accountLabel("m/84'/0'/0'").click();
await metadataPage.editLabelButton("m/84'/0'/0'").click();
await metadataPage.metadataInput.fill('bcash is true bitcoin');
await page.keyboard.press('Escape');
await expect(page.getByTestId('@account-menu/btc/normal/0/label')).toContainText(
'even cooler',
);

// Test account search with metadata
const searchInput = page.getByTestId('@account-menu/search-input').first();
await searchInput.click();
await searchInput.fill('even cooler');
await expect(page.getByTestId('@account-menu/btc/normal/0')).toBeVisible();
await searchInput.fill('something retarded');
await expect(page.getByTestId('@account-menu/btc/normal/0')).not.toBeVisible();
await searchInput.clear();

// Remove metadata by clearing input
await metadataPage.hoverAccountLabel("m/84'/0'/0'");
await metadataPage.editLabelButton("m/84'/0'/0'").click();
await metadataPage.metadataInput.clear();
await page.keyboard.press('Enter');
await expect(page.getByTestId('@account-menu/btc/normal/0/label')).toContainText('Bitcoin');

// Test switching between accounts
await page.getByTestId('@account-menu/segwit').click();
await page.getByTestId('@account-menu/btc/segwit/0').click();

await metadataPage.addLabel("m/49'/0'/0'", 'typing into one input');
await expect(metadataPage.successLabel("m/49'/0'/0'")).toBeVisible();

await page.getByTestId('@account-menu/btc/segwit/1').click();

await expect(metadataPage.successLabel("m/49'/0'/1'")).not.toBeVisible();
await expect(metadataPage.successLabel("m/49'/0'/0'")).not.toBeVisible();

// Check metadata requests when switching routes
await page.getByTestId('@suite/menu/suite-index').click();
await expect(dashboardPage.dashboardGraph).toBeVisible();

// Add and label a new account
await page.getByTestId('@account-menu/btc/normal/0').click();
await page.getByTestId('@account-menu/add-account').click();
await settingsPage.coins.networkButton('btc').click();
await page.getByTestId('@add-account').click();
await metadataPage.addLabel(
"m/84'/0'/2'",
'adding label to a newly added account. does it work?',
);
});

test.afterEach(() => {
provider.stop();
});
});
Loading

0 comments on commit cdc8607

Please sign in to comment.