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"
+}