    /*

    Siesta 5.3.0
    Copyright(c) 2009-2019 Bryntum AB
    https://bryntum.com/contact
    https://bryntum.com/products/siesta/license

    */
    Role('Siesta.Project.Browser.Automation', {

        does        : [
            Siesta.Util.Role.CanStyleOutput,
            Siesta.Util.Role.CanFormatStrings
        ],

        has : {
            outputLog                       : Joose.I.Array,

            currentTestTimeout              : null,

            lastActivity                    : null,
            exitStatus                      : null,

            activeTestAutomationId          : null,

            testResults                     : Joose.I.Array,

            streamAssertions                : false,

            eventLog                        : Joose.I.Array,

            enableCodeCoverage              : false,

            manuallyProcessCoverageResults  : true,

            codeCoverageResults             : Joose.I.Array
        },

        override : {

            setup : function () {
                if (this.isAutomated) {
                    this.autoLaunchTests    = false
                    this.forceDOMVisible    = true

                    this.speedRun           = true
                    this.forcedRunCore      = 'sequential'
                    this.transparentEx      = false
                    this.keepNLastResults   = 0

                    this.waitForTimeout     = this.waitForTimeout * 3
                    this.defaultTimeout     = this.defaultTimeout * 3
                    this.isReadyTimeout     = this.isReadyTimeout * 3

                    this.lastActivity       = new Date()

                    var me                  = this

                    if (typeof window != 'undefined')
                        window.onerror          = function (message, url, lineNumber, col, error) {
                            me.warn("[ERROR] message : " + message)
                            me.warn("[ERROR] url     : " + url)
                            me.warn("[ERROR] line    : " + lineNumber)

                            if (col) me.warn("[ERROR] col     : " + col)
                            if (error && error.stack) me.warn("[ERROR] stack   : " + error.stack)
                        }

                    this.on('maxtimeoutchanged', function (event, timeout) {
                        me.onTestMaxTimeoutChanged(event.source, timeout)
                    })

                    this.on('focuslost', function (event) {
                        me.onTestFocusLost(event.source)
                    })
                }

                this.SUPERARG(arguments)
            }
        },

        after : {

            markMissingFile : function (desc) {
                this.warn(Siesta.Resource('Siesta.Role.ConsoleReporter', 'missingFileText').replace("{URL}", desc.url))

                if (this.isAutomated) {
                    this.lastActivity           = new Date()

                    var result                  = {
                        automationElementId : desc.automationElementId,
                        url                 : desc.url,
                        ERROR               : "Can't open test file: " + desc.url
                    }

                    if (this.streamAssertions)
                        this.eventLog.push({
                            isResult        : true,
                            data            : result
                        })
                    else
                        this.testResults.push(result)
                }
            },

            onTestSuiteStart : function (descriptors, contenManager, launchState) {
                // if (this.isAutomated && this.initialContentManagerState) contenManager.setState(this.initialContentManagerState)
            },

            onTestSuiteEnd : function (descriptors, contenManager, launchState) {
                if (this.isAutomated && !launchState.needToStop) this.exit()
            },

            onTestStart : function (test) {
                if (this.isAutomated && this.isTestActionActual(test)) {
                    this.currentTestTimeout     = test.getMaximalTimeout() * 2

                    this.activeTestAutomationId = test.automationElementId

                    this.lastActivity           = new Date()

                    if (this.streamAssertions)
                        if (!test.parent)
                            this.eventLog.push({
                                isUpdate        : true,
                                data            : test.getResults().toJSON()
                            })
                }
            },

            onTestEnd : function (test) {
                if (this.isAutomated && this.isTestActionActual(test)) {
                    this.lastActivity           = new Date()

                    this.activeTestAutomationId = null

                    if (this.streamAssertions)
                        this.eventLog.push({
                            isResult        : true,
                            data            : test.getResults().toJSON()
                        })
                    else
                        this.testResults.push(test.getResults().toJSON())

                    if (this.enableCodeCoverage && this.manuallyProcessCoverageResults) {
                        var result      = test.global.__coverage__

                        result && this.codeCoverageResults.push(result)
                    }
                }
            },

            onTestUpdate : function (test, result, parentResult) {
                if (this.isAutomated && this.isTestActionActual(test)) {
                    this.lastActivity           = new Date()

                    if (this.streamAssertions)
                        if (!(result.isWaitFor && !result.completed) && !(result instanceof Siesta.Result.Summary)) {
                            this.eventLog.push({
                                isUpdate        : true,
                                data            : result.toJSON()
                            })

                            // in "breakOnFail" case the failed assertion will switch "launchState" to "needToStop"
                            // and "isTestActionActual" will return false for any following updates/results,
                            // so, adding total test result manually
                            if (this.breakOnFail && ((result instanceof Siesta.Result.Assertion) && !result.isPassed()))
                                this.eventLog.push({
                                    isResult        : true,
                                    data            : test.getResults().toJSON()
                                })
                        }
                }
            }
        },

        methods : {

            isTestActionActual : function (test) {
                if (test.launchId != this.currentLaunchId) return false

                var launchState   = this.launches[ this.currentLaunchId ]

                return launchState ? !launchState.needToStop : false
            },

            onTestFocusLost : function (test) {
                if (this.isAutomated) {
                    test.warn(Siesta.Resource('Siesta.Test.Browser').get('focusLostWarningLauncher', { url : test.url }))

                    this.stopCurrentLaunch()

                    this.exit('focus_lost')
                }
            },

            onTestMaxTimeoutChanged : function (test, timeout) {
                if (this.isAutomated) this.currentTestTimeout = timeout * 2
            },

            filterDescriptors : function (includeTests, excludeTests, descriptors) {
                includeTests        = includeTests ? new RegExp(includeTests) : null
                excludeTests        = excludeTests ? new RegExp(excludeTests) : null

                var filtered        = []

                if (includeTests || excludeTests) {
                    Joose.A.each(this.flattenDescriptors(descriptors || this.descriptors), function (desc) {
                        if (includeTests && !includeTests.test(desc.url)) return
                        if (excludeTests && excludeTests.test(desc.url)) return

                        filtered.push(desc)
                    })
                } else
                    filtered        = this.flattenDescriptors(descriptors || this.descriptors)

                return filtered
            },

            getLastActivity : function () {
                return this.lastActivity - 0
            },

            log : function (text) {
                if (this.isAutomated) {
                    if (this.streamAssertions)
                        this.eventLog.push({
                            isLog       : true,
                            data        : text
                        })
                    else
                        this.outputLog.push(text)
                }
            },

            exit : function (status) {
                this.exitStatus = status || 'all_processed'
            },

            getAutomationState : function () {
                var launchState   = this.launches[ this.currentLaunchId ]

                var state   = {
                    activeTestAutomationId  : this.activeTestAutomationId,
                    activityTimeout         : this.currentTestTimeout,
                    lastActivity            : this.lastActivity - 0,
                    testResults             : this.flushTestResults(),
                    log                     : this.flushLog(),
                    exitStatus              : this.exitStatus,
                    commands                : this.flushAutomationCommands && this.flushAutomationCommands(),
                    // launchState is deleted upon launch completion
                    notLaunched             : launchState ? launchState.notLaunchedByAutomationId : {}
                }

                if (this.streamAssertions) {
                    state.eventLog          = this.flushEventLog()
                }

                if (this.enableCodeCoverage) {
                    state.coverageResult    = this.flushCoverageResults()
                }

                return state
            },

            flushLog : function () {
                var outputLog       = this.outputLog

                if (outputLog.length) {
                    this.outputLog  = []

                    return outputLog
                }
            },

            flushCoverageResults : function () {
                var results         = this.codeCoverageResults

                if (results.length) {
                    this.codeCoverageResults    = []

                    return results
                }
            },

            flushTestResults : function () {
                var testResults         = this.testResults

                if (testResults.length) {
                    this.testResults    = []

                    return testResults
                }
            },

            flushEventLog : function () {
                var eventLog        = this.eventLog

                if (eventLog.length) {
                    this.eventLog   = []

                    return eventLog
                }
            },

            cascadeStructureLeafOnly : function (func, list) {
                var me      = this

                Joose.A.each(list, function (desc) {
                    if (desc.group) {
                        me.cascadeStructureLeafOnly(func, desc.items)
                    } else
                        func(desc)
                })
            },

            getDescriptorStructure : function (desc, visibleById) {
                var me      = this

                if (visibleById && desc.id != '__ROOT__' && !visibleById[ desc.id ]) return null

                if (desc.group) {
                    var items       = []

                    Joose.A.each(desc.items, function (desc) {
                        var res     = me.getDescriptorStructure(desc, visibleById)

                        if (res) items.push(res)
                    })

                    return {
                        id      : desc.id,
                        group   : desc.group || desc.title || desc.name,
                        items   : items
                    }
                } else {
                    return desc.id
                }
            },

            getConfigInfo : function (includeTest, excludeTests, filterValue, projectConfig) {
                var me                  = this
                var visibleById

                // need to configure project first, because some command line options (like `simulation`) overrides
                // default project config and affect the result of this method
                if (projectConfig) this.configure(projectConfig)

                if (filterValue) {
                    var filterer        = new Siesta.Util.TreeStoreFilterer({
                        idProp          : 'id',
                        childNodesProp  : 'items',
                        parentNodeProp  : 'parent',
                        isLeaf          : function (node) { return !node.group }
                    })

                    var res             = filterer.parseFilterValue(filterValue)

                    var testFilterRegexps   = res.testFilterRegexps
                    var groupFilterRegexps  = res.groupFilterRegexps

                    var getTitle        = function (node) {
                        // do not consider a test suite title as a "root group", otherwise, for the suite title "Test suite"
                        // the --filter="ui>" will match all top-level tests
                        if (node == me)
                            return ''
                        else
                            return node.group || node.title || node.name
                    }

                    visibleById         = filterer.collectNodes({
                        id              : '__ROOT__',
                        group           : '__ROOT__',
                        items           : this.descriptors
                    }, {
                        filter          : function (node) {
                            return filterer.checkCommonFilter(node, getTitle, testFilterRegexps, groupFilterRegexps)
                        }
                    })
                }

                var structure           = this.getDescriptorStructure(
                    { id : '__ROOT__', group : '__ROOT__', items : this.descriptors },
                    visibleById
                )

                var list

                if (filterValue) {
                    list                = []

                    this.cascadeStructureLeafOnly(function (id) { list.push(me.getDescById(id)) }, structure.items)
                } else
                    list                = this.descriptors

                var filteredFlattenList = this.filterDescriptors(includeTest, excludeTests, list)

                return {
                    // system info
                    VERSION         : Siesta.meta.VERSION,
                    title           : this.title,
                    structure       : structure,
                    descriptors     : this.sortDescriptors(filteredFlattenList, 'sequential', true),

                    // environment
                    hostName        : typeof location != 'undefined' ? location.host : require('os').hostname(),
                    userAgent       : typeof navigator != 'undefined' ? navigator.userAgent : 'NodeJS/' + process.version,
                    platform        : typeof navigator != 'undefined' ? navigator.platform : process.platform,

                    hasNativeSimulation         : this.hasDescriptorWithNativeEventsSimulation(filteredFlattenList),

                    // options
                    breakOnFail                 : this.breakOnFail,
                    screenshotCompareConfig     : this.screenshotCompareConfig
                }
            },

            // chunk task - [ { descId : descId, automationElementId : elId }, ... ]
            launchAutomatedTests : function (task, options) {
                if (!this.setupDone) {
                    this.on('setupdone', function () { this.launchAutomatedTests(task, options) })

                    return
                }

                if (options.projectConfig) this.configure(options.projectConfig)

                this.enableCodeCoverage             = Boolean(options.enableCodeCoverage)
                this.streamAssertions               = Boolean(options.streamAssertions)
                this.showCursor                     = Boolean(options.showCursor)

                if (options.pause != null) this.pauseBetweenTests   = options.pause
                if (options.restartOnBlur != null) this.restartOnBlur   = options.restartOnBlur

                var me              = this

                var descriptors     = Joose.A.map(task, function (el) {
                    var desc                    = me.getScriptDescriptor(el.descId)

                    desc.automationElementId    = el.automationElementId

                    return desc
                })

                this.launch(descriptors)
            },

            warn : function (text) {
                this.log({
                    text        : text,
                    isWarning   : true
                })

                if (typeof window != 'undefined')
                    window.console && console.warn(text)

                if (this.viewport) {
                    Ext.toast({
                        cls     : 'tr-warn-toast',
                        title   : 'Warning',
                        html    : text.replace(/\n/g, '<br>'),
                        align   : 't',
                        autoCloseDelay : 3000
                    })
                }
            },

            getExitCode : function () {
                return this.allPassed() ? 0 : 1
            }

        }
    })
    //eof Siesta.Project.Browser.Automation

    if (!Siesta.Project.NodeJS)
        Siesta.Project.Browser.meta.extend({
            does : [
                Siesta.Project.Browser.Automation,
                Siesta.Project.Browser.Automation.Selenium,
                Siesta.Project.Browser.Automation.Puppeteer
            ]
        })
    else
        if (Siesta.Project.NodeJS)
            Siesta.Project.NodeJS.meta.extend({
                does : [
                    Siesta.Project.Browser.Automation,
                    Siesta.Project.Browser.Automation.NodeJS
                ]
            })