如何测试Web应用程序

Squish的Web特定API使您能够查找和查询对象,访问属性和方法,并在Web应用程序的上下文中评估任意JavaScript代码

此外,Web便捷API 提供了在网站上执行常见操作的功能,例如点击按钮或输入某些文本。

有关如何使用脚本化Web API访问和测试复杂网页元素的示例,请参阅 如何测试网页元素

注意:与Web应用程序交互有两种方式:直接或使用Web代理机制。对于在macOS上的Safari、Windows上的Microsoft Internet Explorer或Unix上的Firefox测试Web应用程序,最好使用代理机制,因为该机制会施加一些限制。有关如何使用Web代理机制的更多信息,请参阅 Web代理

如何查找和查询Web对象

Squish提供了两个函数——Object findObject(objectName)Object waitForObject(objectOrName)——它们返回一个对对象的引用(HTML或DOM元素),对于一个给定合格的对象名。它们之间的区别在于 Object waitForObject(objectOrName) 等待对象变为可用(直至其默认超时,或直至指定超时),因此通常是最方便的一个使用。然而,只有 Object findObject(objectName) 可以用于隐藏对象。

请参阅 Web对象API 以获取Squish的Web类和方法的详细信息。

有几种方法可以识别特定的Web对象

  • 多属性(真实)名——这些名称由一个或多个属性名/属性值对列表组成,如果有多个,则由空格分隔,整个名称用大括号包围。对于这种类型的名称,Squish将搜索文档的DOM树,直到找到匹配的对象。这种名称的一个例子是:"{tagName='INPUT' id='r1' name='rg' form='myform' type='radio' value='Radio 1'} "。
  • 单个属性值——给定一个特定的值,Squish会搜索文档的DOM树,直到找到具有指定值的idnameinnerText属性的元素。
  • 路径——给出元素的完整路径。这种路径的一个例子是 "DOCUMENT.HTML1.BODY1.FORM1.SELECT1"。

要找到对象名,可以使用Spy检查Web应用程序的文档。有关使用Spy的详细信息,请参阅如何使用Spy部分。

如果我们想要与特定对象交互——例如,检查其属性,或者对它进行某些操作,比如点击它,我们必须首先获取该对象的引用。

如果我们使用 Object findObject(objectName) 函数,它将立即返回对象,或者在对象不可用时会抛出一个可捕获的异常。(对象可能不可用,因为它是AJAX对象,只在某些条件下出现,或者它可能只作为某些JavaScript代码执行的结果出现,等等。)以下是一个代码片段,演示如何使用 Object findObject(objectName),同时避免因错误而抛出异常,通过使用 Boolean object.exists(objectName) 函数

radioName = ("{tagName='INPUT' id='r1' name='rg' form='myform' " +
        "type='radio' value='Radio 1'}")
if object.exists(radioName):
    radioButton = findObject(radioName)
    clickButton(radioButton)
var radioName = "{tagName='INPUT' id='r1' name='rg' form='myform' " +
        "type='radio' value='Radio 1'}";
if (object.exists(radioName)) {
    var radioButton = findObject(radioName);
    clickButton(radioButton);
}
my $radioName = "{tagName='INPUT' id='r1' name='rg' form='myform' " .
        "type='radio' value='Radio 1'}"
if (object::exists($radioName)) {
    my $radioButton = findObject($radioName);
    clickButton($radioButton);
}
radioName = "{tagName='INPUT' id='r1' name='rg' form='myform' " +
        "type='radio' value='Radio 1'}"
if Squish::Object.exists(radioName)
    radioButton = findObject(radioName)
    clickButton(radioButton)
end
set radioName {{tagName='INPUT' id='r1' name='rg' form='myform' \
        type='radio' value='Radio 1'}}
if {[object exists $radioName]} {
    set radioButton [findObject $radioName]
    invoke clickButton $radioButton
}

只有当它存在时,即当在调用布尔对象.exists(objectName)时它可访问时,才会点击单选按钮。

另一种方法是用Object waitForObject(objectOrName)函数

radioButton = waitForObject("{tagName='INPUT' id='r1' name='rg' " +
        "form='myform' type='radio' value='Radio 1'}")
clickButton(radioButton)
var radioButton = waitForObject("{tagName='INPUT' id='r1' name='rg' " +
        "form='myform' type='radio' value='Radio 1'}");
clickButton(radioButton);
my $radioButton = waitForObject("{tagName='INPUT' id='r1' name='rg' " .
        "form='myform' type='radio' value='Radio 1'}");
clickButton($radioButton);
radioButton = waitForObject("{tagName='INPUT' id='r1' name='rg' " +
        "form='myform' type='radio' value='Radio 1'}")
clickButton(radioButton)
set radioButton [waitForObject {{tagName='INPUT' id='r1' name='rg' \
        form='myform' type='radio' value='Radio 1'}}]
invoke clickButton $radioButton

这将等待最多20秒(或已设置的默认超时时间),如果在此时间内的单选按钮变为可访问,则对其进行点击。

使用Object findObject(objectName)Object waitForObject(objectOrName)函数结合适当的对象标识符意味着我们可以访问Web文档对象树中的所有元素,测试它们的属性,通常与它们交互。

如何使用XPath

对于由Object findObject(objectName)Object waitForObject(objectOrName)函数返回的每个对象,都可以使用XPath语句进行评估。评估XPath语句的对象被用作上下文节点。

例如,要检索指向URLwww.froglogic.com的链接的引用,它是具有ID "mydiv"的DIV元素的子代,我们可以使用以下代码

div = findObject("{tagName='DIV' id='mydiv'}")
link = div.evaluateXPath("A[contains(@href," +
        "'www.froglogic.com')]").snapshotItem(0)
var div = findObject("{tagName='DIV' id='mydiv'}");
var link = div.evaluateXPath("A[contains(@href," +
        "'www.froglogic.com')]").snapshotItem(0);
my $div = findObject("{tagName='DIV' id='mydiv'}");
my $link = $div->evaluateXPath("A[contains(@href," .
        "'www.froglogic.com')]")->snapshotItem(0);
div = findObject("{tagName='DIV' id='mydiv'}")
link = div.evaluateXPath("A[contains(@href," +
        "'www.froglogic.com')]").snapshotItem(0)
set div [findObject {{tagName='DIV' id='mydiv'}}]
set link [invoke [invoke $div evaluateXPath \
        "A[contains(@href, 'www.froglogic.com')]"] snapshotItem 0]

此处使用的XPath表示,“查找所有具有href属性的A(锚点)标签,其值为www.froglogic.com”。然后我们调用snapshotItem方法,并要求它检索第一个匹配项——它使用基于0的索引,并返回一个类型为HTML_Object类的对象。

每个XPath查询可以产生一个布尔值(true或false)、一个数字、一个字符串或一组元素作为结果。因此,HTML_XPathResult HTML_Object.evaluateXPath(statement)方法返回一个类型为HTML_XPathResult类的对象,你可以对其查询XPath评估的结果。

如何访问表格单元格内容提供了使用HTML_XPathResult HTML_Object.evaluateXPath(statement)方法提取HTML表单元格内容的示例。

有关如何创建XPath查询以帮助生成灵活且紧凑的测试脚本的更多信息,请参阅XPath教程,这是从W3Schools在线网络教程网站提供的。

另请参阅Squish for Web教程插入附加验证点

如何访问Web对象属性

使用脚本API,可以访问Web应用程序中任何HTML或DOM元素的DOM属性的多数。有关Squish的Web类和方法的详细信息,请参阅Web对象API

以下是一个示例,我们将更改和查询表单文本元素的value属性。

entry = waitForObject(
        "{tagName='INPUT' id='input' form='myform' type='text'}")
entry.value = "Some new text"
test.log(entry.value)
var entry = waitForObject(
        "{tagName='INPUT' id='input' form='myform' type='text'}");
entry.value = "Some new text";
test.log(entry.value);
my $entry = waitForObject(
        "{tagName='INPUT' id='input' form='myform' type='text'}");
$entry->value = "Some new text";
test::log($entry->value);
entry = waitForObject(
        "{tagName='INPUT' id='input' form='myform' type='text'}")
entry.value = "Some new text"
Test.log(entry.value)
set entry [waitForObject {{tagName='INPUT' id='input' \
        form='myform' type='text'}}]
[property set $entry value "Some new text"]
test log [property get $entry value]

Squish为所有标准DOM元素的标准属性提供类似的脚本绑定。但是,您也可以使用property方法访问自定义对象的属性。例如,为了检查DIV元素的偏移宽度,我们可以编写如下代码

div = findObject("DOCUMENT.HTML1.BODY1......DIV")
test.compare(div.property("offsetWidth"), 18)
var div = findObject("DOCUMENT.HTML1.BODY1......DIV");
test.compare(div.property("offsetWidth"), 18);
my $div = findObject("DOCUMENT.HTML1.BODY1......DIV");
test::compare($div->property("offsetWidth"), 18);
div = findObject("DOCUMENT.HTML1.BODY1......DIV")
Test.compare(div.property("offsetWidth"), 18)
set div [findObject "DOCUMENT.HTML1.BODY1......DIV"]
test compare [invoke $div property "offsetWidth"] 18

注意,对于隐藏元素,我们必须始终使用Object findObject(objectName)函数而不是Object waitForObject(objectOrName)函数。

如何调用Web对象函数

除了属性外,您还可以在Web对象上调用标准DOM函数,这可以通过在测试脚本中使用 Web对象API 中描述的API来实现。

例如,要获取DIV元素的第一个子节点,您可以使用以下测试脚本,它使用了 HTML_Object.firstChild() 函数

div = findObject("DOCUMENT.HTML1.BODY1......DIV")
child = div.firstChild()
test.log(child.tagName)
var div = findObject("DOCUMENT.HTML1.BODY1......DIV");
var child = div.firstChild();
test.log(child.tagName);
my $div = findObject("DOCUMENT.HTML1.BODY1......DIV");
my $child = $div->firstChild();
test::log($child->tagName);
div = findObject("DOCUMENT.HTML1.BODY1......DIV")
child = div.firstChild()
Test.log(child.tagName)
set div [findObject "DOCUMENT.HTML1.BODY1......DIV"]
set child [invoke $div firstChild]
test log [property get $child tagName]

或者,要获取选择表单元素中选定选项的文本,我们可以使用以下代码

element = findObject(
        ":{tagName='INPUT' id='sel' form='myform' type='select-one'}")
option = element.optionAt(element.selectedIndex)
test.log(option.text)
var element = findObject(
        ":{tagName='INPUT' id='sel' form='myform' type='select-one'}");
var option = element.optionAt(element.selectedIndex);
test.log(option.text);
my $element = findObject(
        ":{tagName='INPUT' id='sel' form='myform' type='select-one'}");
my $option = $element->optionAt($element->selectedIndex);
test::log($option->text);
element = findObject(
        ":{tagName='INPUT' id='sel' form='myform' type='select-one'}")
option = element.optionAt(element.selectedIndex)
Test.log(option.text)
set element [findObject ":{tagName='INPUT' id='sel' \
        form='myform' type='select-one'}"]
set option [invoke $element optionAt [property get element selectedIndex]]
test log [property get $option text]

Squish提供了类似于这里所示的脚本绑定到所有标准DOM元素的标准函数。此外,还可以通过通用的 invoke 函数调用自定义函数。例如,要在DIV元素上调用具有字符串参数“an argument”的自定义 customFunction 函数,我们可以编写如下代码

div = findObject("DOCUMENT.HTML1.BODY1......DIV")
div.invoke("customFunction", "an argument")
var div = findObject("DOCUMENT.HTML1.BODY1......DIV");
div.invoke("customFunction", "an argument");
my $div = findObject("DOCUMENT.HTML1.BODY1......DIV");
$div->invoke("customFunction", "an argument");
div = findObject("DOCUMENT.HTML1.BODY1......DIV")
div.invoke("customFunction", "an argument")
set div [findObject "DOCUMENT.HTML1.BODY1......DIV"]
invoke $div "customFunction" "an argument"

除了DOM API绑定和invoke函数外,Squish还提供了一个 Browser 对象,测试脚本可以使用该对象查询所使用的浏览器,如下面的Python示例所示

# This will print out the name of the browser:
test.log("We are running in " + Browser.name())
if Browser.id() == InternetExplorer:
    ...
elif Browser.id() == Mozilla:
    ...
elif Browser.id() == Firefox:
    ...
elif Browser.id() == Safari:
    ...

如何使用evalJS

除了测试脚本可以访问所有DOM元素的属性和方法外,还可以让Squish在Web浏览器的JavaScript解释器中执行任意JavaScript代码并检索其结果。为此,Squish提供了 Object evalJS(browserTab, code) 函数。以下是如何使用它的示例

style_display = evalJS("var d = document.getElementById(" +
        "'busyDIV'); d ? d.style.display : ''")
var style_display = evalJS("var d = document.getElementById(" +
        "'busyDIV'); d ? d.style.display : ''");
my $style_display = evalJS("var d = document.getElementById(" .
        "'busyDIV'); d ? d.style.display : ''");
style_display = evalJS("var d = document.getElementById(" +
        "'busyDIV'); d ? d.style.display : ''")
set style_display [invoke evalJS "var d = document.getElementById(\
        'busyDIV'); d ? d.style.display : ''"]

Object evalJS(browserTab, code) 函数返回最后执行的语句的结果——在这个例子中是最后一个语句 d ? d.style.display : '',因此如果文档包含一个ID为“busyDIV”的元素,style_display将设置为该元素的style.display属性值;否则将设置为空字符串。

如何使用retrieveJSObject

除了测试脚本可以运行某些JavaScript代码片段并检索结果字符串外,Squish还可以从Web浏览器的解释器中检索实际JavaScript对象的引用。这在JavaScript函数不返回简单的值,如字符串或数字,而是返回对象本身的情况下非常有用。在这种情况下,引用允许从该对象获取属性或在该对象上调用方法。为此,Squish提供了 JsObject retrieveJSObject(javascriptcode) 函数。以下是如何使用它的示例

jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;")
var jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;");
my $jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;");
jsobject = retrieveJSObject("var globalObject = { 'id': 'obj1', 'name': function() { return 'name1'; } };globalObject;")
set jsobject [invoke retrieveJSObject {var globalObject = \{ 'id': 'obj1', 'name': function() \{ return 'name1'; \} \};globalObject;\} }]

JsObject retrieveJSObject(javascriptcode) 函数返回最后执行的语句的结果——在这个例子中是最后一个语句 globalObject;,因此返回了刚刚创建的globalObject的引用。现在可以检索该对象的id属性,或调用name函数。以下是如何在测试结果中记录这两个示例的示例

test.log("id: " + jsobject.property("id"))
test.log("name: " + jsobject.call("name"))
test.log("id: " + jsobject.property("id"));
test.log("name: " + jsobject.call("name"));
test::log("id: " . $jsobject->property("id"));
test::log("name: " . $jsobject->call("name"));
test.log("id: " + jsobject.property("id"))
test.log("name: " + jsobject.call("name"))
test log "id: " [invoke $jsobject property "id"]
test log "name: " [invoke $jsobject call "name"]

如何使用Web便捷API

本节描述了Squish在DOM API之上提供的脚本API,以便轻松执行常见用户操作,例如点击链接、输入文本等。API提供的所有函数均列在工具参考中的Web对象API部分。在这里,我们将展示一些示例来说明如何使用API。

以下示例展示如何点击链接、选择选项和输入文本。

clickLink(":{tagName='A' innerText='Advanced Search'}")
selectOption(":{tagName='INPUT' id='sel' form='myform' " +
        "type='select-one'}", "Banana")
setText(":{tagName='INPUT' id='input' form='myform' type='text'}",
        "Some Text")
clickLink(":{tagName='A' innerText='Advanced Search'}");
selectOption(":{tagName='INPUT' id='sel' form='myform' " +
        "type='select-one'}", "Banana");
setText(":{tagName='INPUT' id='input' form='myform' type='text'}",
        "Some Text");
clickLink(":{tagName='A' innerText='Advanced Search'}");
selectOption(":{tagName='INPUT' id='sel' form='myform' " .
        "type='select-one'}", "Banana");
setText(":{tagName='INPUT' id='input' form='myform' type='text'}",
        "Some Text");
clickLink(":{tagName='A' innerText='Advanced Search'}")
selectOption(":{tagName='INPUT' id='sel' form='myform' " +
        "type='select-one'}", "Banana")
setText(":{tagName='INPUT' id='input' form='myform' type='text'}",
        "Some Text")
invoke clickLink ":{tagName='A' innerText='Advanced Search'}"
invoke selectOption ":{tagName='INPUT' id='sel' form='myform' \
        type='select-one'}" "Banana"
invoke setText ":{tagName='INPUT' id='input' form='myform' \
        type='text'}" "Some Text"

在这些情况下,我们使用真实(多属性)名称识别了对象;我们也可以轻松地使用符号名称,甚至对象引用。请注意,完整的API比这里提到的三个函数(clickLink(objectOrName)selectOption(objectOrName, text)setText(objectOrName, text))包含更多的功能,尽管它们的用法同样简单。

如何同步网页加载以进行测试

在许多简单情况下,只需使用waitForObject(objectOrName)函数等待特定对象可用即可。

然而,在某些情况下,我们需要在尝试访问页面对象之前确保页面已加载。特殊的isPageLoaded(browserTab)函数使得将测试脚本与Web应用程序的页面加载状态同步成为可能。

我们可以使用此函数等待网页完全加载后再点击页面上的特定按钮。例如,如果页面有一个登录按钮,我们可以在尝试点击按钮之前确保页面已加载,使用以下代码

loaded = waitFor("isPageLoaded()", 5000)
if loaded:
    clickButton(waitForObject(
        ":{tagName='INPUT' type='button' value='Login'}"))
else:
    test.fatal("Page loading failed")
var loaded = waitFor("isPageLoaded()", 5000);
if (loaded)
    clickButton(waitForObject(
        ":{tagName='INPUT' type='button' value='Login'}"));
else
    test.fatal("Page loading failed");
my $loaded = waitFor("isPageLoaded()", 5000);
if ($loaded) {
    clickButton(waitForObject(
        ":{tagName='INPUT' type='button' value='Login'}"));
}
else {
    test::fatal("Page loading failed");
}
loaded = waitFor("isPageLoaded", 5000)
if loaded
    clickButton(waitForObject(
        ":{tagName='INPUT' type='button' value='Login'}"))
else
    Test.fatal("Page loading failed")
end
set loaded [waitFor {invoke isPageLoaded} 5000]
if {$loaded} {
    invoke clickButton [invoke waitForObject \
        ":{tagName='INPUT' type='button' value='Login'}"]
} else {
    test fatal "Page loading failed"
}

必须使用isPageLoaded(browserTab)函数确保页面已加载并且它的Web对象可能是可访问的。为了访问特定的对象,我们仍然必须使用waitForObject(objectOrName)函数——我们甚至可能需要指定比默认20,000毫秒更长的超时时间,以允许网络延迟。

如何测试Web元素

在这个部分,我们将介绍如何测试Web应用程序中的特定HTML元素。这将使我们能够验证元素具有我们期望的属性值,以及表单元素具有期望的内容。

测试中的一个方面可能会相当具有挑战性,那就是创建测试验证。如教程:开始测试Web应用程序中的教程:开始测试Web应用程序部分所示,这些中的大部分可以通过Spy及其点播界面对象完成。但在某些情况下,直接在代码中实现验证点实际上更为方便且更灵活。

为了在代码中测试和验证小部件及其属性或内容,我们首先需要在测试脚本中访问该小部件。要通过waitForObject(objectOrName)函数获取小部件的引用。此函数寻找具有给定名称的小部件并返回其引用。为此,我们需要知道我们想要测试的小部件的名称,并且我们可以使用Spy工具(见如何使用Spy)将其添加到对象映射中(以便Squish记住它)并将对象名称(最好是符号名)复制到剪贴板准备好粘贴到我们的测试中。如果我们需要收集大量小部件的名称,录制一个虚拟测试可能更快更容易,在该测试中我们确保我们手动编写的测试脚本中访问了我们想要验证的所有小部件。这将导致Squish将所有相关名称添加到对象映射,然后我们可以将其复制并粘贴到我们的代码中。

如何测试Web元素的当前状态

最常见的测试需求之一是在测试运行过程中验证特定元素是否在某一时刻启用或禁用。通过检查元素的disabled属性可以轻松地进行此验证。

entry = waitForObject("{tagName='INPUT' id='input' " +
        "form='myform' type='text'}")
test.verify(not entry.disabled)
var entry = waitForObject("{tagName='INPUT' id='input' " +
        "form='myform' type='text'}");
test.verify(!entry.disabled);
my $entry = waitForObject("{tagName='INPUT' id='input' " .
        "form='myform' type='text'}");
test::verify(!$entry->disabled);
entry = waitForObject("{tagName='INPUT' id='input' " +
        "form='myform' type='text'}")
Test.verify(!entry.disabled)
set entry [waitForObject "{tagName='INPUT' id='input' \
        form='myform' type='text'}"]
test verify [expr ![property get $entry disabled]]

在此,我们已经验证了文本输入元素已经启用(即其disabled属性为false)。要验证该元素已被禁用,我们需要消除否定(根据语言可能为not!)。

表单复选框和单选按钮

要验证单选按钮或复选框是否被选中,我们只需查询其checked属性。

radiobutton = waitForObject(":{tagName='INPUT' id='r1' name='rg' " +
        "form='myform' type='radio' value='Radio 1'}")
test.verify(radiobutton.checked)
var radiobutton = waitForObject(":{tagName='INPUT' id='r1' name='rg' " +
        "form='myform' type='radio' value='Radio 1'}");
test.verify(radiobutton.checked);
my $radiobutton = waitForObject(":{tagName='INPUT' id='r1' name='rg' " .
        "form='myform' type='radio' value='Radio 1'}");
test::verify($radiobutton->checked);
radiobutton = waitForObject(":{tagName='INPUT' id='r1' name='rg' " +
        "form='myform' type='radio' value='Radio 1'}")
Test.verify(radiobutton.checked)
set radiobutton [waitForObject ":{tagName='INPUT' id='r1' name='rg' \
        form='myform' type='radio' value='Radio 1'}"]
test verify [property get $radiobutton checked]

此处显示的编码模式——获取对象的引用,然后验证其属性之一的值——非常常见,并且可以应用于任何元素。

表单文本字段

无论是text还是textarea表单元素都有value属性,因此很容易检查它们包含的内容。

entry = waitForObject("{tagName='INPUT' id='input' " +
        "form='myform' type='text'}")
test.compare(entry.value, "Ternary")
var entry = waitForObject("{tagName='INPUT' id='input' " +
        "form='myform' type='text'}");
test.compare(entry.value, "Ternary");
my $entry = waitForObject("{tagName='INPUT' id='input' " .
        "form='myform' type='text'}");
test::compare($entry->value, "Ternary");
entry = waitForObject("{tagName='INPUT' id='input' " +
        "form='myform' type='text'}")
Test.compare(entry.value, "Ternary")
set entry [waitForObject "{tagName='INPUT' id='input' \
        form='myform' type='text'}"]
test compare [property get $entry value] "Ternary"

这完全遵循了我们之前示例中使用的相同模式。

表单选择框

Web表单通常将单选列表(元素类型为select-one)以组合框形式呈现,并将多重选择列表(元素类型为select)以列表框形式呈现。我们可以轻松检查哪些项目已被选中。

selection = waitForObject(":{tagName='INPUT' id='sel' " +
        "form='myform' type='select-one'}")
test.compare(selection.selectedIndex, 2)
test.compare(selection.selectedOption, "Cavalier")
var selection = waitForObject(":{tagName='INPUT' id='sel' " +
        "form='myform' type='select-one'}");
test.compare(selection.selectedIndex, 2);
test.compare(selection.selectedOption, "Cavalier");
my $selection = waitForObject(":{tagName='INPUT' id='sel' " .
        "form='myform' type='select-one'}");
test::compare($selection->selectedIndex, 2);
test::compare($selection->selectedOption, "Cavalier");
selection = waitForObject(":{tagName='INPUT' id='sel' " +
        "form='myform' type='select-one'}")
Test.compare(selection.selectedIndex, 2)
Test.compare(selection.selectedOption, "Cavalier")
set selection [waitForObject ":{tagName='INPUT' id='sel' \
        form='myform' type='select-one'}"]
test compare [property get $selection selectedIndex] 2
test compare [property get $selection selectedOption] "Cavalier"

在这里,我们从单选列表框中检索选中的项目,并验证第三个项目(索引位置2的项目)已被选中,其文本为"Cavalier"。

selection = waitForObject(":{tagName='INPUT' id='sel' " +
        "form='myform' type='select'}")
test.verify(selection.optionAt(0).selected)
test.verify(not selection.optionAt(1).selected)
test.verify(selection.optionAt(2).selected)
test.compare(selection.optionAt(1).text, "Round Head")
var selection = waitForObject(":{tagName='INPUT' id='sel' " +
        "form='myform' type='select'}");
test.verify(selection.optionAt(0).selected);
test.verify(!selection.optionAt(1).selected);
test.verify(selection.optionAt(2).selected);
test.compare(selection.optionAt(1).text, "Round Head");
my $selection = waitForObject(":{tagName='INPUT' id='sel' " .
        "form='myform' type='select'}");
test::verify($selection->optionAt(0)->selected);
test::verify(!$selection->optionAt(1)->selected);
test::verify($selection->optionAt(2)->selected);
test::compare($selection->optionAt(1)->text, "Round Head");
selection = waitForObject(":{tagName='INPUT' id='sel' " +
        "form='myform' type='select'}")
Test.verify(selection.optionAt(0).selected)
Test.verify(!selection.optionAt(1).selected)
Test.verify(selection.optionAt(2).selected)
Test.compare(selection.optionAt(1).text, "Round Head")
set selection [waitForObject ":{tagName='INPUT' id='sel' \
        form='myform' type='select'}"]
test verify [property get [invoke selection optionAt 0] selected]
test verify [expr ![property get [invoke selection optionAt 1] selected]]
test verify [property get [invoke selection optionAt 2] selected]
test.compare [property get [invoke selection optionAt 1] text] \
        "Round Head"

在这个示例中,我们检索多个选择列表(通常表示为列表框)的引用,然后检索其选项项。然后我们验证第一个选项(索引位置0)已被选中,第二个选项(索引位置1)未被选中,第三个选项(索引位置2)已被选中。我们还验证第二个选项的文本为"Round Head"。

请参阅HTML_Select类,其HTML_Option HTML_Select.optionAt(index)函数,以及其textselected属性。

如何访问表格单元格内容

当测试Web应用程序时,另一个常见的需求是从HTML表格中检索特定单元格的文本内容。实际上,使用Squish完成这项任务非常容易。

使用Object findObject(objectName)函数和Object waitForObject(objectOrName)函数检索的所有HTML元素都有一个HTML_XPathResult HTML_Object.evaluateXPath(statement)方法,可用于查询HTML元素,并返回查询结果。我们可以利用这个特性创建一个通用的自定义getCellText函数来完成我们想要的任务。以下是一个示例实现:

def getCellText(tableObject, row, column):
    return tableObject.evaluateXPath("TBODY/TR[%d]/TD[%d]" % (
        row + 1, column + 1)).stringValue
function getCellText(tableObject, row, column)
{
    return tableObject.evaluateXPath("TBODY/TR[" + (row + 1) +
        "]/TD[" + (column + 1) + "]").stringValue;
}
sub getCellText
{
    my ($tableObject, $row, $column) = @_;
    ++$row;
    ++$column;
    return $tableObject->evaluateXPath(
        "TBODY/TR[$row]/TD[$column]")->stringValue;
}
def getCellText(tableObject, row, column)
    tableObject.evaluateXPath(
        "TBODY/TR[#{row + 1}]/TD[#{column + 1}]").stringValue
end
proc getCellText {tableObject row column} {
    incr row
    incr column
    set argument "TBODY/TR[$row]/TD[$column]"
    return [property get [invoke $tableObject \
        evaluateXPath $argument] stringValue]
}

XPath 有点像文件路径,因为每个组件之间都由一个 / 分隔。这里使用的 XPath 表示的是,"找到每个 TBODY 标签,然后在每个标签内找到第 row 行的 TR 标签,然后在这个标签内找到第 column 列的 TD 标签"。结果总是类型为 HTML_XPathResult 类 的对象;这里我们通过结果对象的 stringValue 属性将查询结果作为单个字符串值返回。(因此,如果文档中存在多个 TBODY 标签,并且我们在行和列上想要一个单元格,实际上我们会得到所有这些单元格的文本。)我们必须将行和列加1,因为 XPath 查询使用基于1的索引,但我们更喜欢我们的函数使用基于0的索引,因为这是 Squish 所支持的脚本语言使用的索引方式。函数可以像这样使用

table = waitForObject(htmlTableName)
text = getCellText(table, 23, 11)
var table = waitForObject(htmlTableName);
var text = getCellText(table, 23, 11);
my $table = waitForObject($htmlTableName);
my $text = getCellText($table, 23, 11);
table = waitForObject(htmlTableName)
text = getCellText(table, 23, 11)
set table [waitForObject $htmlTableName]
set text [getCellText $table 23 11]

此代码将返回从名为 htmlTableName 的 HTML 表格的第22行第10列的单元格中的文本。

Squish 的 XPath 功能在 如何使用 XPath 中介绍。

非表单元素和同步

当然,也可以验证 Web 应用程序 DOM 树中的任何其他元素的状态和内容。

例如,我们可能会想验证 ID 为 result_table 的表格中包含文本——在某处表格中,我们不在意在哪里——“总计:387.92”。

table = waitForObject("{tagName='TABLE' id='result_table]'}")
contents = table.innerText
test.verify(contents.find("Total: 387.92") != -1)
var table = waitForObject("{tagName='TABLE' id='result_table]'}");
var contents = table.innerText;
test.verify(contents.indexOf("Total: 387.92") != -1);
my $table = waitForObject("{tagName='TABLE' id='result_table]'}");
my $contents = $table->innerText;
test::verify(index($contents, "Total: 387.92") != -1);
table = waitForObject("{tagName='TABLE' id='result_table]'}")
contents = table.innerText
Test.verify(contents.find("Total: 387.92") != -1)
set table [waitForObject "{tagName='TABLE' id='result_table]'}"]
set contents [property get $table innerText]
test verify [expr [string first "Total: 387.92" $contents] != -1]

innerText 属性将整个表格的文本作为字符串提供,因此我们可以轻松搜索它。

这里还有一个例子,这次检查 ID 为 syncDIV 的 DIV 标签是否隐藏。

div = waitForObject(":{tagName='DIV' id='syncDIV'}")
test.compare(div.style().value("display"), "hidden")
var div = waitForObject(":{tagName='DIV' id='syncDIV'}");
test.compare(div.style().value("display"), "hidden");
my $div = waitForObject(":{tagName='DIV' id='syncDIV'}");
test::compare($div->style()->value("display"), "hidden");
div = waitForObject(":{tagName='DIV' id='syncDIV'}")
Test.compare(div.style().value("display"), "hidden")
set div [waitForObject ":{tagName='DIV' id='syncDIV'}"]
test compare [invoke $div style [invoke value "display"]] "hidden"

注意,我们必须使用 HTML_Style HTML_Object.style() 函数(而不是例如写入 div.style.display)。

通常,这样的 DIV 元素用于同步。例如,在加载新页面后,我们可能要等到一个特定的 DIV 元素存在并隐藏——例如 HTML 页面中的某些 JavaScript 代码隐藏了 DIV,因此当 DIV 被隐藏时,我们知道浏览器已准备好,因为 JavaScript 已经执行。

def isDIVReady(name):
    if not object.exists(":{tagName='DIV' id='%s'}" % name):
       return False
    return waitForObject(":{tagName='DIV' id='syncDIV'}").style().value(
        "display") == "hidden"

# later on...
waitFor("isDIVReady('syncDIV')")
function isDIVReady(name)
{
    if (!object.exists(":{tagName='DIV' id='" + name + "'}"))
       return false;
    return waitForObject(":{tagName='DIV' id='syncDIV'}").style().value(
        "display") == "hidden";
}

// later on...
waitFor("isDIVReady('syncDIV')");
sub isDIVReady
{
    my $name = shift @_;
    if (!object::exists(":{tagName='DIV' id='$name'}")) {
       return 0;
    }
     return waitForObject(":{tagName='DIV' id='syncDIV'}")->style()->value(
        "display") eq "hidden";
}

# later on...
waitFor("isDIVReady('syncDIV')");
def isDIVReady(name)
    if !Squish::Object.exists(":{tagName='DIV' id='#{name}'}")
       return false
    end
    waitForObject(":{tagName='DIV' id='syncDIV'}").style().value(
        "display") == "hidden"
end

# later on...
waitFor("isDIVReady('syncDIV')")
proc isDIVReady {name} {
    if {![object exists ":{tagName='DIV' id='${name}'}"]} {
       return false
    }
    set div [waitForObject ":{tagName='DIV' id='syncDIV'}"]
    set display [invoke $div style [invoke value "display"]]
    return [string equal $display "hidden"]
}

# later on...
[waitFor {isDIVReady('syncDIV')}]

我们可以通过使用 Boolean waitFor(condition) 函数轻松地使 Squish 等待我们给定的代码执行完成。(尽管它是为一些不会花费太长时间的事情设计的。)

如何进行 Web 应用程序负载测试

此示例演示了如何使用 Squish 对 Web 服务器进行负载测试。以下假设设置:机器 L 控制负载测试脚本的执行,并在机器上安装了 Squish 和 Python 解释器。机器 L 还有一个要执行的测试套件。机器 C1 到 Cn 是将在其中运行 Web 浏览器的机器。它们都需要安装并运行 squishserver 可执行文件。机器 W 是将要承受负载的 Web 服务器。

由于机器 W(Web 服务器)和机器 L(测试控制器)是物理上不同的机器,我们需要一种方法从网络中检索系统信息。简单网络管理协议(SNMP)非常适合这项任务。

负载测试是通过一个Python脚本(loadtest.py)完成的,该脚本与Squish的示例一起提供: examples/loadtesting/loadtest.py。该脚本启动所有squishrunner进程,并使它们连接到squishserver进程和机器C1到Cn。关于实例数量、启动延迟、目标主机等所有详细信息都在脚本的顶部定义和记录。只需简单地根据您的需求进行修改。

脚本将记录所有squishrunner运行的开始和结束时间。在另一个线程中,脚本轮询来自Web服务器的系统信息并记录下来。关于负载和运行的数据库都写入制表符分隔值文件,因此可以轻松地在该步骤中进行评估。(或者您可以根据需要修改脚本以输出其他格式的结果。)

为了让squishrunner能够连接到远程机器,在每台远程机器上,必须在squishrunnerrc文件中将配置选项ALLOWED_HOSTS设置为启动squishrunner进程的所有机器C1到Cn的宿主机。(参见分布式测试)。

记录系统信息

机器W(Web服务器)需要运行SNMP守护程序。它很容易安装和设置。配置时,snmpconf命令可能很有用。例如

snmpconf -g basic_setup

此工具的源代码、二进制文件、文档和教程可以在http://net-snmp.sourceforge.net找到。此外,负载测试脚本使用命令行SNMP工具,这些工具在机器L上也需要。您也可以使用Python标准库中的Python SNMP模块或第三方SNMP模块——这可能是您需要自己做的事情,如果您发现您经常使用这个脚本。

因此,我们将在squishrunners并行时使用命令行SNMP工具来记录系统信息。脚本末尾将信息写入文件。请注意,您可能需要调整snmpget程序的用法以匹配您的SNMP设置。《code translate="no">cutAvg`函数是从snmpget程序输出中提取我们所需要内容的辅助函数。

def cutAvg(snmpString):
    return snmpString.strip().rsplit("STRING: ", 1)[1]


loads = []
def sysinfo():
    def loadOne(number):
        cmd = "snmpget -v 1 -c commro %s laLoad.%d" % (
            WEBSERVER_HOST, number)
        tmp = os.popen(cmd, "r")
        reply = tmp.read()
        reply = cutAvg(reply)
        tmp.close()
        return reply

    while True:
        l1 = loadOne(1)
        l5 = loadOne(2)
        l15 = loadOne(3)
        loads.append({'ts': time.time(), '1m': l1, '5m': l5, '15m': l15})
        time.sleep(5)

生成负载

我们将存储与每个squishrunner运行相关的信息(主机、开始时间和结束时间),并将它们存储在SquishrunnerRun对象中。使用SquishrunnerRun对象作为参数,runsuite函数设置了starttime变量,启动了指定主机的squishserver连接,并在squishrunner完成后存储了endtime

class SquishrunnerRun:
    id = 0
    starttime = 0
    endtime = 0
    host = ''

    def __init__(self, id, host):
        self.id = id
        self.host = host

    def duration(self):
        return self.endtime - self.starttime


def runSuite(srr):
    srr.starttime = time.time()
    srrCmd = " ".join([SQUISHRUNNER_EXEC, "--host", srr.host,
                       "--testsuite", TEST_SUITE,
                       "--reportgen xml,%s%d.xml" % (REPORTS_DIR, srr.id)])
    os.system(srrCmd)
    srr.endtime = time.time()
    print "call %d finished; needed %s" % (
        srr.id, srr.endtime - srr.starttime)

定义了SquishrunnerRun类和runSuite函数后,我们现在能够开始测试。对于每个RUN_COUNT次运行,下一个主机将与一个新创建的SquishrunnerRun对象关联。下一个runSuite函数调用将在新的线程中启动。等待指定时间后,我们继续。此外,我们还将所有的SquishrunnerRun对象存储在一个列表中。

runs = []
for i in range(RUN_COUNT):
    tmp = SquishrunnerRun(i,
        SQUISHSERVER_HOSTS[i % len(SQUISHSERVER_HOSTS)])
    runs.append(tmp)
    thread.start_new_thread(runSuite, (tmp,))
    time.sleep(RUN_DELAY)

现在,在启动所有的squishrunner进程之后,脚本必须等待直到所有这些进程都完成。如果一个squishrunner进程已经完成,它的endtime就会被设置。所以我们必须等待直到没有任何一个SquishrunnerRun's endtime设置为0。

def allRunnersFinished():
    for runner in runs:
        if runner.endtime != 0:
            return False
    return True

while not allRunnersFinished():
    pass

后处理

一旦完成测试本身,我们必须存储测试结果。它们将写入定义为RUNS_FILELOADS_FILE的两个文件中。

fh = None
try:
    fh = open(RUNS_FILE, "wt")
    for e in runs:
        fh.write("%s\t%s\t%s\n" % (e.id, e.starttime, e.endtime))
finally:
    if fh is not None:
        fh.close()

fh = None
try:
    fh = open(LOADS_FILE, "wt")
    for e in loads:
        fh.write("%(ts)s\t%(1m)s\t%(5m)s\t%(15m)s\n" % e)
finally:
    if fh is not None:
        fh.close()

《RUNS_FILE》包含标记每个单个squishrunner运行开始和结束的时间戳。《LOADS_FILE》包含每5秒测量的服务器平均负载。可以使用适当的SNMP命令轻松配置其他信息的测量(流量、进程数、磁盘I/O)。可用标准图表软件生成数据的图形表示。

我们希望在某个时候提供一套现成的前端,使其能够配置、安排和执行测试运行,并提供结果的可视表示。

©2024 The Qt Company Ltd. 本文档中的文档贡献归其各自的拥有者所有。
本提供的文档按自由软件基金会发布的《GNU自由文档许可证版本1.3》的条款许可。
Qt及其相关标志是芬兰和/或其他国家/地区的Qt公司商标。所有其他商标归其各自的所有者所有。