Skip to content

Commit

Permalink
FI-1432 feat: support waitings before retries
Browse files Browse the repository at this point in the history
fix: remove references on TestCafe
fix: set actual timeout of tests for Playwright engine
fix: turn off unnecessary rule `@typescript-eslint/consistent-return`
feat: add `documentUrl` argument to `assertPage` method of pages
fix: restore `fullMocks` fixture for internal test of "full mocks"
  • Loading branch information
uid11 committed Oct 25, 2024
1 parent a833611 commit b042b43
Show file tree
Hide file tree
Showing 29 changed files with 206 additions and 63 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ rules:
'@typescript-eslint/class-literal-property-style': error
'@typescript-eslint/consistent-generic-constructors': error
'@typescript-eslint/consistent-indexed-object-style': error
'@typescript-eslint/consistent-return': error
'@typescript-eslint/consistent-return': off
'@typescript-eslint/consistent-type-assertions':
[error, {assertionStyle: as, objectLiteralTypeAssertions: never}]
'@typescript-eslint/consistent-type-definitions': [error, type]
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ This parameter can be overridden in the test-specific options.

`viewportWidth: number`: width of viewport of page in pixels.

`waitBeforeRetry: (options: Options) => number`: returns how many milliseconds `e2ed`
should wait before running test (for retries).

`waitForAllRequestsComplete.maxIntervalBetweenRequestsInMs: number`: default maximum interval
(in milliseconds) between requests for `waitForAllRequestsComplete` function.
If there are no new requests for more than this interval, then the promise
Expand Down
1 change: 1 addition & 0 deletions autotests/configurator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export type {
SkipTests,
TestFunction,
TestMeta,
WaitBeforeRetry,
} from './types';
1 change: 0 additions & 1 deletion autotests/configurator/mapLogPayloadInConsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const mapLogPayloadInConsole: MapLogPayloadInConsole = (message, payload)

if (
message.startsWith('Caught an error when running tests in retry') ||
message.startsWith('Warning from TestCafe:') ||
message.startsWith('Usage:')
) {
return payload;
Expand Down
1 change: 1 addition & 0 deletions autotests/configurator/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type {
MapLogPayloadInReport,
Pack,
TestFunction,
WaitBeforeRetry,
} from './packSpecific';
export type {SkipTests} from './skipTests';
export type {TestMeta} from './testMeta';
1 change: 1 addition & 0 deletions autotests/configurator/types/packSpecific.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export type MapLogPayloadInConsole = PackSpecificTypes['MapLogPayloadInConsole']
export type MapLogPayloadInLogFile = PackSpecificTypes['MapLogPayloadInLogFile'];
export type MapLogPayloadInReport = PackSpecificTypes['MapLogPayloadInReport'];
export type TestFunction = PackSpecificTypes['TestFunction'];
export type WaitBeforeRetry = PackSpecificTypes['WaitBeforeRetry'];
export type {Pack};
1 change: 0 additions & 1 deletion autotests/fixtures/fullMocks/jKDXNUZ75U.json

This file was deleted.

1 change: 1 addition & 0 deletions autotests/fixtures/fullMocks/mr-iHTD7Lp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"/api/product/135865":[{"completionTimeInMs":1729850053899,"duration":"4ms","request":{"method":"POST","query":{"size":"13"},"requestBody":{"cookies":[],"input":17,"model":"samsung","version":"12"},"requestHeaders":{"accept":"*/*","accept-language":"en-US","content-type":"application/json; charset=UTF-8","referer":"https://joomcode.github.io/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.35 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.35","sec-ch-ua":"\"Chromium\";v=\"130\", \"HeadlessChrome\";v=\"130\", \"Not?A_Brand\";v=\"99\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Linux\""},"url":"https://reqres.in/api/product/135865?size=13","utcTimeInMs":1729850053895},"responseBody":{"id":135865,"method":"POST","output":"17","payload":{"id":"135865","cookies":[],"input":17,"model":"samsung","version":"12"},"query":{"size":"13"},"url":"https://reqres.in/api/product/135865?size=13"},"responseHeaders":{"content-type":"application/json; charset=UTF-8","content-length":"201"},"statusCode":200},{"completionTimeInMs":1729850054453,"duration":"4ms","request":{"method":"POST","query":{"size":"13"},"requestBody":{"cookies":[],"input":17,"model":"samsung","version":"12"},"requestHeaders":{"accept":"*/*","accept-language":"en-US","content-type":"application/json; charset=UTF-8","referer":"https://joomcode.github.io/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.35 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.35","sec-ch-ua":"\"Chromium\";v=\"130\", \"HeadlessChrome\";v=\"130\", \"Not?A_Brand\";v=\"99\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Linux\""},"url":"https://reqres.in/api/product/135865?size=13","utcTimeInMs":1729850054449},"responseBody":{"cookies":[],"input":17,"model":"samsung","version":"12","id":"816","createdAt":"2024-10-25T09:54:14.388Z"},"responseHeaders":{"reporting-endpoints":"heroku-nel=https://nel.heroku.com/reports?ts=1729850054&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=LZBdsiG2rgFml4T6awFYmhftTlXLyvdfcMob39wiK2I%3D","nel":"{\"report_to\":\"heroku-nel\",\"max_age\":3600,\"success_fraction\":0.005,\"failure_fraction\":0.05,\"response_headers\":[\"Via\"]}","cf-cache-status":"DYNAMIC","etag":"W/\"6c-uS8VtSQALUKVvQbVlIe2ZwBwLRE\"","report-to":"{\"group\":\"heroku-nel\",\"max_age\":3600,\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?ts=1729850054&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=LZBdsiG2rgFml4T6awFYmhftTlXLyvdfcMob39wiK2I%3D\"}]}","via":"1.1 vegur","cf-ray":"8d8152f7a8d43cff-CDG","access-control-allow-origin":"*","content-length":"108","date":"Fri, 25 Oct 2024 09:54:14 GMT","content-type":"application/json; charset=utf-8","x-powered-by":"Express","server":"cloudflare"},"statusCode":201}]}
3 changes: 2 additions & 1 deletion autotests/packs/allTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ export const pack: Pack = {
takeViewportScreenshotOnError: true,
testFileGlobs: ['**/autotests/tests/**/*.ts'],
testIdleTimeout: 8_000,
testTimeout: 60_000,
testTimeout: 15_000,
userAgent,
viewportHeight: 1080,
viewportWidth: 1920,
waitBeforeRetry: () => 0,
waitForAllRequestsComplete: {
maxIntervalBetweenRequestsInMs: 500,
timeout: 30_000,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "e2ed",
"version": "0.18.15",
"description": "E2E testing framework over TestCafe",
"description": "E2E testing framework over Playwright",
"keywords": [
"E2E",
"TestCafe",
"Playwright",
"testing"
],
"author": "uid11",
Expand Down
3 changes: 2 additions & 1 deletion src/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ export abstract class Page<PageParams = undefined> {
* Asserts that we are on the expected page by `isMatch` flage.
* `isMatch` equals `true`, if url matches the page with given parameters, and `false` otherwise.
*/
assertPage(isMatch: boolean): AsyncVoid {
assertPage(isMatch: boolean, documentUrl: Url): AsyncVoid {
assertValueIsTrue(isMatch, `the document url matches the page "${this.constructor.name}"`, {
documentUrl,
page: this,
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/actions/pages/assertPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const assertPage = async <SomePageClass extends AnyPageClassType>(
LogEventType.InternalAction,
);

await page.assertPage(isMatch);
await page.assertPage(isMatch, documentUrl);

await page.afterAssertPage?.();

Expand Down
2 changes: 1 addition & 1 deletion src/actions/pages/navigateToPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const navigateToPage = async <SomePageClass extends AnyPageClassType>(
const documentUrl = await getDocumentUrl();
const isMatch = route.isMatchUrl(documentUrl);

await page.assertPage(isMatch);
await page.assertPage(isMatch, documentUrl);

await page.afterAssertPage?.();

Expand Down
4 changes: 2 additions & 2 deletions src/constants/testRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ declare type TestRunTypesChecks = [

/**
* Main status of test run.
* Failed if it have run error and passed if not.
* Broken if the test failed and TestCafe restarted it themself.
* `Failed` if it have run error and passed if not.
* Probably should never be `Broken`.
*/
export const enum TestRunStatus {
Broken = 'broken',
Expand Down
18 changes: 15 additions & 3 deletions src/generators/createRunId.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import {getRandomId} from './getRandomId';
import {createHash} from 'node:crypto';

import type {RunId} from '../types/internal';
import type {RunId, Test} from '../types/internal';

const runIdBaseLength = 10;

/**
* Creates new RunId for TestRun.
* @internal
*/
export const createRunId = (): RunId => getRandomId().replace(/:/g, '-') as RunId;
export const createRunId = (test: Test, retryIndex: number): RunId => {
const data = {...test, testFn: test.testFn.toString()};
const text = JSON.stringify(data);
const hash = createHash('sha1');

hash.update(text);

const base = hash.digest('base64url').slice(0, runIdBaseLength);

return `${base}-${retryIndex}` as RunId;
};
21 changes: 0 additions & 21 deletions src/types/clientFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,3 @@ export type ClientFunction<Args extends readonly unknown[] = [], Result = Void>
this: void,
...args: Args
) => Promise<Result>;

/**
* Result of the internal client function wrapper (object with error as string or with value).
* @internal
*/
export type ClientFunctionWrapperResult<Result = unknown> = Readonly<
| {
errorMessage: string;
result: undefined;
}
| {
errorMessage: undefined;
result: Result;
}
>;

/**
* Internal TestCafe error object or undefined.
* @internal
*/
export type MaybeTestCafeError = {code?: string} | undefined;
21 changes: 8 additions & 13 deletions src/types/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,6 @@ import type {
import type {WithDoBeforePack} from './doBeforePack';
import type {OwnE2edConfig} from './ownE2edConfig';

/**
* Userland part of TestCafe config.
*/
type UserlandTestCafeConfig = Readonly<{
assertionTimeout: number;
concurrency: number;
pageRequestTimeout: number;
port1: number;
port2: number;
selectorTimeout: number;
}>;

/**
* Supported browsers.
*/
Expand Down Expand Up @@ -69,5 +57,12 @@ export type UserlandPackWithoutDoBeforePack<
CustomReportProperties = CustomReportPropertiesPlaceholder,
SkipTests = SkipTestsPlaceholder,
TestMeta = TestMetaPlaceholder,
> = UserlandTestCafeConfig &
> = Readonly<{
assertionTimeout: number;
concurrency: number;
pageRequestTimeout: number;
port1: number;
port2: number;
selectorTimeout: number;
}> &
OwnE2edConfig<CustomPackProperties, CustomReportProperties, SkipTests, TestMeta>;
15 changes: 15 additions & 0 deletions src/types/config/ownE2edConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type {PlaywrightTestConfig} from '@playwright/test';

import type {TestRunStatus} from '../../constants/internal';

import type {FullMocksConfig} from '../fullMocks';
import type {MapBackendResponseToLog, MapLogPayload, MapLogPayloadInReport} from '../log';
import type {MaybePromise} from '../promise';
Expand Down Expand Up @@ -255,6 +257,19 @@ export type OwnE2edConfig<
*/
viewportWidth: number;

/**
* Returns how many milliseconds `e2ed` should wait before running test (for retries).
*/
waitBeforeRetry: (
this: void,
options: Readonly<{
previousError: string | undefined;
retryIndex: number;
status: TestRunStatus;
testStaticOptions: TestStaticOptions<TestMeta>;
}>,
) => MaybePromise<number>;

/**
* Group of settings for the `waitForAllRequestsComplete` function.
*/
Expand Down
2 changes: 0 additions & 2 deletions src/types/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ export type {Brand, IsBrand} from './brand';
export type {Expect, IsEqual, IsReadonlyKey} from './checks';
export type {Class} from './class';
export type {ClientFunction} from './clientFunction';
/** @internal */
export type {ClientFunctionWrapperResult, MaybeTestCafeError} from './clientFunction';
export type {
AnyPack,
BrowserName,
Expand Down
1 change: 1 addition & 0 deletions src/types/userland/createPackSpecificTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ export type CreatePackSpecificTypes<
MapLogPayloadInLogFile: MapLogPayload;
MapLogPayloadInReport: MapLogPayloadInReport;
TestFunction: TestFunction<PackParameters['TestMeta']>;
WaitBeforeRetry: FullPackConfigByPack<Pack>['waitBeforeRetry'];
}>;
2 changes: 2 additions & 0 deletions src/utils/fs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export {getTestRunEventFileName} from './getTestRunEventFileName';
/** @internal */
export {getLastLogEventTimeInMs, writeLogEventTime} from './logIsoString';
/** @internal */
export {readEventFromFile} from './readEventFromFile';
/** @internal */
export {readEventsFromFiles} from './readEventsFromFiles';
/** @internal */
export {readStartInfo} from './readStartInfo';
Expand Down
23 changes: 23 additions & 0 deletions src/utils/fs/readEventFromFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {readFile} from 'node:fs/promises';
import {join} from 'node:path';

import {EVENTS_DIRECTORY_PATH, READ_FILE_OPTIONS} from '../../constants/internal';

import {generalLog} from '../generalLog';

/**
* Read event object with test run from temporary directory.
* @internal
*/
export const readEventFromFile = (fileName: string): Promise<string | undefined> => {
const filePath = join(EVENTS_DIRECTORY_PATH, fileName);

return readFile(filePath, READ_FILE_OPTIONS).catch((error: unknown) => {
generalLog(`Caught an error on reading text of test run event from file "${fileName}"`, {
error,
filePath,
});

return undefined;
});
};
14 changes: 8 additions & 6 deletions src/utils/fs/readEventsFromFiles.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import {execFile} from 'node:child_process';
import {readdir, readFile} from 'node:fs/promises';
import {readdir} from 'node:fs/promises';
import {join} from 'node:path';

import {
AMOUNT_OF_PARALLEL_OPEN_FILES,
EVENTS_DIRECTORY_PATH,
INTERNAL_REPORTS_DIRECTORY_PATH,
READ_FILE_OPTIONS,
} from '../../constants/internal';

import {assertValueIsDefined, assertValueIsTrue} from '../asserts';
import {generalLog} from '../generalLog';
import {getDurationWithUnits} from '../getDurationWithUnits';

import {readEventFromFile} from './readEventFromFile';

import type {FullTestRun, UtcTimeInMs} from '../../types/internal';

/**
Expand Down Expand Up @@ -43,7 +44,7 @@ export const readEventsFromFiles = async (
fileIndex < newEventFiles.length;
fileIndex += AMOUNT_OF_PARALLEL_OPEN_FILES
) {
const readPromises: Promise<Readonly<{fileName: string; text: string}>>[] = [];
const readPromises: Promise<Readonly<{fileName: string; text: string}> | undefined>[] = [];

for (
let index = fileIndex;
Expand All @@ -58,13 +59,14 @@ export const readEventsFromFiles = async (
newEventFilesLength: newEventFiles.length,
});

const filePath = join(EVENTS_DIRECTORY_PATH, fileName);
const promise = readFile(filePath, READ_FILE_OPTIONS).then((text) => ({fileName, text}));
const promise = readEventFromFile(fileName).then((maybeText) =>
maybeText === undefined ? undefined : {fileName, text: maybeText},
);

readPromises.push(promise);
}

const filesWithNames = await Promise.all(readPromises);
const filesWithNames = (await Promise.all(readPromises)).filter((value) => value !== undefined);

for (const {fileName, text} of filesWithNames) {
try {
Expand Down
1 change: 0 additions & 1 deletion src/utils/selectors/Selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ export class Selector {
return result;
}

// eslint-disable-next-line @typescript-eslint/consistent-return
getPlaywrightLocator(): PlaywrightLocator {
const args = this.args!;
const selector = this.parentSelector!;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/selectors/createCustomMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
} from '../../types/internal';

/**
* Creates custom `e2ed` methods of selector (additional to selector's own methods from TestCafe).
* Creates custom `e2ed` methods of selector (additional to selector's own methods).
* @internal
*/
export const createCustomMethods = (
Expand Down
17 changes: 16 additions & 1 deletion src/utils/test/beforeTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,29 @@ import type {
UtcTimeInMs,
} from '../../types/internal';

import {test} from '@playwright/test';

type Options = Readonly<{
beforeRetryTimeout: number | undefined;
retryIndex: number;
runId: RunId;
testFn: TestFn;
testStaticOptions: TestStaticOptions;
}>;

const additionalDurationToPlaywrightTestTimeoutInMs = 500;

/**
* Internal before test hook.
* @internal
*/
export const beforeTest = ({retryIndex, runId, testFn, testStaticOptions}: Options): void => {
export const beforeTest = ({
beforeRetryTimeout,
retryIndex,
runId,
testFn,
testStaticOptions,
}: Options): void => {
const {options} = testStaticOptions;

setMeta(options.meta);
Expand All @@ -49,6 +60,10 @@ export const beforeTest = ({retryIndex, runId, testFn, testStaticOptions}: Optio
const testIdleTimeout = options.testIdleTimeout ?? testIdleTimeoutFromConfig;
const testTimeout = options.testTimeout ?? testTimeoutFromConfig;

test.setTimeout(
testTimeout + additionalDurationToPlaywrightTestTimeoutInMs + (beforeRetryTimeout ?? 0),
);

setTestIdleTimeout(testIdleTimeout);
setTestTimeout(testTimeout);

Expand Down
Loading

0 comments on commit b042b43

Please sign in to comment.