Commit 36f9de66 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Playwright tests refactoring, pt.1 (#1744)

* abort all requests for external resources and single mock for text ad

* render and mockApiResponse fixtures

* move fixtures into separate files

* rewrite Blocks test

* refactor mockApiResponse signature

* test with auth

* rewrite test with other API domains

* fix test

* separate script for installing deps in pw docker
parent 50a96d5d
......@@ -17,6 +17,7 @@ NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
# api configuration
NEXT_PUBLIC_API_PROTOCOL=http
NEXT_PUBLIC_API_HOST=localhost
NEXT_PUBLIC_API_PORT=3003
NEXT_PUBLIC_API_BASE_PATH=/
......@@ -47,8 +48,8 @@ NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3100
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx
NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006
NEXT_PUBLIC_STATS_API_HOST=http://localhost:3004
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=http://localhost:3005
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=http://localhost:3006
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
\ No newline at end of file
......@@ -2,7 +2,7 @@ export const duck = {
ad: {
name: 'Hello utia!',
description_short: 'Utia is the best! Go with utia! Utia is the best! Go with utia!',
thumbnail: 'https://utia.utia',
thumbnail: 'http://localhost:3100/utia.jpg',
url: 'https://test.url',
cta_button: 'Click me!',
},
......
......@@ -25,6 +25,7 @@
"test:pw": "./tools/scripts/pw.sh",
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw",
"test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.sh",
"test:pw:docker:deps": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.deps.sh",
"test:pw:ci": "yarn test:pw --project=$PW_PROJECT",
"test:pw:detect-affected": "node ./deploy/tools/affected-tests/index.js",
"test:jest": "jest",
......
......@@ -4,6 +4,8 @@ import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr';
import tsconfigPaths from 'vite-tsconfig-paths';
import appConfig from 'configs/app';
/**
* See https://playwright.dev/docs/test-configuration.
*/
......@@ -36,6 +38,7 @@ const config: PlaywrightTestConfig = defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
baseURL: appConfig.app.baseUrl,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
......
......@@ -12,7 +12,7 @@ import wagmiConfig from 'lib/web3/wagmiConfig';
import * as app from 'playwright/utils/app';
import theme from 'theme';
type Props = {
export type Props = {
children: React.ReactNode;
withSocket?: boolean;
appContext?: {
......
import type { TestFixture, Page } from '@playwright/test';
import buildUrl from 'lib/api/buildUrl';
import type { ResourceName, ResourcePayload } from 'lib/api/resources';
interface Options<R extends ResourceName> {
pathParams?: Parameters<typeof buildUrl<R>>[1];
queryParams?: Parameters<typeof buildUrl<R>>[2];
}
export type MockApiResponseFixture = <R extends ResourceName>(resourceName: R, responseMock: ResourcePayload<R>, options?: Options<R>) => Promise<string>;
const fixture: TestFixture<MockApiResponseFixture, { page: Page }> = async({ page }, use) => {
await use(async(resourceName, responseMock, options) => {
const apiUrl = buildUrl(resourceName, options?.pathParams, options?.queryParams);
await page.route(apiUrl, (route) => route.fulfill({
status: 200,
body: JSON.stringify(responseMock),
}));
return apiUrl;
});
};
export default fixture;
import type { TestFixture, Page } from '@playwright/test';
export type MockAssetResponseFixture = (url: string, path: string) => Promise<void>;
const fixture: TestFixture<MockAssetResponseFixture, { page: Page }> = async({ page }, use) => {
await use(async(url, path) => {
await page.route(url, (route) => route.fulfill({
status: 200,
path,
}));
});
};
export default fixture;
import type { MountOptions } from '@playwright/experimental-ct-react';
import type { Locator, TestFixture } from '@playwright/test';
import type router from 'next/router';
import React from 'react';
import type { JsonObject } from '@playwright/experimental-ct-core/types/component';
import type { Props as TestAppProps } from 'playwright/TestApp';
import TestApp from 'playwright/TestApp';
interface MountResult extends Locator {
unmount(): Promise<void>;
update(component: JSX.Element): Promise<void>;
}
type Mount = <HooksConfig extends JsonObject>(component: JSX.Element, options?: MountOptions<HooksConfig>) => Promise<MountResult>;
interface Options extends JsonObject {
hooksConfig?: {
router: Partial<Pick<typeof router, 'query' | 'isReady' | 'asPath' | 'pathname'>>;
};
}
export type RenderFixture = (component: JSX.Element, options?: Options, props?: Omit<TestAppProps, 'children'>) => Promise<MountResult>
const fixture: TestFixture<RenderFixture, { mount: Mount }> = async({ mount }, use) => {
await use((component, options, props) => {
return mount(
<TestApp { ...props }>{ component }</TestApp>,
options,
);
});
};
export default fixture;
......@@ -10,16 +10,16 @@ import type { Transaction } from 'types/api/transaction';
import * as app from 'playwright/utils/app';
type ReturnType = () => Promise<WebSocket>;
export type CreateSocketFixture = () => Promise<WebSocket>;
type Channel = [string, string, string];
export interface SocketServerFixture {
createSocket: ReturnType;
createSocket: CreateSocketFixture;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const createSocket: TestFixture<ReturnType, { page: Page}> = async({ page }, use) => {
export const createSocket: TestFixture<CreateSocketFixture, { page: Page}> = async({ page }, use) => {
const socketServer = new WebSocketServer({ port: app.socketPort });
const connectionPromise = new Promise<WebSocket>((resolve) => {
......
/* eslint-disable no-console */
import { test as base } from '@playwright/experimental-ct-react';
import * as textAdMock from 'mocks/ad/textAd';
import type { MockApiResponseFixture } from './fixtures/mockApiResponse';
import mockApiResponseFixture from './fixtures/mockApiResponse';
import type { MockAssetResponseFixture } from './fixtures/mockAssetResponse';
import mockAssetResponseFixture from './fixtures/mockAssetResponse';
import type { RenderFixture } from './fixtures/render';
import renderFixture from './fixtures/render';
import type { CreateSocketFixture } from './fixtures/socketServer';
import { createSocket as createSocketFixture } from './fixtures/socketServer';
interface Fixtures {
render: RenderFixture;
mockApiResponse: MockApiResponseFixture;
mockAssetResponse: MockAssetResponseFixture;
createSocket: CreateSocketFixture;
}
const test = base.extend<Fixtures>({
render: renderFixture,
mockApiResponse: mockApiResponseFixture,
mockAssetResponse: mockAssetResponseFixture,
createSocket: createSocketFixture,
});
test.beforeEach(async({ page }) => {
// debug
const isDebug = process.env.PWDEBUG === '1';
if (isDebug) {
page.on('console', msg => console.log(msg.text()));
page.on('request', request => console.info('\x1b[34m%s\x1b[0m', '>>', request.method(), request.url()));
page.on('response', response => console.info('\x1b[35m%s\x1b[0m', '<<', String(response.status()), response.url()));
}
// Abort all other requests to external resources
await page.route('**', (route) => {
if (!route.request().url().startsWith('http://localhost')) {
isDebug && console.info('Aborting request to', route.request().url());
route.abort();
} else {
route.continue();
}
});
// with few exceptions:
// 1. mock text AD requests
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: JSON.stringify(textAdMock.duck),
}));
await page.route(textAdMock.duck.ad.thumbnail, (route) => {
return route.fulfill({
status: 200,
path: './playwright/mocks/image_s.jpg',
});
});
});
export * from '@playwright/experimental-ct-react';
export { test };
import { compile } from 'path-to-regexp';
import config from 'configs/app';
import type { ResourceName, ResourcePathParams } from 'lib/api/resources';
import { RESOURCES } from 'lib/api/resources';
// DEPRECATED
/**
* @deprecated please use fixture mockApiResponse from playwright/lib.tsx for rendering test suite
*
* @export
* @template R
* @param {R} resourceName
* @param {ResourcePathParams<R>} [pathParams]
* @return {*} string
*/
export default function buildApiUrl<R extends ResourceName>(resourceName: R, pathParams?: ResourcePathParams<R>) {
const resource = RESOURCES[resourceName];
const defaultApi = 'https://' + process.env.NEXT_PUBLIC_API_HOST + ':' + process.env.NEXT_PUBLIC_API_PORT;
const origin = 'endpoint' in resource && resource.endpoint ? resource.endpoint + (resource.basePath ?? '') : defaultApi;
const origin = 'endpoint' in resource && resource.endpoint ? resource.endpoint + (resource.basePath ?? '') : config.api.endpoint;
return origin + compile(resource.path)(pathParams);
}
#!/bin/bash
yarn install --modules-folder node_modules_linux
#!/bin/bash
yarn install --modules-folder node_modules_linux
export NODE_PATH=$(pwd)/node_modules_linux
yarn test:pw "$@"
......@@ -69,7 +69,7 @@ export type Transactions = Array<Transaction>
export interface UserInfo {
name?: string;
nickname?: string;
email: string;
email: string | null;
avatar?: string;
}
......
import { test as base, expect, devices } from '@playwright/experimental-ct-react';
import type { BrowserContext } from '@playwright/test';
import React from 'react';
import * as textAdMock from 'mocks/ad/textAd';
import * as blockMock from 'mocks/blocks/block';
import * as statsMock from 'mocks/stats/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import * as socketServer from 'playwright/fixtures/socketServer';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import { test, expect, devices } from 'playwright/lib';
import * as configs from 'playwright/utils/configs';
import Blocks from './Blocks';
const BLOCKS_API_URL = buildApiUrl('blocks') + '?type=block';
const STATS_API_URL = buildApiUrl('stats');
const hooksConfig = {
router: {
query: { tab: 'blocks' },
......@@ -21,70 +17,28 @@ const hooksConfig = {
},
};
const test = base.extend<socketServer.SocketServerFixture>({
createSocket: socketServer.createSocket,
});
// FIXME
// test cases which use socket cannot run in parallel since the socket server always run on the same port
test.describe.configure({ mode: 'serial' });
test.beforeEach(async({ page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: JSON.stringify(textAdMock.duck),
}));
await page.route(textAdMock.duck.ad.thumbnail, (route) => {
return route.fulfill({
status: 200,
path: './playwright/mocks/image_s.jpg',
});
});
});
test('base view +@dark-mode', async({ render, mockApiResponse }) => {
await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } });
await mockApiResponse('stats', statsMock.base);
test('base view +@dark-mode', async({ mount, page }) => {
await page.route(BLOCKS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(blockMock.baseListResponse),
}));
await page.route(STATS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
const component = await mount(
<TestApp>
<Blocks/>
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(BLOCKS_API_URL);
const component = await render(<Blocks/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
const hiddenFieldsTest = test.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields) as any,
const hiddenFieldsTest = test.extend<{ context: BrowserContext }>({
context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields),
});
hiddenFieldsTest('hidden fields', async({ mount, page }) => {
await page.route(BLOCKS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(blockMock.baseListResponse),
}));
await page.route(STATS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
const component = await mount(
<TestApp>
<Blocks/>
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(BLOCKS_API_URL);
hiddenFieldsTest('hidden fields', async({ render, mockApiResponse }) => {
await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } });
await mockApiResponse('stats', statsMock.base);
const component = await render(<Blocks/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
......@@ -92,66 +46,33 @@ hiddenFieldsTest('hidden fields', async({ mount, page }) => {
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test(' base view', async({ mount, page }) => {
await page.route(BLOCKS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(blockMock.baseListResponse),
}));
await page.route(STATS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
const component = await mount(
<TestApp>
<Blocks/>
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(BLOCKS_API_URL);
test(' base view', async({ render, mockApiResponse }) => {
await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } });
await mockApiResponse('stats', statsMock.base);
const component = await render(<Blocks/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
const hiddenFieldsTest = test.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields) as any,
const hiddenFieldsTest = test.extend<{ context: BrowserContext }>({
context: contextWithEnvs(configs.viewsEnvs.block.hiddenFields),
});
hiddenFieldsTest('hidden fields', async({ mount, page }) => {
await page.route(BLOCKS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(blockMock.baseListResponse),
}));
await page.route(STATS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
const component = await mount(
<TestApp>
<Blocks/>
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(BLOCKS_API_URL);
hiddenFieldsTest('hidden fields', async({ render, mockApiResponse }) => {
await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } });
await mockApiResponse('stats', statsMock.base);
const component = await render(<Blocks/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
});
test('new item from socket', async({ mount, page, createSocket }) => {
await page.route(BLOCKS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(blockMock.baseListResponse),
}));
test('new item from socket', async({ render, mockApiResponse, createSocket }) => {
await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } });
const component = await mount(
<TestApp withSocket>
<Blocks/>
</TestApp>,
{ hooksConfig },
);
const component = await render(<Blocks/>, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, 'blocks:new_block');
......@@ -167,18 +88,10 @@ test('new item from socket', async({ mount, page, createSocket }) => {
await expect(component).toHaveScreenshot();
});
test('socket error', async({ mount, page, createSocket }) => {
await page.route(BLOCKS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(blockMock.baseListResponse),
}));
const component = await mount(
<TestApp withSocket>
<Blocks/>
</TestApp>,
{ hooksConfig },
);
test('socket error', async({ render, mockApiResponse, createSocket }) => {
await mockApiResponse('blocks', blockMock.baseListResponse, { queryParams: { type: 'block' } });
const component = await render(<Blocks/>, { hooksConfig }, { withSocket: true });
const socket = await createSocket();
await socketServer.joinChannel(socket, 'blocks:new_block');
......
......@@ -44,7 +44,7 @@ const MyProfile = () => {
<Input
required
disabled
value={ data.email }
value={ data.email || '' }
/>
<FormLabel>Email</FormLabel>
</FormControl>
......
import { test as base, expect } from '@playwright/experimental-ct-react';
import type { BrowserContext } from '@playwright/test';
import React from 'react';
import * as textAdMock from 'mocks/ad/textAd';
import * as validatorsMock from 'mocks/validators/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import { test as base, expect } from 'playwright/lib';
import * as configs from 'playwright/utils/configs';
import Validators from './Validators';
const VALIDATORS_API_URL = buildApiUrl('validators', { chainType: 'stability' });
const VALIDATORS_COUNTERS_API_URL = buildApiUrl('validators_counters', { chainType: 'stability' });
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.validators) as any,
});
test.beforeEach(async({ page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: JSON.stringify(textAdMock.duck),
}));
await page.route(textAdMock.duck.ad.thumbnail, (route) => {
return route.fulfill({
status: 200,
path: './playwright/mocks/image_s.jpg',
});
});
const test = base.extend<{ context: BrowserContext }>({
context: contextWithEnvs(configs.featureEnvs.validators),
});
test('base view +@mobile', async({ mount, page }) => {
await page.route(VALIDATORS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(validatorsMock.validatorsResponse),
}));
await page.route(VALIDATORS_COUNTERS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(validatorsMock.validatorsCountersResponse),
}));
test('base view +@mobile', async({ render, mockApiResponse }) => {
await mockApiResponse('validators', validatorsMock.validatorsResponse, { pathParams: { chainType: 'stability' } });
await mockApiResponse('validators_counters', validatorsMock.validatorsCountersResponse, { pathParams: { chainType: 'stability' } });
const component = await mount(
<TestApp>
<Validators/>
</TestApp>,
);
const component = await render(<Validators/>);
await expect(component).toHaveScreenshot();
});
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import * as mocks from 'mocks/account/verifiedAddresses';
import * as profileMock from 'mocks/user/profile';
import authFixture from 'playwright/fixtures/auth';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import { test as base, expect } from 'playwright/lib';
import VerifiedAddresses from './VerifiedAddresses';
const VERIFIED_ADDRESS_URL = buildApiUrl('verified_addresses', { chainId: '1' });
const TOKEN_INFO_APPLICATIONS_URL = buildApiUrl('token_info_applications', { chainId: '1', id: undefined });
const USER_INFO_URL = buildApiUrl('user_info');
const test = base.extend({
context: ({ context }, use) => {
authFixture(context);
......@@ -20,90 +14,37 @@ const test = base.extend({
},
});
test.beforeEach(async({ context }) => {
await context.route(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, (route) => {
return route.fulfill({
status: 200,
path: './playwright/mocks/image_s.jpg',
});
});
test.beforeEach(async({ mockAssetResponse }) => {
await mockAssetResponse(mocks.TOKEN_INFO_APPLICATION_BASE.iconUrl, './playwright/mocks/image_s.jpg');
});
test('base view +@mobile', async({ mount, page }) => {
await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT),
}));
await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT),
}));
await page.route(USER_INFO_URL, (route) => route.fulfill({
body: JSON.stringify(profileMock.base),
}));
const component = await mount(
<TestApp>
<VerifiedAddresses/>
</TestApp>,
);
test('base view +@mobile', async({ render, mockApiResponse }) => {
await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } });
await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT, { pathParams: { chainId: '1', id: undefined } });
await mockApiResponse('user_info', profileMock.base);
const component = await render(<VerifiedAddresses/>);
await expect(component).toHaveScreenshot();
});
test('user without email', async({ mount, page }) => {
await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT),
}));
test('user without email', async({ render, mockApiResponse }) => {
await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } });
await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT, { pathParams: { chainId: '1', id: undefined } });
await mockApiResponse('user_info', profileMock.withoutEmail);
await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT),
}));
await page.route(USER_INFO_URL, (route) => route.fulfill({
body: JSON.stringify(profileMock.withoutEmail),
}));
const component = await mount(
<TestApp>
<VerifiedAddresses/>
</TestApp>,
);
const component = await render(<VerifiedAddresses/>);
await expect(component).toHaveScreenshot();
});
test('address verification flow', async({ mount, page }) => {
const CHECK_ADDRESS_URL = buildApiUrl('address_verification', { chainId: '1', type: ':prepare' });
const VERIFY_ADDRESS_URL = buildApiUrl('address_verification', { chainId: '1', type: ':verify' });
await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT),
}));
await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT),
}));
test('address verification flow', async({ render, mockApiResponse, page }) => {
await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } });
await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.DEFAULT, { pathParams: { chainId: '1', id: undefined } });
await mockApiResponse('address_verification', mocks.ADDRESS_CHECK_RESPONSE.SUCCESS as never, { pathParams: { chainId: '1', type: ':prepare' } });
await mockApiResponse('address_verification', mocks.ADDRESS_VERIFY_RESPONSE.SUCCESS as never, { pathParams: { chainId: '1', type: ':verify' } });
await mockApiResponse('user_info', profileMock.base);
await page.route(CHECK_ADDRESS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.ADDRESS_CHECK_RESPONSE.SUCCESS),
}));
await page.route(VERIFY_ADDRESS_URL, (route) => {
return route.fulfill({
body: JSON.stringify(mocks.ADDRESS_VERIFY_RESPONSE.SUCCESS),
});
});
await page.route(USER_INFO_URL, (route) => route.fulfill({
body: JSON.stringify(profileMock.base),
}));
await mount(
<TestApp>
<VerifiedAddresses/>
</TestApp>,
);
await render(<VerifiedAddresses/>);
// open modal
await page.getByRole('button', { name: /add address/i }).click();
......@@ -125,37 +66,20 @@ test('address verification flow', async({ mount, page }) => {
await expect(page).toHaveScreenshot();
});
test('application update flow', async({ mount, page }) => {
const TOKEN_INFO_APPLICATION_URL = buildApiUrl('token_info_applications', { chainId: '1', id: mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM.id });
const FORM_CONFIG_URL = buildApiUrl('token_info_applications_config', { chainId: '1' });
await page.route(VERIFIED_ADDRESS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT),
}));
test('application update flow', async({ render, mockApiResponse, page }) => {
await mockApiResponse('verified_addresses', mocks.VERIFIED_ADDRESS_RESPONSE.DEFAULT, { pathParams: { chainId: '1' } });
await mockApiResponse('token_info_applications', mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.FOR_UPDATE, { pathParams: { chainId: '1', id: undefined } });
await mockApiResponse('user_info', profileMock.base);
await mockApiResponse('token_info_applications_config', mocks.TOKEN_INFO_FORM_CONFIG, { pathParams: { chainId: '1' } });
await page.route(TOKEN_INFO_APPLICATIONS_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.TOKEN_INFO_APPLICATIONS_RESPONSE.FOR_UPDATE),
}));
await page.route(FORM_CONFIG_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.TOKEN_INFO_FORM_CONFIG),
}));
await page.route(USER_INFO_URL, (route) => route.fulfill({
body: JSON.stringify(profileMock.base),
}));
// PUT request
await page.route(TOKEN_INFO_APPLICATION_URL, (route) => route.fulfill({
body: JSON.stringify(mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM),
}));
await mount(
<TestApp>
<VerifiedAddresses/>
</TestApp>,
await mockApiResponse(
'token_info_applications',
mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM as never, // this mock is for PUT request
{ pathParams: { chainId: '1', id: mocks.TOKEN_INFO_APPLICATION.UPDATED_ITEM.id } },
);
await render(<VerifiedAddresses/>);
// open form
await page.locator('tr').filter({ hasText: 'waiting for update' }).locator('button[aria-label="edit"]').click();
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import * as profileMock from 'mocks/user/profile';
import authFixture from 'playwright/fixtures/auth';
import TestApp from 'playwright/TestApp';
import { test, expect } from 'playwright/lib';
import * as app from 'playwright/utils/app';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import ProfileMenuDesktop from './ProfileMenuDesktop';
test('no auth', async({ mount, page }) => {
test('no auth', async({ render, page }) => {
const hooksConfig = {
router: {
asPath: '/',
pathname: '/',
},
};
const component = await mount(
<TestApp>
<ProfileMenuDesktop/>
</TestApp>,
{ hooksConfig },
);
const component = await render(<ProfileMenuDesktop/>, { hooksConfig });
await component.locator('a').click();
expect(page.url()).toBe(`${ app.url }/auth/auth0?path=%2F`);
});
test.describe('auth', () => {
const extendedTest = test.extend({
context: ({ context }, use) => {
authFixture(context);
use(context);
},
});
extendedTest('+@dark-mode', async({ mount, page }) => {
await page.route(buildApiUrl('user_info'), (route) => route.fulfill({
status: 200,
body: JSON.stringify(profileMock.base),
}));
await page.route(profileMock.base.avatar, (route) => {
return route.fulfill({
status: 200,
path: './playwright/mocks/image_s.jpg',
});
});
const authTest = test.extend({
context: ({ context }, use) => {
authFixture(context);
use(context);
},
});
authTest('auth +@dark-mode', async({ render, page, mockApiResponse, mockAssetResponse }) => {
await mockApiResponse('user_info', profileMock.base);
await mockAssetResponse(profileMock.base.avatar, './playwright/mocks/image_s.jpg');
const component = await mount(
<TestApp>
<ProfileMenuDesktop/>
</TestApp>,
);
const component = await render(<ProfileMenuDesktop/>);
await component.getByAltText(/Profile picture/i).click();
await component.getByAltText(/Profile picture/i).click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } });
});
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 600 } });
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment