Skip to content

Commit d74b38b

Browse files
authored
feat(e2e): Expand buy-coin test to full flow (#16516)
* feat(e2e): expand buy coin test to whole flow adds steps and verificaiton for finishing buy and seeing transaction state minor general refactoring new custom matcher shouldHavePayload refactors invity mock objects new locators * fix(e2e): Adjust redirect of buy-coin to work on CI adjust imports improve logging of shouldHavePayload * fix(e2e): yarn dedupe * fix(e2e): list dependencies * refactor(e2e): PR feedback
1 parent dba2b51 commit d74b38b

File tree

25 files changed

+314
-103
lines changed

25 files changed

+314
-103
lines changed

docs/tests/e2e-playwright-suite.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Steps:
5151

5252
1. **To run both Web and Desktop at same time** you can do: `yarn workspace @trezor/suite-desktop-core test:e2e`
5353

54-
1. **To run tests headed (showing UI)** you can add: `--headed`
54+
1. **To run tests headed (showing UI)** you can add: `--headed --ignore-snapshots`. Some snapshots differ between headless and headed mode, ergo we need to ignore them when running in headed mode.
5555

5656
1. **To run just one test file** you can do: `yarn workspace @trezor/suite-desktop-core test:e2e general/wallet-discovery.test.ts`
5757

packages/components/src/components/InfoSegments/InfoSegments.tsx

+17-5
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,28 @@ export const allowedInfoSegmentsFrameProps = ['margin'] as const satisfies Frame
1414
type AllowedFrameProps = Pick<FrameProps, (typeof allowedInfoSegmentsFrameProps)[number]>;
1515

1616
export type InfoSegmentsProps = AllowedFrameProps &
17-
AllowedTextProps & {
18-
variant?: TextVariant;
19-
} & { children: Array<ReactNode> };
17+
AllowedTextProps & { variant?: TextVariant; 'data-testid'?: string } & {
18+
children: Array<ReactNode>;
19+
};
2020

21-
export const InfoSegments = ({ children, typographyStyle, variant, margin }: InfoSegmentsProps) => {
21+
export const InfoSegments = ({
22+
children,
23+
typographyStyle,
24+
variant,
25+
margin,
26+
'data-testid': dataTestId,
27+
}: InfoSegmentsProps) => {
2228
const validChildren = Children.toArray(children).filter(child => Boolean(child));
2329
const id = useId();
2430

2531
return (
26-
<Text as="div" typographyStyle={typographyStyle} margin={margin} variant={variant}>
32+
<Text
33+
data-testid={dataTestId}
34+
as="div"
35+
typographyStyle={typographyStyle}
36+
margin={margin}
37+
variant={variant}
38+
>
2739
<Row gap={spacings.xxs}>
2840
{validChildren.map((child, index) => (
2941
<Fragment key={`${id}-${index}`}>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"trade": {
3+
"exchange": "topper",
4+
"fiatCurrency": "CZK",
5+
"receiveCurrency": "bitcoin",
6+
"rate": 2434166.7054290725,
7+
"wantCrypto": false,
8+
"exp": "t+vWC5JpkRwVcd4SrCj7LQ==",
9+
"country": "CZ",
10+
"paymentMethodName": "Credit Card",
11+
"fiatStringAmount": "1234",
12+
"receiveStringAmount": "0.00048773",
13+
"minFiat": 252.72,
14+
"maxFiat": 1263581.04,
15+
"minCrypto": 0.00010337502776424505,
16+
"maxCrypto": 0.516867383239845,
17+
"paymentMethod": "creditCard",
18+
"quoteId": "f0efbc62-dcee-4639-be77-0c3d2d47efaf",
19+
"orderId": "63cfc1bf-e0d7-4afa-af6a-77d33941a694",
20+
"paymentId": "6d666a5f-b99c-4482-b8bc-2df04fc11b7b",
21+
"receiveAddress": "bc1q7ceqvaq7fqyywxqcx7qnfxkfk2ykpsla9pe80q"
22+
},
23+
"returnUrl": "http://localhost:8000/coinmarket-redirect#detail/btc/normal/0/6d666a5f-b99c-4482-b8bc-2df04fc11b7b"
24+
}

packages/suite-desktop-core/e2e/fixtures/invity/buy/trade.json

+22-18
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
{
22
"trade": {
3-
"fiatStringAmount": "500",
4-
"fiatCurrency": "EUR",
3+
"receiveAddress": "bc1q7ceqvaq7fqyywxqcx7qnfxkfk2ykpsla9pe80q",
4+
"paymentId": "5e895ff7-c444-4371-a41f-c1735edca46c",
5+
"status": "SUBMITTED",
6+
"originalPaymentId": "e17a2bed-86c6-4974-9d87-9fc926b16614",
7+
"partnerData": "",
8+
"exchange": "topper",
9+
"fiatCurrency": "CZK",
510
"receiveCurrency": "bitcoin",
6-
"receiveStringAmount": "0.02066953",
7-
"rate": 24190.19687433628,
8-
"orderId": "6476d7c4-a873-400f-8276-5a47a43cd392",
9-
"paymentId": "4e9c3760220d839e8e30537d9ed5d172",
10-
"originalPaymentId": "mockedPaymentId3",
11-
"status": "APPROVAL_PENDING",
12-
"exchange": "banxa",
13-
"validUntil": "2022-07-31T16:42:22Z",
14-
"minFiat": 30,
15-
"maxFiat": 1001,
16-
"minCrypto": 0.00128966,
17-
"maxCrypto": 0.04303155,
18-
"paymentMethod": "bankTransfer",
19-
"country": "AT",
20-
"wantCrypto": false
11+
"rate": 2434166.7054290725,
12+
"wantCrypto": false,
13+
"exp": "t+vWC5JpkRwVcd4SrCj7LQ==",
14+
"country": "CZ",
15+
"paymentMethodName": "Credit Card",
16+
"fiatStringAmount": "1234",
17+
"receiveStringAmount": "0.00048773",
18+
"minFiat": 252.72,
19+
"maxFiat": 1263581.04,
20+
"minCrypto": 0.00010337502776424505,
21+
"maxCrypto": 0.516867383239845,
22+
"paymentMethod": "creditCard",
23+
"quoteId": "f0efbc62-dcee-4639-be77-0c3d2d47efaf",
24+
"orderId": "f0efbc62-dcee-4639-be77-0c3d2d47efaf"
2125
},
2226
"tradeForm": {
2327
"form": {
2428
"formMethod": "GET",
25-
"formAction": "https://simulated-partner.com",
29+
"formAction": "",
2630
"fields": {}
2731
}
2832
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"receiveAddress": "bc1q7ceqvaq7fqyywxqcx7qnfxkfk2ykpsla9pe80q",
3+
"paymentId": "5e895ff7-c444-4371-a41f-c1735edca46c",
4+
"status": "SUBMITTED",
5+
"originalPaymentId": "e17a2bed-86c6-4974-9d87-9fc926b16614",
6+
"partnerData": "/coinmarket-redirect#detail/btc/normal/0/5e895ff7-c444-4371-a41f-c1735edca46c",
7+
"exchange": "topper",
8+
"fiatCurrency": "CZK",
9+
"receiveCurrency": "bitcoin",
10+
"rate": 2434166.7054290725,
11+
"wantCrypto": false,
12+
"exp": "t+vWC5JpkRwVcd4SrCj7LQ==",
13+
"country": "CZ",
14+
"paymentMethodName": "Credit Card",
15+
"fiatStringAmount": "1234",
16+
"receiveStringAmount": "0.00048773",
17+
"minFiat": 252.72,
18+
"maxFiat": 1263581.04,
19+
"minCrypto": 0.00010337502776424505,
20+
"maxCrypto": 0.516867383239845,
21+
"paymentMethod": "creditCard",
22+
"quoteId": "f0efbc62-dcee-4639-be77-0c3d2d47efaf",
23+
"orderId": "f0efbc62-dcee-4639-be77-0c3d2d47efaf"
24+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"status": "SUCCESS"
2+
"status": "SUBMITTED"
33
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { cloneDeep } from 'lodash';
2+
13
import exchangeCoins from './exchange/coins.json';
24
import exchangeList from './exchange/list.json';
35
import exchangeQuotes from './exchange/quotes.json';
@@ -10,16 +12,55 @@ import buyTrade from './buy/trade.json';
1012
import buyWatch from './buy/watch.json';
1113
import sellList from './sell/list.json';
1214

15+
const invityUrl = 'https://exchange.trezor.io';
16+
17+
export const invityEndpoint = {
18+
exchangeCoins: `${invityUrl}/api/exchange/coins`,
19+
exchangeList: `${invityUrl}/api/v3/exchange/list`,
20+
exchangeQuotes: `${invityUrl}/api/exchange/quotes`,
21+
exchangeTrade: `${invityUrl}/api/exchange/trade`,
22+
exchangeWatch: `${invityUrl}/api/exchange/watch/*`,
23+
info: `${invityUrl}/api/info`,
24+
buyList: `${invityUrl}/api/v3/buy/list`,
25+
buyQuotes: `${invityUrl}/api/v3/buy/quotes`,
26+
buyTrade: `${invityUrl}/api/v3/buy/trade`,
27+
buyWatch: `${invityUrl}/api/v3/buy/watch/*`,
28+
sellList: `${invityUrl}/api/v3/sell/list`,
29+
};
30+
1331
export const invityResponses = {
14-
'api/exchange/coins': exchangeCoins,
15-
'api/v3/exchange/list': exchangeList,
16-
'api/exchange/quotes': exchangeQuotes,
17-
'api/exchange/trade': exchangeTrade,
18-
'api/exchange/watch/0': exchangeWatch,
19-
'api/info': info,
20-
'api/v3/buy/list': buyList,
21-
'api/v3/buy/quotes': buyQuotes,
22-
'api/v3/buy/trade': buyTrade,
23-
'api/v3/buy/watch/0': buyWatch,
24-
'api/v3/sell/list': sellList,
32+
[invityEndpoint.exchangeCoins]: exchangeCoins,
33+
[invityEndpoint.exchangeList]: exchangeList,
34+
[invityEndpoint.exchangeQuotes]: exchangeQuotes,
35+
[invityEndpoint.exchangeTrade]: exchangeTrade,
36+
[invityEndpoint.exchangeWatch]: exchangeWatch,
37+
[invityEndpoint.info]: info,
38+
[invityEndpoint.buyList]: buyList,
39+
[invityEndpoint.buyQuotes]: buyQuotes,
40+
[invityEndpoint.buyWatch]: buyWatch,
41+
[invityEndpoint.sellList]: sellList,
42+
};
43+
44+
// This modification allows us to skip the provider's part of the flow and go directly to the transaction detail.
45+
export const createRedirectedTradeResponse = (url: string) => {
46+
const redirectToDetail = `${url}coinmarket-redirect#detail/btc/normal/0/${buyTrade.trade.paymentId}`;
47+
const modifiedTrade = cloneDeep(buyTrade);
48+
modifiedTrade.trade.partnerData = redirectToDetail;
49+
modifiedTrade.tradeForm.form.formAction = redirectToDetail;
50+
51+
return modifiedTrade;
52+
};
53+
54+
export {
55+
exchangeCoins,
56+
exchangeList,
57+
exchangeQuotes,
58+
exchangeTrade,
59+
exchangeWatch,
60+
info,
61+
buyList,
62+
buyQuotes,
63+
buyTrade,
64+
buyWatch,
65+
sellList,
2566
};

packages/suite-desktop-core/e2e/support/common.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import test, { _electron as electron, TestInfo } from '@playwright/test';
44
import path from 'path';
55
import { readdirSync, removeSync } from 'fs-extra';
6+
import { isEqual, omit } from 'lodash';
67

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

@@ -111,9 +112,9 @@ export const isDesktopProject = (testInfo: TestInfo) =>
111112
export const isWebProject = (testInfo: TestInfo) =>
112113
testInfo.project.name === PlaywrightProjects.Web;
113114

114-
export const getApiUrl = (webBaseUrl: string | undefined, testInfo: TestInfo) => {
115+
export const getUrl = (testInfo: TestInfo) => {
115116
const electronApiURL = 'file:///';
116-
const apiURL = isDesktopProject(testInfo) ? electronApiURL : webBaseUrl;
117+
const apiURL = isDesktopProject(testInfo) ? electronApiURL : testInfo.project.use.baseURL;
117118
if (!apiURL) {
118119
throw new Error('apiURL is not defined');
119120
}
@@ -163,3 +164,6 @@ const TrezorUserEnvLinkProxy = new Proxy(TrezorUserEnvLink, {
163164
});
164165

165166
export { TrezorUserEnvLinkProxy };
167+
168+
export const isEqualWithOmit = (param: { object1: any; object2: any; mask: string[] }) =>
169+
isEqual(omit(param.object1, param.mask), omit(param.object2, param.mask));

packages/suite-desktop-core/e2e/support/customMatchers.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { Locator, expect as baseExpect } from '@playwright/test';
1+
import { Locator, Request, expect as baseExpect } from '@playwright/test';
2+
import { diff } from 'jest-diff';
3+
4+
import { isEqualWithOmit } from './common';
25

36
const compareTextAndNumber = async (
47
locator: Locator,
@@ -49,4 +52,25 @@ export const expect = baseExpect.extend({
4952
async toHaveTextLessThan(locator: Locator, expectedValue: number) {
5053
return await compareTextAndNumber(locator, expectedValue, (a, b) => a < b, 'less');
5154
},
55+
async toHavePayload(
56+
requestPromise: Promise<Request>,
57+
expectedPayload: any,
58+
options?: { omit: string[] },
59+
) {
60+
const requestPayload = (await requestPromise).postDataJSON();
61+
const isRequestPayloadMatching = isEqualWithOmit({
62+
object1: requestPayload,
63+
object2: expectedPayload,
64+
mask: options?.omit ?? [],
65+
});
66+
67+
return {
68+
pass: isRequestPayloadMatching,
69+
message: () =>
70+
`Request payload differs from expected.
71+
\nDiff: ${diff(requestPayload, expectedPayload)}
72+
\nActual: ${JSON.stringify(requestPayload)}
73+
\nExpected: ${JSON.stringify(expectedPayload)}`,
74+
};
75+
},
5276
});

packages/suite-desktop-core/e2e/support/fixtures.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111

1212
import { DashboardActions } from './pageActions/dashboardActions';
1313
import {
14-
getApiUrl,
14+
getUrl,
1515
getElectronVideoPath,
1616
isDesktopProject,
1717
launchSuite,
@@ -40,7 +40,7 @@ type Fixtures = {
4040
setupEmulator: boolean;
4141
emulatorStartConf: StartEmuModelRequired;
4242
emulatorSetupConf: SetupEmu;
43-
apiURL: string;
43+
url: string;
4444
trezorUserEnvLink: TrezorUserEnvLinkClass;
4545
electronWindow: Page | undefined;
4646
page: Page;
@@ -67,8 +67,9 @@ const test = base.extend<Fixtures>({
6767
setupEmulator: true,
6868
emulatorStartConf: { model: 'T3T1', wipe: true },
6969
emulatorSetupConf: {},
70-
apiURL: async ({ baseURL }, use, testInfo) => {
71-
await use(getApiUrl(baseURL, testInfo));
70+
/* eslint-disable-next-line no-empty-pattern */
71+
url: async ({}, use, testInfo) => {
72+
await use(getUrl(testInfo));
7273
},
7374
/* eslint-disable-next-line no-empty-pattern */
7475
trezorUserEnvLink: async ({}, use) => {
@@ -144,8 +145,8 @@ const test = base.extend<Fixtures>({
144145
const dashboardPage = new DashboardActions(page);
145146
await use(dashboardPage);
146147
},
147-
settingsPage: async ({ page, apiURL }, use) => {
148-
const settingsPage = new SettingsActions(page, apiURL);
148+
settingsPage: async ({ page, url }, use) => {
149+
const settingsPage = new SettingsActions(page, url);
149150
await use(settingsPage);
150151
},
151152
suiteGuidePage: async ({ page }, use) => {
@@ -182,8 +183,8 @@ const test = base.extend<Fixtures>({
182183
const recoveryPage = new RecoveryActions(page);
183184
await use(recoveryPage);
184185
},
185-
marketPage: async ({ page }, use) => {
186-
const marketPage = new MarketActions(page);
186+
marketPage: async ({ page, url }, use) => {
187+
const marketPage = new MarketActions(page, url);
187188
await use(marketPage);
188189
},
189190
assetsPage: async ({ page }, use) => {

0 commit comments

Comments
 (0)