webdriverio-automation
Version:
WebdriverIO-Automation android ios project
410 lines • 18.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerDI = exports.wrap = exports.ClassTestUI = void 0;
/**
* The abstract class ClassTestUI ...
*/
class ClassTestUI {
constructor(runner) {
this.executeAfterHooksInReverseOrder = false;
this.runner = runner;
this.suite = this.makeSuiteObject();
this.test = this.makeTestObject();
this.params = this.makeParamsObject();
this.slow = this.createExecutionOption(ClassTestUI.slowSymbol);
this.timeout = this.createExecutionOption(ClassTestUI.timeoutSymbol);
this.retries = this.createExecutionOption(ClassTestUI.retriesSymbol);
this.pending = this.createExecutionModifier("pending");
this.only = this.createExecutionModifier("only");
this.skip = this.createExecutionModifier("skip");
}
/**
* This is supposed to create a `Symbol(key)` but some platforms does not support Symbols yet so fallback to string keys for now.
* @param key
*/
static MakeSymbol(key) { return "__testdeck_" + key; }
/**
* Declares the provided function as decorator.
* Used to mark decorators such as `@timeout` that can sometimes be provided as single argument to `@suite(timeout(1000))`.
* In those cases the `suite()` overload should be able to distinguish the timeout function from class constructor.
*/
markAsDecorator(arg) {
arg[ClassTestUI.isDecoratorSymbol] = true;
return arg;
}
getSettings(obj) {
let settings;
if (ClassTestUI.slowSymbol in obj) {
(settings || (settings = {})).slow = obj[ClassTestUI.slowSymbol];
}
if (ClassTestUI.timeoutSymbol in obj) {
(settings || (settings = {})).timeout = obj[ClassTestUI.timeoutSymbol];
}
if (ClassTestUI.retriesSymbol in obj) {
(settings || (settings = {})).retries = obj[ClassTestUI.retriesSymbol];
}
if (ClassTestUI.executionSymbol in obj) {
(settings || (settings = {})).execution = obj[ClassTestUI.executionSymbol];
}
return settings;
}
createInstance(testClass) {
const di = dependencyInjectionSystems.find((di) => di.handles(testClass));
const instance = di.create(testClass);
return instance;
}
suiteCallbackFromClass(constructor) {
const theTestUI = this;
return function () {
// Regsiter the static before method of the class to be called before-all tests.
if (constructor.before) {
const settings = theTestUI.getSettings(constructor.before);
if (isAsync(constructor.before)) {
theTestUI.runner.beforeAll("static before", wrap(function (done) {
return constructor.before(done);
}, constructor.before), settings);
}
else {
theTestUI.runner.beforeAll("static before", wrap(function () {
return constructor.before();
}, constructor.before), settings);
}
}
let instance;
// Register the first "before each" callback to be one that will instantiate the class.
theTestUI.runner.beforeEach("setup instance", function setupInstance() {
instance = theTestUI.createInstance(constructor);
});
const prototype = constructor.prototype;
// Register the instance before method to be called before-each test method.
if (prototype.before) {
if (isAsync(prototype.before)) {
theTestUI.runner.beforeEach("before", wrap(function (done) {
return prototype.before.call(instance, done);
}, prototype.before), theTestUI.getSettings(prototype.before));
}
else {
theTestUI.runner.beforeEach("before", wrap(function () {
return prototype.before.call(instance);
}, prototype.before), theTestUI.getSettings(prototype.before));
}
}
function isAsync(method) {
const isParameterised = method[ClassTestUI.parametersSymbol] !== undefined;
const length = method.length;
return (isParameterised && length > 1) || (!isParameterised && length > 0);
}
// All suite before/after each/all calls and instantiation have been set in place.
// Now collect all potential test methods and declare them in the underlying test framework.
const collectedTests = {};
let currentPrototype = prototype;
while (currentPrototype !== Object.prototype) {
Object.getOwnPropertyNames(currentPrototype).forEach((key) => {
const descriptor = Object.getOwnPropertyDescriptor(currentPrototype, key);
if (typeof descriptor.value === 'function'
&& descriptor.value[ClassTestUI.nameSymbol]
&& !collectedTests[key]) {
collectedTests[key] = [prototype, descriptor.value];
}
});
currentPrototype = Object.getPrototypeOf(currentPrototype);
}
function declareTestMethod(prototype, method) {
const testName = method[ClassTestUI.nameSymbol];
const parameters = method[ClassTestUI.parametersSymbol];
if (parameters) {
// we make the parameterised test a child suite so we can late bind the parameterised tests
const settings = theTestUI.getSettings(method);
theTestUI.runner.suite(testName, function () {
const nameForParameters = method[ClassTestUI.nameForParametersSymbol];
parameters.reverse().forEach((parameterOptions, i) => {
const { execution, name, params } = parameterOptions;
let parametersTestName = `${testName} ${i}`;
if (name) {
parametersTestName = name;
}
else if (nameForParameters) {
parametersTestName = nameForParameters(params);
}
const settings = execution ? { execution } : undefined;
applyTestFunc(parametersTestName, method, [params], settings);
});
}, theTestUI.getSettings(method));
}
else {
applyTestFunc(testName, method, [], theTestUI.getSettings(method));
}
}
function applyTestFunc(testName, method, callArgs, testSettings) {
if (isAsync(method)) {
theTestUI.runner.test(testName, wrap(function (done) {
return method.call(instance, done, ...callArgs);
}, method), testSettings);
}
else {
theTestUI.runner.test(testName, wrap(function () {
return method.call(instance, ...callArgs);
}, method), testSettings);
}
}
// run all collected tests
for (const key in collectedTests) {
const value = collectedTests[key];
declareTestMethod(value[0], value[1]);
}
// Register a final after-each method to clear the instance reference.
function registerTeardownHook() {
theTestUI.runner.afterEach("teardown instance", function teardownInstance() {
instance = null;
});
}
// Jest will run the hooks in their reverse order
if (theTestUI.executeAfterHooksInReverseOrder) {
registerTeardownHook();
}
// Register the instance after method to be called after-each test method.
if (prototype.after) {
if (isAsync(prototype.after)) {
theTestUI.runner.afterEach("after", wrap(function (done) {
return prototype.after.call(instance, done);
}, prototype.after), theTestUI.getSettings(prototype.after));
}
else {
theTestUI.runner.afterEach("after", wrap(function () {
return prototype.after.call(instance);
}, prototype.after), theTestUI.getSettings(prototype.after));
}
}
// Mocha will run the hooks in the order they have been added
if (!theTestUI.executeAfterHooksInReverseOrder) {
registerTeardownHook();
}
// Register the static after method of the class to be called after-all tests.
if (constructor.after) {
if (isAsync(constructor.after)) {
theTestUI.runner.afterAll("static after", wrap(function (done) {
return constructor.after(done);
}, constructor.after), theTestUI.getSettings(constructor.after));
}
else {
theTestUI.runner.afterAll("static after", wrap(function () {
return constructor.after();
}, constructor.after), theTestUI.getSettings(constructor.after));
}
}
};
}
makeSuiteObject() {
return Object.assign(this.makeSuiteFunction(), {
skip: this.makeSuiteFunction("skip"),
only: this.makeSuiteFunction("only"),
pending: this.makeSuiteFunction("pending")
});
}
makeSuiteFunction(execution) {
const theTestUI = this;
const decorator = function () {
// Used as `@suite() class MySuite {}`
if (arguments.length === 0) {
return decorator;
}
// Used as `@suite class MySuite {}`
if (arguments.length === 1 && typeof arguments[0] === "function" && !arguments[0][ClassTestUI.isDecoratorSymbol]) {
const ctor = arguments[0];
applySuiteDecorator(ctor.name, ctor);
return;
}
// Used as `@suite("name", timeout(1000))`, return a decorator function,
// that when applied to a class will first apply the execution symbol and timeout decorators, and then register the class as suite.
const hasName = typeof arguments[0] === "string";
const name = hasName ? arguments[0] : undefined;
const decorators = [];
for (let i = hasName ? 1 : 0; i < arguments.length; i++) {
decorators.push(arguments[i]);
}
return function (ctor) {
for (const decorator of decorators) {
decorator(ctor);
}
applySuiteDecorator(hasName ? name : ctor.name, ctor);
};
function applySuiteDecorator(name, ctor) {
if (ctor[ClassTestUI.suiteSymbol]) {
throw new Error(`@suite ${ctor.name} can not subclass another @suite class, use abstract base class instead.`);
}
ctor[ClassTestUI.suiteSymbol] = true;
if (execution) {
ctor[ClassTestUI.executionSymbol] = execution;
}
theTestUI.runner.suite(name, theTestUI.suiteCallbackFromClass(ctor), theTestUI.getSettings(ctor));
}
};
// TODO: figure out why the interface SuiteDecoratorOrName cannot be returned here,
// for now we will just cast to any to make the compiler happy
return decorator;
}
// Things regarding test, abstract in a separate class...
makeTestObject() {
return Object.assign(this.makeTestFunction(), {
skip: this.makeTestFunction("skip"),
only: this.makeTestFunction("only"),
pending: this.makeTestFunction("pending")
});
}
makeTestFunction(execution) {
return this.testOverload({
testProperty(target, propertyKey, descriptor) {
target[propertyKey][ClassTestUI.nameSymbol] = propertyKey.toString();
if (execution) {
target[propertyKey][ClassTestUI.executionSymbol] = execution;
}
},
testDecorator(...decorators) {
return function (target, propertyKey, descriptor) {
target[propertyKey][ClassTestUI.nameSymbol] = propertyKey.toString();
for (const decorator of decorators) {
decorator(target, propertyKey, descriptor);
}
if (execution) {
target[propertyKey][ClassTestUI.executionSymbol] = execution;
}
};
},
testDecoratorNamed(name, ...decorators) {
return function (target, propertyKey, descriptor) {
target[propertyKey][ClassTestUI.nameSymbol] = name;
for (const decorator of decorators) {
decorator(target, propertyKey, descriptor);
}
if (execution) {
target[propertyKey][ClassTestUI.executionSymbol] = execution;
}
};
}
});
}
testOverload({ testProperty, testDecorator, testDecoratorNamed }) {
return function () {
const args = [];
for (let idx = 0; idx < arguments.length; idx++) {
args[idx] = arguments[idx];
}
if (arguments.length >= 2 && typeof arguments[0] !== "string" && typeof arguments[0] !== "function") {
return testProperty.apply(this, args);
}
else if (arguments.length >= 1 && typeof arguments[0] === "string") {
return testDecoratorNamed.apply(this, args);
}
else {
return testDecorator.apply(this, args);
}
};
}
makeParamsFunction(execution) {
return function (params, name) {
return function (target, propertyKey) {
target[propertyKey][ClassTestUI.nameSymbol] = propertyKey.toString();
target[propertyKey][ClassTestUI.parametersSymbol] = target[propertyKey][ClassTestUI.parametersSymbol] || [];
target[propertyKey][ClassTestUI.parametersSymbol].push({ execution, name, params });
};
};
}
makeParamsNameFunction() {
return (nameForParameters) => {
return (target, propertyKey) => {
target[propertyKey][ClassTestUI.nameForParametersSymbol] = nameForParameters;
};
};
}
makeParamsObject() {
return Object.assign(this.makeParamsFunction(), {
skip: this.makeParamsFunction("skip"),
only: this.makeParamsFunction("only"),
pending: this.makeParamsFunction("pending"),
naming: this.makeParamsNameFunction()
});
}
/**
* Create execution options such as `@slow`, `@timeout` and `@retries`.
*/
createExecutionOption(key) {
const classTestUIInstance = this;
return function (value) {
return classTestUIInstance.markAsDecorator(function () {
if (arguments.length === 1) {
const target = arguments[0];
target[key] = value;
}
else {
const proto = arguments[0];
const prop = arguments[1];
const descriptor = arguments[2];
proto[prop][key] = value;
}
});
};
}
/**
* Creates the decorators `@pending`, `@only`, `@skip`.
*/
createExecutionModifier(execution) {
const decorator = function (target, propertyKey) {
if (typeof target === "undefined" || typeof target === "boolean") {
if (target) {
return decorator;
}
else {
return () => { };
}
}
if (arguments.length === 1) {
target[ClassTestUI.executionSymbol] = execution;
}
else {
target[propertyKey][ClassTestUI.executionSymbol] = execution;
}
};
return decorator;
}
}
exports.ClassTestUI = ClassTestUI;
ClassTestUI.suiteSymbol = ClassTestUI.MakeSymbol("suite");
ClassTestUI.nameSymbol = ClassTestUI.MakeSymbol("name");
ClassTestUI.parametersSymbol = ClassTestUI.MakeSymbol("parametersSymbol");
ClassTestUI.nameForParametersSymbol = ClassTestUI.MakeSymbol("nameForParameters");
ClassTestUI.slowSymbol = ClassTestUI.MakeSymbol("slow");
ClassTestUI.timeoutSymbol = ClassTestUI.MakeSymbol("timeout");
ClassTestUI.retriesSymbol = ClassTestUI.MakeSymbol("retries");
ClassTestUI.executionSymbol = ClassTestUI.MakeSymbol("execution");
ClassTestUI.isDecoratorSymbol = ClassTestUI.MakeSymbol("isDecorator");
/**
* Transfers the base's toString and name to the wrapping function.
*/
function wrap(wrap, base) {
wrap.toString = () => base.toString();
Object.defineProperty(wrap, "name", { value: base.name, writable: false });
return wrap;
}
exports.wrap = wrap;
/**
* Core dependency injection support.
*/
const dependencyInjectionSystems = [{
handles() { return true; },
create(cls) {
return new cls();
}
}];
/**
* Register a dependency injection system to be used when instantiating test classes.
* @param instantiator The dependency injection system implementation.
*/
function registerDI(instantiator) {
// Maybe check if it is not already added?
/* istanbul ignore else */
if (!dependencyInjectionSystems.some((di) => di === instantiator)) {
dependencyInjectionSystems.unshift(instantiator);
}
}
exports.registerDI = registerDI;
//# sourceMappingURL=index.js.map