siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
667 lines (531 loc) • 25.5 kB
JavaScript
/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
Class('Siesta.Launcher.BaseLauncher', {
/*PKGVERSION*/VERSION : '5.6.1',
does : [
JooseX.Observable,
Siesta.Util.Role.CanGetType,
Siesta.Launcher.FileSystem.Base,
Siesta.Launcher.CommandLineTool.BaseTool
],
has : {
runners : Joose.I.Array,
// native simulator is shared between all pages for webdriver+robotjs, or per-page for puppeteer
sharedNativeSimulator : true,
optionsWrapper : null,
projectConfig : Joose.I.Object,
helpIntro : function () {
var style = this.style()
return [
style.bold('Usage: ') + this.executableName + ' url [OPTIONS]\n',
'The ' + style.bold('url') + ' should point to the HTML wrapper of your Siesta project file. All options are optional.',
''
]
},
knownOptionGroups : {
init : {
'00-system' : {
name : 'Base options'
},
'10-basic' : {
name : 'Basic options'
},
'15-debug' : {
name : 'Debugging'
},
'20-misc' : {
name : 'Miscellaneous'
},
'30-coverage' : {
name : 'Coverage options'
},
'80-ci' : {
name : 'Continous integration options'
}
}
},
knownOptions : {
init : [
{
name : 'help',
desc : 'Prints this help message and exit',
group : '00-system'
},
{
name : 'version',
desc : 'Prints versions of Siesta and automation component and exit',
group : '00-system'
},
{
name : 'project-url',
desc : 'The url of the project for this test suite',
group : '00-system'
},
{
name : 'config-file',
desc : [
'The file with the "relaxed JSON" object, containing any command line or project options.',
'Please refer to the "Siesta automation" guide for details.'
],
group : '00-system'
},
{
name : 'filter',
desc : [
'A filter string to only launch matching tests. Matches the behaviour of the filter',
'field in Siesta UI (see the tooltip for more info). This option is processed before the --include and --exclude options'
],
group : '10-basic'
},
{
name : 'include',
desc : [
'A regular expression to only include tests with matching urls.'
],
group : '10-basic'
},
{
name : 'exclude',
desc : 'A regular expression to exclude tests with matching urls, takes precedence over `include`',
group : '10-basic'
},
{
name : 'report-file',
desc : [
'If this option is provided Siesta will create a report after running all tests.',
'The format of the report can be specified with the --report-format option.',
'This option specifies the file name template to save the report to. Template can contain',
'variables, marked as {entry}.',
'The value for variables is first taken from the `--cap` option values and then from regular options.',
'For example, if you pass the options "--browser=ie --cap version=9", you can then set the file name template as:\n',
' --report-file=result-{browser}-{version}.json\n',
'and resulting file will have the following name: "result-ie-9.json".',
'For the HTML report format this option actually specifies the directory, to which save the required files.',
'This option can be repeated several times (several reports will be generated), in this case, the --report-format option should be provided',
'exactly the same number of times, the files and formats will be matched by their order'
],
group : '10-basic'
},
{
name : 'report-file-prefix',
desc : [
'Deprecated. This option has the same effect as "report-file", with one addition - ',
'variable {browser} is always inserted before the file extension, so the',
'following report file name are the same:\n',
' --report-file-prefix=report_.json and --report-file=report_{browser}.json\n'
],
group : '10-basic'
},
{
name : 'report-format',
desc : [
'Specifies the test suite report format. Recognizable formats are: "html, json, jsons, junit", default value is `json`.',
'The `html` report includes `jsons` with additional files for visualizing the results.',
'When using `html` report, the `--report-file` option actually specifies the _directory_ to save data into, not a single file.',
'This option can be repeated several times (several reports will be generated), in this case, the --report-file option should be provided',
'exactly the same number of times, the files and formats will be matched by their order'
],
group : '10-basic'
},
{
name : 'restart-on-blur',
desc : [
'Experimental. Restart the test if it has lost the focus for any reason.'
],
group : '20-misc'
},
{
name : 'randomize-tests-order',
desc : [
'Randomize the order of test execution. Helps you ensure your tests do not depend on each other.',
'Also if you have some heavy tests, grouped in the certain place of the test suite, this option',
'will sort of balance the load across the whole suite.'
],
group : '20-misc'
},
{
name : 'no-color',
desc : 'Disable the coloring of the output',
group : '20-misc'
},
{
name : 'verbose',
desc : 'Print all assertions of the test (not only from the failed ones)',
group : '20-misc'
},
{
name : 'debug',
desc : 'Enable diagnostic messages',
group : '20-misc'
},
{
name : 'flat-output',
desc : [
'Print assertions as a flat list (instead of tree structure based on `describe/it` sections).',
'Use this only for compatibility with the Siesta versions before 4.1.0'
],
group : '20-misc'
},
{
name : 'width',
desc : [
'Width of the test page, in pixels. Note, that this option sets the width of the test project page,',
'not the width of the individual test`s iframe. Use `viewportWidth` project option for that.\n',
'IMPORTANT: For IE, this option should have bigger value than `viewportWidth`'
],
group : '20-misc'
},
{
name : 'height',
desc : [
'Height of the test page, in pixels. Note, that this option sets the height of the test project page,',
'not the height of the individual test`s iframe. Use `viewportHeight` project option for that.\n',
'IMPORTANT: For IE, this option should have bigger value than `viewportHeight`'
],
group : '20-misc'
},
{
name : 'chunk-size',
desc : [
'The number of tests, after which the browser will be restarted, default value is 20'
],
group : '20-misc'
},
{
name : 'max-workers',
desc : [
'The maximum number of test pages that can be opened simultaneously. Default value is 1.',
'You can increase this option for the BrowserStack and SauceLabs for example.'
],
group : '20-misc'
},
{
name : 'pause',
desc : [
'Pause between individual tests, in milliseconds, default value is 10. Overrides the',
'`pauseBetweenTests` project option.'
],
group : '20-misc'
},
{
name : 'rerun-failed',
desc : [
'Experimental. When this option is enabled, after the test suite has completed execution,',
'if the number of failed tests is less than 10% from the total number of tests,',
'failed tests are re-run one more time.',
'If such tests will pass on the 2nd execution',
'they will be reported as passed. This option is automatically disabled, when --verbose option',
'is enabled and --max-workers=1 (because assertions will be streamed lived in this case)'
],
group : '20-misc'
},
{
name : 'show-cursor',
desc : [
'Show the simulated mouse cursor, when running in automation mode. Not supported in IE.',
'Slightly affects performance'
],
group : '20-misc'
},
{
name : 'restart-attempts',
desc : [
'The number of Siesta`s attempts to re-start a test if it has stopped functioning',
'for any reason (browser crashed, WebDriver exception, connection lost etc). Default value is 1 (restart one time)'
],
group : '20-misc'
},
{
name : 'coverage-report-format',
desc : [
'Specifies the format of the code coverage report, recognized',
'values are any Nyc reporters, listed here: https://git.io/vHysA\n',
'Typical values are: `html`, `text`, `lcov`.',
'If provided, this option will enable the collection of the code coverage information.',
'This option can be repeated several times, resulting in several reports',
'saved in the same directory. Alternatively, several formats can be',
'concatenated with "," or "+": --coverage-report-format=html+text\n',
'Alias of the --nyc.reporter'
],
group : '30-coverage'
},
{
name : 'coverage-report-dir',
desc : [
'Specifies the output directory for the code coverage report.',
'Default value is "./coverage/"',
'Alias of the --nyc.report-dir'
],
group : '30-coverage'
},
{
name : 'build',
desc : [
'Name of the build. Will be provided to the cloud testing infrastructure in the apropriate capability.',
'Will be also available in the JSON reports as the `build` property and `name` attribute in JUnit report'
],
group : '80-ci'
},
{
name : 'teamcity',
desc : [
'Enables the special markup in the test suite output that is recognized by the',
'TeamCity to generate realtime progress information'
],
group : '80-ci'
},
{
name : 'team-city',
desc : [
'Synonym for --teamcity'
],
group : '80-ci'
},
{
name : 'tc-suite',
desc : [
'Name of the test suite to run (currently used only in TeamCity and requires --teamcity)'
],
group : '80-ci'
},
{
name : 'jenkins',
desc : [
'Forces launcher to always exit with 0 exit code (otherwise Jenkins thinks build has failed',
'and will not try to create a report)'
],
group : '80-ci'
}
]
},
isRunning : false,
shutDownStarted : false,
manuallyProcessCoverageResults : true
},
methods : {
optionToString : function (value, name) {
if (typeof value === 'object' && this.typeOf(value) === 'Boolean')
return '--' + name
else
return '--' + name + '=' + value
},
prepareOptions : function () {
var me = this
var processed = this.processArguments(this.args)
this.argv = processed.argv
this.options = processed.options
this.optionGroups = processed.groups
this.positionalGroups = processed.positionalGroups
var options = this.options
// add trailing slash if missing
this.binDir = this.argv.shift().replace(/\/?$/, '/')
this.terminalWidth = this.argv.shift()
if (options.version) {
me.printVersion()
return 8
}
if (options.help) {
me.printHelp()
return 8
}
var defaultConfig
if (options[ 'config-file' ]) {
defaultConfig = this.readConfigFileOptions(options[ 'config-file' ])
if (!defaultConfig) return false
} else {
try {
defaultConfig = this.readDefaultConfig()
} catch (e) {
return false
}
}
if (defaultConfig) {
this.options = options = Joose.O.extend(this.stripLeadingMinuses(defaultConfig.cmd || {}), options)
this.projectConfig = defaultConfig.project || {}
}
Joose.O.each(options, function (value, name) {
if (!me.hasOption(name)) me.onUnknownOption(value, name)
})
this.optionsWrapper = this.createOptionsWrapper(this.options)
return this.optionsWrapper.validate()
},
onUnknownOption : function (value, name) {
this.warn("Unknown option provided: " + name)
},
readDefaultConfig : function () {
var up = ''
var path = this.normalizePath('siesta.json')
do {
var isRoot = /^\/siesta\.json/.test(path) || this.isWindows && /\w:[\\\/]siesta\.json/.test(path)
if (this.fileExists(path)) {
var config = this.readConfigFileOptions(path)
if (!config) throw "error"
return config
}
up = '..' + this.sep + up
path = this.normalizePath(up + 'siesta.json')
} while (!isRoot)
return null
},
stripLeadingMinuses : function (obj) {
var stripped = {}
Joose.O.each(obj, function (value, name) {
stripped[ /^--/.test(name) ? name.substr(2) : name ] = value
})
return stripped
},
createOptionsWrapper : function (cfg) {
return new Siesta.Launcher.Options.Base({ options : cfg, launcher : this })
},
getSiestaVersion : function () {
return Siesta.Launcher.BaseLauncher.meta.VERSION
},
printVersion : function () {
var version = this.getSiestaVersion()
if (version) this.print(this.style().bold("Siesta : ") + version)
},
getPlatformId : function () {
if (this.isMacOS) return 'macos'
if (this.isWindows) return 'windows'
if (this.is64Bit) return 'linux64'
return 'linux32'
},
checkIsWindows : function () {
throw new Error("Abstract method call: `checkIsWindows`")
},
checkIsMacOS : function () {
throw new Error("Abstract method call: `checkIsMacOS`")
},
checkIs64Bit : function () {
throw new Error("Abstract method call: `checkIs64Bit`")
},
print : function () {
throw new Error("Abstract method call: `print`")
},
printErr : function () {
throw new Error("Abstract method call: `printErr`")
},
doExit : function () {
throw new Error("Abstract method call: `doExit`")
},
exit : function (code) {
this.doExit(this.options.jenkins ? 0 : code)
},
// this method runs before the "prepareOptions" call
createRunners : function () {
throw new Error("Abstract method call: `createRunners`")
},
getTerminalWidth : function () {
throw new Error("Abstract method call: `getTerminalWidth`")
},
destroyRunners : function () {
var promises = []
Joose.A.each(this.runners, function (runner) { promises.push(runner.destroy()) })
return promises
},
constructUrl : function (baseURL, options) {
options = options || {}
if (!baseURL.match(/^https?:\/\//)) baseURL = 'http://' + baseURL
var isFirst = true
for (var name in options) {
var value = options[ name ]
// ignore `null` and `undefined` values
if (value == null) continue
var delimeter = isFirst ? (baseURL.match(/\?/) ? '&' : '?') : '&'
baseURL += delimeter + name + '=' + encodeURIComponent(value)
isFirst = false
}
return baseURL
},
buildHarnessUrlQueryParams : function () {
return {}
},
saveReport : function (reportFormat, reportFile, reportContent) {
if (reportFormat == 'html')
this.saveHtmlReport(reportFile, reportContent)
else
this.saveFile(reportFile, reportContent)
},
saveHtmlReport : function (reportDir, reportContent) {
var binDir = this.binDir
this.copyFile(binDir + 'reports/html/index.html', reportDir + '/index.html')
this.copyFile(binDir + '../resources/css/siesta-all.css', reportDir + '/css/siesta-html-report.css')
this.copyFile(binDir + '../resources/css/siesta-all-part-1.css', reportDir + '/css/siesta-all-part-1.css')
this.copyFile(binDir + '../resources/css/siesta-all-part-2.css', reportDir + '/css/siesta-all-part-2.css')
this.copyFile(binDir + '../siesta-all.js', reportDir + '/siesta-html-report.js')
this.copyFile(binDir + '../resources/images/loadmask/loading.gif', reportDir + '/images/loadmask/loading.gif')
this.copyFile(binDir + '../resources/images/domcontainer-bg.png', reportDir + '/images/domcontainer-bg.png')
this.copyTree(binDir + '../resources/css/fonts', reportDir + '/css/fonts')
this.copyTree(binDir + '../resources/css/font-awesome', reportDir + '/css/font-awesome')
this.copyTree(binDir + '../resources/css/font-ext', reportDir + '/css/font-ext')
this.saveFile(reportDir + '/report-data.json', reportContent)
},
// promised method
destroy : function () {
this.isRunning = false
this.fireEvent('destroy')
return Promise.all(this.destroyRunners())
},
canStreamAssertions : function () {
var options = this.options
var maxWorkers = Number(options[ 'max-workers' ])
if (maxWorkers === 1) return true
if (options[ 'max-workers' ] == null) {
if (this.runners[ 0 ].maxWorkers > 1) return false
return true
}
return false
},
start : function () {
var me = this
var optionsCheck = me.prepareOptions()
if (optionsCheck === false) {
return Promise.resolve(6)
} if (typeof optionsCheck == 'number') {
return Promise.resolve(optionsCheck)
} else {
// create "optionAlias" entries in the "options" object for the "option-alias"
me.aliasOptions()
return me.setup().then(function (res) {
if (res == 'instrumentation') {
// return `null` to not mask the exit code from the instrumentation instance (which is launched in NodeJS launcher)
return null
} else {
var options = me.options
var dispatcher = new Siesta.Launcher.Dispatcher({
// role can be applied to instance (it is called "trait" then)
trait :
options[ 'team-city' ] || options[ 'teamcity' ] ? Siesta.Launcher.Dispatcher.Reporter.TeamCity : null,
projectUrl : me.constructUrl(options[ 'project-url' ], me.buildHarnessUrlQueryParams()),
options : options,
launcher : me,
givenRunners : me.runners = me.createRunners(),
reRunFailed : options[ 'rerun-failed' ],
streamAssertions : me.canStreamAssertions(),
projectConfig : me.getCommonProjectConfig()
})
me.debug("Dispatcher start")
me.isRunning = true
return dispatcher.start()
}
}).then(function (exitCode) {
return me.destroy().then(function () {
return exitCode
})
})
}
},
getCommonProjectConfig : function () {
return Object.assign({}, this.projectConfig, this.optionGroups.project)
},
setup : function () {
// Joose.C.debug = true
return Promise.resolve()
}
}
// eof methods
})