Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ gulp.task('lint', () => {
'src/**/*.ts',
'test/**/*.js',
'!test/client/vendor/**/*.*',
'!test/functional/fixtures/api/es-next/custom-client-scripts/data/*.js',
'Gulpfile.js'
])
.pipe(eslint())
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"source-map-support": "^0.5.5",
"strip-bom": "^2.0.0",
"testcafe-browser-tools": "1.6.8",
"testcafe-hammerhead": "14.6.13",
"testcafe-hammerhead": "14.7.0",
"testcafe-legacy-api": "3.1.11",
"testcafe-reporter-json": "^2.1.0",
"testcafe-reporter-list": "^2.1.0",
Expand Down
26 changes: 24 additions & 2 deletions src/api/structure/fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import handleTagArgs from '../../utils/handle-tag-args';
import TestingUnit from './testing-unit';
import wrapTestFunction from '../wrap-test-function';
import assertRequestHookType from '../request-hooks/assert-type';
import assertClientScriptType from '../../custom-client-scripts/assert-type';
import { flattenDeep as flatten } from 'lodash';
import { SPECIAL_BLANK_PAGE } from 'testcafe-hammerhead';
import { APIError } from '../../errors/runtime';
import OPTION_NAMES from '../../configuration/option-names';
import { RUNTIME_ERRORS } from '../../errors/types';

export default class Fixture extends TestingUnit {
constructor (testFile) {
Expand All @@ -20,8 +24,6 @@ export default class Fixture extends TestingUnit {
this.beforeFn = null;
this.afterFn = null;

this.requestHooks = [];

return this.apiOrigin;
}

Expand Down Expand Up @@ -69,12 +71,32 @@ export default class Fixture extends TestingUnit {
}

_requestHooks$ (...hooks) {
if (this.apiMethodWasCalled.requestHooks)
throw new APIError(OPTION_NAMES.requestHooks, RUNTIME_ERRORS.multipleAPIMethodCallForbidden, OPTION_NAMES.requestHooks);

hooks = flatten(hooks);

assertRequestHookType(hooks);

this.requestHooks = hooks;

this.apiMethodWasCalled.requestHooks = true;

return this.apiOrigin;
}

_clientScripts$ (...scripts) {
if (this.apiMethodWasCalled.clientScripts)
throw new APIError(OPTION_NAMES.clientScripts, RUNTIME_ERRORS.multipleAPIMethodCallForbidden, OPTION_NAMES.clientScripts);

scripts = flatten(scripts);

assertClientScriptType(scripts);

this.clientScripts = scripts;

this.apiMethodWasCalled.clientScripts = true;

return this.apiOrigin;
}
}
Expand Down
42 changes: 33 additions & 9 deletions src/api/structure/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import TestingUnit from './testing-unit';
import { assertType, is } from '../../errors/runtime/type-assertions';
import wrapTestFunction from '../wrap-test-function';
import assertRequestHookType from '../request-hooks/assert-type';
import assertClientScriptType from '../../custom-client-scripts/assert-type';
import { flattenDeep as flatten, union } from 'lodash';
import { RUNTIME_ERRORS } from '../../errors/types';
import { APIError } from '../../errors/runtime';
import OPTION_NAMES from '../../configuration/option-names';

export default class Test extends TestingUnit {
constructor (testFile) {
super(testFile, 'test');

this.fixture = testFile.currentFixture;

this.fn = null;
this.beforeFn = null;
this.afterFn = null;
this.requestHooks = [];
this.fn = null;
this.beforeFn = null;
this.afterFn = null;

return this.apiOrigin;
}
Expand All @@ -23,11 +26,12 @@ export default class Test extends TestingUnit {
assertType(is.function, 'apiOrigin', 'The test body', fn);
assertType(is.nonNullObject, 'apiOrigin', `The fixture of '${name}' test`, this.fixture);

this.name = name;
this.fn = wrapTestFunction(fn);
this.requestHooks = union(this.requestHooks, Array.from(this.fixture.requestHooks));
this.name = name;
this.fn = wrapTestFunction(fn);
this.requestHooks = union(Array.from(this.fixture.requestHooks), this.requestHooks);
this.clientScripts = union(Array.from(this.fixture.clientScripts), this.clientScripts);

if (this.testFile.collectedTests.indexOf(this) < 0)
if (!this.testFile.collectedTests.includes(this))
this.testFile.collectedTests.push(this);

return this.apiOrigin;
Expand All @@ -50,11 +54,31 @@ export default class Test extends TestingUnit {
}

_requestHooks$ (...hooks) {
if (this.apiMethodWasCalled.requestHooks)
throw new APIError(OPTION_NAMES.requestHooks, RUNTIME_ERRORS.multipleAPIMethodCallForbidden, OPTION_NAMES.requestHooks);

hooks = flatten(hooks);

assertRequestHookType(hooks);

this.requestHooks = union(this.requestHooks, hooks);
this.requestHooks = hooks;

this.apiMethodWasCalled.requestHooks = true;

return this.apiOrigin;
}

_clientScripts$ (...scripts) {
if (this.apiMethodWasCalled.clientScripts)
throw new APIError(OPTION_NAMES.clientScripts, RUNTIME_ERRORS.multipleAPIMethodCallForbidden, OPTION_NAMES.clientScripts);

scripts = flatten(scripts);

assertClientScriptType(scripts);

this.clientScripts = scripts;

this.apiMethodWasCalled.clientScripts = true;

return this.apiOrigin;
}
Expand Down
7 changes: 6 additions & 1 deletion src/api/structure/testing-unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { assertUrl, resolvePageUrl } from '../test-page-url';
import handleTagArgs from '../../utils/handle-tag-args';
import { delegateAPI, getDelegatedAPIList } from '../../utils/delegated-api';
import { assertType, is } from '../../errors/runtime/type-assertions';

import FlagList from '../../utils/flag-list';
import OPTION_NAMES from '../../configuration/option-names';

export default class TestingUnit {
constructor (testFile, unitTypeName) {
Expand All @@ -15,9 +16,13 @@ export default class TestingUnit {
this.meta = {};
this.only = false;
this.skip = false;
this.requestHooks = [];
this.clientScripts = [];

this.disablePageReloads = void 0;

this.apiMethodWasCalled = new FlagList([OPTION_NAMES.clientScripts, OPTION_NAMES.requestHooks]);

const unit = this;

this.apiOrigin = function apiOrigin (...args) {
Expand Down
6 changes: 6 additions & 0 deletions src/assets/content-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
javascript: 'application/x-javascript',
css: 'text/css',
png: 'image/png',
icon: 'image/x-icon'
};
18 changes: 18 additions & 0 deletions src/assets/injectables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const TESTCAFE_CORE = '/testcafe-core.js';
export const TESTCAFE_DRIVER = '/testcafe-driver.js';
export const TESTCAFE_LEGACY_RUNNER = '/testcafe-legacy-runner.js';
export const TESTCAFE_AUTOMATION = '/testcafe-automation.js';
export const TESTCAFE_UI = '/testcafe-ui.js';

export const SCRIPTS = [
TESTCAFE_CORE,
TESTCAFE_UI,
TESTCAFE_AUTOMATION,
TESTCAFE_DRIVER
];

export const TESTCAFE_UI_SPRITE = '/testcafe-ui-sprite.png';

export const TESTCAFE_ICON = '/favicon.ico';

export const TESTCAFE_UI_STYLES = '/testcafe-ui-styles.css';
5 changes: 5 additions & 0 deletions src/cli/argument-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,17 @@ export default class CLIArgumentParser {
.option('--qr-code', 'outputs QR-code that repeats URLs used to connect the remote browsers')
.option('--sf, --stop-on-first-fail', 'stop an entire test run if any test fails')
.option('--ts-config-path <path>', 'use a custom TypeScript configuration file and specify its location')
.option('--cs, --client-scripts <paths>', 'inject scripts into tested pages', this._parseList, [])

// NOTE: these options will be handled by chalk internally
.option('--color', 'force colors in command line')
.option('--no-color', 'disable colors in command line');
}

_parseList (val) {
return val.split(',');
}

_filterAndCountRemotes (browser) {
const remoteMatch = browser.match(REMOTE_ALIAS_RE);

Expand Down
3 changes: 2 additions & 1 deletion src/cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ async function runTests (argParser) {
.filter(argParser.filter)
.video(opts.video, opts.videoOptions, opts.videoEncodingOptions)
.screenshots(opts.screenshots, opts.screenshotsOnFails, opts.screenshotPathPattern)
.startApp(opts.app, opts.appInitDelay);
.startApp(opts.app, opts.appInitDelay)
.clientScripts(argParser.opts.clientScripts);

runner.once('done-bootstrapping', () => log.hideSpinner());

Expand Down
13 changes: 12 additions & 1 deletion src/client/driver/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ import {
CurrentIframeIsNotLoadedError,
CurrentIframeNotFoundError,
CurrentIframeIsInvisibleError,
CannotObtainInfoForElementSpecifiedBySelectorError
CannotObtainInfoForElementSpecifiedBySelectorError,
UncaughtErrorInCustomClientScriptCode,
UncaughtErrorInCustomClientScriptLoadedFromModule
} from '../../errors/test-run';

import BrowserConsoleMessages from '../../test-run/browser-console-messages';
Expand Down Expand Up @@ -194,6 +196,15 @@ export default class Driver {
return false;
}

onCustomClientScriptError (err, moduleName) {
const error = moduleName
? new UncaughtErrorInCustomClientScriptLoadedFromModule(err, moduleName)
: new UncaughtErrorInCustomClientScriptCode(err);

if (!this.contextStorage.getItem(PENDING_PAGE_ERROR))
this.contextStorage.setItem(PENDING_PAGE_ERROR, error);
}

// Console messages
_onConsoleMessage ({ meth, line }) {
const messages = this.consoleMessages;
Expand Down
9 changes: 5 additions & 4 deletions src/client/driver/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import Driver from './driver';
import IframeDriver from './iframe-driver';
import ScriptExecutionBarrier from './script-execution-barrier';
import embeddingUtils from './embedding-utils';
import INTERNAL_PROPERTIES from './internal-properties';

const nativeMethods = hammerhead.nativeMethods;
const evalIframeScript = hammerhead.EVENTS.evalIframeScript;

nativeMethods.objectDefineProperty(window, '%testCafeDriver%', { configurable: true, value: Driver });
nativeMethods.objectDefineProperty(window, '%testCafeIframeDriver%', { configurable: true, value: IframeDriver });
nativeMethods.objectDefineProperty(window, '%ScriptExecutionBarrier%', {
nativeMethods.objectDefineProperty(window, INTERNAL_PROPERTIES.testCafeDriver, { configurable: true, value: Driver });
nativeMethods.objectDefineProperty(window, INTERNAL_PROPERTIES.testCafeIframeDriver, { configurable: true, value: IframeDriver });
nativeMethods.objectDefineProperty(window, INTERNAL_PROPERTIES.scriptExecutionBarrier, {
configurable: true,
value: ScriptExecutionBarrier
});
nativeMethods.objectDefineProperty(window, '%testCafeEmbeddingUtils%', { configurable: true, value: embeddingUtils });
nativeMethods.objectDefineProperty(window, INTERNAL_PROPERTIES.testCafeEmbeddingUtils, { configurable: true, value: embeddingUtils });

// eslint-disable-next-line no-undef
hammerhead.on(evalIframeScript, e => initTestCafeClientDrivers(nativeMethods.contentWindowGetter.call(e.iframe), true));
7 changes: 7 additions & 0 deletions src/client/driver/internal-properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
testCafeDriver: '%testCafeDriver%',
testCafeIframeDriver: '%testCafeIframeDriver%',
scriptExecutionBarrier: '%ScriptExecutionBarrier%',
testCafeEmbeddingUtils: '%testCafeEmbeddingUtils%',
testCafeDriverInstance: '%testCafeDriverInstance%'
};
8 changes: 7 additions & 1 deletion src/configuration/option-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,11 @@ export default {
videoPath: 'videoPath',
videoOptions: 'videoOptions',
videoEncodingOptions: 'videoEncodingOptions',
tsConfigPath: 'tsConfigPath'
tsConfigPath: 'tsConfigPath',
clientScripts: 'clientScripts',
requestHooks: 'requestHooks',
retryTestPages: 'retryTestPages',
hostname: 'hostname',
port1: 'port1',
port2: 'port2'
};
1 change: 1 addition & 0 deletions src/configuration/testcafe-configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export default class TestCafeConfiguration extends Configuration {
this._prepareFilterFn();
this._ensureArrayOption(OPTION_NAMES.src);
this._ensureArrayOption(OPTION_NAMES.browsers);
this._ensureArrayOption(OPTION_NAMES.clientScripts);
this._prepareReporters();
}

Expand Down
5 changes: 5 additions & 0 deletions src/custom-client-scripts/assert-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { assertType, is } from '../errors/runtime/type-assertions';

export default function (scripts) {
scripts.forEach(script => assertType([is.string, is.clientScriptInitializer], 'clientScripts', `Client script`, script));
}
Loading