diff --git a/README.md b/README.md index a9fc5a7..4d7b0d1 100644 --- a/README.md +++ b/README.md @@ -1 +1,47 @@ -# agent-js-jasmine \ No newline at end of file +# agent-js-jasmine + +Agent for integration Jasmine + Protractor with ReportPortal. +[ReportPortal](http://reportportal.io/)
+[ReportPortal on GitHub](https://github.com/reportportal) + + You should include agent-js-jasmine as dependency in your package.json. + +Your repository should include protractors' config file. You should add some lines in it: +1. Import agent-js-jasmine +```var AgentJasmine = require('agent-js-jasmine');``` +2. Create object with configuration for ReportPortal +```sh +var reportPortalListener = new AgentJasmine({ + token: "00000000-0000-0000-0000-000000000000", + endpoint: "http://localhost", + launch: "LAUNCH NAME", + project: "PROJECT NAME", + mode: "DEFAULT", + tags: ["your", "tags"], + description: "DESCRIPTION" +}); +``` +3. Add some plugins +```sh +plugins: [{ + path:'node_modules/agent-js-jasmine/lock.js', + inline: { + postTest : function(passed, testInfo) { + return reportPortalListener.completeInReportPortal(); + } + } + + }] +``` + +4. Add custom reporter to Jasmine +```sh + onPrepare: function(){ + global.defaultExplicitWait = 5000; + jasmine.getEnv().addReporter(reportPortalListener); + } +``` + + + + diff --git a/jasmine-epam-reportportal-listener.js b/jasmine-epam-reportportal-listener.js new file mode 100644 index 0000000..0b803ca --- /dev/null +++ b/jasmine-epam-reportportal-listener.js @@ -0,0 +1,224 @@ +'use strict'; + +var ReportPortalClient = require('reportportal-client'); +var JasmineReportController = require('./jasmine-report-controller'); +var _ = require('lodash'); +var q = require('q'); +var l = require('./lock.js'); + +class ReportPortal { + + constructor(conf) { + this.reportPortal = new ReportPortalClient(conf); + this.controller = new JasmineReportController(); + this.launch = undefined; + this.currentSpec = undefined; + this.lock = undefined; + } + + waitForReportPortalLaunch() { + return this.lock; + } + + completeInReportPortal() { + // Force protractor wait for spec + setTimeout(function () { }, 500); + return this.currentSpec; + } + + jasmineStarted(info) { + l.rpListener = this; + this.totalSpecsToDo = info.totalSpecsDefined; + var _self = this; + var request = { + name: _self.reportPortal.helpers.formatName(_self.reportPortal.config.launch), + start_time: _self.reportPortal.helpers.now(), + description: _self.reportPortal.config.description === undefined ? "" : _self.reportPortal.config.description, + tags: _self.reportPortal.config.tags + }; + this.launch = _self.reportPortal.startLaunch(request); + browser.getCapabilities().then(function (caps) { + _self.launch.then(function (launchId) { + _self.reportPortal.updateLaunchDescription(launchId.id, [ + caps.get('browserName'), + caps.get('version'), + caps.get('platform') + ].join(" : ")); + }); + }); + } + + suiteStarted(suite) { + + var reportPortal = this.reportPortal; + if (this.controller.isSuiteStackEmpty()) { + this.controller.registerSuiteStarted(suite.id); + var itemId = this.launch.then(function (launchId) { + var request = { + name: reportPortal.helpers.formatName(suite.description), + launch_id: launchId.id, + start_time: reportPortal.helpers.now(), + type: "SUITE", + description: suite.fullName, + tags: reportPortal.config.tags + }; + return reportPortal.startTestItem( request ); + }); + this.controller.update(suite.id, { _self: itemId }); + } else { + this.controller.registerSuiteStarted(suite.id); + var parentSuite = this.controller.getParent(suite.id); + var itemId = this.launch.then(function (launchId) { + return parentSuite._self.then(function (parentId) { + var request = { + name: reportPortal.helpers.formatName(suite.description), + launch_id: launchId.id, + start_time: reportPortal.helpers.now(), + type: "TEST", + description: suite.fullName, + tags: reportPortal.config.tags + }; + return reportPortal.startTestItem( request, parentId.id); + }); + }); + this.controller.update(suite.id, { _self: itemId }); + } + } + + specStarted(spec) { + var reportPortal = this.reportPortal; + this.controller.registerTestStarted(spec.id); + var parentSuite = this.controller.getParent(spec.id); + var testId = this.launch.then(function (launchId) { + return parentSuite._self.then(function (parentId) { + var request = { + name: reportPortal.helpers.formatName(spec.description), + launch_id: launchId.id, + start_time: reportPortal.helpers.now(), + type: "STEP", + description: spec.fullName, + tags: reportPortal.config.tags + }; + return reportPortal.startTestItem(request, parentId.id); + }); + }); + + this.controller.update(spec.id, { _self: testId }); + } + + specDone(spec) { + var reportPortal = this.reportPortal; + var controller = this.controller; + var _self = this; + + function finish(jasmineId) { + _self.totalSpecsToDo--; + controller.registerTestFinished(jasmineId); + var item = controller.get(jasmineId); + var itemId = item._self; + var itemStatus = spec.status; + if (spec.status === "pending" || spec.status === "disabled") { + itemStatus = "skipped"; + } + + if (itemStatus === "failed") { + + var failures = []; + var message; + _.each(spec.failedExpectations, function (failure) { + failures.push( `message: ${failure.message.replace(/(?:\r\n|\r|\n)/g, "
")}`); + failures.push( `stackTrace: ${failure.stack.replace(/(?:\r\n|\r|\n)/g, "
")}`); + }); + itemId.then(function (id) { + var request = { + item_id: id.id, + time: _self.reportPortal.helpers.now(), + level: "ERROR", + message: failures.join('
') + }; + reportPortal.log( request); + }); + } + + var finished = browser.takeScreenshot().then(function (png) { + return png; + }).then(function (png) { + return itemId.then(function (id) { + var json = [{ + item_id: id.id, + time: _self.reportPortal.helpers.now(), + level: "INFO", + message: spec.fullName, + file: { name: spec.fullName } + }]; + return reportPortal.sendFile(json, spec.fullName, png, "image/png"); + }); + }).then(function (captured) { + return itemId.then(function (id) { + var request = { + status: itemStatus, + end_time: _self.reportPortal.helpers.now() + }; + return reportPortal.finishTestItem(id.id, request); + }); + }); + + _self.currentSpec = finished; + controller.update(jasmineId, { finished: finished }) + } + finish(spec.id); + } + + suiteDone(suite) { + var _self = this; + var controller = this.controller; + var reportPortal = this.reportPortal; + + controller.registerSuiteFinished(suite.id); + + function finish(jasmineId) { + var item = controller.get(jasmineId); + var itemId = item._self; + return itemId.then(function (id) { + var request = { + status: "passed", + end_time: _self.reportPortal.helpers.now() + }; + return reportPortal.finishTestItem(id.id, request); + }); + } + + var syncClients = setInterval(function () { + var root = _.first(controller.getFlatReport()); + var allDone = controller.allDone(suite.id); + var unresolvedChild = _.filter(allDone, function (item) { + return 'undefined' === typeof item; + }); + if (unresolvedChild.length === 0) { + + _self.lock = Promise.all(allDone).then(function (values) { + var finished = finish(suite.id); + controller.update(suite.id, { finished: finished }); + if (_self.totalSpecsToDo === 0) { + return finished.then(function (done) { + return _self.launch.then(function (lid) { + var request = { + end_time: _self.reportPortal.helpers.now() + }; + return reportPortal.finishLaunch(lid.id, request).then(function (result) { + console.log("FINISHED JASMINE LAUNCH"); + l.resolve(); + }); + }); + }); + } else { + return l.getPromise(); + } + }); + clearInterval(syncClients); + } + }, 100); + } +} + +module.exports = ReportPortal; \ No newline at end of file diff --git a/jasmine-report-controller.js b/jasmine-report-controller.js new file mode 100644 index 0000000..9247c44 --- /dev/null +++ b/jasmine-report-controller.js @@ -0,0 +1,165 @@ +'use strict'; + +var _ = require('lodash'); +var fs = require('fs'); +var mkdirp = require('mkdir'); + +class JasmineReportController{ + + constructor(){ + this.stack = []; + this.flatReport = new Map([]); + } + + get(jasmineId){ + return this.flatReport.get(jasmineId); + } + + put(jasmineId, reportItem){ + this.flatReport.set(jasmineId, reportItem); + } + + getClosureSuite(){ + return this.get(_.last(this.stack)); + } + + registerSuiteStarted(jasmineId){ + var item = {}; + if(!this.isSuiteStackEmpty()){ + var closure = this.getClosureSuite(); + item._parent = closure.id; + } + this.stack.push(jasmineId); + item.id = jasmineId; + item.type = 'SUITE'; + item.state = 'started'; + item.level = this.getSuiteLevel(); + this.put(jasmineId, item); + } + + registerTestStarted(jasmineId){ + var item = {}; + if(!this.isSuiteStackEmpty()){ + var closure = this.getClosureSuite(); + item._parent = closure.id; + } + item.id = jasmineId; + item.type = 'TEST'; + item.state = 'started'; + item.level = this.getTestLevel(); + this.put(jasmineId, item); + } + + getParent(jasmineId){ + var parentId = this.get(jasmineId)._parent; + return parentId !== undefined ? this.get(parentId) : undefined; + } + + registerSuiteFinished(jasmineId){ + if(this.isCurrentSuiteStack(jasmineId)){ + this.popSuiteStack(); + this.update(jasmineId, {state: "done"}); + } + } + + registerTestFinished(jasmineId){ + this.update(jasmineId, {state: "done"}); + } + + update(jasmineId, update){ + this.put(jasmineId, Object.assign(this.get(jasmineId), update)); + } + + isCurrentSuiteStack(jasmineId){ + return jasmineId === _.last(this.stack); + } + + popSuiteStack(){ + this.stack.pop(); + } + + isSuiteStackEmpty(){ + return this.stack.length === 0; + } + + collect(){ + var result = this.getFlatReport(); + if(this.isSuiteStackEmpty()){ + this.flatReport.clear(); + } + return result; + } + + getFlatReport(){ + var result = []; + this.flatReport.forEach(function(item, key){ + result.push(item); + }); + return result; + } + + getChilds(result, rootRef){ + return _.filter(result, function (item) { + return ('undefined' !== typeof item._parent) && item._parent === rootRef; + }); + } + + totalFinishes(){ + return _.map(this.getFlatReport(), function(item){ + return item.finished; + }); + } + + allDone(jasmineId){ + var result = this.getFlatReport(); + var childs = this.getChilds(result, jasmineId); + return _.map(childs, function(item){ + return item.finished; + }); + } + + hasChilds(jasmineId){ + return this.getChilds(this.getFlatReport(), jasmineId).length > 0; + } + + collectTree() { + var result = this.collect(); + _.each(result, function (item) { + if ('SUITE' === item.type) { + var childs = this.getChilds(result, item.id); + item.nested = _.filter(childs, function (child) { + return 'SUITE' === child.type; + }); + item.tests = _.filter(childs, function (child) { + return 'TEST' === child.type; + }); + } + }); + return _.first(result); + } + + getSuiteLevel(){ + return this.stack.length; + } + + getTestLevel(){ + return this.stack.length + 1; + } + + takeScreenshot(jasmineSpec){ + var screenShotName = jasmineSpec.id + " " + jasmineSpec.fullName + ".png"; + var dest = "target/screenshots" + var path = dest + "/" + screenShotName; + this.update(jasmineSpec.id, {screenshot_src: path}); + mkdirp(dest); + browser.takeScreenshot().then(function (png) { + var stream = fs.createWriteStream(path); + stream.write(new Buffer(png, 'base64')); + stream.end(); + }); + + } + +} + +module.exports = JasmineReportController; \ No newline at end of file diff --git a/lock.js b/lock.js new file mode 100644 index 0000000..16d28a5 --- /dev/null +++ b/lock.js @@ -0,0 +1,19 @@ +var q = require('q'); + +var deferred = q.defer(); + +var p = deferred.promise; + +exports.getPromise = function(){ + return p; +} + +exports.resolve = function () { + console.log("Resolve deferred"); + deferred.resolve.apply(deferred, arguments); +}; + +exports.teardown = function () { + console.log("TEAR DOWN TEST RUN"); + return p; +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..33a4121 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "agent-js-jasmine", + "version": "2.0.0", + "description": "Agent for integration Jasmine + Protractor with ReportPortal.", + "main": "jasmine-epam-reportportal-listener.js", + "dependencies": { + "reportportal-client": "4.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/reportportal/agent-js-jasmine.git" + }, + "devDependencies": { + "restler": "3.4.0", + "node-rest-client": "1.5.1", + "q": "1.4.1", + "lodash": "^3.10.1", + "protractor": "^3.0.0", + "mkdir":"" + }, + "author": "", + "license": "ISC" +}