UNPKG

flagpole

Version:

Simple and fast DOM integration, headless or headful browser, and REST API testing framework.

789 lines 27.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const enums_1 = require("./enums"); const browsercontrol_1 = require("./browsercontrol"); const responsefactory_1 = require("./responsefactory"); const assertioncontext_1 = require("./assertioncontext"); const assertionresult_1 = require("./logging/assertionresult"); const httpresponse_1 = require("./httpresponse"); const resourceresponse_1 = require("./resourceresponse"); const heading_1 = require("./logging/heading"); const comment_1 = require("./logging/comment"); const logcollection_1 = require("./logging/logcollection"); const httprequest_1 = require("./httprequest"); const probeImage = require("probe-image-size"); class Scenario { constructor(suite, title, onCompletedCallback) { this._log = new logcollection_1.LogCollection(); this._subscribers = []; this._nextCallbacks = []; this._nextMessages = []; this._beforeCallbacks = []; this._beforeMessages = []; this._afterCallbacks = []; this._afterMessages = []; this._finallyCallbacks = []; this._finallyMessages = []; this._errorCallbacks = []; this._errorMessages = []; this._failureCallbacks = []; this._failureMessages = []; this._successCallbacks = []; this._successMessages = []; this._pipeCallbacks = []; this._pipeMessages = []; this._timeScenarioInitialized = Date.now(); this._timeScenarioExecuted = null; this._timeRequestStarted = null; this._timeRequestLoaded = null; this._timeScenarioFinished = null; this._responseType = enums_1.ResponseType.html; this._redirectChain = []; this._finalUrl = null; this._waitToExecute = false; this._waitTime = 0; this._flipAssertion = false; this._ignoreAssertion = false; this._followRedirect = null; this._browserControl = null; this._isMock = false; this._defaultBrowserOptions = { headless: true, recordConsole: true, outputConsole: false, }; this._defaultRequestOptions = { uri: "/", method: "get", }; this._aliasedData = {}; this.suite = suite; this._request = new httprequest_1.HttpRequest(this._defaultRequestOptions); this._title = title; this._onCompletedCallback = onCompletedCallback; this._response = new resourceresponse_1.ResourceResponse(this); } get responseType() { return this._responseType; } get title() { return this._title; } set title(newTitle) { if (this.hasExecuted) { throw new Error("Can not change the scenario's title after execution has started."); } this._title = newTitle; } get totalDuration() { return this._timeScenarioFinished !== null ? this._timeScenarioFinished - this._timeScenarioInitialized : Date.now() - this._timeScenarioInitialized; } get executionDuration() { return this._timeScenarioFinished !== null && this._timeScenarioExecuted !== null ? this._timeScenarioFinished - this._timeScenarioExecuted : null; } get requestDuration() { return this._timeRequestStarted !== null && this._timeRequestLoaded !== null ? this._timeRequestLoaded - this._timeRequestStarted : null; } get hasFailed() { return this._log.items.some((item) => { return item.type == enums_1.LogItemType.Result && item.failed && !item.isOptional; }); } get hasPassed() { return this.hasFinished && !this.hasFailed; } get canExecute() { return (!this.hasExecuted && this.url !== null && this._nextCallbacks.length > 0); } get hasExecuted() { return this._timeScenarioExecuted !== null; } get hasFinished() { return this.hasExecuted && this._timeScenarioFinished !== null; } get url() { return this._request.uri; } get finalUrl() { return this._finalUrl; } get redirectCount() { return this._redirectChain.length; } get redirectChain() { return this._redirectChain; } get request() { return this._request; } static create(suite, title, type, opts, onCompletedCallback) { return new Scenario(suite, title, onCompletedCallback).setResponseType(type, opts); } set(aliasName, value) { this._aliasedData[aliasName] = value; return this; } get(aliasName) { return this._aliasedData[aliasName]; } getLog() { return __awaiter(this, void 0, void 0, function* () { return this._log.items; }); } subscribe(callback) { this._subscribers.push(callback); return this; } setJsonBody(json) { this.setHeader("Content-Type", "application/json"); this._request.data = json; return this; } setRawBody(str) { this._request.data = str; return this; } verifyCert(verify) { this._request.verifyCert = verify; return this; } setProxy(proxy) { this._request.proxy = proxy; return this; } setTimeout(timeout) { this._request.timeout = typeof timeout === "number" ? { open: timeout, } : timeout; return this; } setFormData(form) { this._request.data = form; return this; } setMaxRedirects(n) { this._request.maxRedirects = n; return this; } shouldFollowRedirects(onRedirect) { this._followRedirect = onRedirect; return this; } setBasicAuth(auth) { this._request.auth = auth; return this; } setBearerToken(token) { this.setHeader("Authorization", `Bearer ${token}`); return this; } setCookie(key, value) { this._request.setCookie(key, value); return this; } setHeaders(headers) { this._request.headers = Object.assign(Object.assign({}, this._request.headers), headers); return this; } setHeader(key, value) { this._request.setHeader(key, value); return this; } setMethod(method) { this._request.method = method; return this; } wait(bool = true) { if (this._waitToExecute && !bool) { this._waitTime = Date.now() - this._timeScenarioInitialized; } this._waitToExecute = bool; return this; } waitFor(thatScenario) { if (this === thatScenario) { throw new Error("Scenario can't wait for itself"); } this.wait(); thatScenario.success(() => __awaiter(this, void 0, void 0, function* () { this.wait(false); yield this.execute(); })); return this; } comment(input) { const message = typeof input === "string" ? input : input.toString(); return this._pushToLog(new comment_1.LogComment(message)); } result(result) { return this._pushToLog(result); } ignore(assertions = true) { if (typeof assertions == "boolean") { this._ignoreAssertion = assertions; } else if (typeof assertions == "function") { this.ignore(true); assertions(); this.ignore(false); } return this; } pause(milliseconds) { this.next((context) => { context.comment(`Pause for ${milliseconds}ms`); return context.pause(milliseconds); }); return this; } open(url, opts) { if (!this.hasExecuted) { const match = /([A-Z]+) (.*)/.exec(url); if (match !== null) { const verb = match[1].toLowerCase(); if (httprequest_1.HttpMethodVerbAllowedValues.includes(verb)) { this.setMethod(verb); } url = match[2]; } if (/{[A-Za-z0-9_ -]+}/.test(url)) { this.wait(); } if (opts) { this._request.setOptions(opts); } this._request.uri = String(url); this._isMock = false; this._executeWhenReady(); } return this; } next(a, b) { if (Array.isArray(a)) { a.forEach((callback) => { this._next(callback, null, true); }); } else { this._next(a, b, true); } return this; } nextPrepend(a, b) { return this._next(a, b, false); } skip(message) { return __awaiter(this, void 0, void 0, function* () { if (this.hasExecuted) { throw new Error(`Can't skip Scenario since it already started executing.`); } yield this._fireBefore(); this._publish(enums_1.ScenarioStatusEvent.executionProgress); this.comment(`Skipped${message ? ": " + message : ""}`); yield this._fireAfter(); yield this._fireFinally(); return this; }); } cancel() { return __awaiter(this, void 0, void 0, function* () { if (this.hasExecuted) { throw new Error(`Can't cancel Scenario since it already started executing.`); } yield this._fireBefore(); yield this._fireAfter(); yield this._fireFinally(); return this; }); } getBrowserControl() { this._browserControl = this._browserControl !== null ? this._browserControl : new browsercontrol_1.BrowserControl(); return this._browserControl; } execute(params) { return __awaiter(this, void 0, void 0, function* () { if (!this.hasExecuted && this.url !== null) { if (params) { Object.keys(params).forEach((key) => { var _a; this._request.uri = ((_a = this.url) === null || _a === void 0 ? void 0 : _a.replace(`{${key}}`, String(params[key]))) || null; }); } yield this._fireBefore(); this._pushToLog(new heading_1.LogScenarioHeading(this.title)); this.wait(false); if (this._waitTime > 0) { this.comment(`Waited ${this._waitTime}ms`); } this._publish(enums_1.ScenarioStatusEvent.executionProgress); this._isMock ? this._executeMock() : this._executeRequest(); } return this; }); } error(a, b) { if (this.hasFinished) { throw new Error("Can not add error callbacks after execution has finished."); } const { message, callback } = this._getOverloads(a, b); this._errorMessages.push(message); this._errorCallbacks.push(callback); return this; } success(a, b) { if (this.hasFinished) { throw new Error("Can not add success callbacks after execution has finished."); } const { message, callback } = this._getOverloads(a, b); this._successMessages.push(message); this._successCallbacks.push(callback); return this; } failure(a, b) { if (this.hasFinished) { throw new Error("Can not add failure callbacks after execution has finished."); } const { message, callback } = this._getOverloads(a, b); this._failureMessages.push(message); this._failureCallbacks.push(callback); return this; } pipe(a, b) { if (this.hasExecuted) { throw new Error("Can not add pipe callbacks after execution has started."); } if (Array.isArray(a)) { a.forEach((callback) => { this._pipeMessages.push(null); this._pipeCallbacks.push(callback); }); } else { const { message, callback } = this._getOverloads(a, b); this._pipeMessages.push(message); this._pipeCallbacks.push(callback); } return this; } before(a, b) { if (this.hasExecuted) { throw new Error("Can not add before callbacks after execution has started."); } if (Array.isArray(a)) { a.forEach((callback) => { this._beforeMessages.push(null); this._beforeCallbacks.push(callback); }); } else { const { message, callback } = this._getOverloads(a, b); this._beforeMessages.push(message); this._beforeCallbacks.push(callback); } return this; } after(a, b) { if (this.hasFinished) { throw new Error("Can not add after callbacks after execution has finished."); } if (Array.isArray(a)) { a.forEach((callback) => { this._afterMessages.push(null); this._afterCallbacks.push(callback); }); } else { const { message, callback } = this._getOverloads(a, b); this._afterMessages.push(message); this._afterCallbacks.push(callback); } return this; } finally(a, b) { if (this.hasFinished) { throw new Error("Can not add failure callbacks after execution has finished."); } const { message, callback } = this._getOverloads(a, b); this._finallyMessages.push(message); this._finallyCallbacks.push(callback); return this; } mock(localPath) { this._request.uri = localPath; this._isMock = true; this._executeWhenReady(); return this; } _reset() { this._flipAssertion = false; return this; } _pipeResponses(httpResponse) { this._pipeCallbacks.forEach((callback, i) => { if (this._pipeMessages[i]) { this.comment(this._pipeMessages[i] || ""); } const result = callback(httpResponse); if (result) { httpResponse = result; } }); return httpResponse; } _processResponse(httpResponse) { httpResponse = this._pipeResponses(httpResponse); this._response.init(httpResponse); this._timeRequestLoaded = Date.now(); this.result(new assertionresult_1.AssertionPass("Loaded " + this._response.responseTypeName + " " + this.url)); let lastReturnValue = null; this._publish(enums_1.ScenarioStatusEvent.executionProgress); Promise.mapSeries(this._nextCallbacks, (_then, index) => { const context = new assertioncontext_1.AssertionContext(this, this._response); const comment = this._nextMessages[index]; if (comment !== null) { this._pushToLog(new heading_1.LogScenarioSubHeading(comment)); } context.result = lastReturnValue; lastReturnValue = _then.apply(context, [context]); context.incompleteAssertions.forEach((assertion) => { this.result(new assertionresult_1.AssertionFailWarning(`Incomplete assertion: ${assertion.name}`, assertion)); }); return Promise.all([ lastReturnValue, context.assertionsResolved, context.subScenariosResolved, ]).timeout(30000); }) .then(() => { this._markScenarioCompleted(); }) .catch((err) => { this._markScenarioCompleted(err); }); this._publish(enums_1.ScenarioStatusEvent.executionProgress); } setResponseType(type, opts = {}) { if (this.hasExecuted) { throw new Error("Scenario was already executed. Can not change type."); } this._request .setOptions(type == enums_1.ResponseType.browser || type == enums_1.ResponseType.extjs ? { browser: Object.assign(Object.assign({}, this._defaultBrowserOptions), opts), } : Object.assign(Object.assign({}, this._defaultRequestOptions), opts)) .setOptions({ type: this._responseType === enums_1.ResponseType.json ? "json" : this._responseType === enums_1.ResponseType.image ? "image" : "generic", }); this._responseType = type; this._response = responsefactory_1.createResponse(this); return this; } promise() { return new Promise((resolve, reject) => { this.success(resolve); this.error(reject); this.failure(reject); }); } _executeBrowserRequest() { const browserControl = this.getBrowserControl(); browserControl .open(this._request) .then((next) => { const puppeteerResponse = next.response; if (puppeteerResponse !== null) { this._finalUrl = puppeteerResponse.url(); puppeteerResponse .request() .redirectChain() .forEach((req) => { this._redirectChain.push(req.url()); }); this._processResponse(httpresponse_1.HttpResponse.fromPuppeteer(puppeteerResponse, next.body, next.cookies)); } else { this._markScenarioCompleted(`Failed to load ${this._request.uri}`); } return; }) .catch((err) => this._markScenarioCompleted(`Failed to load ${this._request.uri}`, err)); } _executeDefaultRequest() { this._request .fetch({ redirect: (url) => { this._finalUrl = url; this._redirectChain.push(url); }, }) .then((response) => { this._processResponse(response); }) .catch((err) => { this._markScenarioCompleted(`Failed to load ${this._request.uri}`, err); }); } _executeRequest() { if (!this._timeRequestStarted && this.url !== null) { this._timeRequestStarted = Date.now(); this._request.uri = this.suite.buildUrl(this._request.uri || ""); this._finalUrl = this._request.uri; if (this._responseType == enums_1.ResponseType.browser || this._responseType == enums_1.ResponseType.extjs) { this._executeBrowserRequest(); } else { this._executeDefaultRequest(); } } } _executeMock() { if (!this._timeRequestStarted && this.url !== null) { const scenario = this; this._timeRequestStarted = Date.now(); httpresponse_1.HttpResponse.fromLocalFile(this.url) .then((mock) => { scenario._processResponse(mock); }) .catch((err) => { scenario._markScenarioCompleted(`Failed to load page ${scenario.url}`, err); }); } } _executeWhenReady() { if (!this._waitToExecute && this.canExecute) { this.execute(); return true; } return false; } _markScenarioCompleted(errorMessage = null, errorDetails) { return __awaiter(this, void 0, void 0, function* () { if (!this.hasFinished) { yield this._fireAfter(); this.comment(`Took ${this.executionDuration}ms`); if (errorMessage === null) { this.hasPassed ? yield this._fireSuccess() : yield this._fireFailure(); } else { this.result(new assertionresult_1.AssertionFail(errorMessage, errorDetails)); yield this._fireError(errorDetails || errorMessage); } yield this._fireFinally(); if (this._browserControl !== null) { } } return this; }); } _fireBefore() { const scenario = this; this._timeScenarioExecuted = Date.now(); return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { Promise.mapSeries(scenario._beforeCallbacks, (_then, index) => { const comment = scenario._beforeMessages[index]; if (comment !== null) { this._pushToLog(new comment_1.LogComment(comment)); } return _then.apply(scenario, [scenario]); }) .then(() => { scenario._publish(enums_1.ScenarioStatusEvent.beforeExecute); resolve(); }) .catch((err) => { reject(err); }); })); } _fireAfter() { const scenario = this; this._timeScenarioFinished = Date.now(); return new Promise((resolve, reject) => { Promise.mapSeries(this._afterCallbacks, (_then, index) => { const comment = scenario._afterMessages[index]; if (comment !== null) { this._pushToLog(new comment_1.LogComment(comment)); } return _then.apply(scenario, [scenario]); }) .then(() => { this._publish(enums_1.ScenarioStatusEvent.afterExecute); resolve(); }) .catch((err) => { reject(err); }); }); } _fireSuccess() { const scenario = this; return new Promise((resolve, reject) => { Promise.mapSeries(this._successCallbacks, (_then, index) => { const comment = scenario._successMessages[index]; if (comment !== null) { this._pushToLog(new comment_1.LogComment(comment)); } return _then.apply(scenario, [scenario]); }) .then(() => { this._publish(enums_1.ScenarioStatusEvent.finished); resolve(); }) .catch((err) => { reject(err); }); }); } _fireFailure() { const scenario = this; return new Promise((resolve, reject) => { Promise.mapSeries(this._failureCallbacks, (_then, index) => { const comment = scenario._failureMessages[index]; if (comment !== null) { this._pushToLog(new comment_1.LogComment(comment)); } return _then.apply(scenario, [scenario]); }) .then(() => { this._publish(enums_1.ScenarioStatusEvent.finished); resolve(); }) .catch((err) => { reject(err); }); }); } _fireError(error) { const scenario = this; return new Promise((resolve, reject) => { Promise.mapSeries(this._errorCallbacks, (_then, index) => { const comment = scenario._errorMessages[index]; if (comment !== null) { this._pushToLog(new comment_1.LogComment(comment)); } return _then.apply(scenario, [error, scenario]); }) .then(() => { this._publish(enums_1.ScenarioStatusEvent.finished); resolve(); }) .catch((err) => { reject(err); }); }); } _fireFinally() { const scenario = this; return new Promise((resolve, reject) => { Promise.mapSeries(this._finallyCallbacks, (_then, index) => { const comment = scenario._finallyMessages[index]; if (comment !== null) { this._pushToLog(new comment_1.LogComment(comment)); } return _then.apply(scenario, [scenario]); }) .then(() => { this._onCompletedCallback(scenario); this._publish(enums_1.ScenarioStatusEvent.finished); resolve(); }) .catch((err) => { reject(err); }); }); } _getOverloads(a, b) { return { message: this._getMessageOverload(a), callback: this._getCallbackOverload(a, b), }; } _getCallbackOverload(a, b) { return (() => { if (typeof b == "function") { return b; } else if (typeof a == "function") { return a; } else { throw new Error("No callback provided."); } })(); } _getMessageOverload(a) { return (function () { if (typeof a == "string" && a.trim().length > 0) { return a; } return null; })(); } _next(a, b, append = true) { const callback = this._getCallbackOverload(a, b); const message = this._getMessageOverload(a); if (!this.hasFinished) { if (append) { this._nextCallbacks.push(callback); this._nextMessages.push(message); } else { this._nextCallbacks.unshift(callback); this._nextMessages.unshift(message); } setTimeout(() => { this._executeWhenReady(); }, 0); } else { throw new Error("Scenario already finished."); } return this; } _publish(statusEvent) { return __awaiter(this, void 0, void 0, function* () { const scenario = this; this._subscribers.forEach(function (callback) { return __awaiter(this, void 0, void 0, function* () { callback(scenario, statusEvent); }); }); }); } _pushToLog(logItem) { this._log.add(logItem); return this; } } exports.Scenario = Scenario; //# sourceMappingURL=scenario.js.map