flagpole
Version:
Simple and fast DOM integration, headless or headful browser, and REST API testing framework.
753 lines • 26.4 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 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 flagpoleexecution_1 = require("./flagpoleexecution");
const util_1 = require("./util");
const assertioncontext_1 = require("./assertioncontext");
const bluebird = require("bluebird");
class Scenario {
constructor(suite, title) {
this._log = new logcollection_1.LogCollection();
this._subscribers = [];
this._nextCallbacks = [];
this._nextMessages = [];
this._beforeCallbacks = [];
this._afterCallbacks = [];
this._finallyCallbacks = [];
this._failureCallbacks = [];
this._successCallbacks = [];
this._pipeCallbacks = [];
this._timeScenarioInitialized = Date.now();
this._timeScenarioExecuted = null;
this._timeRequestStarted = null;
this._timeRequestLoaded = null;
this._timeScenarioFinished = null;
this._responseType = "html";
this._redirectChain = [];
this._finalUrl = null;
this._waitToExecute = false;
this._waitTime = 0;
this._flipAssertion = false;
this._ignoreAssertion = false;
this._browserControl = null;
this._isMock = false;
this._defaultBrowserOptions = {
headless: true,
recordConsole: true,
outputConsole: false,
};
this._defaultRequestOptions = {
method: "get",
};
this._aliasedData = {};
this._requestResolve = () => { };
this._finishedResolve = () => { };
this._disposition = "pending";
this.suite = suite;
this._request = new httprequest_1.HttpRequest(this._defaultRequestOptions);
this._title = title;
this._response = new resourceresponse_1.ResourceResponse(this);
this._requestPromise = new Promise((resolve) => {
this._requestResolve = resolve;
});
this._finishedPromise = new Promise((resolve) => {
this._finishedResolve = resolve;
});
}
get responseType() {
return this._responseType;
}
get title() {
return this._title;
}
set title(newTitle) {
if (this.hasExecuted) {
throw "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 == "resultFailure";
});
}
get hasPassed() {
return this.hasFinished && !this.hasFailed;
}
get isReadyToExecute() {
return (!this.hasExecuted &&
!this.isImplicitWait &&
!this.isExplicitWait &&
this.hasNextCallbacks);
}
get hasExecuted() {
return this._timeScenarioExecuted !== null;
}
get hasFinished() {
return this.hasExecuted && this._timeScenarioFinished !== null;
}
get isImplicitWait() {
return this.url === null || /{[A-Za-z0-9_ -]+}/.test(this.url);
}
get isExplicitWait() {
return this._waitToExecute;
}
get hasNextCallbacks() {
return this._nextCallbacks.length > 0;
}
get hasRequestStarted() {
return this._timeRequestStarted !== null;
}
get url() {
return this._request.uri;
}
set url(value) {
if (this.hasRequestStarted) {
throw "Can not change the URL after the request has already started.";
}
if (value !== null) {
const match = /([A-Z]+) (.*)/.exec(value);
if (match !== null) {
const verb = match[1].toLowerCase();
if (httprequest_1.HttpMethodVerbAllowedValues.includes(verb)) {
this.setMethod(verb);
}
value = match[2];
}
if (/{[A-Za-z0-9_ -]+}/.test(value)) {
this.wait();
}
}
this._request.uri = value;
}
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) {
return new Scenario(suite, title).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.request.setJsonData(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, isMultipart) {
this._request.setFormData(form, isMultipart);
return this;
}
setMaxRedirects(n) {
this._request.maxRedirects = n;
return this;
}
setBasicAuth(auth) {
this._request.auth = auth;
this._request.authType = "basic";
return this;
}
setDigestAuth(auth) {
this._request.auth = auth;
this._request.authType = "digest";
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);
}));
return this;
}
comment(input) {
const type = util_1.toType(input);
const message = type === "string"
? input
: input.isFlagpoleValue
? input.toString()
: JSON.stringify(input, null, 2);
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) {
throw `Can call open after scenario has executed`;
}
if (opts) {
this._request.setOptions(opts);
}
this.url = String(url);
this._isMock = false;
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 `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._markScenarioCompleted(null, null, "skipped");
return this;
});
}
cancel(message) {
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();
this._publish(enums_1.ScenarioStatusEvent.executionProgress);
this._markScenarioCompleted(`Cancelled ${message ? ": " + message : ""}`, null, "cancelled");
return this;
});
}
getBrowserControl() {
this._browserControl =
this._browserControl !== null
? this._browserControl
: new browsercontrol_1.BrowserControl();
return this._browserControl;
}
execute(pathParams) {
return __awaiter(this, void 0, void 0, function* () {
if (this.hasExecuted) {
throw "Scenario has already started executing. Can not call execute again.";
}
if (pathParams) {
Object.keys(pathParams).forEach((key) => {
var _a;
this.url =
((_a = this.url) === null || _a === void 0 ? void 0 : _a.replace(`{${key}}`, String(pathParams[key]))) || null;
});
}
this.wait(false);
return this;
});
}
go() {
return __awaiter(this, void 0, void 0, function* () {
if (this.hasExecuted) {
throw "Scenario has already started executing. Can not call execute again.";
}
if (!this.isReadyToExecute) {
throw "This scenario is not ready to execute.";
}
yield this._fireBefore();
this._isMock ? this._executeMock() : this._executeRequest();
this._publish(enums_1.ScenarioStatusEvent.executionProgress);
return this;
});
}
success(a, b) {
return this._pushCallbacks("success", "_successCallbacks", a, b);
}
failure(a, b) {
return this._pushCallbacks("failure", "_failureCallbacks", a, b);
}
pipe(a, b) {
return this._pushCallbacks("pipe", "_pipeCallbacks", a, b);
}
before(a, b) {
return this._pushCallbacks("before", "_beforeCallbacks", a, b);
}
after(a, b) {
return this._pushCallbacks("after", "_afterCallbacks", a, b);
}
finally(a, b) {
return this._pushCallbacks("finally", "_finallyCallbacks", a, b);
}
mock(localPath) {
this.url = localPath;
this._isMock = true;
return this;
}
setResponseType(type, opts = {}) {
if (this.hasExecuted) {
throw "Scenario was already executed. Can not change type.";
}
this._responseType = type;
if (["browser", "extjs"].includes(type)) {
const overrides = {};
if (flagpoleexecution_1.FlagpoleExecution.global.headless !== undefined) {
overrides.headless = flagpoleexecution_1.FlagpoleExecution.global.headless;
}
this._request.setOptions({
browserOptions: Object.assign(Object.assign(Object.assign({}, this._defaultBrowserOptions), opts), overrides),
});
}
else {
this._request
.setOptions(Object.assign(Object.assign({}, this._defaultRequestOptions), opts))
.setOptions({
type: this._responseType === "json"
? "json"
: this._responseType === "image"
? "image"
: "generic",
});
}
this._response = responsefactory_1.createResponse(this);
return this;
}
waitForFinished() {
return this._finishedPromise;
}
waitForResponse() {
return this._requestPromise;
}
promise() {
return new Promise((resolve, reject) => {
this.success(resolve);
this.failure(reject);
});
}
buildUrl() {
const path = this.url || "/";
if (this.suite.baseUrl === null) {
return new URL(path);
}
else if (/^https?:\/\//.test(path) || /^data:/.test(path)) {
return new URL(path);
}
else if (/^\/\//.test(path)) {
return new URL(`${this.suite.baseUrl.protocol}//${path}`);
}
else if (/^\//.test(path)) {
return new URL(`${this.suite.baseUrl.protocol}//${this.suite.baseUrl.host}${path}`);
}
return new URL(path, this.suite.baseUrl.href);
}
_pushCallbacks(name, callbacksName, a, b) {
if (this.hasFinished) {
throw `Can not add ${name} callbacks after execution has finished.`;
}
if (Array.isArray(a)) {
a.forEach((callback) => {
this[callbacksName].push({
message: "",
callback: callback,
});
});
}
else {
const { message, callback } = this._getOverloads(a, b);
this[callbacksName].push({
callback: callback,
message: message,
});
}
return this;
}
_reset() {
this._flipAssertion = false;
return this;
}
_pipeResponses(httpResponse) {
return __awaiter(this, void 0, void 0, function* () {
yield bluebird.mapSeries(this._pipeCallbacks, (cb) => __awaiter(this, void 0, void 0, function* () {
cb.message && this.comment(cb.message);
const result = yield cb.callback(httpResponse);
if (result) {
httpResponse = result;
}
}));
return httpResponse;
});
}
_processResponse(httpResponse) {
return __awaiter(this, void 0, void 0, function* () {
httpResponse = yield this._pipeResponses(httpResponse);
this._response.init(httpResponse);
this._timeRequestLoaded = Date.now();
this._requestResolve();
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, null, "aborted");
});
this._publish(enums_1.ScenarioStatusEvent.executionProgress);
});
}
_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}`, null, "aborted");
}
return;
})
.catch((err) => this._markScenarioCompleted(`Failed to load ${this._request.uri}`, err, "aborted"));
}
_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, "aborted");
});
}
_markRequestAsStarted() {
this._timeRequestStarted = Date.now();
}
_executeRequest() {
if (this.url === null) {
throw "Can not execute request with null URL.";
}
if (this.hasRequestStarted) {
throw "Request has already started.";
}
this.url = this.buildUrl().href;
this._markRequestAsStarted();
this._finalUrl = this._request.uri;
if (["extjs", "browser"].includes(this._responseType)) {
this._executeBrowserRequest();
}
else {
this._executeDefaultRequest();
}
}
_executeMock() {
if (this.url === null) {
throw "Can not execute request with null URL.";
}
if (this.hasRequestStarted) {
throw "Request has already started.";
}
this._markRequestAsStarted();
const scenario = this;
httpresponse_1.HttpResponse.fromLocalFile(this.url)
.then((mock) => {
scenario._processResponse(mock);
})
.catch((err) => {
scenario._markScenarioCompleted(`Failed to load page ${scenario.url}`, err, "aborted");
});
}
_markScenarioCompleted(message = null, details = null, disposition = "completed") {
return __awaiter(this, void 0, void 0, function* () {
if (!this.hasFinished) {
this._disposition = disposition;
if (disposition == "cancelled") {
this._publish(enums_1.ScenarioStatusEvent.executionCancelled);
}
else if (disposition == "skipped") {
this._publish(enums_1.ScenarioStatusEvent.executionSkipped);
this.comment(message);
}
else if (disposition == "aborted") {
this._publish(enums_1.ScenarioStatusEvent.executionAborted);
}
this._timeScenarioFinished = Date.now();
if (disposition == "completed" || disposition == "aborted") {
this.comment(`Took ${this.executionDuration}ms`);
}
if (disposition !== "completed" && disposition !== "skipped") {
this.result(new assertionresult_1.AssertionFail(message || disposition, details));
}
yield this._fireAfter();
this.hasPassed
? yield this._fireSuccess()
: yield this._fireFailure(details || message || disposition);
yield this._fireFinally();
if (this._browserControl !== null) {
yield this._browserControl.close();
}
}
return this;
});
}
_fireCallbacks(callbacks) {
return __awaiter(this, void 0, void 0, function* () {
yield util_1.asyncForEach(callbacks, (cb) => __awaiter(this, void 0, void 0, function* () {
cb.message && this._pushToLog(new comment_1.LogComment(cb.message));
return yield cb.callback(this, this.suite);
}));
});
}
_logScenarioHeading() {
this._pushToLog(new heading_1.LogScenarioHeading(this.title));
if (this._waitTime > 0) {
this.comment(`Waited ${this._waitTime}ms`);
}
}
_markExecutionAsStarted() {
this._timeScenarioExecuted = Date.now();
}
_fireBefore() {
return __awaiter(this, void 0, void 0, function* () {
this._markExecutionAsStarted();
yield this._fireCallbacks(this._beforeCallbacks);
this._publish(enums_1.ScenarioStatusEvent.beforeExecute);
this._logScenarioHeading();
this._publish(enums_1.ScenarioStatusEvent.executionStart);
});
}
_fireAfter() {
return __awaiter(this, void 0, void 0, function* () {
yield this._fireCallbacks(this._afterCallbacks);
this._publish(enums_1.ScenarioStatusEvent.afterExecute);
});
}
_fireSuccess() {
return __awaiter(this, void 0, void 0, function* () {
yield this._fireCallbacks(this._successCallbacks);
this._publish(enums_1.ScenarioStatusEvent.finished);
});
}
_fireFailure(errorMessage) {
return __awaiter(this, void 0, void 0, function* () {
yield this._fireCallbacks(this._failureCallbacks);
errorMessage && this._pushToLog(new comment_1.LogComment(errorMessage));
this._publish(enums_1.ScenarioStatusEvent.finished);
});
}
_fireFinally() {
return __awaiter(this, void 0, void 0, function* () {
yield this._fireCallbacks(this._finallyCallbacks);
this._publish(enums_1.ScenarioStatusEvent.finished);
this._finishedResolve();
});
}
_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 (() => {
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);
}
}
else {
throw "Scenario already finished.";
}
return this;
}
_publish(statusEvent) {
return __awaiter(this, void 0, void 0, function* () {
const scenario = this;
this._subscribers.forEach((callback) => __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