Socket
Socket
Sign inDemoInstall

testrail-jest-reporter

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

testrail-jest-reporter - npm Package Compare versions

Comparing version 1.0.8 to 1.1.0

.github/workflows/test.yml

54

index.js

@@ -8,3 +8,3 @@ 'use strict';

const message = chalk.bold.green;
const {baseUrl, regex, milestone, project_id, user, pass} = require(configPath);
const {baseUrl, regex, milestone, project_id, suite_mode, user, pass} = require(configPath);
const Utils = require('./src/utils');

@@ -14,4 +14,3 @@ const caller = require('./src/caller');

class CustomTestrailReporter {
tests = null
results = []
/**

@@ -29,5 +28,8 @@ * constructor for the reporter

this._options.project_id = _options && _options.project_id || project_id;
this._options.suite_mode = _options && _options.suite_mode || suite_mode;
this._options.run_update = (_options && _options.hasOwnProperty('publish_results')) ? _options.publish_results : true;
this._options.auth = 'Basic ' + new Buffer.from(user + ':' + pass, 'utf-8').toString('base64');
caller.init(this._options);
this._utils = new Utils({regex: regex || null, statuses: _options && _options.statuses});
this.results = []
}

@@ -43,10 +45,9 @@

onRunStart(_results, _options) {
if (this._options.project_id) {
caller.get_tests()
.then(_tests => this.tests = _tests);
if (this._options.project_id && !isNaN(this._options.project_id) && this._options.milestone) {
caller.get_milestone_id();
}
else {
console.log(error(`! Testrail Jest Reporter Error !`));
console.log(warning(`You must define "project_id" in jets configurations!
\n Example: "reporters": [ ["testrail-jest-reporter", { "project_id": "1" }] ]`));
console.log(warning(`You must define "project_id" and "milestone" in jets configurations!
\n Example: "reporters": [ ["testrail-jest-reporter", { "project_id": "1", "milestone": "Sprint 1" }] ]`));
}

@@ -73,6 +74,10 @@ }

onTestResult(_test, _testResults, _aggregatedResult) {
if (this.tests) {
if (caller._milestone_id) {
_testResults.testResults.forEach((result) => {
const testcases = this._utils.formatCase(result);
if (testcases) this._accumulateResults(testcases);
if (testcases) {
for (let i=0, len = testcases.length; i<len; i++) {
this.results.push(testcases[i])
}
}
});

@@ -90,7 +95,16 @@ }

onRunComplete(_contexts, _results) {
caller.add_results(this.results)
.then(count => {
if (count) console
.log(message(`Testrail Jest Reporter updated ${count} tests in ${this.results.length} runs.`));
});
if (caller._milestone_id) {
console.log(message('Testrail Jest Reporter is updating tests results...'));
caller.get_tests()
.then(() => {
return caller.add_results(this.results)
})
.then(({tests_count, runs_count}) => {
if (tests_count) console
.log(message(`\nTestrail Jest Reporter updated ${tests_count} tests in ${runs_count} runs.`));
})
.catch(e => {
console.log(error(`! Testrail Jest Reporter Error !\n${e.stack}`));
});
}
}

@@ -103,14 +117,4 @@

}
_accumulateResults(testcases_list) {
for (let i=0, len = testcases_list.length; i<len; i++) {
let index = -1;
const test = this.tests.find(test => test.case_id === testcases_list[i].case_id);
const run_id = test && test.run_id;
if (run_id && !!this.results.length) index = this.results.findIndex(run => run.id === run_id);
if (~index) this.results[index].results.push(testcases_list[i]);
else if (run_id) this.results.push({id: run_id, "results": [testcases_list[i]]});
}
}
}
module.exports = CustomTestrailReporter;
{
"name": "testrail-jest-reporter",
"version": "1.0.8",
"version": "1.1.0",
"description": "Custom Jest reporter for Testrail synchronization",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -22,2 +22,6 @@ [![TestRail v6.7](https://img.shields.io/badge/TestRail%20API-v2-green.svg)](http://docs.gurock.com/testrail-api2/start) [![NPM](https://img.shields.io/npm/l/testrail-jest-reporter)](https://github.com/AntonChaukin/testrail-jest-reporter/blob/main/LICENSE) [![NPM](https://img.shields.io/node/v/testrail-jest-reporter)](https://github.com/AntonChaukin/testrail-jest-reporter/blob/main/package.json)

- Specify the TestRail Milestone name as parameter 'milestone' _(recommended)_.
- Specify the TestRail **`suite mode`** id as parameter 'suite_mode' _(recommended)_. If that parameter is not specified, the Reporter will get this automatically.
>single repository for all cases - `suite_mode:1`<br>
single repository with baseline support - `suite_mode:2`<br>
multiple test suites to manage cases - `suite_mode:3`<br>
- There is no 'pending' or 'skipped' test result status in the TestRail results default <br>statuses.

@@ -35,6 +39,7 @@ You can add your custom status to the TestRail and specify it id as parameter

"jest-2-testrail",
{ project_id: "1",
{ project_id: 1,
baseUrl: 'http://localhost',
milestone: '<milestone_name>',
statuses: {pending: "7"}
suite_mode: 3,
statuses: {pending: 7}
},

@@ -58,2 +63,3 @@ ]

"milestone": '<milestone_name>',
"suite_mode": "3",
"statuses": {"pending": "7"}

@@ -100,5 +106,6 @@ }

### Add TestRail tests Runs
The first version of the Reporter requires you to add tests Runs with all tests you want to automate.
The Reporter parse all TestRail tests Plans
<br>and test Runs of the Milestone to collect testcases.
You can add a TestRail tests Runs or tests Plan with all tests you want to automate.<br>
If you don't, the Reporter will publish Jest tests results into the new TestRail test Run.<br>
Each time the Jest runs tests the Reporter parse all TestRail tests Plans
<br>and tests Runs of the Milestone to collect testcases.
The Reporter collects only unique testcases,

@@ -137,3 +144,3 @@ <br>if you have several tests Runs with one testcase

**This version:**
- Add new tests Run if there are testcases that are not present in any of the existing TestRail tests Runs.
- ~~Add new tests Run if there are testcases that are not present in any of the existing TestRail tests Runs.~~ >> **Done in 1.1.0**
- Add new test Runs if the Milestone not specified.

@@ -140,0 +147,0 @@ - Add new TestRail Milestone if the specified Milestone not present in the Project.

'use strict';
const chalk = require('chalk'), tr_api = require('./interface'), ReporterError = require('./error');
const chalk = require('chalk'), tr_api = require('../lib/interface'), ReporterError = require('../lib/error'),
Utils = require('./utils');
const utils = new Utils();
const Ajv = require("ajv").default;
const ajv = new Ajv({
strict: false,
allErrors: true,
});
const error = chalk.bold.red;
module.exports = {
init: init,
add_results: add_results,
get_tests: get_tests
init,
get_milestone_id,
add_results,
get_tests
}

@@ -15,2 +23,4 @@

this._project_id = _options.project_id;
this._suite_mode = _options.suite_mode;
this._run_update = _options.run_update;
tr_api.defaults.headers['Authorization'] = _options.auth;

@@ -20,37 +30,59 @@ tr_api.defaults.baseUrl = this._baseUrl + '/index.php?/api/v2/';

function add_results(testsResults) {
return Promise.all(
testsResults.map(run => {
return tr_api.add_results_for_cases(run.id, {"results": run.results});
})
)
.then(response => {
let count = 0;
response.map(run => {
run.map((result) => {
if (result && result.id) count++;
});
/**
*
* @param {array<object>} testsResults
* @return {Promise<number | boolean>}
*/
async function add_results(testsResults) {
const results = utils.groupCases(testsResults, this._tests);
let runs = results.filter(result => result.hasOwnProperty('run_id'));
const cases = results.filter(result => result.hasOwnProperty('case_id'));
if (!!cases.length) {
const updated_runs = await update_run.call(this, cases);
runs = runs.concat(updated_runs);
}
return Promise.all(
runs.map(run => {
return tr_api.add_results_for_cases(run.run_id, {"results": run.results});
})
)
.then(response => {
let tests_count = 0;
response.map(run => {
run.map((result) => {
if (result && result.id) tests_count++;
});
return count;
})
.catch((err) => {
console.log(error(err));
return false;
});
let runs_ids = runs.map(run => run.run_id).filter((id, i, arr) => !arr.includes(id,i+1))
return {tests_count, runs_count: runs_ids.length};
})
.catch((err) => {
console.log(error(err));
return false;
});
}
function get_tests() {
function get_milestone_id() {
this._milestone_id = null;
this._runs_ids = [];
this._tests = [];
return tr_api.get_milestones(this._project_id, {is_completed: 0})
return get_suite_mode.call(this)
.then(() => tr_api.get_milestones(this._project_id, {is_completed: 0}))
.then(res => {
const _milestone = res.filter((milestone) => milestone.name === this._milestone_name);
if (_milestone && !!_milestone.length) {
this._milestone_id = _milestone[0].id;
return tr_api.get_plans(this._project_id, {is_completed: 0, milestone_id: this._milestone_id});
} else {
throw new ReporterError(`Can not find milestone with name ${this._milestone_name}!
\nNo one tests results will be reported.
\nPlease, check the "milestone" param you specified in congif and try again`)
}
return false;
})
.catch(e => {
console.log(error(e.stack));
})
}
function get_tests() {
this._runs_ids = [];
this._tests = [];
return tr_api.get_plans(this._project_id, {is_completed: 0, milestone_id: this._milestone_id})
.then(res => {

@@ -71,3 +103,7 @@ if (res) {

for (let j=0, len = plan.entries.length; j<len; j++) {
plan.entries[j].runs.map(run => this._runs_ids.push(run.id));
plan.entries[j].runs
.map(run => {
this._runs_ids
.push({id: run.id, suite_id: run.suite_id, plan_id: run.plan_id})
});
}

@@ -88,7 +124,8 @@ }

for (let i=0, len = res.length; i<len; i++) {
if (res[i] && res[i].id) this._runs_ids.push(res[i].id);
if (res[i] && res[i].id) this._runs_ids
.push({id: res[i].id, suite_id: res[i].suite_id, plan_id: null});
}
}
if (!!this._runs_ids.length) {
return Promise.all(this._runs_ids.map(id => tr_api.get_tests(id)));
return Promise.all(this._runs_ids.map(run => tr_api.get_tests(run.id)));
}

@@ -102,4 +139,5 @@ return false;

for(let j=0, t_len=tests.length; j<t_len; j++) {
if (tests[j] && tests[j].case_id) this._tests
.push({"case_id": tests[j].case_id, "run_id": tests[j].run_id})
if (tests[j] && tests[j].case_id) {
this._tests.push({"case_id": tests[j].case_id, "run_id": tests[j].run_id})
}
}

@@ -111,11 +149,157 @@ }

.filter((test, i, arr) => !arr.slice(i+1).find(t => t.case_id === test.case_id));
return this._tests;
}
throw new ReporterError(`There is no one Testrail testcase was finding in Project id=${this._project_id}
by milestone "${this._milestone_name}"`);
})
.catch((err) => {
console.log(error(err.stack));
return false;
});
}
function get_suite_mode() {
if (!this._suite_mode) {
return tr_api.get_project(this._project_id)
.then(resp => this._suite_mode = resp.suite_mode)
.catch(e => {
this._suite_mode = 2;
console.log(error(`The trying to get TestRail Project was failed!
\n The suite mode was defined as 2 by default.
\n Context: ${e.stack}`))
});
}
return Promise.resolve();
}
/**
*
* @param {object} cases
* @return {Promise<[]>}
*/
async function update_run(cases) {
const valid = ajv.validate({
"type": "array",
"items": {
"type": "object",
"properties": {
"case_id": {
"type": "integer"
},
"result": {
"type": "object",
"properties": {
"case_id": {
"type": "integer"
},
"status_id": {
"type": "integer"
},
"comment": {
"type": "string"
},
"elapsed": {
"type": "string"
},
"defects": {
"type": "string"
},
"version": {
"type": "string"
}
},
}
},
"required": ["case_id", "result"]
}
},
cases);
if (!valid) {
console.log(error(`\nCan not update cases of test JSON schema error
\nContext: ${JSON.stringify(cases)}\n${JSON.stringify(ajv.errors, null, 2)}`));
return [];
}
let run_data;
let suits = [];
let runs = [];
let _update = false;
if(this._run_update && !!this._runs_ids.length) _update = true
if (this._suite_mode === 1) {
let case_ids = cases.map(c => c.case_id);
let results = cases.map(c => c.result);
suits.push({case_ids, results})
} else {
for (let i=0, i_len=cases.length; i<i_len; i++) {
let suite_id = null;
try {
const resp = await tr_api.get_case(cases[i].case_id);
suite_id = resp.suite_id;
}
catch (e) {
console.log(error(e.stack));
}
const index = suits.findIndex(suite => suite.suite_id === suite_id);
if (~index) {
suits[index].case_ids.push(cases[i].case_id);
suits[index].results.push(cases[i].result);
} else if (suite_id) {
suits.push({suite_id, case_ids: [cases[i].case_id], results: [cases[i].result]})
}
}
}
for (let j=0, j_len=suits.length; j<j_len; j++) {
let run = null;
if (_update && suits[j].suite_id) {
run = this._runs_ids.find(run => run.suite_id === suits[j].suite_id && !run.plan_id);
} else if (_update) {run = this._runs_ids[0]}
if (run) {
for (let i=0, len=this._tests.length; i<len; i++) {
if(this._tests[i].run_id === run.id) {
suits[j].case_ids.push(this._tests[i].case_id)
}
}
run_data = {"include_all": false, "case_ids": suits[j].case_ids, "milestone_id": this._milestone_id};
try {
const {id} = await tr_api.update_run(run.id, run_data);
runs.push({run_id: id, results: suits[j].results});
}
catch (e) {
console.log(error(e.stack));
}
} else {
const today = new Date;
let suite_name = 'Automated Run';
if(suits[j].suite_id) {
try {
const {name} = await tr_api.get_suite(suits[j].suite_id);
suite_name = name;
}
catch (e) {
console.log(error(e.stack));
}
run_data = {"suite_id": suits[j].suite_id,
"name": suite_name + ` ${today.getDate()}.${today.getMonth() + 1}.${today.getFullYear()}`,
"include_all": false,
"case_ids": suits[j].case_ids,
"milestone_id": this._milestone_id
};
}
else {
run_data = {"name": suite_name + ` ${today.getDate()}.${today.getMonth()+1}.${today.getFullYear()}`,
"include_all": false,
"case_ids": suits[j].case_ids,
"milestone_id": this._milestone_id
};
}
try {
const {id} = await tr_api.add_run(this._project_id, run_data);
runs.push({run_id: id, results: suits[j].results})
}
catch (e) {
console.log(error(e.stack));
}
}
}
return runs;
}
'use strict';
require('@babel/plugin-syntax-class-properties');
const chalk = require('chalk'), Ajv = require("ajv").default;
const ajv = new Ajv({
strict: false,
allErrors: true,
});
const error = chalk.bold.red;

@@ -66,3 +73,3 @@ class Utils {

for (let i=0, len=_t.length; i<len; i++) {
case_ids.push(_t[i].match(/[?\d]{3,6}/gm))
case_ids.push(_t[i].match(/\d+/gm)[0])
}

@@ -96,70 +103,108 @@ return case_ids.length && case_ids;

merge = merge
isPlainObject = isPlainObject
isArray = isArray
}
module.exports = Utils;
/**
* Accepts varargs expecting each argument to be an object, then
* immutably merges the properties of each object and returns result.
*
* When multiple objects contain the same key the later object in
* the arguments list will take precedence.
*
* Example:
*
* ```js
* var result = merge({foo: 123}, {foo: 456});
* console.log(result.foo); // outputs 456
* ```
*
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
*/
function merge(/* obj1, obj2, obj3, ... */) {
let result = {};
function assignValue(val, key) {
if (isPlainObject(result[key]) && isPlainObject(val)) {
result[key] = merge(result[key], val);
} else if (isPlainObject(val)) {
result[key] = merge({}, val);
} else if (isArray(val)) {
result[key] = val.slice();
} else {
result[key] = val;
/**
* Group jest test results in a testrail runs or a single cases
*
* @param {array} jest_result_list
* @param {array} tr_tests
* @return {[] || [{case_id, result}, {run_id, results}]}
*/
groupCases(jest_result_list, tr_tests) {
const valid_results = ajv.validate({
"type": "array",
"items": {
"type": "object",
"properties": {
"case_id": {
"type": "integer"
},
"status_id": {
"type": "integer"
},
"comment": {
"type": "string"
},
"elapsed": {
"type": "string"
},
"defects": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": ["case_id", "status_id", "comment", "elapsed"]
}
},
jest_result_list);
if (!valid_results ) {
console.log(error(`\nCan not update cases of test JSON schema error
\nContext: ${JSON.stringify(ajv.errors, null, 2)}`));
return [];
}
const valid_tests_list = ajv.validate({
"type": "array",
"items": {
"type": "object",
"properties": {
"case_id": {
"type": "integer"
},
"run_id": {
"type": "integer"
}
},
"required": ["case_id", "run_id"]
}
},
tr_tests);
if (!valid_tests_list) {
console.log(error(`\nCan not update cases of test JSON schema error
\nContext: ${JSON.stringify(ajv.errors, null, 2)}`));
return [];
}
let results = [];
for (let i=0, len = jest_result_list.length; i<len; i++) {
const test = tr_tests ?
tr_tests.find(test => test.case_id === jest_result_list[i].case_id)
: null;
if (test) {
const index = results.findIndex(run => run.run_id === test.run_id);
if (~index) results[index].results.push(jest_result_list[i]);
else results.push({run_id: test.run_id, "results": [jest_result_list[i]]});
} else {
results.push({case_id: jest_result_list[i].case_id, "result": jest_result_list[i]})
}
}
return results;
}
for (let i = 0, l = arguments.length; i < l; i++) {
this.forEach(arguments[i], assignValue);
/**
* Determine if a value is a plain Object
*
* @param {Object} val The value to test
* @return {boolean} True if value is a plain Object, otherwise false
*/
isPlainObject(val) {
if (toString.call(val) !== '[object Object]') {
return false;
}
const prototype = Object.getPrototypeOf(val);
return prototype === null || prototype === Object.prototype;
}
return result;
}
/**
* Determine if a value is a plain Object
*
* @param {Object} val The value to test
* @return {boolean} True if value is a plain Object, otherwise false
*/
function isPlainObject(val) {
if (toString.call(val) !== '[object Object]') {
return false;
/**
* Determine if a value is an Array
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Array, otherwise false
*/
isArray(val) {
return toString.call(val) === '[object Array]';
}
const prototype = Object.getPrototypeOf(val);
return prototype === null || prototype === Object.prototype;
}
/**
* Determine if a value is an Array
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Array, otherwise false
*/
function isArray(val) {
return toString.call(val) === '[object Array]';
}
module.exports = Utils;

@@ -13,2 +13,3 @@ const baseUrl = process.env.URL;

//'milestone': '<milestone_name>'
//'suite_mode': '<suite_mode>'
}

@@ -16,3 +16,4 @@ const faker = require('faker'), Utils = require('../src/utils');

tr_plan: tr_plan,
tr_milestone: tr_milestone
tr_milestone: tr_milestone,
tr_get_project
};

@@ -337,2 +338,22 @@

/**
*
* @param {object} options
* @param {number || string} options.suite_mode
* @return {{suite_mode: *, completed_on: null, name: string, id: number, is_completed: boolean, show_announcement: boolean, url: string, announcement: string}}
*/
function tr_get_project(options) {
const _suite_mode = options && options.suite_mode || faker.random.arrayElement([1,2,3]);
return {
"announcement": "..",
"completed_on": null,
"id": 1,
"is_completed": false,
"name": "Datahub",
"show_announcement": true,
"suite_mode": _suite_mode,
"url": "http:///testrail/index.php?/projects/overview/1"
}
}
function count(start) {

@@ -339,0 +360,0 @@ let _count = start;

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc