1131 lines
197 KiB
JavaScript
1131 lines
197 KiB
JavaScript
|
"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 get_url_1 = __importDefault(require("../custom-client-scripts/get-url"));
|
||
|
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 hook_method_names_1 = __importDefault(require("../api/request-hooks/hook-method-names"));
|
||
|
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 observation_1 = require("./commands/observation");
|
||
|
const marker_1 = require("../services/serialization/replicator/transforms/re-executable-promise-transform/marker");
|
||
|
const re_executable_promise_1 = __importDefault(require("../utils/re-executable-promise"));
|
||
|
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 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 observationCommands = lazyRequire('./commands/observation');
|
||
|
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';
|
||
|
const COMPILER_SERVICE_EVENTS = [
|
||
|
'setMock',
|
||
|
'setConfigureResponseEventOptions',
|
||
|
'setHeaderOnConfigureResponseEvent',
|
||
|
'removeHeaderOnConfigureResponseEvent',
|
||
|
];
|
||
|
class TestRun extends async_event_emitter_1.default {
|
||
|
constructor({ test, browserConnection, screenshotCapturer, globalWarningLog, opts, compilerService, messageBus, startRunExecutionTime }) {
|
||
|
super();
|
||
|
this._clientEnvironmentPrepared = false;
|
||
|
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.opts = opts;
|
||
|
this.test = test;
|
||
|
this.browserConnection = browserConnection;
|
||
|
this.unstable = false;
|
||
|
this.browser = (0, get_browser_1.default)(browserConnection);
|
||
|
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.disableMultipleWindows = opts.disableMultipleWindows;
|
||
|
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);
|
||
|
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.compilerService = compilerService;
|
||
|
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.opts.experimentalProxyless);
|
||
|
this._storagesProvider = factory_2.StoragesProviderFactory.create(this, this.opts.experimentalProxyless);
|
||
|
this._addInjectables();
|
||
|
}
|
||
|
_getRequestHookEventProvider() {
|
||
|
if (!this.opts.experimentalProxyless)
|
||
|
return this.session.requestHookEventProvider;
|
||
|
return this._proxylessRequestPipeline.requestHookEventProvider;
|
||
|
}
|
||
|
saveStoragesSnapshot(storageSnapshot) {
|
||
|
if (this.opts.experimentalProxyless)
|
||
|
this._proxylessRequestPipeline.restoringStorages = storageSnapshot;
|
||
|
}
|
||
|
get _proxylessRequestPipeline() {
|
||
|
return this._proxyless.requestPipeline;
|
||
|
}
|
||
|
get _proxyless() {
|
||
|
const runtimeInfo = this.browserConnection.provider.plugin.openedBrowsers[this.browserConnection.id];
|
||
|
return runtimeInfo.proxyless;
|
||
|
}
|
||
|
_getRoleProvider() {
|
||
|
if (this.opts.experimentalProxyless)
|
||
|
return new role_provider_1.ProxylessRoleProvider(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;
|
||
|
}
|
||
|
_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();
|
||
|
this.injectable.scripts.push(...INJECTABLES.SCRIPTS);
|
||
|
this.injectable.userScripts.push(...this.test.clientScripts.map(script => {
|
||
|
return {
|
||
|
url: (0, get_url_1.default)(script),
|
||
|
page: script.page,
|
||
|
};
|
||
|
}));
|
||
|
this.injectable.styles.push(INJECTABLES.TESTCAFE_UI_STYLES);
|
||
|
}
|
||
|
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));
|
||
|
}));
|
||
|
}
|
||
|
async _initRequestHookForCompilerService(hookId, hookClassName, rules) {
|
||
|
const testId = this.test.id;
|
||
|
await Promise.all(rules.map(rule => {
|
||
|
return this._requestHookEventProvider.addRequestEventListeners(rule, {
|
||
|
onRequest: (event) => { var _a; return (_a = this.compilerService) === null || _a === void 0 ? void 0 : _a.onRequestHookEvent({ testId, hookId, name: hook_method_names_1.default.onRequest, eventData: event }); },
|
||
|
onConfigureResponse: (event) => { var _a; return (_a = this.compilerService) === null || _a === void 0 ? void 0 : _a.onRequestHookEvent({ testId, hookId, name: hook_method_names_1.default._onConfigureResponse, eventData: event }); },
|
||
|
onResponse: (event) => { var _a; return (_a = this.compilerService) === null || _a === void 0 ? void 0 : _a.onRequestHookEvent({ testId, hookId, name: hook_method_names_1.default.onResponse, eventData: event }); },
|
||
|
}, err => this._onRequestHookMethodError(err, hookClassName));
|
||
|
}));
|
||
|
}
|
||
|
_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);
|
||
|
}));
|
||
|
}
|
||
|
_subscribeOnCompilerServiceEvents() {
|
||
|
COMPILER_SERVICE_EVENTS.forEach(eventName => {
|
||
|
if (this.compilerService) {
|
||
|
this.compilerService.on(eventName, async (args) => {
|
||
|
// @ts-ignore
|
||
|
await this.session[eventName](...args);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
if (this.compilerService) {
|
||
|
this.compilerService.on('addRequestEventListeners', async ({ hookId, hookClassName, rules }) => {
|
||
|
await this._initRequestHookForCompilerService(hookId, hookClassName, rules);
|
||
|
});
|
||
|
this.compilerService.on('removeRequestEventListeners', async ({ rules }) => {
|
||
|
await this._detachRequestEventListeners(rules);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
async _initRequestHooks() {
|
||
|
if (this.compilerService) {
|
||
|
this._subscribeOnCompilerServiceEvents();
|
||
|
await Promise.all(this.test.requestHooks.map(hook => {
|
||
|
return this._initRequestHookForCompilerService(hook.id, hook._className, hook._requestFilterRules);
|
||
|
}));
|
||
|
}
|
||
|
else
|
||
|
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() {
|
||
|
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),
|
||
|
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),
|
||
|
browserCloseWindowUrl: JSON.stringify(this.browserConnection.closeWindowUrl),
|
||
|
browserOpenFileProtocolRelativeUrl: JSON.stringify(this.browserConnection.openFileProtocolRelativeUrl),
|
||
|
browserDispatchProxylessEventRelativeUrl: JSON.stringify(this.browserConnection.dispatchProxylessEventRelativeUrl),
|
||
|
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()),
|
||
|
proxyless: JSON.stringify(this.opts.experimentalProxyless),
|
||
|
domain: JSON.stringify(this.browserConnection.browserConnectionGateway.proxy.server1Info.domain),
|
||
|
});
|
||
|
}
|
||
|
async getIframePayloadScript() {
|
||
|
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),
|
||
|
proxyless: JSON.stringify(this.opts.experimentalProxyless),
|
||
|
});
|
||
|
}
|
||
|
// 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) {
|
||
|
if (this.compilerService) {
|
||
|
const warnings = await this.compilerService.getWarningMessages({ testRunId: id });
|
||
|
warnings.forEach(warning => {
|
||
|
this.warningLog.addWarning(warning);
|
||
|
});
|
||
|
await this.compilerService.removeTestRunFromState({ testRunId: 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, 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, error) {
|
||
|
if (this.debugLogger)
|
||
|
this.debugLogger.showBreakpoint(this.session.id, this.browserConnection.userAgent, callsite, error);
|
||
|
this.debugging = await this._internalExecuteCommand(new serviceCommands.SetBreakpointCommand(!!error, !!this.compilerService), callsite);
|
||
|
}
|
||
|
_removeAllNonServiceTasks() {
|
||
|
this.driverTaskQueue = this.driverTaskQueue.filter(driverTask => (0, utils_2.isServiceCommand)(driverTask.command));
|
||
|
this.browserManipulationQueue.removeAllNonServiceManipulations();
|
||
|
}
|
||
|
_handleDebugState(driverStatus) {
|
||
|
if (driverStatus.debug)
|
||
|
this.emit(driverStatus.debug);
|
||
|
}
|
||
|
// Current driver task
|
||
|
get currentDriverTask() {
|
||
|
return this.driverTaskQueue[0];
|
||
|
}
|
||
|
_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 observationCommands.ExecuteSelectorCommand ||
|
||
|
currentCommand instanceof observation_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);
|
||
|
this._handleDebugState(driverStatus);
|
||
|
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}`;
|
||
|
if (this.compilerService) {
|
||
|
return this.compilerService.executeJsExpression({
|
||
|
expression,
|
||
|
testRunId: this.id,
|
||
|
options: { skipVisibilityCheck: false },
|
||
|
});
|
||
|
}
|
||
|
return executeJsExpression(expression, this, { skipVisibilityCheck: false });
|
||
|
}
|
||
|
async _executeAsyncJsExpression(command, callsite) {
|
||
|
if (this.compilerService) {
|
||
|
this.asyncJsExpressionCallsites.clear();
|
||
|
return this.compilerService.executeAsyncJsExpression({
|
||
|
expression: command.expression,
|
||
|
testRunId: this.id,
|
||
|
callsite,
|
||
|
});
|
||
|
}
|
||
|
return executeAsyncJsExpression(command.expression, this, callsite);
|
||
|
}
|
||
|
_redirectReExecutablePromiseExecutionToCompilerService(command) {
|
||
|
if (!this.compilerService)
|
||
|
return;
|
||
|
const self = this;
|
||
|
command.actual = re_executable_promise_1.default.fromFn(async () => {
|
||
|
var _a;
|
||
|
return (_a = self.compilerService) === null || _a === void 0 ? void 0 : _a.getAssertionActualValue({
|
||
|
testRunId: self.id,
|
||
|
commandId: command.id,
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
_redirectAssertionFnExecutionToCompilerService(executor) {
|
||
|
executor.fn = () => {
|
||
|
var _a;
|
||
|
return (_a = this.compilerService) === null || _a === void 0 ? void 0 : _a.executeAssertionFn({
|
||
|
testRunId: this.id,
|
||
|
commandId: executor.command.id,
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
async _executeAssertion(command, callsite) {
|
||
|
if (command.actual === Symbol.for(marker_1.RE_EXECUTABLE_PROMISE_MARKER_DESCRIPTION))
|
||
|
this._redirectReExecutablePromiseExecutionToCompilerService(command);
|
||
|
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)));
|
||
|
executor.once('non-serializable-actual-value', this._redirectAssertionFnExecutionToCompilerService);
|
||
|
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;
|
||
|
else if (command.type === type_1.default.disableDebug) {
|
||
|
this.debugLogger.hideBreakpoint(this.session.id);
|
||
|
this.debugging = false;
|
||
|
}
|
||
|
}
|
||
|
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);
|
||
|
}
|
||
|
else if (command instanceof observation_1.ExecuteClientFunctionCommandBase && !!this.compilerService && !this._clientEnvironmentPrepared) {
|
||
|
this._clientEnvironmentPrepared = true;
|
||
|
await this._internalExecuteCommand(new serviceCommands.PrepareClientEnvironmentInDebugMode(command.esmRuntime));
|
||
|
}
|
||
|
}
|
||
|
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) {
|
||
|
// NOTE: In regular mode, it's possible to debug tests only using TestCafe UI ('Resume' and 'Next step' buttons).
|
||
|
// So, we should warn on trying to debug in headless mode.
|
||
|
// In compiler service mode, we can debug even in headless mode using any debugging tools. So, in this case, the warning is excessive.
|
||
|
const canDebug = !!this.compilerService || !this.browserConnection.isHeadlessBrowser();
|
||
|
if (canDebug)
|
||
|
return await this._enqueueSetBreakpointCommand(callsite, 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.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)));
|
||
|
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 }));
|
||
|
}
|
||
|
_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() {
|
||
|
if (this.compilerService) {
|
||
|
await this.compilerService.setCtx({
|
||
|
testRunId: this.id,
|
||
|
value: Object.create(null),
|
||
|
});
|
||
|
await this.compilerService.setFixtureCtx({
|
||
|
testRunId: this.id,
|
||
|
value: Object.create(null),
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
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,
|
||
|
};
|
||
|
if (this.compilerService) {
|
||
|
const compilerServicePredicateData = Object.assign(predicateData, {
|
||
|
testRunId: this.id,
|
||
|
commandId: command.id,
|
||
|
});
|
||
|
return this.compilerService.checkWindow(compilerServicePredicateData);
|
||
|
}
|
||
|
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() {
|
||
|
await this._clearCookiesAndStorages();
|
||
|
await this._initRequestHooks();
|
||
|
if (!this.compilerService)
|
||
|
return;
|
||
|
await this.compilerService.initializeTestRunData({
|
||
|
testRunId: this.id,
|
||
|
testId: this.test.id,
|
||
|
browser: this.browser,
|
||
|
activeWindowId: this.activeWindowId,
|
||
|
messageBus: this._messageBus,
|
||
|
});
|
||
|
}
|
||
|
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 instanceof Error) {
|
||
|
error = {
|
||
|
name: err.name,
|
||
|
message: err.message,
|
||
|
stack: err.stack,
|
||
|
isInternalError: true,
|
||
|
};
|
||
|
}
|
||
|
else
|
||
|
error = err;
|
||
|
}
|
||
|
return { result, error };
|
||
|
}
|
||
|
async [client_messages_1.default.waitForFileDownload](msg) {
|
||
|
this.debugLog.driverMessage(msg);
|
||
|
return new Promise(resolve => {
|
||
|
if (this.fileDownloadingHandled) {
|
||
|
this.fileDownloadingHandled = false;
|
||
|
resolve(true);
|
||
|
}
|
||
|
else
|
||
|
this.resolveWaitForFileDownloadingPromise = resolve;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
exports.default = TestRun;
|
||
|
module.exports = exports.default;
|
||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVzdC1ydW4vaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLG1DQUlnQjtBQUVoQixtQ0FBZ0M7QUFDaEMsMkRBQXNEO0FBQ3RELHNFQUE2QztBQUM3Qyx3REFBZ0M7QUFDaEMsdUZBQTZEO0FBQzdELDREQUEwQztBQUMxQyxpR0FBb0Y7QUFDcEYsc0VBQXFEO0FBQ3JELCtDQUFpRDtBQUVqRCxrREFVNkI7QUFFN0Isd0VBQWdEO0FBQ2hELDJEQUEyQztBQUMzQywyREFBbUM7QUFDbkMsbUZBQXlEO0FBQ3pELG9FQUE0QztBQUM1QywrRUFBcUQ7QUFDckQsMERBQXVDO0FBQ3ZDLDBFQUF5RDtBQUN6RCwwRkFBZ0U7QUFDaEUsK0VBQXNEO0FBQ3RELHVGQUErRDtBQUUvRCw2REFXNkI7QUFFN0IsbUVBQXFEO0FBQ3JELDBEQUF3RTtBQUN4RSwrRUFBd0U7QUFDeEUsNENBQStFO0FBRS9FLDRDQVEwQjtBQUUxQixnREFZNEI7QUFFNUIsMkNBQWtFO0FBQ2xFLDRGQUFpRTtBQUNqRSwrRkFBNEU7QUFDNUUsK0RBQXlGO0FBS3pGLDhFQUFxRDtBQUVyRCw4RkFBb0U7QUFDcEUsOEZBQW9FO0FBTXBFLGdEQUFvRTtBQUtwRSx1R0FBNkU7QUFNN0Usb0RBQW1DO0FBRW5DLHdEQUlnQztBQUVoQyxtSEFBa0o7QUFDbEosMkZBQWlFO0FBQ2pFLGlHQUF1RTtBQUN2RSx1RUFBOEM7QUFDOUMsc0VBQXVEO0FBQ3ZELHlFQUFnRDtBQUdoRCwrRkFBb0U7QUFDcEUsNkJBQTBCO0FBRTFCLDBEQUFtRTtBQUNuRSwrQ0FBMEQ7QUFHMUQsZ0RBQTZEO0FBRTdELG1GQUF5RDtBQUV6RCxtREFJeUI7QUFLekIsTUFBTSxXQUFXLEdBQW1CLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUNwRSxNQUFN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
|