protractor-sync-options-plugin
Version:
Protractor plugin to ignore specific async calls in angular application
280 lines (271 loc) • 11.6 kB
JavaScript
import { __awaiter, __generator } from 'tslib';
import { browser } from 'protractor';
/**
* set of functions, that can be executed by client/frontend.
* every function must be "all inclusive" without any references to outside,
* because they are transferred as text to client by selenium.
*/
var patchTestability_1 = patchTestability;
var restoreTestability_1 = restoreTestability;
/**
* (monkey)patches, if not already patched, angular.testability#whenStable, to be able to ignore some async tasks
* @param {IgnoreTask[]} ignoreTasks ignore filter tasks definitions
*/
function patchTestability(ignoreTasks) {
if (!window.getAllAngularTestabilities) {
throw Error('Testability not found. Not an angular app?');
}
var testability = window.getAllAngularTestabilities()[0]; // TODO all apps
var Testability = testability.constructor;
var isStableOrig = Testability.prototype.isStable;
if (!!isStableOrig.originalFn) {
throw Error('Testability already patched');
}
var newIsStable = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var isStable = isStableOrig.apply(this, args);
var isStableWithoutFiltered = hasOnlyFilteredTasks(testability);
return isStable || isStableWithoutFiltered;
};
newIsStable.originalFn = isStableOrig;
Testability.prototype.isStable = newIsStable;
// -----------------------------------------------------
// helpers
// -----------------------------------------------------
function matchesSource(/** PendingMacrotask */ task, /** IgnoreTask */ ignoreTask) {
return task.source === ignoreTask.source;
}
function isSourceDefined(/** IgnoreTask */ ignoreTask) {
var source = ignoreTask.source;
return (typeof source === 'string') || (source instanceof RegExp);
}
function matchesLocation(/** PendingMacrotask */ task, /** IgnoreTask */ ignoreTask) {
var locationFilter = ignoreTask.creationLocation;
var callLocations = getCallLocations(task);
return callLocations.some(function (callLocation) {
if (locationFilter instanceof RegExp) {
return callLocation.match(locationFilter);
}
if (typeof locationFilter === 'string') {
return callLocation.includes(locationFilter);
}
});
}
function isLocationDefined(/** IgnoreTask */ ignoreTask) {
var loc = ignoreTask.creationLocation;
return (typeof loc === 'string') || (loc instanceof RegExp);
}
/**
* @param {PendingMacrotask} task task to check
* @param {IgnoreTask} ignoreTask matching conditions
* @return {boolean} true if all defined condition properties of `ignoreTask` matches (conjunction)
*/
function areMatching(task, ignoreTask) {
var result = true;
if (isSourceDefined(ignoreTask)) {
result &= matchesSource(task, ignoreTask);
}
if (isLocationDefined(ignoreTask)) {
result &= matchesLocation(task, ignoreTask);
}
return result;
}
function getCallLocations(/** PendingMacrotask */ task) {
return task.creationLocation.stack.split(' at ');
}
function shouldIgnore(/** PendingMacrotask */ t) {
return ignoreTasks.some(function (it) { return (areMatching(t, it)); });
}
function hasOnlyFilteredTasks(testability) {
var trZone = testability['taskTrackingZone']; // private prop TODO use any api?
var tasks = trZone.macroTasks;
return tasks.every(shouldIgnore);
}
}
/**
* restores patched whenStable function to original, if patched.
*/
function restoreTestability() {
if (!window.getAllAngularTestabilities) {
throw Error('Testability not found. Not an angular app?');
}
var testability = window.getAllAngularTestabilities()[0]; // TODO all apps
var Testability = testability.constructor;
var isStableFn = Testability.prototype.isStable;
if (!isStableFn.originalFn) {
throw Error('Cant restore, Testability.whenStable is not patched');
}
Testability.prototype.isStable = isStableFn.originalFn;
}
var clientScripts = {
patchTestability: patchTestability_1,
restoreTestability: restoreTestability_1
};
/**
* makes monkey-patching for protractor and frontend angular.testability
* @TODO find a way to use some api instead of monkey-patching
*/
var Patcher = /** @class */ (function () {
function Patcher(browser) {
this.browser = browser;
}
/**
*
* @param config config to use
* @returns `true` in success case, `false` otherwise (e.g. already patched)
*/
Patcher.prototype.patchClientTestability = function (config) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.browser.executeScript(patchTestability_1, config.ignoreTasks || [])];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
/**
* restores patched with {@link this#patchClientTestability} client
*/
Patcher.prototype.restoreClientTestability = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.browser.executeScript(restoreTestability_1)];
});
});
};
/**
* patches {@link browser#waitForAngular}
* @returns `true` in success case, `false` otherwise (e.g. already patched)
*/
Patcher.prototype.patchWaitForAngularEnabled = function (config) {
return __awaiter(this, void 0, void 0, function () {
var origFn, thisPlugin, newFn;
return __generator(this, function (_a) {
if (this.isWaitForAngularPatched()) {
throw Error('browser.waitForAngularEnabled is already patched');
}
origFn = this.browser.constructor.prototype.waitForAngularEnabled;
thisPlugin = this;
newFn = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(args[0] === true)) return [3 /*break*/, 2];
return [4 /*yield*/, thisPlugin.patchClientTestability(config).catch(function (e) {
console.warn('patched waitForAngularEnabled => patchClientTestability error:', e); // TODO logger
})];
case 1:
_a.sent();
_a.label = 2;
case 2: return [2 /*return*/, origFn.apply(this, args)];
}
});
});
};
newFn.orignalFn = origFn;
this.browser.constructor.prototype.waitForAngularEnabled = newFn;
return [2 /*return*/];
});
});
};
Patcher.prototype.restoreWaitForAngularEnabled = function () {
return __awaiter(this, void 0, void 0, function () {
var proto;
return __generator(this, function (_a) {
if (!this.isWaitForAngularPatched()) {
throw Error('Cant restore. this.browser.constructor.prototype.waitForAngularEnabled is not patched');
}
proto = this.browser.constructor.prototype;
proto.waitForAngularEnabled = proto.waitForAngularEnabled.originalFn;
return [2 /*return*/];
});
});
};
Patcher.prototype.isWaitForAngularPatched = function () {
return !!this.browser.constructor.prototype.waitForAngularEnabled.originalFn; // better check?
};
return Patcher;
}());
var SyncOptionsPlugin = /** @class */ (function () {
function SyncOptionsPlugin() {
this.name = 'Protractor Sync Options Plugin';
this.patcher = new Patcher(browser);
}
SyncOptionsPlugin.prototype.onPageLoad = function (currBrowser) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.trySafe(function () { return _this.patcher.patchClientTestability(_this.config); })];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
SyncOptionsPlugin.prototype.setup = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.patcher.browser = browser;
return [4 /*yield*/, this.trySafe(function () { return _this.patcher.patchWaitForAngularEnabled(_this.config); })];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
/**
* safe function call
* shows only warning in function error case
* @returns `false` if `funcToCall` throws an error, `true` otherwise
*/
SyncOptionsPlugin.prototype.trySafe = function (funcToCall) {
return __awaiter(this, void 0, void 0, function () {
var e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, funcToCall()];
case 1:
_a.sent();
return [2 /*return*/, true];
case 2:
e_1 = _a.sent();
console.warn('function call failed', e_1); // TODO logger;
return [2 /*return*/, false];
case 3: return [2 /*return*/];
}
});
});
};
return SyncOptionsPlugin;
}());
// workaround to export the plugin instance from package require
var initializedPlugin = new SyncOptionsPlugin();
Object.assign(exports, initializedPlugin);
exports.setup = initializedPlugin.setup;
exports.onPageLoad = initializedPlugin.onPageLoad;
exports.trySafe = initializedPlugin.trySafe;
var plugin = exports; // need to usual 'import' initialized plugin
/**
* Generated bundle index. Do not edit.
*/
export { Patcher, SyncOptionsPlugin, plugin };
//# sourceMappingURL=protractor-sync-options-plugin.js.map