如何在Appium中识别定位器(含实例)

836 阅读19分钟

如今,自动化正成为正在开发的产品的整体质量的组成部分。特别是对于移动应用程序来说,稳健地实施自动化就更加重要了。

根据Statista,到2022年,移动用户的数量可能是72.6亿,到2025年将增加到74.6亿。这表明移动应用在未来几年将如何增长,使移动应用测试变得极为重要。

Statista

有许多移动测试框架可用于执行移动自动化测试,但在这个Appium测试教程中,我们将重点介绍Appium自动化工具,以实现Android和iOS应用程序的自动化。要了解更多关于移动自动化的信息,你可以参考我之前的博客:Appium与TestNG执行Android自动化测试iOS自动化测试

在我的经验中,我看到许多测试工程师,无论是新手还是有经验的,都盲目地使用XPaths,后来导致了测试的不稳定。因此,为了使自动化变得稳健,必须了解Appium支持哪些定位器策略,以及什么定位器策略是提高自动化性能的理想选择。

在这个关于在Appium测试中使用定位器的教程结束时,你将了解到。

  • Appium提供的常见定位器策略。
  • 当自动化名称为UIAutomator2时,Android特有的定位器策略。
  • 当自动化名称为Espresso时,Android特定的定位器策略。
  • iOS特有的定位器策略。

本博客中使用的关于Appium中定位器的代码片段和例子都可以在GitHub上找到。你可以克隆该仓库并跟随。

我们将使用Proverbial应用程序的AndroidiOS的大部分定位器策略,而我们将使用API-demo Android应用程序的UIAutomator -> UiScrollable 定位器和Espresso特定的Data MatcherView Matcher 定位器策略。

在Appium中使用定位器的策略

Appium中,有一些定位器策略,你可以在Android和iOS平台上使用。这些策略非常简单明了,几乎与我们已经习惯的Selenium定位器相同;因此,所有这些定位器都非常容易使用。

让我们看看Appium中使用定位器的所有常见策略。

Appium中的ID定位器

Appium支持的第一个常见的定位器是ID. ID ,即在Android应用中定义的resource-id ,而在iOS中,它是元素的name 属性。

在使用Java时,我们可以在Appium中使用这个定位器,如下图所示。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorById = MobileBy.id ("color");

Appium检查器也建议使用ID ,如果它是为元素声明的。同样的,如下图所示。

Appium Inspector

ID定位器和Accessibility ID定位器非常相似,由于它的唯一性,使得寻找元素很容易。如果为定位器设置了这些ID,那么在Appium中应该总是优先使用这个定位器,而不是其他策略来使用Appium中的定位器。在Appium中使用ID定位器是首选,因为它使元素具有唯一性,并能更快地找到该元素。

通常情况下,Android平台的ID定位器的格式是 < package-name >:id/< id-name >.所以在寻找元素时,你可以使用整个文本或只使用 < id-name >.在截图的例子中,你可以使用com.lambdatest.proverbial:id/geoLocation ,或者直接使用geoLocation

Appium中的可访问性ID定位器

这也是继Appium中的ID 定位器之后最受欢迎的策略。Android的Accessibility ID是元素的content-desc 属性,而在iOS,它是accessibility-id 属性。它也是性能最快的定位器策略之一。

在使用Java时,我们可以在Appium中使用这个定位器,如下图所示。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByAccessibilityId = MobileBy.AccessibilityId ("color");

如果为元素定义了Accessibility id ,Appium Inspector会建议使用它。同样的情况如下图所示。

Accessibility ID locator in Appium

如果你发现在你测试的应用程序中,如果有任何元素不是动态的,但仍然没有设置accessibility id ,也没有设置任何ID ,那么你应该要求你的开发团队添加这些属性。这将帮助你节省大量的时间,以至于你可能不得不在Appium中建立其他定位器,如XPath、UISelector等。

Appium中的类名定位器

类名是另一种识别应用程序中的元素的常用策略。对于iOS来说,类是XCUI 元素的全名,以XCUIElementType 开始,而对于Android来说,是元素的完全限定名,当你在能力中选择UIAutomator2 作为自动化名称时,通常以android.widget.* 开始。

在使用Java时,你可以在Appium中使用这个定位器,如下图所示。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByClassName = MobileBy.className ("android.widget.Button");

你可以在Appium Inspector中准确地找到任何元素有什么类名,如下图所示。

Class Name locator in Appium

通常情况下,你不需要使用类名,除非该元素是一个动态元素。对于那个特定的类名,只有一个元素。在很多情况下,你可以使用class name 定位器,但强烈建议尽可能使用IDaccessibility id

Appium中的XPath定位器

XPath扫描应用程序屏幕的整个XML源树。在Appium支持的所有定位器策略中,这是Appium团队唯一不推荐的定位器。原因是它有性能问题,也是性能最慢的定位器策略。通常情况下,当所有其他定位器策略都不起作用的罕见情况下,我们可以使用XPath来寻找元素,这种定位器仍然被支持。

在使用Java时,你可以在Appium中使用XPath定位器,如下图所示。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByXpath = MobileBy.xpath (".//android.widget.Button[@text='COLOR']");

Appium Inspector还帮助你提供预建的XPath表达式,你可以直接使用。同样的,如下图所示。

XPath locator in Appium

最好不要使用这种定位器策略,因为即使没有IDaccessibility id, ,你仍然可以使用其他平台特定的定位器策略(我们将在一段时间内看一下)来寻找元素。

在3000多个真实设备和操作系统上运行Appium测试自动化。现在就试试LambdaTest吧!

在Appium中使用Android特定定位器的策略

在Appium中使用定位器的常见支持策略之后,还有一些平台和自动化类型特定的定位器策略。让我们详细了解一下Appium中的Android特定定位器。

UIAutomator2:UIAutomator选择器

Appium中的UIAutomator选择器定位器是独特的定位器策略之一,你必须创建一个Java语句并将其作为定位器文本传递给方法。在这个语句中,你需要使用UiSelector 类来建立Java语句。

new UiSelector().<method_name>

下面显示的是一个在我们的Java测试中使用这个定位器的例子。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByUiSelector = MobileBy.AndroidUIAutomator ("new UiSelector().text(\"COLOR\")");

在这里,我们正在为UiSelector 创建一个实例,并通知服务器我们需要找到一个具有文本COLOR 的元素。

UiSelector 类中一些经常使用的方法如下。

  • **checked:**这个方法获取元素的预期值来寻找一个被选中的元素。大多数情况下,它是与复选框元素一起使用的。
  • **className:**这个方法需要一个类名字符串,你可以从Appium检查器中找到。
  • **classNameMatches:**这个方法需要一个regex表达式,根据它的类名找到一个元素。
  • description:: 这个方法接收元素的DeepL 内容属性,这可以在Appium Inspector中找到。
  • **descriptionContains:**该方法接收该元素的全部或部分DeepL 内容属性。
  • descriptionMatches::该方法使用一个重词表达式,根据元素的DeepL 内容找到该元素。
  • descriptionStartsWith::此方法需要一个起始部分或完整的DeepL 内容来寻找元素。
  • enabled:: 这个方法接受一个布尔值来决定是否启用。
  • **index:**该方法从元素列表中获取该元素的索引。
  • **resourceId:**该方法接收相当于ID定位器的资源ID。
  • **resourceIdMatches:**该方法接收一个资源ID的regex表达式,以根据其资源ID找到元素。
  • **selected:**该方法采用一个布尔值来寻找被选中或未被选中的元素。
  • **text:**该方法接收一个文本值,通过其文本值找到一个元素。
  • **textContains:**该方法接收一个部分文本,通过它的部分文本值找到一个元素。
  • **textMatches:**该方法接受一个regex值来寻找一个元素。
  • **textStartsWith:**该方法接收一个元素的文本值,根据文本起始值找到它。

你可以通过在Appium Inspector的Search for element 窗口中执行定位器表达式来验证你的选择器是否有效。

UIAutomator Selector

在Appium Inspector中,点击放大镜图标,选择定位器策略为UIAutomator Selector ,创建你的选择器,并点击Search 按钮。你会在下一个屏幕上看到元素被找到后的情况,如下图所示。

UIAutomator Selector 1

你也可以使用UiScrollable 类来使元素随着UiSelector 类移动到视图中。

让我们看看UiScrollable 的例子,普通的UiSelector 无法找到不在视口中的元素,而UiScrollable 则能够找到该元素并将其滚动到视图中。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByUiSelector = MobileBy.AndroidUIAutomator ("new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text(\"PathEffects\"))");

上面的片段是作为一个例子添加到这里的,但在我们的演示项目中没有同样的情况。

在Appium Inspector中,我们可以验证UiSelector无法找到该元素,如下图所示。

UiSelector

然而,使用UiScrollable,它能够找到并滚动到该元素,没有任何问题,如下图所示。

UiScrollable

这些定位器策略在寻找动态元素时更加有用,通常使用其他常用的定位器策略很难找到这些元素。

Espresso:数据匹配器

在Android中,当你想找到任何元素时,只有在视口中可见的元素才能被找到。你无法在有100多个项目的列表底部找到一个元素,除非你滚动列表直到找到那个元素,或者你使用UiScrollableUiSelector 定位器策略。当你在Appium中使用Espresso作为自动化类型时,这是有可能的。

在Appium Espresso驱动中,有一个特定的定位器策略,你可以用它来寻找列表中的任何一个元素,而且不管这个列表中有多少个项目,都没关系。这个定位器策略是Data Matcher

数据匹配器只对属于视图的元素起作用,是AdapterViews 的一个子类型,例如:ScrollView, ListView,GridView. 如果你要直接使用本地Espresso来查找该元素,那么你会写出如下所示的内容。

onData (hasEntry ("title", "App")
 .inAdapterView (withId ("android:id/list"))
 .perform (click ());

在Appium中,你可以通过使用Hamcrest暴露的方法来编写类似的定位器,并创建JSON字符串,然后将其传递给Data matcher定位器方法。让我们看看如何在Java中做到这一点。

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
import org.openqa.selenium.json.Json;
. . .
private final By appItemDataMatcher = MobileBy.androidDataMatcher (new Json ().toJson (ImmutableMap.of (
  "name", "hasEntry",
  "args", ImmutableList.of ("title", "App")
)));

如果你将这个定位器的值与我们之前使用的本地Espresso表达式进行比较,我们正在打破hasEntry ,并创建JSON字符串。

让我们看看如何使用Appium Inspector中的数据匹配器定位器找到元素。

data matcher locator in Appium Inspector

由于我们想从列表中找到元素,首先,我们将选择主列表视图,并从其adapters 属性中获取值,如上面的截图所示。

让我们看看适配器属性里有什么值。

注意到我们在Java例子中为Appium中args 字段的数据匹配器定位器使用的值吗?如上所述,我们使用了title 属性,它是adapters 属性的一部分。你可以使用这些属性中的任何键/值对的组合,例如,除了title ,还有contentDescriptionintent

额外提示。

当你试图通过点击元素找到它时,Appium Inspector的行为很奇怪。相反,你需要从XML树中找到该元素。另外,Appium Inspector不会像Appium中的其他定位器那样,帮助你提供一个预建的定位器。为了确定你创建的数据匹配器定位器是否有效,你可以使用检查器中的Search for element 按钮来检查它,如下所示。

Search for element in appium inspector

Espresso:视图匹配器

视图匹配器定位器策略与数据匹配器策略非常相似,我们创建一个JSON字符串并将其传递给Appium中的定位器。唯一的区别是我们使用ViewMatchers类的方法而不是Hamcrest类。在JSON字符串中,有三个字段被传递。

  • **name:**这是该方法的名称。
  • **args:**这将有方法的参数值。
  • **class:**这将有包含该方法的类名。大多数情况下,它将是androidx.test.espresso.matcher.ViewMatchers.

在使用Java时,我们可以在Appium中使用View Matcher定位器,如下图所示。

import com.google.common.collect.ImmutableMap;
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
import org.openqa.selenium.json.Json;
. . .
private final By animationItemViewMatcher = MobileBy.androidViewMatcher (new Json ().toJson (ImmutableMap.of (
  "name", "withText",
  "args", "Animation",
  "class", "androidx.test.espresso.matcher.ViewMatchers"
)));

在Appium中使用iOS专用定位器的策略

在iOS中,只有一种自动化类型,即XCUITest,它支持在Appium中使用定位器的以下策略。

XCUITest 谓词字符串

谓语字符串定位器策略类似于SQL查询,你使用其属性的组合来查询一个特定的元素。

在使用Java时,你可以在Appium中使用谓词字符串定位器,如下图所示。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByPredicate = MobileBy.iOSNsPredicateString ("label == \"Colour\" AND name == \"color\"");

Appium检查器也有助于提供一个预先建立的谓词字符串,你只需复制并在测试中使用。下面的截图显示了Appium Inspector如何在Appium中显示预建的定位器。

Appium Inspector

在创建谓词字符串时,你也可以使用以下比较表达式。

基本比较。

  • =,==: 左手表达式等于右手表达式。
  • >=,=>: 左手表达式大于并等于右手表达式。
  • <=,=<: 左手表达式小于并等于右手表达式。
  • >:左侧表达式比右侧表达式大。
  • <:左手表达式小于右手表达式。
  • !=,<> :左手表达式不等于右手表达式。
  • < INPUT > BETWEEN { lower range, upper range }:左手表达式的值位于右手表达式中提到的范围之间,两个值都包括在内。

基本谓词。

  • AND,&&: 逻辑and 将检查两个相连的表达式为true.
  • OR,||: 逻辑or 将检查任何一个连接的表达式是否是true.
  • NOT,!: 否定一个表达式将否定与它一起使用的表达式的结果。

字符串比较。

  • BEGINSWITH:检查属性是否以提供的字符串开始。
  • CONTAINS:检查该属性是否包含所提供的字符串。
  • ENDSWITH:检查该属性是否以提供的字符串结束。
  • LIKE:检查属性是否包含所提供的字符串,如?*. 这里,? 将尝试只匹配一个字符,而* 将尝试从该位置匹配0个或多个字符。
  • MATCHES:检查该属性是否与反义词字符串匹配。

XCUITest:类链

Appium中的类链定位器类似于XPath,但比XPath更稳定。通过类链,你可以结合谓词字符串或直接引用子代或子代索引来获得一个特定的元素。

在使用Java时,你可以在Appium中使用iOS类链定位器,如下图所示。

import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
. . .
private final By colorByClassChain = MobileBy.iOSClassChain ("**/XCUIElementTypeButton[`label == \"Colour\"`]");

Appium Inspector也有助于在Appium中提供预建的iOS类链定位器,如下图所示。

Appium Inspector IOS Class

创建类链表达式时的提示。

  • 你可以在你的表达式中连锁多个类,例如:。 **/< parent-class >/< target-class >

  • 你可以使用索引来从索引前的表达式确定的定位器列表中获得一个特定的元素,例如:。 < parent-class >[1]/< target-class >[2],其中父类的第一个实例将被使用,然后用这个表达式找到目标类的第二个实例。

  • 这里的索引从1开始,而不是通常的0索引。

  • 索引也可以是负数。例如,-1 表示元素列表中的最后一个元素。同样地,-2 表示倒数第二项。

  • 你也可以像使用索引一样将谓词字符串表达式与类链结合起来,例如: 。 `< target-class >[`name CONTAINS[cd] "text"`]`意味着它将找到其名称包含特定文本的目标元素,而不考虑其大小写。

  • 谓词字符串应该总是用刻度线括起来,如例子中所示。

  • 你也可以在同一表达式中结合索引和谓词字符串,在层次结构的任何一级,例如:。 `**/< parent-class >[`name BEGINSWITH "Some Text"`][-1]/< target-class >[3]` 这将被解释为找到父类列表中最后一个名称以某种文本开头的父类,并从该父类中找到目标元素的第三个实例。

如果你更喜欢XPath,Appium中的这个定位器是XPath的完美替代品,但它更稳定,是性能最好的定位器策略之一。这里也建议,如果你能使用IDAccessibility ID ,或者要求开发团队将这些ID添加到元素中,你应该使用它们而不是Appium中的类链定位器。

演示 在Appium中使用定位器

到目前为止,我们已经看到了代码片段。现在让我们看看GitHub上分享的演示项目中的页面对象和测试。你可以下载并克隆同样的东西,然后继续学习。

DriverManager类

这个类用于为不同的自动化类型创建驱动会话,以展示Appium中特定于该自动化类型的不同定位器。

在这里,我们创建了几个不同的创建驱动方法,为不同的自动化类型创建不同的驱动实例。只有Espresso驱动,我们有两个方法,一个用于本地执行,一个用于云。这是因为大多数云平台供应商还不支持Espresso驱动,由于这个原因,我们会遇到像Data matcher locator strategy not implementedView matcher locator strategy not implemented 的错误。

对于云测试执行,我们使用LambdaTest云平台,而对于本地执行,我们直接从这个类中启动和停止Appium服务器。

像LambdaTest这样的云测试平台提供了一个由3000多个真实设备和操作系统组成的在线设备场,通过云端Appium网格大规模地执行应用测试自动化。你可以在真实的Android和iOS设备上进行移动自动化测试

以下是你如何使用LambdaTest的真实设备云来执行Appium移动测试

您还可以订阅LambdaTest的YouTube频道,随时了解围绕Selenium测试Cypress测试、CI/CD等的最新教程。

页面对象

Android和iOS各有一个页面对象,以展示Appium中不同的定位器。首先,让我们看看AndroidLocators 页面对象。

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.MobileBy;
import lombok.Getter;
import org.openqa.selenium.By;
import org.openqa.selenium.json.Json;
 
@Getter
public class AndroidLocators {
   private final By animationItemViewMatcher = MobileBy.androidViewMatcher (new Json ().toJson (ImmutableMap.of ("name", "withText", "args", "Animation", "class", "androidx.test.espresso.matcher.ViewMatchers")));
   private final By appItemDataMatcher = MobileBy.androidDataMatcher (new Json ().toJson (ImmutableMap.of (
  "name", "hasEntry",
  "args", ImmutableList.of ("title", "App")
)));
   private final By colorByClassName = MobileBy.className ("android.widget.Button");
   private final By colorById = MobileBy.id ("color");
   private final By colorByUiSelector = MobileBy.AndroidUIAutomator ("new UiSelector().text(\"COLOR\")");
   private final By colorByXpath = MobileBy.xpath (".//android.widget.Button[@text='COLOR']");
}

这个类看起来非常简单,我们已经声明了所有Android特有的定位器。现在让我们来看看IOSLocator 页面对象。

import io.appium.java_client.MobileBy;
import lombok.Getter;
import org.openqa.selenium.By;
 
@Getter
public class IOSLocators {
   // Demoed with iOS.
   private final By colorByAccessibilityId = MobileBy.AccessibilityId ("color");
   private final By colorByClassChain = MobileBy.iOSClassChain ("**/XCUIElementTypeButton[`label == \"Colour\"`]");
   private final By colorByPredicate = MobileBy.iOSNsPredicateString ("label == \"Colour\" AND name == \"color\"");
}

在这里,我们也声明了Appium中所有的iOS特定定位器。

测试类

基础测试

在编写测试之前,我们有一个共同的BaseTest 类,它将被扩展到我们所有的测试。让我们看看我们在Base测试中有什么。

在这个类中,我们只有两个方法,一个是退出驱动会话,如果本地Appium服务器正在运行,则关闭它。同时,另一个方法用于使用传递给这个方法的定位器策略寻找元素,并打印出寻找元素所花费的时间,这样我们就可以确定哪个定位器策略是最快的,哪个是最慢的。

LocatorsAndroidEspressoTest

在这个类中,我们将测试特定于Espresso自动化类型的定位器。这个测试将在本地Appium服务器上执行。

我们还有一个类似的类,在LambdaTest云平台上运行。但如前所述,该类将失败。

LocatorsAndroidUiAutomatorTest

在这个测试中,我们将测试所有特定于UiAutomator2 Automation类型的定位器策略,以及Appium中一些常见的定位器。

LocatorsIOSTest

在这个类中,我们将测试所有iOS特定的定位器策略,用于XCUITest 自动化名称。

在XCUITest云上测试你的本地应用程序。现在就试试LambdaTest吧!

测试输出

当你执行我们之前看到的所有测试时,你会看到一个如下所示的输出。

Time taken by View Matcher: 36ms
Time taken by Data Matcher: 209ms
Time taken by Ui Selector: 230ms
Time taken by ID: 251ms
Time taken by Predicate: 266ms
Time taken by Class Chain: 272ms
Time taken by Class Name: 337ms
Time taken by Accessibility Id: 376ms
Time taken by Xpath: 517ms

这里提到了所有的定位器,从最快执行的到最慢的。

总结

现在,你可能对在Appium中何时使用哪个定位器有了一个合理的概念,这取决于你的需求。Appium中还有一些其他的定位器,在这篇关于Appium中的定位器的博客中被跳过了,因为它要么被废弃,要么是Appium的实验性功能。我希望你喜欢这篇博客,并从中学到一些新东西。

常见问题(FAQ)

Appium中使用的定位器是什么?

  • Appium中一些常用的定位器包括。
  • ID。
  • 可访问性ID。
  • 类名。
  • XPath。
  • Android UI Automator (UI Automator 2)
  • 安卓视图标签(仅Espresso)。
  • iOS UIAutomation

如何在Appium中检查定位器?

与其他移动自动化工具相比,Appium的独特之处在于它提供了使用非常简单的步骤来检查屏幕上任何元素的能力。这对识别UI元素有很大的帮助。下面的步骤解释了如何在Appium中检查你的移动应用元素。

  1. 在你的本地系统上启动你所需要的应用程序,然后启动Appium服务器。
  2. 打开你的浏览器并打开所需的应用程序的URL(http://127.0.0.1:4723)。
  3. 现在点击高级设置中的 "检查 "按钮(这将打开一个新窗口,屏幕上有所有元素)。
  4. 现在,只需点击应用程序视图中的元素,就可以在下一个面板中看到DOM/源,并在右侧看到所选元素的属性。完成了!

我如何在Appium中写XPath?

要在Appium中创建一个简单的XPath查询,点击Spy图标按钮以获得屏幕上所有对象的Native/Web属性。然后,右键点击一个或多个属性,然后点击复制XPath。