flagpole
Version:
Simple and fast DOM integration, headless or headful browser, and REST API testing framework.
616 lines • 22.3 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const consoleline_1 = require("./consoleline");
const response_1 = require("./response");
const browser_1 = require("./browser");
const Bluebird = require("bluebird");
const r = require("request");
const responsefactory_1 = require("./responsefactory");
const assertioncontext_1 = require("./assertioncontext");
const assertionresult_1 = require("./assertionresult");
const request = require('request');
const probeImage = require('probe-image-size');
var ScenarioStatusEvent;
(function (ScenarioStatusEvent) {
ScenarioStatusEvent[ScenarioStatusEvent["beforeExecute"] = 0] = "beforeExecute";
ScenarioStatusEvent[ScenarioStatusEvent["executionProgress"] = 1] = "executionProgress";
ScenarioStatusEvent[ScenarioStatusEvent["afterExecute"] = 2] = "afterExecute";
ScenarioStatusEvent[ScenarioStatusEvent["finished"] = 3] = "finished";
})(ScenarioStatusEvent = exports.ScenarioStatusEvent || (exports.ScenarioStatusEvent = {}));
class Scenario {
constructor(suite, title, onCompletedCallback) {
this._subscribers = [];
this._nextCallbacks = [];
this._nextMessages = [];
this._beforeCallbacks = [];
this._afterCallbacks = [];
this._finallyCallbacks = [];
this._errorCallbacks = [];
this._failureCallbacks = [];
this._successCallbacks = [];
this._log = [];
this._failures = [];
this._passes = [];
this._timeScenarioInitialized = Date.now();
this._timeScenarioExecuted = null;
this._timeRequestStarted = null;
this._timeRequestLoaded = null;
this._timeScenarioFinished = null;
this._responseType = response_1.ResponseType.html;
this._redirectCount = 0;
this._finalUrl = null;
this._url = null;
this._waitToExecute = false;
this._flipAssertion = false;
this._ignoreAssertion = false;
this._options = {};
this._followRedirect = null;
this._browser = null;
this._isMock = false;
this._defaultBrowserOptions = {
headless: true,
recordConsole: true,
outputConsole: false,
};
this._defaultRequestOptions = {
method: 'GET',
headers: {}
};
this.suite = suite;
this._cookieJar = new request.jar();
this._options = this._defaultRequestOptions;
this._title = title;
this._onCompletedCallback = onCompletedCallback;
}
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) : null;
}
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._failures.length > 0);
}
get hasPassed() {
return !!(this.hasFinished && this._failures.length == 0);
}
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._url;
}
get finalUrl() {
return this._finalUrl;
}
get requestOptions() {
return this._options;
}
static create(suite, title, type, opts, onCompletedCallback) {
const scenario = new Scenario(suite, title, onCompletedCallback);
opts = (() => {
return (type == response_1.ResponseType.browser || type == response_1.ResponseType.extjs) ? Object.assign({}, scenario._defaultBrowserOptions, opts) : Object.assign({}, scenario._defaultRequestOptions, opts);
})();
return scenario._setResponseType(type, opts);
}
getLog() {
return __awaiter(this, void 0, void 0, function* () {
let output = [];
output = this._log;
return output;
});
}
subscribe(callback) {
this._subscribers.push(callback);
}
setJsonBody(jsonObject) {
this.setHeader('Content-Type', 'application/json');
return this.setRawBody(JSON.stringify(jsonObject));
}
setRawBody(str) {
this._options.body = str;
return this;
}
verifySslCert(verify) {
this._options.strictSSL = verify;
this._options.rejectUnauthorized = verify;
return this;
}
setProxyUrl(proxyUrl) {
this._options.proxy = proxyUrl;
return this;
}
setTimeout(timeout) {
this._options.timeout = timeout;
return this;
}
setFormData(form) {
this._options.form = form;
return this;
}
setMaxRedirects(n) {
this._options.maxRedirects = n;
return this;
}
shouldFollowRedirects(onRedirect) {
this._followRedirect = onRedirect;
return this;
}
setBasicAuth(authorization) {
this._options.auth = authorization;
return this;
}
setBearerToken(token) {
this.setHeader('Authorization', `Bearer ${token}`);
return this;
}
setCookie(key, value, opts) {
let cookie = r.cookie(key + '=' + value);
if (cookie !== undefined) {
this._cookieJar.setCookie(cookie, this._buildUrl(), opts);
}
else {
throw new Error('error setting cookie');
}
return this;
}
setHeaders(headers) {
this._options.headers = Object.assign({}, this._options.headers, headers);
return this;
}
setHeader(key, value) {
this._options.headers = this._options.headers || {};
this._options.headers[key] = value;
return this;
}
setMethod(method) {
this._options.method = method.toUpperCase();
return this;
}
wait(bool = true) {
this._waitToExecute = bool;
return this;
}
subheading(message) {
this._log.push(new consoleline_1.SubheadingLine(message));
return this;
}
comment(message) {
this._log.push(new consoleline_1.CommentLine(message));
return this;
}
logResult(result) {
if (result.passed) {
this._passes.push(result);
this._log.push(new consoleline_1.PassLine(result.message));
}
else if (!result.isOptional) {
this._failures.push(result);
this._log.push(new consoleline_1.FailLine(result.message));
(result.details !== null) && this._log.push(new consoleline_1.DetailLine(result.details));
}
else {
this._log.push(new consoleline_1.OptionalFailLine(result.message));
(result.details !== null) && this._log.push(new consoleline_1.DetailLine(result.details));
}
return this;
}
logWarning(message) {
this._log.push(new consoleline_1.WarningLine(message));
return this;
}
ignore(assertions = true) {
if (typeof assertions == 'boolean') {
this._ignoreAssertion = assertions;
}
else if (typeof assertions == 'function') {
this.ignore(true);
assertions();
this.ignore(false);
}
return this;
}
open(url) {
if (!this.hasExecuted) {
this._url = url;
this._isMock = false;
this._executeWhenReady();
}
return this;
}
next(a, b) {
const callback = (() => {
if (typeof b == 'function') {
return b;
}
else if (typeof a == 'function') {
return a;
}
else {
throw new Error('No callback provided.');
}
})();
const message = (function () {
if (typeof a == 'string' && a.trim().length > 0) {
return a;
}
return null;
})();
if (!this.hasExecuted) {
this._nextCallbacks.push(callback);
this._nextMessages.push(message);
setTimeout(() => {
this._executeWhenReady();
}, 0);
}
else {
throw new Error('Scenario already executed.');
}
return this;
}
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.`);
}
const scenario = this;
yield this._fireBefore();
message = "Skipped" + (message ? ': ' + message : '');
scenario._publish(ScenarioStatusEvent.executionProgress);
scenario._log.push(new consoleline_1.CommentLine(message));
yield scenario._fireAfter();
yield scenario._fireFinally();
return this;
});
}
getBrowser() {
this._browser = (this._browser !== null) ? this._browser : new browser_1.Browser();
return this._browser;
}
execute() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.hasExecuted && this._url !== null) {
yield this._fireBefore();
this.subheading(this.title);
if (this._waitToExecute) {
this._log.push(new consoleline_1.CommentLine(`Waited ${this.executionDuration}ms`));
}
this._publish(ScenarioStatusEvent.executionProgress);
this._isMock ?
this._executeMock() :
this._executeRequest();
}
return this;
});
}
error(callback) {
this._errorCallbacks.push(callback);
return this;
}
success(callback) {
this._successCallbacks.push(callback);
return this;
}
failure(callback) {
this._failureCallbacks.push(callback);
return this;
}
before(callback) {
this._beforeCallbacks.push(callback);
return this;
}
after(callback) {
this._afterCallbacks.push(callback);
return this;
}
finally(callback) {
this._finallyCallbacks.push(callback);
return this;
}
image(opts) {
return this._setResponseType(response_1.ResponseType.image, Object.assign({}, this._defaultRequestOptions, opts));
}
video(opts) {
return this._setResponseType(response_1.ResponseType.video, Object.assign({}, this._defaultRequestOptions, opts));
}
html(opts) {
return this._setResponseType(response_1.ResponseType.html, Object.assign({}, this._defaultRequestOptions, opts));
}
json(opts = {}) {
return this._setResponseType(response_1.ResponseType.json, Object.assign({}, this._defaultRequestOptions, opts));
}
script(opts = {}) {
return this._setResponseType(response_1.ResponseType.script, Object.assign({}, this._defaultRequestOptions, opts));
}
stylesheet(opts = {}) {
return this._setResponseType(response_1.ResponseType.stylesheet, Object.assign({}, this._defaultRequestOptions, opts));
}
resource(opts = {}) {
return this._setResponseType(response_1.ResponseType.resource, Object.assign({}, this._defaultRequestOptions, opts));
}
browser(opts = {}) {
return this._setResponseType(response_1.ResponseType.browser, Object.assign({}, this._defaultBrowserOptions, opts));
}
extjs(opts = {}) {
return this._setResponseType(response_1.ResponseType.extjs, Object.assign({}, this._defaultBrowserOptions, opts));
}
mock(localPath) {
this._url = localPath;
this._isMock = true;
this._executeWhenReady();
return this;
}
_reset() {
this._flipAssertion = false;
return this;
}
_getCookies() {
return this._cookieJar.getCookies(this._options.uri);
}
_processResponse(r) {
const scenario = this;
const response = responsefactory_1.createResponse(this, r);
const context = new assertioncontext_1.AssertionContext(scenario, response);
this._timeRequestLoaded = Date.now();
this.logResult(assertionresult_1.AssertionResult.pass('Loaded ' + response.typeName + ' ' + this._url));
let lastReturnValue = null;
this._publish(ScenarioStatusEvent.executionProgress);
Bluebird.mapSeries(scenario._nextCallbacks, (_then, index) => {
const comment = scenario._nextMessages[index];
comment !== null && this.comment(comment);
context.result = lastReturnValue;
lastReturnValue = _then.apply(context, [context]);
return lastReturnValue;
}).then(() => {
scenario._markScenarioCompleted();
}).catch((err) => {
scenario._markScenarioCompleted(err);
});
this._publish(ScenarioStatusEvent.executionProgress);
}
_buildUrl() {
return this.suite.buildUrl(this._url || '');
}
_setResponseType(type, opts = {}) {
if (this.hasExecuted) {
throw new Error('Scenario was already executed. Can not change type.');
}
this._options = opts;
this._responseType = type;
return this;
}
_executeImageRequest() {
const scenario = this;
probeImage(this._options.uri, this._options)
.then(result => {
const response = response_1.NormalizedResponse.fromProbeImage(result, scenario._getCookies());
scenario._finalUrl = scenario.url;
scenario._processResponse(response);
})
.catch(err => {
scenario._markScenarioCompleted(`Failed to load image ${scenario._url}`, err);
});
}
_executeBrowserRequest() {
const scenario = this;
this.getBrowser()
.open(this._options)
.then((next) => {
const response = next.response;
const body = next.body;
if (response !== null) {
scenario._finalUrl = response.url();
scenario._processResponse(response_1.NormalizedResponse.fromPuppeteer(response, body, scenario._getCookies()));
}
else {
scenario._markScenarioCompleted(`Failed to load ${scenario._url}`);
}
return;
})
.catch(err => scenario._markScenarioCompleted(`Failed to load ${scenario._url}`, err));
}
_executeDefaultRequest() {
const scenario = this;
this._options.followRedirect = (this._followRedirect === null) ?
(response) => {
const url = require('url');
scenario._finalUrl = response.request.href;
if (response.headers.location) {
scenario._finalUrl = url.resolve(response.headers.location, response.request.href);
}
scenario._redirectCount++;
return true;
} : this._followRedirect;
request(this._options, function (err, response, body) {
if (!err) {
scenario._processResponse(response_1.NormalizedResponse.fromRequest(response, body, scenario._getCookies()));
}
else {
scenario._markScenarioCompleted(`Failed to load ${scenario._url}`, err);
}
});
}
_executeRequest() {
if (!this._timeRequestStarted && this._url !== null) {
this._timeRequestStarted = Date.now();
this._options.uri = this._buildUrl();
this._options.jar = this._cookieJar;
if (this._responseType == response_1.ResponseType.image) {
this._executeImageRequest();
}
else if (this._responseType == response_1.ResponseType.browser ||
this._responseType == response_1.ResponseType.extjs) {
this._executeBrowserRequest();
}
else {
this._executeDefaultRequest();
}
}
}
_executeMock() {
if (!this._timeRequestStarted && this._url !== null) {
const scenario = this;
this._timeRequestStarted = Date.now();
response_1.NormalizedResponse.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();
}
}
_markScenarioCompleted(errorMessage = null, errorDetails) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.hasFinished) {
yield this._fireAfter();
this._log.push(new consoleline_1.CommentLine(`Took ${this.executionDuration}ms`));
if (errorMessage === null) {
this.hasPassed ?
yield this._fireSuccess() :
yield this._fireFailure();
}
else {
this.logResult(assertionresult_1.AssertionResult.fail(errorMessage, errorDetails));
yield this._fireError(errorDetails || errorMessage);
}
yield this._fireFinally();
}
return this;
});
}
_fireBefore() {
const scenario = this;
this._timeScenarioExecuted = Date.now();
return new Promise(function (resolve, reject) {
return __awaiter(this, void 0, void 0, function* () {
Bluebird.mapSeries(scenario._beforeCallbacks, (_then, index) => {
return _then.apply(scenario, [scenario]);
}).then(() => {
scenario._publish(ScenarioStatusEvent.beforeExecute);
resolve();
}).catch((err) => {
reject(err);
});
});
});
}
_fireAfter() {
const scenario = this;
this._timeScenarioFinished = Date.now();
return new Promise((resolve, reject) => {
Bluebird.mapSeries(this._afterCallbacks, (_then, index) => {
return _then.apply(scenario, [scenario]);
}).then(() => {
this._publish(ScenarioStatusEvent.afterExecute);
resolve();
}).catch((err) => {
reject(err);
});
});
}
_fireSuccess() {
const scenario = this;
return new Promise((resolve, reject) => {
Bluebird.mapSeries(this._successCallbacks, (_then, index) => {
return _then.apply(scenario, [scenario]);
}).then(() => {
this._publish(ScenarioStatusEvent.finished);
resolve();
}).catch((err) => {
reject(err);
});
});
}
_fireFailure() {
const scenario = this;
return new Promise((resolve, reject) => {
Bluebird.mapSeries(this._failureCallbacks, (_then, index) => {
return _then.apply(scenario, [scenario]);
}).then(() => {
this._publish(ScenarioStatusEvent.finished);
resolve();
}).catch((err) => {
reject(err);
});
});
}
_fireError(error) {
const scenario = this;
return new Promise((resolve, reject) => {
Bluebird.mapSeries(this._errorCallbacks, (_then) => {
return _then.apply(scenario, [error, scenario]);
}).then(() => {
this._publish(ScenarioStatusEvent.finished);
resolve();
}).catch((err) => {
reject(err);
});
});
}
_fireFinally() {
const scenario = this;
return new Promise((resolve, reject) => {
Bluebird.mapSeries(this._finallyCallbacks, (_then, index) => {
return _then.apply(scenario, [scenario]);
}).then(() => {
this._onCompletedCallback(scenario);
this._publish(ScenarioStatusEvent.finished);
resolve();
}).catch((err) => {
reject(err);
});
});
}
_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);
});
});
});
}
}
exports.Scenario = Scenario;
//# sourceMappingURL=scenario.js.map