Source: reporter/testrail.js

"use strict";
/**
 * [TestRail](https://www.gurock.com/testrail) reporter publishs test results
 * to remote testrail server via its API.
 *
 * @module
 */

const util = require("util");

const expect = require("chai").expect;
const Testrail = require("testrail-api");
const LOG = require("glace-utils").logger;

const CONF = require("../config");
const TestCase = require("../testing").TestCase;

for (const opt in CONF.testrail) {
    expect(CONF.testrail[opt],
        `TestRail option '${opt}' is not specified in config`)
        .to.exist;
}

const testrail = new Testrail({
    host: CONF.testrail.host,
    user: CONF.testrail.user,
    password: CONF.testrail.token });

testrail.isFailed = false;

const Results = {
    PASSED:   1,
    BLOCKED:  2,
    UNTESTED: 3,
    RETEST:   4,
    FAILED:   5
};

const cases = {};
let report = Promise.resolve();

const Reporter = {
    /**
     * Called on tests start.
     *
     * @method
     * @instance
     */
    start: () => {

        report = report.then(() => {
            return testrail.getCases(
                CONF.testrail.projectId, { suite_id: CONF.testrail.suiteId });

        }).then(result => {
            for (const remoteCase of result.body) {
                if (cases[remoteCase.title]) {
                    throw new Error("Detect duplicated cases in TestRail for " +
                        `name '${remoteCase.title}'. Only unique names should be.`);
                }
                cases[remoteCase.title] = { id: remoteCase.id };
            }

        }).then(() => {
            return testrail.addRun(CONF.testrail.projectId, {
                suite_id: CONF.testrail.suiteId, name: CONF.testrail.runName,
                description: CONF.testrail.runDescription });

        }).then(result => {
            CONF.testrail.runId = result.body.id;

        }).catch(e => {
            testrail.isFailed = true;
            LOG.error(util.format("Error to init TestRail report:", e));
        });
    },
    /**
     * Called before tests end.
     *
     * @method
     * @instance
     */
    end: () => {
        if (testrail.isFailed) return;
        console.log();
        const reportMsg = "TestRail report is " + CONF.testrail.host +
            "/index.php?/runs/view/" + CONF.testrail.runId;
        console.log(Array(reportMsg.length + 1).join("-").yellow);
        console.log(reportMsg.yellow);
    },
    /**
     * Called on test end.
     *
     * @method
     * @instance
     */
    testEnd: () => {
        if (testrail.isFailed) return;
        const testrailCase = cases[CONF.test.curCase.name];
        if (!testrailCase) {
            LOG.error(`Testrail case '${CONF.test.curCase.name}' is absent`);
            return;
        }

        const testResult = { status_id: Results.PASSED, comment: "" };
        testResult.comment = Reporter.setComment();

        if (CONF.test.curCase.status === TestCase.SKIPPED) {
            testResult.status_id = Results.BLOCKED;
        }
        if (CONF.test.curCase.screenshots.length) {
            testResult.comment += Reporter.processScreens(CONF.test.curCase.screenshots);
        }
        if (CONF.test.curCase.videos.length) {
            testResult.comment += Reporter.processVideos(CONF.test.curCase.videos);
        }
        if (CONF.test.curCase.status === TestCase.FAILED) {
            testResult.status_id = Results.FAILED;
            testResult.comment += Reporter.processErrors(CONF.test.curCase.errors);
        }
        if (CONF.test.curCase.rawInfo.length) {
            testResult.comment += Reporter.processExtras(CONF.test.curCase.rawInfo);
        }

        testResult.comment = testResult.comment.trim();

        report = report.then(() => {
            return testrail.addResultForCase(
                CONF.testrail.runId, testrailCase.id, testResult);

        }).catch(e => {
            LOG.error(util.format(
                `Error to publish test '${CONF.test.curCase.name}' report to TestRail:`, e));
        });
    },
    /**
     * Called on report finalizing.
     *
     * @method
     * @instance
     */
    done: () => report,
};

/**
 * Entry point to set comment of test before testrail publication.
 * Can be overridden with custom function.
 *
 * @memberOf module:reporter/testrail
 * @instance
 * @function setComment
 * @return {string} - Test comment.
 *
 * @example <caption><b>Overriding with custom function</b></caption>
 * // should be after configuration but before tests run
 * const testrail = require("glace-core/lib/reporter/testrail");
 * testrail.setComment = myFuncToSetComment;
 */
Reporter.setComment = () => "";

/**
 * Entry point to process test screenshot paths before testrail publication.
 * Can be overridden with custom function.
 *
 * @memberOf module:reporter/testrail
 * @instance
 * @function processScreens
 * @arg {string[]} screens - List of screenshot paths.
 * @return {string} - Test screenshots info, attaching to test comment.
 *
 * @example <caption><b>Overriding with custom function</b></caption>
 * // should be after configuration but before tests run
 * const testrail = require("glace-core/lib/reporter/testrail");
 * testrail.processScreens = myFuncToProcessScreens;
 */
Reporter.processScreens = screens => {
    let result = "\n\nScreenshots:";
    screens.forEach((screen, i) => {
        result += `\n${i + 1}. ${screen}`;
    });
    return result;
};

/**
 * Entry point to process test video paths before testrail publication.
 * Can be overridden with custom function.
 *
 * @memberOf module:reporter/testrail
 * @instance
 * @function processVideos
 * @arg {string[]} videos - List of video paths.
 * @return {string} - Test videos info, attaching to test comment.
 *
 * @example <caption><b>Overriding with custom function</b></caption>
 * // should be after configuration but before tests run
 * const testrail = require("glace-core/lib/reporter/testrail");
 * testrail.processVideos = myFuncToProcessVideos;
 */
Reporter.processVideos = videos => {
    let result = "\n\nVideos:";
    videos.forEach((video, i) => {
        result += `\n${i + 1}. ${video}`;
    });
    return result;
};

/**
 * Entry point to process test errors before testrail publication.
 * Can be overridden with custom function.
 *
 * @memberOf module:reporter/testrail
 * @instance
 * @function processErrors
 * @arg {string[]} errors - List of test errors.
 * @return {string} - Test errors info, attaching to test comment.
 *
 * @example <caption><b>Overriding with custom function</b></caption>
 * // should be after configuration but before tests run
 * const testrail = require("glace-core/lib/reporter/testrail");
 * testrail.processErrors = myFuncToProcessErrors;
 */
Reporter.processErrors = errors => {
    let result = "\n\nErrors:";
    errors.forEach((error, i) => {
        result += `${i ? "\n" : ""}\n${i + 1}. ${error}`;
    });
    return result;
};

/**
 * Entry point to process test extra details before testrail publication.
 * Can be overridden with custom function.
 *
 * @memberOf module:reporter/testrail
 * @instance
 * @function processExtras
 * @arg {string[]} extras - List of extra details.
 * @return {string} - Test extra details, attaching to test comment.
 *
 * @example <caption><b>Overriding with custom function</b></caption>
 * // should be after configuration but before tests run
 * const testrail = require("glace-core/lib/reporter/testrail");
 * testrail.processExtras = myFuncToProcessExtras;
 */
Reporter.processExtras = extras => {
    let result = "\n\nExtra details:";
    extras.forEach((extra, i) => {
        result += `${i ? "\n" : ""}\n${i + 1}. ${extra}`;
    });
    return result;
};

module.exports = Reporter;