对象名称生成
对象名称
在记录测试用例或在Spy中选定对象时,Squish会自动为访问的每个对象创建一个名称,以便在以后,例如在测试脚本中识别。此名称可能是一个多属性(真实)名称,也可能是一个分层名称。在测试某些工具包,如Web和Tk时,默认情况下仍然使用分层名称。对于大多数工具包而言,默认命名方案是多属性(真实)名称,因为它们在应用程序变化的情况下,可以导致更灵活、更可靠的对象识别。
如对象映射章节中所述,真实名称的表示根据所使用的对象映射实现方式而异;对于基于文本的对象映射,一个真实名称由一系列用花括号括起来的,用空格分隔的 <propertyName>=<value> 对组成的。例如,一个多属性名称可以用来识别类型为 Button
且文本为 "Hello" 的对象,这样的字符串如下所示,在所有脚本语言中均为
{type='Button' text='Hello'}
脚本基于对象映射的结构部分解释说,同样的对象名称也可以表示为原生的字典或映射(当然,语法根据所使用的脚本语言有所不同)
{"type": "Button", "text": "Hello"}
{type: 'Button', text: 'Hello'};
{:type => 'Button', :text => 'Hello'}
{'type' => 'Button', 'text' => 'Hello'}
ObjectName type Button text Hello
每个多属性名称必须至少包含两个属性,其中之一是工具包特定属性。工具包特定属性包括
- Java:类型或基础类型(但不能两者同时出现)
- Web:tagName
- 其他所有:类型
某些对象由于其类设计只能在包含对象内部找到。在这种情况下,真实名称中还需要一个容器属性。这是一个容器(或窗口、或父项)的对象引用。当Squish需要跨不相关对象层次结构找到对象时,需要容器属性。以下是一些例子:列表、树和表项,QGraphicsItem
s,QtQuick项,以及来自嵌入HTML控制(如QWebView
)的HTML元素。
请注意,类型、基础类型、tagName属性(等等)只能完全匹配—大多数其他字符串属性可以使用通配符或正则表达式匹配,这样可以使Squish测试脚本在面对变化时更加灵活。(见提高对象识别。)
许多对象具有很多属性,Squish必须从中选择以创建正确、唯一识别每个对象的名称,并且尽可能短,即尽可能使用尽可能少的属性。不幸的是,这两个要求相互冲突:可以通过使用对象的所有或大部分属性来确保唯一性,但短期名称需要我们使用尽可能少的属性。
为什么我们总是需要短名称呢?因为使用的属性越少,应用程序中某个对象的属性更改影响我们用于识别对象的属性的几率就越小。因此,短名称(即尽可能使用最少属性创建的名称)在面临应用程序变化时更加健壮。另一方面,如果我们使用过多的属性,可能会得到名称过于通用的名称,它可以匹配两个或多个对象,如果Squish无法唯一地识别对象,它如何知道测试脚本中打算访问哪个对象呢?
对于这个问题,没有正确或完美的答案。由于应用程序更改而失效的名称(即,名称中用于名称的一个或多个属性值已更改的名称),会导致测试脚本失败。同样,不再独特的名称(可能是由于为对象使用太多的属性,然后应用程序发生更改,导致添加具有相同属性的另一个对象)也会导致测试脚本失败。因此,Squish必须在创建名称时争取平衡,使其既独特又健壮。
Squish使用一系列内置的启发式规则来确定使用哪些属性。在大多数日常测试情况下,这些规则可行,并且Squish创建既独特又健壮的名称。
不幸的是,在某些情况下,Squish使用的启发式规则产生不良结果。例如,在编写Qt测试时,如果name属性(Squish称为名称)中包含文本,Squish将使用QObject对象属性来识别对象。在大多数情况下,这非常好,特别是如果AUT的开发人员已为他们的QObject选择独特的名称。然而,如果该属性随时间发生变化,那么显然Squish无法依赖它进行标识,我们需要有一种方式将其排除在Squish使用属性列表之外。
定义属性集
在Squish中,创建真实名称所使用的属性列表可以通过编辑一些简单的XML文件进行配置。如何使用这些文件自定义名称创建过程将在以下两个部分中解释。
注意:以下部分不适用于Web应用程序的测试。有关这些,请参阅 Squish用于Web的名称生成算法。
描述文件位置
Squish用于为对象创建多属性(真实)名称的属性列表由XML文件指定。对于您应用程序中使用的每个包装器,都可能有两个XML文件——在Squish术语中称为“描述文件”——它们必须遵循特定的命名方案,以便Squish能够找到它们。
- SQUISHDIR/etc/wrapper_descriptors.xml:这个XML文件包含Squish用于创建真实(多属性)名称的默认属性。根据您使用的包的类型,您可能会在
SQUISHDIR/etc
中找到qtwrapper_descriptors.xml
,javawrapper_descriptors.xml
或其他。<SQUISHDIR>代表您安装Squish包的目录。
- Squish_User_Settings/wrapper_user_descriptors.xml:如果您想覆盖默认行为,或添加新属性,您可以通过为每个要影响的包装器创建用户描述文件来实现。这些文件具有与Squish提供的预定义描述文件类似的名字,但在名字中间插入
_user_
,如上所示。<Squish_User_Settings>代表存储用户特定设置的目录。在Windows上,这是
%APPDATA%\froglogic\Squish
,而在类Unix系统上,例如Linux和macOS,它是~/.squish
。如果您将环境变量SQUISH_USER_SETTINGS_DIR指向不同的目录,则该目录将用于存储用户设置——以及用户描述文件。
对于Squish提供的预定义描述文件和您创建的用户描述文件,都使用相同的文件格式来自定义Squish生成名称的方式。
每个描述文件都包含一个类型的列表及其对应的属性名称,这些属性名称可以在为每种特定类型的对象生成名称时使用。Squish首先读取其预定义的描述文件,然后读取任何用户描述文件。这使得用户描述文件(那些文件名为wrapper_user_descriptors.xml
的文件)可以覆盖预定义描述文件中指定的行为,并添加新的类型描述。
描述文件格式
类型的列表及其对象可使用的属性使用简单的XML格式指定。以下是一个虚构的应用程序工具包的简短示例,其中包含“按钮”类型。
<objectdescriptors> <descriptor> <type name="Button"/> <realidentifiers> <property>caption</property> </realidentifiers> </descriptor> </objectdescriptors>
此描述文件定义了单个<描述符>,表示对于所有类型为按钮
的对象,在生成真实名称时应使用文本。
注意:名称继承表示不仅按钮
类的实例将在其真实名称中获得文本属性,而且继承自按钮
的类的实例也将获得此属性。当决定为特定对象生成名称时,Squish会考虑继承层次结构,并使用在给定对象的继承层次结构中出现类型名称的所有描述符。
在这个例子中,我们只使用了类型的名称来标识对象。但是,也可以应用约束,以便仅识别符合特定约束的给定类型的对象。以下是一个稍长的示例来说明这一点。
<objectdescriptors> <descriptor> <type name="Button"> <constraint name="visible">false</constraint> </type> <realidentifiers> <property>caption</property> <property>tooltip</property> </realidentifiers> </descriptor> <descriptor> <type name="Button"/> <realidentifiers> <property>caption</property> <property>xpos</property> <property>ypos</property> </realidentifiers> </descriptor> </objectdescriptors>
在我们这里,我们创建了两个独立的描述符,尽管它们都指的是同一类型的对象。
第一个描述符适用于类型为按钮
的对象——但只有在按钮的可见属性为false
的情况下,换句话说,这个描述符只适用于隐藏按钮。因此,当应用程序有一个隐藏按钮时,这个描述符表示在创建真实名称时应使用文本和工具提示属性来标识它。
第二个描述符也适用于类型为按钮
的对象,但仅适用于可见按钮,因为隐藏按钮由第一个描述符处理。此描述符表示在创建按钮
对象的真实名称时,应使用文本、xpos和ypos属性。
尽管我们只说明了单约束的使用,但可以使用尽可能多的约束。在这种情况下,只有在所有的约束都满足的情况下,才会使用描述符。
通常,当指定了多个适用于同一类型的描述符时,Squish将尝试使用与所访问的对象最匹配的那个,这基本上是从具有最多约束(即一个约束)的描述符工作到具有最少约束的描述符。
因此,在上面的第二个示例中,如果Squish遇到一个按钮
,它将首先尝试第一个描述符(因为那有一个约束,即一个,最多)。如果按钮是隐藏的,Squish就有匹配,并且会生成使用按钮的文本和工具提示的真实名称。然而,如果按钮是可见的,第一个描述符就不会匹配,因此Squish将尝试下一个描述符,这将匹配(因为没有约束,所以会匹配任何按钮
),并且会生成使用按钮的文本、xpos和ypos的真实名称。
高级属性集定义
通配符*
描述符
除了正常的描述符,通过类型名称(和可选的约束条件)匹配对象的描述符,还存在一个特殊的描述符称为 *
(星号),它可以匹配 任何 类型的对象。这个特殊描述符也可以对其应用约束条件,与正常描述符的方式完全相同。支持使用 *
作为通配符的工具包包装器有 Qt、Java 和 Mac。对于 Windows 或 Android,请使用 WinGUIObject
或 Control
作为类型名称。以下是一个使用示例
<objectdescriptors> <descriptor> <type name="*"/> <realidentifiers> <property>id</property> </realidentifiers> </descriptor> <descriptor> <type name="Button"/> <realidentifiers> <property>caption</property> </realidentifiers> </descriptor> </objectdescriptors>
在这个示例中,定义了一个通配符描述符。这意味着对于所有对象,无论它们的类型名称是什么,都会在生成真实名称时使用 id。如果特定对象没有 id,则属性将被静默忽略,并且不会触发错误。
注意:在通配符描述符中列出不适用于某些对象类型的属性是无害的。例如,如果我们的对象类型中的大多数都有一个我们通常在生成真实名称时想要使用的 id,那么最好的方法是在通配符描述符中指定此属性。这将确保对于那些具有该属性的对象的真名始终使用 id,并且对于那些不存在的属性,它会被安全、静默地忽略。
生成真实名称时,使用的属性是匹配的类描述符的属性,以及匹配的对象基类描述符的属性,依此类推,直到继承层级。此外,还会使用任何通配符描述符。
根据上面所示的示例描述符,如果遇到一个 Button
对象——假设按钮有 id,Squish 会生成一个真实名称,该名称将使用标题(来自 Button
描述符)和 id(来自 *
描述符)。如果 Button
没有id,Squish 将仅使用标题并忽略 id。如果 Button
从另一个类型派生,例如 Widget
,并且该类型具有指定了 hasfocus 等属性的描述符,则该属性也会包含在生成的真实名称中。
排他属性组
假设我们有一个包含以下规则描述符的描述文件:
<objectdescriptors> <descriptor> <type name="Button"/> <realidentifiers> <property>id</property> <property>caption</property> <property>tooltip</property> <property>enabled</property> </realidentifiers> </descriptor> </objectdescriptors>
这个描述符指定了四个属性:id、标题、工具提示和启用,这些属性将用于为 Button
对象生成真实名称。不幸的是,在实际应用中,这可能会导致真实名称不够稳健。这是因为我们使用了太多的属性,所以如果按钮的标题或工具提示更改,我们的测试脚本将容易受到威胁。我们真正希望表达的是,如果标题有一个值,则应使用它;如果标题为空,则应使用工具提示作为后备。
Squish 提供了一种解决问题的方式。该机制是描述文件格式的一部分,允许我们指定两个或多个属性,使 Squish 只使用其中之一——即第一个实际有值的属性。这种机制称为“属性组”。以下是一个示例
<objectdescriptors> <descriptor> <type name="Button"/> <realidentifiers> <property>id</property> <group> <property>caption</property> <property>tooltip</property> </group> <property>enabled</property> </realidentifiers> </descriptor> </objectdescriptors>
我们将标题和工具提示放入一个 <group> 中。这样做的效果是告诉 Squish,如果标题有值(即它不是一个空字符串),则使用标题;否则使用工具提示。因此,当 Squish 使用此描述符生成 Button
的真实名称时,名称将包含 id、启用属性,以及 标题 或 工具提示 中的一个——但从不会同时两者——加上任何类描述符中与 Button
继承相关的属性,以及任何来自通配符描述符(*
)的属性。
具有对象名称值的属性
除了指定属性名称和值之外,真实名称也可以指定对相关对象的引用,这种引用可以帮助唯一标识特定类型的对象。
例如,我们可能有一个包含几个单行编辑器的表单。这些编辑器本身可能具有相同的属性,并无明显区别,但它们很可能都有标签,以便用户知道应该输入什么。在某些工具包中,这些标签可能通过其相对于对象的位置——左侧或上方——来标识,在其他工具包中,它们通过其与对象的关系来标识——例如,它们是其伙伴。
以下是一个虚构单行编辑器的真实名称示例,该编辑器通过其自身的属性被识别,同时也通过其伙伴被识别,该伙伴的值是对相关对象的引用
{type='LineEdit' maxChars='32' allowDigits='false' buddy={type='Label' text='Last Name:'}}
常规属性值用引号括起来,但当值是相关对象的引用时,不使用引号,而是使用相关对象的真实名称,用括号括起来。(因此,我们最终得到一个真实名称嵌套在另一个真实名称中。)
当然,当使用基于脚本的对象映射时,也可以表达相同的对象名称(不翻译)。
{"type": "LineEdit", "maxChars": 32, "allowDigits": False, "buddy": {"type": "Label", "text": "Last Name:"}}
{type: 'LineEdit', maxChars: 32, allowDigits: false, buddy: {type: 'Label', text: 'Last Name:'}}
{:type => 'LineEdit', :maxChars => 32, :allowDigits => false, :buddy => {:type => 'Label', :text => 'Last Name:'}}
{'type' => 'LineEdit', 'maxChars' => 32, 'allowDigits' => 'false', 'buddy' => {'type' => 'Label', 'text' => 'Last Name:'}}
ObjectName type LineEdit maxChars 32 allowDigits false buddy [ObjectName type Label text {Last Name:}]
如果我们想在描述符中引用另一个对象的属性,我们只需使用标签而不是标签,如下例所示
<objectdescriptors> <descriptor> <type name="LineEdit"/> <realidentifiers> <property>maxChars</property> <property>allowDigits</property> <object>buddy</object> </realidentifiers> </descriptor> </objectdescriptors>
此描述符所要表达的是,当Squish遇到LineEdit
时,它应该生成包含maxChars和allowDigits的名称,并且还包含一个对象引用(伙伴)。
从某些对象的真实名称中排除个别属性
在某些情况下,我们希望特定属性在生成类型对象的真实名称时被使用,但在大多数对象中却不使用。例如,我们可能有一系列从Widget
派生的对象,具有dropEnabled属性。如果大多数派生对象是编辑控件,则属性有意义,但对于派生的Button
对象,这可能没有意义。因此,我们希望创建一个描述符,为所有Widget
,但不为Button
创建。Squish允许我们通过使用
<objectdescriptors> <descriptor> <type name="Widget"/> <realidentifiers> <property>dropEnabled</property> <property>text</property> </realidentifiers> </descriptor> <descriptor> <type name="Button"/> <realidentifiers> <property exclude="yes">dropEnabled</property> </realidentifiers> </descriptor> </objectdescriptors>
在这个例子中,Widget
是所有GUI对象的基类。第一个描述符指示Squish在为Widget
对象及其派生对象(如LineEdit
和Button
)生成真实名称时使用dropEnabled和text。没有为LineEdit
创建描述符,因为它们是从Widget
派生的,并且我们没有要添加或更改的属性。但对于Button
,我们已经创建了一个描述符,这样我们就可以阻止dropEnabled被用于Button
名称。
如果我们使用上面的描述符文件与我们的虚构工具包一起使用,它将改变Squish 创建真实名称的方式。对于LineEdit
(假设它们是从Widget
派生的),Squish将创建具有dropEnabled、text(当然,还包括类型,因为那总是包含的)的真实名称,但对于Button
,Squish将创建仅使用text和类型的真实名称。
Squish在Web上使用的名称生成算法
Web版的Squish在生成对象名称时使用不同的启发式方法,它不使用上一节中提到的描述符文件。名称生成首先检查所有通过String Squish.nameOf(objectOrName)注册了名称生成钩子的扩展。之后,它应用以下规则,取第一个匹配的规则。所有多属性名称都包含tagName,这是Squish for Web对象名称必需的。名称还可以包含一个上下文,其中包含对象所在的frame/iframe元素的名称。对于顶级页面的对象,此属性不包括在内。
SELECT
、INPUT
和BUTTON
元素包含name、id、type和innerText属性,如果它们不为空。如果包含它的表单有name或id属性设置,则还生成一个表单属性。SELECT
和INPUT
字段也会在对象名称中添加类型属性。TD
元素具有cMenuTD
类时,如果这些属性不为空,则包含class、id、name和innerText属性。IMG
元素如果id、name或alt设置为非空字符串,将生成一个包括这些属性的多个属性名称。否则,Squish将使用一个层次结构名称用于IMG
元素。元素的alt属性将在多个属性名称中反映为img_alt。- 下一步是遍历元素层次结构,找到要为其生成名称的元素。对于每个父元素,Squish检查它是否是一个
link
元素。如果是,将对它应用以下规则来生成名称。- 如果元素具有非空的id、name或innerText属性,它将获得一个包括这些属性的多个属性名称。
- 如果这三个都为空或不存在,Squish将查看链接的内容,看是否有IMG元素在其中。如果有这样的元素并且它有一个非空的id、name或alt属性,则
link
元素的名称将分别使用这些值作为img_id、img_name和img_alt的多属性名称。 - 如果内部的
IMG
元素没有非空的id、name或alt属性,Squish将检查src属性。如果有非空的src属性,Squish将提取src属性中的最后一个'/'之后的部分,并在链接的多属性名称中使用img_src属性包括它。 - 如果上述所有属性都为空,Squish将回退到为
link
元素使用层次结构名称。
如果达到了顶层元素,Squish将回到要为其生成名称的原始对象,并继续下一个规则。
- 如果元素具有tagName并设置了title、id或name属性,Squish将生成一个包括这些属性的多个属性名称。
- 如果元素是
SPAN
、DIV
、LI
、TD
或TR
元素并且它有一个非空的innerText属性,然后Squish将使用该属性作为属性生成多个属性名称。 - 在其他所有情况下,Squish将为对象生成一个层次结构名称
最后一步是在生成多个属性名称的情况下计算出现属性。
Squish for Web还允许定制名称生成。有关详细信息,请参阅JavaScript扩展API。
©2024Qt公司有限公司。本文件中的文档贡献是各自所有者的版权。
本文件中的文档使用GNU自由文档许可协议版1.3进行许可,该许可协议由自由软件基金会发布。
Qt和相应的标志是芬兰Qt公司及其全球其他国家的商标。所有其他商标均为其各自拥有者的财产。