siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
1,221 lines (942 loc) • 67.5 kB
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
<link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="../resources/prettify/prettify.js"></script>
<style type="text/css">
.highlight { display: block; background-color: #ddd; }
</style>
<script type="text/javascript">
function highlight() {
document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
}
</script>
</head>
<body onload="prettyPrint(); highlight();">
<pre class="prettyprint lang-js">/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
<span id='Siesta-Test-Browser'>/**
</span>@class Siesta.Test.Browser
@extends Siesta.Test
@mixin Siesta.Test.TextSelection
@mixin Siesta.Test.Element
@mixin Siesta.Test.UserAgent.Mouse
@mixin Siesta.Test.UserAgent.Keyboard
@mixin Siesta.Test.UserAgent.Touch
@mixin Siesta.Test.Browser.Role.CanGetElementFromPoint
A base class for testing a generic browser functionality. It has various DOM-related assertions, and is not optimized for any framework.
*/
Class('Siesta.Test.Browser', {
isa : Siesta.Test,
does : [
Siesta.Util.Role.CanParseBrowser,
Siesta.Util.Role.CanCalculatePageScroll,
Siesta.Util.Role.Dom,
Siesta.Test.Browser.Role.CanGetElementFromPoint,
Siesta.Test.Browser.Role.CanWorkWithKeyboard,
Siesta.Test.Browser.Role.CanRebindJQueryContext,
Siesta.Test.UserAgent.Mouse,
Siesta.Test.UserAgent.Keyboard,
Siesta.Test.UserAgent.Touch,
Siesta.Test.Element,
Siesta.Test.TextSelection,
Siesta.Test.Observable
],
has : {
<span id='Siesta-Test-Browser-property-bowser'> /**
</span> * @property {Object} bowser An instance of browser detection library - [Bowser](https://github.com/ded/bowser).
* Please refer to the provided link for the detailed documentation, here we just provide some examples how
* it can be used in the test file:
*
// Browser detection
if (t.bowser.chrome && t.bowser.version > 50) { .. do something .. }
if (t.bowser.msie && t.bowser.version >= 10) { .. do something .. }
// OS detection
if (t.bowser.mac) { .. do something .. }
// Rendering engine detection
if (t.bowser.gecko) { .. do something .. }
// Rendering engine detection
if (t.bowser.webkit || t.bowser.blink) { .. do something .. }
*
* This property has an alias - {@link #browser}.
*/
bowser : null,
<span id='Siesta-Test-Browser-property-bowser'> /**
</span> * @property {Object} bowser Alias for {@link #bowser}
*/
browser : null,
forceDOMVisible : false,
isDOMForced : false,
browserInfo : {
lazy : function () {
return this.parseBrowser(window.navigator.userAgent)
}
},
nextConfirmValue : null,
nextPromptReturnValue : null,
realAlert : null,
realConfirm : null,
realPrompt : null,
realPrint : null,
realOpen : null,
previousConfirm : null,
previousPrompt : null,
blurListener : null,
restartOnBlur : false,
blurWindow : null,
mouseVisualizer : null,
popups : Joose.I.Array,
simulator : null,
simulatorConfig : null,
isHandlingThrowAsync : false,
initialCursorPosition : null
},
override : {
cleanup : function () {
if (this.mouseVisualizer) {
this.mouseVisualizer.destroy()
this.mouseVisualizer = null
}
this.SUPERARG(arguments)
this.bowser = this.browser = null
this._global = null
this.realAlert = this.realConfirm = this.realPrompt = this.realPrint = this.realOpen = null
this.previousPrompt = this.previousConfirm = null
this.blurListener = null
this.blurWindow && this.blurWindow.close();
this.blurWindow = null;
Joose.A.each(this.popups, function (handle) {
if (!handle.popup.closed) handle.popup.close()
})
this.popups.length = 0
this.popups = null
this.simulator.cleanup()
},
attachSimulator : function () {
var me = this
var simulator = this.simulator
var isRoot = !this.parent
// this will "attach" simulator to a test window, by setting up a "global" property
if (isRoot) simulator.onTestLaunch(this)
if (simulator.type == 'synthetic') {
// for synthetic simulator we can just update the current position directly (move to simulator class?)
if (isRoot && me.initialCursorPosition) {
simulator.currentPosition[ 0 ] = me.initialCursorPosition[ 0 ]
simulator.currentPosition[ 1 ] = me.initialCursorPosition[ 1 ]
}
return
}
var cont = Promise.resolve()
return cont.then(function () {
if (isRoot) {
var x = me.initialCursorPosition ? me.initialCursorPosition[ 0 ] : 0
var y = me.initialCursorPosition ? me.initialCursorPosition[ 1 ] : 0
// for native simulator we need to actually move the cursor
return simulator.simulateMouseMove(x, y, null, { moveKind : 'instant' })
}
}).then(function () {
var hasFocus = me.project.browserWindowHasFocus()
// browser window has lost focus for some reason, trying to re-focus by clicking
// in -1, -1 point
if (!hasFocus) {
var currentPosition = simulator.currentPosition.slice()
return simulator.simulateMouseMove(-1, -1, null, { moveKind : 'instant' }).then(function () {
return simulator.simulateMouseClick({ globalXY : [] })
}).then(function () {
return simulator.simulateMouseMove(currentPosition[ 0 ], currentPosition[ 1 ], null, { moveKind : 'instant' })
})
}
})
},
launch : function (errorMessage) {
var me = this
var SUPER = this.SUPER
var cont = this.attachSimulator()
// "attachSimulator" completed synchronously - call SUPER immediately
// this is required for "subTest" call to be synchronous on what some tests relies (subject for change)
if (!cont) {
this.SUPER(errorMessage)
} else {
// "attachSimulator" returned promise
cont.then(function () {
SUPER.call(me, errorMessage)
}, function (reason) {
SUPER.call(me, reason)
})
}
}
},
methods : {
initialize : function () {
if (!this.simulator) this.simulator = new (this.getSimulatorClass())(this.simulatorConfig || {})
// copy the "currentPosition" to the test instance for backward compatibility
this.currentPosition = this.simulator.currentPosition
this.SUPERARG(arguments)
},
onBeforeTestFinalize : function () {
var global = this.global
// If expectAlertMessage(which overwrites the alert method) was called but no alert() call happened - fail the test
if (global.alert.__EXPECTED_ALERT__) {
this.fail(Siesta.Resource('Siesta.Test.Browser', 'alertMethodNotCalled'))
}
this.SUPERARG(arguments)
},
getSimulatorClass : function () {
return Siesta.Test.Simulator
},
// setup : function (callback, errback) {
// var simulator = this.simulator
//
// // this will "attach" simulator to a test window, by setting up a "global" property
// simulator.onTestLaunch(this)
//
// if (simulator instanceof Siesta.Test.Simulator) {
// // synthetic events start with [ 0, 0 ] point anyway, so avoid extra mouseover/mousemove event
// callback()
// } else {
// simulator.simulateMouseMove(0, 0, null, { moveKind : 'instant' }).then(callback, errback)
// }
// },
earlySetup : function (callback, errback) {
var simulator = this.simulator
if (simulator.type == 'synthetic') {
// synthetic events start with [ 0, 0 ] point anyway, so avoid extra mouseover/mousemove event
callback()
} else
// for native events need to reset the simulation state *before* the test starts
// all keys up, mouse buttons up, cursor in 0, 0
simulator.doFullSimulationReset().then(callback, errback)
},
launch : function (errorMessage) {
var me = this
var win = this.global
// top test
if (!me.parent) {
me.realAlert = win.alert
me.realConfirm = win.confirm
me.realPrompt = win.prompt
me.realPrint = win.print
me.realOpen = win.open
this.maintainScrollPositionDuring(function () {
if (!me.project.browserWindowHasFocus() && !bowser.safari) me.onWindowBlur()
// trying to focus the window (hopefully fixes the tab key issues)
win.focus && win.focus()
})
// win.addEventListener && win.addEventListener('blur', me.blurListener = function () {
// if (bowser.gecko && win.document.getElementsByTagName('iframe').length > 0)
// // this "waitFor" can be interrupted, but only by forceful test finalization, which
// // happens when test throws exception for example, so it fails anyway
// me.waitFor({
// method : 0,
// suppressAssertion : true,
// callback : function () { me.onWindowBlur() }
// })
// else
// me.onWindowBlur()
// })
// 1. IE can't show cursor, since it's IE.
// 2. Clients can opt-in for cursor display in automation mode
// 3. Currently we don't show cursor for tests running in popups, since that would require
// injecting cursor element on the test page and we don't want that
if (!bowser.msie && me.project.showCursor && !win.opener) {
me.mouseVisualizer = new Siesta.Project.Browser.UI.MouseVisualizer({
currentContainer : me.global.frameElement.parentElement
});
}
}
// WARN: behavior when several sub-tests are running at the same time is not well-defined
me.previousConfirm = win.confirm
me.previousPrompt = win.prompt
var emptyFn = function () {};
win.alert = win.print = emptyFn;
win.confirm = function () {
var retVal = typeof me.nextConfirmValue === 'boolean' ? me.nextConfirmValue : true;
me.nextConfirmValue = null;
return retVal;
};
win.prompt = function () {
var retVal = me.nextPromptReturnValue || '';
me.nextPromptReturnValue = null;
return retVal;
};
win.open = function (url) {
var popup = me.realOpen.apply(win, arguments)
if (!popup)
me.fail(Siesta.Resource('Siesta.Test.Browser','popupsDisabled', { url : url }))
else {
me.popups.push({ url : url, popup : popup })
}
return popup
}
this.SUPERARG(arguments)
},
onTestFinalize : function () {
var win = this.global
if (win) {
if (!this.parent) {
win.confirm = this.previousConfirm;
win.prompt = this.previousPrompt;
win.print = this.realPrint
win.alert = this.realAlert
win.open = this.realOpen
} else {
win.confirm = this.realConfirm;
win.prompt = this.realPrompt;
win.alert = win.print = function () {}
}
}
// this.blurListener && win.removeEventListener('blur', this.blurListener)
//
// this.blurListener = null
this.SUPERARG(arguments)
},
onWindowBlur : function (arg1, arg2) {
// var doc = this.global.document
//
// // ignore the case when focus is moved inside of the child iframe
// // IGNORE
// if (!doc.hasFocus && doc.hasFocus()) return
//
// var slice = Array.prototype.slice
//
// // convert from HTMLCollection to Array
// var iframes = slice.apply(doc.getElementsByTagName('iframe'))
//
// while (iframes.length) {
// try {
// var innerDoc = iframes[ 0 ].contentWindow.document
//
// if (innerDoc.hasFocus()) return
//
// iframes.push.apply(iframes, slice.apply(innerDoc.getElementsByTagName('iframe')))
// } catch (e) {
// }
//
// iframes.shift()
// }
// // EOF IGNORE
if (this.restartOnBlur)
this.fireEvent('focuslost')
else
this.warn(Siesta.Resource('Siesta.Test.Browser').get('focusLostWarning', { url : this.url }))
},
sizzle : function (selector, root) {
return Siesta.Sizzle(selector, root || this.global.document)
},
isEventPrevented : function (event) {
// our custom property - takes highest priority
if (event.preventDefault && this.typeOf(event.preventDefault.$prevented) == 'Boolean') return event.preventDefault.$prevented
// W3C standards property
if (this.typeOf(event.defaultPrevented) == 'Boolean') return event.defaultPrevented
return event.returnValue === false
},
// only called for the re-used contexts
cleanupContextBeforeStart : function () {
this.cleanupContextBeforeStartDom()
this.SUPER()
},
cleanupContextBeforeStartDom : function () {
var doc = this.global.document
doc.body.innerHTML = ''
},
getElementPageRect : function (el, $el) {
$el = $el || this.$(el)
var offset = $el.offset()
return new Siesta.Util.Rect({
left : offset.left,
top : offset.top,
width : $el.outerWidth(),
height : $el.outerHeight()
})
},
// TODO no longer used, remove after some time
// elementHasScroller : function (el, $el) {
// $el = $el || this.$(el)
//
// var overflowX = $el.css('overflow-x')
// var overflowY = $el.css('overflow-y')
//
// var hasX = el.scrollWidth != el.clientWidth && overflowX != 'visible' && overflowX != 'hidden'
// var hasY = el.scrollHeight != el.clientHeight && overflowY != 'visible' && overflowY != 'hidden'
//
// return hasX || hasY ? { x : hasX, y : hasY } : false
// },
hasForcedIframe : function () {
return Boolean(
(this.isDOMForced || this.forceDOMVisible) && (this.scopeProvider instanceof Scope.Provider.IFrame) && this.scopeProvider.iframe
)
},
getDivBox : function (doc, left, top, width, height) {
var div = doc.createElement('div')
div.style.cssText =
'position: absolute !important; left: ' + left + 'px !important; top: ' + top + 'px !important;' +
'border-width: 0 !important; padding: 0 !important; margin: 0 !important;' +
'width: ' + width + 'px !important; height: ' + height + 'px !important;'
return div
},
elementIsScrolledOut : function (el, offset) {
var doc = el.ownerDocument
var win = doc.defaultView || doc.parentWindow
var body = this.getBodyElement(el)
var parents = []
for (var parent = el; parent && parent !== body; parent = parent.parentNode) parents.push(parent)
var currentRect = new Siesta.Util.Rect({
left : this.getPageScrollX(win),
top : this.getPageScrollY(win),
// using height / width of the *whole viewport*, BODY tag may have 0 height in some cases
width : this.$(win).width(),
height : this.$(win).height()
})
for (var i = parents.length - 1; i >= 0; i--) {
var parent = parents[ i ]
var overflowX = this.$(parent).css('overflow-x')
var overflowY = this.$(parent).css('overflow-y')
if (overflowX !== 'visible' || overflowY !== 'visible') {
// only get the bounding rect if we need it
// in IE10 a series of call to `getBoundingClientRect` of the parent elements
// were making the el itself hidden (offsetWidth = offsetHeight = 0)
var parentRect = this.getElementPageRect(parent)
if (overflowX !== 'visible') {
currentRect = currentRect.cropLeftRight(parentRect)
if (currentRect.isEmpty()) return true
}
if (overflowY !== 'visible') {
currentRect = currentRect.cropTopBottom(parentRect)
if (currentRect.isEmpty()) return true
}
}
}
var $el = this.$(el)
var elPageRect = this.getElementPageRect($el[ 0 ], $el)
offset = this.normalizeOffset(offset, $el)
return !currentRect.contains(elPageRect.left + offset[ 0 ], elPageRect.top + offset[ 1 ])
},
// returns "true" if scrolling has actually occured
scrollTargetIntoView : function (target, offset) {
var win = this.global
var doc = win.document
if (this.typeOf(target) !== 'Array') {
target = this.normalizeElement(target, true, null, false);
offset = this.normalizeOffset(offset, this.$(target))
var isInside = this.isOffsetInsideElementBox(target, offset);
if (
target && this.isElementVisible(target) &&
// If element isn't visible, try to bring it into view
isInside && this.elementIsScrolledOut(target, offset)
) {
this.maintainScrollPositionDuring(function () {
// Required to handle the case where the body is scrolled
target.scrollIntoView();
})
// No longer use jQuery "scrollIntoView" plugin - tests passes w/o it
// and it does not take the offset point into account anyway
// we still need it for ":scrollable" pseudo (which it does kind of ok)
// this.$(target).scrollintoview({ duration : 0 });
// If element is still out of view, try manually scrolling first scrollable parent found
if (this.elementIsScrolledOut(target, offset)) {
var scrollableParent = this.$(target).closest(':scrollable')[ 0 ];
if (scrollableParent) {
var parentBox = this.getBoundingClientRect(scrollableParent)
var targetBox = this.getBoundingClientRect(target)
scrollableParent.scrollLeft = Math.max(0, scrollableParent.scrollLeft + targetBox.left - parentBox.left + offset[ 0 ] - 1)
scrollableParent.scrollTop = Math.max(0, scrollableParent.scrollTop + targetBox.top - parentBox.top + offset[ 1 ] - 1)
}
}
return true
}
} else {
var leftVisible = this.getPageScrollX()
var rightVisible = leftVisible + this.$(win).width()
var topVisible = this.getPageScrollY()
var bottomVisible = topVisible + this.$(win).height()
if (
leftVisible <= target[ 0 ] && target[ 0 ] <= rightVisible
&& topVisible <= target[ 1 ] && target[ 1 ] <= bottomVisible
) {
// no need to scroll, target point is within visible viewport area
return false
}
var div = this.getDivBox(doc, target[ 0 ], target[ 1 ], 1, 1)
doc.body.appendChild(div)
this.maintainScrollPositionDuring(function () {
div.scrollIntoView()
})
doc.body.removeChild(div)
return true
}
},
processSubTestConfig : function (config) {
var res = this.SUPER(config)
var me = this
Joose.A.each([
'currentPosition',
'mouseVisualizer',
'simulator',
'simulatorConfig',
'bowser', 'browser',
'moveCursorBetweenPoints',
'realAlert', 'realConfirm', 'realPrompt', 'realPrint', 'realOpen', 'popups'
], function (name) {
res[ name ] = me[ name ]
})
return res
},
// Normalizes the element to an HTML element. Every 'framework layer' will need to provide its own implementation
// This implementation accepts either a CSS selector or an Array with xy coordinates.
normalizeElement : function (el, allowMissing, shallow, detailed) {
// Quick exit if already an element
if (el && el.nodeName) return el;
var matchingMultiple = false
if (this.typeOf(el) === 'String') {
var query = el;
// DOM query
var matches = this.query(el);
el = matches[0];
matchingMultiple = matches.length > 1
if (!allowMissing && !el) {
var warning = Siesta.Resource('Siesta.Test.Browser','noDomElementFound') + ': ' + query
this.warn(warning);
throw new Error(warning);
}
}
if (this.typeOf(el) === 'Array') {
var x, y
if (el.length < 2) {
x = this.simulator.currentPosition[ 0 ]
y = this.simulator.currentPosition[ 1 ]
} else {
x = this.pageXtoViewportX(Math.round(el[ 0 ]))
y = this.pageYtoViewportY(Math.round(el[ 1 ]))
}
el = this.elementFromPoint(x, y);
}
return detailed ? { el : el, matchingMultiple : matchingMultiple } : el;
},
// this method generally has the same semantic as the "normalizeElement", its being used in
// Siesta.Test.Action.Role.HasTarget to determine what to pass to next step
//
// on the browser level the only possibility is DOM element
// but on ExtJS level user can also use ComponentQuery and next step need to receive the
// component instance
normalizeActionTarget : function (el, allowMissing) {
return this.normalizeElement(el, allowMissing);
},
randomBetween : function (min, max) {
return Math.floor(min + (Math.random() * (max - min + 1)));
},
<span id='Siesta-Test-Browser-method-getElementAtCursor'> /**
</span> * This method uses native `document.elementFromPoint()` and returns the DOM element under the current logical cursor
* position in the test. Note, that this method may work not 100% reliable in IE due to its bugs. In cases
* when `document.elementFromPoint()` can't find any element this method returns the &lt;body&gt; element.
*
* @return {HTMLElement}
*/
getElementAtCursor : function() {
var xy = this.simulator.currentPosition;
return this.elementFromPoint(xy[ 0 ], xy[ 1 ])
},
addListenerToObservable : function (observable, event, listener, isSingle) {
if (this.typeOf(observable) === 'String') observable = this.normalizeElement(observable)
observable.addEventListener(event, listener)
},
removeListenerFromObservable : function (observable, event, listener) {
if (this.typeOf(observable) === 'String') observable = this.normalizeElement(observable)
observable.removeEventListener(event, listener)
},
// This method accepts actionTargets as input (Dom node, string, CQ etc) and does a first
// normalization pass to get a DOM element.
// After initial normalization it also tries to locate, the 'top' DOM node at the center of
// the first pass resulting DOM node.
// This is the only element we can truly interact with in a real browser.
// returns an object containing the element plus viewport coordinates
getNormalizedTopElementInfo : function (actionTarget, skipWarning, actionName, offset) {
var localXY, globalXY, el;
// support empty array as a synonym for empty target
if (this.typeOf(actionTarget) == 'Array' && actionTarget.length == 0) { actionTarget = null }
var targetIsPoint = !actionTarget || this.typeOf(actionTarget) == 'Array'
// First lets get a normal DOM element to work with
if (targetIsPoint) {
// viewport coords
var x, y
if (actionTarget) {
x = this.pageXtoViewportX(Math.round(actionTarget[ 0 ]))
y = this.pageYtoViewportY(Math.round(actionTarget[ 1 ]))
} else {
x = this.simulator.currentPosition[ 0 ]
y = this.simulator.currentPosition[ 1 ]
}
globalXY = [ x, y ]
var info = this.elementFromPoint(x, y, false, null, true)
el = info.el
localXY = info.localXY
} else {
el = this.normalizeElement(actionTarget, skipWarning)
}
if (!el && skipWarning) {
return;
}
// 1. If this element is not visible, something is wrong
// 2. If element is visible but not reachable (scrolled out of view) this is also an invalid scenario (this check is skipped for IE)
// TODO needs further investigation, conflicting with starting a drag operation on an element that isn't visible until the cursor is above it
// we don't need to this check if target is a coordinate point, because in this case element is reachable by definition
if (!targetIsPoint) {
var R = Siesta.Resource('Siesta.Test.Browser');
var message = 'getNormalizedTopElementInfo: ' + (actionName ? R.get('targetElementOfAction') + " [" + actionName + "]" : R.get('targetElementOfSomeAction')) +
" " + R.get('isNotVisible') + ": " + (el.id ? '#' + el.id : el)
if (!this.isElementVisible(el)) {
this.fail(message)
return;
}
else if (!skipWarning && this.isOffsetInsideElementBox(el, offset) && !this.elementIsTop(el, true, offset)) {
this.warn(message)
}
}
var isOption = el && el.nodeName.toLowerCase() === 'option';
if (isOption) {
localXY = this.simulator.currentPosition.slice();
globalXY = this.simulator.currentPosition.slice();
} else if (!targetIsPoint) {
var doc = this.getQueryableContainer(el);
var R = Siesta.Resource('Siesta.Test.Browser');
localXY = this.getTargetCoordinate(el, true, offset)
globalXY = this.getTargetCoordinate(el, false, offset)
// trying 2 times for IE
el = doc.elementFromPoint(localXY[ 0 ], localXY[ 1 ]) || doc.elementFromPoint(localXY[ 0 ], localXY[ 1 ]) || doc.body;
if (!el) {
this.fail('getNormalizedTopElementInfo: ' + R.get('noElementFound') + ' [' + localXY + ']');
return; // No point going further
}
}
return {
el : el,
// both are viewport coords
localXY : localXY,
globalXY : globalXY,
offset : isOption ? [ 0, 0 ] : this.getOffsetRelativeToEl(el, localXY)
}
},
// point should be in page coords
getOffsetRelativeToEl : function(el, point) {
var box = this.getElementPageRect(el);
return [ point[0] - box.left, point[1] - box.top ];
},
<span id='Siesta-Test-Browser-method-waitForTextPresent'> /**
</span> * This method will wait for the presence of the passed string.
*
* @param {String} text The text to wait for
* @param {Function} callback The callback to call
* @param {Object} scope The scope for the callback
* @param {Number} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
*/
waitForTextPresent : function (text, callback, scope, timeout) {
var R = Siesta.Resource('Siesta.Test.Browser');
return this.waitFor({
method : function () {
return this.global.document.body.innerText.indexOf(text) >= 0;
},
callback : callback,
scope : scope,
timeout : timeout,
assertionName : 'waitForTextPresent',
description : ' ' + R.get('text') + ' "' + text + '" ' + R.get('toBePresent')
});
},
<span id='Siesta-Test-Browser-method-waitForTextNotPresent'> /**
</span> * This method will wait for the absence of the passed string.
*
* @param {String} text The text to wait for
* @param {Function} callback The callback to call
* @param {Object} scope The scope for the callback
* @param {Number} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
*/
waitForTextNotPresent : function (text, callback, scope, timeout) {
var R = Siesta.Resource('Siesta.Test.Browser');
return this.waitFor({
method : function () {
return this.global.document.body.innerText.indexOf(text) < 0;
},
callback : callback,
scope : scope,
timeout : timeout,
assertionName : 'waitForTextNotPresent',
description : ' ' + R.get('text') + ' "' + text + '" ' + R.get('toNotBePresent')
});
},
<span id='Siesta-Test-Browser-method-waitForTarget'> /**
</span> * Waits until the passed action target is detected. This can be a string such as a component query, CSS query or a composite query.
*
* @param {String/Siesta.Test.ActionTarget} target The target presence to wait for
* @param {Function} callback The callback to call after the target has been found
* @param {Object} scope The scope for the callback
* @param {Int} timeout The maximum amount of time to wait for the condition to be fulfilled. Defaults to the {@link Siesta.Test.ExtJS#waitForTimeout} value.
*/
waitForTarget : function(target, callback, scope, timeout, offset) {
var me = this;
var R = Siesta.Resource('Siesta.Test.Browser');
return this.waitFor({
method : function () {
var el = me.normalizeElement(target, true, true, false, { offset : offset })
// If user is aiming outside the target, we'll *not* use the offset while
// detecting target presence since having a visible sized box will suffice
if (el && offset && me.isElementVisible(el) && !me.isOffsetInsideElementBox(el, offset)) {
return true;
}
return el && me.elementIsTop(el, true, offset)
},
callback : callback,
scope : scope,
timeout : timeout,
assertionName : 'waitForTarget',
description : ' ' + R.get('target') + ' "' + target + '" ' + R.get('toAppear')
});
},
<span id='Siesta-Test-Browser-method-setWindowSize'> /**
</span> * Sets a new size for the test iframe
*
* @param {Int} width The new width
* @param {Int} height The new height
*/
setWindowSize : function(width, height, callback) {
this.scopeProvider.setViewportSize(width, height);
callback && callback.call(this);
},
getJUnitClass : function () {
var browserInfo = this.getBrowserInfo()
browserInfo = browserInfo.name + browserInfo.version
return browserInfo + ':' + this.SUPER()
},
// a stub method for the Lite package
screenshot : function (options, callback) {
this.diag("Command: `screenshot` skipped - not running in Standard Package")
this.processCallbackFromTest(callback, [ 'skipped' ], this)
},
// a stub method for the Lite package
screenshotElement : function (target, fileName, callback) {
this.diag("Command: `screenshot` skipped - not running in Standard Package")
this.processCallbackFromTest(callback, [ 'skipped' ], this)
},
<span id='Siesta-Test-Browser-method-setUrl'> /**
</span> * setUrl Opens the url provided (make sure you use the {@link Siesta.Project.Browser#enablePageRedirect} option on the Harness when using this API method)
*
* @param {String} url The new url for current page
* @param {Function} callback The callback to call after the page has been loaded
* @param {Object} scope The scope for the callback
*/
setUrl : function(url, callback, scope) {
if (!url) throw new Error('Must provide a valid URL');
var me = this;
if (me.global.location.href !== url) {
var baseUrl = this.scopeProvider.sourceURL || this.project.baseUrl;
var absURl = this.project.absolutizeURL(url, baseUrl);
me.waitForPageLoad(callback, scope);
me.global.location.href = absURl;
} else {
callback.call(scope || me);
}
},
<span id='Siesta-Test-Browser-method-setHash'> /**
</span> * Sets the hash value of the location object
*
* @param {String} url The new hash
* @param {Function} callback
* @param {Object} scope The scope for the callback
*/
setHash : function(hash, callback, scope) {
var me = this;
me.global.location.hash = hash;
callback && callback(scope || me);
},
<span id='Siesta-Test-Browser-method-expectAlertMessage'> /**
</span> * Expects an alert message with the specified text to be shown during the test. If no alert is called,
* or the text doesn't match, a failed assertion will be added.
*
* @param {String/RegExp} message The expected alert message or a regular expression to match
* @param callback Only used internally when this method is called in a t.chain command
*/
expectAlertMessage : function (message, callback) {
var me = this
var global = this.global
var prevAlert = global.alert
global.alert = function (msg) {
var passed = me.typeOf(message) == 'RegExp' ? message.test(msg) : message == msg
if (passed)
me.pass("Expected alert message has been shown")
else
me.fail("Wrong alert message has been shown", {
assertionName : 'expectAlertMessage',
got : msg,
gotDesc : "Message shown",
need : message,
needDesc : "Expected message"
})
global.alert = prevAlert
};
global.alert.__EXPECTED_ALERT__ = true
this.processCallbackFromTest(callback, null, this)
},
<span id='Siesta-Test-Browser-method-setNextConfirmReturnValue'> /**
</span> * Sets the confirm dialog return value for the next window.confirm() call.
*
* @param {Boolean} value The confirm dialog return value (true or false)
* @param callback Only used internally when this method is called in a t.chain command
* */
setNextConfirmReturnValue : function (value, callback) {
this.nextConfirmValue = value;
this.processCallbackFromTest(callback, null, this)
},
<span id='Siesta-Test-Browser-method-setNextPromptReturnValue'> /**
</span> * Sets the prompt dialog return value for the next window.prompt() call.
*
* @param {String} value The confirm dialog return value
* @param callback Only used internally when this method is called in a t.chain command
*/
setNextPromptReturnValue : function (value, callback) {
this.nextPromptReturnValue = value;
this.processCallbackFromTest(callback, null, this)
},
waitForAnimations : function(callback) {
callback.call(this);
},
popupHasStartedLoading : function (popup, initialUrl) {
if (String(initialUrl).toLowerCase() != 'about:blank' && popup.location.href == 'about:blank') return false
return true
},
<span id='Siesta-Test-Browser-method-switchTo'> /**
</span> * Switches the target of all Siesta interactive commands (like "click/type" etc) to a different
* window (usually a popup). You can use {@link #switchToMain} method to switch back to main window.
*
* @param {String/RegExp/Object/Window/HTMLIFrameElement} [win] A new window which should be a target for all interactive commands.
* If this argument is specified as `null` a first opened popup is used.
* Can be specified as the:
*
* - Window - A global window instance
* - Object - Object with the following properties
* - url : String/RegExp - The first popup, opened with matching url will be used
* - title : String/RegExp - The first popup, opened with matching title will be used
* - String - corresponds to the `title` property of the Object branch
*
* @param {Function} callback Function to call once the switch has complete (will also wait until the target page
* completes loading)
*
* @return {Window} Previously active window reference
*/
switchTo : function (win, callback) {
var me = this
// In Chrome, when popup for some url is just created, it has "url" set to "about:blank"
// after some time the url is set to the original value and load process begins
// this opens a race condition - one can not reliably predict when the popup has completed loading
// doing our best
this.waitFor({
method : function () {
for (var i = 0; i < me.popups.length; i++)
if (!me.popupHasStartedLoading(me.popups[ i ].popup, me.popups[ i ].url)) return false
return true
},
suppressAssertion : true,
callback : function () {
var found
if (!win) {
Joose.A.each(this.popups, function (handle) {
if (!handle.popup.closed) { found = handle.popup; return false }
})
win = found
}
if (this.typeOf(win) == 'String') win = { title : win }
if (this.typeOf(win) == 'Object') {
found = null
var regexp = win.title || win.url
if (this.typeOf(regexp) == 'String') regexp = new RegExp('^' + this.escapeRegExp(regexp) + '$')
Joose.A.each(this.popups, function (handle) {
var popup = handle.popup
if (!popup.closed)
if (
win.url && regexp.test(popup.location.href)
||
win.title && regexp.test(popup.document && popup.document.title || '')
) {
found = popup
return false
}
})
win = found
}
if (!win || win.self != win) {
this.fail("Can't resolve target win: " + win)
this.processCallbackFromTest(callback, null, this)
return
}
this.setGlobal(win)
// This has to be revised properly in the "context" branch, idea is, that we switch to popup's implementation
// of `setTimeout` for waiting, asyncing etc, because thats what really user expect
// however in IE test just hangs
// this.originalSetTimeout = win.setTimeout
// this.originalClearTimeout = win.clearTimeout
this.waitFor({
suppressAssertion : true,
method : function () {
return win.document && win.document.readyState == 'complete'
},
callback : callback
})
}
})
return this.global
},
setGlobal : function (newGlobal) {
this.global = newGlobal
this.simulator.global = newGlobal
},
<span id='Siesta-Test-Browser-method-switchToMain'> /**
</span> * Switches all interactive commands back to main test window.
*
* @param {Function} callback Function to call once the switch has complete.
*/
switchToMain : function (callback) {
this.switchTo(this.scopeProvider.scope, callback)
},
<span id='Siesta-Test-Browser-method-waitForPageLoad'> /**
</span> * Only useful along with {@link Siesta.Project.Browser.enablePageRedirect enablePageRedirect} option
*
* Wait for the page load to occur and runs the callback. The callback will receive a "window" object.
* Should be used when you are doing a redirect / refresh of the test page:
*
* t.waitForPageLoad(function (window) {
* ...
* })
*
* Note, that method obviously must be called before the new page has completed loading, otherwise it will
* wait indefinitely and fail (since there will be no page load). So, to avoid the race conditions, one
* should always start waiting for page load *before* the action, that causes it.
*
* Consider the following example (where click on the `>> #loginPanel button` trigger a page redirect):
// this code does not reliably - it contains a race condition
// in Chrome, page refresh may happen too fast (even synchronously),
// so, by the time the `waitForPageLoad` action will start, the page load event will already happen
// and `waitForPageLoad` will wait indefinitely
{ click : '>> #loginPanel button' },
{ waitFor : 'PageLoad'}
* &nbsp;
// Need to start waiting first, and only then - click
// we'll use "trigger" config of the `wait` action for that
{
waitFor : 'PageLoad',
trigger : {
click : '>> #loginPanel button'
}
}
// or, same action using function step:
function (next) {
t.waitForPageLoad(next)
t.click('>> #loginPanel button', function () {})
}
*
* @method
* @member Siesta.Test.Browser
*/
waitForPageLoad : f