diff --git a/src/actions/takeScreenshot.ts b/src/actions/takeScreenshot.ts index 3d5958e5..945663cf 100644 --- a/src/actions/takeScreenshot.ts +++ b/src/actions/takeScreenshot.ts @@ -1,21 +1,46 @@ import {LogEventType} from '../constants/internal'; import {testController} from '../testController'; +import {E2edError} from '../utils/error'; +import {getDurationWithUnits} from '../utils/getDurationWithUnits'; import {log} from '../utils/log'; +import {getPromiseWithResolveAndReject} from '../utils/promise'; import type {Inner} from 'testcafe-without-typecheck'; type TakeScreenshot = ((path?: string) => Promise) & - ((options: Inner.TakeScreenshotOptions) => Promise); + ((options: Inner.TakeScreenshotOptions & Readonly<{timeout?: number}>) => Promise); /** * Takes a screenshot of the tested page. */ export const takeScreenshot: TakeScreenshot = (pathOrOptions) => { - const options: Inner.TakeScreenshotOptions | undefined = - typeof pathOrOptions === 'string' ? {path: pathOrOptions} : pathOrOptions; - const {fullPage, path: pathToScreenshot} = options ?? {}; + const options = typeof pathOrOptions === 'string' ? {path: pathOrOptions} : pathOrOptions; + const {fullPage, path: pathToScreenshot, timeout = 10_0000} = options ?? {}; - log('Take a screenshot of the page', {fullPage, pathToScreenshot}, LogEventType.InternalAction); + const timeoutWithUnits = getDurationWithUnits(timeout); - return testController.takeScreenshot(pathOrOptions as never); + log( + 'Take a screenshot of the page', + {fullPage, pathToScreenshot, timeoutWithUnits}, + LogEventType.InternalAction, + ); + + const {promise, reject, setRejectTimeoutFunction} = getPromiseWithResolveAndReject(timeout); + + setRejectTimeoutFunction(() => { + const error = new E2edError( + `takeScreenshot promise rejected after ${timeoutWithUnits} timeout`, + {fullPage, pathToScreenshot}, + ); + + reject(error); + }); + + return Promise.race([ + promise, + testController.takeScreenshot({ + fullPage, + path: pathToScreenshot, + } as Inner.TakeScreenshotOptions), + ]) as Promise; }; diff --git a/src/actions/waitFor/waitForInterfaceStabilization.ts b/src/actions/waitFor/waitForInterfaceStabilization.ts index c51ff6bc..3f9947e5 100644 --- a/src/actions/waitFor/waitForInterfaceStabilization.ts +++ b/src/actions/waitFor/waitForInterfaceStabilization.ts @@ -130,9 +130,9 @@ export const waitForInterfaceStabilization = async ( config; // eslint-disable-next-line no-param-reassign - stabilizationInterval = stabilizationIntervalFromConfig; + stabilizationInterval ??= stabilizationIntervalFromConfig; // eslint-disable-next-line no-param-reassign - timeout = timeoutFromConfig; + timeout ??= timeoutFromConfig; } if (!(stabilizationInterval > 0) || !(timeout > 0)) { diff --git a/src/utils/report/client/addDomContentLoadedHandler.ts b/src/utils/report/client/addDomContentLoadedHandler.ts index 2ed08294..d04bd1c6 100644 --- a/src/utils/report/client/addDomContentLoadedHandler.ts +++ b/src/utils/report/client/addDomContentLoadedHandler.ts @@ -1,5 +1,5 @@ /** - * Adds DOMContentloaded handler for report page. + * Adds `DOMContentLoaded` handler for report page. * If the page is already loaded, then call the handler immediately. * This client function should not use scope variables (except global functions). * @internal diff --git a/src/utils/report/client/chooseTestRun.ts b/src/utils/report/client/chooseTestRun.ts index e904ec78..1640ba53 100644 --- a/src/utils/report/client/chooseTestRun.ts +++ b/src/utils/report/client/chooseTestRun.ts @@ -10,7 +10,7 @@ declare const e2edTestRunDetailsContainer: HTMLElement; declare const reportClientState: ReportClientState; /** - * Choose TestRun (render chosen TestRun in right panel). + * Chooses TestRun (render chosen TestRun in right panel). * This base client function should not use scope variables (except other base functions). * @internal */ diff --git a/src/utils/report/client/index.ts b/src/utils/report/client/index.ts index 7a903e53..57f14644 100644 --- a/src/utils/report/client/index.ts +++ b/src/utils/report/client/index.ts @@ -13,7 +13,9 @@ export {clickOnStep} from './clickOnStep'; /** @internal */ export {clickOnTestRun} from './clickOnTestRun'; /** @internal */ -export {domContentLoadedHandler} from './domContentLoadedHandler'; +export {onDomContentLoad} from './onDomContentLoad'; +/** @internal */ +export {onFirstJsonReportDataLoad} from './onFirstJsonReportDataLoad'; /** @internal */ export {initialScript} from './initialScript'; /** @internal */ @@ -21,6 +23,8 @@ export {parseMarkdownLinks} from './parseMarkdownLinks'; /** @internal */ export {readJsonReportData} from './readJsonReportData'; /** @internal */ +export {readPartOfJsonReportData} from './readPartOfJsonReportData'; +/** @internal */ export { renderDatesInterval, renderDuration, diff --git a/src/utils/report/client/initialScript.ts b/src/utils/report/client/initialScript.ts index 2fe8c589..dd76436a 100644 --- a/src/utils/report/client/initialScript.ts +++ b/src/utils/report/client/initialScript.ts @@ -3,7 +3,7 @@ import {addOnClickOnClass as clientAddOnClickOnClass} from './addOnClickOnClass' import {clickOnRetry as clientClickOnRetry} from './clickOnRetry'; import {clickOnStep as clientClickOnStep} from './clickOnStep'; import {clickOnTestRun as clientClickOnTestRun} from './clickOnTestRun'; -import {domContentLoadedHandler as clientDomContentLoadedHandler} from './domContentLoadedHandler'; +import {onDomContentLoad as clientOnDomContentLoad} from './onDomContentLoad'; import {setReadJsonReportDataObservers as clientSetReadJsonReportDataObservers} from './setReadJsonReportDataObservers'; const addDomContentLoadedHandler = clientAddDomContentLoadedHandler; @@ -11,7 +11,7 @@ const addOnClickOnClass = clientAddOnClickOnClass; const clickOnRetry = clientClickOnRetry; const clickOnStep = clientClickOnStep; const clickOnTestRun = clientClickOnTestRun; -const domContentLoadedHandler = clientDomContentLoadedHandler; +const onDomContentLoad = clientOnDomContentLoad; const setReadJsonReportDataObservers = clientSetReadJsonReportDataObservers; /** @@ -26,5 +26,5 @@ export const initialScript = (): void => { setReadJsonReportDataObservers(); - addDomContentLoadedHandler(domContentLoadedHandler); + addDomContentLoadedHandler(onDomContentLoad); }; diff --git a/src/utils/report/client/domContentLoadedHandler.ts b/src/utils/report/client/onDomContentLoad.ts similarity index 85% rename from src/utils/report/client/domContentLoadedHandler.ts rename to src/utils/report/client/onDomContentLoad.ts index 34b5b097..48cff803 100644 --- a/src/utils/report/client/domContentLoadedHandler.ts +++ b/src/utils/report/client/onDomContentLoad.ts @@ -7,11 +7,11 @@ declare const reportClientState: ReportClientState; const readJsonReportData = clientReadJsonReportData; /** - * DOMContentloaded handler for report page. + * `DOMContentLoaded` handler for report page. * This client function should not use scope variables (except global functions). * @internal */ -export function domContentLoadedHandler(): void { +export function onDomContentLoad(): void { readJsonReportData(true); for (const observer of reportClientState.readJsonReportDataObservers) { diff --git a/src/utils/report/client/onFirstJsonReportDataLoad.ts b/src/utils/report/client/onFirstJsonReportDataLoad.ts new file mode 100644 index 00000000..321ebb53 --- /dev/null +++ b/src/utils/report/client/onFirstJsonReportDataLoad.ts @@ -0,0 +1,36 @@ +import {clickOnTestRun as clientClickOnTestRun} from './clickOnTestRun'; + +const clickOnTestRun = clientClickOnTestRun; + +declare const e2edTestRunDetailsContainer: HTMLElement; + +/** + * Handler of loading first part of JSON report data for report page. + * This client function should not use scope variables (except global functions). + * @internal + */ +export function onFirstJsonReportDataLoad(): void { + if (window.location.hash !== '') { + return; + } + + const buttonForFailedTestRun = document.querySelector( + '.retry:not([hidden]) .test-button_status_failed', + ); + + if (!buttonForFailedTestRun) { + return; + } + + clickOnTestRun(buttonForFailedTestRun as HTMLElement); + + const buttonOfOpenStep = document.querySelector('.step-expanded[aria-expanded="true"]'); + + if (buttonOfOpenStep) { + const {top} = buttonOfOpenStep.getBoundingClientRect(); + + setTimeout(() => { + e2edTestRunDetailsContainer.scrollTop = top; + }, 8); + } +} diff --git a/src/utils/report/client/readJsonReportData.ts b/src/utils/report/client/readJsonReportData.ts index 83976afc..bcdb3a62 100644 --- a/src/utils/report/client/readJsonReportData.ts +++ b/src/utils/report/client/readJsonReportData.ts @@ -1,4 +1,10 @@ -import type {FullTestRun, ReportClientState} from '../../../types/internal'; +import {onFirstJsonReportDataLoad as clientOnFirstJsonReportDataLoad} from './onFirstJsonReportDataLoad'; +import {readPartOfJsonReportData as clientReadPartOfJsonReportData} from './readPartOfJsonReportData'; + +import type {ReportClientState} from '../../../types/internal'; + +const onFirstJsonReportDataLoad = clientOnFirstJsonReportDataLoad; +const readPartOfJsonReportData = clientReadPartOfJsonReportData; declare const reportClientState: ReportClientState; @@ -9,7 +15,7 @@ declare const reportClientState: ReportClientState; * This client function should not use scope variables (except global functions). * @internal */ -export function readJsonReportData(areAllScriptsLoaded?: boolean): void { +export function readJsonReportData(areAllScriptsLoaded = false): void { const {lengthOfReadedJsonReportDataParts} = reportClientState; const scripts = document.querySelectorAll('body > script.e2edJsonReportData'); const {length} = scripts; @@ -18,26 +24,20 @@ export function readJsonReportData(areAllScriptsLoaded?: boolean): void { return; } - let hasParseErrorForLastScript = false; + let isLastReadSuccessful = true; - for (let i = lengthOfReadedJsonReportDataParts; i < length; i += 1) { - try { - const fullTestRuns = JSON.parse(scripts[i]?.textContent ?? '') as readonly FullTestRun[]; + for (let index = lengthOfReadedJsonReportDataParts; index < length; index += 1) { + isLastReadSuccessful = readPartOfJsonReportData({ + scriptToRead: scripts[index], + shouldLogError: index < length - 1 || areAllScriptsLoaded, + }); + } - (reportClientState.fullTestRuns as FullTestRun[]).push(...fullTestRuns); - } catch (error) { - if (i < length - 1 || areAllScriptsLoaded) { - // eslint-disable-next-line no-console - console.error(`Cannot parse JSON report data from script number ${i}`); - } + const newLength = areAllScriptsLoaded || isLastReadSuccessful ? length : length - 1; - if (i === length - 1) { - hasParseErrorForLastScript = true; - } - } + if (reportClientState.lengthOfReadedJsonReportDataParts === 0 && newLength > 0) { + onFirstJsonReportDataLoad(); } - const newLength = !areAllScriptsLoaded && hasParseErrorForLastScript ? length - 1 : length; - reportClientState.lengthOfReadedJsonReportDataParts = newLength; } diff --git a/src/utils/report/client/readPartOfJsonReportData.ts b/src/utils/report/client/readPartOfJsonReportData.ts new file mode 100644 index 00000000..177631db --- /dev/null +++ b/src/utils/report/client/readPartOfJsonReportData.ts @@ -0,0 +1,31 @@ +import type {FullTestRun, ReportClientState} from '../../../types/internal'; + +declare const reportClientState: ReportClientState; + +type Options = Readonly<{ + scriptToRead: Element | undefined; + shouldLogError: boolean; +}>; + +/** + * Reads part of JSON report data from script tag. + * Returns `true` if read successfully, and `false` if it has parse errors. + * This client function should not use scope variables (except global functions). + * @internal + */ +export function readPartOfJsonReportData({scriptToRead, shouldLogError}: Options): boolean { + try { + const fullTestRuns = JSON.parse(scriptToRead?.textContent ?? '') as readonly FullTestRun[]; + + (reportClientState.fullTestRuns as FullTestRun[]).push(...fullTestRuns); + } catch (error) { + if (shouldLogError) { + // eslint-disable-next-line no-console + console.error('Cannot parse JSON report data from script', error); + } + + return false; + } + + return true; +} diff --git a/src/utils/report/render/renderScriptFunctions.ts b/src/utils/report/render/renderScriptFunctions.ts index d9678b0c..8f124626 100644 --- a/src/utils/report/render/renderScriptFunctions.ts +++ b/src/utils/report/render/renderScriptFunctions.ts @@ -9,9 +9,11 @@ import { clickOnStep, clickOnTestRun, createSafeHtmlWithoutSanitize, - domContentLoadedHandler, + onDomContentLoad, + onFirstJsonReportDataLoad, parseMarkdownLinks, readJsonReportData, + readPartOfJsonReportData, renderDatesInterval, renderDuration, renderStep, @@ -39,10 +41,12 @@ ${createSafeHtmlWithoutSanitize.toString()} ${clickOnRetry.toString()} ${clickOnStep.toString()} ${clickOnTestRun.toString()} -${domContentLoadedHandler.toString()} +${onDomContentLoad.toString()} +${onFirstJsonReportDataLoad.toString()} ${getDurationWithUnits.toString()} ${parseMarkdownLinks.toString()} ${readJsonReportData.toString()} +${readPartOfJsonReportData.toString()} ${renderDatesInterval.toString()} ${renderDuration.toString()} ${renderStep.toString()}