-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreateClientFunction.ts
More file actions
111 lines (88 loc) · 3.63 KB
/
Copy pathcreateClientFunction.ts
File metadata and controls
111 lines (88 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import {TARGET_CLOSED_ERROR_MESSAGE, TEST_ENDED_ERROR_MESSAGE} from './constants/internal';
import {getTestIdleTimeout} from './context/testIdleTimeout';
import {E2edError} from './utils/error';
import {setCustomInspectOnFunction} from './utils/fn';
import {generalLog} from './utils/generalLog';
import {getDurationWithUnits} from './utils/getDurationWithUnits';
import {addTimeoutToPromise} from './utils/promise';
import {createTestRunCallback} from './utils/testRun';
import {getPlaywrightPage} from './useContext';
import type {ClientFunction} from './types/internal';
type Options = Readonly<{name?: string; retries?: number; timeout?: number}>;
const contextErrorMessage = 'Execution context was destroyed';
/**
* Creates a client function.
*/
export const createClientFunction = <Args extends readonly unknown[], Result>(
originalFn: (...args: Args) => Result,
{name: nameFromOptions, retries = 0, timeout}: Options = {},
): ClientFunction<Args, Result> => {
setCustomInspectOnFunction(originalFn);
const name = nameFromOptions ?? originalFn.name;
const printedClientFunctionName = `client function${name ? ` "${name}"` : ''}`;
const clientFunctionWithTimeout = (...args: Args): Promise<Result> => {
const page = getPlaywrightPage();
const clientFunctionTimeout = timeout ?? getTestIdleTimeout();
const timeoutWithUnits = getDurationWithUnits(clientFunctionTimeout);
const error = new E2edError(
`Client function "${name}" rejected after ${timeoutWithUnits} timeout`,
);
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
const func = new Function('args', `return (${originalFn.toString()})(...args)`) as (
args: readonly unknown[],
) => Result;
return addTimeoutToPromise(
page.evaluate(func, args).catch(async (evaluateError: unknown) => {
const errorString = String(evaluateError);
if (
errorString.includes(contextErrorMessage) ||
errorString.includes(TARGET_CLOSED_ERROR_MESSAGE) ||
errorString.includes(TEST_ENDED_ERROR_MESSAGE)
) {
await page.waitForLoadState();
return page.evaluate(func, args).catch((suberror: unknown) => {
const suberrorString = String(suberror);
if (
suberrorString.includes(contextErrorMessage) ||
suberrorString.includes(TARGET_CLOSED_ERROR_MESSAGE) ||
suberrorString.includes(TEST_ENDED_ERROR_MESSAGE)
) {
return new Promise(() => {});
}
throw suberror;
});
}
if (retries > 0) {
let retryIndex = 1;
while (retryIndex <= retries) {
retryIndex += 1;
try {
return page.evaluate(func, args).catch((suberror: unknown) => {
const suberrorString = String(suberror);
if (
suberrorString.includes(contextErrorMessage) ||
suberrorString.includes(TARGET_CLOSED_ERROR_MESSAGE) ||
suberrorString.includes(TEST_ENDED_ERROR_MESSAGE)
) {
return new Promise(() => {});
}
throw suberror;
});
} catch {}
}
}
throw evaluateError;
}),
clientFunctionTimeout,
error,
);
};
generalLog(`Create ${printedClientFunctionName}`, {originalFn});
return (...args: Args) => {
const clientFunctionWithTestRun = createTestRunCallback({
targetFunction: clientFunctionWithTimeout,
throwExceptionAtCallPoint: true,
});
return clientFunctionWithTestRun(...args);
};
};