"use strict"; /** * Configures `GlaceJS` before tests run. * * @namespace GlaceConfig * @prop {object} cluster - Cluster namespace. * @prop {boolean} [cluster.isMaster=true] - Mark current process as master. * @prop {boolean} [cluster.isSlave=false] - Mark current process as slave. * @prop {number} [cluster.slavesNum=0] - Number of slaves to launch. * @prop {number} [cluster.slaveId=null] - ID of slave. * @prop {object} session - Session namespace. * @prop {string} session.name - Session name. By default contains timestamp. * Can be overridden with CLI option `--session-name`. * @prop {string} session.id - Session ID. Default is timestamp. * @prop {boolean} [session.errors=[]] - List of session errors. * @prop {boolean} [session.isPassed=false] - Flag to define if tests session * run is passed or no. * @prop {array<string>} [session.preloads=[]] - Array of paths to `js` modules * which will be loaded before tests session run. * @prop {boolean} [session.interactive=false] - Flag to launch interactive * session. Can be overridden with CLI option `-i / --interactive`. * @prop {boolean} [session.debugOnFail=false] - Flag to enter to interactive * mode on step failure. Can be overridden with CLI option `--debug-on-fail`. * @prop {boolean} [session.exitOnFail=false] - Flag to break tests session on * first test failure. Can be overridden with CLI option `--exit-on-fail`. * @prop {string} [session.uncaughtException=log] - Strategy to process uncaught * exceptions. Available values are `log`, `fail`, `mocha`. Can be overridden * with CLI option `--uncaught`. * @prop {string} session.rootConftest - Path to `js` module which will be * loaded right after preloads. Can be overridden with CLI * option `--root-conftest`. * @prop {array<string>} session.killProcs - Array of process names which will * be killed before tests session run. Can be overridden with CLI * option `--kill-procs`. * @prop {object} retry - Retry settings namespace. * @prop {integer} [retry.id=0] - Number of retried session. * @prop {object} [retry.chunkIds={}] - Chunk ids grouped by retry values. For * example: `{1: ['1_1', '1_2'], 3: ['2_1'], 2: ['3_1', '3_2']}`. Populated on * first session run. * @prop {?array<string>} [retry.curChunkIds=null] - Reference to currently * populated group in `retry.chunkIds`. * @prop {object} test - Test settings namespace. * @prop {integer} [test.id=0] - Number of currently created test. Each test has * unique incremental id. On first session run it's assigned, on retry is used * to find test by id, because test name can be non-unique. * @prop {?TestCase} [test.curCase=null] - Currently executed test case. * @prop {array<TestCase>} [test.cases=[]] - Array of session test cases. * @prop {array<string>} [test.languages=[]] - Array of language names. Can be * overridden with CLI option `--languages`. * @prop {array<string>} [test.dirs=[]] - Array of test files or folders. Can * be overridden with CLI arguments or option `--targets` separated with comma. * @prop {boolean} [test.checkNames=true] - Flag to check test names uniqueness. * Can be overridden with CLI option `--dont-check-names`. * @prop {integer} [test.retries=0] - <a name="test-retry" href="#test-retry">#</a> * Number of test retries on failure. Overridden with CLI option `--retry`. * @prop {object} chunk - Chunk settings namespace. * @prop {integer} [chunk.id=0] - Number of chunk inside a test, incremental, * starts from `0` in each test. Fully unique chunk id is consist of `test.id` & * `chunk.id`, like `1_1`, `1_2`, `2_1`. * @prop {?string} [chunk.curId=null] - Currently executed fully unique chunk id. * @prop {array<string>} [chunk.passedIds=[]] - Fully unique ids of passed chunks. * Used to skip already passed chunks on retry. * @prop {integer} [chunk.retries=0] - <a name="test-chunk-retry" href="#test-chunk-retry">#</a> * Number of chunk retries on failure. Overridden with CLI option `--chunk-retry`. * @prop {integer} [chunk.timeout=180] - <a name="test-chunk-timeout" href="#test-chunk-timeout">#</a> * Timeout of chunk execution, **sec**. Overridden with CLI option `--chunk-timeout`. * @prop {object} report - Report namespace. * @prop {string} [report.dir=cwd/report] - Folder to save tests session report. * @prop {string} [report.testDir] - Folder to save test-specific artifacts. * @prop {boolean} [report.clear=true] - Flag to clear report before tests run. * Can be overridden with CLI option `--dont-clear-report`. * @prop {boolean} [report.errorsNow=false] - Flag to print test error right * after its capture. Can be overridden with CLI option `--errors-now`. * @prop {string} [report.failedTestsPath=cwd/failed-tests.json] - Path to file * where info about failed tests will be saved to. Can be overridden with CLI * option `--failed-tests-path`. * @prop {object} [xunit] - xUnit report namespace. * @prop {boolean} [xunit.use=false] - Flag to activate xUnit report. Can be * overridden with CLI option `--xunit`. * @prop {string} [xunit.path=report.dir/xunit.xml] - Path to xUnit report. Can * be overridden with CLI option `--xunit-path`. * @prop {string} [xunit.suiteName=session.name] - xUnit suite name. Can be * overridden with CLI option `--xunit-suite-name`. * @prop {object} [allure] - Allure report namespace. * @prop {boolean} [allure.use=false] - Flag to activate allure report. Can be * overridden with CLI option `--allure`. * @prop {string} [allure.dir=report.dir/allure] - Folder to save allure report. * Can be overridden with CLI option `--allure-dir`. * @prop {string} [allure.suiteName=session.name] - Allure suite name. Can be * overridden with CLI option `--allure-suite-name`. * @prop {object} testrail - Testrail report namespace. * @prop {boolean} [testrail.use=false] - Flag to activate testrail report. Can * be overridden with CLI option `--testrail`. * @prop {string} testrail.host - Testrail host. Can be overridden with CLI * option `--testrail-host`. * @prop {string} testrail.user - Testrail user name or email. Can be overridden * with CLI option `--testrail-user`. * @prop {string} testrail.token - Testrail auth token. Can be overridden with * CLI option `--testrail-token`. * @prop {string} testrail.projectId - Testrail project ID. Can be overridden * with CLI option `--testrail-project-id`. * @prop {string} testrail.suiteId - Testrail suite ID. Can be overridden with * CLI option `--testrail-suite-id`. * @prop {string} testrail.runName - Testrail run name. Can be overridden with * CLI option `--testrail-run-name`. * @prop {string} testrail.runDescription - Testrail run description. Can be * overridden with CLI option `--testrail-run-description`. * @prop {object} plugins - Plugins namespace. * @prop {string} plugins.dir - Folder with custom plugins. Can be overridden * with CLI option `--plugins-dir`. * @prop {object} filter - Tests filter namespace. * @prop {string} filter.grep - Mocha grep option to filter tests, scopes and * suites. Can be overridden with CLI option `-g / --grep`. * @prop {array<string>} filter.include - List of test names which should be * included to tests session. Can be overridden with CLI option `--include`. * @prop {array<string>} filter.exclude - List of test names which should be * excluded from tests session. Can be overridden with CLI option `--exclude`. * @prop {boolean} [filter.precise=false] - Flag for precise tests inclusion or * exclusion (not substring pattern). Can be overridden with CLI option * `--precise`. * @prop {object} tools - Tools namespace. * @prop {boolean} [tools.stepsList=false] - Flag to list available steps only. * Can be overridden with CLI option `--list-steps`. * @prop {string} tools.stepsFilter - String to filter steps. Can be overridden * with CLI option `--list-steps`. * @prop {boolean} [tools.testsList=false] - Flag to list implemented tests * only. Can be overridden with CLI option `--list-tests`. * @prop {string} tools.testsFilter - String to filter tests. Can be overridden * with CLI option `--list-tests`. * @prop {boolean} [tools.fixturesList=false] - Flag to list available fixtures * only. Can be overridden with CLI option `--list-fixtures`. * @prop {string} tools.fixturesFilter - String to filter fixtures. Can be * overridden with CLI option `--list-fixtures`. * @prop {boolean} [tools.checkTestrail=false] - Flag to check matching of * testrail cases with implemented tests only. Can be overridden with CLI * option `--testrail-check`. */ const fs = require("fs"); const path = require("path"); require("colors"); const _ = require("lodash"); const expect = require("chai").expect; const U = require("glace-utils"); U.docString(); const plugins = require("./plugins"); let config = U.config; const args = config.args; config.cluster = U.defVal(config.cluster, {}); config.cluster.slavesNum = U.defVal(args.slaves, 0); if (config.cluster.slavesNum === "auto") { config.cluster.slavesNum = require("os").cpus().length; } else { config.cluster.slavesNum = +config.cluster.slavesNum; } config.cluster.slaveId = parseInt(process.env.GLACE_SLAVE_ID) || null; config.cluster.isSlave = !!process.env.GLACE_SLAVE_ID; config.cluster.isMaster = !!config.cluster.slavesNum && !config.cluster.isSlave; config.cluster.artifactsDir = path.resolve(U.cwd, U.defVal(args.reportDir, "report")); config.session = U.defVal(config.session, {}); const date = new Date(); config.session.name = U.defVal(args.sessionName, `Session ${date.toLocaleString()}`); config.session.id = date.getTime(); config.session.errors = []; config.session.isPassed = false; config.session.preloads = []; config.session.interactive = args.i || args.interactive || false; if (config.cluster.slavesNum) { expect(config.session.interactive, "Interactive mode is incompatible with `--slaves`").to.be.false; } config.session.debugOnFail = args.debugOnFail || false; if (config.cluster.slavesNum) { expect(config.session.debugOnFail, "`--debug-on-fail` is incompatible with `--slaves`").to.be.false; } config.session.exitOnFail = args.exitOnFail || false; config.session.uncaughtException = (args.uncaught || "log").toLowerCase(); expect([ "log", "fail", "mocha" ], "Invalid `--uncaught` option").include(config.session.uncaughtException); if (args.rootConftest) config.session.rootConftest = path.resolve(U.cwd, args.rootConftest); if (args.killProcs && !config.cluster.isSlave) config.session.killProcs = U.splitBy(args.killProcs, ","); config.retry = U.defVal(config.retry, {}); config.retry.id = 0; config.retry.chunkIds = {}; config.retry.curChunkIds = null; const get_targets = () => { let tt; if (args._ && args._.length) { tt = args._; } else if (args.targets && args.targets.length) { tt = U.splitBy(args.targets, ","); } else { tt = ["tests"]; }; return tt.map(t => path.resolve(U.cwd, t)); }; config.test = U.defVal(config.test, {}); config.test.id = 0; config.test.curCase = null; config.test.cases = []; config.test.languages = []; if (args.languages) config.test.languages = U.splitBy(args.languages, ","); config.test.dirs = get_targets(); config.test.checkNames = !args.dontCheckNames; config.test.retries = Math.max(0, U.defVal(args.retry, 0)); config.chunk = U.defVal(config.chunk, {}); config.chunk.id = 0; config.chunk.curId = null; config.chunk.passedIds = []; config.chunk.retries = Math.max(0, U.defVal(args.chunkRetry, 0)); config.chunk.timeout = (args.chunkTimeout || 180) * 1000 || Infinity; config.report = U.defVal(config.report, {}); config.report.dots = args.dots || false; config.report.dir = config.cluster.artifactsDir; if (config.cluster.isMaster) { config.report.dir = path.resolve(config.report.dir, "master"); } if (config.cluster.isSlave) { config.report.dir = path.resolve(config.report.dir, "slave-" + config.cluster.slaveId); } config.report.testDir = null; config.report.clear = !args.dontClearReport; config.report.errorsNow = args.errorsNow || false; config.report.deepErrors = args.deepErrors || false; config.report.failedTestsPath = path.resolve(config.report.dir, U.defVal(args.failedTestsPath, "failed-tests.json")); if (!config.report.failedTestsPath.endsWith(".json")) config.report.failedTestsPath += ".json"; const tests_filter = filter => { const filePath = path.resolve(U.cwd, filter); if (fs.existsSync(filePath)) { config.filter.precise = true; return U.loadJson(filePath); } else { return U.splitBy(filter, "|").map(e => ({ id: e })); } }; config.filter = U.defVal(config.filter, {}); config.filter.grep = args.g || args.grep; config.filter.precise = args.preciseMatch || false; if (args.include) config.filter.include = tests_filter(args.include); if (args.exclude) config.filter.exclude = tests_filter(args.exclude); if (config.filter.include) { for (const include of config.filter.include) { if (!include.passed_chunk_ids) continue; config.chunk.passedIds = config.chunk.passedIds.concat(include.passed_chunk_ids); } } if (process.env.GLACE_TEST_IDS) { config.filter.testIds = U.splitBy(process.env.GLACE_TEST_IDS, ",").map(i => +i); } else { config.filter.testIds = null; } config.xunit = U.defVal(config.xunit, {}); config.xunit.use = U.defVal(args.xunit, false); config.xunit.path = path.resolve(config.report.dir, U.defVal(args.xunitPath, "xunit.xml")); config.xunit.suiteName = U.defVal(args.xunitSuiteName, config.session.name); config.allure = U.defVal(config.allure, {}); config.allure.use = U.defVal(args.allure, false); config.allure.dir = path.resolve(config.report.dir, U.defVal(args.allureDir, "allure")); config.allure.suiteName = U.defVal(args.allureSuiteName, config.session.name); config.testrail = U.defVal(config.testrail, {}); config.testrail.use = U.defVal(args.testrail, false); config.testrail.host = U.defVal(args.testrailHost); config.testrail.user = U.defVal(args.testrailUser); config.testrail.token = U.defVal(args.testrailToken); config.testrail.projectId = U.defVal(args.testrailProjectId); config.testrail.suiteId = U.defVal(args.testrailSuiteId); config.testrail.runName = U.defVal(args.testrailRunName); config.testrail.runDescription = U.defVal(args.testrailRunDesc); config.plugins = U.defVal(config.plugins, {}); if (args.pluginsDir) config.plugins.dir = path.join(U.cwd, args.pluginsDir); config.plugins.disableDefault = U.defVal(args.disableDefaultPlugins, false); plugins.getModules("config"); config.tools = U.defVal(config.tools, {}); config.tools.stepsList = !!args.listSteps; config.tools.stepsFilter = typeof(args.listSteps) === "string" ? args.listSteps : null; config.tools.testsList = !!args.listTests; config.tools.testsFilter = typeof(args.listTests) === "string" ? args.listTests : null; config.tools.fixturesList = !!args.listFixtures; config.tools.fixturesFilter = typeof(args.listFixtures) === "string" ? args.listFixtures : null; config.tools.pluginsList = !!args.listPlugins; config.tools.checkTestrail = U.defVal(args.testrailCheck, false); if (config.session.debugOnFail) { config.chunk.timeout = Infinity; config.xunit.use = false; config.allure.use = false; config.testrail.use = false; } if (config.session.interactive) { const temp = require("temp").track(); const tempPath = temp.path({ prefix: "test", suffix: ".js" }); const tempData = "test('interactive', () => chunk(async () => await $.debug()));"; fs.writeFileSync(tempPath, tempData); config.test.dirs = [tempPath]; config.chunk.timeout = Infinity; config.filter.grep = null; config.filter.include = null; config.filter.exclude = null; config.xunit.use = false; config.allure.use = false; config.testrail.use = false; } let userConfig = {}; const userConfigPath = path.resolve(U.cwd, (args.userConfig || "config.js")); if (fs.existsSync(userConfigPath)) userConfig = require(userConfigPath); _.assign(config, userConfig); module.exports = config;