diff --git a/package.json b/package.json index 6bf128d..4ab49b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mocha-testrail-reporter", - "version": "1.0.11", - "description": "A Testrail reporter for mocha.", + "version": "1.0.12", + "description": "A Testrail reporter for mocha including TestRail API basic library", "main": "index.js", "private": false, "keywords": [ diff --git a/src/lib/mocha-testrail-reporter.ts b/src/lib/mocha-testrail-reporter.ts index 9ca3eab..1d4d7f1 100644 --- a/src/lib/mocha-testrail-reporter.ts +++ b/src/lib/mocha-testrail-reporter.ts @@ -1,10 +1,11 @@ import {reporters} from 'mocha'; import {TestRail} from "./testrail"; import {titleToCaseIds} from "./shared"; +import {Status, TestRailResult} from "./testrail.interface"; export class MochaTestRailReporter extends reporters.Spec { - private testCases: TestCase[] = []; + private results: TestRailResult[] = []; private passes: number = 0; private fails: number = 0; private pending: number = 0; @@ -40,23 +41,23 @@ export class MochaTestRailReporter extends reporters.Spec { let caseIds = titleToCaseIds(test.title); if (caseIds.length > 0) { if (test.speed === 'fast') { - let testCases = caseIds.map(caseId => { + let results = caseIds.map(caseId => { return { - caseId: caseId, - pass: true, + case_id: caseId, + status_id: Status.Passed, comment: test.title }; }); - this.testCases.push(...testCases); + this.results.push(...results); } else { - let testCases = caseIds.map(caseId => { + let results = caseIds.map(caseId => { return { - caseId: caseId, - pass: true, + case_id: caseId, + status_id: Status.Passed, comment: `${test.title} (${test.duration}ms)` }; }); - this.testCases.push(...testCases); + this.results.push(...results); } } }); @@ -66,23 +67,36 @@ export class MochaTestRailReporter extends reporters.Spec { this.out.push(test.fullTitle() + ': fail'); let caseIds = titleToCaseIds(test.title); if (caseIds.length > 0) { - let testCases = caseIds.map(caseId => { + let results = caseIds.map(caseId => { return { - caseId: caseId, - pass: false, + case_id: caseId, + status_id: Status.Failed, comment: `${test.title} ${test.err}` }; }); - this.testCases.push(...testCases); + this.results.push(...results); } }); runner.on('end', () => { - if (this.testCases.length == 0) { + if (this.results.length == 0) { console.warn("No testcases were matched. Ensure that your tests are declared correctly and matches TCxxx"); } - new TestRail(reporterOptions).publish(this.passes, this.fails, this.pending, this.out, this.testCases); + let executionDateTime = new Date().toISOString(); + let total = this.passes + this.fails + this.pending; + let name = `Automated test run ${executionDateTime}`; + let description = `Automated test run executed on ${executionDateTime} +Execution summary: +Passes: ${this.passes} +Fails: ${this.fails} +Pending: ${this.pending} +Total: ${total} + +Execution details: +${this.out.join('\n')} +`; + new TestRail(reporterOptions).publish(name, description, this.results); }); } diff --git a/src/lib/test.interface.ts b/src/lib/test.interface.ts deleted file mode 100644 index 236f945..0000000 --- a/src/lib/test.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface TestCase { - caseId: number, - pass: boolean, - comment?: String, -} \ No newline at end of file diff --git a/src/lib/testrail-options.interface.ts b/src/lib/testrail-options.interface.ts deleted file mode 100644 index 26fdd5c..0000000 --- a/src/lib/testrail-options.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface TestRailOptions { - domain: string, - username: string, - password: string, - projectId: number, - suiteId: number, - assignedToId?: number, -} \ No newline at end of file diff --git a/src/lib/testrail.interface.ts b/src/lib/testrail.interface.ts new file mode 100644 index 0000000..0a8669a --- /dev/null +++ b/src/lib/testrail.interface.ts @@ -0,0 +1,46 @@ +export interface TestRailOptions { + domain: string, + username: string, + password: string, + projectId: number, + suiteId: number, + assignedToId?: number, +} + +export enum Status { + Passed = 1, + Blocked = 2, + Untested = 3, + Retest = 4, + Failed = 5 +} + +export interface TestRailResult { + case_id: number, + status_id: Status, + comment?: String, +} + +export interface TestRailCase { + id: number, + title: string, + section_id: number, + template_id: number, + type_id: number, + priority_id: number, + milestone_id?: number, + refs?: string, + created_by: number, + created_on: number, + updated_by: number, + updated_on: number, + estimate?: string, + estimate_forecast?: string, + suite_id: number, + custom_preconds?: string, + custom_steps?: string, + custom_expected?: string, + custom_steps_separated?: string, + custom_mission?: string, + custom_goals?: string +} \ No newline at end of file diff --git a/src/lib/testrail.ts b/src/lib/testrail.ts index c0efcd9..dd82f46 100644 --- a/src/lib/testrail.ts +++ b/src/lib/testrail.ts @@ -1,6 +1,9 @@ -import btoa = require('btoa'); -import unirest = require("unirest"); +import request = require("unirest"); +import {TestRailOptions, TestRailResult} from "./testrail.interface"; +/** + * TestRail basic API wrapper + */ export class TestRail { private base: String; @@ -10,7 +13,7 @@ export class TestRail { } private _post(api: String, body: any, callback: Function, error?: Function) { - var req = unirest("POST", this.base) + var req = request("POST", this.base) .query(`/api/v2/${api}`) .headers({ "content-type": "application/json" @@ -31,32 +34,63 @@ export class TestRail { }); } - publish(passes: number, fails: number, pending: number, out: string[], tests: TestCase[], callback?: Function): void { - let total = passes + fails + pending; - let results: any = []; - for (let test of tests) { - results.push({ - "case_id": test.caseId, - "status_id": test.pass ? 1 : 5, - "comment": test.comment ? test.comment: "", + private _get(api: String, callback: Function, error?: Function) { + var req = request("GET", this.base) + .query(`/api/v2/${api}`) + .headers({ + "content-type": "application/json" + }) + .type("json") + .auth(this.options.username, this.options.password) + .end((res) => { + if (res.error) { + console.log("Error: %s", JSON.stringify(res.body)); + if (error) { + error(res.error); + } else { + throw new Error(res.error); + } + } + callback(res.body); }); + } + + /** + * Fetchs test cases from projet/suite based on filtering criteria (optional) + * @param {{[p: string]: number[]}} filters + * @param {Function} callback + */ + public fetchCases(filters?: { [key: string]: number[] }, callback?: Function): void { + let filter = ""; + if(filters) { + for (var key in filters) { + if (filters.hasOwnProperty(key)) { + filter += "&" + key + "=" + filters[key].join(","); + } + } } - console.log(`Publishing ${results.length} test result(s) to ${this.base}`) - let executionDateTime = new Date().toISOString(); + let req = this._get(`get_cases/${this.options.projectId}&suite_id=${this.options.suiteId}${filter}`, (body) => { + if (callback) { + callback(body); + } + }); + } + + /** + * Publishes results of execution of an automated test run + * @param {string} name + * @param {string} description + * @param {TestRailResult[]} results + * @param {Function} callback + */ + public publish(name: string, description: string, results: TestRailResult[], callback?: Function): void { + console.log(`Publishing ${results.length} test result(s) to ${this.base}`); + this._post(`add_run/${this.options.projectId}`, { "suite_id": this.options.suiteId, - "name": `Automated test run ${executionDateTime}`, - "description": `Automated test run executed on ${executionDateTime} -Execution summary: -Passes: ${passes} -Fails: ${fails} -Pending: ${pending} -Total: ${total} - -Execution details: -${out.join('\n')} -`, + "name": name, + "description": description, "assignedto_id": this.options.assignedToId, "include_all": true }, (body) => { @@ -66,7 +100,7 @@ ${out.join('\n')} results: results }, (body) => { // execute callback if specified - if(callback) { + if (callback) { callback(); } }) diff --git a/src/test/testrail.ts b/src/test/testrail.ts index 89eb4fb..4527294 100644 --- a/src/test/testrail.ts +++ b/src/test/testrail.ts @@ -1,23 +1,39 @@ import {TestRail} from "../lib/testrail"; +import {TestRailResult, TestRailCase, Status} from "../lib/testrail.interface"; -describe.skip("TestRail API", () => { +describe("TestRail API", () => { it("Publish test run", (done) => { - new TestRail({ - domain: "testingoutone", - username: "testingout.one@mailinator.com", - password: "XyMp8uojG3wkzNNNXiTk-dP4MnBmOiQhVC2xGvmyY", - projectId: 1, - suiteId: 2, - assignedToId: 1, - }).publish(0, 0, 0, ["test 1: pass", "test 2: fail"], [ - { - caseId: 74, - pass: true - }, { - caseId: 75, - pass: false, - comment: "Failure...." - } - ], done); - }) + let testRail = new TestRail({ + domain: process.env.DOMAIN, + username: process.env.USERNAME, + password: process.env.PASSWORD, + projectId: 10, + suiteId: 104, + // assignedToId: 2, + }); + + testRail.fetchCases({type_id: [3], priority_id: [4]}, (cases: TestRailCase[]) => { + console.log(cases); + let results: TestRailResult + cases.forEach((value => { + console.log(value.id, value.title); + })); + }); + + testRail.publish("Unit Test of mocha-testrail-reporter", "Unit Test of mocha-testrail-reporter", [{ + case_id: 3033, + status_id: Status.Passed, + comment: "Passing...." + }, { + case_id: 3034, + status_id: Status.Passed + }, { + case_id: 3035, + status_id: Status.Passed + }, { + case_id: 3036, + status_id: Status.Failed, + comment: "Failure...." + }], done); + }); }); \ No newline at end of file