UNPKG

webdriverio-automation

Version:

WebdriverIO-Automation android ios project

410 lines 18.6 kB
"use strict"; 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