loadmill
Version:
A node.js module for running load tests and functional tests on loadmill.com
280 lines (279 loc) • 16.9 kB
JavaScript
"use strict";
exports.__esModule = true;
var tslib_1 = require("tslib");
var Loadmill = require("./index");
var program = require("commander");
var utils_1 = require("./utils");
var reporter_1 = require("./reporter");
program
.usage("<testPlanId | load-config-file | flowId> -t <token> [options] [parameter=value...]")
.description("Run a test plan (default option) or a load test on loadmill.com.\n " +
"You may set parameter values by passing space-separated 'name=value' pairs, e.g. 'host=www.myapp.com port=80' or supply a file using --parameters-file.\n\n " +
"Learn more at https://www.npmjs.com/package/loadmill#cli")
.option("-t, --token <token>", "Loadmill API Token. You must provide a token in order to run tests.")
.option("-l, --load-test", "Create a load test from a test suite flow and launch it.")
.option("--suite-id <test-suite-id>", "Test suite id that contains the flow (used with -l).")
.option("--flow-id <test-suite-flow-id>", "Flow id to use when creating a load test (used with -l). Defaults to positional input.")
.option("--load-test-options <loadTestOptions>", "Load test options JSON to pass when creating a load test from flow (e.g. '{\"users\":10,\"duration\":60}').")
.option("--test-plan", "Launch a test plan (default option).")
.option("-p, --parallel <parallel>", "Set the concurrency of a running test suites in a test plan")
.option("--additional-description <description>", "Add an additional description at the end of the current suite's description - available only for test suites.")
.option("--labels <labels>", "Run flows that are assigned to a specific label (when running a test suite).. Multiple labels can be provided by seperated them with ',' (e.g. 'label1,label2').")
.option("--labels-expression <labelsExpression>", "Run a test plan's suites with flows that match the labels expression. An expression may contain the characters ( ) & | ! (e.g. '(label1 | label2) & !label3')")
.option("--pool <pool>", "Execute tests from a dedicated agent's pool (when using private agent)")
.option("--tags <tags>", "Tag a test plan run with a comma separated list of tags (e.g. 'tag1,tag2')")
.option("--overrideSuites <overrideSuites>", "Run only the specified test suites in the test plan. Provide a comma separated list of suite UUIDs.")
.option("-w, --wait", "Wait for the test to finish.")
.option("-n, --no-bail", "Return exit code 0 even if test fails.")
.option("-q, --quiet", "Do not print out anything (except errors).")
.option("-v, --verbose", "Print out extra information for debugging.")
.option("-r, --report", "Print out Test Suite Flow Runs report when the plan has ended.")
.option("--errors-report", "Print out Test Suite Flow Runs errors report when the plan has ended.")
.option("-j, --junit-report", "Create Test Suite (junit style) report when the suite has ended.")
.option("--junit-report-path <junitReportPath>", "Save junit styled report to a path (defaults to current location).")
.option("-m, --mochawesome-report", "Create Test Suite (mochawesome style) report when the suite has ended.")
.option("--mochawesome-report-path <mochawesomeReportPath>", "Save JSON mochawesome styled report to a path (defaults to current location).")
.option("--colors", "Print test results in color")
.option("-b, --branch <branch>", "Run the test plan's suites from a GitHub branch. The latest version of the selected Git branch will be used as the test configuration for the chosen Test Plan")
.option("--retry-failed-flows <numberOfRetries>", "Configure the test plan to re-run failed flows in case your tested system is unstable. Tests that pass after a retry will be considered successful.")
.option("--parameters-file <parametersFile>", "Supply a file with parameters to override. File format should be 'name=value' divided by new line.")
.option("--inlineParameterOverride", "Override parameters strategy: by default, overrided parameters are appended to the end of the parameters list. Using this flag will replace the parameters inline.")
.option("--apiCatalogService <apiCatalogService>", "Use the provided service when mapping the APIs in the catalog. Service will be created if not exist")
.option("--turbo-parallel", "Run the test plan in turbo mode")
.parse(process.argv);
start()["catch"](function (err) {
console.error(err);
process.exit(2);
});
function start() {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var wait, bail, quiet, token, verbose, colors, report, errorsReport, junitReport, junitReportPath, mochawesomeReport, mochawesomeReportPath, parallel, loadTest, suiteId, flowId, loadTestOptions, testPlan, additionalDescription, labels, labelsExpression, pool, tags, overrideSuites, branch, retryFailedFlows, parametersFile, inlineParameterOverride, apiCatalogService, turboParallel, _a, input, rawParams, logger, parameters, loadmill, testFailed, testStopped, res, planLabels, planTags, planOverrideSuites, running, e_1, extInfo, res_1, id, effectiveFlowId, loadTestDef, configFile;
return tslib_1.__generator(this, function (_b) {
switch (_b.label) {
case 0:
wait = program.wait, bail = program.bail, quiet = program.quiet, token = program.token, verbose = program.verbose, colors = program.colors, report = program.report, errorsReport = program.errorsReport, junitReport = program.junitReport, junitReportPath = program.junitReportPath, mochawesomeReport = program.mochawesomeReport, mochawesomeReportPath = program.mochawesomeReportPath, parallel = program.parallel, loadTest = program.loadTest, suiteId = program.suiteId, flowId = program.flowId, loadTestOptions = program.loadTestOptions, testPlan = program.testPlan, additionalDescription = program.additionalDescription, labels = program.labels, labelsExpression = program.labelsExpression, pool = program.pool, tags = program.tags, overrideSuites = program.overrideSuites, branch = program.branch, retryFailedFlows = program.retryFailedFlows, parametersFile = program.parametersFile, inlineParameterOverride = program.inlineParameterOverride, apiCatalogService = program.apiCatalogService, turboParallel = program.turboParallel, _a = program.args, input = _a[0], rawParams = _a.slice(1);
logger = utils_1.getLogger({ verbose: verbose, colors: colors });
if (!token) {
validationFailed("No API token provided.");
}
parameters = toParams(rawParams, parametersFile);
if (verbose) {
// verbose trumps quiet:
quiet = false;
logger.log("Inputs:", {
wait: wait,
bail: bail,
quiet: quiet,
token: token,
verbose: verbose,
colors: colors,
report: report,
errorsReport: errorsReport,
junitReport: junitReport,
junitReportPath: junitReportPath,
mochawesomeReport: mochawesomeReport,
mochawesomeReportPath: mochawesomeReportPath,
parallel: parallel,
input: input,
loadTest: loadTest,
suiteId: suiteId,
flowId: flowId,
loadTestOptions: loadTestOptions,
testPlan: testPlan,
additionalDescription: additionalDescription,
labels: labels,
labelsExpression: labelsExpression,
pool: pool,
tags: tags,
overrideSuites: overrideSuites,
branch: branch,
retryFailedFlows: retryFailedFlows,
inlineParameterOverride: inlineParameterOverride,
apiCatalogService: apiCatalogService,
turboParallel: turboParallel,
parameters: parameters
});
}
loadmill = Loadmill({ token: token });
testFailed = function (msg) {
logger.log("");
logger.error("\u274C " + msg + ".");
if (bail) {
process.exit(1);
}
};
testStopped = function (msg) {
logger.log("");
logger.error("\u270B " + msg + ".");
if (bail) {
process.exit(1);
}
};
if (!(testPlan || (!loadTest))) return [3 /*break*/, 13];
if (!utils_1.isUUID(input)) { //if test plan flag is on then the input should be uuid
validationFailed("Test plan run flag is on but no valid test plan id was provided.");
}
planLabels = utils_1.convertStrToArr(labels);
planTags = utils_1.convertStrToArr(tags);
planOverrideSuites = utils_1.convertStrToArr(overrideSuites);
_b.label = 1;
case 1:
_b.trys.push([1, 11, , 12]);
logger.verbose("Executing test plan with id " + input);
return [4 /*yield*/, loadmill.runTestPlan({
id: input,
options: {
additionalDescription: additionalDescription,
labels: planLabels,
labelsExpression: labelsExpression,
pool: pool,
tags: planTags,
parallel: parallel,
branch: branch,
overrideSuites: planOverrideSuites,
maxFlakyFlowRetries: retryFailedFlows,
inlineParameterOverride: inlineParameterOverride,
apiCatalogService: apiCatalogService,
turboParallel: turboParallel
}
}, parameters)];
case 2:
running = _b.sent();
if (!(running && running.id)) return [3 /*break*/, 9];
if (!wait) return [3 /*break*/, 8];
logger.verbose("Waiting for test plan run with id", running.id);
return [4 /*yield*/, loadmill.wait(running)];
case 3:
res = _b.sent();
if (!quiet) {
logger.log(res ? utils_1.getObjectAsString(res, colors) : running.id);
}
if (report && res.testSuitesRuns) {
utils_1.printTestSuitesRunsReport(res.description, res.testSuitesRuns, logger, colors);
}
if (errorsReport && res.testSuitesRuns) {
utils_1.printOnlyFailedFlowRunsReport(res.testSuitesRuns, logger, colors);
}
if (!res) return [3 /*break*/, 7];
if (!junitReport) return [3 /*break*/, 5];
return [4 /*yield*/, reporter_1.junitReport(res, token, junitReportPath)];
case 4:
_b.sent();
_b.label = 5;
case 5:
if (!mochawesomeReport) return [3 /*break*/, 7];
return [4 /*yield*/, reporter_1.mochawesomeReport(res, token, mochawesomeReportPath)];
case 6:
_b.sent();
_b.label = 7;
case 7:
if (res && res.status === 'STOPPED') {
testStopped("Test plan with id " + (res.id || input) + " has stopped");
}
if (res && res.passed != null && !res.passed) {
testFailed("Test plan with id " + (res.id || input) + " has failed");
}
_b.label = 8;
case 8: return [3 /*break*/, 10];
case 9:
testFailed("Couldn't run test plan with id " + input);
_b.label = 10;
case 10: return [3 /*break*/, 12];
case 11:
e_1 = _b.sent();
if (verbose) {
logger.error(e_1);
}
extInfo = e_1.response && e_1.response.res && e_1.response.res.text;
testFailed("Couldn't run test plan with id " + input + " " + (extInfo ? extInfo : ''));
return [3 /*break*/, 12];
case 12: return [3 /*break*/, 20];
case 13:
id = void 0;
if (!loadTest) return [3 /*break*/, 15];
effectiveFlowId = flowId || input;
if (!suiteId || !utils_1.isUUID(suiteId)) {
validationFailed("Load from flow mode requires a valid --suite-id value.");
}
if (!effectiveFlowId || !utils_1.isUUID(effectiveFlowId)) {
validationFailed("Load from flow mode requires a valid flow id (provide --flow-id or positional input).");
}
logger.verbose("Creating and launching load test from suite " + suiteId + " flow " + effectiveFlowId);
return [4 /*yield*/, loadmill.runLoadTestFromFlow({
suiteId: suiteId,
flowId: effectiveFlowId,
loadTestOptions: parseJsonOption(loadTestOptions)
})];
case 14:
loadTestDef = _b.sent();
id = loadTestDef.id;
return [3 /*break*/, 17];
case 15:
configFile = input;
if (!configFile) {
validationFailed("No configuration file were provided.");
}
logger.verbose("Launching " + configFile + " as load test");
return [4 /*yield*/, loadmill.run(configFile, parameters)];
case 16:
id = _b.sent();
_b.label = 17;
case 17:
if (!(wait && (loadTest))) return [3 /*break*/, 19];
logger.verbose("Waiting for test:", res_1 ? res_1.id : id);
return [4 /*yield*/, loadmill.wait(res_1 || id)];
case 18:
res_1 = _b.sent();
_b.label = 19;
case 19:
if (!quiet) {
logger.log(JSON.stringify(res_1, null, 4) || id);
}
if (res_1 && res_1.passed != null && !res_1.passed) {
logger.error("\u274C Test " + id + " failed.");
if (bail) {
process.exit(1);
}
}
_b.label = 20;
case 20: return [2 /*return*/];
}
});
});
}
function validationFailed() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
console.log('');
console.error.apply(console, args);
console.log('');
program.outputHelp();
process.exit(3);
}
function toParams(rawParams, filePath) {
try {
var paramsArray = filePath ? tslib_1.__spreadArray(tslib_1.__spreadArray([], utils_1.readRawParams(filePath)), rawParams) : rawParams;
return utils_1.toLoadmillParams(paramsArray);
}
catch (err) {
validationFailed(err.message);
return {};
}
}
function parseJsonOption(value) {
if (!value) {
return undefined;
}
if (typeof value === 'object') {
return value;
}
try {
return JSON.parse(value);
}
catch (err) {
validationFailed("Invalid JSON provided for load test options: " + err.message);
return undefined;
}
}