常见控件定位方法
常见控件定位方法
简介
在 Appium 中,控件定位是指通过一些特定的属性来定位和识别应用程序界面中的元素或控件。
当进行自动化测试时,需要与应用程序进行交互,例如点击按钮、输入文本、验证文本内容等。为了执行这些操作,就需要在应用程序的界面中准确定位所需的 UI 元素,例如按钮、文本框、标签等。
通过控件定位,自动化测试脚本可以在应用程序的不同界面中定位和操作特定的 UI 元素,以验证应用程序的功能和交互是否正常工作。控件定位是自动化测试的关键步骤之一,准确和可靠的控件定位对于成功执行自动化测试非常重要。
控件基础知识
Android 应用程序页面源码结构
Android 应用程序的页面源码结构通常使用 XML(可扩展标记语言)来定义界面布局。以下是 Android 页面源码的典型结构:
- 布局文件(Layout File):以
.xml
为扩展名的文件,定义了界面的布局结构、视图组件和其属性。 - 根元素(Root Element):通常是一个布局容器(如 LinearLayout、RelativeLayout、ConstraintLayout 等),作为根视图容器,包含其他视图组件。
- 视图组件(Views):例如 TextView、Button、ImageView 等,它们是界面中的可见元素,定义了元素的外观、行为和属性。
- 属性(Attributes):每个视图组件可以具有不同的属性,用于定义元素的特性,例如大小、位置、颜色等。
- 嵌套视图(Nested Views):可以在布局文件中嵌套其他视图组件,形成层次结构。
iOS 应用程序页面源码结构
iOS 应用程序的页面源码结构使用一种名为 XIB(Interface Builder)或 Storyboard 的文件格式来定义界面布局。以下是 iOS 页面源码的典型结构:
- XIB 文件或 Storyboard 文件:以.xib 或.storyboard 为扩展名的文件,包含了界面的布局和视图控件的配置信息。
- 视图控件(Views):例如 UILabel、UIButton、UIImageView 等,它们是界面中的可见元素,可以通过 XIB 或 Storyboard 文件进行配置。
- 连接线(Connections):通过连接线(Outlets 和 Actions)将视图控件与代码文件(ViewController)进行关联,以便进行交互和事件处理。
- 约束(Constraints):使用 Auto Layout 来定义视图之间的关系和自适应布局,以适应不同尺寸的设备和屏幕方向变化。
总之,Android 和 iOS 的页面源码结构都是用于描述界面布局和视图组件的配置信息。它们使用不同的文件格式和方式来实现,但目的都是定义应用程序的用户界面。
iOS 与 Android 页面结构的区别
- 节点结构类似
- 名字和属性命名不同
- Android 的 resourceid 和 iOS 的 name - Android 的 content-desc 和 iOS 的 accessibility-id
App 页面结构解析
- 节点 node
- 属性 attribute
- clickable - content-desc - resource-id - text - bounds
控件定位方式
在 Appium 中,控件定位是指通过一些标志性的特征来定位应用界面中的元素,以便进行自动化测试操作。Appium 支持多种控件定位方式,以下是一些常见的方法。
定位策略 | 描述 |
---|---|
Accessibility ID | 识别一个唯一的 UI 元素,对于 XCUITest 引擎,它对应的的属性名是 accessibility-id ,对于 Android 系统的页面元素,对应的属性名是 content-desc |
Class name | 对于 iOS 系统,它的 class 属性对应的属性值会以XCUIElementType 开头,对于 Android 系统,它对应的是 UIAutomator2 的 class 属性(e.g.: android.widget.TextView) |
ID | 原生元素的标识符,Android 系统对应的属性名为resource-id ,iOS 为name |
Name | 元素的名称 |
XPath | 使用 XPath 表达式查找页面所对应的 xml 的路径 |
控件查找方法
- 返回单个元素
WebElement
。 - 返回元素列表
[WebElement, WebElement, WebElement...]
。
# 返回单个元素 WebElement
driver.find_element(AppiumBy.xxx, "xxx属性值")
# 返回元素列表 [WebElement, WebElement, WebElement...]
driver.find_elements(AppiumBy.xxx, "xxx属性值")
常用定位方式
ID 定位
- 通过身份标识 id 查找元素
- 写法:
find_element(AppiumBy.ID, "ID属性值")
ACCESSIBILITY_ID 定位
- 通过 accessibility id 查找元素
- 写法:
find_element(AppiumBy.ACCESSIBILITY_ID, "ACCESSIBILITY_ID属性值")
XPath 定位
- 单属性定位:
//*[@属性名='属性值']
- 多属性定位:
//*[@属性名='属性值' and @属性名='属性值' ]
表达式 | 描述 |
---|---|
/ | 从根节点选取(取子节点)。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置(取子孙节点)。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
元素定位示例
- 使用 appium 官方 Demo apk 进行练习,apk 网盘地址。
- 安装 ApiDemo.apk。
- 打开应用。
- 定位文字为【App】元素。
- Python
# test_location.py
class TestLocation:
def setup_class(self):
# Capability 设置定义为字典
caps = {}
# 设置 app 安装的平台(Android、iOS)
caps["platformName"] = "Android"
# 设置 app 的包
caps["appium:appPackage"] = "io.appium.android.apis"
# 设置 app 启动页
caps["appium:appActivity"] = ".ApiDemos"
# 不清空缓存
caps["appium:noReset"] = True
# app 不重启
caps["appium:shouldTerminateApp"] = True
# 初始化 driver
appium_server_url = 'http://localhost:4723'
self.driver = webdriver.Remote(
appium_server_url,
options=UiAutomator2Options(). \
load_capabilities(caps)
)
# 设置隐式等待
self.driver.implicitly_wait(10)
def teardown_class(self):
# 退出driver
self.driver.quit()
def test_id(self):
# 通过 ID 定位
print(self.driver.find_element(
AppiumBy.ID,
"android:id/text1"
))
def test_aid(self):
# 通过 ACCESSIBILITY ID 进行元素定位
print(self.driver.find_element(
AppiumBy.ACCESSIBILITY_ID, "App"
))
def test_xpath(self):
# 通过 XPATH 进行元素定位
print(self.driver.find_element(
AppiumBy.XPATH,
"//*[@text='App']"
))
def test_xpath1(self):
# 通过 XPATH 进行元素定位
print(self.driver.find_element(
AppiumBy.XPATH,
"//*[@text='App' and @resource-id='android:id/text1']"
))
- Java
// LocationTest.java
package org.example;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.MobileCapabilityType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;
public class LocationTest {
private static AndroidDriver driver;
@BeforeAll
public static void setUpClass() throws MalformedURLException {
// 初始化capability
DesiredCapabilities caps = new DesiredCapabilities();
// 设置 app 安装的平台(Android、iOS)
caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
// 设置 appium 驱动
caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UiAutomator2");
// 设置设备名称
caps.setCapability(MobileCapabilityType.DEVICE_NAME, "emulator-5554");
// 设置 app 的包名
caps.setCapability("appPackage", "io.appium.android.apis");
// 设置 app 的启动页
caps.setCapability("appActivity", ".ApiDemos");
// 设置 app 不清空缓存
caps.setCapability("appium:noReset", true);
// 设置 app 不重启
caps.setCapability("appium:shouldTerminateApp", true);
// 设置启动url
URL remoteUrl = new URL("http://127.0.0.1:4723");
// 初始化driver
driver = new AndroidDriver(remoteUrl, caps);
}
@Test
public void idTest() {
// 通过 ID 定位
System.out.println(driver.findElement(AppiumBy.id("android:id/text1")));
}
@Test
public void aidTest() {
// 通过 ACCESSIBILITY ID 进行元素定位
System.out.println(driver.findElement(AppiumBy.accessibilityId("App")));
}
@Test
public void xpathTest() {
// 通过 XPATH 进行元素定位
System.out.println(driver.findElement(AppiumBy.xpath("//*[@text='App']")));
}
@Test
public void xpath1Test() {
// 通过 XPATH 进行元素定位
System.out.println(driver.findElement(AppiumBy.xpath("//*[@text='App' and @resource-id='android:id/text1']")));
}
@AfterAll
public static void tearDownClass() {
// 退出driver
driver.quit();
}
}
总结
- 控件基础知识
- 控件定位方式
- 控件查找方法