Skip to content

高级定位技巧 css定位与原生定位

高级定位技巧-css 定位与原生定位

简介

App 的种类

  • 原生App(Native app) : 基于Androidios平台官方的语言,工具进行开发
  • Web App :通过web 语言进行开发,依托浏览器运行。
  • 混合App(Hybrid App):同时使用了原生技术和HTML5技术。
  • 总结:原生速度最优,web app成本低,支持广,混合APP(Hybrid App) 开发周期短,功能更新快

原生定位

Android 系统原生定位

appiumAndroid 端调用其底层的 UIAutomator2 自动化测试框架去进行元素定位,借助UIAutomator API(UISelector类)来搜索特定元素。

IOS 系统原生定位

对于 9.2 及以下版本的 IOS 系统,苹果唯一的自动化技术为 UIAutomation ,它运行在Instruments中。

对于 9.3 及以上版本的IOS系统,苹果已经淘汰了 UIAutomation 工具,转而使用XCUITest 自动化技术,而对于 appium 而言,1.6 版本才开始支持 XCUITest

CSS Selector 定位

CSS 选择器是由 Web 开发领域的标准化组织 W3C(World Wide Web Consortium)制定和实现的。appium上使用的 CSS Selectorweb 上使用的大致相同,但是在实际使用上有些许差异。

应用场景

  • 原生定位使用场景:针对一些重视性能的场景时,建议使用原生定位,因为原生定位一般使用的是移动端系统的内嵌框架进行操作,其余定位方法其实都是对原生定位语法的调用。
  • CSS Selector 定位:针对复杂结构但是元素属性相对独立的应用,可以使用 CSS Selector定位。CSS Selector 着重于通过元素的属性进行定位。

Android 原生元素定位

单属性定位

  • 格式 'new UiSelector().属性名("<属性值>")'

  • 比如:'new UiSelector().resourceId("android:id/text1")'

注意:外面是单引号,里面是双引号,顺序不能变。

Python 示例:

# ApiDemos.apk Accessibility 控件

# ID定位
ass_id = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().resourceId("android:id/text1")'
)
print('ass_id', ass_id)

# 文本定位
ass_text = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().text("Accessibility")'
)

# className 定位
ass_classname = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().className("android.widget.TextView")'
)

Java 示例:

// ApiDemos.apk Accessibility控件

// ID定位
WebElement assId = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().resourceId('android:id/text1')"));
System.out.println("ass_id: " + assId);

// 文本定位
WebElement assText = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text('Accessibility')"));

// className 定位
WebElement assClassname = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().className('android.widget.TextView')"));

组合定位

多个属性同时确定元素的(多个属性任意组合 ,不限长度)。

Python 示例:

# App 控件
ass_mul = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().resourceId("android:id/text1").text("App")'
)

Java 示例:

// App 控件
WebElement assMul = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().resourceId('android:id/text1').text('App')"));

模糊匹配

  • 文字包含。
  • 文字以 x 开头。
  • 文字正则匹配。

Python 示例:

# 模糊匹配

# contains
ass_text_contains = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().textContains("ssi")'
)
print(ass_text_contains.text)

# start with
ass_text_contains_start = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().textStartsWith("Ani")'
)
print(ass_text_contains_start.text)

# match
ass_match_contains = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().textMatches("^Pre.*")'
)
print(ass_match_contains.text)

Java 示例:

WebElement assTextContains = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().textContains('ssi')"));
System.out.println(assTextContains.getText());

// start with
WebElement assTextContainsStart = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().textStartsWith('Ani')"));
System.out.println(assTextContainsStart.getText());

// match
WebElement assMatchContains = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().textMatches('^Pre.*')"));
System.out.println(assMatchContains.getText());

层级定位

  • 兄弟元素定位:fromParent
  • 父子结点定位:childSelector, 可以传入 resourceId() , description() 等方法。

Python 示例:

# 兄弟元素
ass_bro = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().text("App").fromParent(text("Text"))'
)
print('ass_bro', ass_bro)

# 父子元素
ass_fa = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().\
    className("android.widget.ListView").\
    childSelector(text("Text"))'
)
print('ass_fa', ass_fa)

Java 示例:

// 兄弟元素
WebElement assBro = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text('App').fromParent(text('Text'))"));
System.out.println("assBro" + assBro);

// 父子元素
WebElement assFa = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().className('android.widget.ListView').childSelector(text('Text'))'"));
System.out.println("assFa" + assFa);

滑动查找元素

在 Appium 中进行 Android 原生定位时,滑动查找元素是一种常见的操作,尤其是在面对长列表或需要查找位于屏幕之外的元素时。

"new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text('查找的元素文本').instance(0))"

CSS Selector 定位

使用 CSS 定位时,定位方式和 Web 自动化类似,语法如下:

查找单个元素

  • Python: driver.find_element(AppiumBy.CSS_SELECTOR, selector)
  • Java: WebElement element = driver.findElement(AppiumBy.cssSelector(selector));

查找多个元素

  • Python: driver.find_elements(AppiumBy.CSS_SELECTOR, selector)
  • Java: List<WebElement> elements = driver.findElements(AppiumBy.cssSelector(selector));

CSS 常见语法

定位方式 语法格式 描述
标签名 selector = "input" 表示查找所有的 input 元素
id selector = "#elementId" 表示查找 id 为"elementId"的元素。
"#elementId"中的"#"表示 id 属性。
class selector = ".elementClass" 表示查找 class 属性为"elementClass"的元素。
".elementClass"中的"."表示 class 属性。
属性定位 selector = "input[name='username']" 表示查找 name 属性为"username"的 input 元素。
可以根据元素的其他属性进行定位,如"value"、"type"等。
层级关系 selector = "div > input" 表示查找 div 下所有的 input 元素。
文本内容 selector = "button:contains('Submit')" 表示查找文本内容包含"Submit"的所有 button 元素。
:contains('Submit')表示匹配包含指定文本的元素。

CSS Selector 定位原理分析

CSS Selector 的定位表达式在传入 appium 服务器时通常会经过一系列解析步骤,最终被转换为原生定位的形式,以便在移动应用中准确地定位和操作元素。大致的解析过程如下所示:

  • 解析 CSS 选择器:Appium 解析器首先会分析和解释 CSS 选择器的语法和结构。
  • 映射到原生定位:解析器将 CSS 选择器映射到移动应用测试框架所支持的原生定位方式。
代码:
driver.find_element(AppiumBy.CSS_SELECTOR,\
    "#com\.xueqiu\.android\:id\/tv_search")

解析前:
{"using":"css selector",\
    "value":"#com\\.xueqiu\\.android\\:id\\/tv_search"}

解析后:
{"strategy":"-android uiautomator",\
    "selector":"new UiSelector().resourceId
    (\"com.xueqiu.android:id/tv_search\")",...}

由此可以看出,由于 CSS 选择器是针对 Web 开发涉及的,有时在移动应用中的解析过程可能不如直接使用移动应用测试框架提供的原生定位方式那样高效。因此,根据实际需求和应用特性,大多推荐使用 XPATH 或者原生定位的方式去对元素进行操作。

示例

场景:

  • 打开【雪球】应用首页
  • 点击搜索框
  • 向搜索框输入: alibaba
  • 判断【阿里巴巴】可见

代码实现

  • 使用 setup_method 函数创建 driver 建立连接并打开【雪球】应用首页。
  • 添加 teardown_method 函数关闭应用。

Python 示例:

class TestXueqiu:

    def setup_method(self):
        # 设置 cpability
        caps = {
            # 设置 app 安装的平台(Android,iOS)
            "platformName": "Android",
            # 设置 appium 驱动
            "appium:automationName": "uiautomator2",
            # 设置设备名称
            "appium:deviceName": "emulator-5554",
            # 设置被测 app 的包名
            "appium:appPackage": "com.xueqiu.android",
            # 设置被测 app 启动页面的 Activity
            "appium:appActivity": ".view.WelcomeActivityAlias",
            # 不清空缓存信息,保存登录信息
            "appium:noReset": True,
            # 强制app重启,整个测试运行之前重启 app
            "appium:forceAppLaunch": True
        }
        self.driver = webdriver.Remote(
            "http://localhost:4723",
            options=UiAutomator2Options().load_capabilities(cpas)
        )
        self.driver.implicitly_wait(15)

    def teardown_method(self):
        self.driver.quit()

Java 示例:

public class TestXueqiu {

    private static AndroidDriver driver;

    @BeforeAll
    public static void setUp() 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", "com.xueqiu.android");
        // 设置 app 的启动页
        caps.setCapability("appActivity", ".view.WelcomeActivityAlias");
        // 设置 app 不清空缓存
        caps.setCapability("appium:noReset", true);
        // 设置 app 不重启
        caps.setCapability("appium:shouldTerminateApp", true);

        URL remoteUrl = new URL("http://127.0.0.1:4723");
        driver = new AndroidDriver(remoteUrl, caps);
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(15));
    }

    @AfterAll
    public static void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}
  • 使用原生定位判断【阿里巴巴】是否可见:

Python 示例:

class TestXueqiu:

    def test_search(self):
        '''
        使用原生定位
        '''
        # 点击搜索框
        self.driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            'new UiSelector().resourceId("com.xueqiu.android:id/home_search")'
        ).click()
        # 输入 阿里巴巴
        self.driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            'new UiSelector().resourceId("com.xueqiu.android:id/search_input_text"'
        ).send_keys("阿里巴巴")
        # 获取当前页面包含文本 阿里巴巴 的元素
        alibaba_element = self.driver.find_element(
            AppiumBy.ANDROID_UIAUTOMATOR,
            'new UiSelector().text("阿里巴巴")'
        )
        # 判断属性为 true
        result = alibaba_element.get_attribute("displayed")
        # 断言最终结果正确
        assert result == 'true'

Java 示例:

@Test
public void testSearch() {
    // 使用原生定位
    // 点击搜索框
    WebElement searchButtonEle = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().resourceId('com.xueqiu.android:id/home_search')"));
    searchButtonEle.click();
    // 输入阿里巴巴
    WebElement searchInputEle = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().resourceId('com.xueqiu.android:id/search_input_text'"));
    searchInputEle.sendKeys("阿里巴巴");
    // 获取当前页面包含文本 阿里巴巴 的元素
    WebElement alibabaElement = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text('阿里巴巴')"));
    // 判断属性为 true
    String result = alibabaElement.getAttribute("displayed");
    // 判断【阿里巴巴】可见
    assert result.equals("true");
}
  • 同样的步骤,使用 CSS Selector 实现:

Python 示例:

class TestXueqiu:

    def test_search_withcss(self):
        '''
        使用 css 定位
        '''
        # 点击搜索框
        self.driver.find_element(
            AppiumBy.CSS_SELECTOR,
            "#com\.xueqiu\.android\:id\/home_search"
        ).click()
        # 向搜索框输入:阿里巴巴
        self.driver.find_element(
            AppiumBy.CSS_SELECTOR, "#com\.xueqiu\.android\:id\/search_input_text"
        ).send_keys("阿里巴巴")
        # 获取当前页面文本包含 阿里巴巴 的元素
        alibaba_element = self.driver.find_element(
            AppiumBy.CSS_SELECTOR, "*[text='阿里巴巴']"
        )
        # 获取可见属性
        result = alibaba_element.get_attribute("displayed")
        ## 判断【阿里巴巴】可见
        assert result == "true"

Java 示例:

@Test
public void testSearch() {
    // 使用 css 定位
    // 点击搜索框
    WebElement searchButtonEle = driver.findElement(AppiumBy.cssSelector("#com\\.xueqiu\\.android\\:id\\/home_search"));
    searchButtonEle.click();
    // 输入阿里巴巴
    WebElement searchInputEle = driver.findElement(AppiumBy.cssSelector("#com\\.xueqiu\\.android\\:id\\/search_input_text"));
    searchInputEle.sendKeys("阿里巴巴");
    // 获取当前页面包含文本 阿里巴巴 的元素
    WebElement alibabaElement = driver.findElement(AppiumBy.cssSelector("*[text='阿里巴巴']"));
    // 判断属性为 true
    String result = alibabaElement.getAttribute("displayed");
    // 判断【阿里巴巴】可见
    assert result.equals("true");
}

总结

  • Android 原生元素定位
  • IOS 原生元素定位
  • CSS Selector 定位