diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 5d6d3e3e..b7eb4bca 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -174,6 +174,7 @@ rules: '@typescript-eslint/no-unnecessary-boolean-literal-compare': off '@typescript-eslint/no-use-before-define': error '@typescript-eslint/quotes': [error, single, {avoidEscape: true}] + '@typescript-eslint/sort-type-constituents': [error, {checkIntersections: false}] settings: import/extensions: [.ts] import/resolver: {node: {extensions: [.ts]}} diff --git a/autotests/configurator/mapBackendResponseToLog.ts b/autotests/configurator/mapBackendResponseToLog.ts index e61baa5c..615d89ca 100644 --- a/autotests/configurator/mapBackendResponseToLog.ts +++ b/autotests/configurator/mapBackendResponseToLog.ts @@ -9,8 +9,8 @@ import type {MapBackendResponseToLog} from 'autotests/types/packSpecific'; */ export const mapBackendResponseToLog: MapBackendResponseToLog = ({ duration, - statusCode, request, + statusCode, }) => { if (statusCode >= 400) { return undefined; diff --git a/src/README.md b/src/README.md index 2d9748eb..1117717c 100644 --- a/src/README.md +++ b/src/README.md @@ -19,23 +19,22 @@ Modules in the dependency graph should only import the modules above them: 12. `utils/valueToString` 13. `utils/error` 14. `utils/asserts` -15. `utils/userlandHooks` +15. `utils/userland` 16. `utils/fn` 17. `utils/environment` 18. `utils/getFullPackConfig` 19. `utils/runLabel` 20. `utils/generalLog` -21. `utils/runArrayOfFunctionsSafely` -22. `utils/fs` -23. `selectors` -24. `Route` -25. `ApiRoute` -26. `PageRoute` -27. `testController` -28. `useContext` -29. `context` -30. `utils/log` -31. `utils/waitForEvents` -32. `utils/expect` -33. `expect` -34. ... +21. `utils/fs` +22. `selectors` +23. `Route` +24. `ApiRoute` +25. `PageRoute` +26. `testController` +27. `useContext` +28. `context` +29. `utils/log` +30. `utils/waitForEvents` +31. `utils/expect` +32. `expect` +33. ... diff --git a/src/actions/pages/navigateToPage.ts b/src/actions/pages/navigateToPage.ts index f5af33b5..8085e003 100644 --- a/src/actions/pages/navigateToPage.ts +++ b/src/actions/pages/navigateToPage.ts @@ -1,7 +1,7 @@ import {LogEventType} from '../../constants/internal'; import {getDurationWithUnits} from '../../utils/getDurationWithUnits'; import {log} from '../../utils/log'; -import {getUserlandHooks} from '../../utils/userlandHooks'; +import {getUserlandHooks} from '../../utils/userland'; import {createPageInstance} from './createPageInstance'; diff --git a/src/actions/setFilesToUpload.ts b/src/actions/setFilesToUpload.ts index c76633e4..485c15ab 100644 --- a/src/actions/setFilesToUpload.ts +++ b/src/actions/setFilesToUpload.ts @@ -10,7 +10,7 @@ import type {Selector, TestCafeSelector} from '../types/internal'; */ export const setFilesToUpload = ( selector: Selector, - filePath: string | string[], + filePath: string[] | string, ): Promise => { const hasManyFiles = Array.isArray(filePath) && filePath.length > 0; const description = getDescriptionFromSelector(selector); diff --git a/src/actions/setNativeDialogHandler.ts b/src/actions/setNativeDialogHandler.ts index 3556a513..a8790b91 100644 --- a/src/actions/setNativeDialogHandler.ts +++ b/src/actions/setNativeDialogHandler.ts @@ -2,7 +2,7 @@ import {LogEventType} from '../constants/internal'; import {testController} from '../testController'; import {log} from '../utils/log'; -type NativeDialogType = 'alert' | 'confirm' | 'beforeunload' | 'prompt'; +type NativeDialogType = 'alert' | 'beforeunload' | 'confirm' | 'prompt'; /** * Specifies handler function for the browser native dialogs diff --git a/src/configurator/getReplacedObject.ts b/src/configurator/getReplacedObject.ts index 2503aa55..2c0d0ee6 100644 --- a/src/configurator/getReplacedObject.ts +++ b/src/configurator/getReplacedObject.ts @@ -21,7 +21,8 @@ export const getReplacedObject = ( const replacedObject = (Array.isArray(value) ? [] : {}) as Value; - const keys: PropertyKey[] = Object.keys(value); + const keys: PropertyKey[] = + value instanceof Error ? Object.getOwnPropertyNames(value) : Object.keys(value); keys.push(...Object.getOwnPropertySymbols(value)); diff --git a/src/constants/report.ts b/src/constants/report.ts index b437956d..df622e3f 100644 --- a/src/constants/report.ts +++ b/src/constants/report.ts @@ -18,4 +18,6 @@ export const enum ExitCode { HasErrors = 2, NoRetries = 3, NoReportData = 4, + HasErrorsInDoAfterPackFunctions = 5, + HasErrorsInDoBeforePackFunctions = 6, } diff --git a/src/createTestFunction.ts b/src/createTestFunction.ts index d029f890..aeee711f 100644 --- a/src/createTestFunction.ts +++ b/src/createTestFunction.ts @@ -1,4 +1,4 @@ -import {setUserlandHooks} from './utils/userlandHooks'; +import {setUserlandHooks} from './utils/userland'; import {test} from './test'; import type {AnyPack, GetPackParameters, TestFunction, UserlandHooks} from './types/internal'; diff --git a/src/types/clientFunction.ts b/src/types/clientFunction.ts index b6ef118e..e744714d 100644 --- a/src/types/clientFunction.ts +++ b/src/types/clientFunction.ts @@ -32,14 +32,14 @@ export type ClientFunctionState = { * @internal */ export type ClientFunctionWrapperResult = Readonly< - | { - errorMessage: undefined; - result: Result; - } | { errorMessage: string; result: undefined; } + | { + errorMessage: undefined; + result: Result; + } >; /** diff --git a/src/types/http/http.ts b/src/types/http/http.ts index dde1e740..70ddbce3 100644 --- a/src/types/http/http.ts +++ b/src/types/http/http.ts @@ -51,12 +51,12 @@ export type Query = | Readonly< Record< string, - | string - | number | boolean - | readonly string[] - | readonly number[] + | number + | string | readonly boolean[] + | readonly number[] + | readonly string[] | null | undefined > diff --git a/src/types/mockApiRoute.ts b/src/types/mockApiRoute.ts index 7c696a02..ea1d8005 100644 --- a/src/types/mockApiRoute.ts +++ b/src/types/mockApiRoute.ts @@ -24,7 +24,7 @@ export type ApiMockFunction< > = ( routeParams: RouteParams, request: SomeRequest, -) => Promise> | Partial; +) => Partial | Promise>; /** * Internal state of mockApiRoute/unmockApiRoute. diff --git a/src/types/promise.ts b/src/types/promise.ts index 743bbf49..a6be6d1f 100644 --- a/src/types/promise.ts +++ b/src/types/promise.ts @@ -8,7 +8,7 @@ export type AsyncVoid = MaybePromise; /** * A value of a type `Type` that may be wrapped in a promise. */ -export type MaybePromise = Type | Promise; +export type MaybePromise = Promise | Type; /** * Reexecutable promise from TestCafe. diff --git a/src/types/properties.ts b/src/types/properties.ts index 48ae6fda..feae6a9d 100644 --- a/src/types/properties.ts +++ b/src/types/properties.ts @@ -9,7 +9,7 @@ type DataPropertyDescriptor = Readonly< GenericPropertyDescriptor; type AccessorPropertyDescriptor = Readonly< - {get(): Value; set?(value: Value): void} | {get?(): Value; set(value: Value): void} + {get?(): Value; set(value: Value): void} | {get(): Value; set?(value: Value): void} > & GenericPropertyDescriptor; @@ -31,15 +31,15 @@ export type FieldReplacer = ( /** * Primitive value. */ -export type PrimitiveValue = bigint | boolean | null | number | string | symbol | undefined; +export type PrimitiveValue = bigint | boolean | number | string | symbol | null | undefined; /** * Property descriptor. */ export type PropertyDescriptor = - | GenericPropertyDescriptor + | AccessorPropertyDescriptor | DataPropertyDescriptor - | AccessorPropertyDescriptor; + | GenericPropertyDescriptor; /** * Property key. diff --git a/src/types/testRun.ts b/src/types/testRun.ts index c68185d9..c13ad6cd 100644 --- a/src/types/testRun.ts +++ b/src/types/testRun.ts @@ -17,7 +17,7 @@ export type RejectTestRun = (error: E2edError) => void; /** * Test run error (in string presentation), if any. */ -export type RunError = string | StringForLogs | undefined; +export type RunError = StringForLogs | string | undefined; /** * Hash string of each test run, generated by userland hook. diff --git a/src/utils/events/registerEndE2edRunEvent.ts b/src/utils/events/registerEndE2edRunEvent.ts index d8dcc61c..2072d9b8 100644 --- a/src/utils/events/registerEndE2edRunEvent.ts +++ b/src/utils/events/registerEndE2edRunEvent.ts @@ -3,6 +3,7 @@ import {ExitCode} from '../../constants/internal'; import {processExit} from '../exit'; import {failMessage, generalLog, okMessage} from '../generalLog'; import {collectReportData, getLiteReport, writeHtmlReport, writeLiteJsonReport} from '../report'; +import {setReadonlyProperty} from '../setReadonlyProperty'; import {collectFullEventsData} from './collectFullEventsData'; import {runAfterPackFunctions} from './runAfterPackFunctions'; @@ -29,7 +30,13 @@ export const registerEndE2edRunEvent = async (): Promise => { const liteReport = getLiteReport(reportData); - await runAfterPackFunctions(liteReport); + try { + await runAfterPackFunctions(liteReport); + } catch (error) { + generalLog('Caught an error on run "after pack" functions', {error}); + + setReadonlyProperty(reportData, 'exitCode', ExitCode.HasErrorsInDoAfterPackFunctions); + } const {customReportProperties} = liteReport; diff --git a/src/utils/events/registerEndTestRunEvent.ts b/src/utils/events/registerEndTestRunEvent.ts index 4b3301d4..56fe7d63 100644 --- a/src/utils/events/registerEndTestRunEvent.ts +++ b/src/utils/events/registerEndTestRunEvent.ts @@ -4,7 +4,7 @@ import {cloneWithoutLogEvents} from '../clone'; import {getRunErrorFromError} from '../error'; import {writeTestRunToJsonFile} from '../fs'; import {generalLog, logEndTestRunEvent, writeLogsToFile} from '../generalLog'; -import {getUserlandHooks} from '../userlandHooks'; +import {getUserlandHooks} from '../userland'; import {calculateTestRunStatus} from './calculateTestRunStatus'; import {getTestRunEvent} from './getTestRunEvent'; diff --git a/src/utils/events/registerStartE2edRunEvent.ts b/src/utils/events/registerStartE2edRunEvent.ts index 9fcfb3a9..506d0a0f 100644 --- a/src/utils/events/registerStartE2edRunEvent.ts +++ b/src/utils/events/registerStartE2edRunEvent.ts @@ -1,6 +1,8 @@ import {RunEnvironment} from '../../configurator'; -import {EVENTS_DIRECTORY_PATH, TMP_DIRECTORY_PATH} from '../../constants/internal'; +import {EVENTS_DIRECTORY_PATH, ExitCode, TMP_DIRECTORY_PATH} from '../../constants/internal'; +import {E2edError} from '../error'; +import {setGlobalExitCode} from '../exit'; import {createDirectory, removeDirectory, writeStartInfo} from '../fs'; import {generalLog, writeLogsToFile} from '../generalLog'; import {getFullPackConfig, updateConfig} from '../getFullPackConfig'; @@ -21,7 +23,13 @@ export const registerStartE2edRunEvent = async (): Promise => { const startInfo = getStartInfo(); - await runBeforePackFunctions(startInfo); + try { + await runBeforePackFunctions(startInfo); + } catch (cause) { + setGlobalExitCode(ExitCode.HasErrorsInDoBeforePackFunctions); + + throw new E2edError('Caught an error on running "before pack" functions', {cause}); + } const fullPackConfig = getFullPackConfig(); diff --git a/src/utils/events/runAfterPackFunctions.ts b/src/utils/events/runAfterPackFunctions.ts index bb07d48f..1177865e 100644 --- a/src/utils/events/runAfterPackFunctions.ts +++ b/src/utils/events/runAfterPackFunctions.ts @@ -1,5 +1,6 @@ +import {generalLog} from '../generalLog'; import {getFullPackConfig} from '../getFullPackConfig'; -import {runArrayOfFunctionsSafely} from '../runArrayOfFunctionsSafely'; +import {runArrayOfUserlandFunctions} from '../userland'; import type {CustomReportPropertiesPlaceholder, LiteReport, Void} from '../../types/internal'; @@ -19,5 +20,12 @@ export const runAfterPackFunctions = async (liteReport: LiteReport): Promise>(liteReport, {customReportProperties: result}); }; - await runArrayOfFunctionsSafely(functions, () => args, processCurrentFunctionResult); + const message = + functions.length > 0 + ? `Will be run ${functions.length} after pack function${functions.length > 1 ? 's' : ''}` + : 'There are no after pack functions'; + + generalLog(message); + + await runArrayOfUserlandFunctions(functions, () => args, processCurrentFunctionResult); }; diff --git a/src/utils/events/runBeforePackFunctions.ts b/src/utils/events/runBeforePackFunctions.ts index 913f4c18..bf2061ef 100644 --- a/src/utils/events/runBeforePackFunctions.ts +++ b/src/utils/events/runBeforePackFunctions.ts @@ -1,4 +1,5 @@ -import {runArrayOfFunctionsSafely} from '../runArrayOfFunctionsSafely'; +import {generalLog} from '../generalLog'; +import {runArrayOfUserlandFunctions} from '../userland'; import type { FullPackConfig, @@ -33,7 +34,14 @@ export const runBeforePackFunctions = async (startInfo: StartInfo): Promise args, processCurrentFunctionResult); + const message = + functions.length > 0 + ? `Will be run ${functions.length} before pack function${functions.length > 1 ? 's' : ''}` + : 'There are no before pack functions'; + + generalLog(message); + + await runArrayOfUserlandFunctions(functions, () => args, processCurrentFunctionResult); Object.assign>( startInfo.fullPackConfig, diff --git a/src/utils/exit/getExitCode.ts b/src/utils/exit/getExitCode.ts index 18ff842a..a31b527d 100644 --- a/src/utils/exit/getExitCode.ts +++ b/src/utils/exit/getExitCode.ts @@ -5,7 +5,7 @@ import {assertValueIsDefined} from '../asserts'; import type {Retry} from '../../types/internal'; /** - * Get e2ed exit code by hasErrors flag and array of retries. + * Get e2ed exit code by `hasErrors` flag and array of retries. * @internal */ export const getExitCode = (hasErrors: boolean, retries: readonly Retry[]): ExitCode => { diff --git a/src/utils/exit/globalExitCode.ts b/src/utils/exit/globalExitCode.ts new file mode 100644 index 00000000..df64c626 --- /dev/null +++ b/src/utils/exit/globalExitCode.ts @@ -0,0 +1,23 @@ +import {assertValueIsDefined, assertValueIsUndefined} from '../asserts'; + +import type {ExitCode} from '../../constants/internal'; + +let globalExitCode: ExitCode | undefined; + +/** + * Get global exit code. + * @internal + */ +export const getGlobalExitCode = (): ExitCode | undefined => globalExitCode; + +/** + * Set global exit code (once). + * @internal + */ +export const setGlobalExitCode = (exitCode: ExitCode): void => { + assertValueIsUndefined(globalExitCode, 'globalExitCode is not defined', {exitCode}); + + assertValueIsDefined(exitCode, 'exitCode is defined', {globalExitCode}); + + globalExitCode = exitCode; +}; diff --git a/src/utils/exit/index.ts b/src/utils/exit/index.ts index 048398fd..ed183b18 100644 --- a/src/utils/exit/index.ts +++ b/src/utils/exit/index.ts @@ -1,4 +1,6 @@ /** @internal */ export {getExitCode} from './getExitCode'; /** @internal */ +export {setGlobalExitCode} from './globalExitCode'; +/** @internal */ export {processExit} from './processExit'; diff --git a/src/utils/exit/processExit.ts b/src/utils/exit/processExit.ts index 15365a61..5f02c28a 100644 --- a/src/utils/exit/processExit.ts +++ b/src/utils/exit/processExit.ts @@ -2,11 +2,19 @@ import {ExitCode} from '../../constants/internal'; import {generalLog, writeLogsToFile} from '../generalLog'; +import {getGlobalExitCode} from './globalExitCode'; + /** * Exit from e2ed process with correct exit code. * @internal */ -export const processExit = async (exitCode: ExitCode = ExitCode.NoReportData): Promise => { +export const processExit = async ( + exitCodeFromReport: ExitCode = ExitCode.NoReportData, +): Promise => { + const globalExitCode = getGlobalExitCode(); + + const exitCode = globalExitCode === undefined ? exitCodeFromReport : globalExitCode; + generalLog(`Exit from e2ed with code ${exitCode}`); await writeLogsToFile(); diff --git a/src/utils/fn/getFunctionPresentationForLogs.ts b/src/utils/fn/getFunctionPresentationForLogs.ts index 00446b80..e16a6950 100644 --- a/src/utils/fn/getFunctionPresentationForLogs.ts +++ b/src/utils/fn/getFunctionPresentationForLogs.ts @@ -7,7 +7,7 @@ import type {Fn, StringForLogs} from '../../types/internal'; /** * Get custom function string presentation for logs. */ -export const getFunctionPresentationForLogs = (fn: Fn): string | StringForLogs => { +export const getFunctionPresentationForLogs = (fn: Fn): StringForLogs | string => { const {length, name} = fn; const code = getFunctionCode(fn); const withName = name ? `: ${name}` : ''; diff --git a/src/utils/fn/setCustomInspectOnFunction.ts b/src/utils/fn/setCustomInspectOnFunction.ts index 6c83fbdc..7840c9f5 100644 --- a/src/utils/fn/setCustomInspectOnFunction.ts +++ b/src/utils/fn/setCustomInspectOnFunction.ts @@ -8,7 +8,7 @@ import {getFunctionPresentationForLogs} from './getFunctionPresentationForLogs'; import type {Fn, StringForLogs} from '../../types/internal'; -function getFunctionPresentationForThis(this: Fn): string | StringForLogs { +function getFunctionPresentationForThis(this: Fn): StringForLogs | string { return getFunctionPresentationForLogs(this); } @@ -29,5 +29,5 @@ export const setCustomInspectOnFunction = = readonly Type[] | Readonly<{firstElements: readonly Type[]; length: number}>; +type Return = Readonly<{firstElements: readonly Type[]; length: number}> | readonly Type[]; /** * Truncate a long array for a short printout. diff --git a/src/utils/log/logWithPreparedOptions.ts b/src/utils/log/logWithPreparedOptions.ts index 449ae2fb..a8e20359 100644 --- a/src/utils/log/logWithPreparedOptions.ts +++ b/src/utils/log/logWithPreparedOptions.ts @@ -1,7 +1,7 @@ import {getRunId} from '../../context/runId'; import {generalLog} from '../generalLog'; -import {getUserlandHooks} from '../userlandHooks'; +import {getUserlandHooks} from '../userland'; import type {LogEventType} from '../../constants/internal'; import type {LogPayload, RunId, UtcTimeInMs} from '../../types/internal'; diff --git a/src/utils/requestHooks/applyHeadersMapper.ts b/src/utils/requestHooks/applyHeadersMapper.ts index a37b69ec..ffe0ac21 100644 --- a/src/utils/requestHooks/applyHeadersMapper.ts +++ b/src/utils/requestHooks/applyHeadersMapper.ts @@ -21,7 +21,7 @@ export const applyHeadersMapper = (headers: Headers, mapper?: MapHeaders): void // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete mutableHeaders[key]; } else { - mutableHeaders[key] = value as string | string[] | undefined; + mutableHeaders[key] = value as string[] | string | undefined; } } }; diff --git a/src/utils/test/beforeTest.ts b/src/utils/test/beforeTest.ts index 75ac71fb..6b411f7e 100644 --- a/src/utils/test/beforeTest.ts +++ b/src/utils/test/beforeTest.ts @@ -7,7 +7,7 @@ import {setTestTimeout} from '../../context/testTimeout'; import {getRunLabel} from '../environment'; import {registerStartTestRunEvent} from '../events'; import {getFullPackConfig} from '../getFullPackConfig'; -import {getUserlandHooks} from '../userlandHooks'; +import {getUserlandHooks} from '../userland'; import {getTestFnAndReject} from './getTestFnAndReject'; import {processBrokenTestRuns} from './processBrokenTestRuns'; diff --git a/src/utils/userland/index.ts b/src/utils/userland/index.ts new file mode 100644 index 00000000..0261288f --- /dev/null +++ b/src/utils/userland/index.ts @@ -0,0 +1,4 @@ +/** @internal */ +export {getUserlandHooks, setUserlandHooks} from './userlandHooks'; +/** @internal */ +export {runArrayOfUserlandFunctions} from './runArrayOfUserlandFunctions'; diff --git a/src/utils/runArrayOfFunctionsSafely.ts b/src/utils/userland/runArrayOfUserlandFunctions.ts similarity index 56% rename from src/utils/runArrayOfFunctionsSafely.ts rename to src/utils/userland/runArrayOfUserlandFunctions.ts index e5d321da..4ac27083 100644 --- a/src/utils/runArrayOfFunctionsSafely.ts +++ b/src/utils/userland/runArrayOfUserlandFunctions.ts @@ -1,12 +1,12 @@ -import {generalLog} from './generalLog'; +import {E2edError} from '../error'; -import type {Fn} from '../types/internal'; +import type {Fn} from '../../types/internal'; /** - * Safely run array of userland function (from `doBeforePack`/`doAfterPack`). + * Run array of userland function (from `doBeforePack`/`doAfterPack`). * @internal */ -export const runArrayOfFunctionsSafely = async ( +export const runArrayOfUserlandFunctions = async ( functions: readonly Fn[], getCurrentFunctionArgs: () => Args, processCurrentFunctionResult: (result: Awaited) => void, @@ -21,8 +21,8 @@ export const runArrayOfFunctionsSafely = async { const copy = {} as Mutable; for (const key of Reflect.ownKeys(value)) { - const property = (value as Record)[key]; + const property = (value as Record)[key]; if (property && (typeof property === 'object' || typeof property === 'function')) { try { diff --git a/src/utils/valueToString/wrapStringForLogs.ts b/src/utils/valueToString/wrapStringForLogs.ts index a9b6d5b7..c28c47d9 100644 --- a/src/utils/valueToString/wrapStringForLogs.ts +++ b/src/utils/valueToString/wrapStringForLogs.ts @@ -20,7 +20,7 @@ function toMultipleString(this: StringForLogs): string { * with a more beautiful presentation through nodejs `inspect`. * @internal */ -export const wrapStringForLogs = (text: string, options?: Options): string | StringForLogs => { +export const wrapStringForLogs = (text: string, options?: Options): StringForLogs | string => { if (!text.includes('\n')) { return getStringTrimmedToMaxLength(text); }