Skip to content

Commit

Permalink
FI-1557 fix: ingore all re-requests when waitForAllRequestsComplete
Browse files Browse the repository at this point in the history
tests: add tests of types for `mockWebSocketRoute`
tests: add tests for `mockWebSocketRoute`
fix: protocol in `WebSocketRoute.getUrl` method
fix: types of request and response for `mockWebSocketRoute`
fix: context in `onMessage` handler for `mockWebSocketRoute`
  • Loading branch information
uid11 committed Dec 2, 2024
1 parent c948cec commit 18553b3
Show file tree
Hide file tree
Showing 18 changed files with 263 additions and 44 deletions.
1 change: 1 addition & 0 deletions autotests/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {createDevice} from './device';
export {addProduct} from './product';
export {sendScore} from './score';
export {createUser} from './user';
export {addUser, getUsers} from './worker';
25 changes: 25 additions & 0 deletions autotests/entities/score.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {createClientFunction} from 'e2ed';

import type {ClientFunction, Url} from 'e2ed/types';

/**
* Sends page score.
*/
export const sendScore: ClientFunction<[string, Url], Promise<string>> = createClientFunction(
(pageState, url) => {
const socket = new WebSocket(url);
const data = JSON.stringify({pageState});
const promise = new Promise<string>((resolve) => {
socket.onmessage = (event) => {
resolve(event.data as string);
};
});

socket.onopen = () => {
socket.send(data);
};

return promise;
},
{name: 'sendScore', timeout: 1_000},
);
12 changes: 12 additions & 0 deletions autotests/routes/webSocketRoutes/Base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {WebSocketRoute} from 'e2ed';

import type {WebSocketBaseRequest, WebSocketBaseResponse} from 'autotests/types';

/**
* Base WebSocket.
*/
export class Base extends WebSocketRoute<undefined, WebSocketBaseRequest, WebSocketBaseResponse> {
getPath(): string {
return '/base';
}
}
33 changes: 33 additions & 0 deletions autotests/routes/webSocketRoutes/Score.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {stringify} from 'node:querystring';
import {URL} from 'node:url';

import {WebSocketRoute} from 'e2ed';
import {assertValueIsTrue} from 'e2ed/utils';

import type {WebSocketScoreRequest, WebSocketScoreResponse} from 'autotests/types';
import type {Url} from 'e2ed/types';

type Params = Readonly<{size: number}>;

const pathname = '/score';

/**
* Score WebSocket.
*/
export class Score extends WebSocketRoute<Params, WebSocketScoreRequest, WebSocketScoreResponse> {
static override getParamsFromUrlOrThrow(url: Url): Params {
const urlObject = new URL(url);
const size = urlObject.searchParams.get('size');

assertValueIsTrue(urlObject.pathname === pathname, 'pathname is correct', {urlObject});

return {size: Number(size)};
}

getPath(): string {
const {size} = this.routeParams;
const query = stringify({size});

return `${pathname}?${query}`;
}
}
2 changes: 2 additions & 0 deletions autotests/routes/webSocketRoutes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {Base} from './Base';
export {Score} from './Score';
45 changes: 45 additions & 0 deletions autotests/tests/e2edReportExample/mockWebSocketRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {test} from 'autotests';
import {sendScore} from 'autotests/entities';
import {E2edReportExample} from 'autotests/pageObjects/pages';
import {Score as ScoreRoute} from 'autotests/routes/webSocketRoutes';
import {expect} from 'e2ed';
import {mockWebSocketRoute, navigateToPage, unmockWebSocketRoute} from 'e2ed/actions';
import {assertFunctionThrows} from 'e2ed/utils';

import type {Url} from 'e2ed/types';

test(
'mockWebSocketRoute correctly intercepts requests, and unmockWebSocketRoute cancels the interception',
{meta: {testId: '19'}, testIdleTimeout: 3_000},
async () => {
await mockWebSocketRoute(ScoreRoute, ({size}, {pageState}) => {
const stateScore = Number(pageState);

return {score: size * stateScore};
});

await navigateToPage(E2edReportExample);

const pageState = '5';
const size = 3;
const webSocketUrl = `wss://localhost/score?size=${size}` as Url;

const result = await sendScore(pageState, webSocketUrl);

const scoreRouteParams = ScoreRoute.getParamsFromUrlOrThrow(webSocketUrl);

const scoreRouteFromUrl = new ScoreRoute(scoreRouteParams);

await expect(scoreRouteFromUrl.routeParams.size, 'route has correct params').eql(size);

await expect(JSON.parse(result), 'mocked WebSocket returns correct result').eql({
score: size * Number(pageState),
});

await unmockWebSocketRoute(ScoreRoute);

await assertFunctionThrows(async () => {
await sendScore(pageState, webSocketUrl);
}, 'throws an error after unmocking WebSocket route');
},
);
55 changes: 55 additions & 0 deletions autotests/tests/internalTypeTests/mockWebSocketRoute.skip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {Main} from 'autotests/routes/pageRoutes';
import {Base, Score} from 'autotests/routes/webSocketRoutes';
import {mockWebSocketRoute, unmockWebSocketRoute} from 'e2ed/actions';

import type {WebSocketScoreRequest, WebSocketScoreResponse} from 'autotests/types';
import type {Any} from 'e2ed/types';

const anyMockFunction = (..._args: Any[]): Any => {};

const webSocketMockFunction = (
{size}: {size: number},
{pageState}: WebSocketScoreRequest,
): WebSocketScoreResponse => {
if (pageState !== '') {
return {score: 8};
}

return {score: size > 2 ? size : 2};
};

// @ts-expect-error: mockWebSocketRoute require WebSocket route as first argument
void mockWebSocketRoute(Main, anyMockFunction);

// @ts-expect-error: unmockWebSocketRoute require WebSocket route as first argument
void unmockWebSocketRoute(Main);

// @ts-expect-error: mockWebSocketRoute require WebSocket route with static method getParamsFromUrlOrThrow
void mockWebSocketRoute(Base, anyMockFunction);

// ok
void mockWebSocketRoute(Score, anyMockFunction);

// @ts-expect-error: unmockWebSocketRoute require WebSocket route with static method getParamsFromUrlOrThrow
void unmockWebSocketRoute(Base);

// ok
void mockWebSocketRoute(Score, webSocketMockFunction);

// ok
void unmockWebSocketRoute(Score);

// ok
void mockWebSocketRoute(
Score,
async (
{size},
{pageState}, // eslint-disable-next-line @typescript-eslint/require-await
) => {
if (pageState !== '') {
return {score: 10};
}

return {score: size > 1 ? size : 1};
},
);
2 changes: 1 addition & 1 deletion autotests/tests/mockApiRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {Url} from 'e2ed/types';

test(
'mockApiRoute correctly intercepts requests, and unmockApiRoute cancels the interception',
{meta: {testId: '6'}, testIdleTimeout: 15_000},
{meta: {testId: '6'}, testIdleTimeout: 4_000},
async () => {
await mockApiRoute(CreateProductRoute, (routeParams, {method, query, requestBody, url}) => {
const responseBody = {
Expand Down
9 changes: 9 additions & 0 deletions autotests/types/api/Base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Request for base WebSocket.
*/
export type WebSocketBaseRequest = Readonly<{pageState: string}>;

/**
* Response for base WebSocket.
*/
export type WebSocketBaseResponse = Readonly<{tags: readonly string[]}>;
9 changes: 9 additions & 0 deletions autotests/types/api/Score.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Request for score WebSocket.
*/
export type WebSocketScoreRequest = Readonly<{pageState: string}>;

/**
* Response for score WebSocket.
*/
export type WebSocketScoreResponse = Readonly<{score: number}>;
2 changes: 2 additions & 0 deletions autotests/types/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export type {ApiAddUserRequest, ApiAddUserResponse} from './AddUser';
export type {WebSocketBaseRequest, WebSocketBaseResponse} from './Base';
export type {ApiCreateDeviceRequest, ApiCreateDeviceResponse} from './CreateDevice';
export type {ApiCreateProductRequest, ApiCreateProductResponse} from './CreateProduct';
export type {ApiGetUserRequest, ApiGetUserResponse} from './GetUser';
export type {ApiGetUsersRequest, ApiGetUsersResponse} from './GetUsers';
export type {WebSocketScoreRequest, WebSocketScoreResponse} from './Score';
export type {ApiUserSignUpRequest, ApiUserSignUpResponse} from './UserSignUp';
4 changes: 4 additions & 0 deletions autotests/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export type {
ApiGetUsersResponse,
ApiUserSignUpRequest,
ApiUserSignUpResponse,
WebSocketBaseRequest,
WebSocketBaseResponse,
WebSocketScoreRequest,
WebSocketScoreResponse,
} from './api';
export type {
ApiDevice,
Expand Down
3 changes: 3 additions & 0 deletions src/ApiRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export abstract class ApiRoute<
return true;
}

/**
* Returns the origin of the route.
*/
getOrigin(): Url {
return 'http://localhost' as Url;
}
Expand Down
3 changes: 3 additions & 0 deletions src/PageRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import type {Url} from './types/internal';
* Abstract route for page.
*/
export abstract class PageRoute<Params = undefined> extends Route<Params> {
/**
* Returns the origin of the route.
*/
getOrigin(): Url {
const {E2ED_ORIGIN} = process.env;

Expand Down
29 changes: 29 additions & 0 deletions src/WebSocketRoute.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import {Route} from './Route';

import type {Url} from './types/internal';

const http = 'http:';
const https = 'https:';

/**
* Abstract route for WebSocket "requests".
*/
Expand Down Expand Up @@ -33,4 +38,28 @@ export abstract class WebSocketRoute<
getIsResponseBodyInJsonFormat(): boolean {
return true;
}

/**
* Returns the origin of the route.
*/
getOrigin(): Url {
return 'http://localhost' as Url;
}

/**
* Returns the url of the route.
*/
override getUrl(): Url {
const url = super.getUrl();

if (url.startsWith(https)) {
return `wss:${url.slice(https.length)}` as Url;
}

if (url.startsWith(http)) {
return `ws:${url.slice(http.length)}` as Url;
}

return url;
}
}
2 changes: 1 addition & 1 deletion src/actions/mock/mockWebSocketRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type {
* (by methods `getParamsFromUrlOrThrow` and `isMatchUrl`).
*/
export const mockWebSocketRoute = async <RouteParams, SomeRequest, SomeResponse>(
Route: WebSocketRouteClassTypeWithGetParamsFromUrl<RouteParams>,
Route: WebSocketRouteClassTypeWithGetParamsFromUrl<RouteParams, SomeRequest, SomeResponse>,
webSocketMockFunction: WebSocketMockFunction<RouteParams, SomeRequest, SomeResponse>,
{skipLogs = false}: {skipLogs?: boolean} = {},
): Promise<void> => {
Expand Down
54 changes: 28 additions & 26 deletions src/utils/mockWebSocketRoute/getSetResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,34 @@ export const getSetResponse = ({
const isRequestBodyInJsonFormat = route.getIsRequestBodyInJsonFormat();
const isResponseBodyInJsonFormat = route.getIsResponseBodyInJsonFormat();

playwrightRoute.onMessage(async (message) => {
const {value: request, hasParseError} = parseValueAsJsonIfNeeded(
String(message),
isRequestBodyInJsonFormat,
);

if (hasParseError && skipLogs !== true) {
log(
'WebSocket message is not in JSON format',
{logEventStatus: LogEventStatus.Failed, message, url},
LogEventType.InternalUtil,
playwrightRoute.onMessage(
AsyncLocalStorage.bind(async (message) => {
const {value: request, hasParseError} = parseValueAsJsonIfNeeded(
String(message),
isRequestBodyInJsonFormat,
);
}

const response = await webSocketMockFunction(route.routeParams, request);

const responseAsString = getBodyAsString(response, isResponseBodyInJsonFormat);

playwrightRoute.send(responseAsString);

if (skipLogs !== true) {
log(
`A mock was applied to the WebSocket route "${route.constructor.name}"`,
{request, response, route, webSocketMockFunction},
LogEventType.InternalUtil,
);
}
});
if (hasParseError && skipLogs !== true) {
log(
'WebSocket message is not in JSON format',
{logEventStatus: LogEventStatus.Failed, message, url},
LogEventType.InternalUtil,
);
}

const response = await webSocketMockFunction(route.routeParams, request);

const responseAsString = getBodyAsString(response, isResponseBodyInJsonFormat);

playwrightRoute.send(responseAsString);

if (skipLogs !== true) {
log(
`A mock was applied to the WebSocket route "${route.constructor.name}"`,
{request, response, route, webSocketMockFunction},
LogEventType.InternalUtil,
);
}
}),
);
});
17 changes: 1 addition & 16 deletions src/utils/waitForEvents/isReRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,9 @@ export const isReRequest = (
return false;
}

const baseHeaders = baseRequest.requestHeaders;
const headers = reRequest.requestHeaders;

const headersNames = Object.keys(headers);
const baseHeadersNames = Object.keys(baseHeaders);

if (headersNames.length < baseHeadersNames.length) {
if (reRequest.method !== baseRequest.method) {
return false;
}

for (const headerName of baseHeadersNames) {
if (
!(headerName in headers) ||
String(baseHeaders[headerName]) !== String(headers[headerName])
) {
return false;
}
}

return true;
};

0 comments on commit 18553b3

Please sign in to comment.