622 lines
106 KiB
JavaScript
622 lines
106 KiB
JavaScript
|
"use strict";
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
const path_1 = require("path");
|
||
|
const debug_1 = __importDefault(require("debug"));
|
||
|
const promisify_event_1 = __importDefault(require("promisify-event"));
|
||
|
const events_1 = require("events");
|
||
|
const config_storage_1 = __importDefault(require("../dashboard/config-storage"));
|
||
|
const lodash_1 = require("lodash");
|
||
|
const bootstrapper_1 = __importDefault(require("./bootstrapper"));
|
||
|
const reporter_1 = __importDefault(require("../reporter"));
|
||
|
const task_1 = __importDefault(require("./task"));
|
||
|
const debug_logger_1 = __importDefault(require("../notifications/debug-logger"));
|
||
|
const runtime_1 = require("../errors/runtime");
|
||
|
const types_1 = require("../errors/types");
|
||
|
const type_assertions_1 = require("../errors/runtime/type-assertions");
|
||
|
const utils_1 = require("../errors/test-run/utils");
|
||
|
const detect_ffmpeg_1 = __importDefault(require("../utils/detect-ffmpeg"));
|
||
|
const check_file_path_1 = __importDefault(require("../utils/check-file-path"));
|
||
|
const handle_errors_1 = require("../utils/handle-errors");
|
||
|
const option_names_1 = __importDefault(require("../configuration/option-names"));
|
||
|
const flag_list_1 = __importDefault(require("../utils/flag-list"));
|
||
|
const prepare_reporters_1 = __importDefault(require("../utils/prepare-reporters"));
|
||
|
const load_1 = __importDefault(require("../custom-client-scripts/load"));
|
||
|
const utils_2 = require("../custom-client-scripts/utils");
|
||
|
const reporter_stream_controller_1 = __importDefault(require("./reporter-stream-controller"));
|
||
|
const customizable_compilers_1 = __importDefault(require("../configuration/customizable-compilers"));
|
||
|
const string_1 = require("../utils/string");
|
||
|
const is_localhost_1 = __importDefault(require("../utils/is-localhost"));
|
||
|
const warning_log_1 = __importDefault(require("../notifications/warning-log"));
|
||
|
const authentication_helper_1 = __importDefault(require("../cli/authentication-helper"));
|
||
|
const testcafe_browser_tools_1 = require("testcafe-browser-tools");
|
||
|
const is_ci_1 = __importDefault(require("is-ci"));
|
||
|
const remote_1 = __importDefault(require("../browser/provider/built-in/remote"));
|
||
|
const connection_1 = __importDefault(require("../browser/connection"));
|
||
|
const os_family_1 = __importDefault(require("os-family"));
|
||
|
const detect_display_1 = __importDefault(require("../utils/detect-display"));
|
||
|
const quarantine_1 = require("../utils/get-options/quarantine");
|
||
|
const log_entry_1 = __importDefault(require("../utils/log-entry"));
|
||
|
const message_bus_1 = __importDefault(require("../utils/message-bus"));
|
||
|
const get_env_options_1 = __importDefault(require("../dashboard/get-env-options"));
|
||
|
const skip_js_errors_1 = require("../utils/get-options/skip-js-errors");
|
||
|
const DEBUG_LOGGER = (0, debug_1.default)('testcafe:runner');
|
||
|
const DASHBOARD_REPORTER_NAME = 'dashboard';
|
||
|
class Runner extends events_1.EventEmitter {
|
||
|
constructor({ proxy, browserConnectionGateway, configuration, compilerService }) {
|
||
|
super();
|
||
|
this._messageBus = new message_bus_1.default();
|
||
|
this.proxy = proxy;
|
||
|
this.bootstrapper = this._createBootstrapper(browserConnectionGateway, compilerService, this._messageBus, configuration);
|
||
|
this.pendingTaskPromises = [];
|
||
|
this.configuration = configuration;
|
||
|
this.isCli = configuration._options && configuration._options.isCli;
|
||
|
this.warningLog = new warning_log_1.default(null, warning_log_1.default.createAddWarningCallback(this._messageBus));
|
||
|
this.compilerService = compilerService;
|
||
|
this._options = {};
|
||
|
this._hasTaskErrors = false;
|
||
|
this.apiMethodWasCalled = new flag_list_1.default([
|
||
|
option_names_1.default.src,
|
||
|
option_names_1.default.browsers,
|
||
|
option_names_1.default.reporter,
|
||
|
option_names_1.default.clientScripts,
|
||
|
]);
|
||
|
}
|
||
|
_createBootstrapper(browserConnectionGateway, compilerService, messageBus, configuration) {
|
||
|
return new bootstrapper_1.default({ browserConnectionGateway, compilerService, messageBus, configuration });
|
||
|
}
|
||
|
_disposeBrowserSet(browserSet) {
|
||
|
return browserSet.dispose().catch(e => DEBUG_LOGGER(e));
|
||
|
}
|
||
|
_disposeReporters(reporters) {
|
||
|
return Promise.all(reporters.map(reporter => reporter.dispose().catch(e => DEBUG_LOGGER(e))));
|
||
|
}
|
||
|
_disposeTestedApp(testedApp) {
|
||
|
return testedApp ? testedApp.kill().catch(e => DEBUG_LOGGER(e)) : Promise.resolve();
|
||
|
}
|
||
|
async _disposeTaskAndRelatedAssets(task, browserSet, reporters, testedApp, runnableConfigurationId) {
|
||
|
task.abort();
|
||
|
task.unRegisterClientScriptRouting();
|
||
|
task.clearListeners();
|
||
|
this._messageBus.abort();
|
||
|
await this._finalizeCompilerServiceState(task, runnableConfigurationId);
|
||
|
await this._disposeAssets(browserSet, reporters, testedApp);
|
||
|
}
|
||
|
_disposeAssets(browserSet, reporters, testedApp) {
|
||
|
return Promise.all([
|
||
|
this._disposeBrowserSet(browserSet),
|
||
|
this._disposeReporters(reporters),
|
||
|
this._disposeTestedApp(testedApp),
|
||
|
]);
|
||
|
}
|
||
|
_prepareArrayParameter(array) {
|
||
|
array = (0, lodash_1.flattenDeep)(array);
|
||
|
if (this.isCli)
|
||
|
return array.length === 0 ? void 0 : array;
|
||
|
return array;
|
||
|
}
|
||
|
_createCancelablePromise(taskPromise) {
|
||
|
const promise = taskPromise.then(({ completionPromise }) => completionPromise);
|
||
|
const removeFromPending = () => (0, lodash_1.pull)(this.pendingTaskPromises, promise);
|
||
|
promise
|
||
|
.then(removeFromPending)
|
||
|
.catch(removeFromPending);
|
||
|
promise.cancel = () => taskPromise
|
||
|
.then(({ cancelTask }) => cancelTask())
|
||
|
.then(removeFromPending);
|
||
|
this.pendingTaskPromises.push(promise);
|
||
|
return promise;
|
||
|
}
|
||
|
async _finalizeCompilerServiceState(task, runnableConfigurationId) {
|
||
|
var _a;
|
||
|
if (!this.compilerService)
|
||
|
return;
|
||
|
await ((_a = this.compilerService) === null || _a === void 0 ? void 0 : _a.removeUnitsFromState({ runnableConfigurationId }));
|
||
|
// NOTE: In some cases (browser restart, stop task on first fail, etc.),
|
||
|
// the fixture contexts may not be deleted.
|
||
|
// We remove all fixture context at the end of test execution to clean forgotten contexts.
|
||
|
const fixtureIds = (0, lodash_1.uniq)(task.tests.map(test => test.fixture.id));
|
||
|
await this.compilerService.removeFixtureCtxsFromState({ fixtureIds });
|
||
|
}
|
||
|
// Run task
|
||
|
_getFailedTestCount(task, reporter) {
|
||
|
let failedTestCount = reporter.taskInfo.testCount - reporter.taskInfo.passed;
|
||
|
if (task.opts.stopOnFirstFail && !!failedTestCount)
|
||
|
failedTestCount = 1;
|
||
|
return failedTestCount;
|
||
|
}
|
||
|
async _getTaskResult(task, browserSet, reporters, testedApp, runnableConfigurationId) {
|
||
|
if (!task.opts.live) {
|
||
|
task.on('browser-job-done', async (job) => {
|
||
|
await Promise.all(job.browserConnections.map(async (bc) => {
|
||
|
await browserSet.releaseConnection(bc);
|
||
|
}));
|
||
|
});
|
||
|
}
|
||
|
this._messageBus.clearListeners('error');
|
||
|
const browserSetErrorPromise = (0, promisify_event_1.default)(browserSet, 'error');
|
||
|
const taskErrorPromise = (0, promisify_event_1.default)(task, 'error');
|
||
|
const messageBusErrorPromise = (0, promisify_event_1.default)(this._messageBus, 'error');
|
||
|
const streamController = new reporter_stream_controller_1.default(this._messageBus, reporters);
|
||
|
const taskDonePromise = this._messageBus.once('done')
|
||
|
.then(() => browserSetErrorPromise.cancel())
|
||
|
.then(() => {
|
||
|
return Promise.all(reporters.map(reporter => reporter.taskInfo.pendingTaskDonePromise));
|
||
|
});
|
||
|
const promises = [
|
||
|
taskDonePromise,
|
||
|
browserSetErrorPromise,
|
||
|
taskErrorPromise,
|
||
|
messageBusErrorPromise,
|
||
|
];
|
||
|
if (testedApp)
|
||
|
promises.push(testedApp.errorPromise);
|
||
|
try {
|
||
|
await Promise.race(promises);
|
||
|
}
|
||
|
catch (err) {
|
||
|
await this._disposeTaskAndRelatedAssets(task, browserSet, reporters, testedApp, runnableConfigurationId);
|
||
|
throw err;
|
||
|
}
|
||
|
await this._disposeAssets(browserSet, reporters, testedApp);
|
||
|
await this._finalizeCompilerServiceState(task, runnableConfigurationId);
|
||
|
if (streamController.multipleStreamError)
|
||
|
throw streamController.multipleStreamError;
|
||
|
return this._getFailedTestCount(task, reporters[0]);
|
||
|
}
|
||
|
_createTask(tests, browserConnectionGroups, proxy, opts, warningLog) {
|
||
|
return new task_1.default({
|
||
|
tests,
|
||
|
browserConnectionGroups,
|
||
|
proxy,
|
||
|
opts,
|
||
|
runnerWarningLog: warningLog,
|
||
|
compilerService: this.compilerService,
|
||
|
messageBus: this._messageBus,
|
||
|
});
|
||
|
}
|
||
|
_runTask({ reporters, browserSet, tests, testedApp, options, runnableConfigurationId }) {
|
||
|
const task = this._createTask(tests, browserSet.browserConnectionGroups, this.proxy, options, this.warningLog);
|
||
|
const completionPromise = this._getTaskResult(task, browserSet, reporters, testedApp, runnableConfigurationId);
|
||
|
let completed = false;
|
||
|
this._messageBus.on('start', handle_errors_1.startHandlingTestErrors);
|
||
|
if (!this.configuration.getOption(option_names_1.default.skipUncaughtErrors)) {
|
||
|
this._messageBus.on('test-run-start', handle_errors_1.addRunningTest);
|
||
|
this._messageBus.on('test-run-done', ({ errs }) => {
|
||
|
if (errs.length)
|
||
|
this._hasTaskErrors = true;
|
||
|
(0, handle_errors_1.removeRunningTest)();
|
||
|
});
|
||
|
}
|
||
|
this._messageBus.on('done', handle_errors_1.stopHandlingTestErrors);
|
||
|
task.on('error', handle_errors_1.stopHandlingTestErrors);
|
||
|
const onTaskCompleted = () => {
|
||
|
task.unRegisterClientScriptRouting();
|
||
|
completed = true;
|
||
|
};
|
||
|
completionPromise
|
||
|
.then(onTaskCompleted)
|
||
|
.catch(onTaskCompleted);
|
||
|
const cancelTask = async () => {
|
||
|
if (!completed)
|
||
|
await this._disposeTaskAndRelatedAssets(task, browserSet, reporters, testedApp, runnableConfigurationId);
|
||
|
};
|
||
|
return { completionPromise, cancelTask };
|
||
|
}
|
||
|
_registerAssets(assets) {
|
||
|
assets.forEach(asset => this.proxy.GET(asset.path, asset.info));
|
||
|
}
|
||
|
_validateDebugLogger() {
|
||
|
const debugLogger = this.configuration.getOption(option_names_1.default.debugLogger);
|
||
|
const debugLoggerDefinedCorrectly = debugLogger === null || !!debugLogger &&
|
||
|
['showBreakpoint', 'hideBreakpoint'].every(method => method in debugLogger && (0, lodash_1.isFunction)(debugLogger[method]));
|
||
|
if (!debugLoggerDefinedCorrectly) {
|
||
|
this.configuration.mergeOptions({
|
||
|
[option_names_1.default.debugLogger]: debug_logger_1.default,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
_validateSpeedOption() {
|
||
|
const speed = this.configuration.getOption(option_names_1.default.speed);
|
||
|
if (speed === void 0)
|
||
|
return;
|
||
|
if (typeof speed !== 'number' || isNaN(speed) || speed < 0.01 || speed > 1)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.invalidSpeedValue);
|
||
|
}
|
||
|
_validateConcurrencyOption() {
|
||
|
const concurrency = this.configuration.getOption(option_names_1.default.concurrency);
|
||
|
if (concurrency === void 0)
|
||
|
return;
|
||
|
if (typeof concurrency !== 'number' || isNaN(concurrency) || concurrency < 1)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.invalidConcurrencyFactor);
|
||
|
if (concurrency > 1 && this.bootstrapper.browsers.some(browser => {
|
||
|
return browser instanceof connection_1.default
|
||
|
? browser.browserInfo.browserOption.cdpPort
|
||
|
: browser.browserOption.cdpPort;
|
||
|
}))
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.cannotSetConcurrencyWithCDPPort);
|
||
|
}
|
||
|
_validateSkipJsErrorsOption() {
|
||
|
const skipJsErrorsOptions = this.configuration.getOption(option_names_1.default.skipJsErrors);
|
||
|
if (!skipJsErrorsOptions)
|
||
|
return;
|
||
|
(0, skip_js_errors_1.validateSkipJsErrorsOptionValue)(skipJsErrorsOptions, runtime_1.GeneralError);
|
||
|
}
|
||
|
_validateCustomActionsOption() {
|
||
|
const customActions = this.configuration.getOption(option_names_1.default.customActions);
|
||
|
if (!customActions)
|
||
|
return;
|
||
|
if (typeof customActions !== 'object')
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.invalidCustomActionsOptionType);
|
||
|
for (const name in customActions) {
|
||
|
if (typeof customActions[name] !== 'function')
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.invalidCustomActionType, name, typeof customActions[name]);
|
||
|
}
|
||
|
}
|
||
|
async _validateBrowsers() {
|
||
|
const browsers = this.configuration.getOption(option_names_1.default.browsers);
|
||
|
if (!browsers || Array.isArray(browsers) && !browsers.length)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.browserNotSet);
|
||
|
if (os_family_1.default.mac)
|
||
|
await this._checkRequiredPermissions(browsers);
|
||
|
if (os_family_1.default.linux && !(0, detect_display_1.default)())
|
||
|
await this._checkThatTestsCanRunWithoutDisplay(browsers);
|
||
|
}
|
||
|
_validateRequestTimeoutOption(optionName) {
|
||
|
const requestTimeout = this.configuration.getOption(optionName);
|
||
|
if (requestTimeout === void 0)
|
||
|
return;
|
||
|
(0, type_assertions_1.assertType)(type_assertions_1.is.nonNegativeNumber, null, `"${optionName}" option`, requestTimeout);
|
||
|
}
|
||
|
_validateProxyBypassOption() {
|
||
|
let proxyBypass = this.configuration.getOption(option_names_1.default.proxyBypass);
|
||
|
if (proxyBypass === void 0)
|
||
|
return;
|
||
|
(0, type_assertions_1.assertType)([type_assertions_1.is.string, type_assertions_1.is.array], null, 'The "proxyBypass" argument', proxyBypass);
|
||
|
if (typeof proxyBypass === 'string')
|
||
|
proxyBypass = [proxyBypass];
|
||
|
proxyBypass = proxyBypass.reduce((arr, rules) => {
|
||
|
(0, type_assertions_1.assertType)(type_assertions_1.is.string, null, 'The "proxyBypass" argument', rules);
|
||
|
return arr.concat(rules.split(','));
|
||
|
}, []);
|
||
|
this.configuration.mergeOptions({ proxyBypass });
|
||
|
}
|
||
|
_getScreenshotOptions() {
|
||
|
let { path, pathPattern, takeOnFails } = this.configuration.getOption(option_names_1.default.screenshots) || {};
|
||
|
if (!path)
|
||
|
path = this.configuration.getOption(option_names_1.default.screenshotPath);
|
||
|
if (!pathPattern)
|
||
|
pathPattern = this.configuration.getOption(option_names_1.default.screenshotPathPattern);
|
||
|
if (!takeOnFails)
|
||
|
takeOnFails = false;
|
||
|
return { path, pathPattern, takeOnFails };
|
||
|
}
|
||
|
_validateScreenshotOptions() {
|
||
|
const { path, pathPattern } = this._getScreenshotOptions();
|
||
|
const disableScreenshots = this.configuration.getOption(option_names_1.default.disableScreenshots) || !path;
|
||
|
this.configuration.mergeOptions({ [option_names_1.default.disableScreenshots]: disableScreenshots });
|
||
|
if (disableScreenshots)
|
||
|
return;
|
||
|
if (path) {
|
||
|
this._validateScreenshotPath(path, 'screenshots base directory path');
|
||
|
this.configuration.mergeOptions({ [option_names_1.default.screenshots]: { path: (0, path_1.resolve)(path) } });
|
||
|
}
|
||
|
if (pathPattern) {
|
||
|
this._validateScreenshotPath(pathPattern, 'screenshots path pattern');
|
||
|
this.configuration.mergeOptions({ [option_names_1.default.screenshots]: { pathPattern } });
|
||
|
}
|
||
|
}
|
||
|
async _validateVideoOptions() {
|
||
|
const videoPath = this.configuration.getOption(option_names_1.default.videoPath);
|
||
|
const videoEncodingOptions = this.configuration.getOption(option_names_1.default.videoEncodingOptions);
|
||
|
let videoOptions = this.configuration.getOption(option_names_1.default.videoOptions);
|
||
|
if (!videoPath) {
|
||
|
if (videoOptions || videoEncodingOptions)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.cannotSetVideoOptionsWithoutBaseVideoPathSpecified);
|
||
|
return;
|
||
|
}
|
||
|
this.configuration.mergeOptions({ [option_names_1.default.videoPath]: (0, path_1.resolve)(videoPath) });
|
||
|
if (!videoOptions) {
|
||
|
videoOptions = {};
|
||
|
this.configuration.mergeOptions({ [option_names_1.default.videoOptions]: videoOptions });
|
||
|
}
|
||
|
if (videoOptions.ffmpegPath)
|
||
|
videoOptions.ffmpegPath = (0, path_1.resolve)(videoOptions.ffmpegPath);
|
||
|
else
|
||
|
videoOptions.ffmpegPath = await (0, detect_ffmpeg_1.default)();
|
||
|
if (!videoOptions.ffmpegPath)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.cannotFindFFMPEG);
|
||
|
}
|
||
|
_validateCompilerOptions() {
|
||
|
const compilerOptions = this.configuration.getOption(option_names_1.default.compilerOptions);
|
||
|
if (!compilerOptions)
|
||
|
return;
|
||
|
const specifiedCompilers = Object.keys(compilerOptions);
|
||
|
const customizedCompilers = Object.keys(customizable_compilers_1.default);
|
||
|
const wrongCompilers = specifiedCompilers.filter(compiler => !customizedCompilers.includes(compiler));
|
||
|
if (!wrongCompilers.length)
|
||
|
return;
|
||
|
const compilerListStr = (0, string_1.getConcatenatedValuesString)(wrongCompilers, void 0, "'");
|
||
|
const pluralSuffix = (0, string_1.getPluralSuffix)(wrongCompilers);
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.cannotCustomizeSpecifiedCompilers, compilerListStr, pluralSuffix);
|
||
|
}
|
||
|
_validateRetryTestPagesOption() {
|
||
|
const retryTestPagesOption = this.configuration.getOption(option_names_1.default.retryTestPages);
|
||
|
if (!retryTestPagesOption)
|
||
|
return;
|
||
|
const ssl = this.configuration.getOption(option_names_1.default.ssl);
|
||
|
if (ssl)
|
||
|
return;
|
||
|
const hostname = this.configuration.getOption(option_names_1.default.hostname);
|
||
|
if ((0, is_localhost_1.default)(hostname))
|
||
|
return;
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.cannotEnableRetryTestPagesOption);
|
||
|
}
|
||
|
_validateQuarantineOptions() {
|
||
|
const quarantineMode = this.configuration.getOption(option_names_1.default.quarantineMode);
|
||
|
if (typeof quarantineMode === 'object')
|
||
|
(0, quarantine_1.validateQuarantineOptions)(quarantineMode);
|
||
|
}
|
||
|
async _validateRunOptions() {
|
||
|
this._validateDebugLogger();
|
||
|
this._validateScreenshotOptions();
|
||
|
await this._validateVideoOptions();
|
||
|
this._validateSpeedOption();
|
||
|
this._validateProxyBypassOption();
|
||
|
this._validateCompilerOptions();
|
||
|
this._validateRetryTestPagesOption();
|
||
|
this._validateRequestTimeoutOption(option_names_1.default.pageRequestTimeout);
|
||
|
this._validateRequestTimeoutOption(option_names_1.default.ajaxRequestTimeout);
|
||
|
this._validateQuarantineOptions();
|
||
|
this._validateConcurrencyOption();
|
||
|
this._validateSkipJsErrorsOption();
|
||
|
this._validateCustomActionsOption();
|
||
|
await this._validateBrowsers();
|
||
|
}
|
||
|
_createRunnableConfiguration() {
|
||
|
return this.bootstrapper
|
||
|
.createRunnableConfiguration()
|
||
|
.then(runnableConfiguration => {
|
||
|
this.emit('done-bootstrapping');
|
||
|
return runnableConfiguration;
|
||
|
});
|
||
|
}
|
||
|
_validateScreenshotPath(screenshotPath, pathType) {
|
||
|
const forbiddenCharsList = (0, check_file_path_1.default)(screenshotPath);
|
||
|
if (forbiddenCharsList.length)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.forbiddenCharatersInScreenshotPath, screenshotPath, pathType, (0, utils_1.renderForbiddenCharsList)(forbiddenCharsList));
|
||
|
}
|
||
|
_setBootstrapperOptions() {
|
||
|
this.configuration.prepare();
|
||
|
this.configuration.notifyAboutOverriddenOptions(this.warningLog);
|
||
|
this.configuration.notifyAboutDeprecatedOptions(this.warningLog);
|
||
|
this.bootstrapper.sources = this.configuration.getOption(option_names_1.default.src) || this.bootstrapper.sources;
|
||
|
this.bootstrapper.browsers = this.configuration.getOption(option_names_1.default.browsers) || this.bootstrapper.browsers;
|
||
|
this.bootstrapper.concurrency = this.configuration.getOption(option_names_1.default.concurrency);
|
||
|
this.bootstrapper.appCommand = this.configuration.getOption(option_names_1.default.appCommand) || this.bootstrapper.appCommand;
|
||
|
this.bootstrapper.appInitDelay = this.configuration.getOption(option_names_1.default.appInitDelay);
|
||
|
this.bootstrapper.filter = this.configuration.getOption(option_names_1.default.filter) || this.bootstrapper.filter;
|
||
|
this.bootstrapper.reporters = this.configuration.getOption(option_names_1.default.reporter) || this.bootstrapper.reporters;
|
||
|
this.bootstrapper.tsConfigPath = this.configuration.getOption(option_names_1.default.tsConfigPath);
|
||
|
this.bootstrapper.clientScripts = this.configuration.getOption(option_names_1.default.clientScripts) || this.bootstrapper.clientScripts;
|
||
|
this.bootstrapper.disableMultipleWindows = this.configuration.getOption(option_names_1.default.disableMultipleWindows);
|
||
|
this.bootstrapper.proxyless = this.configuration.getOption(option_names_1.default.experimentalProxyless);
|
||
|
this.bootstrapper.compilerOptions = this.configuration.getOption(option_names_1.default.compilerOptions);
|
||
|
this.bootstrapper.browserInitTimeout = this.configuration.getOption(option_names_1.default.browserInitTimeout);
|
||
|
this.bootstrapper.hooks = this.configuration.getOption(option_names_1.default.hooks);
|
||
|
this.bootstrapper.configuration = this.configuration;
|
||
|
}
|
||
|
async _addDashboardReporterIfNeeded() {
|
||
|
const dashboardOptions = await this._getDashboardOptions();
|
||
|
let reporterOptions = this.configuration.getOption(option_names_1.default.reporter);
|
||
|
// NOTE: we should send reports when sendReport is undefined
|
||
|
// TODO: make this option binary instead of tri-state
|
||
|
if (!dashboardOptions.token || dashboardOptions.sendReport === false)
|
||
|
return;
|
||
|
if (!reporterOptions)
|
||
|
reporterOptions = [];
|
||
|
const dashboardReporter = reporterOptions.find(reporter => reporter.name === DASHBOARD_REPORTER_NAME);
|
||
|
if (!dashboardReporter)
|
||
|
reporterOptions.push({ name: DASHBOARD_REPORTER_NAME, options: dashboardOptions });
|
||
|
else
|
||
|
dashboardReporter.options = dashboardOptions;
|
||
|
this.configuration.mergeOptions({ [option_names_1.default.reporter]: reporterOptions });
|
||
|
}
|
||
|
_turnOnScreenshotsIfNeeded() {
|
||
|
const { takeOnFails } = this._getScreenshotOptions();
|
||
|
const reporterOptions = this.configuration.getOption(option_names_1.default.reporter);
|
||
|
if (!takeOnFails && reporterOptions && (0, lodash_1.castArray)(reporterOptions).some(reporter => reporter.name === DASHBOARD_REPORTER_NAME))
|
||
|
this.configuration.mergeOptions({ [option_names_1.default.screenshots]: { takeOnFails: true, autoTakeOnFails: true } });
|
||
|
}
|
||
|
async _getDashboardOptions() {
|
||
|
const storageOptions = await this._loadDashboardOptionsFromStorage();
|
||
|
const configOptions = this.configuration.getOption(option_names_1.default.dashboard);
|
||
|
const envOptions = (0, get_env_options_1.default)();
|
||
|
return (0, lodash_1.merge)({}, storageOptions, configOptions, envOptions);
|
||
|
}
|
||
|
async _loadDashboardOptionsFromStorage() {
|
||
|
const storage = new config_storage_1.default();
|
||
|
await storage.load();
|
||
|
return storage.options;
|
||
|
}
|
||
|
async _prepareClientScripts(tests, clientScripts) {
|
||
|
return Promise.all(tests.map(async (test) => {
|
||
|
if (test.isLegacy)
|
||
|
return;
|
||
|
let loadedTestClientScripts = await (0, load_1.default)(test.clientScripts, (0, path_1.dirname)(test.testFile.filename));
|
||
|
loadedTestClientScripts = clientScripts.concat(loadedTestClientScripts);
|
||
|
test.clientScripts = (0, utils_2.setUniqueUrls)(loadedTestClientScripts);
|
||
|
}));
|
||
|
}
|
||
|
async _hasLocalBrowsers(browserInfo) {
|
||
|
for (const browser of browserInfo) {
|
||
|
if (browser instanceof connection_1.default)
|
||
|
continue;
|
||
|
if (await browser.provider.isLocalBrowser(void 0, browser.browserName))
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
async _checkRequiredPermissions(browserInfo) {
|
||
|
const hasLocalBrowsers = await this._hasLocalBrowsers(browserInfo);
|
||
|
const { error } = await (0, authentication_helper_1.default)(() => (0, testcafe_browser_tools_1.findWindow)(''), testcafe_browser_tools_1.errors.UnableToAccessScreenRecordingAPIError, {
|
||
|
interactive: hasLocalBrowsers && !is_ci_1.default,
|
||
|
});
|
||
|
if (!error)
|
||
|
return;
|
||
|
if (hasLocalBrowsers)
|
||
|
throw error;
|
||
|
remote_1.default.canDetectLocalBrowsers = false;
|
||
|
}
|
||
|
async _checkThatTestsCanRunWithoutDisplay(browserInfoSource) {
|
||
|
for (let browserInfo of browserInfoSource) {
|
||
|
if (browserInfo instanceof connection_1.default)
|
||
|
browserInfo = browserInfo.browserInfo;
|
||
|
const isLocalBrowser = await browserInfo.provider.isLocalBrowser(void 0, browserInfo.browserName);
|
||
|
const isHeadlessBrowser = await browserInfo.provider.isHeadlessBrowser(void 0, browserInfo.browserName);
|
||
|
if (isLocalBrowser && !isHeadlessBrowser) {
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.cannotRunLocalNonHeadlessBrowserWithoutDisplay, browserInfo.alias);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
async _setConfigurationOptions() {
|
||
|
await this.configuration.asyncMergeOptions(this._options);
|
||
|
}
|
||
|
// API
|
||
|
embeddingOptions(opts) {
|
||
|
const { assets, TestRunCtor } = opts;
|
||
|
this._registerAssets(assets);
|
||
|
this._options.TestRunCtor = TestRunCtor;
|
||
|
return this;
|
||
|
}
|
||
|
src(...sources) {
|
||
|
if (this.apiMethodWasCalled.src)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.multipleAPIMethodCallForbidden, option_names_1.default.src);
|
||
|
this._options[option_names_1.default.src] = this._prepareArrayParameter(sources);
|
||
|
this.apiMethodWasCalled.src = true;
|
||
|
return this;
|
||
|
}
|
||
|
browsers(...browsers) {
|
||
|
if (this.apiMethodWasCalled.browsers)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.multipleAPIMethodCallForbidden, option_names_1.default.browsers);
|
||
|
this._options.browsers = this._prepareArrayParameter(browsers);
|
||
|
this.apiMethodWasCalled.browsers = true;
|
||
|
return this;
|
||
|
}
|
||
|
concurrency(concurrency) {
|
||
|
this._options.concurrency = concurrency;
|
||
|
return this;
|
||
|
}
|
||
|
reporter(name, output) {
|
||
|
if (this.apiMethodWasCalled.reporter)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.multipleAPIMethodCallForbidden, option_names_1.default.reporter);
|
||
|
this._options[option_names_1.default.reporter] = this._prepareArrayParameter((0, prepare_reporters_1.default)(name, output));
|
||
|
this.apiMethodWasCalled.reporter = true;
|
||
|
return this;
|
||
|
}
|
||
|
filter(filter) {
|
||
|
this._options.filter = filter;
|
||
|
return this;
|
||
|
}
|
||
|
useProxy(proxy, proxyBypass) {
|
||
|
this._options.proxy = proxy;
|
||
|
this._options.proxyBypass = proxyBypass;
|
||
|
return this;
|
||
|
}
|
||
|
screenshots(...options) {
|
||
|
let fullPage;
|
||
|
let thumbnails;
|
||
|
let [path, takeOnFails, pathPattern] = options;
|
||
|
if (options.length === 1 && options[0] && typeof options[0] === 'object')
|
||
|
({ path, takeOnFails, pathPattern, fullPage, thumbnails } = options[0]);
|
||
|
this._options.screenshots = { path, takeOnFails, pathPattern, fullPage, thumbnails };
|
||
|
return this;
|
||
|
}
|
||
|
video(path, options, encodingOptions) {
|
||
|
this._options[option_names_1.default.videoPath] = path;
|
||
|
this._options[option_names_1.default.videoOptions] = options;
|
||
|
this._options[option_names_1.default.videoEncodingOptions] = encodingOptions;
|
||
|
return this;
|
||
|
}
|
||
|
startApp(command, initDelay) {
|
||
|
this._options[option_names_1.default.appCommand] = command;
|
||
|
this._options[option_names_1.default.appInitDelay] = initDelay;
|
||
|
return this;
|
||
|
}
|
||
|
tsConfigPath(path) {
|
||
|
this._options[option_names_1.default.tsConfigPath] = path;
|
||
|
return this;
|
||
|
}
|
||
|
clientScripts(...scripts) {
|
||
|
if (this.apiMethodWasCalled.clientScripts)
|
||
|
throw new runtime_1.GeneralError(types_1.RUNTIME_ERRORS.multipleAPIMethodCallForbidden, option_names_1.default.clientScripts);
|
||
|
this._options[option_names_1.default.clientScripts] = this._prepareArrayParameter(scripts);
|
||
|
this.apiMethodWasCalled.clientScripts = true;
|
||
|
return this;
|
||
|
}
|
||
|
compilerOptions(opts) {
|
||
|
this._options[option_names_1.default.compilerOptions] = opts;
|
||
|
return this;
|
||
|
}
|
||
|
run(options = {}) {
|
||
|
let reporters;
|
||
|
this.apiMethodWasCalled.reset();
|
||
|
this._messageBus.clearListeners();
|
||
|
const messageBusErrorPromise = (0, promisify_event_1.default)(this._messageBus, 'error');
|
||
|
this._options = Object.assign(this._options, options);
|
||
|
const runTaskPromise = Promise.resolve()
|
||
|
.then(() => this._setConfigurationOptions())
|
||
|
.then(async () => {
|
||
|
await this._addDashboardReporterIfNeeded();
|
||
|
await this._turnOnScreenshotsIfNeeded();
|
||
|
})
|
||
|
.then(() => reporter_1.default.getReporterPlugins(this.configuration.getOption(option_names_1.default.reporter)))
|
||
|
.then(reporterPlugins => {
|
||
|
reporters = reporterPlugins.map(reporter => new reporter_1.default(reporter.plugin, this._messageBus, reporter.outStream, reporter.name));
|
||
|
return Promise.all(reporters.map(reporter => reporter.init()));
|
||
|
})
|
||
|
.then(() => this._setBootstrapperOptions())
|
||
|
.then(() => {
|
||
|
(0, log_entry_1.default)(DEBUG_LOGGER, this.configuration);
|
||
|
return this._validateRunOptions();
|
||
|
})
|
||
|
.then(() => this._createRunnableConfiguration())
|
||
|
.then(async ({ browserSet, tests, testedApp, commonClientScripts, id }) => {
|
||
|
var _a, _b;
|
||
|
await this._prepareClientScripts(tests, commonClientScripts);
|
||
|
const dashboardReporter = (_a = reporters.find(r => r.plugin.name === 'dashboard')) === null || _a === void 0 ? void 0 : _a.plugin;
|
||
|
const dashboardUrl = (dashboardReporter === null || dashboardReporter === void 0 ? void 0 : dashboardReporter.getReportUrl) ? dashboardReporter.getReportUrl() : '';
|
||
|
const resultOptions = Object.assign(Object.assign({}, this.configuration.getOptions()), { dashboardUrl });
|
||
|
await ((_b = this.bootstrapper.compilerService) === null || _b === void 0 ? void 0 : _b.setOptions({ value: resultOptions }));
|
||
|
return this._runTask({
|
||
|
reporters,
|
||
|
browserSet,
|
||
|
tests,
|
||
|
testedApp,
|
||
|
options: resultOptions,
|
||
|
runnableConfigurationId: id,
|
||
|
});
|
||
|
});
|
||
|
const promises = [
|
||
|
runTaskPromise,
|
||
|
messageBusErrorPromise,
|
||
|
];
|
||
|
return this._createCancelablePromise(Promise.race(promises));
|
||
|
}
|
||
|
async stop() {
|
||
|
// NOTE: When taskPromise is cancelled, it is removed from
|
||
|
// the pendingTaskPromises array, which leads to shifting indexes
|
||
|
// towards the beginning. So, we must copy the array in order to iterate it,
|
||
|
// or we can perform iteration from the end to the beginning.
|
||
|
const cancellationPromises = this.pendingTaskPromises.reduceRight((result, taskPromise) => {
|
||
|
result.push(taskPromise.cancel());
|
||
|
return result;
|
||
|
}, []);
|
||
|
await Promise.all(cancellationPromises);
|
||
|
}
|
||
|
}
|
||
|
exports.default = Runner;
|
||
|
module.exports = exports.default;
|
||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcnVubmVyL2luZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUEsK0JBQXVEO0FBQ3ZELGtEQUEwQjtBQUMxQixzRUFBNkM7QUFDN0MsbUNBQXNDO0FBQ3RDLGlGQUFpRTtBQUVqRSxtQ0FPZ0I7QUFFaEIsa0VBQTBDO0FBQzFDLDJEQUFtQztBQUNuQyxrREFBMEI7QUFDMUIsaUZBQStEO0FBQy9ELCtDQUFpRDtBQUNqRCwyQ0FBaUQ7QUFDakQsdUVBQW1FO0FBQ25FLG9EQUFvRTtBQUNwRSwyRUFBa0Q7QUFDbEQsK0VBQXFEO0FBQ3JELDBEQUtnQztBQUVoQyxpRkFBeUQ7QUFDekQsbUVBQTBDO0FBQzFDLG1GQUEwRDtBQUMxRCx5RUFBOEQ7QUFDOUQsMERBQStEO0FBQy9ELDhGQUFvRTtBQUNwRSxxR0FBNEU7QUFDNUUsNENBQStFO0FBQy9FLHlFQUFnRDtBQUNoRCwrRUFBc0Q7QUFDdEQseUZBQWdFO0FBQ2hFLG1FQUE0RDtBQUM1RCxrREFBeUI7QUFDekIsaUZBQXdFO0FBQ3hFLHVFQUFzRDtBQUN0RCwwREFBMkI7QUFDM0IsNkVBQW9EO0FBQ3BELGdFQUE0RTtBQUM1RSxtRUFBMEM7QUFDMUMsdUVBQThDO0FBQzlDLG1GQUF5RDtBQUN6RCx3RUFBc0Y7QUFFdEYsTUFBTSxZQUFZLEdBQWMsSUFBQSxlQUFLLEVBQUMsaUJBQWlCLENBQUMsQ0FBQztBQUN6RCxNQUFNLHVCQUF1QixHQUFHLFdBQVcsQ0FBQztBQUU1QyxNQUFxQixNQUFPLFNBQVEscUJBQVk7SUFDNUMsWUFBYSxFQUFFLEtBQUssRUFBRSx3QkFBd0IsRUFBRSxhQUFhLEVBQUUsZUFBZSxFQUFFO1FBQzVFLEtBQUssRUFBRSxDQUFDO1FBRVIsSUFBSSxDQUFDLFdBQVcsR0FBVyxJQUFJLHFCQUFVLEVBQUUsQ0FBQztRQUM1QyxJQUFJLENBQUMsS0FBSyxHQUFpQixLQUFLLENBQUM7UUFD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
|