ExtJS is a cross-browser JavaScript library for building rich internet applications.
Selenium IDE has trouble testing apps written in ExtJS.
There are two reasons.
First, the DOM of ExtJS widget is complex, many elements have a dynamic id
Let's look at the Button widget:
<table cellspacing="0" cellpadding="0" border="0" class="x-btn-wrap x-btn eos-widget x-btn-focus" id="button1" style="width: auto;"> <tbody> <tr> <td class="x-btn-left"><i> </i></td> <td class="x-btn-center" id="ext-gen411"> <em unselectable="on"> <button type="button" class="x-btn-text" id="ext-gen78">button1</button> </em> </td> <td class="x-btn-right"><i> </i></td> </tr> </tbody> </table>
I have recorded the command click for button1 like this;
| command | click |
| target | //button[@id='ext-gen78'] |
But next time the id of button element may be changed to ext-gen88, since ExtJS used dynamic code to create the widget.
To resolve the problem I write a ext locator to locate the ExtJS element.
For example, the command is
| command | click |
| target | //TABLE[@id='button1']/tbody/tr/td[2]/em/button |
The root element's id does not change, and the DOM structure is also stable.
My extension is :
var EXT_PREFIX = "ext-gen"; function findExtLocator(e) { function getElementIndex(el, p) { var childs = p.childNodes; for (var i = 0; i < childs.length; i++) { var curr = childs[i]; if (curr == el) { return "[" + (i + 1) + "]"; } } } if (e.id) { var elId = e.id; if (elId.indexOf(EXT_PREFIX) == 0) { var currNode = e; var locator = ""; while (currNode && currNode.tagName.toLowerCase() != "body") { parentNode = currNode.parentNode; locator = this.relativeXPathFromParent(currNode) + locator;// if (parentNode.id && parentNode.id.length > 0 && parentNode.id.indexOf(EXT_PREFIX) != 0) { locator = "//" + parentNode.tagName + "[@id='" + parentNode.id + "']" + locator; return locator; } currNode = currNode.parentNode; } } } return null; } LocatorBuilders.add('ext', findExtLocator); // You can change the priority of builders by setting LocatorBuilders.order. LocatorBuilders.order = ['ext', 'id', 'link', 'name', 'dom:name', 'xpath:link', 'xpath:img', 'xpath:attributes', 'xpath:href', 'dom:index', 'xpath:position'];
Then you can record ExtJs's page.
Second, ExtJS is based on JavaScript context
We can't assert JavaScript value of ExtJS.
To resolve this problem, I have extended selenium-core a little. You can include the following in your user-extensions.js file.
Selenium.prototype.assertExtEqual = function(expression, text) { /** * the euqal assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var result = this.extEval(expression) if (result != text) { Assert.fail("the value of [" + result + "] " + expression + " is not equal with " + text); } }; Selenium.prototype.assertExtGreaterThan = function(expression, text) { /** * the greater than assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var result = this.extEval(expression) if (result <= text) { Assert.fail("the value of [" + result + "] " + expression + " is not greater than " + text); } } Selenium.prototype.assertExtGreaterEqualThan = function(expression, text) { /** * the greater and equal than assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var result = this.extEval(expression) if (result < text) { Assert.fail("the value of [" + result + "] " + expression + " is not greater equal than " + text); } } Selenium.prototype.assertExtLessThan = function(expression, text) { /** * the less than assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var result = this.extEval(expression) if (result >= text) { Assert.fail("the value of [" + result + "] " + expression + " is not less than " + text); } } Selenium.prototype.assertExtLessEqualThan = function(expression, text) { /** * the less and equal than assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var result = this.extEval(expression) if (result > text) { Assert.fail("the value of [" + result + "] " + expression + " is not less equal than " + text); } } Selenium.prototype.doExecuteExtFunction = function(expression, text) { /** * do ext function ,if the expression end with ")" ,the params is not useful * @param expression ext expression return a ext function, just like "button1.getText" or "text1.getValue()" * @param String params ,just like "a,b,c" */ if (expression.lastIndexOf(")") == expression.length - 1) { this.extEval(expression); } else { var scopeObj = this.extEval(expression.substring(0, expression .lastIndexOf("."))); var func = this.extEval(expression); if (typeof(func) != "function") { Assert.fail("the value of [" + func + "] " + expression + " is not a function"); } var params = []; if (text) { params = text.split(","); } try { func.apply(scopeObj, params); } catch (e) { Assert.fail("error execute function [" + func + "] " + expression); } } } Selenium.prototype.assertExtTrue = function(expression) { /** * the true assertion of ext * @param expression ext expression , just like "button1.hidden" */ var result = this.extEval(expression); if (result !== true) { Assert.fail("the value of [" + result + "] " + expression + " is not true"); } } Selenium.prototype.assertExtFalse = function(expression) { /** * the false assertion of ext * @param expression ext expression , just like "button1.hidden" */ var result = this.extEval(expression); if (result !== true) { Assert.fail("the value of [" + result + "] " + expression + " is not false"); } } Selenium.prototype.assertExtNull = function(expression, text) { /** * the null assertion of ext * @param expression ext expression , just like "button1.text" */ var result = this.extEval(expression); if (result !== null) { Assert.fail("the value of [" + result + "] " + expression + " is not null"); } } Selenium.prototype.assertExtNotNull = function(expression, text) { /** * the not null assertion of ext * @param expression ext expression , just like "button1.text" */ var result = this.extEval(expression); if (result === null) { Assert.fail("the value of [" + result + "] " + expression + " is null"); } } Selenium.prototype.assertExtUndefined = function(expression, text) { /** * the undefined assertion of ext * @param expression ext expression , just like "button1" */ var result = this.extEval(expression); if (result !== undefined) { Assert.fail("the value of [" + result + "] " + expression + " is not undefined"); } } Selenium.prototype.assertExtNotUndefined = function(expression, text) { /** * the not undefined assertion of ext * @param expression ext expression , just like "button1" */ var result = this.extEval(expression); if (result === undefined) { Assert.fail("the value of [" + result + "] " + expression + " is undefined"); } } Selenium.prototype.assertExtPresent = function(expression, text) { /** * the present assertion of ext * @param expression ext expression , just like "button1" */ var result = this.extEval(expression); if (result == null || result == undefined) { Assert.fail("the value of [" + result + "] " + expression + " is not present"); } } Selenium.prototype.assertExtNotPresent = function(expression, text) { /** * the not present assertion of ext * @param expression ext expression , just like "button1" */ var result = this.extEval(expression); if (result != null || result != undefined) { Assert.fail("the value of [" + result + "] " + expression + " is present"); } } Selenium.prototype.assertExtMatches = function(expression, text) { /** * the matches assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var result = this.extEval(expression); var reg = new RegExp(text); if (!reg.test(result)) { Assert.fail("the value of [" + result + "] " + expression + " is not match " + text); } } Selenium.prototype.assertExtContains = function(expression, text) { /** * the contains assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var result = this.extEval(expression); if (typeof(result) == "undefined" || result == null) { Assert.fail("the value of " + expression + " dos not contains " + text); } else if (result.indexOf) { if (result.indexOf(text) < 0) { Assert.fail("the value of [" + result + "] " + expression + " dos not contains " + text); } } else { Assert.fail("the value of [" + result + "] " + expression + " is not a String or Array"); } } Selenium.prototype.assertExtTypeof = function(expression, text) { /** * the typeof assertion of ext * @param expression ext expression , just like "button1.text" or "text1.getValue()" * @param String target value */ var type = typeof(this.extEval(expression)); if (type != text) { Assert.fail("the type of [" + type + "] " + expression + " is not " + text); } } PageBot.prototype.getWrappedWindow = function(extpath) { var win = this.getCurrentWindow() || {}; return win.wrappedJSObject; } Selenium.prototype.getWrappedWindow = function(extpath) { return this.browserbot.getWrappedWindow(); } Selenium.prototype.extEval = function(expression) { var script = expression; if (expression) { var expArr = expression.split("."); expArr[0] = "(window.Ext.getCmp('" + expArr[0] + "')||window.Ext.get('" + expArr[0] + "')||window.Ext.StoreMgr.lookup('" + expArr[0] + "'))"; expression = expArr.join("."); } try { return this.doEval(expression); } catch (e) { throw new SeleniumError("the expression " + script + " is not a Ext expression !"); } }; // I have to rewrite the eval function to get the context of window Selenium.prototype.doEval = function(expression) { /** * execute js ecpression * * @param {Object} * expression js expression */ try { var win = this.getWrappedWindow(); var result = eval(expression, win); return result; } catch (e) { throw new SeleniumError("the expression " + expression + " is not a Ext expression !"); } }
There are now enough assertions to test ExtJS.
We can now write a assert command like this:
| command | assertExtEqual |
| target | button1.getText() |
| value | button1 |
In a word, we can record and assert ExtJS applications using the above extensions.

Comments (5)
May 17, 2010
alex knol says:
Super, is there a chance you can tell me where and how I put the first extension...Super, is there a chance you can tell me where and how I put the first extension.
Maybe we can complete this page into a complete howto.
Jul 20, 2010
spoona says:
Brilliant post... Keep on going..Brilliant post... Keep on going..
Jan 07, 2011
Joel Ransom says:
I'm surprise there's not more activity here considering how widespread this prob...I'm surprise there's not more activity here considering how widespread this problem is. This (the first example) seems to work wonderfully. I would like to understand a little better exactly when it runs, are there any performance considerations, etc. I don't know much about extensions to Selenium.
Apr 28, 2011
Ethan Thomason says:
Alex, I found this blurb on "Contributed Extensions and Formats" main page: To ...Alex, I found this blurb on "Contributed Extensions and Formats" main page:
To use the extensions:
Apr 29, 2011
Jitendra Singh says:
Hi Olle, thanks for such a wonderful post!!Hi Olle, thanks for such a wonderful post!!