flagpole
Version:
Simple and fast DOM integration, headless or headful browser, and REST API testing framework.
789 lines • 27.5 kB
JavaScript
;
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