Android 平台使用 appium 自动化操作 webView 的经验

发布时间 2023-04-13 22:51:04作者: wkmcyz

更多内容见草稿: https://wkmcyz.notion.site/Appium-H5-c9c287855ef74ef0ae5d8f819da3923f

本文章主要介绍在 Android 平台上使用 appium 对 app 内的 webView 进行自动化操作上的一些知识,包括一些配置和可以进行的操作等。
需要读者:

  • 熟悉如何使用 appium 进行 native app 的自动化操作

自动化操作 App 内的 webview 的前置要求

注意: 这是针对某个 app 内的 webview 进行自动化操作;而不是使用 chrome 等浏览器应用里的 webview 进行操作。

  1. 本地有和手机系统的 webview 版本一致的 chromedriver 。
  2. app 的 webView 已经开启了 debug 模式。

这两者的具体操作方法为:

  • 如何获取 chromedriver?
  1. 命令行里执行 adb shell dumpsys package com.google.android.webview | grep versionName 可以获取当前设备的 webview 版本(有些时候会获得多个值,可以每个版本都试一下)。 或者在 “开发者选项” 里也可以查看到系统的 webView 版本。
  2. https://registry.npmmirror.com/binary.html?path=chromedriver/ 这个地址下载对应版本的 chromedriver。
  • 如何开启 app 的 webview 的 debug 模式?
    修改 app 代码实现,详情请搜索 setWebContentsDebuggingEnabled

在 appium 端,增加 desired_capabilities 信息:

# python
options.chromedriver_executable = '/tmp/chromedriver'
options.chrome_options = {
    # 待测试的 app package
    "androidPackage": YOUR_APP_PACKAGE,
}
# 省略其他的 driver capabilities 信息。
# 打开 app 的 webView 页面后,查看当前的 context。
print(driver.contexts)
# 切换到 webView 的 context
driver.switch_to.context("WEBVIEW_XXXX")

appium driver 可以执行的功能。

从 appium 调用 js

def run_js(driver,js_code):
    driver.execute_script(js_code)

run_js(driver, "console.log('123')")

获取 js 的 console 日志

logs = driver.get_log('browser')
for l in logs:
        print("log:", l)
# log: {'level': 'INFO', 'message': 'x.js 4:236286 "123"', 'source': 'console-api', 'timestamp': 1681386146296}
# log: {'level': 'INFO', 'message': 'x.js 4:236286 "123"', 'source': 'console-api', 'timestamp': 1681386146355}

获取页面信息

driver.page_source

寻找元素

  1. 使用 xpath 寻找

略过。

  1. 使用 css_selector 寻找。

如何使用 CSS Selector 获取元素

要使用 CSS 选择器获取元素,首先确保您处于 WebView 上下文,因为 CSS 选择器仅在 WebView 中可用。然后,使用 **`find_element_by_css_selector()`** 或 **`find_elements_by_css_selector()`** 方法查找元素。以下是一些使用 CSS 选择器查找元素的示例:

1. **通过 ID 获取元素**:
    
    ```
    pythonCopy code
    element = driver.find_element_by_css_selector("#element_id")
    
    ```
    
2. **通过类名获取元素**:
    
    ```
    pythonCopy code
    elements = driver.find_elements_by_css_selector(".element_class")
    
    ```
    
3. **通过标签名获取元素**:
    
    ```
    pythonCopy code
    elements = driver.find_elements_by_css_selector("tag_name")
    
    ```
    
4. **通过属性获取元素**:
    
    ```
    pythonCopy code
    element = driver.find_element_by_css_selector("tag_name[attribute='value']")
    
    ```
    
5. **通过子元素获取元素**:
    
    ```
    pythonCopy code
    element = driver.find_element_by_css_selector("parent_tag > child_tag")
    
    ```
    
6. **通过兄弟元素获取元素**:
    
    ```
    pythonCopy code
    element = driver.find_element_by_css_selector("sibling_tag1 ~ sibling_tag2")
    
    ```
    
这些仅是使用 CSS 选择器查找元素的一些基本示例。CSS 选择器提供了许多其他方法和组合,可以更具体地选择元素。在使用 CSS 选择器时,请确保您熟悉 CSS 选择器语法。

如何使用 CSS Selector 获取元素

通过属性获取元素是指使用 CSS 选择器根据元素的属性(如 idclassname 等)来查找页面上的元素。以下是一些基于属性的 CSS 选择器示例:

  1. 通过特定属性获取元素

    查找具有特定属性的元素,无论属性值是什么。例如,查找具有 data-custom-attribute 属性的所有元素:

    elements = driver.find_elements_by_css_selector("[data-custom-attribute]")
    
  2. 通过属性值获取元素

    查找具有特定属性值的元素。例如,查找 name 属性值为 username 的所有元素:

    elements = driver.find_elements_by_css_selector("[name='username']")
    
  3. 通过部分属性值获取元素

    查找属性值包含指定子串的元素。例如,查找 class 属性值包含 container 的所有元素:

    elements = driver.find_elements_by_css_selector("[class*='container']")
    
  4. 通过属性值前缀获取元素

    查找属性值以特定前缀开头的元素。例如,查找 href 属性值以 https:// 开头的所有链接:

    elements = driver.find_elements_by_css_selector("a[href^='https://']")
    
  5. 通过属性值后缀获取元素

    查找属性值以特定后缀结尾的元素。例如,查找 src 属性值以 .jpg 结尾的所有图片:

    elements = driver.find_elements_by_css_selector("img[src$='.jpg']")
    

这些示例展示了如何使用不同的属性选择器来查找元素。可以组合这些选择器以满足更复杂的选择需求。请注意,这些选择器仅适用于 WebView 上下文,因为它们是基于 CSS 选择器的。在原生应用上下文中,请使用其他定位策略。

获取元素位置

  • 通过上面的方法获取到目标元素以后,可以使用 element.rect 获取到其在 webView 里的宽高和位置坐标。
    webView 使用逻辑像素,跟物理像素有倍数差距。int(driver.execute_script("return window.devicePixelRatio;"))

    
    def get_device_pixel_ratio(driver) -> int:
        device_pixel_ratio = int(driver.execute_script("return window.devicePixelRatio;"))
        return device_pixel_ratio
    
    
  • webView 是可滑动的,获得的 x/y 需要考虑当前的滑动值。

  • 获取 element 在屏幕上的坐标 (没考虑 statusbar)

    def get_device_pixel_ratio(driver) -> float:
        device_pixel_ratio = float(driver.execute_script("return window.devicePixelRatio;"))
        return device_pixel_ratio
    
    def get_scroll(driver) -> Tuple[float, float]:
        scroll_x = float(driver.execute_script("return window.pageXOffset;"))
        scroll_y = float(driver.execute_script("return window.pageYOffset;"))
        return scroll_x, scroll_y
    
    def get_element_asb_location(driver, element) -> Tuple[float, float, float, float]:
        webview_elment_rect = element.rect
        webview_elment_height = webview_elment_rect['height']
        webview_elment_width = webview_elment_rect['width']
        webview_elment_x = webview_elment_rect['x']
        webview_elment_y = webview_elment_rect['y']
        print(webview_elment_rect)
        device_pixel_ratio = get_device_pixel_ratio(driver)
        scroll_x, scroll_y = get_scroll(driver)
        print(scroll_x, scroll_y)
        return (
            webview_elment_height * device_pixel_ratio,
            webview_elment_width * device_pixel_ratio,
            (webview_elment_x - scroll_x) * device_pixel_ratio,
            (webview_elment_y - scroll_y) * device_pixel_ratio,
        )