UNPKG

flagpole

Version:

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

616 lines 22.3 kB
"use strict"; 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