Skip to content

Commit

Permalink
feat: add maxIntervalBetweenRequestsInMs option to waitForAllRequests…
Browse files Browse the repository at this point in the history
…Complete

tests: add more tests for waitForAllRequestsComplete function
refactor: use E2edError instead of Error in internal runtime tests
  • Loading branch information
uid11 committed Jul 15, 2023
1 parent 09d6166 commit c2c5d98
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 93 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,20 +341,20 @@ This parameter can be overridden in the test-specific options.
If the test run takes longer than this timeout, the test fails and rerun on the next retry.
This parameter can be overridden in the test-specific options.

`waitForAllRequestsComplete.firstRequestTimeout: number`: default timeout (in milliseconds) for
first request for waitForAllRequestsComplete function.
If before the expiration of this timeout there is no request that satisfies the predicate,
then the promise returned by the waitForAllRequestsComplete function will be successfully resolved.
`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
returned by the `waitForAllRequestsComplete` function will be successfully resolved.

`waitForAllRequestsComplete.timeout: number`: default timeout (in milliseconds) for waitForAllRequestsComplete function.
`waitForAllRequestsComplete.timeout: number`: default timeout (in milliseconds) for `waitForAllRequestsComplete` function.
If the wait is longer than this timeout, then the promise
returned by the waitForAllRequestsComplete function will be rejected.
returned by the `waitForAllRequestsComplete` function will be rejected.

`waitForRequestTimeout: number`: default timeout (in milliseconds) for waitForRequest function.
If the wait is longer than this timeout, then the promise returned by the waitForRequest function will be rejected.
`waitForRequestTimeout: number`: default timeout (in milliseconds) for `waitForRequest` function.
If the wait is longer than this timeout, then the promise returned by the `waitForRequest` function will be rejected.

`waitForResponseTimeout: number`: default timeout (in milliseconds) for waitForResponse function.
If the wait is longer than this timeout, then the promise returned by the waitForResponse function will be rejected.
`waitForResponseTimeout: number`: default timeout (in milliseconds) for `waitForResponse` function.
If the wait is longer than this timeout, then the promise returned by the `waitForResponse` function will be rejected.

### Environment variables

Expand Down
2 changes: 1 addition & 1 deletion autotests/packs/allTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const pack: Pack = {
testIdleTimeout: 20_000,
testTimeout: 60_000,
waitForAllRequestsComplete: {
firstRequestTimeout: 500,
maxIntervalBetweenRequestsInMs: 500,
timeout: 30_000,
},
waitForRequestTimeout: 30_000,
Expand Down
4 changes: 2 additions & 2 deletions autotests/tests/main/exists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
waitForRequest,
waitForResponse,
} from 'e2ed/actions';
import {getDocumentUrl} from 'e2ed/utils';
import {E2edError, getDocumentUrl} from 'e2ed/utils';

const language = 'en';
const searchQuery = 'foo';
Expand All @@ -23,7 +23,7 @@ it('exists', {meta: {testId: '1'}, testIdleTimeout: 35_000, testTimeout: 90_000}
.eql(2)
.then(
() => {
throw new Error('the "expect" function did not throw an error');
throw new E2edError('the "expect" function did not throw an error');
},
() => undefined,
);
Expand Down
3 changes: 2 additions & 1 deletion autotests/tests/notInAllTestsPack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
*/

import {it} from 'autotests';
import {E2edError} from 'e2ed/utils';

it('not in allTests pack', {meta: {testId: '13'}}, () => {
throw new Error('Test filtered from the pack "allTests" was running');
throw new E2edError('Test filtered from the pack "allTests" was running');
});
4 changes: 2 additions & 2 deletions autotests/tests/request.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {it} from 'autotests';
import {GetUsers} from 'autotests/routes/apiRoutes';
import {expect} from 'e2ed';
import {request} from 'e2ed/utils';
import {E2edError, request} from 'e2ed/utils';

it(
'send correct requests and rejects on timeout',
Expand All @@ -15,7 +15,7 @@ it(

await request(GetUsers, {maxRetriesCount: 1, timeout: 2_000}).then(
() => {
throw new Error('the "request" function did not throw timeout error');
throw new E2edError('the "request" function did not throw timeout error');
},
() => undefined,
);
Expand Down
80 changes: 63 additions & 17 deletions autotests/tests/waitForAllRequestsComplete.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import {it} from 'autotests';
import {createClientFunction} from 'e2ed';
import {waitForAllRequestsComplete} from 'e2ed/actions';
import {log} from 'e2ed/utils';
import {waitForAllRequestsComplete, waitForTimeout} from 'e2ed/actions';
import {E2edError, log} from 'e2ed/utils';

it(
'waitForAllRequestsComplete works correct with timeout and predicate in base cases',
{meta: {testId: '9'}, testIdleTimeout: 3_000},
{meta: {testId: '9'}, testIdleTimeout: 6_000},
// eslint-disable-next-line complexity, max-statements
async () => {
let startRequestInMs = Date.now();

await waitForAllRequestsComplete(() => true, {firstRequestTimeout: 300});
await waitForAllRequestsComplete(() => true, {maxIntervalBetweenRequestsInMs: 300});

let waitedInMs = Date.now() - startRequestInMs;

if (waitedInMs < 300 || waitedInMs > 400) {
throw new Error('waitForAllRequestsComplete did not wait for firstRequestTimeout');
throw new E2edError(
'waitForAllRequestsComplete did not wait for maxIntervalBetweenRequestsInMs in the beginning',
{waitedInMs},
);
}

await waitForAllRequestsComplete(() => true, {timeout: 100}).then(
() => {
throw new Error('waitForAllRequestsComplete did not throw an error after timeout');
throw new E2edError('waitForAllRequestsComplete did not throw an error after timeout');
},
(error: unknown) => {
log('Catch error from waitForAllRequestsComplete for {timeout: 100}', {error});
Expand All @@ -30,18 +34,23 @@ it(

startRequestInMs = Date.now();

const promise = waitForAllRequestsComplete(() => true, {timeout: 1000});
let promise = waitForAllRequestsComplete(() => true, {timeout: 1000});

const getUsers = createClientFunction(
() => fetch('https://reqres.in/api/users?delay=2', {method: 'GET'}),
{name: 'getUsers', timeout: 3_000},
const clientGetUsers = createClientFunction(
(delay: number) => fetch(`https://reqres.in/api/users?delay=${delay}`, {method: 'GET'}),
{name: 'getUsers', timeout: 6_000},
);
const getUsers = (delay: number): Promise<unknown> => {
log(`Send API request with delay = ${delay}000ms`);

return clientGetUsers(delay);
};

void getUsers();
void getUsers(2);

await promise.then(
() => {
throw new Error(
throw new E2edError(
'waitForAllRequestsComplete did not throw an error after timeout with real request',
);
},
Expand All @@ -53,23 +62,60 @@ it(
waitedInMs = Date.now() - startRequestInMs;

if (waitedInMs < 1000 || waitedInMs > 1100) {
throw new Error('waitForAllRequestsComplete did not wait for timeout');
throw new E2edError('waitForAllRequestsComplete did not wait for timeout', {waitedInMs});
}

void getUsers();
void getUsers(2);

startRequestInMs = Date.now();

await waitForAllRequestsComplete(
({url}) => !url.includes('https://reqres.in/api/users?delay=2'),
({url}) => !url.includes('https://reqres.in/api/users?delay='),
{timeout: 1000},
);

waitedInMs = Date.now() - startRequestInMs;

if (waitedInMs < 500 || waitedInMs > 600) {
throw new Error(
'waitForAllRequestsComplete did not wait for firstRequestTimeout with filtered request',
throw new E2edError(
'waitForAllRequestsComplete did not wait for maxIntervalBetweenRequestsInMs with filtered request',
{waitedInMs},
);
}

promise = waitForAllRequestsComplete(() => true);

await getUsers(1);
await waitForTimeout(400);
await getUsers(1);

startRequestInMs = Date.now();

await promise;

waitedInMs = Date.now() - startRequestInMs;

if (waitedInMs < 400 || waitedInMs > 600) {
throw new E2edError(
'waitForAllRequestsComplete did not wait for maxIntervalBetweenRequestsInMs between requests',
{waitedInMs},
);
}

promise = waitForAllRequestsComplete(() => true, {maxIntervalBetweenRequestsInMs: 300});

await getUsers(1);

startRequestInMs = Date.now();

await promise;

waitedInMs = Date.now() - startRequestInMs;

if (waitedInMs < 200 || waitedInMs > 400) {
throw new E2edError(
'waitForAllRequestsComplete did not wait for maxIntervalBetweenRequestsInMs in the end',
{waitedInMs},
);
}
},
Expand Down
3 changes: 2 additions & 1 deletion autotests/tests/waitForRequest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {it} from 'autotests';
import {createClientFunction, expect} from 'e2ed';
import {waitForRequest} from 'e2ed/actions';
import {E2edError} from 'e2ed/utils';

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

Expand Down Expand Up @@ -33,7 +34,7 @@ it(

await waitForRequest(() => false, {timeout: 100}).then(
() => {
throw new Error('waitForRequest did not throw an error after timeout');
throw new E2edError('waitForRequest did not throw an error after timeout');
},
() => undefined,
);
Expand Down
3 changes: 2 additions & 1 deletion autotests/tests/waitForResponse.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {it} from 'autotests';
import {createClientFunction, expect} from 'e2ed';
import {waitForResponse} from 'e2ed/actions';
import {E2edError} from 'e2ed/utils';

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

Expand Down Expand Up @@ -33,7 +34,7 @@ it(

await waitForResponse(() => false, {timeout: 100}).then(
() => {
throw new Error('waitForResponse did not throw an error after timeout');
throw new E2edError('waitForResponse did not throw an error after timeout');
},
() => undefined,
);
Expand Down
61 changes: 39 additions & 22 deletions src/actions/waitFor/waitForAllRequestsComplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ import type {
Void,
} from '../../types/internal';

type Options = Readonly<{maxIntervalBetweenRequestsInMs?: number; timeout?: number}>;

/**
* Wait for the complete of all requests that satisfy the request predicate.
* If the function runs longer than the specified timeout, it is rejected.
* If there are no new requests for more than `maxIntervalBetweenRequestsInMs`ms,
* the function resolves successfully.
*/
export const waitForAllRequestsComplete = async (
predicate: RequestPredicate,
{firstRequestTimeout, timeout}: {firstRequestTimeout?: number; timeout?: number} = {},
{maxIntervalBetweenRequestsInMs, timeout}: Options = {},
): Promise<void> => {
const startTimeInMs = Date.now() as UtcTimeInMs;

Expand All @@ -34,25 +39,31 @@ export const waitForAllRequestsComplete = async (
const {allRequestsCompletePredicates, hashOfNotCompleteRequests} = getWaitForEventsState(
RequestHookToWaitForEvents,
);
const {waitForAllRequestsComplete: defaultTimeouts} = getFullPackConfig();
const resolveTimeout =
maxIntervalBetweenRequestsInMs ?? defaultTimeouts.maxIntervalBetweenRequestsInMs;
const rejectTimeout = timeout ?? defaultTimeouts.timeout;

log(
`Set wait for all requests complete with timeout ${rejectTimeout}ms`,
{maxIntervalBetweenRequestsInMs: resolveTimeout, predicate},
LogEventType.InternalCore,
);

const requestHookContextIds = await getInitialIdsForAllRequestsCompletePredicate(
hashOfNotCompleteRequests,
predicate,
);

const {waitForAllRequestsComplete: defaultTimeouts} = getFullPackConfig();
const firstRequestResolveTimeout = firstRequestTimeout ?? defaultTimeouts.firstRequestTimeout;
const rejectTimeout = timeout ?? defaultTimeouts.timeout;

const {clearRejectTimeout, promise, reject, resolve, setRejectTimeoutFunction} =
getPromiseWithResolveAndReject<Void>(rejectTimeout);

const allRequestsCompletePredicateWithPromise: AllRequestsCompletePredicateWithPromise = {
clearResolveTimeout: undefined,
clearResolveTimeout: () => {},
predicate,
reject,
requestHookContextIds,
resolve,
startTimeInMs,
setResolveTimeout: () => {},
};
const testRunPromise = getTestRunPromise();

Expand All @@ -70,22 +81,16 @@ export const waitForAllRequestsComplete = async (

allRequestsCompletePredicates.delete(allRequestsCompletePredicateWithPromise);

allRequestsCompletePredicateWithPromise.clearResolveTimeout?.();
allRequestsCompletePredicateWithPromise.clearResolveTimeout();

reject(error);
});

allRequestsCompletePredicates.add(allRequestsCompletePredicateWithPromise);

log(
`Set wait for all requests complete with timeout ${rejectTimeout}ms and first request timeout ${firstRequestResolveTimeout}ms`,
{predicate},
LogEventType.InternalCore,
);
const setResolveTimeout = (): void => {
allRequestsCompletePredicateWithPromise.clearResolveTimeout();

if (requestHookContextIds.size === 0) {
const {clearRejectTimeout: clearResolveTimeout, promise: firstRequestResolvePromise} =
getPromiseWithResolveAndReject<Void>(firstRequestResolveTimeout);
const {clearRejectTimeout: clearResolveTimeout, promise: resolvePromise} =
getPromiseWithResolveAndReject<Void>(resolveTimeout);

setReadonlyProperty(
allRequestsCompletePredicateWithPromise,
Expand All @@ -95,19 +100,31 @@ export const waitForAllRequestsComplete = async (

void testRunPromise.then(clearResolveTimeout);

firstRequestResolvePromise.catch(() => {
resolvePromise.catch(() => {
allRequestsCompletePredicates.delete(allRequestsCompletePredicateWithPromise);

const waitInMs = Date.now() - startTimeInMs;

log(
`Have waited for all requests complete by first request timeout for ${waitInMs}ms`,
{firstRequestResolveTimeout, predicate, rejectTimeout},
`Have waited for all requests complete for ${waitInMs}ms`,
{maxIntervalBetweenRequestsInMs: resolveTimeout, predicate, timeout: rejectTimeout},
LogEventType.InternalUtil,
);

resolve();
});
};

setReadonlyProperty(
allRequestsCompletePredicateWithPromise,
'setResolveTimeout',
setResolveTimeout,
);

allRequestsCompletePredicates.add(allRequestsCompletePredicateWithPromise);

if (requestHookContextIds.size === 0) {
allRequestsCompletePredicateWithPromise.setResolveTimeout();
}

return promise;
Expand Down
2 changes: 1 addition & 1 deletion src/actions/waitFor/waitForInterfaceStabilization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export const waitForInterfaceStabilization = async (stabilizationInterval = 500)
const startDateTimeInIso = new Date(startTimeInMs).toISOString();

log(
`Waited for interface stabilization for ${waitInMs}ms with stabilization interval ${stabilizationInterval}ms`,
`Have waited for interface stabilization for ${waitInMs}ms with stabilization interval ${stabilizationInterval}ms`,
{error: maybeErrorReason, startDateTimeInIso},
LogEventType.InternalAction,
);
Expand Down
1 change: 1 addition & 0 deletions src/actions/waitFor/waitForRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {

/**
* Wait for some request (from browser) by the request predicate.
* If the function runs longer than the specified timeout, it is rejected.
*/
export const waitForRequest = <SomeRequest extends Request>(
predicate: RequestPredicate<SomeRequest>,
Expand Down
1 change: 1 addition & 0 deletions src/actions/waitFor/waitForResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {

/**
* Wait for some response (from browser) by the response predicate.
* If the function runs longer than the specified timeout, it is rejected.
*/
export const waitForResponse = <SomeResponse extends Response>(
predicate: ResponsePredicate<SomeResponse>,
Expand Down
Loading

0 comments on commit c2c5d98

Please sign in to comment.