我们一直在使用 selenium 成功地处理高级网站测试(除了模块级别的大量 python 文档测试)。然而,现在我们在很多页面上都使用 extjs,事实证明很难将 Selenium 测试合并到像网格这样的复杂组件中。
有没有人成功地为基于 extjs 的网页编写自动化测试?许多谷歌搜索发现有类似问题的人,但答案很少。谢谢!
我们一直在使用 selenium 成功地处理高级网站测试(除了模块级别的大量 python 文档测试)。然而,现在我们在很多页面上都使用 extjs,事实证明很难将 Selenium 测试合并到像网格这样的复杂组件中。
有没有人成功地为基于 extjs 的网页编写自动化测试?许多谷歌搜索发现有类似问题的人,但答案很少。谢谢!
用 Selenium 测试 ExtJS 的最大障碍是 ExtJS 不呈现标准的 HTML 元素,而且 Selenium IDE 会天真地(并且正确地)生成针对仅作为装饰的元素的命令 - 帮助 ExtJS 处理整个桌面的多余元素 -外观和感觉。以下是我在针对 ExtJS 应用程序编写自动化 Selenium 测试时收集的一些提示和技巧。
通过在 Firefox 上使用 Selenium IDE 记录用户操作来生成 Selenium 测试用例时,Selenium 将根据 HTML 元素的 id 记录操作。然而,对于大多数可点击元素,ExtJS 使用生成的 id,例如“ext-gen-345”,即使没有对代码进行任何更改,它们也可能在随后访问同一页面时发生变化。在为测试记录用户操作后,需要手动完成所有依赖于生成的 id 的操作并替换它们。可以进行两种类型的替换:
CSS 定位器以“css=”开头,XPath 定位器以“//”开头(“xpath=”前缀是可选的)。CSS 定位器不那么冗长,更易于阅读,应该优于 XPath 定位器。但是,在某些情况下需要使用 XPath 定位器,因为 CSS 定位器根本无法解决它。
由于 ExtJS 执行的复杂渲染,一些元素需要的不仅仅是简单的鼠标/键盘交互。例如,Ext.form.CombBox 并不是一个真正的<select>
元素,而是一个带有分离下拉列表的文本输入,它位于文档树的底部。为了正确模拟 ComboBox 选择,可以先模拟单击下拉箭头,然后单击出现的列表。但是,通过 CSS 或 XPath 定位器定位这些元素可能很麻烦。另一种方法是定位 ComoBox 组件本身并调用其上的方法来模拟选择:
var combo = Ext.getCmp('genderComboBox'); // returns the ComboBox components
combo.setValue('female'); // set the value
combo.fireEvent('select'); // because setValue() doesn't trigger the event
在 Selenium 中,该runScript
命令可用于以更简洁的形式执行上述操作:
with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }
当用户操作导致页面转换或重新加载时,Selenium 对所有等待页面加载的命令都有“*AndWait”风格。但是,由于 AJAX 提取不涉及实际的页面加载,因此这些命令不能用于同步。解决方案是利用视觉线索,例如 AJAX 进度指示器的存在/不存在或网格中行的外观、附加组件、链接等。例如:
Command: waitForElementNotPresent
Target: css=div:contains('Loading...')
有时一个元素只会在一定时间后出现,这取决于用户操作导致视图更改后 ExtJS 渲染组件的速度。与其在pause
命令中使用任意延迟,理想的方法是等到感兴趣的元素进入我们的掌握范围内。例如,在等待某个项目出现后单击它:
Command: waitForElementPresent
Target: css=span:contains('Do the funky thing')
Command: click
Target: css=span:contains('Do the funky thing')
依赖任意暂停并不是一个好主意,因为在不同浏览器或不同机器上运行测试导致的时间差异会使测试用例变得不稳定。
click
该命令无法触发某些元素。这是因为事件侦听器实际上在容器上,在其子元素上监视鼠标事件,最终会冒泡到父元素。选项卡控件就是一个示例。要单击选项卡,您必须mouseDown
在选项卡标签处模拟一个事件:
Command: mouseDownAt
Target: css=.x-tab-strip-text:contains('Options')
Value: 0,0
表单字段(Ext.form.* 组件)关联了正则表达式或vtypes进行验证validationDelay
,会在用户输入文本后或字段丢失时立即触发验证,并有一定的延迟(参见默认设置为250ms的属性)焦点——或模糊(见validateOnDelay
属性)。为了在发出 type Selenium 命令以在字段中输入一些文本后触发字段验证,您必须执行以下任一操作:
触发延迟验证
当字段接收到 keyup 事件时,ExtJS 会触发验证延迟计时器。要触发这个计时器,只需发出一个虚拟的 keyup 事件(不管你使用哪个键,因为 ExtJS 会忽略它),然后是一个比validationDelay 更长的短暂暂停:
Command: keyUp
Target: someTextArea
Value: x
Command: pause
Target: 500
触发立即验证
您可以在字段中注入模糊事件以触发立即验证:
Command: runScript
Target: someComponent.nameTextField.fireEvent("blur")
验证后,您可以检查是否存在错误字段:
Command: verifyElementNotPresent
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]
Command: verifyElementPresent
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]
请注意,“显示:无”检查是必要的,因为一旦显示错误字段然后需要隐藏它,ExtJS 将简单地隐藏错误字段而不是将其从 DOM 树中完全删除。
选项1
命令:点击目标:css=button:contains('Save')
按标题选择按钮
选项 2
命令:单击目标:css=#save-options 按钮
按 id 选择按钮
Command: runScript
Target: with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }
首先设置值,然后在有观察者的情况下显式触发 select 事件。
我一直在用 selenium 测试我的 ExtJs Web 应用程序。最大的问题之一是在网格中选择一个项目以便对其进行处理。
为此,我编写了辅助方法(在 SeleniumExtJsUtils 类中,它是一组有用的方法,可以更轻松地与 ExtJs 交互):
/**
* Javascript needed to execute in order to select row in the grid
*
* @param gridId Grid id
* @param rowIndex Index of the row to select
* @return Javascript to select row
*/
public static String selectGridRow(String gridId, int rowIndex) {
return "Ext.getCmp('" + gridId + "').getSelectionModel().selectRow(" + rowIndex + ", true)";
}
当我需要选择一行时,我只需调用:
selenium.runScript( SeleniumExtJsUtils.selectGridRow("<myGridId>", 5) );
为此,我需要在网格上设置我的 id,而不是让 ExtJs 生成它自己的。
要检测该元素是否可见,请使用以下子句:
not(contains(@style, "display: none")
最好使用这个:
visible_clause = "not(ancestor::*[contains(@style,'display: none')" +
" or contains(@style, 'visibility: hidden') " +
" or contains(@class,'x-hide-display')])"
hidden_clause = "parent::*[contains(@style,'display: none')" +
" or contains(@style, 'visibility: hidden')" +
" or contains(@class,'x-hide-display')]"
您能否更深入地了解您在 extjs 测试中遇到的问题类型?
我觉得有用的一个 Selenium 扩展是waitForCondition。如果您的问题似乎与 Ajax 事件有关,您可以使用 waitForCondition 等待事件发生。
Ext JS 网页可能很难测试,因为它们最终生成的 HTML 很复杂,就像使用 Ext JS 网格一样。
HTML5 Robot通过使用一系列最佳实践来处理这个问题,这些最佳实践如何基于非动态的属性和条件可靠地查找组件并与之交互。然后,它提供了使用您需要与之交互的所有 HTML、Ext JS 和 Sencha Touch 组件执行此操作的快捷方式。它有 2 种口味:
例如,如果您想查找包含文本“Foo”的 Ext JS 网格行,您可以在 Java 中执行以下操作:
findExtJsGridRow("Foo");
...您可以在 Gwen 中执行以下操作:
extjsgridrow by text "Foo"
关于如何使用 Ext JS 特定组件,有很多关于Java和 Gwen 的文档。该文档还详细介绍了所有这些 Ext JS 组件的生成 HTML,您也可能会发现这些组件很有用。
通过页面上的网格 ID 获取网格的有用提示:我认为您可以从此 API 扩展更多有用的功能。
sub get_grid_row {
my ($browser, $grid, $row) = @_;
my $script = "var doc = this.browserbot.getCurrentWindow().document;\n" .
"var grid = doc.getElementById('$grid');\n" .
"var table = grid.getElementsByTagName('table');\n" .
"var result = '';\n" .
"var row = 0;\n" .
"for (var i = 0; i < table.length; i++) {\n" .
" if (table[i].className == 'x-grid3-row-table') {\n".
" row++;\n" .
" if (row == $row) {\n" .
" var cols_len = table[i].rows[0].cells.length;\n" .
" for (var j = 0; j < cols_len; j++) {\n" .
" var cell = table[i].rows[0].cells[j];\n" .
" if (result.length == 0) {\n" .
" result = getText(cell);\n" .
" } else { \n" .
" result += '|' + getText(cell);\n" .
" }\n" .
" }\n" .
" }\n" .
" }\n" .
"}\n" .
"result;\n";
my $result = $browser->get_eval($script);
my @res = split('\|', $result);
return @res;
}
当没有可用的对象引用时,itemId 可用作获取对组件的引用的替代方法。不要使用带有 Ext.getCmp 的 id,而是使用带有 Ext.container.Container.getComponent 的 itemId,它将检索 itemId 或 id。由于 itemId 是容器内部 MixedCollection 的索引,因此 itemId 的范围仅限于容器本地——避免与需要唯一 id 的 Ext.ComponentManager 的潜在冲突。
覆盖Ext.AbstractComponent
'sonBoxReady
方法,如果存在,我将自定义数据属性(其名称来自testIdAttr
每个组件的自定义属性)设置为组件的itemId
值。将Testing.overrides.AbstractComponent
类添加到application.js
文件的requires
数组中。
/**
* Overrides the Ext.AbstracComponent's onBoxReady
* method to add custom data attributes to the
* component's dom structure.
*
* @author Brian Wendt
*/
Ext.define('Testing.overrides.AbstractComponent', {
override: 'Ext.AbstractComponent',
onBoxReady: function () {
var me = this,
el = me.getEl();
if (el && el.dom && me.itemId) {
el.dom.setAttribute(me.testIdAttr || 'data-selenium-id', me.itemId);
}
me.callOverridden(arguments);
}
});
此方法为开发人员提供了一种在其代码中重用描述性标识符并在每次呈现页面时都可以使用这些标识符的方法。不再搜索非描述性的、动态生成的 ID。
我们正在开发一个使用 selenium 的测试框架,并遇到了 extjs 的问题(因为它是客户端渲染)。我发现在 DOM 准备好后查找元素很有用。
public static boolean waitUntilDOMIsReady(WebDriver driver) {
def maxSeconds = DEFAULT_WAIT_SECONDS * 10
for (count in 1..maxSeconds) {
Thread.sleep(100)
def ready = isDOMReady(driver);
if (ready) {
break;
}
}
}
public static boolean isDOMReady(WebDriver driver){
return driver.executeScript("return document.readyState");
}
对于不是正式 HTML 的复杂 UI,xPath 始终是您可以信赖的东西,但在使用 ExtJs 实现不同的 UI 时会有点复杂。
您可以使用 Firebug 和 Firexpath 作为 firefox 扩展来测试某个元素的 xpath,并将完整的 xpath 作为参数简单地传递给 selenium。
例如在java代码中:
String fullXpath = "xpath=//div[@id='mainDiv']//div[contains(@class,'x-grid-row')]//table/tbody/tr[1]/td[1]//button"
selenium.click(fullXpath);
当我使用 WebDriver 测试 ExtJS 应用程序时,我使用了下一种方法:我通过标签的文本查找字段并@for
从标签中获取属性。例如,我们有一个标签
<label id="dynamic_id_label" class="TextboxLabel" for="textField_which_I_am_lloking_for">
Name Of Needed Label
<label/>
我们需要给 WebDriver 一些输入://input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)]
.
因此,它将从@for
属性中选择 id 并进一步使用它。这可能是最简单的情况,但它为您提供了定位元素的方法。当你没有标签时会更难,但是你需要找到一些元素并编写你的 xpath 来寻找兄弟姐妹,下降/上升元素。