ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Appium WebView自动化测试全攻略:环境搭建与chromedriver版本匹配指南

Appium WebView自动化测试全攻略:环境搭建与chromedriver版本匹配指南 1. WebView自动化测试的整体思路与前置判断1.1 为什么WebView让UI自动化“卡脖子”先聊一个很多人在Appium实战里都会撞上的现象原生页面定位元素稳得很一到内嵌H5页面就抓瞎dump出来的控件树里什么都没有要么只剩一个WebView的框要么干脆报错。原因不复杂——Android原生页面走的是View体系Appium通过UIAutomator2可以拿到完整的控件层级但WebView是Chromium内核渲染出来的H5页面里面的按钮、输入框、列表项都不是原生ViewUIAutomator2根本摸不到它们。Appium的解决方案是检测到页面里有WebView之后自动挂上一个chromedriver通过Chrome DevTools ProtocolCDP去和WebView通信这样就能拿到DOM树、执行JS、模拟点击输入。也就是说WebView自动化本质上不是Appium的原生能力而是Appium调起chromedriver的能力。这一层的依赖链一旦没打通后面写再多脚本都是白搭。这一期我重点讲的就是这条链路怎么搭基础环境怎么准备、被测App的WebView怎么打开调试开关、chromedriver和WebView版本怎么匹配、context怎么切换、元素怎么定位最后给一套可以直接照抄的Python脚本。适合刚入门Appium、或者已经在做原生自动化但被WebView卡住的同学。1.2 自动化WebView的核心链路先明确整个流程后面所有配置和代码都是围绕这条链路展开的。以一台Android真机为例Appium通过uiautomator2 driver启动被测App。Appium检测到WebView组件出现在当前页面层级里并且WebView处于可调试状态。Appium根据chromedriverExecutable配置启动对应版本的chromedriver。chromedriver通过CDP协议与WebView建立连接。测试脚本把上下文从NATIVE_APP切换到WEBVIEW_包名。在WebView上下文里脚本可以通过CSS选择器、XPath等Web定位方式操作页面元素。注意第2步和第3步这两步是大多数人失败的根源。第2步需要WebView开启调试模式第3步需要chromedriver版本和WebView内核版本严格匹配。后面会用单独的章节展开讲。1.3 什么时候用WebView方案什么时候换思路不是所有内嵌H5页面都适合走这条链路动手之前先做个判断。如果页面是原生WebView包括X5内核这种基于Chromium改造的可以走chromedriver方案如果页面是Flutter渲染的或者小程序这种自定义渲染引擎chromedriver方案基本无效需要换成Flutter Driver或者小程序专用的自动化框架如果只是想在原生层判断WebView加载成功没有那用UIAutomator2查WebView节点的存在性就够不需要切context。实战里还有一类场景被测App里有大量H5页面但WebView复用同一个实例切换页面时DOM变了、context没变脚本需要重新等待关键元素出现不能想当然地认为切一次context就一劳永逸。这个判断放在最前面能帮你省掉很多无效调试时间。2. 环境准备从零搭起Appium WebView自动化2.1 基础组件清单先过一遍基础环境按下面这张表准备缺哪个补哪个组件版本建议用途Node.js16.x及以上Appium运行依赖JDK8或11Android SDK编译与签名工具依赖Android SDKAPI 28及以上adb、uiautomator等基础工具Appium2.x1.x也可自动化服务端Appium Inspector最新版元素定位辅助工具Python或Java按团队技术栈选测试脚本语言我个人的习惯是用Appium 2.x因为它的驱动管理比1.x清晰很多chromedriver的版本管理也更好控制。如果你还在用Appium 1.x建议尽快迁移1.x的chromedriver自动下载逻辑在一些老版本里经常出莫名其妙的问题。2.2 Appium 2.x安装与驱动配置Appium 2.x安装很简单一条npm命令搞定npm install -g appium装完之后需要单独安装平台驱动Android这边安装uiautomator2驱动appium driver install uiautomator2装完可以用appium driver list确认驱动状态。启动服务用appium命令默认监听4723端口。如果是一台干净的机器装安卓SDK时记得把platform-tools和build-tools都装上这俩目录里的adb、aapt、zipalign等工具后面都会用到。这里有个小细节很多人在Windows上装了Appium但是命令行找不到多半是Node.js的全局bin目录没加到PATH里。npm全局安装路径可以通过npm prefix -g查把对应的bin目录加进PATH即可。2.3 被测App的WebView调试开关这是整个环境搭建里最重要的一步也是最容易忽略的一步。Android系统有个安全策略WebView默认不允许外部调试。如果被测App没有显式打开调试开关Appium的chromedriver连上WebView之后会发现连接被拒绝或者压根检测不到WebView context。如果你能拿到自家App的源码在WebView初始化的地方加一行WebView.setWebContentsDebuggingEnabled(true);注意这是一个静态方法只需要调用一次全局生效。常见做法是在Application的onCreate里调用或者在所有WebView创建之前的某个初始化点调用。调试模式下API 19的WebView都会开放DevTools协议端口Appium才能接管。如果你拿不到源码比如测试第三方App那WebView自动化基本做不了多少——除非对方App自身开启了可调试状态或者你通过自己维护的测试包来绕过限制。这里我不展开那些非常规手段合规地讲WebView自动化最稳妥的落地方式是让开发在测试包/灰度包里打开调试开关正式包保持关闭。我见过一些团队直接在线上包开了调试这在生产环境是很危险的等于把页面DOM完全暴露给任何连上设备的人。3. 版本检测与chromedriver配置3.1 为什么版本匹配是WebView自动化的“命门”chromedriver和WebView内核版本不匹配会直接导致自动化起不来。说白了两者之间的关系chromedriver是Chrome/Chromium内核的调试代理它必须说和内核版本一致的方言版本差异大了连会话都建不起来。报错通常是session not created、unable to connect to renderer或者unknown error: cannot connect to chrome at localhost:xxxxx。Appium自己也做自动匹配但它能匹配的前提是你把chromedriver的镜像源配好而且你的WebView版本在它已知的版本列表里。实际测试中国产ROM对WebView的定制程度很高比如Android 8.1这种老版本机型经常会出现系统WebView版本和Appium默认chromedriver列表对不上的情况。所以我把版本检测单独拉出来讲这是WebView自动化最有价值的一段经验。3.2 检测WebView真实版本的几种方法先说怎么查系统WebView版本。最直接的方式是用adbadb shell dumpsys webviewupdate输出里会显示当前WebView provider和版本号比如Current WebView package (name, version)。这一行能告诉你系统用的WebView到底是哪个包、哪个版本。如果你连的是真机WebView版本一般跟随系统更新或Google Play更新如果是测试机禁用了WebView更新版本就停留在出厂状态。我遇到过一台Android 8.1的测试机系统WebView版本是58.x对应的chromedriver应该是2.43左右但很多人直接装了新版的chromedriver连怎么挂都挂不上。另一种方法是用chrome://inspect。打开Chrome浏览器地址栏输入chrome://inspect如果设备连着USB调试页面里会列出所有可调试的WebView实例。点开某个实例的inspect链接Chrome DevTools里可以直接看到网页的DOM、网络、Console。这个方法不仅能确认WebView能不能被调试还能顺便看页面的真实DOM结构对后面写定位器帮助极大。用chrome://inspect还有一个隐藏好处能看到WebView的title和URL。我在一次实测里发现某个大型社交App内的活动页其实就是一个WebView壳子包着H5页面URL里的scheme是类似snssdk1128://webview?urlxxxx这种自定义协议。这种情况下用Appium的driver.contexts都能看到WEBVIEW_xxx但你不知道它加载的具体是哪个页面用chrome://inspect能直接看到真实地址和DOM。3.3 把chromedriver配置进AppiumAppium 2.x里配置chromedriver是在desired capabilities里指定常用的几个capability作用chromedriverExecutable指定单个chromedriver文件路径chromedriverExecutableDir指定chromedriver目录Appium按需选择chromedriverChromeMappingFile自定义版本映射文件chromedriverUseSystemExecutable是否优先用系统PATH里的chromedriver实际落地我建议不要只用单个chromedriverExecutable而是准备一个chromedriver目录里面放多个版本然后让Appium按需匹配。比如目录结构/opt/chromedrivers/ ├── chromedriver_2.43 ├── chromedriver_2.46 ├── chromedriver_76.0.3809.68 └── chromedriver_116.0.5845.96然后在desired capabilities里写chromedriverExecutableDir: /opt/chromedrivers/有多个版本兜底之后Appium找到合适版本的概率高很多排查问题的时候也方便手动换版本验证。版本获取的官方渠道就是ChromeDriver下载页把自己WebView的版本号输入进去找对应的大版本号。记住一个规律chromedriver的分发版本和Chrome/WebView版本号是强对应的不要指望跨好几个大版本还能正常工作偶尔能通是你运气好严格讲这种状态不可维护。4. 实操跑通一个WebView页面的完整流程4.1 最小可用的desired capabilities配置下面给一套我在真实项目中用过的配置基于Python和Appium-Python-Clientfrom appium import webdriver desired_caps { platformName: Android, deviceName: emulator-5554, appPackage: com.example.app, appActivity: .MainActivity, automationName: UiAutomator2, noReset: True, chromedriverExecutableDir: /opt/chromedrivers/, chromeOptions: { androidDeviceSerial: emulator-5554, androidPackage: com.android.webview, } } driver webdriver.Remote(http://127.0.0.1:4723, desired_caps)注意chromeOptions里有两个关键字段androidDeviceSerial是设备序列号androidPackage是WebView的包名。如果系统WebView是com.android.webview就填这个如果是Chrome稳定版则填com.android.chrome。这个信息可以通过adb shell dumpsys webviewupdate里的Current WebView package确认。4.2 context切换与元素定位WebView自动化区别于原生自动化的核心操作就是context切换。先看当前有哪些上下文print(driver.contexts)正常情况下输出类似[NATIVE_APP, WEBVIEW_com.example.app]如果没有WEBVIEW_xxx说明WebView没有开启调试或者页面还没加载完成这时候先等一下再刷新一次import time time.sleep(3) print(driver.contexts)切到WebView上下文之后定位方式就从find_element_by_id变成了Web方式的定位driver.switch_to.context(WEBVIEW_com.example.app) # 通过CSS选择器定位 element driver.find_element(css selector, #login-btn) # 通过XPath定位 element driver.find_element(xpath, //input[placeholder请输入手机号])元素操作和原生差不多click()、send_keys()都用得上。但有几个需要特别注意的点WebView上下文里不能用driver.page_source去看原生控件树它拿到的是DOM结构两者是两套体系。页面里如果有iframe定位的时候要先用switch_to.frame()切进iframe否则会定位不到。页面如果是SPA单页应用DOM会频繁变元素过期问题比原生页面严重建议优先用稳定的CSS选择器或XPath尽量避免用index定位。4.3 一份可以直接抄作业的WebView脚本下面这段脚本演示了从启动App到WebView页面完成一次登录操作和断言的全过程核心步骤都加了注释import time from appium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By desired_caps { platformName: Android, deviceName: emulator-5554, appPackage: com.example.app, appActivity: .MainActivity, automationName: UiAutomator2, noReset: True, chromedriverExecutableDir: /opt/chromedrivers/, chromeOptions: { androidDeviceSerial: emulator-5554, androidPackage: com.android.webview, } } driver webdriver.Remote(http://127.0.0.1:4723, desired_caps) # 等待WebView出现并切换上下文 WebDriverWait(driver, 30).until( lambda d: len(d.contexts) 1 ) webview_context next(ctx for ctx in driver.contexts if ctx.startswith(WEBVIEW_)) driver.switch_to.context(webview_context) # 等待关键元素出现 phone_input WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.CSS_SELECTOR, input[namephone])) ) phone_input.send_keys(13800138000) driver.find_element(By.CSS_SELECTOR, input[namepassword]).send_keys(123456) # 点击登录 login_btn driver.find_element(By.CSS_SELECTOR, button#loginBtn) login_btn.click() # 断言登录成功后的文案 WebDriverWait(driver, 20).until( EC.text_to_be_present_in_element((By.CSS_SELECTOR, .user-name), 测试用户) ) print(登录成功页面跳转正常) driver.quit()这段脚本里的WebDriverWait是从Selenium继承过来的能力Appium WebView上下文里完全兼容。强烈建议用显式等待而不是time.sleep()去等元素尤其是WebView场景页面渲染速度受网络影响波动很大固定sleep要么等不够、要么等太久。我实测跑这段脚本时踩过一个坑contexts列表第一次获取的时候只有NATIVE_APP因为WebView还没加载出来。用WebDriverWait去等len(d.contexts) 1这个条件是最稳的比固定sleep五秒可靠得多。5. 常见问题与排查技巧实录WebView自动化的坑远比其他模块多我把这几年遇到的问题整理成一个排查表按出现频率排序现象可能原因排查步骤contexts列表里没有WEBVIEW_xxxWebView未开启调试页面没加载完chromedriver连接失败1. 确认setWebContentsDebuggingEnabled(true)已调用 2. 用chrome://inspect确认可调试状态 3. 查看Appium日志中chromedriver启动情况报错session not createdchromedriver版本与WebView版本不匹配1. dumpsys查WebView版本 2. 换成对应版本的chromedriver 3. 确认映射文件配置正确能切到WebView上下文但元素定位不到页面在iframe里元素被遮挡SPA渲染未完成1. 检查是否需switch_to.frame2. 用chrome://inspect确认元素真实存在 3. 用显式等待unknown error: cannot connect to chromechromedriver启动失败或端口冲突1. 手动启动chromedriver加--verbose看日志 2. 换端口 3. 确认设备只有一个调试通道页面白屏或加载失败第三方App的WebView限制网络问题X5内核兼容性1. chrome://inspect看Console报错 2. 检查网络代理 3. 确认内核是标准Chromium挑两个最典型的展开说。session not created这个报错我见过太多次。曾经有个项目在Android 8.1上跑系统WebView版本是58我一开始用了chromedriver 2.40Appium一直报session not created。后来我一个个版本试过去发现2.43才能稳定连上。这个排查过程很折磨人但一旦确定了映射关系后面就好办了。建议把设备型号、系统版本、WebView版本、chromedriver版本四者的对应关系记成一份文档团队里共享能帮后面的人省很多时间。元素定位不到这个坑也很常见。有一次脚本里定位一个按钮始终报找不到用chrome://inspect打开一看按钮在一个iframe里。解决方式是先切frame再定位driver.switch_to.frame(driver.find_element(By.CSS_SELECTOR, iframe#modal))切完之后别忘了操作完要切回主文档否则后续定位会一直失败driver.switch_to.default_content()另外一些活动页会用position: fixed的遮罩层盖住按钮元素客观存在但点击被拦截。遇到这种情况可以先执行JS强制点击driver.execute_script(arguments[0].click();, element)这个方法可以用来绕过遮挡层也能解决某些元素click不生效的问题但注意它绕过了用户真实操作路径断言逻辑里要慎重使用。根据我个人经验WebView自动化里真正难的从来不是写脚本而是环境匹配和版本管理。把WebView调试开关、chromedriver版本映射、context切换这三件事理顺了一套WebView自动化框架就已经成功了一大半。最后再分享一个小技巧脚本里尽量少用绝对等待所有关键节点都套上WebDriverWait如果发现某次跑挂是网络原因导致元素加载慢优先加等待条件而不是加sleep。这样脚本的稳定性能提升一个量级维护成本也会低很多。
返回列表