/* Siesta 5.1.0 Copyright(c) 2009-2018 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ /** @class Siesta.Test.Element This is a mixin, with helper methods for testing functionality relating to DOM elements. This mixin is consumed by {@link Siesta.Test} */ Role('Siesta.Test.Element', { does : [ Siesta.Util.Role.CanCalculatePageScroll ], requires : [ 'typeOf', 'chain', 'normalizeElement' ], has : { allowMonkeyToClickOnAnchors : false, allowedCharacters : function () { return { // does not include TAB by purpose, because our "TAB" simulation is not perfect // Also exclude BACKSPACE since it navigates the page special : 'ENTER/ESCAPE/PAGE-UP/PAGE-DOWN/END/HOME/UP/RIGHT/DOWN/LEFT/INSERT/DELETE', // does not inlcude * because Ext fails on typing it punctuation : '.,/()[]{}\\"\'`~!?@#$%^&_=+-', normal : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" } } }, methods : { /** * Utility method which returns the center of a passed element. The coordinates are by default relative to the * containing document of the element (so for example if the element is inside of the nested iframe, coordinates * will be "local" to that iframe element). To get coordinates relative to the test iframe ("global" coordinates), * pass `local` as `false`. * * @param {Siesta.Test.ActionTarget} el The element to find the center of. * @param {Boolean} [local] Pass `true` means coordinates are relative to the containing document. This is the default value. * Pass `false` to make sure the coordinates are global to the test window. * * @return {Array} The array first element of which is the `x` coordinate and 2nd - `y` */ findCenter : function (target, local) { return this.getTargetCoordinate(target, local); }, isInDom : function (el) { var doc = el.ownerDocument if (!doc) return false while (el && el != doc.body) { el = el.parentNode } return Boolean(el) }, normalizeOffset : function (offset, $el) { var parts; if (this.typeOf(offset) == 'Function') offset = offset.call(this) offset = offset && offset.slice() || [ '50%', '50%' ]; var rect = this.getBoundingClientRect($el[ 0 ]) // The rounding rules are still magical // one is for sure, that if we get a precise whole number w/o fractional part // we subtract 1 from it, since pixel counting starts from 0 // when we have fractional part, in good browsers it seems its enough to throw it away with `floor` // in IE we additionally need to subtract 1 // following the conservative path and subtracting 1 in all browsers var width = Math.floor(rect.width) // if (bowser.msie || bowser.edge || width === rect.width) width-- width-- var height = Math.floor(rect.height) // if (bowser.msie || bowser.edge || height === rect.height) height-- height-- if (typeof (offset[ 0 ]) === 'string') { parts = offset[ 0 ].split('%'); offset[ 0 ] = parseInt(offset[ 0 ].match(/\d+/)[ 0 ], 10) * width / 100; if (parts[ 1 ]) { offset[ 0 ] += parseInt(parts[ 1 ]); } } offset[ 0 ] = Math.round(offset[ 0 ]) if (typeof (offset[ 1 ]) === 'string') { parts = offset[ 1 ].split('%'); offset[ 1 ] = parseInt(offset[ 1 ].match(/\d+/)[ 0 ], 10) * height / 100; if (parts[ 1 ]) { offset[ 1 ] += parseInt(parts[ 1 ]); } } offset[ 1 ] = Math.round(offset[ 1 ]) return offset }, // return viewport coordinates // can be simplified with just `getBoundingClientRect` ? getTargetCoordinate : function (target, local, offset) { var el = this.normalizeElement(target), $el = this.$(el), elOffset = $el.offset(), elDoc = el.ownerDocument, elWin = elDoc.defaultView || elDoc.parentWindow, xy = [ this.pageXtoViewportX(elOffset.left, elWin), this.pageYtoViewportY(elOffset.top, elWin) ] offset = this.normalizeOffset(offset, $el) xy[ 0 ] += offset[ 0 ]; xy[ 1 ] += offset[ 1 ]; if (local === false) { // Potentially we're interacting with an element inside a nested frame, which means // the coordinates are local to that frame if (elWin !== this.global) { var offsetsToTop = this.$(elWin.frameElement).offset() xy[ 0 ] += this.pageXtoViewportX(offsetsToTop.left) xy[ 1 ] += this.pageYtoViewportY(offsetsToTop.top) } } return xy; }, /** * Returns true if the element is visible, checking jQuery :visible selector + style visibility value. * * @param {Siesta.Test.ActionTarget} el The element * @return {Boolean} */ isElementVisible : function(el) { el = this.normalizeElement(el); // Workaround for OPTION elements which don't behave like normal DOM elements. jQuery always consider them invisible. // Decide based on visibility of the parent SELECT node if (el && el.nodeName.toLowerCase() === 'option') { el = this.$(el).closest('select')[ 0 ] } if (el) { try { // Jquery :visible doesn't handle SVG/VML, so manual check // accessing to `this.global.SVGElement` throws exceptions for popups in IE 9 if (window.SVGElement && el instanceof this.global.SVGElement) return el.style.display !== 'none' && el.style.visibility !== 'hidden' } catch (e) { } // Jquery :visible doesn't take visibility into account return this.$(el).is(':visible') && (this.$(el).css('visibility') !== 'hidden') } return false }, /** * Passes if the `innerText` property of the <body> element contains the text passed * * @param {String} text The text to match * @param {String} [description] The description for the assertion */ assertTextPresent : function(text, description) { this.like(this.global.document.body.innerText, text, description); }, /** * Passes if the innerHTML of the passed element contains the text passed * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} text The text to match * @param {String} [description] The description for the assertion */ contentLike : function(el, text, description) { el = this.normalizeElement(el); this.like(el.innerHTML, text, description); }, /** * Passes if the innerHTML of the passed element does not contain the text passed * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} text The text to match * @param {String} [description] The description for the assertion */ contentNotLike : function(el, text, description) { el = this.normalizeElement(el); this.unlike(el.innerHTML, text, description); }, /** * Waits until the innerHTML of the passed element contains the text passed * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} text The text to match * @param {Function} callback The callback to call after the CSS selector 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. */ waitForContentLike : function(el, text, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); return this.waitFor({ method : function() { return el.innerHTML.match(text); }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForContentLike', description : ' ' + R.get('elementContent') + ' "' + text + '" ' + R.get('toAppear') }); }, /** * Waits until the innerHTML of the passed element does not contain the text passed * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} text The text to match * @param {Function} callback The callback to call after the CSS selector 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. */ waitForContentNotLike : function(el, text, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); return this.waitFor({ method : function() { return !el.innerHTML.match(text); }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForContentNotLike', description : ' ' + R.get('elementContent') + ' "' + text + '" ' + R.get('toDisappear') }); }, getRandomTypeString : function (length) { var allowedCharacters = this.allowedCharacters var special = allowedCharacters.special.split('/') var punctuation = allowedCharacters.punctuation var normal = allowedCharacters.normal var total = special.length + punctuation.length + normal.length var str = '' for (var i = 0; i < length; i++) { var index = this.randomBetween(0, total - 1) if (index < normal.length) str += normal.substr(index, 1) else { index -= normal.length if (index < punctuation.length) str += punctuation.substr(index, 1) else { index -= punctuation.length str += '[' + special[ index ] + ']' } } } return str }, /** * Performs clicks, double clicks, right clicks and drags at random coordinates within the passed target. * While doing all these random actions it also tracks the number of exceptions thrown and reports a failure * if there was any. Otherwise it reports a passed assertion. * * Use this assertion to "stress-test" your component, making sure it will work correctly in various unexpected * interaction scenarious. * * Note that as a special case, when this method is provided with the document's <body> element, * it will test the whole browser viewport. * * @param {Siesta.Test.ActionTarget} el The element to upon which to unleash the "monkey". * @param {Int} nbrInteractions The number of random interactions to perform. * @param {String} [description] The description for the assertion * @param {Function} callback The callback to call after all actions are completed * @param {Object} scope The scope for the callback */ monkeyTest : function(el, nbrInteractions, description, callback, scope, stepCallback) { el = this.normalizeElement(el, false, true); this.suppressPassedWaitForAssertion = true; if (typeof nbrInteractions === 'function') { callback = nbrInteractions; scope = description; description = ''; } else if (typeof description === 'function') { callback = description; description = ''; } nbrInteractions = typeof nbrInteractions === 'number' ? nbrInteractions : 30; var global = this.global var isBody = el == global.document.body var me = this, offset = me.$(el).offset(), right = offset.left + me.$(isBody ? global : el).width(), bottom = offset.top + me.$(isBody ? global : el).height(); var actionLog = [] var R = Siesta.Resource('Siesta.Test.Element'); var queue = new Siesta.Util.Queue({ deferer : me.originalSetTimeout, deferClearer : me.originalClearTimeout, interval : 50, observeTest : this, processor : function (data) { if (me.nbrExceptions || me.failed) { assertionChecker() // do not continue if the test has detected an exception thrown queue.abort() } else { var async = me.beginAsync(null, function (test) { test.fail( description || R.get('monkeyNoExceptions'), R.get('monkeyActionLog') + ":" + JSON.stringify(actionLog) ) return true }); var next = data.next data.next = function () { // callback to call after each monkey action stepCallback && stepCallback(data) me.endAsync(async) next() } data.action(data) } } }); var dummy = [] dummy.length = nbrInteractions var ignoreActionOnAnchor = function (data, i) { var target = me.normalizeElement(data.dragFrom || data.xy) // Prevent also drag target as it could be a link tag var dragToTarget = data.dragTo && me.normalizeElement(data.dragTo) // do not click on <a> elements, unless those // w/o `href' or with #hash-style hrefs if ( (target && (target.tagName.toLowerCase() == 'a' || $(target).closest('a').length > 0)) || dragToTarget && (dragToTarget.tagName.toLowerCase() == 'a' || $(dragToTarget).closest('a').length > 0) ) { data.next() return true } else return false } Joose.A.each(dummy, function (value, i) { // Inject { waitForSelector : 'body' } before every monkey action to make sure we always have a body queue.addAsyncStep({ action : function (data) { me.waitForSelector('body', data.next); } }); var xy = [ me.randomBetween(offset.left, right), me.randomBetween(offset.top, bottom) ]; switch (me.randomBetween(0, 4)) { case 0: queue.addAsyncStep({ action : function (data) { if (!ignoreActionOnAnchor(data)) { actionLog.push({ 'click' : xy }) me.click(data.xy, data.next) } }, xy : xy }); break; case 1: queue.addAsyncStep({ action : function (data) { if (!ignoreActionOnAnchor(data)) { actionLog.push({ 'doubleclick' : xy }) me.doubleClick(data.xy, data.next) } }, xy : xy }); break; case 2: queue.addAsyncStep({ action : function (data) { if (me.simulator.type != 'native' && "oncontextmenu" in window) { actionLog.push({ 'rightclick' : xy }) me.rightClick(data.xy, data.next) } else { if (!ignoreActionOnAnchor(data)) { actionLog.push({ 'click' : xy }) me.click(data.xy, data.next) } } }, xy : xy }); break; case 3: var dragTo = [ me.randomBetween(offset.left, right), me.randomBetween(offset.top, bottom) ] queue.addAsyncStep({ action : function (data) { if (!ignoreActionOnAnchor(data)) { actionLog.push({ action : 'drag', target : xy, to : dragTo }) me.dragTo(data.dragFrom, data.dragTo, data.next) } }, dragFrom : xy, dragTo : dragTo }); break; case 4: var text = me.getRandomTypeString(15) // First click somewhere then type queue.addAsyncStep({ action : function (data) { if (!ignoreActionOnAnchor(data)) { actionLog.push({ 'click' : xy }) me.click(data.xy, function () { me.waitForSelector('body', function () { actionLog.push({ 'type' : text.replace("'", "\\'") }) me.type(null, text, data.next) }); }) } }, xy : xy }); break; } }) var checkerActivated = false var assertionChecker = function () { checkerActivated = true if (me.nbrExceptions || me.failed) { me.fail(description || R.get('monkeyNoExceptions'), R.get('monkeyActionLog') + ":" + JSON.stringify(actionLog)) } else me.pass(description || R.get('monkeyNoExceptions')) } this.on('beforetestfinalizeearly', assertionChecker) queue.run(function () { if (!checkerActivated) { me.un('beforetestfinalizeearly', assertionChecker) assertionChecker() } this.suppressPassedWaitForAssertion = false; me.processCallbackFromTest(callback, [actionLog], scope || me) }); }, /** * Passes if the element has the supplied CSS classname * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} cls The class name to check for * @param {String} [description] The description for the assertion */ hasCls : function (el, cls, description) { var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); if (this.$(el).hasClass(cls)) { this.pass(description, { descTpl : R.get('elementHasClass') + ' {cls}', cls : cls }); } else { this.fail(description, { assertionName : 'hasCls', got : el.className, gotDesc : R.get('elementClasses'), need : cls, needDesc : R.get('needClass') }) } }, /** * Passes if the element does not have the supplied CSS classname * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} cls The class name to check for * @param {String} [description] The description for the assertion */ hasNotCls : function (el, cls, description) { var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); if (!this.$(el).hasClass(cls)) { this.pass(description, { descTpl : R.get('elementHasNoClass') + ' {cls}', cls : cls }); } else { this.fail(description, { assertionName : 'hasNotCls', got : el.className, gotDesc : R.get('elementClasses'), annotation : R.get('elementHasClass') + ' [' + cls + ']' }) } }, /** * Passes if the element has the supplied style value * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} property The style property to check for * @param {String} value The style value to check for * @param {String} [description] The description for the assertion */ hasStyle : function (el, property, value, description) { var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); if (this.$(el).css(property) === value) { this.pass(description, { descTpl : R.get('hasStyleDescTpl'), value : value, property : property }); } else { this.fail(description, { assertionName : 'hasStyle', got : this.$(el).css(property), gotDesc : R.get('elementStyles'), need : value, needDesc : R.get('needStyle') }); } }, /** * Passes if the element does not have the supplied style value * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} property The style property to check for * @param {String} value The style value to check for * @param {String} [description] The description for the assertion */ hasNotStyle : function (el, property, value, description) { var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); if (this.$(el).css(property) !== value) { this.pass(description, { descTpl : R.get('hasNotStyleDescTpl'), value : value, property : property }); } else { this.fail(description, { assertionName : 'hasNotStyle', got : el.style.toString(), gotDesc : R.get('elementStyles'), annotation : R.get('hasTheStyle') + ' [' + property + ']' }); } }, /** * Waits for a certain CSS selector to be found at the passed XY coordinate, and calls the callback when found. * The callback will receive the element from the passed XY coordinates. * * @param {Array} xy The x and y coordinates to query * @param {String} selector The CSS selector to check for * @param {Function} callback The callback to call after the CSS selector 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#waitForTimeout} value. */ waitForSelectorAt : function(xy, selector, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); if (!selector) throw R.get('noCssSelector'); var me = this return this.waitFor({ method : function() { var el = me.elementFromPoint(xy[0], xy[1], true); if (el && me.$(el).is(selector)) return el; }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForSelectorAt', description : ' ' + R.get('selector') + ' "' + selector + '" ' + R.get('toAppearAt') + ': [' + xy.toString() + ']' }); }, /** * Waits for a certain CSS selector to be found at current cursor position, and calls the callback when found. * The callback will receive the element found. * * @param {String} selector The CSS selector to check for * @param {Function} callback The callback to call after the CSS selector 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#waitForTimeout} value. */ waitForSelectorAtCursor : function(selector, callback, scope, timeout) { return this.waitForSelectorAt(this.simulator.currentPosition, selector, callback, scope, timeout); }, /** * Waits for a certain CSS selector to be found in the DOM, and then calls the callback supplied. * The callback will receive the results of jQuery selector. * * @param {String} selector The CSS selector to check for * @param {Siesta.Test.ActionTarget} root (optional) The root element in which to detect the selector. * @param {Function} callback The callback to call after the CSS selector 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#waitForTimeout} value. */ waitForSelector : function(selector, root, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); var me = this; if (!selector) throw R.get('noCssSelector'); if (this.typeOf(root) == 'Function') { timeout = scope; scope = callback; callback = root; root = null; } if (root) root = this.normalizeElement(root); return this.waitFor({ method : function() { var result = me.$(selector, root); if (result.length > 0) return result; }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForSelector', description : ' ' + R.get('selector') + ' "' + selector + '" ' + R.get('toAppear') }); }, /** * Waits till all the CSS selectors from the provided array to be found in the DOM, and then calls the callback supplied. * * @param {Array[String]} selectors The array of CSS selectors to check for * @param {Siesta.Test.ActionTarget} root (optional) The root element in which to detect the selector. * @param {Function} callback The callback to call after the CSS selector 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#waitForTimeout} value. */ waitForSelectors : function(selectors, root, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); if (selectors.length < 1) throw R.get('waitForSelectorsBadInput'); if (this.typeOf(root) == 'Function') { timeout = scope; scope = callback; callback = root; root = null; } if (root) root = this.normalizeElement(root); var me = this return this.waitFor({ method : function () { var allPresent = true Joose.A.each(selectors, function (selector) { if (me.$(selector, root).length === 0) { allPresent = false // stop iteration return false } }) return allPresent }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForSelectors', description : ' ' + R.get('selectors') + ' "' + selectors + '" ' + R.get('toAppear') }); }, /** * Waits for a certain CSS selector to not be found in the DOM, and then calls the callback supplied. * * @param {String} selector The CSS selector to check for * @param {Siesta.Test.ActionTarget} root (optional) The root element in which to detect the selector. * @param {Function} callback The callback to call after the CSS selector 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#waitForTimeout} value. */ waitForSelectorNotFound : function(selector, root, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); var me = this; if (!selector) throw 'A CSS selector must be supplied'; if (this.typeOf(root) == 'Function') { timeout = scope; scope = callback; callback = root; root = null; } if (root) root = this.normalizeElement(root); return this.waitFor({ method : function() { return me.$(selector, root).length === 0; }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForSelectorNotFound', description : ' ' + R.get('selector') + ' "' + selector + '" ' + R.get('toDisappear') }); }, /** * Waits until the passed element becomes "visible" in the DOM and calls the provided callback. * Please note, that "visible" means element will just have a DOM node, and still may be hidden by another visible element. * * The callback will receive the passed element as the 1st argument. * * See also {@link #waitForElementTop} method. * * @param {Siesta.Test.ActionTarget} el The element to look for. * @param {Function} callback The callback to call after the CSS selector 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. */ waitForElementVisible : function(el, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); return this.waitFor({ method : function() { var normalized = this.normalizeElement(el, true); if (normalized && this.isElementVisible(normalized)) return normalized; }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForElementVisible', description : ' ' + R.get('element') + ' "' + el.toString() + '" ' + R.get('toAppear') }); }, /** * Waits until the passed element is becomes not "visible" in the DOM and call the provided callback. * Please note, that "visible" means element will just have a DOM node, and still may be hidden by another visible element. * * The callback will receive the passed element as the 1st argument. * * See also {@link #waitForElementNotTop} method. * * @param {Siesta.Test.ActionTarget} el The element to look for. * @param {Function} callback The callback to call after the CSS selector 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. */ waitForElementNotVisible : function(el, callback, scope, timeout) { el = this.normalizeElement(el); var R = Siesta.Resource('Siesta.Test.Element'); var me = this; return this.waitFor({ method : function() { return !me.isElementVisible(el) && el; }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForElementNotVisible', description : ' ' + R.get('element') + ' "' + el.toString() + '" ' + R.get('toDisappear') }); }, /** * Waits until the passed element is the 'top' element in the DOM and call the provided callback. * * The callback will receive the passed element as the 1st argument. * * @param {Siesta.Test.ActionTarget} el The element to look for. * @param {Function} callback The callback to call * @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. */ waitForElementTop : function(el, callback, scope, timeout) { var R = Siesta.Resource('Siesta.Test.Element'); return this.waitFor({ method : function() { var normalized = this.normalizeElement(el, true); if (normalized && this.elementIsTop(normalized, true)) { return normalized; } }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForElementTop', description : ' ' + R.get('element') + ' "' + el.toString() + '" ' + R.get('toBeTopEl') }); }, /** * Waits until the passed element is not the 'top' element in the DOM and calls the provided callback with the element found. * * The callback will receive the actual top element. * * @param {Siesta.Test.ActionTarget} el The element to look for. * @param {Function} callback The callback to call * @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. */ waitForElementNotTop : function(el, callback, scope, timeout) { el = this.normalizeElement(el); var R = Siesta.Resource('Siesta.Test.Element'); var me = this return this.waitFor({ method : function() { if (!me.elementIsTop(el, true)) { var center = me.findCenter(el); return me.elementFromPoint(center[0], center[1], true); } }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForElementNotTop', description : ' ' + R.get('element') + ' "' + el.toString() + '" ' + R.get('toNotBeTopEl') }); }, /** * Passes if the element is in the DOM and visible. * @param {Siesta.Test.ActionTarget} el The element * @param {String} [description] The description for the assertion */ elementIsVisible : function(el, description) { el = this.normalizeElement(el, true, false, false, { ignoreNonVisible : false }); this.ok(el && this.isElementVisible(el), description); }, /** * Passes if the element is not visible. * @param {Siesta.Test.ActionTarget} el The element * @param {String} [description] The description for the assertion */ elementIsNotVisible : function(el, description) { el = this.normalizeElement(el, false, false, false, { ignoreNonVisible : false }); this.notOk(this.isElementVisible(el), description); }, /** * Utility method which checks if the passed method is the 'top' element at its position. By default, "top" element means, * that center point of the element is not covered with any other elements. You can also check any other point reachability * using the "offset" argument. * * @param {Siesta.Test.ActionTarget} el The element to look for. * @param {Boolean} allowChildren true to also include child nodes. False to strictly check for the passed element. * @param {Array} offset An array of 2 elements, defining "x" and "y" offset from the left-top corner of the element * * @return {Boolean} true if the element is the top element. */ elementIsTop : function (el, allowChildren, offset) { el = this.normalizeElement(el); // Workaround for OPTION elements which don't behave like normal DOM elements. jQuery always consider them invisible. // Decide based on visibility of the parent SELECT node if (el && el.nodeName.toLowerCase() === 'option') { el = this.$(el).closest('select')[0]; } var elDoc = el.ownerDocument var localPoint = this.getTargetCoordinate(el, true, offset) var foundEl = elDoc.elementFromPoint(localPoint[ 0 ], localPoint[ 1 ]); return foundEl && (foundEl === el || (allowChildren && this.$(foundEl).closest(el).length > 0)); }, // Helper method to find out if an offset is targeting a point outside its target // Assumes the el passed is visible isOffsetInsideElementBox : function (el, offset) { if (!offset) return true; var $el = this.$(this.normalizeElement(el)); var w = $el.outerWidth(); var h = $el.outerHeight(); offset = this.normalizeOffset(offset, $el); return offset[0] >= 0 && offset[0] < w && offset[1] >= 0 && offset[1] < h; }, /** * Passes if the element is found at the supplied xy coordinates. * * @param {Siesta.Test.ActionTarget} el The element to query * @param {Array} xy The xy coordinate to query. * @param {Boolean} allowChildren true to also include child nodes. False to strictly check for the passed element. * @param {String} [description] The description for the assertion */ elementIsAt : function(el, xy, allowChildren, description) { el = this.normalizeElement(el); var foundEl = this.elementFromPoint(xy[0], xy[1], true); var R = Siesta.Resource('Siesta.Test.Element'); if (!foundEl) { this.fail(description, { assertionName : 'elementIsAt', got : { x: xy[0], y : xy[1] }, gotDesc : R.get('Position'), annotation : R.get('noElementAtPosition') }); } else if (allowChildren) { if (foundEl === el || this.$(foundEl).closest(el).length > 0) { this.pass(description, { descTpl : R.get('elementIsAtDescTpl'), x : xy[ 0 ], y : xy[ 1 ] }); } else { this.fail(description, { assertionName : 'elementIsAt', got : foundEl, gotDesc : R.get('topElement'), need : el, needDesc : R.get('allowChildrenDesc'), annotation : R.get('allowChildrenAnnotation') }); } } else { if (foundEl === el) { this.pass(description, { descTpl : R.get('elementIsAtPassTpl'), x : xy[ 0 ], y : xy[ 1 ] }); } else { this.fail(description, { assertionName : 'elementIsAt', got : foundEl, gotDesc : R.get('topElement'), need : el, needDesc : 'Should be', annotation : R.get('noChildrenFailAnnotation') }); } } }, /** * Passes if the element is the top element (using its center xy coordinates). "Top" element means, * that element is not covered with any other elements. * * This assertion can be used for example to test, that some element, that appears only when mouse hovers some other element is accessible by user * with mouse (which is not always true because of various z-index issues). * * @param {Siesta.Test.ActionTarget} el The element to look for. * @param {Boolean} allowChildren true to also include child nodes. False to strictly check for the passed element. * @param {String} [description] The description for the assertion * @param {Boolean} strict true to check all four corners of the element. False to only check at element center. */ elementIsTopElement : function(el, allowChildren, description, strict) { el = this.normalizeElement(el); if (strict) { var o = this.$(el).offset(); var R = Siesta.Resource('Siesta.Test.Element'); var region = { top : o.top, right : o.left + this.$(el).outerWidth(), bottom : o.top + this.$(el).outerHeight(), left : o.left }; this.elementIsAt(el, [region.left+1, region.top+1], allowChildren, description + ' ' + R.get('topLeft')); this.elementIsAt(el, [region.left+1, region.bottom-1], allowChildren, description + ' ' + R.get('bottomLeft')); this.elementIsAt(el, [region.right-1, region.top+1], allowChildren, description + ' ' + R.get('topRight')); this.elementIsAt(el, [region.right-1, region.bottom-1], allowChildren, description + ' ' + R.get('bottomRight')); } else { this.elementIsAt(el, this.findCenter(el), allowChildren, description); } }, /** * Passes if the element is not the top element (using its center xy coordinates). * * @param {Siesta.Test.ActionTarget} el The element to look for. * @param {Boolean} allowChildren true to also include child nodes. False to strictly check for the passed element. * @param {String} [description] The description for the assertion */ elementIsNotTopElement : function(el, allowChildren, description) { el = this.normalizeElement(el); var center = this.findCenter(el); var foundEl = this.elementFromPoint(center[ 0 ], center[ 1 ], true); if (!foundEl) { var R = Siesta.Resource('Siesta.Test.Element'); this.pass(description, { descTpl : R.get('elementIsNotTopElementPassTpl') }); return } if (allowChildren) { this.ok(foundEl !== el && this.$(foundEl).closest(el).length === 0, description); } else { this.isnt(foundEl, el, description); } }, /** * Passes if the element is found at the supplied xy coordinates. * * @param {String} selector The selector to query for * @param {Array} xy The xy coordinate to query. * @param {Boolean} allowChildren true to also include child nodes. False to strictly check for the passed element. * @param {String} [description] The description for the assertion */ selectorIsAt : function(selector, xy, description) { var R = Siesta.Resource('Siesta.Test.Element'); if (!selector) throw R.get('noCssSelector'); var foundEl = this.$(this.elementFromPoint(xy[0], xy[1], true)); if (foundEl.has(selector).length > 0 || foundEl.closest(selector).length > 0) { this.pass(description, { descTpl : R.get('selectorIsAtPassTpl'), selector : selector, xy : xy }); } else { this.fail(description, { got : foundEl[0].outerHTML ? foundEl[0].outerHTML : foundEl[0].innerHTML, need : R.get('elementMatching') + ' ' + selector, assertionName : 'selectorIsAt', annotation : R.get('selectorIsAtFailAnnotation') + ' [' + xy + ']' }); } }, /** * Passes if the selector is found in the DOM * * @param {String} selector The selector to query for * @param {String} [description] The description for the assertion */ selectorExists : function (selector, description) { var R = Siesta.Resource('Siesta.Test.Element'); if (!selector) throw R.get('noCssSelector'); if (this.$(selector).length <= 0) { this.fail(description, R.get('selectorExistsFailTpl') + ' : ' + selector); } else { this.pass(description, { descTpl : R.get('selectorExistsPassTpl'), selector : selector }); } }, /** * Passes if the selector is not found in the DOM * * @param {String} selector The selector to query for * @param {String} [description] The description for the assertion */ selectorNotExists : function (selector, description) { var R = Siesta.Resource('Siesta.Test.Element'); var els = this.$(selector); if (els.length > 0) { this.fail(description, { descTpl : R.get('selectorNotExistsFailTpl') + ': ' + selector, annotation : $.map(els, function (el, i) { return (i + 1) + ". " + el.cloneNode().outerHTML; }).join('\r\n') }); } else { this.pass(description, { descTpl : R.get('selectorNotExistsPassTpl'), selector : selector }); } }, /** * Waits until the passed scroll property of the element has changed. * * The callback will receive the new `scroll` value. * * @param {Siesta.Test.ActionTarget} el The element * @param {String} side 'left' or 'top' * @param {Function} callback The callback to call * @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. */ waitForScrollChange : function(el, side, callback, scope, timeout) { el = this.normalizeElement(el); var scrollProp = 'scroll' + Joose.S.uppercaseFirst(side); var original = el[ scrollProp ]; var R = Siesta.Resource('Siesta.Test.Element'); return this.waitFor({ method : function() { if (el[ scrollProp ] !== original) return el[ scrollProp ]; }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForScrollChange', description : ' ' + scrollProp + ' ' + R.get('toChangeForElement') + ' ' + el.toString() }); }, /** * Waits until the `scrollLeft` property of the element has changed. * * The callback will receive the new `scrollLeft` value. * * @param {Siesta.Test.ActionTarget} el The element * @param {Function} callback The callback to call * @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. */ waitForScrollLeftChange : function(el, callback, scope, timeout) { return this.waitForScrollChange(this.normalizeElement(el), 'left', callback, scope, timeout); }, /** * Waits until the scrollTop property of the element has changed * * The callback will receive the new `scrollTop` value. * * @param {Siesta.Test.ActionTarget} el The element * @param {Function} callback The callback to call * @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. */ waitForScrollTopChange : function(el, callback, scope, timeout) { return this.waitForScrollChange(this.normalizeElement(el), 'top', callback, scope, timeout); }, /** * This method changes the `scrollLeft` and `scrollTop` properties of the dom element, then waits for the "scroll" event * from it and calls the provided callback. * * For example: * // scroll the domEl to the 50px horizontal offset, 100px vertical, wait for "scroll" event, call the callback t.scrollTo(domEl, 50, 100, function () { ... }) * Optionally it can also wait some additional time before calling the callback: * // scroll the domEl to the 50px horizontal offset, 100px vertical, wait for "scroll" event, wait 1000ms more, call the callback t.scrollTo(domEl, 50, 100, function () { ... }, 1000) * * @param {Siesta.Test.ActionTarget} el The element * @param {Number} newLeft The value for the `scrollLeft` property * @param {Number} newTop The value for the `scrollTop` property * @param {Function} callback A function to call after "scroll" event has been fired and additional delay completed (if any) * @param {Number} [delay] Optional, additional delay before calling a callback * * @return {Object} An object with new scrolling position * @return {Number} return.scrollLeft The new value of the `scrollLeft` property of the dom element * @return {Number} return.scrollTop The new value of the `scrollTop` property of the dom element */ scrollTo : function (el, scrollLeft, scrollTop, callback, delay, allowEmpty) { el = this.normalizeElement(el, allowEmpty); if (!el) { this.processCallbackFromTest(callback); return } var me = this var originalSetTimeout = this.originalSetTimeout; var doc = el.ownerDocument var waiter = this.waitForEvent(el == this.getElForPageScroll() ? doc : el, 'scroll', function () { if (delay > 0) { var async = me.beginAsync(delay + 100) originalSetTimeout(function () { me.endAsync(async) me.processCallbackFromTest(callback) }, delay) } else me.processCallbackFromTest(callback) }) var prevScrollTop = el.scrollTop var prevScrollLeft = el.scrollLeft el.scrollLeft = scrollLeft el.scrollTop = scrollTop // no event will be fired in this case probably - force the waiting operation to complete if (el.scrollTop == prevScrollTop && el.scrollLeft == prevScrollLeft) { waiter.force() } return { scrollLeft : el.scrollLeft, scrollTop : el.scrollTop } }, /** * This method changes the "scrollTop" property of the dom element, then waits for the "scroll" event from it and calls the provided callback. * * For example: * // scroll the domEl to the 100px offset, wait for "scroll" event, call the callback t.scrollVerticallyTo(domEl, 100, function () { ... }) * Optionally it can also wait some additional time before calling the callback: * // scroll the domEl to the 100px offset, wait for "scroll" event, wait 1000ms more, call the callback t.scrollVerticallyTo(domEl, 100, 1000, function () { ... }) * * @param {Siesta.Test.ActionTarget} el The element * @param {Number} newTop The value for the "scrollTop" property * @param {Number} [delay] Additional delay, this argument can be omitted * @param {Function} callback A function to call after "scroll" event has been fired and additional delay completed (if any) * * @return {Number} The new value of the "scrollTop" property of the dom element */ scrollVerticallyTo : function (el, newTop, delay, callback) { el = this.normalizeElement(el); if (this.typeOf(delay) != 'Number') { callback = delay delay = null } var me = this var originalSetTimeout = this.originalSetTimeout; var doc = el.ownerDocument var waiter = this.waitForEvent(el == this.getElForPageScroll() ? doc : el, 'scroll', function () { if (delay > 0) { var async = me.beginAsync(delay + 100) originalSetTimeout(function () { me.endAsync(async) me.processCallbackFromTest(callback) }, delay) } else me.processCallbackFromTest(callback) }) var prevScrollTop = el.scrollTop el.scrollTop = newTop // no event will be fired in this case probably - force the waiting operation to complete if (el.scrollTop == prevScrollTop) { waiter.force() } // re-read the scrollTop value and return it (newTop can be too big for example and will be truncated) return el.scrollTop }, /** * This method changes the "scrollLeft" property of the dom element, then waits for the "scroll" event from it and calls the provided callback. * * For example: * // scroll the domEl to the 100px offset, wait for "scroll" event, call the callback t.scrollHorizontallyTo(domEl, 100, function () { ... }) * Optionally it can also wait some additional time before calling the callback: * // scroll the domEl to the 100px offset, wait for "scroll" event, wait 1000ms more, call the callback t.scrollHorizontallyTo(domEl, 100, 1000, function () { ... }) * * @param {Siesta.Test.ActionTarget} el The element * @param {Number} newLeft The value for the "scrollLeft" property * @param {Number} [delay] Additional delay, this argument can be omitted * @param {Function} callback A function to call after "scroll" event has been fired and additional delay completed (if any) * * @return {Number} The new value of the "scrollLeft" property of the dom element */ scrollHorizontallyTo : function (el, newLeft, delay, callback) { el = this.normalizeElement(el); if (this.typeOf(delay) != 'Number') { callback = delay delay = null } var me = this var originalSetTimeout = this.originalSetTimeout; var doc = el.ownerDocument var waiter = this.waitForEvent(el == this.getElForPageScroll() ? doc : el, 'scroll', function () { if (delay > 0) { var async = me.beginAsync(delay + 100) originalSetTimeout(function () { me.endAsync(async) me.processCallbackFromTest(callback) }, delay) } else me.processCallbackFromTest(callback) }) var prevScrollLeft = el.scrollLeft el.scrollLeft = newLeft // no event will be fired in this case probably - force the waiting operation to complete if (el.scrollLeft == prevScrollLeft) { waiter.force() } // re-read the scrollLeft value and return it (newLeft can be too big for example and will be truncated) return el.scrollLeft }, /** * This method accepts an array of the DOM elements and performs a mouse click on them, in order. After that, it calls the provided callback: * ```javascript t.clickAll([ el1, el2 ], function () { ... }) * the elements can be also provided inline, w/o wrapping array: t.clickAll(el1, el2, function () { ... }) ``` * * @param {Array[Siesta.Test.ActionTarget]} elements The array of elements to click * @param {Function} callback The function to call after clicking all elements */ clickAll : function () { var args = Array.prototype.concat.apply([], arguments) var callback if (this.typeOf(args[ args.length - 1 ]) == 'Function') callback = args.pop() // poor-man Array.flatten, with only 1 level of nesting support args = Array.prototype.concat.apply([], args) var steps = [] Joose.A.each(args, function (arg) { steps.push({ action : 'click', target : arg }) }) var me = this if (callback) steps.push(function () { me.processCallbackFromTest(callback) }) this.chain.apply(this, steps) }, /* * @deprecated * Alias for {@link clickAll} * */ chainClick : function() { return this.clickAll.apply(this, arguments); }, /** * This method is a wrapper around the {@link #clickAll}, it performs a click on the every element found by the DOM query. * * You can specify the optional `root` element to start the query from: * * t.clickSelector('.my-grid .x-grid-row', someEl, function () {}) * * or omit it (query will start from the document): * * t.clickSelector('.my-grid .x-grid-row', function () {}) * * The provided callback will receive an array with DOM elements - result of query. * * * @param {String} selector The selector/xpath query * @param {Siesta.Test.ActionTarget} [root=document] The root of the query, defaults to the `document`. You can omit this parameter. * @param {Function} [callback] * @param {Object} [scope] */ clickSelector : function (selector, root, callback, scope) { if (arguments.length > 1 && this.typeOf(arguments[ 1 ]) == 'Function') { scope = callback; callback = root; root = null; } if (root) root = this.normalizeElement(root); // convert the result from jQuery dom query to a usual array var result = Joose.A.map(this.sizzle(selector, root), function (el) { return el }); this.clickAll(result, function () { callback && callback.call(scope || this, result) }) }, /** * This assertion passes when the DOM query with specified selector returns the expected number of elements * * You can specify the optional `root` element to start the query from: * * t.selectorCountIs('.x-grid-row', grid, 5, "Grid has 5 rows") * * or omit it (query will start from the document): * * t.selectorCountIs('.x-grid-row', 0, "No grid rows on the page") * * @param {String} selector DOM query selector * @param {Siesta.Test.ActionTarget} [root] An optional root element to start the query from, if omited query will start from the document * @param {Number} count The expected number of elements in the query result * @param {String} [description] The description for the assertion */ selectorCountIs : function (selector, root, count, description) { var R = Siesta.Resource('Siesta.Test.Element'); if (!selector) throw R.get('noCssSelector'); if (this.typeOf(root) == 'Number') { description = count count = root root = null } else root = this.normalizeElement(root) var inDOMCount = this.$(selector, root).length if (inDOMCount != count) { this.fail(description, { assertionName : 'selectorCountIs', descTpl : R.get('selectorCountIsFailTpl'), selector : selector, got : inDOMCount, need : count }); } else { this.pass(description, { descTpl : R.get('selectorCountIsPassTpl'), count : count, selector : selector }); } }, /** * Passes if the passed element is inside of the visible viewport * * @param {Siesta.Test.ActionTarget} el The element * @param {String} [description] The description for the assertion */ isInView : function (el, description) { if (this.elementIsInView(el)) { var R = Siesta.Resource('Siesta.Test.Element'); this.pass(description, { descTpl : R.get('isInViewPassTpl') }) } else this.fail(description, { assertionName : 'isInView' }) }, /** * Returns true if the passed element is inside of the visible viewport * * @param {Siesta.Test.ActionTarget} el The element */ elementIsInView : function(el) { el = this.normalizeElement(el); var inView = false; var offset = this.$(el).offset(); if (offset) { var docViewTop = $(this.global).scrollTop(); var docViewBottom = docViewTop + $(this.global).height(); var elemTop = offset.top; var elemBottom = elemTop + $(el).height(); inView = elemBottom >= docViewTop && elemTop <= docViewBottom; } return inView; }, /** * Waits until element is inside in the visible viewport and then calls the supplied callback * * @param {Siesta.Test.ActionTarget} el The element * @param {Function} callback The callback to call * @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. */ waitUntilInView : function (el, callback, scope, timeout) { var me = this; var R = Siesta.Resource('Siesta.Test.Element'); this.waitFor({ method : function() { var normalized = this.normalizeElement(el, true); if(normalized && me.elementIsInView(normalized)) { return normalized; } }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitUntilInView', description : el.toString + ' ' + R.get('toAppearInTheViewport') }); }, findScrolledParent : function(el) { var body = el.ownerDocument.body; var parent = this.$(el); while (parent = parent.parent(), parent.length && parent[ 0 ] != body) { if (parent[0].scrollTop > 0 || parent[0].scrollLeft > 0) { return parent[0]; } } }, focus : function (el, tryPreventScrollChange) { var prevIndex = el.getAttribute('tabIndex') var scrolledParent; if (this.activeElement() === el) return; try { if (prevIndex == null) el.setAttribute('tabIndex', -1) if (tryPreventScrollChange) { var oldScrollLeft, oldScrollTop, pageScrollX, pageScrollY; // In Chrome, when calling focus() manually on an element - it's scrolled into view in its parent hierarchy // Try to detect this and restore (This is far from optimal since application might have a listener triggering a desired // scroll of this element. But not triggering focus() on mousedown seems like a worse situation scrolledParent = this.findScrolledParent(el); if (scrolledParent) { oldScrollLeft = scrolledParent.scrollLeft; oldScrollTop = scrolledParent.scrollTop; } pageScrollX = this.getPageScrollX() pageScrollY = this.getPageScrollY() } el.focus({ preventScroll : tryPreventScrollChange }) if (tryPreventScrollChange) { if (scrolledParent) { if (oldScrollLeft !== scrolledParent.scrollLeft) { scrolledParent.scrollLeft = oldScrollLeft; } if (oldScrollTop !== scrolledParent.scrollTop) { scrolledParent.scrollTop = oldScrollTop; } } if (pageScrollX != this.getPageScrollX() || pageScrollY != this.getPageScrollY()) { this.global.scrollTo(pageScrollX, pageScrollY) } } } catch (e) { } finally { if (prevIndex == null) el.removeAttribute('tabIndex') else el.setAttribute('tabIndex', prevIndex) } }, /** * Passes if the passed element has no content (whitespace will be trimmed) * * @param {Siesta.Test.ActionTarget} el The element * @param {String} [description] The description for the assertion */ elementIsEmpty : function (el, description) { el = this.normalizeElement(el); if (el && this.isElementEmpty(el)) { var R = Siesta.Resource('Siesta.Test.Element'); this.pass(description, { descTpl : R.get('elementIsEmptyPassTpl') }) } else this.fail(description, { got : el.innerHTML, need : '', assertionName : 'elementIsEmpty' }) }, /** * Passes if the passed element has some non-whitespace content * * @param {Siesta.Test.ActionTarget} el The element * @param {String} [description] The description for the assertion */ elementIsNotEmpty : function (el, description) { el = this.normalizeElement(el); if (el && !this.isElementEmpty(el)) { var R = Siesta.Resource('Siesta.Test.Element'); this.pass(description, { descTpl : R.get('elementIsNotEmptyPassTpl') }) } else this.fail(description, { assertionName : 'elementIsNotEmpty' }) }, /** * Waits until the innerHTML of the passed element is empty (whitespace will be trimmed) * * @param {Siesta.Test.ActionTarget} el The element to query * @param {Function} callback The callback to call after the CSS selector 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. */ waitForElementEmpty : function(el, callback, scope, timeout) { var me = this; var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); return this.waitFor({ method : function() { return me.isElementEmpty(el); }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForElementEmpty', description : ' ' + R.get('elementToBeEmpty') }); }, /** * Waits until the innerHTML of the passed element contains some non-whitespace text. * * @param {Siesta.Test.ActionTarget} el The element to query * @param {Function} callback The callback to call after the CSS selector 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. */ waitForElementNotEmpty : function(el, callback, scope, timeout) { var me = this; var R = Siesta.Resource('Siesta.Test.Element'); el = this.normalizeElement(el); return this.waitFor({ method : function() { return !me.isElementEmpty(el); }, callback : callback, scope : scope, timeout : timeout, assertionName : 'waitForElementNotEmpty', description : ' ' + R.get('elementToNotBeEmpty') }); }, isElementEmpty : function (el) { return !el.innerHTML.replace(/^\s+|\s+$/g, ''); }, /** * Passes if the target element has an attribute with the provided value. * * @param {Siesta.Test.ActionTarget} el The element to query * @param {String} attribute The attribute * @param {String} value The value * @param {String} [description] The description for the assertion */ hasAttributeValue : function(el, attribute, value, description) { el = this.normalizeElement(el); var foundValue = el.getAttribute(attribute); this.is(foundValue, value, description); }, /** * Passes if the passed element has the expected value as its "value" property (use with SELECT, INPUT type elements). * * @param {Siesta.Test.ActionTarget} el The element to query * @param {Mixed} value The value to compare to. * @param {String} [description] The description of the assertion */ hasValue : function(el, value, description) { el = this.normalizeElement(el); var foundValue = el.value; this.is(foundValue, value, description); }, /** * This assertion passes when the passed element offsetWidth matches the offsetWidth of the reference element * * @param {Siesta.Test.ActionTarget} el The element to query * @param {Siesta.Test.ActionTarget} referenceEl The element to compare against * @param {String} [description] The description of the assertion */ hasSameWidth: function (el, referenceEl, description) { el = this.normalizeElement(el); referenceEl = this.normalizeElement(referenceEl); this.is(el.offsetWidth, referenceEl.offsetWidth, description); }, /** * This assertion passes when the passed element offsetHeight matches the offsetHeight of the reference element * * @param {Siesta.Test.ActionTarget} el The element to query * @param {Siesta.Test.ActionTarget} referenceEl The element to compare against * @param {String} [description] The description of the assertion */ hasSameHeight: function (el, referenceEl, description) { el = this.normalizeElement(el); referenceEl = this.normalizeElement(referenceEl); this.is(el.offsetHeight, referenceEl.offsetHeight, description); }, isElementFocusable : function(el) { var disabled = el.getAttribute('disabled') === "true"; var nodeName = el.nodeName.toLowerCase(); // Other tags are covered in isTextInput var focusable = { a : 1, area : 1, button : 1, object : 1, select : 1 }; return !disabled && ( this.isTextInput(el) || el.isContentEditable || (el.nodeName.toLowerCase() === 'input' && el.type === 'file') || (nodeName in focusable) || (el.getAttribute('tabIndex') != null && (!bowser.msie || String(el.getAttribute('unselectable')).toLowerCase() != 'on')) ); } } });