UNPKG

testcafe

Version:

Automated browser testing for the modern web development stack.

994 lines 186 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const lodash_1 = require("lodash"); const nanoid_1 = require("nanoid"); const read_file_relative_1 = require("read-file-relative"); const promisify_event_1 = __importDefault(require("promisify-event")); const mustache_1 = __importDefault(require("mustache")); const async_event_emitter_1 = __importDefault(require("../utils/async-event-emitter")); const debug_log_1 = __importDefault(require("./debug-log")); const formattable_adapter_1 = __importDefault(require("../errors/test-run/formattable-adapter")); const error_list_1 = __importDefault(require("../errors/error-list")); const runtime_1 = require("../errors/runtime"); const test_run_1 = require("../errors/test-run/"); const client_messages_1 = __importDefault(require("./client-messages")); const type_1 = __importDefault(require("./commands/type")); const delay_1 = __importDefault(require("../utils/delay")); const is_password_input_1 = __importDefault(require("../utils/is-password-input")); const marker_symbol_1 = __importDefault(require("./marker-symbol")); const test_run_tracker_1 = __importDefault(require("../api/test-run-tracker")); const phase_1 = __importDefault(require("../role/phase")); const plugin_host_1 = __importDefault(require("../reporter/plugin-host")); const browser_console_messages_1 = __importDefault(require("./browser-console-messages")); const warning_log_1 = __importDefault(require("../notifications/warning-log")); const warning_message_1 = __importDefault(require("../notifications/warning-message")); const testcafe_hammerhead_1 = require("testcafe-hammerhead"); const INJECTABLES = __importStar(require("../assets/injectables")); const utils_1 = require("../custom-client-scripts/utils"); const string_1 = require("../utils/string"); const utils_2 = require("./commands/utils"); const actions_1 = require("./commands/actions"); const types_1 = require("../errors/types"); const process_test_fn_error_1 = __importDefault(require("../errors/process-test-fn-error")); const replicator_1 = require("../client-functions/replicator"); const session_controller_1 = __importDefault(require("./session-controller")); const browser_manipulation_queue_1 = __importDefault(require("./browser-manipulation-queue")); const observed_callsites_storage_1 = __importDefault(require("./observed-callsites-storage")); const base_js_1 = require("./commands/base.js"); const get_assertion_timeout_1 = __importDefault(require("../utils/get-options/get-assertion-timeout")); const phase_2 = __importDefault(require("./phase")); const execute_client_function_1 = require("./commands/execute-client-function"); const add_rendered_warning_1 = __importDefault(require("../notifications/add-rendered-warning")); const get_browser_1 = __importDefault(require("../utils/get-browser")); const executor_1 = __importDefault(require("../assertions/executor")); const async_filter_1 = __importDefault(require("../utils/async-filter")); const execute_fn_with_timeout_1 = __importDefault(require("../utils/execute-fn-with-timeout")); const url_1 = require("url"); const skip_js_errors_1 = require("../api/skip-js-errors"); const factory_1 = require("./cookies/factory"); const factory_2 = require("./storages/factory"); const wrap_custom_action_1 = __importDefault(require("../api/wrap-custom-action")); const role_provider_1 = require("./role-provider"); const report_data_log_1 = __importDefault(require("../reporter/report-data-log")); const lazyRequire = require('import-lazy')(require); const ClientFunctionBuilder = lazyRequire('../client-functions/client-function-builder'); const TestRunBookmark = lazyRequire('./bookmark'); const actionCommands = lazyRequire('./commands/actions'); const browserManipulationCommands = lazyRequire('./commands/browser-manipulation'); const serviceCommands = lazyRequire('./commands/service'); const executeClientFunctionCommands = lazyRequire('./commands/execute-client-function'); const { executeJsExpression, executeAsyncJsExpression } = lazyRequire('./execute-js-expression'); const TEST_RUN_TEMPLATE = (0, read_file_relative_1.readSync)('../client/test-run/index.js.mustache'); const IFRAME_TEST_RUN_TEMPLATE = (0, read_file_relative_1.readSync)('../client/test-run/iframe.js.mustache'); const TEST_DONE_CONFIRMATION_RESPONSE = 'test-done-confirmation'; const MAX_RESPONSE_DELAY = 3000; const CHILD_WINDOW_READY_TIMEOUT = 30 * 1000; const ALL_DRIVER_TASKS_ADDED_TO_QUEUE_EVENT = 'all-driver-tasks-added-to-queue'; class TestRun extends async_event_emitter_1.default { constructor({ test, browserConnection, screenshotCapturer, globalWarningLog, opts, messageBus, startRunExecutionTime, nativeAutomation }) { super(); this[marker_symbol_1.default] = true; this._messageBus = messageBus; this.warningLog = new warning_log_1.default(globalWarningLog, warning_log_1.default.createAddWarningCallback(messageBus, this)); this.reportDataLog = new report_data_log_1.default(report_data_log_1.default.createAddDataCallback(messageBus, this)); this.opts = opts; this.test = test; this.browserConnection = browserConnection; this.unstable = false; this.browser = (0, get_browser_1.default)(browserConnection, nativeAutomation); this.phase = phase_2.default.initial; this.driverTaskQueue = []; this.testDoneCommandQueued = false; this.activeDialogHandler = null; this.activeIframeSelector = null; this.speed = this.opts.speed; this.pageLoadTimeout = this._getPageLoadTimeout(test, opts); this.testExecutionTimeout = this._getTestExecutionTimeout(opts); this.disablePageReloads = test.disablePageReloads || opts.disablePageReloads && test.disablePageReloads !== false; this.disablePageCaching = test.disablePageCaching || opts.disablePageCaching; this.isNativeAutomation = nativeAutomation; this.disableMultipleWindows = opts.disableMultipleWindows; this.isExperimentalMultipleWindows = opts.experimentalMultipleWindows; this.requestTimeout = this._getRequestTimeout(test, opts); this.session = session_controller_1.default.getSession(this); this.consoleMessages = new browser_console_messages_1.default(); this.pendingRequest = null; this.pendingPageError = null; this.controller = null; this.ctx = Object.create(null); this.fixtureCtx = null; this.testRunCtx = null; this.currentRoleId = null; this.usedRoleStates = Object.create(null); this.errs = []; this.lastDriverStatusId = null; this.lastDriverStatusResponse = null; this.fileDownloadingHandled = false; this.resolveWaitForFileDownloadingPromise = null; this.attachmentDownloadingHandled = false; this.addingDriverTasksCount = 0; this.debugging = this.opts.debugMode; this.debugOnFail = this.opts.debugOnFail; this.disableDebugBreakpoints = false; this.debugReporterPluginHost = new plugin_host_1.default({ noColors: false }); this.browserManipulationQueue = new browser_manipulation_queue_1.default(browserConnection, screenshotCapturer, this.warningLog, nativeAutomation); this.debugLog = new debug_log_1.default(this.browserConnection.userAgent); this.quarantine = null; this.debugLogger = this.opts.debugLogger; this.observedCallsites = new observed_callsites_storage_1.default(); this.asyncJsExpressionCallsites = new Map(); this.replicator = (0, replicator_1.createReplicator)([new replicator_1.SelectorNodeTransform()]); this.disconnected = false; this.errScreenshotPath = null; this.startRunExecutionTime = startRunExecutionTime; this.runExecutionTimeout = this._getRunExecutionTimeout(opts); this._requestHookEventProvider = this._getRequestHookEventProvider(); this._roleProvider = this._getRoleProvider(); this.cookieProvider = factory_1.CookieProviderFactory.create(this, this.isNativeAutomation); this._storagesProvider = factory_2.StoragesProviderFactory.create(this, this.isNativeAutomation); this._addInjectables(); } _getRequestHookEventProvider() { if (!this.isNativeAutomation) return this.session.requestHookEventProvider; return this._nativeAutomationRequestPipeline.requestHookEventProvider; } saveStoragesSnapshot(storageSnapshot) { if (this.isNativeAutomation) this._nativeAutomationRequestPipeline.restoringStorages = storageSnapshot; } get _nativeAutomationRequestPipeline() { return this._nativeAutomation.requestPipeline; } get _nativeAutomation() { return this.browserConnection.getNativeAutomation(); } _getRoleProvider() { if (this.isNativeAutomation) return new role_provider_1.NativeAutomationRoleProvider(this); return new role_provider_1.ProxyRoleProvider(this); } _getPageLoadTimeout(test, opts) { var _a; if (((_a = test.timeouts) === null || _a === void 0 ? void 0 : _a.pageLoadTimeout) !== void 0) return test.timeouts.pageLoadTimeout; return opts.pageLoadTimeout; } _getRequestTimeout(test, opts) { var _a, _b; return { page: ((_a = test.timeouts) === null || _a === void 0 ? void 0 : _a.pageRequestTimeout) || opts.pageRequestTimeout, ajax: ((_b = test.timeouts) === null || _b === void 0 ? void 0 : _b.ajaxRequestTimeout) || opts.ajaxRequestTimeout, }; } _getExecutionTimeout(timeout, error) { return { timeout, rejectWith: error, }; } _getTestExecutionTimeout(opts) { const testExecutionTimeout = opts.testExecutionTimeout || 0; if (!testExecutionTimeout) return null; return this._getExecutionTimeout(testExecutionTimeout, new test_run_1.TestTimeoutError(testExecutionTimeout)); } _getRunExecutionTimeout(opts) { const runExecutionTimeout = opts.runExecutionTimeout || 0; if (!runExecutionTimeout) return null; return this._getExecutionTimeout(runExecutionTimeout, new test_run_1.RunTimeoutError(runExecutionTimeout)); } get restRunExecutionTimeout() { if (!this.startRunExecutionTime || !this.runExecutionTimeout) return null; const currentTimeout = Math.max(this.runExecutionTimeout.timeout - (Date.now() - this.startRunExecutionTime.getTime()), 0); return this._getExecutionTimeout(currentTimeout, this.runExecutionTimeout.rejectWith); } get executionTimeout() { return this.restRunExecutionTimeout && (!this.testExecutionTimeout || this.restRunExecutionTimeout.timeout < this.testExecutionTimeout.timeout) ? this.restRunExecutionTimeout : this.testExecutionTimeout || null; } async getCurrentCDPSession() { return this.browserConnection.getCurrentCDPSession(); } _addClientScriptContentWarningsIfNecessary() { const { empty, duplicatedContent } = (0, utils_1.findProblematicScripts)(this.test.clientScripts); if (empty.length) this.warningLog.addWarning(warning_message_1.default.clientScriptsWithEmptyContent); if (duplicatedContent.length) { const suffix = (0, string_1.getPluralSuffix)(duplicatedContent); const duplicatedContentClientScriptsStr = (0, string_1.getConcatenatedValuesString)(duplicatedContent, '\n'); this.warningLog.addWarning(warning_message_1.default.clientScriptsWithDuplicatedContent, suffix, duplicatedContentClientScriptsStr); } } _addInjectables() { this._addClientScriptContentWarningsIfNecessary(); if (!this.isNativeAutomation) { this.injectable.scripts.push(...INJECTABLES.SCRIPTS); this.injectable.styles.push(INJECTABLES.TESTCAFE_UI_STYLES); } this.injectable.userScripts.push(...this.test.clientScripts.map(script => { return { url: script.getResultUrl(this.id), page: script.page, }; })); } get id() { return this.session.id; } get injectable() { return this.session.injectable; } addQuarantineInfo(quarantine) { this.quarantine = quarantine; } async _addRequestHook(hook) { if (this.test.requestHooks.includes(hook)) return; this.test.requestHooks.push(hook); await this._initRequestHook(hook); } async _removeRequestHook(hook) { if (!this.test.requestHooks.includes(hook)) return; (0, lodash_1.pull)(this.test.requestHooks, hook); await this._disposeRequestHook(hook); } async _initRequestHook(hook) { hook._warningLog = this.warningLog; await Promise.all(hook._requestFilterRules.map(rule => { return this._requestHookEventProvider.addRequestEventListeners(rule, { onRequest: hook.onRequest.bind(hook), onConfigureResponse: hook._onConfigureResponse.bind(hook), onResponse: hook.onResponse.bind(hook), }, (err) => this._onRequestHookMethodError(err, hook._className)); })); } _onRequestHookMethodError(event, hookClassName) { let err = event.error; const isRequestHookNotImplementedMethodError = (err === null || err === void 0 ? void 0 : err.code) === types_1.TEST_RUN_ERRORS.requestHookNotImplementedError; if (!isRequestHookNotImplementedMethodError) err = new test_run_1.RequestHookUnhandledError(err, hookClassName, event.methodName); this.addError(err); } async _disposeRequestHook(hook) { hook._warningLog = null; await Promise.all(hook._requestFilterRules.map(rule => { return this._requestHookEventProvider.removeRequestEventListeners(rule); })); } async _detachRequestEventListeners(rules) { await Promise.all(rules.map(rule => { return this._requestHookEventProvider.removeRequestEventListeners(rule); })); } async _initRequestHooks() { await Promise.all(this.test.requestHooks.map(hook => this._initRequestHook(hook))); } _prepareSkipJsErrorsOption() { const options = this.test.skipJsErrorsOptions !== void 0 ? this.test.skipJsErrorsOptions : this.opts.skipJsErrors || false; return (0, skip_js_errors_1.prepareSkipJsErrorsOptions)(options); } // Hammerhead payload async getPayloadScript(windowId) { this.fileDownloadingHandled = false; this.resolveWaitForFileDownloadingPromise = null; const skipJsErrors = this._prepareSkipJsErrorsOption(); return mustache_1.default.render(TEST_RUN_TEMPLATE, { testRunId: JSON.stringify(this.session.id), browserId: JSON.stringify(this.browserConnection.id), activeWindowId: JSON.stringify(this.activeWindowId), windowId: JSON.stringify(windowId || ''), browserHeartbeatRelativeUrl: JSON.stringify(this.browserConnection.heartbeatRelativeUrl), browserStatusRelativeUrl: JSON.stringify(this.browserConnection.statusRelativeUrl), browserStatusDoneRelativeUrl: JSON.stringify(this.browserConnection.statusDoneRelativeUrl), browserIdleRelativeUrl: JSON.stringify(this.browserConnection.idleRelativeUrl), browserActiveWindowIdUrl: JSON.stringify(this.browserConnection.activeWindowIdUrl), browserEnsureWindowInNativeAutomationUrl: JSON.stringify(this.browserConnection.ensureWindowInNativeAutomationUrl), browserCloseWindowUrl: JSON.stringify(this.browserConnection.closeWindowUrl), browserOpenFileProtocolRelativeUrl: JSON.stringify(this.browserConnection.openFileProtocolRelativeUrl), browserDispatchNativeAutomationEventRelativeUrl: JSON.stringify(this.browserConnection.dispatchNativeAutomationEventRelativeUrl), browserDispatchNativeAutomationEventSequenceRelativeUrl: JSON.stringify(this.browserConnection.dispatchNativeAutomationEventSequenceRelativeUrl), browserParseSelectorUrl: JSON.stringify(this.browserConnection.parseSelectorRelativeUrl), userAgent: JSON.stringify(this.browserConnection.userAgent), testName: JSON.stringify(this.test.name), fixtureName: JSON.stringify(this.test.fixture.name), selectorTimeout: this.opts.selectorTimeout, pageLoadTimeout: this.pageLoadTimeout, childWindowReadyTimeout: CHILD_WINDOW_READY_TIMEOUT, skipJsErrors: JSON.stringify(skipJsErrors), retryTestPages: this.opts.retryTestPages, speed: this.speed, dialogHandler: JSON.stringify(this.activeDialogHandler), canUseDefaultWindowActions: JSON.stringify(await this.browserConnection.canUseDefaultWindowActions()), nativeAutomation: this.isNativeAutomation, experimentalMultipleWindows: this.isExperimentalMultipleWindows, domain: JSON.stringify(this.browserConnection.browserConnectionGateway.proxy.server1Info.domain), }); } async getIframePayloadScript() { const browserEnsureWindowInNativeAutomationUrl = JSON.stringify(this.browserConnection.ensureWindowInNativeAutomationUrl); const experimentalMultipleWindows = this.isExperimentalMultipleWindows; return mustache_1.default.render(IFRAME_TEST_RUN_TEMPLATE, { testRunId: JSON.stringify(this.session.id), selectorTimeout: this.opts.selectorTimeout, pageLoadTimeout: this.pageLoadTimeout, retryTestPages: !!this.opts.retryTestPages, speed: this.speed, dialogHandler: JSON.stringify(this.activeDialogHandler), nativeAutomation: JSON.stringify(this.isNativeAutomation), domain: JSON.stringify(this.browserConnection.browserConnectionGateway.proxy.server1Info.domain), browserEnsureWindowInNativeAutomationUrl, experimentalMultipleWindows, }); } // Hammerhead handlers getAuthCredentials() { return this.test.authCredentials; } handleFileDownload() { if (this.resolveWaitForFileDownloadingPromise) { this.resolveWaitForFileDownloadingPromise(true); this.resolveWaitForFileDownloadingPromise = null; } else this.fileDownloadingHandled = true; } handleAttachment(data) { if (data.isOpenedInNewWindow) this.attachmentDownloadingHandled = true; } handlePageError(ctx, err) { this.pendingPageError = new test_run_1.PageLoadError(err, ctx.reqOpts.url); ctx.redirect(ctx.toProxyUrl(testcafe_hammerhead_1.SPECIAL_ERROR_PAGE)); } // Test function execution async _executeTestFn(phase, fn, timeout) { this.phase = phase; try { await (0, execute_fn_with_timeout_1.default)(fn, timeout, this); } catch (err) { await this._makeScreenshotOnFail(); this.addError(err); return false; } finally { this.errScreenshotPath = null; } return !this._addPendingPageErrorIfAny(); } async _runBeforeHook() { var _a, _b; if (this.test.globalBeforeFn) await this._executeTestFn(phase_2.default.inTestBeforeHook, this.test.globalBeforeFn, this.executionTimeout); if (this.test.beforeFn) return await this._executeTestFn(phase_2.default.inTestBeforeHook, this.test.beforeFn, this.executionTimeout); if ((_a = this.test.fixture) === null || _a === void 0 ? void 0 : _a.beforeEachFn) return await this._executeTestFn(phase_2.default.inFixtureBeforeEachHook, (_b = this.test.fixture) === null || _b === void 0 ? void 0 : _b.beforeEachFn, this.executionTimeout); return true; } async _runAfterHook() { var _a, _b; if (this.test.afterFn) await this._executeTestFn(phase_2.default.inTestAfterHook, this.test.afterFn, this.executionTimeout); else if ((_a = this.test.fixture) === null || _a === void 0 ? void 0 : _a.afterEachFn) await this._executeTestFn(phase_2.default.inFixtureAfterEachHook, (_b = this.test.fixture) === null || _b === void 0 ? void 0 : _b.afterEachFn, this.executionTimeout); if (this.test.globalAfterFn) await this._executeTestFn(phase_2.default.inTestAfterHook, this.test.globalAfterFn, this.executionTimeout); } async _finalizeTestRun(id) { test_run_tracker_1.default.removeActiveTestRun(id); } async start() { test_run_tracker_1.default.addActiveTestRun(this); await this.emit('start'); const onDisconnected = (err) => this._disconnect(err); this.browserConnection.once('disconnected', onDisconnected); await this.once('connected'); await this.emit('ready'); if (await this._runBeforeHook()) { await this._executeTestFn(phase_2.default.inTest, this.test.fn, this.executionTimeout); await this._runAfterHook(); } if (this.disconnected) return; this.phase = phase_2.default.pendingFinalization; this.browserConnection.removeListener('disconnected', onDisconnected); if (this.errs.length && this.debugOnFail) { const errStr = this.debugReporterPluginHost.formatError(this.errs[0]); await this._enqueueSetBreakpointCommand(void 0, null, errStr); } await this.emit('before-done'); await this._internalExecuteCommand(new serviceCommands.TestDoneCommand()); this._addPendingPageErrorIfAny(); this._requestHookEventProvider.clearRequestEventListeners(); this.normalizeRequestHookErrors(); await this._finalizeTestRun(this.session.id); await this.emit('done'); } // Errors _addPendingPageErrorIfAny() { const error = this.pendingPageError; if (error) { this.addError(error); this.pendingPageError = null; return true; } return false; } _ensureErrorId(err) { // @ts-ignore err.id = err.id || (0, nanoid_1.nanoid)(7); } _createErrorAdapter(err) { this._ensureErrorId(err); return new formattable_adapter_1.default(err, { userAgent: this.browserConnection.userAgent, screenshotPath: this.errScreenshotPath || '', testRunId: this.id, testRunPhase: this.phase, }); } addError(err) { const errList = (err instanceof error_list_1.default ? err.items : [err]); errList.forEach(item => { const adapter = this._createErrorAdapter(item); this.errs.push(adapter); }); } normalizeRequestHookErrors() { const requestHookErrors = (0, lodash_1.remove)(this.errs, e => e.code === types_1.TEST_RUN_ERRORS.requestHookNotImplementedError || e.code === types_1.TEST_RUN_ERRORS.requestHookUnhandledError); if (!requestHookErrors.length) return; const uniqRequestHookErrors = (0, lodash_1.chain)(requestHookErrors) .uniqBy(e => { const err = e; return err.hookClassName + err.methodName; }) .sortBy(['hookClassName', 'methodName']) .value(); this.errs = this.errs.concat(uniqRequestHookErrors); } // Task queue _enqueueCommand(command, callsite) { if (this.pendingRequest) this._resolvePendingRequest(command); return new Promise(async (resolve, reject) => { this.addingDriverTasksCount--; this.driverTaskQueue.push({ command, resolve, reject, callsite }); if (!this.addingDriverTasksCount) await this.emit(ALL_DRIVER_TASKS_ADDED_TO_QUEUE_EVENT, this.driverTaskQueue.length); }); } get driverTaskQueueLength() { return this.addingDriverTasksCount ? (0, promisify_event_1.default)(this, ALL_DRIVER_TASKS_ADDED_TO_QUEUE_EVENT) : Promise.resolve(this.driverTaskQueue.length); } async _enqueueBrowserConsoleMessagesCommand(command, callsite) { await this._enqueueCommand(command, callsite); const consoleMessageCopy = this.consoleMessages.getCopy(); // @ts-ignore return consoleMessageCopy[String(this.activeWindowId)]; } async _enqueueGetCookies(command) { const { cookies, urls } = command; return this.cookieProvider.getCookies(cookies, urls); } async _enqueueSetCookies(command) { const cookies = command.cookies; const url = command.url || await this.getCurrentUrl(); return this.cookieProvider.setCookies(cookies, url); } async _enqueueDeleteCookies(command) { const { cookies, urls } = command; return this.cookieProvider.deleteCookies(cookies, urls); } async _enqueueSetBreakpointCommand(callsite, selector, error) { if (this.debugLogger) this.debugLogger.showBreakpoint(this.session.id, this.browserConnection.userAgent, callsite, error); this.debugging = await this._internalExecuteCommand(new serviceCommands.SetBreakpointCommand(!!error, selector), callsite); } _removeAllNonServiceTasks() { this.driverTaskQueue = this.driverTaskQueue.filter(driverTask => (0, utils_2.isServiceCommand)(driverTask.command)); this.browserManipulationQueue.removeAllNonServiceManipulations(); } // Current driver task get currentDriverTask() { return this.driverTaskQueue[0]; } get baseUrl() { return this.opts.baseUrl || ''; } _resolveCurrentDriverTask(result) { this.currentDriverTask.resolve(result); this.driverTaskQueue.shift(); if (this.testDoneCommandQueued) this._removeAllNonServiceTasks(); } _rejectCurrentDriverTask(err) { // @ts-ignore err.callsite = err.callsite || this.currentDriverTask.callsite; this.currentDriverTask.reject(err); this._removeAllNonServiceTasks(); } // Pending request _clearPendingRequest() { if (this.pendingRequest) { clearTimeout(this.pendingRequest.responseTimeout); this.pendingRequest = null; } } _resolvePendingRequest(command) { this.lastDriverStatusResponse = command; if (this.pendingRequest) this.pendingRequest.resolve(command); this._clearPendingRequest(); } // Handle driver request _shouldResolveCurrentDriverTask(driverStatus) { const currentCommand = this.currentDriverTask.command; const isExecutingObservationCommand = currentCommand instanceof executeClientFunctionCommands.ExecuteSelectorCommand || currentCommand instanceof execute_client_function_1.ExecuteClientFunctionCommand; const isDebugActive = currentCommand instanceof serviceCommands.SetBreakpointCommand; const shouldExecuteCurrentCommand = driverStatus.isFirstRequestAfterWindowSwitching && (isExecutingObservationCommand || isDebugActive); return !shouldExecuteCurrentCommand; } _fulfillCurrentDriverTask(driverStatus) { var _a; if (!this.currentDriverTask) return; if ((_a = driverStatus.warnings) === null || _a === void 0 ? void 0 : _a.length) { driverStatus.warnings.forEach((warning) => { (0, add_rendered_warning_1.default)(this.warningLog, warning_message_1.default[warning.type], this.currentDriverTask.callsite, ...warning.args); }); } if (driverStatus.executionError) this._rejectCurrentDriverTask(driverStatus.executionError); else if (this._shouldResolveCurrentDriverTask(driverStatus)) this._resolveCurrentDriverTask(driverStatus.result); } _handlePageErrorStatus(pageError) { if (this.currentDriverTask && (0, utils_2.isCommandRejectableByPageError)(this.currentDriverTask.command)) { this._rejectCurrentDriverTask(pageError); this.pendingPageError = null; return true; } this.pendingPageError = this.pendingPageError || pageError; return false; } async _handleDriverRequest(driverStatus) { const isTestDone = this.currentDriverTask && this.currentDriverTask.command.type === type_1.default.testDone; const pageError = this.pendingPageError || driverStatus.pageError; const currentTaskRejectedByError = pageError && this._handlePageErrorStatus(pageError); this.consoleMessages.concat(driverStatus.consoleMessages); if (!currentTaskRejectedByError && driverStatus.isCommandResult) { if (isTestDone) { this._resolveCurrentDriverTask(); return TEST_DONE_CONFIRMATION_RESPONSE; } this._fulfillCurrentDriverTask(driverStatus); if (driverStatus.isPendingWindowSwitching) return null; } return this._getCurrentDriverTaskCommand(); } async _getCurrentDriverTaskCommand() { if (!this.currentDriverTask) return null; const command = this.currentDriverTask.command; if (command.type === type_1.default.navigateTo && command.stateSnapshot) await this._roleProvider.useStateSnapshot(JSON.parse(command.stateSnapshot)); return command; } // Execute command async _executeJsExpression(command) { const resultVariableName = command.resultVariableName; let expression = command.expression; if (resultVariableName) expression = `${resultVariableName} = ${expression}, ${resultVariableName}`; return executeJsExpression(expression, this, { skipVisibilityCheck: false }); } async _executeAsyncJsExpression(command, callsite) { return executeAsyncJsExpression(command.expression, this, callsite); } async _executeAssertion(command, callsite) { const assertionTimeout = (0, get_assertion_timeout_1.default)(command, this.opts); const executor = new executor_1.default(command, assertionTimeout, callsite); executor.once('start-assertion-retries', (timeout) => this._internalExecuteCommand(new serviceCommands.ShowAssertionRetriesStatusCommand(timeout))); executor.once('end-assertion-retries', (success) => this._internalExecuteCommand(new serviceCommands.HideAssertionRetriesStatusCommand(success))); const executeFn = this.decoratePreventEmitActionEvents(() => executor.run(), { prevent: true }); return await executeFn(); } _adjustConfigurationWithCommand(command) { if (command.type === type_1.default.testDone) { this.testDoneCommandQueued = true; if (this.debugLogger) this.debugLogger.hideBreakpoint(this.session.id); } else if (command.type === type_1.default.setNativeDialogHandler) this.activeDialogHandler = command.dialogHandler; else if (command.type === type_1.default.switchToIframe) this.activeIframeSelector = command.selector; else if (command.type === type_1.default.switchToMainWindow) this.activeIframeSelector = null; else if (command.type === type_1.default.setTestSpeed) this.speed = command.speed; else if (command.type === type_1.default.setPageLoadTimeout) this.pageLoadTimeout = command.duration; else if (command.type === type_1.default.debug) this.debugging = true; } async _adjustScreenshotCommand(command) { const browserId = this.browserConnection.id; const { hasChromelessScreenshots } = await this.browserConnection.provider.hasCustomActionForBrowser(browserId); if (!hasChromelessScreenshots) command.generateScreenshotMark(); } async _adjustCommandOptionsAndEnvironment(command, callsite) { var _a; if (((_a = command.options) === null || _a === void 0 ? void 0 : _a.confidential) !== void 0) return; if (command.type === type_1.default.typeText) { const result = await this._internalExecuteCommand(command.selector, callsite); if (!result) return; const node = this.replicator.decode(result); command.options.confidential = (0, is_password_input_1.default)(node); } else if (command.type === type_1.default.pressKey) { const result = await this._internalExecuteCommand(new serviceCommands.GetActiveElementCommand()); if (!result) return; const node = this.replicator.decode(result); command.options.confidential = (0, is_password_input_1.default)(node); } } async _setBreakpointIfNecessary(command, callsite) { if (!this.disableDebugBreakpoints && this.debugging && (0, utils_2.canSetDebuggerBreakpointBeforeCommand)(command)) await this._enqueueSetBreakpointCommand(callsite); } async executeCommand(command, callsite) { return command instanceof base_js_1.ActionCommandBase ? this._executeActionCommand(command, callsite) : this._internalExecuteCommand(command, callsite); } async _executeActionCommand(command, callsite) { const actionArgs = { apiActionName: command.methodName, command }; let errorAdapter = null; let error = null; let result = null; const start = new Date().getTime(); try { await this._adjustCommandOptionsAndEnvironment(command, callsite); } catch (err) { error = err; } await this.emitActionEvent('action-start', actionArgs); try { if (!error) result = await this._internalExecuteCommand(command, callsite); } catch (err) { if (this.phase === phase_2.default.pendingFinalization && err instanceof test_run_1.ExternalAssertionLibraryError) (0, add_rendered_warning_1.default)(this.warningLog, { message: warning_message_1.default.unawaitedMethodWithAssertion, actionId: command.actionId }, callsite); else error = err; } const duration = new Date().getTime() - start; if (error) { // NOTE: check if error is TestCafeErrorList is specific for the `useRole` action // if error is TestCafeErrorList we do not need to create an adapter, // since error is already was processed in role initializer if (!(error instanceof error_list_1.default)) { await this._makeScreenshotOnFail(command.actionId); errorAdapter = this._createErrorAdapter((0, process_test_fn_error_1.default)(error)); } else errorAdapter = error.adapter; } Object.assign(actionArgs, { result, duration, err: errorAdapter, }); await this.emitActionEvent('action-done', actionArgs); if (error) throw error; return result; } async _internalExecuteCommand(command, callsite) { this.debugLog.command(command); if (this.pendingPageError && (0, utils_2.isCommandRejectableByPageError)(command)) return this._rejectCommandWithPageError(callsite); if ((0, utils_2.isExecutableOnClientCommand)(command)) this.addingDriverTasksCount++; this._adjustConfigurationWithCommand(command); await this._setBreakpointIfNecessary(command, callsite); if ((0, utils_2.isScreenshotCommand)(command)) { if (this.opts.disableScreenshots) { this.warningLog.addWarning({ message: warning_message_1.default.screenshotsDisabled, actionId: command.actionId }); return null; } await this._adjustScreenshotCommand(command); } if ((0, utils_2.isBrowserManipulationCommand)(command)) { this.browserManipulationQueue.push(command); if ((0, utils_2.isResizeWindowCommand)(command) && this.opts.videoPath) this.warningLog.addWarning({ message: warning_message_1.default.videoBrowserResizing, actionId: command.actionId }, this.test.name); } if (command.type === type_1.default.wait) return (0, delay_1.default)(command.timeout); if (command.type === type_1.default.setPageLoadTimeout) return null; if (command.type === type_1.default.debug) { const canDebug = !this.browserConnection.isHeadlessBrowser(); if (canDebug) return await this._enqueueSetBreakpointCommand(callsite, command === null || command === void 0 ? void 0 : command.selector, void 0); this.debugging = false; this.warningLog.addWarning({ message: warning_message_1.default.debugInHeadlessError, actionId: command.actionId }); return null; } if (command.type === type_1.default.useRole) { let fn = () => this._useRole(command.role, callsite); fn = this.decoratePreventEmitActionEvents(fn, { prevent: true }); fn = this.decorateDisableDebugBreakpoints(fn, { disable: true }); return await fn(); } if (command.type === type_1.default.runCustomAction) { const { fn, args } = command; const wrappedFn = (0, wrap_custom_action_1.default)(fn); return await wrappedFn(this, args); } if (command.type === type_1.default.report) return await this.reportDataLog.addData(command.args); if (command.type === type_1.default.assertion) return this._executeAssertion(command, callsite); if (command.type === type_1.default.executeExpression) return await this._executeJsExpression(command); if (command.type === type_1.default.executeAsyncExpression) return this._executeAsyncJsExpression(command, callsite); if (command.type === type_1.default.getBrowserConsoleMessages) return this._enqueueBrowserConsoleMessagesCommand(command, callsite); if (command.type === type_1.default.switchToPreviousWindow) command.windowId = this.browserConnection.previousActiveWindowId; if (command.type === type_1.default.switchToWindowByPredicate) return this._switchToWindowByPredicate(command); if (command.type === type_1.default.getCookies) return this._enqueueGetCookies(command); if (command.type === type_1.default.setCookies) return this._enqueueSetCookies(command); if (command.type === type_1.default.deleteCookies) return this._enqueueDeleteCookies(command); if (command.type === type_1.default.addRequestHooks) return Promise.all(command.hooks.map(hook => this._addRequestHook(hook))); if (command.type === type_1.default.removeRequestHooks) return Promise.all(command.hooks.map(hook => this._removeRequestHook(hook))); if (command.type === type_1.default.getCurrentCDPSession) return this.getCurrentCDPSession(); return this._enqueueCommand(command, callsite); } _rejectCommandWithPageError(callsite) { const err = this.pendingPageError; // @ts-ignore err.callsite = callsite; this.pendingPageError = null; return Promise.reject(err); } _sendCloseChildWindowOnFileDownloadingCommand() { return new actionCommands.CloseChildWindowOnFileDownloading(); } async _makeScreenshotOnFail(failedActionId) { const { screenshots } = this.opts; if (!this.errScreenshotPath && (screenshots === null || screenshots === void 0 ? void 0 : screenshots.takeOnFails)) this.errScreenshotPath = await this._internalExecuteCommand(new browserManipulationCommands.TakeScreenshotOnFailCommand({ failedActionId, fullPage: screenshots.fullPage })); } _decorateWithFlag(fn, flagName, value) { return async () => { // @ts-ignore this[flagName] = value; try { return await fn(); } finally { // @ts-ignore this[flagName] = !value; } }; } decoratePreventEmitActionEvents(fn, { prevent }) { return this._decorateWithFlag(fn, 'preventEmitActionEvents', prevent); } decorateDisableDebugBreakpoints(fn, { disable }) { return this._decorateWithFlag(fn, 'disableDebugBreakpoints', disable); } // Role management async getStateSnapshot() { const state = await this._roleProvider.getStateSnapshot(); state.storages = await this._internalExecuteCommand(new serviceCommands.BackupStoragesCommand()); return state; } async _cleanUpCtxs() { this.ctx = Object.create(null); this.fixtureCtx = Object.create(null); this.testRunCtx = Object.create(null); } async switchToCleanRun(url) { await this._cleanUpCtxs(); this.consoleMessages = new browser_console_messages_1.default(); await this._roleProvider.useStateSnapshot(testcafe_hammerhead_1.StateSnapshot.empty()); if (this.speed !== this.opts.speed) { const setSpeedCommand = new actionCommands.SetTestSpeedCommand({ speed: this.opts.speed }); await this._internalExecuteCommand(setSpeedCommand); } if (this.pageLoadTimeout !== this.opts.pageLoadTimeout) { const setPageLoadTimeoutCommand = new actionCommands.SetPageLoadTimeoutCommand({ duration: this.opts.pageLoadTimeout }); await this._internalExecuteCommand(setPageLoadTimeoutCommand); } await this.navigateToUrl(url, true); if (this.activeDialogHandler) { const removeDialogHandlerCommand = new actionCommands.SetNativeDialogHandlerCommand({ dialogHandler: { fn: null } }); await this._internalExecuteCommand(removeDialogHandlerCommand); } } async navigateToUrl(url, forceReload, stateSnapshot) { const navigateCommand = new actionCommands.NavigateToCommand({ url, forceReload, stateSnapshot }); await this._internalExecuteCommand(navigateCommand); } async _getStateSnapshotFromRole(role) { const prevPhase = this.phase; if (role.phase === phase_1.default.initialized && role.initErr instanceof error_list_1.default && role.initErr.hasErrors) role.initErr.adapter = this._createErrorAdapter(role.initErr.items[0]); this.phase = phase_2.default.inRoleInitializer; if (role.phase === phase_1.default.uninitialized) await role.initialize(this); else if (role.phase === phase_1.default.pendingInitialization) await (0, promisify_event_1.default)(role, 'initialized'); if (role.initErr) throw role.initErr; this.phase = prevPhase; return role.stateSnapshot; } async _useRole(role, callsite) { if (this.phase === phase_2.default.inRoleInitializer) throw new test_run_1.RoleSwitchInRoleInitializerError(callsite); const bookmark = new TestRunBookmark(this, role); await bookmark.init(); if (this.currentRoleId) this.usedRoleStates[this.currentRoleId] = await this.getStateSnapshot(); const stateSnapshot = this.usedRoleStates[role.id] || await this._getStateSnapshotFromRole(role); await this._roleProvider.useStateSnapshot(stateSnapshot); this.currentRoleId = role.id; await bookmark.restore(callsite, stateSnapshot); } async getCurrentUrl() { const builder = new ClientFunctionBuilder(() => { return window.location.href; // eslint-disable-line no-undef }, { boundTestRun: this }); const getLocation = builder.getFunction(); return await getLocation(); } async _switchToWindowByPredicate(command) { const currentWindows = await this._internalExecuteCommand(new actions_1.GetCurrentWindowsCommand({}, this)); const windows = await (0, async_filter_1.default)(currentWindows, async (wnd) => { try { const predicateData = { url: new url_1.URL(wnd.url), title: wnd.title, }; return command.checkWindow(predicateData); } catch (e) { throw new test_run_1.SwitchToWindowPredicateError(e.message); } }); if (!windows.length) throw new test_run_1.WindowNotFoundError(); if (windows.length > 1) this.warningLog.addWarning({ message: warning_message_1.default.multipleWindowsFoundByPredicate, actionId: command.actionId }); await this._internalExecuteCommand(new actions_1.SwitchToWindowCommand({ windowId: windows[0].id }, this)); } _disconnect(err) { this.disconnected = true; if (this.currentDriverTask) this._rejectCurrentDriverTask(err); this.emit('disconnected', err); test_run_tracker_1.default.removeActiveTestRun(this.session.id); } _handleFileDownloadingInNewWindowRequest() { if (this.attachmentDownloadingHandled) { this.attachmentDownloadingHandled = false; return this._sendCloseChildWindowOnFileDownloadingCommand(); } return null; } async emitActionEvent(eventName, args) { // @ts-ignore if (!this.preventEmitActionEvents) await this.emit(eventName, args); } static isMultipleWindowsAllowed(testRun) { const { disableMultipleWindows, test } = testRun; return !disableMultipleWindows && !test.isLegacy && !!testRun.activeWindowId; } async initialize() { if (!this.test.skip) { await this._clearCookiesAndStorages(); await this._initRequestHooks(); } } async _clearCookiesAndStorages() { if (this.disablePageReloads) return; await this.cookieProvider.initialize(); await this._storagesProvider.initialize(); } get activeWindowId() { return this.browserConnection.activeWindowId; } // NOTE: this function is time-critical and must return ASAP to avoid client disconnection async [client_messages_1.default.ready](msg) { if (msg.status.isObservingFileDownloadingInNewWindow) return this._handleFileDownloadingInNewWindowRequest(); this.debugLog.driverMessage(msg); if (this.disconnected) return Promise.reject(new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.testRunRequestInDisconnectedBrowser, this.browserConnection.browserInfo.alias)); this.emit('connected'); this._clearPendingRequest(); // NOTE: the driver sends the status for the second time if it didn't get a response at the // first try. This is possible when the page was unloaded after the driver sent the status. if (msg.status.id === this.lastDriverStatusId) return this.lastDriverStatusResponse; this.lastDriverStatusId = msg.status.id; this.lastDriverStatusResponse = await this._handleDriverRequest(msg.status); if (this.lastDriverStatusResponse || msg.status.isPendingWindowSwitching) return this.lastDriverStatusResponse; // NOTE: we send an empty response after the MAX_RESPONSE_DELAY timeout is exceeded to keep connection // with the client and prevent the response timeout exception on the client side const responseTimeout = setTimeout(() => this._resolvePendingRequest(null), MAX_RESPONSE_DELAY); return new Promise((resolve, reject) => { this.pendingRequest = { resolve, reject, responseTimeout }; }); } async [client_messages_1.default.readyForBrowserManipulation](msg) { this.debugLog.driverMessage(msg); let result = null; let error = null; try { result = await this.browserManipulationQueue.executePendingManipulation(msg, this._messageBus); } catch (err) { if (err