Skip to content

Commit

Permalink
Add support for 'clientScripts' (close #1739) (#3880)
Browse files Browse the repository at this point in the history
* initial

* fix wrong rebase

* new logic for resolving relative paths (test api and others)

* refactoring

* fix tests

* provide error messages for uncaught errors occured in injected client scripts

* fix tests

* fix error messages

* fix server tests

* fix relative paths

* fix type definition

* prevent multiple method calls for clientScripts and requestHooks

* fix an error message during module loading

* specify execution order for clientScripts and requestHooks

* skip test

* skip test

* refix the test

* fix client scripts loading order and handle module path resolving errors
  • Loading branch information
benmonro authored and miherlosev committed Jul 24, 2019
1 parent cef05a4 commit 8c6e721
Show file tree
Hide file tree
Showing 80 changed files with 1,436 additions and 172 deletions.
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));
}

0 comments on commit 8c6e721

Please sign in to comment.