"use strict";
/**
* Glace tools.
*
* @module
*/
const util = require("util");
require("colors");
const _ = require("lodash");
const expect = require("chai").expect;
const highlight = require("cli-highlight").highlight;
const Testrail = require("testrail-api");
const U = require("glace-utils");
const classifier = require("./classifier")();
const CONF = require("./config");
const plugins = require("./plugins");
const utils = require("./utils");
const d = U.switchColor();
let stepsCache = null;
/**
* Get list of steps data, where step data is an object with keys:
* `name` - name of step, `decription` - short details of steps,
* `doc` - documentation of step.
*
* @memberOf module:tools
* @function
* @arg {string} [filter] - String chunk to filter steps.
* @arg {boolean} [namesOnly=false] - Flag to filter by step names only.
* @return {array<object>}
*/
const listSteps = (filter, namesOnly=false) => {
if (!stepsCache) {
stepsCache = getStepsData(getStepNames());
learnClassifier(stepsCache);
}
if (!filter) return stepsCache;
return filterSteps(stepsCache, filter, namesOnly);
};
/**
* Print list of steps in stdout.
*
* @memberOf module:tools
* @function
* @arg {string} [filter] - String chunk to filter steps.
* @arg {boolean} [namesOnly=false] - Flag to filter by step names only.
*/
const printSteps = (filter, namesOnly=false) => {
const steps = listSteps(filter, namesOnly);
if (!steps.length) {
console.log("No steps are found".yellow);
return;
}
steps.reverse().forEach((step, i) => {
console.log(d(`${i+1}. ${step.name}:`));
console.log(d(step.description));
if (step.doc) console.log(highlight(step.doc, { language: "js" }));
});
};
/**
* Print list of implemented test cases.
*
* <img src="./list_tests.gif" title="listTests example" />
*
* @memberOf module:tools
* @name listTests
* @function
* @arg {string} [filter] - String chunk to filter test cases.
*/
const printTests = filter => {
fakeLoad();
let cases = CONF.test.cases;
if (filter) cases = cases.filter(c => U.textContains(c.name, filter));
if (!cases.length) {
console.log("No tests are found".yellow);
return;
}
cases.forEach((c, i) => {
console.log(d(`${i+1}. ${c.name}`));
});
};
/**
* Print list of available plugins.
*
* <img src="./list_plugins.gif" title="listPlugins example" />
*
* @memberOf module:tools
* @name listPlugins
* @function
*/
const printPlugins = () => {
const pluginsList = plugins.get();
if (!pluginsList.length) {
console.log("No plugins are detected".yellow);
return;
}
pluginsList.forEach((p, i) => {
console.log(`${i+1}. ${p.name}`.yellow, p.path);
});
};
/**
* Get list of available fixtures.
*
* @memberOf module:tools
* @function
* @arg {string} [filter] - String chunk to filter fixtures.
* @arg {boolean} [namesOnly=false] - Flag to filter by fixture names only.
* @return {array<object>}
*/
const listFixtures = (filter, namesOnly=false) => {
fakeLoad();
const fixtures = getFixtures();
if (!filter) return fixtures;
return filterFixtures(fixtures, filter, namesOnly);
};
/**
* Print list of fixtures in stdout.
*
* @memberOf module:tools
* @function
* @arg {string} [filter] - String chunk to filter fixtures.
* @arg {boolean} [namesOnly=false] - Flag to filter by fixture names only.
*/
const printFixtures = (filter, namesOnly=false) => {
const fixtures = listFixtures(filter, namesOnly);
if (!fixtures.length) {
console.log("No fixtures are found".yellow);
return;
}
fixtures.forEach((fx, i) => {
console.log(d(`${i+1}. ${fx.name}`));
if (fx.doc) console.log(highlight(fx.doc, { language: "js" }));
});
};
/**
* Make fake load of tests in order to collect tests, fixtures, steps, etc.
*
* @memberOf module:tools
* @function
*/
const fakeLoad = () => {
const dummy = () => {};
global.before = dummy;
global.after = dummy;
global.beforeEach = dummy;
global.afterEach = dummy;
global.it = dummy;
global.describe = (name, cb) => {
cb.call({
retries: dummy,
timeout: dummy,
});
};
require("./globals");
require("./loader");
};
/**
* Check testrail cases consistency with implemented tests.
*
* @memberOf module:tools
* @function
* @arg {function} cb - Callback function.
*/
const checkTestrail = cb => {
checkTestrailOpts();
fakeLoad();
console.log("TestRail connecting...".yellow);
const testrail = new Testrail({
host: CONF.testrail.host,
user: CONF.testrail.user,
password: CONF.testrail.token });
checkTestrailCases(testrail, cb);
};
module.exports = {
checkTestrail,
fakeLoad,
listSteps,
printSteps,
printTests,
printPlugins,
listFixtures,
printFixtures,
};
/**
* Check testrail options.
* @ignore
*/
const checkTestrailOpts = () => {
for (const opt in CONF.testrail) {
expect(CONF.testrail[opt],
`TestRail option '${opt}' isn't specified in config`)
.to.exist;
}
};
/**
* Check testrail missed cases which are implemented.
* @ignore
*/
const checkTestrailMissed = cases => {
const testrailNames = cases.map(case_ => case_.title);
const testNames = CONF.test.cases.map(case_ => case_.name);
const missed = _.difference(testNames, testrailNames);
if (!missed.length) return 0;
console.log("\nMissed TestRail cases:".magenta.bold);
missed.forEach((title, i) => {
console.log(`${i+1}. ${title}`.cyan.bold);
});
return 1;
};
/**
* Check testrail cases which are not implemented yet.
* @ignore
*/
const checkTestrailNotImplemented = cases => {
const testrailNames = cases.map(case_ => case_.title);
const testNames = CONF.test.cases.map(case_ => case_.name);
const notImplemented = _.difference(testrailNames, testNames);
if (!notImplemented.length) return 0;
console.log("\nNot implemented TestRail cases:".magenta.bold);
notImplemented.forEach((title, i) => {
console.log(`${i+1}. ${title}`.cyan.bold);
});
return 1;
};
/**
* Check testrail duplicated cases.
* @ignore
*/
const checkTestrailDuplicates = cases => {
const testrailNames = [];
let testrailDups = [];
cases.forEach(case_ => {
if (testrailNames.includes(case_.title)) {
testrailDups.push(case_.title);
} else {
testrailNames.push(case_.title);
}
});
testrailDups = _.uniq(testrailDups);
if (!testrailDups.length) return 0;
console.log("\nTestRail duplicated cases:".magenta.bold);
testrailDups.forEach((title, i) => {
console.log(`${i+1}. ${title}`.cyan.bold);
});
return 1;
};
/**
* Check testrail cases.
* @ignore
*/
const checkTestrailCases = (client, cb) => {
client.getCases(
CONF.testrail.projectId,
{ suite_id: CONF.testrail.suiteId },
(err, response, cases) => {
if (err) {
console.log(err);
cb(1);
return;
}
let errorCode = 0;
errorCode += checkTestrailDuplicates(cases);
errorCode += checkTestrailNotImplemented(cases);
errorCode += checkTestrailMissed(cases);
if (!errorCode) {
console.log("TestRail cases correspond current tests".green.bold);
}
console.log(
"\nTestRail suite is",
`${CONF.testrail.host}/index.php?/suites/view/${CONF.testrail.suiteId}`.yellow);
cb(errorCode);
});
};
/**
* Get fixtures.
* @ignore
*/
const getFixtures = () => {
const fixtures = [];
for (const name in global) {
if (!name.startsWith("fx")) continue;
const func = global[name];
if (!util.isFunction(func)) continue;
fixtures.push({
name: name,
doc: utils.getDoc(func),
});
}
return fixtures;
};
/**
* Filter fixtures.
* @ignore
*/
const filterFixtures = (fixtures, filter, namesOnly) => {
const filtered = [];
for (const fx of fixtures) {
if (namesOnly) {
if (!U.textContains(fx.name, filter)) continue;
} else {
if (!U.textContains(fx.name, filter) && !U.textContains(fx.doc, filter)) continue;
}
filtered.push(fx);
}
return filtered;
};
/**
* Get list of step names.
*
* @ignore
* @function
* @return {array<string>}
*/
const getStepNames = () => {
global.$ || fakeLoad();
const NOT_STEPS = [
"constructor",
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable",
"toLocaleString",
"toString",
"valueOf",
];
let names = [];
for (const key in $) {
names.push(key);
};
names = _.union(
names,
Object.getOwnPropertyNames($),
Object.getOwnPropertyNames(Object.getPrototypeOf($))
).sort()
.filter(i => !i.startsWith("_"))
.filter(i => /^\w+$/.test(i))
.filter(i => !NOT_STEPS.includes(i))
.filter(i => /^\D/.test(i));
return names;
};
/**
* Get list of step data, where step data is an object with keys:
* `name` - name of step, `decription` - short details of steps,
* `doc` - documentation of step.
*
* @ignore
* @function
* @arg {array<string>} names - List of step names.
* @return {array<object>}
*/
const getStepsData = names => {
const result = [];
for (const name of names) {
const func = $[name];
if (!util.isFunction(func)) continue;
result.push({
name: name,
description: funcDescription(func),
doc: utils.getDoc(func),
});
};
return result;
};
/**
* Learn classifier on step names and description.
* @ignore
*/
const learnClassifier = steps => {
steps.forEach(step => classifier.learn(step.doc, step.name));
};
/**
* Filter steps and return relevant result merged with ML predictions.
* @ignore
*/
const filterSteps = (steps, filter, namesOnly) => {
let filtered = [];
for (const step of steps) {
if (namesOnly) {
if (!U.textContains(step.name, filter)) continue;
} else {
if (!U.textContains(step.name, filter) && !U.textContains(step.doc, filter)) continue;
}
filtered.push(step);
}
if (!namesOnly) {
const classified = classifySteps(steps, filter);
filtered = mergeSteps(classified, filtered);
}
return filtered;
};
/**
* Get classified steps based on ML.
* @ignore
*/
const classifySteps = (steps, filter) => {
const result = [];
const sampling = classifier.classify(filter);
for (const sample of sampling) {
for (const step of steps) {
if (sample.label === step.name) result.push(step);
}
}
return result;
};
/**
* Merge steps.
* @ignore
*/
const mergeSteps = (base, merging) => {
const usedNames = base.map(s => s.name);
const result = _.clone(base);
for (const step of merging) {
if (!usedNames.includes(step.name)) result.push(step);
}
return result;
};
/**
* Get function description.
* @ignore
*/
const funcDescription = func => {
return " " + func.toString().replace("\n", " ").split(/\) *\{/)[0] + ") {...}";
};