siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
1,324 lines (982 loc) • 50.8 kB
JavaScript
/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
/**
@class Siesta.Project.Browser
@extends Siesta.Project
Class, representing the browser project. This class provides a web-based UI and defines some additional configuration options.
The default value of the `testClass` configuration option in this class is {@link Siesta.Test.Browser}, which contains
only generic browser-related assertions. So, use this project class, when testing a generic web page.
This file is for reference only, for a getting start guide and manual, please refer to <a href="#!/guide/getting_started_browser">Siesta getting started in browser environment</a> guide.
Synopsys
========
var project = new Siesta.Project.Browser();
project.configure({
title : 'Awesome Test Suite',
transparentEx : true,
autoCheckGlobals : true,
expectedGlobals : [
'Ext',
'Sch'
],
preload : [
"http://cdn.sencha.io/ext-4.0.2a/ext-all-debug.js",
"../awesome-project-all.js",
{
text : "console.log('preload completed')"
}
]
})
project.plan(
// simple string - url relative to project file
'sanity.t.js',
// test file descriptor with own configuration options
{
url : 'basic.t.js',
// replace `preload` option of project
preload : [
"http://cdn.sencha.io/ext-4.0.6/ext-all-debug.js",
"../awesome-project-all.js"
]
},
// groups ("folders") of test files (possibly with own options)
{
group : 'Sanity',
autoCheckGlobals : false,
items : [
'data/crud.t.js',
...
]
},
...
)
project.start()
*/
Class('Siesta.Project.Browser', {
isa : Siesta.Project,
has : {
id : null,
/**
* @cfg {Class} testClass The test class which will be used for creating test instances, defaults to {@link Siesta.Test.Browser}.
* You can subclass {@link Siesta.Test.Browser} and provide a new class, please refer to the <a href="#!/guide/extending_test_clas">Extending test class</a> guide.
*
* This option can be also specified in the test file descriptor.
*/
testClass : Siesta.Test.Browser,
viewportClass : "Siesta.Project.Browser.UI.Viewport",
viewport : null,
/**
* @cfg {Boolean} autoRun When set to `true`, project will automatically launch the execution either of the checked test files or the whole suite.
* Default value is `false`
*/
autoRun : false,
/**
* @cfg {Boolean} viewDOM When set to `true`, project will expand the panel with the `<iframe>` of the test file, so you can examine the content of DOM.
* Default value is `false`
*/
viewDOM : false,
/**
* @cfg {Boolean} scaleToFit When set to `true`, the `<iframe>` of the test file will be scaled to fit the available space.
*/
scaleToFit : true,
/**
* @cfg {String} scaleToFitMode One of the `full`, `width`, `height` to scale to fit to particular dimension only, or to both.
*/
scaleToFitMode : 'full',
/**
* @cfg {String} domContainerRegion Should be exactly one of `east/west/south/north`. Controls the position of the dom container
* panel. Set it to `south` to create a "portrait" mode of the results panel with the assertions grid at the top and DOM container
* at the bottom.
*/
domContainerRegion : 'east',
// flag which enables mouse cursor visualization
showCursor : true,
/**
* @cfg {Boolean} showTestDurationColumn Enable to display the column with tests duration. This column is always
* visible in the HTML report.
*/
showTestDurationColumn : false,
/**
* @cfg {Boolean} speedRun When set to `true`, project will reduce the quality or completely remove the visual effects
* for events simulation, improving the speed of test. Default value is `true`.
*
* Note, that events will still be simulated for every point in the mouse move path, so the accuracy of the
* simulation is not affected by this option. See {@link #mouseMovePrecision} for additional performance improvement.
*
* This option can be also specified in the test file descriptor.
*/
speedRun : true,
/**
* @cfg {Boolean} turboMode When set to `true`, project will reduce the precision of mouse move operations to only simulate 'mouseover' events for 1st and last
* point of a path. Siesta also has various internal delays, which happen between action steps and after every action. The purpose of these delays is to allow
* to tests to pass more easily, as UI frameworks typically expect the user behavior to be slow. For example, there is an afterAction delay set to 100ms, which can add up to be a quite big number, especially for big UI test suites.
* With the turboMode option, Siesta sets all such delays to 1, providing the fastest possible test execution. This will force you to know your code very well and
* introduce proper waitFor statements wherever you have asynchronous flows.
*
* Default value is `false`. **Only intended for power users who want extremely fast UI tests**.
*
* Note, that only start and end point events will be simulated, so you may experience unexpected side effects
* See {@link #mouseMovePrecision} for additional performance improvement.
*
* This option can be also specified in the test file descriptor.
*/
turboMode : false,
/**
* @cfg {Integer} mouseMovePrecision
*
* Defines how precisely to follow the path between two points when simulating a drag or mouse move.
*
* - Value 1 indicates that "mouseover/mouseout" events are simulated for every point along the path (which is
* often not required at all).
* - Value 2 indicates every 2nd point will be used
* - ... and so on, in general, low value = slow dragging, higher accuracy, high value = fast dragging, lower accuracy.
*
* Setting this option to some big number (like 100000) will cause Siesta to only simulate events for the
* two initial and two final points in the path, which provides significant performance boost. However, if you need
* to verify some behaviour, triggered by mouse events from the element somewhere in the middle of the path
* you may need to use lower value for this option.
*
* See also {@link #speedRun} option.
*
* This option can be also specified in the test file descriptor.
*/
mouseMovePrecision : 1,
/**
* @cfg {Boolean} failOnResourceLoadError When set to `true`, test will try to detect the failures for loading of
* various resources (`script/link/img` tags) and report those as failed assertions. Only supported in modern browsers.
* Default value is `false`.
*
* This option can be also specified in the test file descriptor.
*/
failOnResourceLoadError : false,
/**
* @cfg {Boolean} failOnPromiseRejection When set to `true`, test will add a failing assertion for the unhandled promise rejections (if browser
* supports detecting them).
*
* This option can be also specified in the test file descriptor.
*/
failOnPromiseRejection : true,
contentManagerClass : Siesta.Content.Manager.Browser,
scopeProvider : 'Scope.Provider.IFrame',
/**
* @cfg {Boolean} disableCaching When set to `true`, project will prevent the browser caching of files being preloaded and the test files, by appending
* a query string to it.
* Note, that in this case, debuggers may not understand that you are actually loading the same file, and breakpoints may not work. Default value is `false`
*/
disableCaching : false,
baseUrl : window.location.href.replace(/(\?|#).*$/, '').replace(/\/[^/]*$/, '/'),
baseHost : window.location.host,
baseProtocol : window.location.protocol,
/**
* @cfg {Boolean} forceDOMVisible When set to `true` the tests will be executed in "fullscreen" mode, with their iframes on top of all other elements.
* This is required in IE if your test includes interaction with the DOM, because the `document.getElementFromPoint()` method
* does not work correctly in IE unless the element is visible.
*
* This option is enabled by default in IE / Edge and disabled in all other browsers.
* This option can be also specified in the test file descriptor (usually you will create a group of "rendering" tests). Usually it's only relevant for IE,
* so using this option should look like:
*
project.plan(
{
group : 'Rendering',
forceDOMVisible : bowser.msie,
items : [
'rendering/01_grid.t.js',
...
]
},
...
)
*/
forceDOMVisible : bowser.msie || bowser.msedge,
// "override from UI" setting
observerMode : false,
/**
* @cfg {Boolean} runInPopup Experimental. When set to `true` the tests will be executed in the popup, instead of iframe.
* You will need to enable popups the host you are testing from.
*
* Popups provides almost exactly the same environment as standalone page - notably the `window.top` property
* reference the popup itself, making it easier to test hash-based navigation.
*
* Note, that mouse cursor visualization does not work for tests in popups.
*
* This option can be also specified in the test file descriptor.
*/
runInPopup : false,
/**
* @cfg {String} pageUrl The url of the HTML page which will be the target for the test(s) (the URL must be on the same domain the project HTML page). This option is used for application level testing, Siesta will visit this URL and then launch
* the test. See `/examples/021-extjs-drag-drop/index.js` for an example.
*
* Note that with this option, the test descriptor will stop inheriting the {@link #preload} option from parent descriptors/project
* (to make sure you don't preload your dependencies twice). This is usually an expected behavior, and you still can specify the `preload` option
* directly on such descriptor if needed.
*
* This option can be also specified in the test file descriptor. This option has a deprecated synonym - "hostPageUrl"
*
* For example, to define that a test should be executed on a page generated by some php script:
project.plan(
{
pageUrl : '../my_php_script?page=home', // url of the html page for test
url : '020_home_page_drag_n_drop.t.js' // url of the js file, containing actual test code
},
...
)
*
*
*/
pageUrl : null,
/**
* @cfg {Boolean} useStrictMode When set to `false` the test scopes will be created w/o strict mode `DOCTYPE`. Default value is `true`.
* This option is not applicable for tests with `pageUrl` option.
*
* This option can be also specified in the test file descriptor.
*/
useStrictMode : true,
/**
* @cfg {String} innerHtmlHead
*
* A string that will be placed into the `innerHTML` property of the <head> tag, before starting the preload process.
* No validity checks will be performed.
*
* This option will not be inherited by the tests with {@link #pageUrl} option set, unless it is explicitly set to the
* `inherit` value.
*
* This option can be specified in the test file descriptor.
*
* See also {@link #innerHtmlBody}
*/
innerHtmlHead : null,
/**
* @cfg {String} innerHtmlBody
*
* A string that will be placed into the `innerHTML` property of the <body> tag, before starting the preload process.
* No validity checks will be performed.
*
* This option will not be inherited by the tests with {@link #pageUrl} option set, unless it is explicitly set to the
* `inherit` value.
*
* This option can be specified in the test file descriptor.
*
* See also {@link #innerHtmlHead}
*/
innerHtmlBody : null,
/**
* @cfg {String} runCore Either `parallel` or `sequential`. Indicates how the individual tests should be run - several at once or one-by-one.
*
* Default value is "parallel", except for IE 6, 7, 8 where it's set to `sequential`.
*
* Set this option to `sequential` for tests, that uses some exclusive resources (like for example focus of the
* text fields).
*
project.plan(
'some_test.t.js',
{
url : 'test_that_relies_on_focus.t.js',
runCore : 'sequential'
}
)
*
* This option can be also specified in the test file descriptor.
*/
runCore : 'parallel',
// a `runCore` value, "forced" for all tests, private, used for automation
forcedRunCore : null,
/**
* @cfg {String} simulateEventsWith
*
* This option is IE9-strict mode (and probably above) specific. It specifies how Siesta should simulate events.
* The options are 'dispatchEvent' (W3C standard) or 'fireEvent' (MS interface) - both are available in IE9 strict mode
* and each activates different set of event listeners. See this blog post for detailed explanations:
* <http://www.digitalenginesoftware.com/blog/archives/76-DOM-Event-Model-Compatibility-or-Why-fireEvent-Doesnt-Trigger-addEventListener.html>
*
* Valid values are "dispatchEvent" and "fireEvent".
*
* The framework specific adapters (like {@link Siesta.Test.ExtJS} and like {@link Siesta.Test.jQuery}) choose the most appropriate value
* automatically (unless explicitly configured).
*/
simulateEventsWith : {
is : 'rw',
init : 'dispatchEvent'
},
// the test with currently "forced" (by the "forceDOMVisible" option) iframe
testOfForcedIFrame : null,
/**
* @cfg {Boolean} autoScrollElementsIntoView
*
* With this option enabled Siesta will try to scroll invisible action targets into the view automatically, before performing an
* action (such as click etc).
*
* This option can also be specified in the test file descriptor.
*/
autoScrollElementsIntoView : true,
/**
* @cfg {Boolean} enableUnreachableClickWarning When this option is set to `true` (default) Siesta will generate warnings
* when click happens in the unreachable point of some element. For example, imagine the following situation: you have
* a 10x10px "div" element with "overflow : hidden", and inside of it, another inner "div" 10x50px. Then you ask Siesta
* to click on the inner div (by default it clicks in the center). The center of inner div is hidden by the outer div,
* so click will happen on some other element and a warning will be issued.
*
* Usually this behaviour is what you want, since it protects you from various mistakes, but sometimes you may want
* to disable it, for example if you want to write your clicks like this: `{ click : someEl, offset : [ "50%", "100%+10" ] }`
* (which means - click 10px to the right from right edge of the `someEl`).
*
* Note, that warning won't be issued if in the click point there's some child element of the target element.
*
* This option can also be specified in the test file descriptor.
*/
enableUnreachableClickWarning : true,
/**
* @cfg {Boolean} maintainViewportSize
*
* Enabling this option will cause Siesta to honor the {@link #viewportWidth} and {@link #viewportHeight} configuration options.
*
* This option can also be specified in the test file descriptor.
*/
maintainViewportSize : true,
/**
* @cfg {Number} viewportWidth
*
* The width of the test iframe, default value is 1024
*/
viewportWidth : 1024,
/**
* @cfg {Number} viewportHeight
*
* The height of the test iframe, default value is 768
*/
viewportHeight : 768,
/**
* @cfg {Function} recorderClass A recorder class to use
*/
recorderClass : function () {
// in the RootCause bundle recorder classes are missing
return Siesta.Recorder && Siesta.Recorder.Recorder
},
/**
* @cfg {Object} recorderConfig A custom config object used to configure the {@link Siesta.Recorder.Recorder} instance
*/
recorderConfig : null,
/**
* @cfg {Boolean} jasmine This option can only be specified in the {@link Siesta.Project#start test files descriptor}.
* If its set to `true`, the `url` property of the descriptor should point to the Jasmine spec runner html page.
* Siesta then will automatically import the results from the Jasmine suite.
*
* Additionally, one need to add a special reporter to the spec runner page, which is available
* as `SIESTA_FOLDER/bin/jasmine-siesta-reporter.js`.
*
* Currently Siesta can import the results from Jasmine 2.0 and above.
*
* Typical setup will look like (see also `SIESTA_FOLDER/examples/1.unit-tests/jasmine_suite/SpecRunner.html` example):
<head>
...
<script src="lib/jasmine-2.2.0/jasmine.js"></script>
<script src="lib/jasmine-2.2.0/jasmine-html.js"></script>
<script src="lib/jasmine-2.2.0/boot.js"></script>
<!-- Add Siesta reporter to your Jamsine spec runner (adjust the path) -->
<script src="../../../bin/jasmine-siesta-reporter.js"></script>
....
</head>
*
project.plan(
// regular Siesta test
'010_regular_test.t.js',
// a Jasmine test suite
{
jasmine : true,
expectedGlobals : [ 'Player', 'Song' ],
// url should point to the specs runner html page in this case
url : 'jasmine_suite/SpecRunner.html'
}
)
*/
/**
* @cfg {Boolean} needUI This option determines whether the Siesta UI should be rendered. By default, it is enabled
* when running suite in the browser and disabled in automation launcher. You can explictly set it to `true`, to show the UI
* even in automation mode:
*
project.configure({
needUI : true
})
*/
needUI : true,
// will read the settings from cookies when started
stateful : true,
uiMask : null,
uiMaskActive : false,
hostPageUrlStopsInheriting : function () {
return this.buildHostPageUrlStopsInheriting()
},
/**
* @cfg {String} rerunHotKey The key to press together with CTRL to rerun the latest test
*/
rerunHotKey : 'E',
/**
* @cfg {String} observerModeHotKey The key to press together with CTRL to toggle the "observer mode" on/off.
* The "observer mode" is a combination of enabled {@link #forceDOMVisible} and {@link #runCore} `sequential`.
*/
observerModeHotKey : 'O',
/**
* @cfg {Boolean} restartOnBlur
*
* **Experimental**. When this option is enabled, Siesta will emit a warning when focus moves outside of the test window.
* When running in automation mode Siesta will also restart the test.
*
* This option can also be specified in the test file descriptor.
*/
restartOnBlur : false,
focusChecker : {
lazy : function () {
var el = document.createElement('input')
el.setAttribute('style', 'position : absolute; left : -1000px; top : -1000px')
el.type = 'text'
document.body.appendChild(el)
return el
}
},
supportEcmaModules : function () {
var script = document.createElement('script');
return 'noModule' in script;
}
},
after : {
onBeforeScopePreload : function (scopeProvider, url) {
if (this.viewport) this.viewport.onBeforeScopePreload(scopeProvider, url)
},
onTestSuiteStart : function (descriptors, contentManager, launchState) {
if (this.viewport) this.viewport.onTestSuiteStart(descriptors, contentManager)
},
onTestSuiteEnd : function (descriptors, contentManager) {
if (this.viewport) this.viewport.onTestSuiteEnd(descriptors, contentManager)
// remove the links to forced iframe / test in hope to ease the memory pressure
delete this.testOfForcedIFrame
if (this.uiMaskActive) this.hideUiMask()
},
onTestStart : function (test) {
if (this.viewport) this.viewport.onTestStart(test)
if (test.hasForcedIframe()) {
if (this.testOfForcedIFrame) this.hideForcedIFrame(this.testOfForcedIFrame)
this.showForcedIFrame(test)
this.testOfForcedIFrame = test
} else {
if (this.uiMaskActive) this.hideUiMask()
}
},
onTestUpdate : function (test, result, parentResult) {
if (this.viewport) this.viewport.onTestUpdate(test, result, parentResult)
if ((result instanceof Siesta.Result.Diagnostic) && result.isWarning && this.needUI) {
if (typeof console != 'undefined' && console.warn) console.warn(result + '')
}
},
onTestEnd : function (test) {
if (test.hasForcedIframe()) this.hideForcedIFrame(test)
if (test == this.testOfForcedIFrame) this.testOfForcedIFrame = null
if (this.viewport) this.viewport.onTestEnd(test)
// when browser is simulating the event on the element that is not visible in the iframe
// it will scroll that point into view, using the `scrollLeft` property of the parent element
// this line fixes that displacement
var wrapper = test.scopeProvider && test.scopeProvider.wrapper
if (wrapper) {
wrapper.scrollLeft = wrapper.scrollTop = 0
}
document.body.scrollLeft = document.body.scrollTop = 0
},
onTestFail : function (test, exception, stack) {
if (this.viewport) this.viewport.onTestFail(test, exception, stack)
}
},
methods : {
setObserverMode : function (value) {
if (value) {
this.forcedRunCore = 'sequential'
} else
this.forcedRunCore = null
this.observerMode = value
},
buildHostPageUrlStopsInheriting : function () {
// the value for the key is a default value which is used when descriptor is "hidden" by `pageUrl`
return {
preload : [],
innerHtmlHead : null,
innerHtmlBody : null
}
},
buildConfigSynonyms : function () {
return Joose.O.extend(this.SUPER(), {
pageUrl : 'hostPageUrl',
enablePageRedirect : 'separateContext'
})
},
createViewport : function(config) {
return Ext.create(this.viewportClass, config);
},
configure : function() {
this.SUPERARG(arguments);
this.id = this.title || window.location.href;
},
start : function () {
// Opera's global variables handling is weird
if (bowser.opera) {
this.autoCheckGlobals = false;
}
if (bowser.msie) {
if (!this.hasOwnProperty('runCore')) this.runCore = 'sequential'
}
this.SUPERARG(arguments)
},
populateCleanScopeGlobals : function (scopeProvider, callback) {
if (this.disableGlobalsCheck || bowser.msie && bowser.version < 9) {
// do nothing for IE < 9 - testing leakage of globals is not supported
// also IE8 often crashes on this stage
this.disableGlobalsCheck = true
callback()
return
}
// always populate the globals from IFrame (even if user specified the Window provider)
this.SUPER('Scope.Provider.IFrame', callback)
},
onUnload : function () {
Joose.O.each(this.scopesByURL, function (scopeProvider, url) {
// to close opened popups when project page unloads
scopeProvider.cleanup()
})
},
launch : function () {
// init the singleton
Siesta.Project.Browser.FeatureSupport();
this.SUPERARG(arguments)
},
setup : function (callback) {
var me = this
var SUPER = this.SUPER
window.onunload = function () { me.onUnload() }
window.addEventListener('error', function (errorEvent) {
if (/__SIESTA_TEST_EXIT_EXCEPTION__/.test(errorEvent.message)) errorEvent.preventDefault()
})
// required to bring the window to front in FF
// skip, when Siesta UI is enmbedded into iframe, to prevent
// scrolling
if (!window.parent) window.focus()
if (this.title) document.title = this.title
$(function () {
var needUI = me.hasOwnProperty('needUI') || !me.isAutomated ? me.needUI : false
if (typeof Ext != 'undefined' && needUI) {
me.autoLaunchTests = false
Ext.onReady(function () {
SUPER.call(me, function () {
var filter = me.getQueryParam('filter')
me.viewport = me.createViewport({
filter : filter ? decodeURIComponent(filter) : null,
autoLaunch : me.getQueryParam('autolaunch'),
title : me.title,
project : me
})
callback && callback()
})
})
} else {
SUPER.call(me, callback)
}
})
},
getDescriptorConfig : function (descriptor, configName, doNotLookAtRoot) {
// for any "normal" config use regular parent implementation
if (!(configName in this.hostPageUrlStopsInheriting)) return this.SUPERARG(arguments)
var defaultValue = this.hostPageUrlStopsInheriting[ configName ]
var pageUrlConfigFound = false
var isInheriting = false
do {
pageUrlConfigFound = pageUrlConfigFound || this.descriptorHasOwnValueFor(descriptor, 'pageUrl')
if (this.descriptorHasOwnValueFor(descriptor, configName)) {
var value = this.getConfigValueFromDescriptor(descriptor, configName)
if (value == 'inherit')
isInheriting = true
else
return value
}
if (pageUrlConfigFound && !isInheriting) return defaultValue
descriptor = descriptor.parent
} while (descriptor && descriptor != this)
if (doNotLookAtRoot)
return undefined
else
return this.getConfigValueFromDescriptor(this, configName)
},
normalizeScopeProvider : function (desc) {
this.SUPERARG(arguments)
if (this.getDescriptorConfig(desc, 'runInPopup')) desc.scopeProvider = 'Scope.Provider.Window'
},
getScopeProviderConfigFor : function (desc, launchId) {
var me = this
var config = this.SUPERARG(arguments)
config.cleanupDelay = 0
config.name = desc.title || desc.url.replace(/(.*\/)?(.*)/, '$2')
config.cls = 'tr-iframe'
config.performWrap = true
var wrapper = document.createElement('div')
wrapper.className = 'tr-iframe-wrapper'
wrapper.innerHTML = "<div class='tr-close fa fa-close'> </div><div class='tr-iframe-wrapper-scroll-canvas'><div class='tr-iframe-wrapper-inner'></div></div>"
wrapper.childNodes[ 0 ].onclick = function () {
var test = me.getTestByURL(desc.url)
if (test) {
me.hideForcedIFrame(test)
if (me.viewport) me.viewport.onManualCloseOfForcedIframe(test)
} else {
this.parentElement.style.left = '-10000px'
this.parentElement.style.top = '-10000px'
}
}
config.wrapper = wrapper
config.iframeParentEl = wrapper.childNodes[ 1 ].childNodes[ 0 ]
config.sourceURL = config.sourceURL || this.getDescriptorConfig(desc, 'pageUrl')
config.minViewportSize = config.minViewportSize || {
width : this.getDescriptorConfig(desc, 'viewportWidth'),
height : this.getDescriptorConfig(desc, 'viewportHeight')
}
config.innerHtmlHead = this.getDescriptorConfig(desc, 'innerHtmlHead')
config.innerHtmlBody = this.getDescriptorConfig(desc, 'innerHtmlBody')
config.failOnResourceLoadError = this.getDescriptorConfig(desc, 'failOnResourceLoadError')
config.failOnPromiseRejection = this.getDescriptorConfig(desc, 'failOnPromiseRejection')
if (!config.hasOwnProperty('useStrictMode')) config.useStrictMode = this.getDescriptorConfig(desc, 'useStrictMode')
return config
},
getNewTestConfiguration : function (desc, scopeProvider, contentManager, launchState) {
var me = this
var config = this.SUPERARG(arguments)
// the simulation-related configs are grouped in `simulatorConfig` attribute of the test
config.simulatorConfig = this.getSimulatorConfigForNewTest(desc)
Joose.A.each([
'forceDOMVisible',
'autoScrollElementsIntoView',
'restartOnBlur'
], function (name) {
config[ name ] = me.getDescriptorConfig(desc, name)
})
if (this.observerMode) config.forceDOMVisible = true
config.browser = config.bowser = bowser
return config
},
getSimulatorConfigForNewTest : function (desc) {
var me = this
var config = {}
if (this.hasOwnProperty('simulateEventsWith')) config.simulateEventsWith = this.simulateEventsWith
Joose.A.each([
'actionDelay',
'afterActionDelay',
'dragDelay',
'pathBatchSize',
'mouseDragPrecision',
'mouseMovePrecision',
// TODO this config is currently not supported in native events, though we could do that
// for that we would need to split all click-like actions in 2 stages
// - sync cursor (native)
// - one more getNormalizedTopElementInfo with "skipWarning=false" (user agent layer)
// - actual click (native)
'enableUnreachableClickWarning'
], function (name) {
var cfg = me.getDescriptorConfig(desc, name);
if (cfg != null) {
config[ name ] = me.getDescriptorConfig(desc, name)
}
})
if (this.getDescriptorConfig(desc, 'turboMode')) {
config.speedRun = false;
Joose.O.extend(config, Siesta.Test.Simulator.speedPresets.turboMode)
}
else if (this.getDescriptorConfig(desc, 'speedRun')) {
Joose.O.extend(config, Siesta.Test.Simulator.speedPresets.speedRun)
}
return config
},
testMustRunSequential : function (descriptor) {
return this.getDescriptorConfig(descriptor, 'forceDOMVisible')
},
runCoreGeneral : function (descriptors, contentManager, launchState, callback) {
var me = this
var sorted = this.sortDescriptors(descriptors, this.forcedRunCore, false)
me.runCoreSharedContext(sorted.sharedContextGroups, contentManager, launchState, function () {
me.runCoreParallel(sorted.canRunParallel, contentManager, launchState, function () {
me.runCoreSequential(sorted.mustRunSequential, contentManager, launchState, callback)
})
})
},
runCoreSharedContext : function (sharedContextGroups, contentManager, launchState, callback) {
var me = this
var processDescriptor = function (group, isFirst, scopeProvider, firstDesc, testHolder) {
var descriptors = group.items
if (!descriptors.length || launchState.needToStop) { processGroup(sharedContextGroups); return }
var desc = descriptors.shift()
// if there's a descriptor left after the shift do not cleanup the
// scope provider at the end of the test (as its going to be re-used by the next test)
var noCleanup = descriptors.length > 0
if (isFirst) {
// new context should be created for the 1st item in the group
me.processURL(desc, desc.index, contentManager, launchState, function (testHolder) {
processDescriptor(group, false, me.scopesByURL[ desc.url ], desc, testHolder)
// trying hard to cleanup and avoid memory leaks
testHolder = null
}, noCleanup, group)
} else {
// same context should be re-used
me.processUrlShared(desc, desc.index, contentManager, launchState, function () {
processDescriptor(group, false, scopeProvider, firstDesc, testHolder)
}, noCleanup, group, scopeProvider, firstDesc, testHolder)
}
}
var processGroup = function (sharedContextGroups) {
if (!sharedContextGroups.length || launchState.needToStop) { callback(); return }
var group = sharedContextGroups.shift()
processDescriptor(group, true)
}
processGroup(sharedContextGroups)
},
processUrlShared : function (
desc, index, contentManager, launchState, callback, noCleanup, sharedSandboxState, scopeProvider, firstDesc, testHolder
) {
var me = this
var url = desc.url
// If first test in group is missing - behavior is undefined
if (desc.isMissing) {
callback()
return
}
// an array of errors occurred during preload phase
var preloadErrors = []
var transparentEx = this.getDescriptorConfig(desc, 'transparentEx')
delete scopeProvider.scope.describe.called
if (desc.testCode || this.cachePreload && contentManager.hasContentOf(desc.url))
scopeProvider.runCode(desc.testCode || contentManager.getContentOf(desc.url), cont)
else
scopeProvider.runScript(this.resolveURL(desc.url, scopeProvider, desc), cont, this.getDescriptorConfig(desc, 'isEcmaModule'))
function cont() {
// scope provider has been cleaned up while setting up? (may be user has restarted the test)
// then do nothing
if (!scopeProvider.scope) { callback(); return }
var testClass = me.getDescriptorConfig(desc, 'testClass')
if (me.typeOf(testClass) == 'String') testClass = Joose.S.strToClass(testClass)
var testConfig = me.getNewTestConfiguration(desc, scopeProvider, contentManager, launchState, sharedSandboxState)
// testHolder here is a shared object between the whole group
// create the test instance early, so that one can perform some setup (as the test class method call)
// even before the "pageUrl" starts loading
var test = testHolder.test = new testClass(testConfig)
test.earlySetup(function () {
cont2()
}, function (errorMessage) {
preloadErrors.push({ isException : false, message : errorMessage })
cont2()
})
function cont2() {
me.launchTest({
testHolder : testHolder,
desc : desc,
scopeProvider : scopeProvider,
contentManager : contentManager,
launchState : launchState,
preloadErrors : preloadErrors,
startTestAnchor : scopeProvider.scope.StartTest,
noCleanup : noCleanup,
cleanupUrl : firstDesc.url,
reusingSandbox : true
}, callback)
}
}
},
normalizeURL : function (url) {
// ref to JSAN module - DEPRECATED
if (/^jsan:/.test(url)) url = '/jsan/' + url.replace(/^jsan:/, '').replace(/\./g, '/') + '.js'
// ref to lib in current dist (no `/` and trailing `.js`) - DEPRECATED
if (!/\.js$/.test(url) && !/\//.test(url) && !/\.css(\?.*)?$/i.test(url)) url = '../lib/' + url.replace(/\./g, '/') + '.js'
return url
},
normalizeDescriptor : function (desc, parent, index, level) {
var desc = this.SUPERARG(arguments)
if (!desc.group && desc.jasmine) {
desc.pageUrl = desc.url
desc.testCode = this.getJasmineTestCode()
// preloads will not be inherited anyway because "pageUrl" option presents
// but we explicitly remove them one more time
desc.preload = []
desc.sandbox = true
}
return desc
},
resolveURL : function (url, scopeProvider, desc) {
// if the `scopeProvider` is provided and it has a sourceURL - then absolutize the preloads relative to that url
if (scopeProvider && scopeProvider.sourceURL) url = this.absolutizeURL(url)
if (this.disableCaching)
// if there's a ?param string in url - append new param
if (/\?./.test(url))
url += '&disableCaching=' + new Date().getTime()
else
if (!/\?$/.test(url))
url += '?disableCaching=' + new Date().getTime()
// otherwise assumed to be a raw filename, relative or absolute
return url
},
absolutizeURL : function (url, baseUrl) {
// if the url is already absolute - just return it (perhaps with some normalization - 2nd case)
// the url starting with // is also valid absolute url
if (/^((https?|file):)?\/\//.test(url)) return url
if (/^\//.test(url)) return this.baseProtocol + '//' + this.baseHost + url
baseUrl = baseUrl || this.baseUrl
// strip the potential query and filename from baseURL, leaving only the "directory" part
baseUrl = baseUrl.replace(/\?.*$/,'').replace(/\/[^/]*$/, '/')
// first absolutize the base url relative the project page (which will be always global, so it won't recurse)
var absBaseUrl = this.absolutizeURL(baseUrl, this.baseUrl)
// add a trailing "/" if missing
absBaseUrl = absBaseUrl.replace(/\/?$/, '/')
return absBaseUrl + url
},
getUiMask : function () {
if (this.uiMask) return this.uiMask
var uiMask = this.uiMask = document.createElement('div')
uiMask.className = 'tr-ui-mask'
uiMask.style.display = 'none'
document.body.appendChild(uiMask)
return uiMask
},
showUiMask : function () {
var mask = this.getUiMask()
mask.style.display = 'block'
this.uiMaskActive = true
},
hideUiMask : function () {
var mask = this.getUiMask()
mask.style.display = 'none'
this.uiMaskActive = false
},
showForcedIFrame : function (test) {
$.rebindWindowContext(window);
test.isDOMForced = true
var wrapper = test.scopeProvider.wrapper
wrapper.classList.add('tr-iframe-forced')
wrapper.classList.remove('tr-iframe-hidden')
$(wrapper).center()
// round left/top offset of the forced iframe window - this is to avoid weird fractional offsets
// in event coordinates
wrapper.style.top = this.roundNumericStyleValue(wrapper.style.top)
wrapper.style.left = this.roundNumericStyleValue(wrapper.style.left)
test.fireEvent('testframeshow')
var rect = wrapper.getBoundingClientRect()
// a dummy call to `elementFromPoint`, to force the rendering engine to actually render the
// centered iframe (perhaps the `getBoundingRect` is enought, this is extra caution
// otherwise, when using native simulation, it seems the frame is centered (via styles update)
// but actually not rendered at that position (browser waits for animation frame or whatever)
// so native simulation starts, w/o actual frame in place
document.elementFromPoint(rect.left, rect.top)
},
roundNumericStyleValue : function (styleStr) {
var match = /(.*?)([^\d\.-]*)$/.exec(styleStr)
var rounded = Math.round(Number(match[ 1 ]))
return String(rounded) + match[ 2 ]
},
hideForcedIFrame : function (test) {
$.rebindWindowContext(window);
// once hidden, forced iframe will be never be shown again as "forced", only as "normal"
test.isDOMForced = false
test.forceDOMVisible = false
var wrapper = test.scopeProvider.wrapper
wrapper.classList.remove('tr-iframe-forced')
wrapper.classList.add('tr-iframe-hidden')
test.fireEvent('testframehide')
},
showForcedIFrameScreenshot : function (test) {
this.showUiMask()
$.rebindWindowContext(window);
var wrapper = test.scopeProvider.wrapper
wrapper.classList.add('tr-iframe-forced-screenshot')
wrapper.classList.remove('tr-iframe-forced')
wrapper.classList.remove('tr-iframe-hidden')
},
hideForcedIFrameScreenshot : function (test) {
this.hideUiMask()
$.rebindWindowContext(window);
var wrapper = test.scopeProvider.wrapper
wrapper.classList.remove('tr-iframe-forced-screenshot')
if (test.isDOMForced) {
wrapper.classList.add('tr-iframe-forced')
$(wrapper).center()
}
},
stopCurrentLaunch : function (sourceTest) {
if (this.SUPER(sourceTest) === true) {
if (this.viewport) this.viewport.onTestSuiteStop(sourceTest)
return true
}
},
/**
* @param {String} paramName
*
* Returns a query string parameter with name `paramName` (`http://domain.com/index.html?paramName=paramValue`)
*
* @return {String}
*/
getQueryParam : function (paramName) {
return this.my.getQueryParam(paramName, true)
},
getJasmineTestCode : function () {
return ';(' + (function () {
StartTest(function (t) {
t.expectGlobals(
'getJasmineRequireObj', 'jasmineRequire', 'jasmine', 'xdescribe', 'describe', 'xdescribe', 'fdescribe',
'it', 'xit', 'fit', 'spyOn', 'fail', 'jsApiReporter', 'beforeEach', 'afterEach', 'beforeAll', 'afterAll',
'expect', 'pending'
)
if (!window.jasmine) {
t.fail(t.resource('Siesta.Project.Browser', 'noJasmine'))
return
}
if (!jasmine.SiestaReporter) {
t.fail(t.resource('Siesta.Project.Browser', 'noJasmineSiestaReporter'))
return
}
jasmine.SiestaReporter.importResults(t)
})
}).toString() + ')();'
},
/**
* This methos returns `true` if this project is being run on the
* [Standard package](https://www.bryntum.com/products/siesta/) of Siesta, `false` otherwise.
*
* @return {Boolean}
*/
isStandardPackage : function () {
return this.my.isStandardPackage(true)
},
// should only be used when no tests are running
// does not work for IE
// Safari webdriver has known problem of starting browser in the background, w/o focus
browserWindowHasFocus : function () {
window.focus()
var el = this.getFocusChecker()
el.focus({preventScroll : true})
$.rebindWindowContext(window);
return $(el).is(':focus')
},
/**
* This method returns a test instance of the currently selected test in the UI.
*
* You can use this instance to call usual test methods, like:
*
project.getSelectedTest().click("#some-el")
*
* Note, that test instance might be already cleaned up, so DOM interaction methods might not be functional.
* This cleanup behavior is controlled by the {@link #keepNLastResults} option.
*
* For convenience, the result of this method is duplicated as simple `T` global variable:
*
T.click("#some-el")
*
* @return {Siesta.Test} Currently selected test in the UI
*/
getSelectedTest : function () {
var selected = this.viewport.slots.filesTree.getSelectionModel().getSelection()[ 0 ]
if (selected) {
return this.getTestByURL(selected.get('url'))
}
}
},
my : {
meth