diff --git a/README.md b/README.md index ce904781..d838a620 100644 --- a/README.md +++ b/README.md @@ -323,8 +323,6 @@ If `null`, the report will not be saved. `skipTests: SkipTests`: this setting allows you to describe a set of skipped tests in a custom form. You can define the `SkipTests` type and `skipTests` processing rules in the hook `autotests/hooks/isTestSkipped.ts`. -`stabilizationInterval: number`: default stabilization interval for `waitForInterfaceStabilization` action. - `takeFullPageScreenshotOnError: boolean`: if `true`, then takes a screenshot of the full page (not just the viewport) at the time of the test error, for display in the HTML report. @@ -352,6 +350,12 @@ returned by the `waitForAllRequestsComplete` function will be successfully resol If the wait is longer than this timeout, then the promise returned by the `waitForAllRequestsComplete` function will be rejected. +`waitForInterfaceStabilization.stabilizationInterval: number`: default stabilization interval for `waitForInterfaceStabilization` function. + +`waitForInterfaceStabilization.timeout: number`: default timeout (in milliseconds) for `waitForInterfaceStabilization` function. +If the wait is longer than this timeout, then the promise +returned by the `waitForInterfaceStabilization` 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. diff --git a/autotests/configurator/skipTests.ts b/autotests/configurator/skipTests.ts index b989ff6c..64ed3ecb 100644 --- a/autotests/configurator/skipTests.ts +++ b/autotests/configurator/skipTests.ts @@ -5,7 +5,7 @@ import type {SkipTests} from 'autotests/types/skipTests'; */ export const skipTests: SkipTests = [ { - reason: 'Skip for testing skipping mechanism (with link [Google](https://google.com))', + reason: 'Skip for testing skipping mechanism (with link to [Google](https://google.com))', testIds: ['4'], unskipTaskUrl: 'https://tasktracker.com/3', }, diff --git a/autotests/packs/allTests.ts b/autotests/packs/allTests.ts index 9ddc3dc1..d8b19ff1 100644 --- a/autotests/packs/allTests.ts +++ b/autotests/packs/allTests.ts @@ -48,7 +48,6 @@ export const pack: Pack = { reportFileName: 'report.html', selectorTimeout: 10_000, skipTests, - stabilizationInterval: 500, takeFullPageScreenshotOnError: false, takeViewportScreenshotOnError: true, testFileGlobs: ['./autotests/tests/**/*.ts', '!**/*.skip.ts'], @@ -58,6 +57,10 @@ export const pack: Pack = { maxIntervalBetweenRequestsInMs: 500, timeout: 30_000, }, + waitForInterfaceStabilization: { + stabilizationInterval: 500, + timeout: 30_000, + }, waitForRequestTimeout: 30_000, waitForResponseTimeout: 30_000, }; diff --git a/autotests/packs/local.example.ts b/autotests/packs/local.example.ts index ddf28138..0a686743 100644 --- a/autotests/packs/local.example.ts +++ b/autotests/packs/local.example.ts @@ -8,4 +8,5 @@ import type {Pack} from 'autotests/types/pack'; export const pack: Pack = { ...allTestsPack, browserInitTimeout: 40_000, + dockerImage: 'e2edhub/e2ed', }; diff --git a/autotests/pageObjects/pages/Main.ts b/autotests/pageObjects/pages/Main.ts index 0af100e4..af715dce 100644 --- a/autotests/pageObjects/pages/Main.ts +++ b/autotests/pageObjects/pages/Main.ts @@ -52,7 +52,7 @@ export class Main extends Page { /** * Header selector. */ - readonly headerSelector = mainPageLocator.header(); + readonly header = mainPageLocator.header(); /** * Search input. diff --git a/autotests/tests/e2edReportExample/browserData.ts b/autotests/tests/e2edReportExample/browserData.ts new file mode 100644 index 00000000..bd9257a0 --- /dev/null +++ b/autotests/tests/e2edReportExample/browserData.ts @@ -0,0 +1,57 @@ +/* eslint-disable no-console */ + +import {it} from 'autotests'; +import {E2edReportExample} from 'autotests/pageObjects/pages'; +import {createClientFunction, expect} from 'e2ed'; +import { + getBrowserConsoleMessages, + getBrowserJsErrors, + navigateToPage, + setPageElementsIgnoredOnInterfaceStabilization, + waitForInterfaceStabilization, +} from 'e2ed/actions'; + +it('correctly read data from browser', {meta: {testId: '14'}}, async () => { + await navigateToPage(E2edReportExample); + + await setPageElementsIgnoredOnInterfaceStabilization(['.retry']); + + await waitForInterfaceStabilization(100); + + await createClientFunction(() => { + console.error('error'); + console.info('info'); + console.log('log'); + console.warn('warn'); + + const key = Symbol.for('e2ed:PageElementsIgnoredOnInterfaceStabilization'); + const global = globalThis as {[key]?: readonly string[] | undefined}; + + console.log(global[key]); + + setTimeout(() => { + throw new Error('foo'); + }, 8); + setTimeout(() => { + throw new Error('bar'); + }, 32); + })(); + + const {error, info, log, warn} = await getBrowserConsoleMessages(); + + await expect( + error[0] === 'error' && info[0] === 'info' && log[0] === 'log' && warn[0] === 'warn', + '`getBrowserConsoleMessages` read all of messages', + ).eql(true); + + await expect(log[1], '`setPageElementsIgnoredOnInterfaceStabilization` works correct').eql( + '.retry', + ); + + const jsErrors = await getBrowserJsErrors(); + + await expect( + jsErrors.length === 2 && jsErrors[0]?.message === 'foo' && jsErrors[1]?.message === 'bar', + '`getBrowserJsErrors` read JS errors', + ).eql(true); +}); diff --git a/autotests/tests/main/exists.ts b/autotests/tests/main/exists.ts index 234d8bf6..ef9b8c2c 100644 --- a/autotests/tests/main/exists.ts +++ b/autotests/tests/main/exists.ts @@ -38,7 +38,9 @@ it('exists', {meta: {testId: '1'}, testIdleTimeout: 35_000, testTimeout: 90_000} const mainPage = await navigateToPage(Main, {language}); - await expect(mainPage.pageParams, 'pageParams is correct after navigateToPage').eql({language}); + await expect(mainPage.pageParams, '`pageParams` is correct after `navigateToPage`').eql({ + language, + }); await expect(mainPage.searchString, 'search string is empty').eql(''); @@ -67,7 +69,9 @@ it('exists', {meta: {testId: '1'}, testIdleTimeout: 35_000, testTimeout: 90_000} * Do not use the following pageParams and url (by getParamsFromUrl) checks in your code. * These are e2ed internal checks. Use `assertPage` instead. */ - await expect(searchPage.pageParams, 'pageParams is correct after assertPage').eql({searchQuery}); + await expect(searchPage.pageParams, '`pageParams` is correct after `assertPage`').eql({ + searchQuery, + }); const url = await getDocumentUrl(); diff --git a/src/actions/getBrowserConsoleMessages.ts b/src/actions/getBrowserConsoleMessages.ts index b19bcad4..fdfca28f 100644 --- a/src/actions/getBrowserConsoleMessages.ts +++ b/src/actions/getBrowserConsoleMessages.ts @@ -2,11 +2,9 @@ import {LogEventType} from '../constants/internal'; import {testController} from '../testController'; import {log} from '../utils/log'; -type ConsoleMessages = ReturnType extends Promise< - infer Type -> - ? Type - : never; +import type {UnwrapPromise} from '../types/internal'; + +type ConsoleMessages = UnwrapPromise>; /** * Returns an object that contains messages output to the browser console. diff --git a/src/actions/getBrowserJsErrors.ts b/src/actions/getBrowserJsErrors.ts new file mode 100644 index 00000000..edb95884 --- /dev/null +++ b/src/actions/getBrowserJsErrors.ts @@ -0,0 +1,29 @@ +import {LogEventType} from '../constants/internal'; +import {createClientFunction} from '../createClientFunction'; +import {log} from '../utils/log'; + +import type {BrowserJsError} from '../types/internal'; + +const clientGetBrowserJsErrors = createClientFunction( + (): readonly BrowserJsError[] => { + const key = Symbol.for('e2ed:JsErrors'); + const global = window as {[key]?: BrowserJsError[]}; + // eslint-disable-next-line no-multi-assign + const errors = (global[key] ??= []); + const copyOfErrors = [...errors]; + + errors.length = 0; + + return copyOfErrors; + }, + {name: 'getBrowserJsErrors'}, +); + +/** + * Get browser JS errors. + */ +export const getBrowserJsErrors = (): Promise => { + log('Get browser JS errors', LogEventType.InternalAction); + + return clientGetBrowserJsErrors(); +}; diff --git a/src/actions/index.ts b/src/actions/index.ts index 5a61e922..0ca01205 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -15,6 +15,7 @@ export {doubleClick} from './doubleClick'; export {drag} from './drag'; export {dragToElement} from './dragToElement'; export {getBrowserConsoleMessages} from './getBrowserConsoleMessages'; +export {getBrowserJsErrors} from './getBrowserJsErrors'; export {getCookies} from './getCookies'; export {hover} from './hover'; export {mockApiRoute, unmockApiRoute} from './mock'; @@ -38,6 +39,7 @@ export {setCookies} from './setCookies'; export {setFilesToUpload} from './setFilesToUpload'; export {setHeadersAndNavigateToUrl} from './setHeadersAndNavigateToUrl'; export {setNativeDialogHandler} from './setNativeDialogHandler'; +export {setPageElementsIgnoredOnInterfaceStabilization} from './setPageElementsIgnoredOnInterfaceStabilization'; export {switchToIframe} from './switchToIframe'; export {takeElementScreenshot} from './takeElementScreenshot'; export {takeScreenshot} from './takeScreenshot'; diff --git a/src/actions/setPageElementsIgnoredOnInterfaceStabilization.ts b/src/actions/setPageElementsIgnoredOnInterfaceStabilization.ts new file mode 100644 index 00000000..7ae13aaf --- /dev/null +++ b/src/actions/setPageElementsIgnoredOnInterfaceStabilization.ts @@ -0,0 +1,29 @@ +import {LogEventType} from '../constants/internal'; +import {createClientFunction} from '../createClientFunction'; +import {log} from '../utils/log'; + +const clientSetPageElementsIgnoredOnInterfaceStabilization = createClientFunction( + (elementsCssSelectors: readonly string[]) => { + const key = Symbol.for('e2ed:PageElementsIgnoredOnInterfaceStabilization'); + const global = globalThis as {[key]?: readonly string[] | undefined}; + + global[key] = elementsCssSelectors; + }, + {name: 'setPageElementsIgnoredOnInterfaceStabilization'}, +); + +/** + * Set an array of elements (by their string CSS selectors) that will be ignored + * when determining the end of interface stabilization (these are usually animated elements). + */ +export const setPageElementsIgnoredOnInterfaceStabilization = ( + elementsCssSelectors: readonly string[], +): Promise => { + log( + 'Set page element that will be ignored when determining the end of interface stabilization', + {elementsCssSelectors}, + LogEventType.InternalAction, + ); + + return clientSetPageElementsIgnoredOnInterfaceStabilization(elementsCssSelectors); +}; diff --git a/src/actions/waitFor/waitForInterfaceStabilization.ts b/src/actions/waitFor/waitForInterfaceStabilization.ts index e886e25d..c51ff6bc 100644 --- a/src/actions/waitFor/waitForInterfaceStabilization.ts +++ b/src/actions/waitFor/waitForInterfaceStabilization.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-labels */ + import {LogEventType} from '../../constants/internal'; import {createClientFunction} from '../../createClientFunction'; import {getDurationWithUnits} from '../../utils/getDurationWithUnits'; @@ -10,64 +12,64 @@ import type {UtcTimeInMs} from '../../types/internal'; * This function in a universal way waits for the end of the movements and redrawing * of the page interface. The function takes on the duration of an interval during * which the interface must not change to be considered stable. - * Then, every 250 ms, a snapshot of the state of the interface is taken. + * Then, every 250ms, a snapshot of the state of the interface is taken. * If the state does not change within the specified period, the function successfully * resolves the returned promise. * The state of the interface is the size of the window, scrolling in the entire window, * as well as the classes, sizes and positions of some DOM elements on the page. - * Elements from different points on the page are taken as checked elements, - * as well as a number of elements with the data-testid attribute used in tests. + * Elements from different points on the page are taken as checked elements. */ const clientWaitForInterfaceStabilization = createClientFunction( - (stabilizationInterval: number) => { - /** - * Asserts that the value is defined (is not undefined). - */ - function assertValueIsDefined(value: T): asserts value is Exclude { - if (value === undefined) { - throw new TypeError('Asserted value is undefined'); - } - } + (stabilizationInterval: number, timeout: number) => { + const isPointInsideRectangle = ( + x: number, + y: number, + {left, top, width, height}: DOMRect, + ): boolean => x >= left && x <= left + width && y >= top && y <= top + height; + + const keyOfIgnoredElements = Symbol.for('e2ed:PageElementsIgnoredOnInterfaceStabilization'); + const global = globalThis as {[keyOfIgnoredElements]?: readonly string[] | undefined}; + const ignoredElementsSelectors = global[keyOfIgnoredElements] || []; const CHECK_INTERVAL_IN_MS = 250; const COUNT_OF_POINTED_NODES = 8; - const COUNT_OF_TEST_ID_NODES = 50; const startTimeInMs = Date.now() as UtcTimeInMs; - const TOTAL_TIMEOUT_IN_STABILIZATION_INTERVAL = 30; const getInterfaceState = (): string => { - const {innerWidth, innerHeight} = window; - const elements: Element[] = [document.documentElement]; - const elementsWithDataTestId = document.querySelectorAll('[data-test-id]'); - const elementsWithDataTestIdInLowerCase = document.querySelectorAll('[data-testid]'); - const deltaX = innerWidth / (COUNT_OF_POINTED_NODES + 1); - const deltaY = innerHeight / (COUNT_OF_POINTED_NODES + 1); - - for (let i = 0; i < elementsWithDataTestId.length && i < COUNT_OF_TEST_ID_NODES; i += 1) { - const elementWithDataTestId = elementsWithDataTestId[i]; - - assertValueIsDefined(elementWithDataTestId); + const ignoredElements: Element[] = []; - elements.push(elementWithDataTestId); + for (const selector of ignoredElementsSelectors) { + for (const element of document.querySelectorAll(selector)) { + if (!ignoredElements.includes(element)) { + ignoredElements.push(element); + } + } } - for ( - let i = 0; - i < elementsWithDataTestIdInLowerCase.length && i < COUNT_OF_TEST_ID_NODES; - i += 1 - ) { - const elementWithDataTestIdInLowerCase = elementsWithDataTestIdInLowerCase[i]; - - assertValueIsDefined(elementWithDataTestIdInLowerCase); + const ignoredRectangles: DOMRect[] = ignoredElements.map((element) => + element.getBoundingClientRect(), + ); - elements.push(elementWithDataTestIdInLowerCase); - } + const {innerWidth, innerHeight} = window; + const elements: Element[] = [document.documentElement]; + const deltaX = innerWidth / (COUNT_OF_POINTED_NODES + 1); + const deltaY = innerHeight / (COUNT_OF_POINTED_NODES + 1); for (let xIndex = 1; xIndex <= COUNT_OF_POINTED_NODES; xIndex += 1) { - for (let yIndex = 1; yIndex <= COUNT_OF_POINTED_NODES; yIndex += 1) { - const element = document.elementFromPoint(deltaX * xIndex, deltaY * yIndex); + Points: for (let yIndex = 1; yIndex <= COUNT_OF_POINTED_NODES; yIndex += 1) { + const x = deltaX * xIndex; + const y = deltaY * yIndex; + + for (const rectangle of ignoredRectangles) { + // eslint-disable-next-line max-depth + if (isPointInsideRectangle(x, y, rectangle)) { + continue Points; + } + } + + const element = document.elementFromPoint(x, y); - if (element) { + if (element && !elements.includes(element)) { elements.push(element); } } @@ -103,13 +105,9 @@ const clientWaitForInterfaceStabilization = createClientFunction( return; } - const totalTimeoutInMs = stabilizationInterval * TOTAL_TIMEOUT_IN_STABILIZATION_INTERVAL; - - if (Date.now() - startTimeInMs > totalTimeoutInMs) { - const timeoutWithUnits = getDurationWithUnits(totalTimeoutInMs); - + if (Date.now() - startTimeInMs > timeout) { clearInterval(intervalId); - resolve(`Time was out in waitForInterfaceStabilization (${timeoutWithUnits})`); + resolve(`Time was out in waitForInterfaceStabilization (${timeout}ms)`); } }, CHECK_INTERVAL_IN_MS); }); @@ -124,32 +122,41 @@ const clientWaitForInterfaceStabilization = createClientFunction( */ export const waitForInterfaceStabilization = async ( stabilizationInterval?: number, + timeout?: number, ): Promise => { - if (stabilizationInterval === undefined) { - const {stabilizationInterval: stabilizationIntervalFromConfig} = getFullPackConfig(); + if (stabilizationInterval === undefined || timeout === undefined) { + const {waitForInterfaceStabilization: config} = getFullPackConfig(); + const {stabilizationInterval: stabilizationIntervalFromConfig, timeout: timeoutFromConfig} = + config; // eslint-disable-next-line no-param-reassign stabilizationInterval = stabilizationIntervalFromConfig; + // eslint-disable-next-line no-param-reassign + timeout = timeoutFromConfig; } - if (!(stabilizationInterval > 0)) { + if (!(stabilizationInterval > 0) || !(timeout > 0)) { return; } const startTimeInMs = Date.now() as UtcTimeInMs; - const maybeErrorReason = await clientWaitForInterfaceStabilization(stabilizationInterval); + const maybeErrorReason = await clientWaitForInterfaceStabilization( + stabilizationInterval, + timeout, + ); const waitInMs = Date.now() - startTimeInMs; const startDateTimeInIso = new Date(startTimeInMs).toISOString(); const stabilizationIntervalWithUnits = getDurationWithUnits(stabilizationInterval); + const timeoutWithUnits = getDurationWithUnits(timeout); const waitWithUnits = getDurationWithUnits(waitInMs); log( `Have waited for interface stabilization for ${waitWithUnits} with stabilization interval ${stabilizationIntervalWithUnits}`, - {error: maybeErrorReason, startDateTimeInIso}, + {error: maybeErrorReason, startDateTimeInIso, timeout: timeoutWithUnits}, LogEventType.InternalAction, ); }; diff --git a/src/test.ts b/src/test.ts index 3c1423f6..c5e66242 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,4 +1,4 @@ -import {getRunTest} from './utils/test'; +import {getRunTest, safeJsError} from './utils/test'; import {fixture, test as testcafeTest} from './testcafe'; import type {TestFunction} from './types/internal'; @@ -8,7 +8,7 @@ import type {TestFunction} from './types/internal'; * @internal */ export const test: TestFunction = (name, options, testFn) => { - fixture(' - e2ed - '); + fixture(' - e2ed - ').skipJsErrors(safeJsError); const runTest = getRunTest({name, options, testFn}); diff --git a/src/types/config/ownE2edConfig.ts b/src/types/config/ownE2edConfig.ts index 95146753..c795f89b 100644 --- a/src/types/config/ownE2edConfig.ts +++ b/src/types/config/ownE2edConfig.ts @@ -130,11 +130,6 @@ export type OwnE2edConfig< */ skipTests: SkipTests; - /** - * Default stabilization interval for `waitForInterfaceStabilization` action. - */ - stabilizationInterval: number; - /** * If `true`, then takes a screenshot of the full page (not just the viewport) * at the time of the test error, for display in the HTML report. @@ -180,13 +175,30 @@ export type OwnE2edConfig< maxIntervalBetweenRequestsInMs: number; /** - * Default timeout (in milliseconds) for waitForAllRequestsComplete function. + * 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. */ timeout: number; }>; + /** + * Group of settings for the `waitForInterfaceStabilization` function. + */ + waitForInterfaceStabilization: Readonly<{ + /** + * Default stabilization interval for `waitForInterfaceStabilization` function. + */ + stabilizationInterval: number; + + /** + * Default timeout (in milliseconds) for `waitForInterfaceStabilization` function. + * If the wait is longer than this timeout, then the promise + * returned by the `waitForInterfaceStabilization` function will be rejected. + */ + timeout: number; + }>; + /** * Default timeout (in milliseconds) for `waitForRequest` function. * If the wait is longer than this timeout, then the promise diff --git a/src/types/errors.ts b/src/types/errors.ts index 2bb6bb0e..10e6fee7 100644 --- a/src/types/errors.ts +++ b/src/types/errors.ts @@ -1,3 +1,12 @@ +import type {Inner} from 'testcafe-without-typecheck'; + +/** + * Browser's JS-error from TestCafe. + */ +export type BrowserJsError = Readonly< + Exclude[0], undefined> +>; + /** * Maybe error params with optional field isTestRunBroken (or undefined). * The presence of such a field in a reject error results in diff --git a/src/types/index.ts b/src/types/index.ts index 2b46ecc5..f8788f00 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,6 +3,7 @@ export type {Expect, IsEqual, IsReadonlyKey} from './checks'; export type {Class} from './class'; export type {UtcTimeInMs} from './date'; export type {DeepMutable, DeepPartial, DeepReadonly, DeepRequired} from './deep'; +export type {BrowserJsError} from './errors'; export type {LogEvent, Onlog, TestRunEvent} from './events'; export type {Fn, MergeFunctions} from './fn'; export type { diff --git a/src/types/internal.ts b/src/types/internal.ts index 4988ee29..cf1b8d1c 100644 --- a/src/types/internal.ts +++ b/src/types/internal.ts @@ -22,6 +22,7 @@ export type {UtcTimeInMs} from './date'; export type {DeepMutable, DeepPartial, DeepReadonly, DeepRequired} from './deep'; /** @internal */ export type {E2edEnvironment} from './environment'; +export type {BrowserJsError} from './errors'; /** @internal */ export type {MaybeWithIsTestRunBroken} from './errors'; export type {LogEvent, Onlog, TestRunEvent} from './events'; diff --git a/src/types/promise.ts b/src/types/promise.ts index c8ce5cd0..8621dc38 100644 --- a/src/types/promise.ts +++ b/src/types/promise.ts @@ -1,18 +1,18 @@ /** - * Void or Promise as return value for maybe async functions. + * `void` or `Promise` as return value for maybe async functions. */ export type AsyncVoid = MaybePromise; /** - * A value of a type `T` that may be wrapped in a promise. + * A value of a type `Type` that may be wrapped in a promise. */ -export type MaybePromise = T | Promise; +export type MaybePromise = Type | Promise; /** * If the type is a promise, unwraps it and returns the promise value type * (until a non-promise value is obtained). - * UnwrapPromise = number. - * UnwrapPromise> = string. - * UnwrapPromise>> = bigint. + * `UnwrapPromise` = `number`. + * `UnwrapPromise>` = `string`. + * `UnwrapPromise>>` = `bigint`. */ -export type UnwrapPromise = T extends Promise ? UnwrapPromise : T; +export type UnwrapPromise = Type extends Promise ? UnwrapPromise : Type; diff --git a/src/types/tuples.ts b/src/types/tuples.ts index 3999f1dd..f9cfbe2e 100644 --- a/src/types/tuples.ts +++ b/src/types/tuples.ts @@ -1,6 +1,6 @@ /** * Merge (union) sequentially all elements of the tuple. - * MergeTuples<[string, number] | [boolean, bigint]> = [string | boolean, number | bigint]. + * `MergeTuples<[string, number] | [boolean, bigint]>` = `[string | boolean, number | bigint]`. */ export type MergeTuples = [Tuples] extends [[]] ? [] @@ -8,8 +8,10 @@ export type MergeTuples = [Tuples] extends [[]] /** * Returns rest of tuple (everything but the first element). - * Rest<[]> = never. - * Rest<[1]> = []. - * Rest<[1, 2, 3]> = [2, 3]. + * `TupleRest<[]>` = `never`. + * `TupleRest<[1]>` = `[]`. + * `TupleRest<[1, 2, 3]>` = `[2, 3]`. */ -export type TupleRest = T extends [unknown, ...infer R] ? R : never; +export type TupleRest = Type extends [unknown, ...infer Result] + ? Result + : never; diff --git a/src/utils/generalLog/removeStyleFromString.ts b/src/utils/generalLog/removeStyleFromString.ts index 496066f7..a7a7d433 100644 --- a/src/utils/generalLog/removeStyleFromString.ts +++ b/src/utils/generalLog/removeStyleFromString.ts @@ -1,5 +1,7 @@ +// eslint-disable-next-line no-control-regex +const stylesRegexp = /\x1B\[[\d;]+m/gi; + /** * Removes console (terminal) styles from a string (like `\x1B[3m...`). */ -// eslint-disable-next-line no-control-regex -export const removeStyleFromString = (text: string): string => text.replace(/\x1B\[[^m]+m/gi, ''); +export const removeStyleFromString = (text: string): string => text.replace(stylesRegexp, ''); diff --git a/src/utils/generalLog/successfulTestRunCount.ts b/src/utils/generalLog/successfulTestRunCount.ts index c437da45..67b99730 100644 --- a/src/utils/generalLog/successfulTestRunCount.ts +++ b/src/utils/generalLog/successfulTestRunCount.ts @@ -11,7 +11,7 @@ let successfulInCurrentRetry = 0; let successfulTotalInPreviousRetries = 0; /** - * Add one successful test run (in current retry). + * Adds one successful test run (in current retry). * @internal */ export const addSuccessfulInCurrentRetry = (): void => { diff --git a/src/utils/notIncludedInPackTests.ts b/src/utils/notIncludedInPackTests.ts index 434b7755..0c60c655 100644 --- a/src/utils/notIncludedInPackTests.ts +++ b/src/utils/notIncludedInPackTests.ts @@ -38,7 +38,7 @@ export const getNotIncludedInPackTests = async (): Promise { @@ -77,7 +77,7 @@ export const getTestFnAndReject = ({ }; /** - * Reject test run by some run error. + * Rejects test run by some run error. * @internal */ const reject: RejectTestRun = (error) => { @@ -92,7 +92,7 @@ export const getTestFnAndReject = ({ }; /** - * Reject test run by test idle timeout error (timeout between steps). + * Rejects test run by test idle timeout error (timeout between steps). * @internal */ function rejectByIdleTimeoutError(): void { diff --git a/src/utils/test/index.ts b/src/utils/test/index.ts index ef9593b4..370ce73d 100644 --- a/src/utils/test/index.ts +++ b/src/utils/test/index.ts @@ -1,2 +1,4 @@ /** @internal */ export {getRunTest} from './getRunTest'; +/** @internal */ +export {safeJsError} from './safeJsError'; diff --git a/src/utils/test/safeJsError.ts b/src/utils/test/safeJsError.ts new file mode 100644 index 00000000..4fdc7152 --- /dev/null +++ b/src/utils/test/safeJsError.ts @@ -0,0 +1,18 @@ +import type {BrowserJsError} from '../../types/internal'; + +/** + * Safes browser JS errors for futher logging. + * @internal + */ +export const safeJsError = (error?: BrowserJsError): true => { + const key = Symbol.for('e2ed:JsErrors'); + const global = globalThis as {[key]?: BrowserJsError[]}; + // eslint-disable-next-line no-multi-assign + const errors = (global[key] ??= []); + + if (error) { + errors.push(error); + } + + return true; +}; diff --git a/src/utils/waitForEvents/processEventsPredicate.ts b/src/utils/waitForEvents/processEventsPredicate.ts index bdd3ddf7..96250f27 100644 --- a/src/utils/waitForEvents/processEventsPredicate.ts +++ b/src/utils/waitForEvents/processEventsPredicate.ts @@ -22,6 +22,7 @@ export const processEventsPredicate = async ({ requestOrResponse, requestOrResponsePredicateWithPromise, }: Options): Promise => { + const eventTypeInLowerCase = eventType.toLowerCase(); const {predicate, reject, resolve, startTimeInMs} = requestOrResponsePredicateWithPromise; try { @@ -34,8 +35,8 @@ export const processEventsPredicate = async ({ const waitWithUnits = getDurationWithUnits(Date.now() - startTimeInMs); log( - `Have waited for the ${eventType} for ${waitWithUnits}`, - {predicate, [eventType.toLowerCase()]: requestOrResponse}, + `Have waited for ${eventTypeInLowerCase} for ${waitWithUnits}`, + {[eventTypeInLowerCase]: requestOrResponse, predicate}, LogEventType.InternalUtil, ); @@ -43,7 +44,7 @@ export const processEventsPredicate = async ({ } catch (cause) { const error = new E2edError( `waitFor${eventType} promise rejected due to error in predicate function`, - {cause, predicate, [eventType.toLowerCase()]: requestOrResponse}, + {cause, [eventTypeInLowerCase]: requestOrResponse, predicate}, ); reject(error);