十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

微信小程序UI自动化测试实践:从技术选型到稳定性优化

微信小程序UI自动化测试实践:从技术选型到稳定性优化 有一段时间我们团队发布微信小程序版本前最怕听到一句话“回归过了吗”。十几个核心页面登录、首页流转、列表筛选、下单、支付回调手工点一遍少说也得大半天临发版前改一行样式都得重跑。后来我把这套回归流程改造成了微信小程序UI自动化测试情况才真正好转。这篇博客就把整个实践过程写出来包括技术选型、环境准备、脚本编写、真机执行和稳定性优化适合正在做小程序质量保障的测试开发也适合想在小程序工程里补自动化能力的前端同学参考。我踩过的坑不少一开始以为小程序UI自动化就是Web自动化换个入口结果一跑就废后来老老实实研究了小程序的双线程架构、渲染机制和开发工具提供的自动化能力才把链路真正打通。下面按实操顺序讲能帮你省掉至少两个礼拜的试错时间。1. 小程序UI自动化为什么不能直接套Web自动化框架先说结论把桌面Web那套Selenium/Playwright脚本直接搬到小程序里绝大部分是跑不起来的。这不是工具不够好而是小程序的技术底座和普通页面有本质区别。1.1 双线程架构决定了自动化技术底座不同普通网页的逻辑、渲染和DOM都在同一个WebView进程空间里自动化测试框架通过浏览器驱动协议就能定位元素、模拟点击。小程序的运行架构是双线程模型逻辑层负责业务数据与生命周期渲染层负责界面展示两边通过一套异步消息机制通信。你在页面里看到的WXML节点并不是传统意义上浏览器能直接操作的DOM树而是小程序自己维护的一套组件树。这就导致两个直接影响传统WebDriver协议没法直接“看到”小程序的内部页面结构更没法对组件树做稳定操作不同宿主环境Android、iOS、开发工具对小程序渲染层的实现细节有差异脚本必须要有一层适配能力。所以做小程序UI自动化第一步不是选框架而是承认“要换一种技术思路”。目前能大规模落地的方案基本都依赖微信开发者工具对外暴露的自动化接口或在真机环境里的特殊调试通道围绕这些通道做封装而不是直接照搬Web自动化那套注入逻辑。1.2 页面结构、原生组件和手势操作带来的额外变量小程序里除了普通视图组件还有一类原生组件video、map、canvas、live-player、camera它们不是由渲染层直接绘制的而是由客户端原生能力渲染并且层级永远在最上层。这个特性在UI自动化里非常要命具体表现是用常规元素定位方式去查video内部的播放按钮可能查不到因为它不在渲染层节点体系里即使页面结构里能看到组件实际点击事件也可能被原生层拦截canvas、map这类组件有大量手势操作单靠tap模拟很难覆盖真实用户行为。另外小程序里的滚动容器、可拖拽排序、单选框选择、顶部导航栏吸顶等交互和浏览器里的实现逻辑也不完全一致尤其要注意滚动到底部后元素的坐标会变化脚本里不能写死坐标点。这个小差异后面实操部分我还会反复提到。还有一个很多人忽略的变量小程序的页面栈机制。小程序中页面的前进、返回、tab切换都有专用API自动化脚本如果在Web框架里用“浏览器back”按钮会让页面栈状态错乱。后面我会用reLaunch、navigateTo这些业务路由方法来代替。2. 工具选型对比官方方案、Appium、Airtest到底怎么选我在决定做小程序自动化时微信生态里还没有“一个官方大而全的测试平台”大家的做法大致分成三条路线。这里直接给你结论和对比。2.1 三种主流路线的能力与局限先看市面上用得比较多的三种方案方案核心原理支持范围上手难度稳定性微信官方自动化SDKminiprogram-automator / MiniTest通过开发者工具的自动化接口连接小程序运行时拿到页面实例和组件树开发工具模拟器为主部分能力支持真机低Node.js编写中高依赖工具版本Appium WebView/UIAutomator通过移动端驱动框架拉起微信App再尝试进入小程序上下文Android/iOS真机高环境复杂中小程序内上下文不稳定Airtest图像识别 UI层级识别脚本可跑在真机和模拟器跨App场景中中图像匹配受机型影响2.2 我最终采用的组合与选型依据我做的是业务小程序的质量回归场景特征是页面数量多、组件类型杂、版本迭代快但核心流程相对固定。所以我选了第一套方案微信官方自动化能力 Node.js脚本再围绕它写自己的封装库。选官方这条路的核心理由有三个元素定位最准确。它直接解决了我前面说的组件树问题能以小程序运行时视角拿到真实节点在页面里能通过选择器查到element对象这不是图像识别能比的稳定性等待机制好控制。它提供页面级、元素级的等待方法能等待某个选择器出现在组件树里再继续操作这正是小程序异步渲染场景下最需要的不需要处理安卓/iOS底层的系统差异。跑自动化的大头在开发工具模拟器里版本验证和核心回归都能完成。但也要说清楚局限官方这套能力的本质是“连接开发者工具”不是直接连线上用户微信自动跑。真机测试时需要借助开发工具提供的真机调试能力并且要保证测试微信账号登录了开发者账号。如果你的需求是“同时拉起App和小程序做跨端流程”比如从App分享卡片跳小程序那确实还得引入Appium或Airtest做外层驱动。我当时把外层拉起和授权这种操作单独交给Airtest进入小程序页面后的业务流程断言再用官方SDK跑两个方案做成互补不互斥。2.3 环境准备开发者工具上必须开的开关这一步很多人会漏导致脚本和工具连不上。微信开发者工具默认不允许外部自动化进程连接必须手动打开服务端口。具体操作路径是打开微信开发者工具 - 设置 - 安全设置 - 打开“服务端口”开关。打开之后工具会在本机监听一个端口我们脚本里的automator才能通过本地端口连上去。这个端口不是公网开放端口只是本机的自动化调试通道不会影响正常开发流程。环境上我还建议做这几项准备使用独立的小程序测试工程目录不要直接在正在改需求的分支上跑自动化开发者工具保持登录状态同时在“通用设置”里开启“不校验合法域名、web-view、TLS版本以及HTTPS证书”这类开发调试项避免接口环境把脚本挡在门外Node.js环境建议用16以上版本miniprogram-automator本身对Node版本没特别严格要求但项目中常常要配合其他依赖一起用高版本更省心基础库版本尽量和线上用户灰度版本保持一致否则某些组件的select结果会有偏差。我第一次跑连接脚本时卡了很久最后发现就是服务端口没开。这类问题不在框架层面但会直接劝退一半新手所以单独拎出来讲。3. 从连接开发工具到跑通首个用例的完整实操下面进入正题。我用miniprogram-automator做例子因为它是目前社区资料最多的官方底层库。MiniTest本质上也是基于这套能力封装的你在理解原理后可以随时替换。3.1 初始化项目与启动最小自动化用例初始化一个自动化测试项目其实比我想象中简单mkdir mini-program-ui-test cd mini-program-ui-test npm init -y npm install -D miniprogram-automator然后创建第一个脚本test-home.jsconst automator require(miniprogram-automator); (async () { const miniProgram await automator.launch({ projectPath: /Users/你的账号/workspace/你的小程序目录, cliPath: /Applications/wechatwebdevtools.app/Contents/MacOS/cli }); await miniProgram.reLaunch(/pages/home/index); const page await miniProgram.currentPage(); await page.waitFor(.product-card, 5000); const card await page.$(.product-card); console.log(商品卡片文本, await card.text()); await miniProgram.close(); })();这里解释几个关键点automator.launch会拉起开发者工具并打开指定项目cliPath是指向开发者工具内置命令行工具的路径Mac和Windows写法不同Windows下通常是安装目录里的cli.batreLaunch关闭所有页面再打开指定页面适合用例开始时重置页面栈currentPage()拿到的是当前页面实例后续所有元素定位都基于它waitFor是我个人的最佳实践之一它表示在5秒内持续等待某个选择器出现在页面里等不到就抛错。小程序页面经常有首屏异步加载不等待直接查元素是自动化脚本不稳定最大的来源之一。跑第一个用例时建议先打印“能不能成功拉起工具”这个结果工具能起来链路就通了三分之一。3.2 元素定位的三种写法与选择器设计跑通了连接下一步就是稳定地定位元素。miniprogram-automator支持的选择器没有浏览器里那么丰富但覆盖日常业务绰绰有余。我常用的有三种第一种是page.$单元素查询传入CSS类名或标签选择器。const submitButton await page.$(.submit-btn);第二种是page.$$查找一组元素适合列表、宫格、轮播图这类结构。const menuItems await page.$$(.nav-item); console.log(导航项数量, menuItems.length);第三种是page.$xpath用XPath定位适合“按文本内容找元素”的场景。前提是页面元素结构稳定不要在XPath里写太深的绝对路径。const confirmBtn await page.$xpath(//view[contains(class,dialog) and contains(text(),确定)]);但光会这些写法还不够真实项目里最大的痛点往往是“选择器不稳定”。我的建议是在业务组件的WXML上增加专用属性。比如商品卡片统一写成view classproduct-card>const productItem await page.$xpath(//view[data-automationproduct-item]);为什么这么做因为我测试过的很多后端项目里CSS类名经过样式调整、代码压缩后可能变化而 text 内容随着推荐策略变化更不可控。数据自动化属性能让开发和测试达成约定是“把不确定性前置解决”的思路。3.3 点击、输入、滑动背后的操作细节元素定位到了操作层才是真正的暗坑区。我总结几个高频操作的最小可用写法。点击操作await card.tap();这一步看起来简单但如果页面同时有多个同样class的元素tap()可能会报“元素不唯一”或点错位置。解决办法是先用xpath精确匹配先把目标缩小到一个再做点击。另外按钮如果是button原生组件它的点击事件需要等绑定函数执行完建议tap之后立刻插入一个等待断言比如等待某个跳转后页面出现再继续不要盲目sleep太久。输入操作const input await page.$(.search-input); await input.input(保温杯);输入操作在聚焦和键盘弹出方面和Web不一样尤其在真机里手机软键盘会遮挡页面下半部分区域导致后续元素根本不在可视区内。脚本层能做的就是输入完主动收起键盘或者把目标元素滚动到可视区域再操作。开发工具模拟器问题小一些但脚本要想跨环境跑必须把“滚动到可视区域”做成公共方法。滑动操作await page.evaluate(() { wx.pageScrollTo({ scrollTop: 1000, duration: 200 }); });这里要注意小程序的wx.pageScrollTo作用的是页面滚动而不是某个容器内部滚动。容器滚动我一般通过第三方组件暴露的scroll-view属性和事件去控制或者在自动化里锁定容器节点后连续模拟多个触摸点去实现真实手势。真实项目里“上拉加载更多”这种交互如果只是调用pageScrollTo到底部再等接口返回其实也能断言出列表页数变多这样写简单稳定不要执着于复刻用户手指轨迹。3.4 把授权弹窗、地理位置这类变数变成可控条件UI自动化里最怕遇到系统级弹窗因为用户点击“允许”“拒绝”时弹窗已经超出了小程序页面组件树的管辖范围用页面元素定位方式根本点不到。授权弹窗恰恰是登录流程里绕不过去的环节。我在实践中发现处理这类问题的惯例不是硬点击系统弹窗而是绕开它在小程序逻辑里把授权判断封装成一个统一函数自动化环境下走白名单逻辑直接返回用户数据更合理的做法是拦截小程序API的调用模拟成功回调。miniprogram-automator提供mockWxMethod能力比如把wx.getLocation的返回值固定成一个测试坐标。await miniProgram.mockWxMethod(getLocation, () { return { latitude: 39.915, longitude: 116.404, speed: -1, accuracy: 30 }; });注意我的写法这里mock的不是原始wx实现在测试期间生效自然就不需要系统弹窗。真机场景下的首次授权如果必须覆盖我的建议是把它放到用例的前置引导步骤里人工点一次后清除授权再跑后续逻辑。不要试图用图像识别去点“允许”按钮因为不同机型、不同语言环境、不同微信版本的按钮位置差异能让你维护到怀疑人生。4. 真机执行、多机型适配和上报策略开发工具模拟器里脚本跑得再顺真机永远会给你来点“惊喜”。我在实践阶段就遇到模拟器空跑通过、真机画面直接卡在启动页的情况。4.1 真机为什么和模拟器表现不一致我自己总结出来的差异有这几类渲染性能不同。模拟器用的是电脑CPU和GPU真机渲染页面有真实性能瓶颈。列表图片多的时候真机上waitFor的时间阈值要调得更宽松触摸反馈不同。模拟器里鼠标点击的响应速度远快于真实手指触摸一些双击、长按、滑动操作在模拟器上的坐标计算和真机手势有偏差原生输入法影响。这个我在前面提到过真机上输入操作必然弹出微信键盘键盘会压缩webview可视区域元素坐标一旦超出新视口就执行不了视频、地图组件的实现差异。比如video组件在不同iOS版本上全屏方向切换时可能出现画面错位这个本身是业务要修的bug但它也会打断自动化脚本后续的点击流程。真机回归建议只在发版前跑关键冒烟用例不要每天全量跑。每天全量跑还是放在开发工具模拟器上效率最高。4.2 多机执行时如何减少不稳定结果如果团队有真机机房或者云测平台可以尝试做“一部手机绑定一个账号、一套稳定网络”的真机任务每一位机型的差异缩小到可控范围。否则你分析失败用例时会发现一半是业务bug另一半是环境干扰。我在真机执行实际跑过两大类方案一类是开发工具“真机调试”通道。通过开发者工具在手机上打开调试模式后自动化脚本能通过工具局域网通道连接真机上的页面运行时。这类方案的优点是不用写Appium那套系统层配置缺点是对网络环境有要求并且每次运行前都要人工扫码确认。另一类是Appium 微信宿主 Airtest图像识别组合。这种适合“必须从手机桌面启动微信再一步步进入小程序”的完整链路。但进入小程序内部后的元素定位没那么稳因为微信对小程序webview的上下文做了封装外部框架经常拿不到内部DOM。所以我的策略是尽量保留官方通道的优势把Airtest限制在外层应用启动和系统弹窗处理上。无论哪种方案建议数据上报做全。脚本执行结果、失败截图、页面栈信息、关键元素截图这些都要留档。我习惯在每个用例结束后自动保存当前页面路径和页面截图分析失败时会节省大量时间。5. 高频问题和稳定性优化实录自动化跑起来容易稳定跑一个月才难。这一节我把实践中遇到的高频问题和排查思路逐条罗列出来后面再讲三条稳定性经验。5.1 一张表搞定连接与元素定位高频问题现象大概率原因排查思路脚本连不上开发者工具服务端口没开 / 工具没登录到设置里重新确认“服务端口”开关重启工具运行中卡在 paused in debugger工具里打开了断点调试主线程被挂起关闭调试器断点重新运行脚本元素一直定位不到页面还没渲染完或使用了v-if控制且条件未满足加waitFor到页面里看WXML节点结构是否和预期一致用xpath按文本找到了却点不到命中了多个节点或父节点有事件冒泡把xpath再收紧一级比如限定到最近的button/view容器真机上video组件黑屏、不能播放真机播放策略或视频源限制先用开发者工具看看视频地址是否能直接获取再针对video组件做专项用例自定义标题栏上边距和设计稿不一致不同机型状态栏高度不同脚本里写死了数值通过运行环境获取statusBarHeight后做动态计算页面元素明明存在但脚本里查询不到部分原生组件和自定义组件节点层级特殊确认该元素是否为video/map/canvas或给原生组件再加一层业务容器便于定位标签隐藏判断不准在WXML里用hidden控制隐藏时元素仍在组件树中断言“隐藏”应优先检查display样式而不是“元素不存在”这里面我要展开讲一下“paused in debugger”。它本质上是微信开发者工具的调试器进入断点状态导致自动化脚本所在环境的主线程被暂停。我遇到过几次现象是脚本没有任何报错但所有操作停在那里不动控制台提示paused in debugger。解决办法也简单关闭工具里所有断点或者重启工具后直接用自动化模式跑尽量不手动打开source面板打断点。标签隐藏判断也是典型误区。小程序里两种隐藏方式wx:if为 false 时节点不会渲染到组件树里hidden为 true 时节点仍然渲染只是用样式隐藏了。你要是用“元素是否存在”去断言一个hidden控制的标签永远得不到预期结果。正确姿势是拿到元素后检查样式属性或者可见区域。这个细节我专门写进了代码注释里避免团队后续踩同一坑。5.2 提升用例稳定性的三条经验经验一所有异步行为都必须等接口层结果不要靠sleep。小程序里点了按钮后经常有 loading、请求、数据回填、二次渲染的过程。sleep两秒网络一慢就失败网络一快又浪费执行时间。我用的是等待业务标志元素等加载完成后的某个列表项出现或者等loading组件从组件树消失。经验二把用例拆小不要写“从首页一直点到支付”的超长脚本。超长脚本一旦失败从头排查的成本极高。我会把完整业务流程拆成登录、首页、列表、详情、购物车、下单支付几个独立用例每个用例自己准备前置数据。这样失败定位快也能并行执行只是前置数据的构造成本要高一些。经验三断言要落到业务预期不落到实现细节。比如购买流程完成后我断言的是“订单列表第一单的状态为已完成”而不是“某个toast文本等于success”。UI文案经常是产品和运营的改动重点一条文案调整就会让用例红一整天但它跟功能对错无关。5.3 用接口数据校验补齐UI断言的短板UI自动化有一个天然短板它只能观察到界面层的表象。一个按钮点击后前端展示“成功”但接口实际可能返回了一个错误码只是前端没处理。这类问题靠UI断言发现不了。所以我们团队在UI用例执行时同步引入了接口层的观测。开发调试阶段用本地代理工具记录请求响应把代理暴露的接口返回数据导入自动化脚本在关键步骤后比对希望出现的返回字段。比如下单接口返回的订单号如果服务端返回了但页面没有渲染那大概率是前端渲染问题如果接口本身就报错了则要考虑服务端稳定性或参数构造。这里要注意接口观测不是去破解什么而是用自己业务服务端提供的正当接口做数据校验适用于自研小程序系统的常规联调和回归。签名相关逻辑也可以用同样的思路小程序请求里经常带签名参数自动化脚本如果把用例数据写死了等到签名过期或时间戳失效整条链路都会失败。所以脚本里的签名应由自动化环境在运行时重新生成不要沉淀成静态参数代入用例。5.4 导航栏、单选框、自定义组件这类UI细节的断言思路做UI自动化测试时除了业务功能UI细节也常常是回归对象。我在实践中总结过几个典型组件容易被测试同学当成普通DOM去处理结果一堆坑。先说单选框。小程序里的radio-group和radio是组件体系里的关系不是自带checked属性就给到一个标准的HTML input。自动化时你如果只定位到最外层的radio-group点击某个radio可能不触发change。我推荐的定位方法是直接给每个选项绑定业务可读的数据属性比如>
返回列表