news 2026/9/22 3:15:22

Weex 跨平台 UI 自动化测试实战:基于 Macaca 与 Mocha 的端到端测试体系解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Weex 跨平台 UI 自动化测试实战:基于 Macaca 与 Mocha 的端到端测试体系解析
  • 移动开发
  • 跨平台
  • 前端
  • UI组件
  • OpenHarmony

【免费下载链接】weex

A framework for building Mobile cross-platform UI

项目地址:https://gitcode.com/gh_mirrors/we/weex
点击查看免费下载

Weex 是一套用于构建移动端跨平台 UI 的框架,同一份 Vue/Weex 页面代码可运行在 Android、iOS 与 H5 三种端上。为了让这套"一次编写、三端运行"的代码在每次改动后依然行为一致,仓库在 test/ 目录下沉淀了一套完整的端到端(E2E)自动化测试体系:以 Macaca 作为移动端驱动引擎、Mocha 作为用例框架、Blink-Diff 做像素级截图比对,覆盖内置组件与模块的全部常用功能。本文将以 test/README.md 为核心线索,结合仓库中的运行脚本与真实用例,完整讲解该测试体系的环境搭建、运行方式、目录结构与用例编写方法,让读者既能一键跑通全量回归,也能自行扩展新的测试页面与断言用例。

测试体系概览

按照 test/README.md 的定位,这套测试的目标是:

覆盖所有内置公共组件(components)与模块(modules)的功能,包括真实应用中的常见 UI 模式,例如基于列表的页面(list-based page)、包含各种输入控件的表单(a form with all kind of input)等。

也就是说,它并非单元测试(单元测试在 test/js-framework 中),而是面向真实运行环境的端到端测试:先把每个用例对应的 Weex 页面编译成 JS bundle,再通过 Macaca 驱动 Android 模拟器 / iOS 模拟器 / Chrome 浏览器加载页面,模拟真实用户点击、输入、滑动,最后通过 DOM 断言或截图比对来验证三端行为是否一致。

整套测试链路可以概括为三步:

  1. 构建:把 test/pages 下的 Vue 页面编译为可被三端加载的 JS bundle;
  2. 服务:在本地启动静态资源服务器,供页面与 bundle 下载;
  3. 驱动:通过 Macaca 启动目标端的 App/浏览器,由 Mocha 执行 test/scripts 下的用例脚本,逐步操作页面并断言结果。

环境准备(Setup)

官方 README 的第一步就是安装 Macaca 环境。Macaca 是本套测试的移动端驱动核心,它负责把 WebDriver 协议翻译成对 Android/iOS 模拟器的真实控制指令。环境搭建包含以下要点:

  • 安装 Macaca 及对应平台的驱动(iOS 需要 Xcode 与模拟器运行时,Android 需要 SDK、Platform Tools 与 AVD 模拟器镜像);
  • 准备 Node.js 运行时(仓库 package.json 声明engines.node >= 8);
  • 若在 CI 中运行,还需要安装 Chrome 用于 H5 端测试。

仓库提供了 test/ci-funcs.sh 作为 CI 环境自动准备的参考脚本,其中封装了四个关键函数:

  • installAndroidSDK:通过android update sdk安装 platform-tools、build-tools-23.0.2、android-19/23 平台与 armeabi-v7a 系统镜像;
  • createAVD/startAVD:创建并启动名为weexavd的 Android 模拟器(-no-audio -no-window无头模式,适合 CI);
  • waitForEmulator:轮询adb -e shell getprop init.svc.bootanim直到模拟器完成启动,再发送keyevent 82解锁;
  • installNode:通过 nvm 安装 Node 7.0.0(与engines.node >= 8相比略旧,说明该脚本面向当时的 CI 基线)。

这些函数说明:要在 CI 上跑通 Android 端测试,至少需要 SDK 工具链、一个可用的 AVD 以及等待模拟器就绪的同步逻辑;本地开发时可以直接用 Android Studio 自带模拟器,不必完全照搬该脚本。

运行内置测试用例(Run)

四步运行流程

test/README.md 给出了运行内置用例的标准四步流程,这是整个文档最核心的实战内容:

  1. 进入 Weex 项目根目录
  2. 执行npm install安装全部依赖(Mocha、weex-wd、macaca-utils、blink-diff、serve 等测试相关依赖都在 package.json 的devDependencies中);
  3. 执行./test/serve.sh构建测试 bundle 并启动静态资源服务;
  4. 执行./test/run.sh [platform]执行 Weex SDK 测试,platform可选all(默认)、androidiosh5

构建与静态服务:serve.sh

test/serve.sh 的实际内容非常简短,但每行都对应一个重要环节:

#!/usr/bin/env bash npm run build:vue # 构建 Vue 相关的 JS framework bundle npm run build:ci & # 后台构建 CI 测试 bundle port="${serport:-12581}" # 端口可用环境变量 serport 覆盖,默认 12581 npm run serve:no-port -- -p $port # 用 serve 把仓库根目录作为静态站点

几点值得注意:

  • 端口通过环境变量serport控制,默认12581。这个端口会贯穿测试全过程:test/scripts/util.js 中同样读取process.env.serport || 12581来拼接页面 URL;
  • npm run serve:no-port对应 package.json 中的"serve:no-port": "serve ./",即把仓库根目录作为静态资源根路径,这样测试页面可以直接通过http://<ip>:12581/test/build/...访问到编译产物;
  • build:ci会调用 [test/scripts/build.js 对应的构建流程](见 scripts/config.js),把 test/pages 下的 Vue 页面编译为 JS bundle 输出到test/build目录。

三端测试驱动:run.sh

test/run.sh 是测试执行的总调度脚本,它按平台拆分为三条执行路径,并在结束时统一清理 Macaca 服务进程(killserver)。脚本整体流程如下:

platform=${1:-android} # 第一个参数:平台,默认 android needCoverage=${2:-noCover} # 第二个参数:是否需要覆盖率,默认 noCover killserver # 先杀掉残留的 macaca-cli-server # android 分支 runAndroid ./test/scripts/ "$needCoverage" # web 分支(对应 README 中的 h5) runWeb ./test/scripts/ # 其余情况走 iOS runiOS ./test/scripts/ "$needCoverage" killserver

各平台分支的核心逻辑如下表(来源于 test/run.sh 源码):

平台构建步骤测试命令
Android./gradlew clean assembleDebug构建 playground 调试包(传入cover时切换为带 JaCoCo 覆盖率插桩的:weex_sdk:assembleDebugplatform=android mocha test/scripts --reporter mocha-simple-html-reporter -f '@ignore-android' -i --recursive --bail --retries 3
iOSpod update+ 三组xcodebuild分别构建 Pods-WeexDemo、WeexSDK 与 WeexDemo(iphonesimulator,关闭代码签名),并killAll Simulator清理platform=ios mocha ... -f '@ignore-ios' -i ...
Web/H5无需构建原生包browser=chrome mocha ... -f '@ignore-web' -i ...

脚本中的startMacacaServer会后台拉起macaca server --verbose,并通过nc -z 127.0.0.1 3456轮询等待 Macaca 默认端口 3456 就绪;startWeexServer则等待 12581 静态服务可用。也就是说,一次测试运行时实际存在三个常驻进程:Macaca 服务(3456)、静态资源服务(12581)、Mocha 测试进程。

关于 Mocha 命令需要说明两点:

  • -f '@ignore-android' -i表示"忽略标题中包含 @ignore-android 的用例",即允许单个用例通过命名声明自己跳过某个平台,实现三端用例共享一套脚本;
  • --retries 3允许用例失败自动重试 3 次,可有效缓解移动端测试常见的偶发不稳定问题;
  • 结果统一输出为report.html(mocha-simple-html-reporter),便于在 CI 上归档与查看。

平台参数的实际行为

README 声称platform可为all(默认)、androidiosh5。对照 test/run.sh 源码可以发现实际映射略有不同:脚本只判断了androidweb两个分支,h5实际对应web分支(在 Chrome 中运行),而all并没有独立的并发分支。换句话说,目前一次只能运行单一平台,"all" 更接近文档层面的约定而非脚本行为;README 标注为 "**todo##" 的独立项目测试(bash run.sh [platform] [path])也属于规划中的能力——当前run.sh的第二个参数实际被解释为覆盖率开关(cover/noCover),并不会按路径加载外部项目。从源码结构推断,后续若支持path参数,需要让run.sh把用例目录从固定的./test/scripts/改为参数指定的目录。

测试目录结构解析

test/README.md 的 Develop 章节只列出了两个目录,但实际 test 目录下共有 8 个顶层条目,各自职责如下:

目录/文件职责
test/scripts测试脚本。测试命令会逐个执行该目录下的每个用例文件,是断言与操作逻辑所在
test/pagesWeex 页面脚本。这些 Vue 页面会在运行测试前被编译成 JS bundle,所有测试实际都运行在这些页面上
test/screenshot截图比对基准图,如 border 组件的 Android/iOS 对照截图
test/js-frameworkJS Framework 层面的用例与测试器(对应 package.json 中的test:case/test:unit脚本)
test/serve.sh构建并启动静态服务
test/run.sh三端测试总调度
test/ci-funcs.shCI 环境准备函数库
test/update-cli.sh测试 CLI 更新脚本

其中scriptspages一一对应、按主题分子目录组织:components(组件行为)、modules(模块调用)、attributes(属性)、css(样式)。例如:

  • 组件目录:test/scripts/components/a-src.test.js ↔ test/pages/components/a-src.vue;
  • 模块目录:test/scripts/modules/storage-event.test.js ↔ test/pages/modules/storage-event.vue。

这种"一页一用例"的组织方式让测试意图非常清晰:页面负责提供可交互的 UI 与结果回显,脚本负责像真人一样驱动页面并断言结果。

测试用例编写实战:从页面到断言

理解这套体系最快的方式,是完整拆解一个真实用例。以 storage 模块的storage-event用例为例。

第一步:编写测试页面

test/pages/modules/storage-event.vue 是一个典型的 Weex 测试页面:页面内放置两个按钮setItemgetItem,点击后调用weex.requireModule("storage")的对应方法,并把回调结果写入resultTxt回显到页面上:

<template> <div> <button value="setItem" @click.native="setItem"></button> <button value="getItem" @click.native="getItem"></button> <text>{{resultTxt}}</text> </div> </template> <script> var storage = weex.requireModule("storage"); module.exports = { data: { resultTxt: '' }, methods: { setItem: function () { storage.setItem('key', 'value', () => { this.resultTxt = 'setItem success'; }); }, getItem: function () { storage.getItem('key', (e) => { this.resultTxt = 'getItem:' + e.data; }); } } } </script>

页面中的wxc-desc组件专门用于用中文描述"测试点"与"测试方式",相当于把人工回归时的检查清单固化在页面里。

第二步:编写测试脚本

test/scripts/modules/storage-event.test.js 则负责驱动与断言,它的骨架可以拆成五部分:

var assert = require('chai').assert var wd = require('weex-wd') var util = require("../util.js"); var goal = 'storage-event'; var timeout = util.getGETActionWaitTimeMills(); describe('weex ' + goal + ' test', function () { this.timeout(util.getTimeoutMills()); // 1. 放宽 Mocha 超时(CI 下 60 分钟,本地 10 分钟) var driver = util.createDriver(wd); // 2. 创建/复用 WebDriver beforeEach(function () { return util.init(driver) // 3. 初始化驱动(启动 App/浏览器) .get(util.getPage('/modules/' + goal + '.js')) // 4. 加载对应编译产物 .waitForElementByName(goal, timeout, 2000) // 5. 等待页面元素出现 }); // ...用例断言 });

用例本身是一条完整的用户路径:

it('#1 ' + goal + ' event', () => { return driver .waitForElementByName('setItem', timeout, 2000).click() .waitForElementByName('setItem success', timeout, 2000) // 断言写入成功 .waitForElementByName('getItem', timeout, 2000).click() .waitForElementByName('getItem:value', timeout, 2000) // 断言读取结果 })

整个流程的精髓是把断言对象做成页面上的可见文本storage.setItem的回调把resultTxt改成setItem success,脚本只需waitForElementByName('setItem success')就能同时完成"操作成功 + UI 正确回显"的双重验证,完全不依赖原生控件的内部状态,天然跨端可用。

第三步:复用测试基础设施

几乎所有用例都通过 test/scripts/util.js 获得统一的设备与手势能力,它是整个测试体系的"基础设施层":

  • 平台与设备配置:通过环境变量platform(默认 android)、browserserportrun_in_ci决定目标端。Android 加载playground-debug.apk,iOS 加载WeexDemo.app,H5 则直接驱动 Chrome;
  • URL 拼接getPage):Android/iOS 使用wxpage://<ip>:<port>/test/build<name>自定义 scheme 唤起 SDK 加载页面,H5 使用http://<ip>:<port>/vue.html?page=/test/build-web<name>getDeviceHost会自动探测本机非回环 IPv4 地址;
  • 手势封装:在 WebDriver 上扩展了dragUpAndDowndrag(支持 toUp/toLeft/toRight/toDown 四个方向与起点偏移)、swipeLeft/swipeRightclickScreenById(按元素 id 计算中心点点击)等 Promise 链方法,模拟真实用户的上滑加载、左右滑动等操作;
  • 截图能力saveShot把 Base64 截图写入本地文件;
  • 生命周期init负责初始化驱动并等待 20 秒(注释说明 iOS 无法立即检测到 App),quit在退出前保存最后一张截图到test/last.png并返回上一页,方便失败排查。

组件用例可以进一步体现这些基础设施的配合。以 test/scripts/components/a-src.test.js 为例,它验证<a>组件的 src 跳转与动态更新:先点击a-itself断言跳到a-support-href1页面,再点击changeSrchref动态改为a-support-href2后再次跳转断言。对应页面 test/pages/components/a-src.vue 中用test-id属性标记关键元素(如a-itselfcontent-inside-a),脚本据此精准定位,覆盖了"点击组件本身"与"点击组件内部内容"两类真实场景。

截图比对机制:三端视觉一致性验证

除了 DOM 断言,这套体系还支持像素级截图比对,用于验证同一页面在三端上的渲染效果一致。test/scripts/util.js 中的diffImage基于blink-diff实现:

var diff = new BlinkDiff({ imageAPath: imageAPath, // 基准图路径(首次运行时自动写入基准) imageB: imageB, // 当前截图 Buffer thresholdType: BlinkDiff.THRESHOLD_PIXEL, threshold: threshold, // 允许的差异像素阈值 imageOutputPath: outputPath, // 差异图输出路径 cropImageA: isIOS ? {y:128} : {y:242,height:1530}, // 裁剪状态栏/导航栏区域 cropImageB: isIOS ? {y:128} : {y:242,height:1530} });

其中的裁剪参数值得一提:iOS 裁剪从 y=128 开始(去掉状态栏与导航栏),Android 从 y=242 开始且固定高度 1530(源码注释解释了 Android 需要减去 status bar 72 + navigator bar 170 的偏移),避免不同系统 UI 栏差异干扰页面内容比对。diff.hasPassed(result.code)判断是否在像素阈值内通过,并输出实际差异像素数。

test/screenshot 目录下的基准图就是这套机制的产物,例如 border 组件在 Android 与 iOS 上的渲染对照:

border-android.png(1080×1920)与border-ios.png(750×1334)的尺寸差异也能看出,比对前必须经过裁剪与归一化处理,才能在不同分辨率屏幕上进行有意义的像素对比。这种"截图即断言"的方式非常适合验证边框、圆角、间距等纯视觉属性,与 DOM 断言互补。

常见问题与调试建议

  • 端口冲突:Macaca 固定使用 3456,静态服务默认 12581。若端口被占用,可通过serport环境变量更换静态服务端口(serport=13000 ./test/serve.sh),但注意util.js读取的是同一个环境变量,需要同步设置;
  • 偶发失败:移动端测试不稳定是常态,run.sh已内置--retries 3;若仍失败,可查看report.html与退出前保存的test/last.png定位是操作失败还是渲染不一致;
  • 新增用例的步骤:在 test/pages 对应子目录新增.vue页面(用test-id标注关键元素)→ 在 test/scripts 对应子目录新增同名.test.js(复用util.createDriver与手势方法)→ 若涉及视觉断言,先跑一次生成基准图 → 通过./test/run.sh [platform]验证;
  • 平台差异标注:若某个交互只在部分平台支持,在用例标题中加入@ignore-android/@ignore-ios/@ignore-webrun.sh会通过-f ... -i自动跳过。

小结

Weex 仓库的这套 E2E 测试体系给出了一个可复制的跨端 UI 测试范式:用真实页面承载可断言结果,用统一基础设施抹平三端差异,用截图比对兜住视觉回归。理解 test/README.md 描述的"构建(serve.sh)→ 驱动(run.sh)→ 断言(scripts/pages)"三层结构后,开发者既可以在本地一键回归 test/pages 中覆盖的内置组件与模块,也可以参照 test/scripts/util.js 的封装模式,为自己的业务页面快速搭建一套 Android、iOS、H5 三端一致运行的自动化测试,并平滑接入 CI(参考 test/ci-funcs.sh)。

  • 移动开发
  • 跨平台
  • 前端
  • UI组件
  • OpenHarmony

【免费下载链接】weex

A framework for building Mobile cross-platform UI

项目地址:https://gitcode.com/gh_mirrors/we/weex
点击查看免费下载
上一篇:Cutter 代码贡献入门指南:从构建环境到首个 Pull Request 的完整工作流
下一篇:Claudian 完整排错指南:把 Claude Code 装进 Obsidian 知识库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 2:18:58

Nix 源码调试指南:从带调试符号的构建到 gdb/lldb 断点实战

开发工具CLI 【免费下载链接】nix Nix, the purely functional package manager 项目地址&#xff1a; https://gitcode.com/gh_mirrors/ni/nix 点击查看 免费下载 本篇指南面向需要深入 Nix&#xff08;purely functional package manager&#xff09;源码内部进行排障、内存…

作者头像 李华
网站建设 2026/9/21 2:16:57

基于STM32的DDS信号发生器设计:从原理到波形输出

简介&#xff1a;一份基于STM32的信号发生器系统设计与实现文档&#xff0c;定位为电子工程/嵌入式方向课程设计或毕业设计参考&#xff0c;面向需要掌握嵌入式信号发生器开发流程的本科生、研究生及工程技术人员。文档完整覆盖从需求分析、方案对比到软硬件实现全过程&#xf…

作者头像 李华
网站建设 2026/9/21 2:13:21

SumatraPDF 命令行参数完全指南:启动、导航、打印与自动化实战

SumatraPDF 命令行参数完全指南&#xff1a;启动、导航、打印与自动化实战 【免费下载链接】sumatrapdf SumatraPDF reader 项目地址: https://gitcode.com/gh_mirrors/su/sumatrapdf SumatraPDF 是一款开源 Windows PDF 阅读器&#xff0c;其命令行接口功能强大&#x…

作者头像 李华