| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 16 天前 | ||
| 1 天前 | ||
| 1 天前 | ||
| 16 天前 | ||
| 8 天前 | ||
| 4 天前 | ||
| 15 天前 | ||
| 2 天前 | ||
| 11 天前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 16 天前 | ||
| 16 天前 |
flexui-engine Test Lab(引擎测试台)
独立的 flexui-engine 测试 demo 工程。用于系统性验证引擎 ArkTS API 能力,测试用例与 demo skill 完全隔离,AI 可低成本新增用例,用户可在 UI 上动态修改参数验证。
一、目录结构
demos/engine-test-demo/
├── engine-tests/ # ★ 用例资产(AI 新增用例的唯一入口)
│ ├── manifest.json # 用例清单(注册用例)
│ ├── lib/test-harness.js # JS 断言框架(globalThis.__testHarness)
│ ├── skills/echo-skill/ # 测试专用技能(executeSkillApi 链路测试)
│ └── cases/<category>/<case>.js # 用例脚本(按引擎能力分类)
├── pc-server/ # ★ PC 侧布局校验服务(uitest dumpLayout 解析)
│ └── server.js # Node HTTP 服务:/api/health + /api/dump
├── entry/src/main/ets/
│ ├── pages/EngineTest.ets # 测试台 UI 页面
│ ├── pages/CardPreviewPage.ets # 独立全屏卡片页(layout 用例渲染载体)
│ └── test/ # 测试基础设施(Runner / Module / Types)
│ ├── EngineTestRunner.ets # 执行器:读清单→执行→汇总→hilog [TEST]
│ ├── EngineTestModule.ets # JS→ETS 断言上报原生模块 + AscfAIModule 回调代理
│ ├── LayoutVerifier.ets # PC 布局数据拉取 + layoutExpect 断言匹配器
│ └── TestResultTypes.ets # 结果类型定义
└── scripts/build.sh # 构建/回归命令
二、快速开始
# 1. 构建 HAP(自动同步 engine-tests → rawfile)
./build.sh build
# 2. 安装 + 真机运行
./build.sh install # hdc install HAP
# 3. 命令行回归(AI/CI 场景)——启动测试台自动跑指定级别用例,轮询 hilog [TEST] 输出 PASS/FAIL 汇总
./build.sh test-engine # 默认只跑基础用例(basic)
./build.sh test-engine feature # 只跑特性用例
./build.sh test-engine bug # 只跑缺陷回归用例
./build.sh test-engine all # 全部用例(发版前回归)
# 4. 含布局校验的全量回归——后台起 PC 布局服务(uitest dumpLayout),layout 用例正常断言
./build.sh test-layout
# 5. 手工启动(UI 场景)——桌面点开 Engine Test Lab,或
./build.sh launch # aa start(无 ascfTest 参数,进 UI 模式)
# 6. PC 布局服务单独调试
./build.sh fport # 建立 hdc fport(设备 8710 → 主机 8710)
./build.sh pc-server # 前台启动 PC 布局服务(curl http://127.0.0.1:8710/api/health 验证)
Windows 下用 Git Bash 执行:
"D:\Program Files\Git\bin\bash.exe" -lc './build.sh <cmd>'
三、用例形态
| 形态 | type | 驱动方 | 覆盖能力 | 示例 |
|---|---|---|---|---|
| JS 用例 | js |
runScriptFromUri 在引擎 JS VM 执行 | has.*、callNative、Timer Policy、异常、来源归因 | cases/03-native-module/callnative-async.js |
| ETS 用例 | ets |
Runner 在 ArkTS 侧直接调引擎 API | createEngine、EnvAPI、executeSkillApi、回调链路、卡片特化场景 | handler envapi-set / skill-api-echo / card-dedupe |
| 卡片用例 | card |
页面 FlexView 驱动渲染 + 生命周期断言 + 可选 JS 断言 | 卡片渲染全链路(load/上屏/就绪/异常)、卡片上下文能力(自断言卡片) | card-minimal-render / card-assert-js |
| 布局校验用例 | layout |
独立卡片页(CardPreviewPage)全屏渲染 + PC 侧 uitest dumpLayout 布局断言 |
渲染效果精准感知:大小/类型/文本/颜色(经 PC 服务回传后应用内断言) | card-layout-minimal / card-layout-complex |
卡片自动化测试机制(card 用例不再 SKIP)
card 用例由测试台页面(EngineTest.ets)注册的 CardCaseDriver 驱动,Runner 的 runCardCase 编排:
Runner.runCardCase
├─ 1. 注入 lib/test-harness.js(waitCardReport / jsAssert 用例需要同 scope 的 __testHarness)
├─ 2. waitCardReport=true(自断言卡片):注册 reportCase 完成回调
├─ 3. CardCaseDriver.runCard(info) → 页面 FlexView 重建加载(cardVersion 强制重建)
│ └─ 收集生命周期信号:onLoadCompleted(0/-204/错误码) / onFirstViewAdded /
│ onViewReady(rootId) / onJsException,按用例期望结算(expectFail / requireRender)
├─ 4. jsAssert 脚本(可选):卡片就绪后同 scope 执行(与卡片共享 globalThis,
│ 可读 __GLOBAL__.appRegister / __ascfRegistry__ 等运行时状态)
└─ 5. waitCardReport:等待卡片自身 JS 经 __testHarness 上报 reportCase
三种断言层次(可按用例组合):
| 层次 | 机制 | 断言什么 | 示例 |
|---|---|---|---|
| ETS 生命周期 | 页面 FlexView client 回调 | 加载码、首帧上屏、viewReady rootId>0、无 JS 异常 | card-minimal-render(真实渲染) |
| 同 scope JS 脚本 | jsAssert 后置脚本 |
appRegister 注册、实例注册表、卡片运行时可观测状态 | 待扩展 |
| 卡片自身 JS | 自断言卡片(engine-tests/cards/,run() 内直接写断言) |
params 交付、调用来源归因(origin='event' + rootId>0)、定时器默认拒绝/特批放行、屏幕事件(Page.onResize / pageLifetimes.resize) | card-assert-js / card-assert-timer-allow / card-screen-resize |
自断言卡片是覆盖「卡片上下文能力」的关键——卡片 bundle(engine-tests/cards/*/index.pack.js)与测试 harness 同 scope,
run()(loadInstance 入口)内直接 __testHarness.expect(...) 并同步上报,能验证只有卡片上下文才存在的语义:
- params 交付(loadCard paramsJson →
run(payload).params); - 宏任务归因(
getCallOrigin():卡片实例化入口origin='event'+rootId=卡片根,与__instanceId__一致); - Timer Policy 卡片级默认拒绝(
setTimeout同步抛permission denied)/allowTimer特批放行; - 屏幕事件:卡片经
EngineTestModule.pushCardSize(ETS,走生产updateDimension → Dimensions.set链路) 推送容器尺寸变化 → 页面根Page.onResize/pageLifetimes.resize收到res.size.windowWidth/windowHeight(card-screen-resize,需allowTimer轮询等待广播落地)。
特殊卡片场景(file:// scheme、REPEAT_LOAD 去重、未知 scheme)由 type=ets + card-* handler 实现,
handler 内做前置准备(写沙箱文件 / 多轮驱动)后复用同一个 CardCaseDriver。
布局校验用例(type=layout)——渲染效果精准感知
生命周期断言只能回答「卡片加载成功没」,无法感知渲染出来的效果(大小/类型/文本/颜色)。 layout 用例打通「设备应用 ↔ PC」通道:
应用(CardPreviewPage 独立全屏渲染卡片)
└─ http 127.0.0.1:8710(hdc fport 转发)→ PC pc-server/server.js
├─ hdc shell uitest dumpLayout -a # 抓当前窗口控件树 JSON(含 bounds/text/type/颜色)
├─ hdc file recv <json> <local> # 拉回主机
└─ 解析压缩 → { ok, nodes[] } # 每节点 {type,text,x,y,w,h,color,bg,depth}
← 应用按 manifest.layoutExpect 断言(匹配器 LayoutVerifier)
流程:Runner 推入 pages/CardPreviewPage(校验模式 = 无头部、全屏纯卡片,避免测试台
其它布局污染 dump)→ 卡片生命周期结算(与 card 用例同语义)→ 等 600ms 布局稳定 →
health 探测 → dump → 逐条 check 断言 → reportAssert/reportCase 上报 → 返回测试台。
约定(重要):应用连不上 PC 服务 → 跳过布局校验(用例仍按生命周期结算,不判失败,
断言标记 SKIP);连接成功但 dump 失败(hdc/uitest 异常)→ 判失败并上报具体错误,便于 AI 排查。
layoutExpect 断言规格(manifest 字段,JSON 字符串):
{
"tolerance": 4, // 尺寸/坐标容差 px(默认 4)
"checks": [
{ "name": "标题存在", "type": "Text", "text": "Engine Test" }, // 过滤 + 至少 1 个
{ "name": "标题尺寸", "type": "Text", "text": "Engine Test", "w": 240, "h": 40 },
{ "name": "红色块存在", "bg": "#ff0000", "count": 1 }, // 颜色过滤 + 精确数量
{ "name": "无图片", "type": "Image", "count": 0 } // 断言不存在
]
}
- 过滤字段:
type(子串、忽略大小写)、text(子串)、bg/color(归一化相等,#AARRGGBB/0x 兼容); - 期望字段(对首个匹配节点,±tolerance):
w/h/x/y/minW/maxW/minH/maxH; count:精确匹配数(缺省 = 至少 1 个;0= 断言不存在)。- flexui 卡片在 dump 中的形态(真机实测):
- 非滚动卡片:flexui 元素 =
type="Custom"节点(key=FlexUIId<N>),尺寸/位置/背景色可断言; 文本与字体颜色本机型 dump 不携带(text空、无 fontColor)→ 文本语义断言走 JS 自断言通道; - scroll-view 卡片:单个
JsView叶子节点(子树不展开)→ 只能断言容器级(存在/尺寸); - 颜色断言优先用
bg(backgroundColor 真机可用),color(fontColor)本机型恒为空。
- 非滚动卡片:flexui 元素 =
PC 服务(pc-server/server.js,Node 无第三方依赖):
GET /api/health(连通性探测)、POST /api/dump(执行 dumpLayout + recv + 解析);- 端口默认 8710(
hdc fport tcp:8710 tcp:8710设备侧→主机侧,类似 adb reverse); ./build.sh pc-server一键起服务;./build.sh test-layout后台起服务跑全量回归。
手工调试:测试台选中 card/layout 用例 → 「全屏预览」→ 页面内「验证布局」可即时查看 解析出的节点列表(类型/文本/尺寸/颜色)与逐条断言结果,方便迭代 layoutExpect。 已知边界:本机型 scroll-view 卡片的 flexui 子树不展开(显示为单个 JsView 白盒), 非滚动卡片只暴露 Custom 节点(尺寸/背景色可断言,文本不可见)——按实际 dump 调整断言。
命令行回归(
./build.sh test-engine)与 UI 模式共用同一驱动:ascfTest=true启动时页面自动 runAll, card 用例照常执行并输出[TEST] PASS/FAIL,无需人工操作。
四、用例分级与执行(basic / feature / bug)
每个用例在 manifest 中按性质打标 level,测试台 UI 分 3 个 tab(基础/特性/缺陷)展示与执行:
| level | 含义 | 何时编写 | 执行 |
|---|---|---|---|
basic |
基础用例:稳定能力冒烟(渲染/桥/API 主链路) | 新能力面落地 | 默认执行(test-engine) |
feature |
特性用例:新特性覆盖 | 新增特性必须补 | test-engine feature |
bug |
缺陷回归用例:关键修复覆盖 | 关键设计/缺陷修复必须补 | test-engine bug;发版前 test-engine all |
- UI:顶部 3 个 tab 切换用例列表(各 tab 显示该类用例数量);「运行本类」执行当前 tab,「运行所有」执行全部级别;
- 命令行:
./build.sh test-engine [basic|feature|bug|all](缺省 basic),经启动参数ascfLevel透传; - 发版前回归跑全部:
./build.sh test-engine all(含 feature + bug 全量); - 强制约定见根
CLAUDE.md「flexui-engine 测试用例规范」:新特性/关键修复必须随提交携带对应 level 用例并通过真机回归。
五、AI 新增一个测试用例(3 步)
① 新增用例脚本(JS 用例示例):
// engine-tests/cases/<category>/<case-id>.js
// 用 globalThis.__testHarness 写断言(由测试台注入)
__testHarness.run('<case-id>', [
{
name: '断言描述',
fn: async function () {
const res = await __testHarness.callModuleSync('EngineTestModule', 'ping', { tag: 'x' })
__testHarness.expect(JSON.parse(res).errMsg).toEqual('ping:ok')
},
},
])
② manifest.json 注册(追加一项):
{
"id": "<case-id>",
"name": "用例显示名",
"category": "03-native-module",
"capability": "场景二:JS 调用原生能力",
"type": "js",
"level": "feature", // ★ 按性质打标:basic(默认)/ feature(新特性)/ bug(关键修复回归)
"script": "cases/<category>/<case-id>.js",
"timeoutMs": 15000
}
③ 跑回归:
./build.sh test-engine # 基础用例(默认),结果在 hilog:[TEST] PASS/FAIL case=<id>
./build.sh test-engine feature # 只跑本用例所属级别
./build.sh test-engine all # 发版前全量
ETS 用例(type=ets)需要同时在 EngineTestRunner.ets 的 runEtsCase() 中新增 handler 分支;卡片用例(type=card)分两种:
普通卡片渲染用例(真实渲染验证,无需写 JS):
{
"id": "card-my-render",
"name": "我的卡片渲染",
"category": "02-card-render",
"capability": "场景一:...",
"type": "card",
"bundleUrl": "ascf-ai-demo/.../index.pack.js", // 卡片 bundle URL(asset 相对路径)
"componentName": "skill_component", // JS 侧注册的组件名
"paramsJson": "{\"title\":\"x\"}",
"timeoutMs": 20000
// 可选:expectFail=true(负向:期望加载失败)、requireRender=false(自断言卡片不渲染原生树)
}
自断言卡片用例(验证卡片上下文能力,卡片 JS 内写断言):
- 在
engine-tests/cards/<case>/index.pack.js写卡片 bundle——顶层__GLOBAL__.appRegister[componentName] = { run },run()内用globalThis.__testHarness写断言(可验证 params 交付 / origin 归因 / 定时器策略等卡片独有语义), 参考cards/assert-card/index.pack.js; - manifest 注册(
waitCardReport: true表示等待卡片 JS 上报 reportCase,requireRender: false表示不要求首帧上屏)。 特殊准备(写沙箱文件、多轮加载、自定义 scheme)用 type=ets +card-*handler 实现,参考card-file/card-dedupe。
五、能力覆盖矩阵(对齐 docs/learn/flexui-engine-arkts-api.md)
| API 文档场景 | 用例 | 形态 | 说明 |
|---|---|---|---|
| 场景一:卡片渲染与交互 | card-minimal-render / card-complex-render(生命周期自动化断言) |
card | ✅ 自动化:onLoadCompleted(0/-204) + onFirstViewAdded + onViewReady(rootId>0) |
| 场景一:渲染效果精准感知 | card-layout-minimal / card-layout-complex(PC uitest dumpLayout + 应用内断言) |
layout | ✅ 自动化:大小/类型/文本/颜色断言(PC 服务不可达时跳过) |
| 场景一:卡片上下文能力 | card-assert-js(自断言:params 交付 / origin 归因 / 定时器默认拒绝) |
card | ✅ 自动化:卡片 run() 内断言 |
| 场景一:-204 去重语义 | card-dedupe(同 URL 二次加载) |
ets | ✅ 自动化:-204 + 实例化仍成功 |
| 场景一 1.2.1:URL 加载规范 | card-file(file:// scheme) |
ets | ✅ 自动化:沙箱文件 → file:// loadCard |
| 场景一 1.2.1:未注册 scheme | card-unknown-scheme(负向) |
card | ✅ 自动化:加载失败不崩溃 |
| 场景一 1.2.2:系统配置变更自动重建 | config-change-recreate(首帧就绪 → 模拟 colorMode 变更 → FlexView 按 recreateOnConfigChange 自动 reloadCard,rootIdA→rootIdB 换新实例) |
ets | ✅ 自动化 |
| 场景二:JS 调用原生能力 | callnative-async / callnative-sync |
js | ✅ PASS |
| 场景二:真实异步 promise 回包 | callnative-async-roundtrip(callId → callBack → .then) |
ets | ✅ 自动化 |
| 场景二:promise resolve 数据完整回传 | promise-resolve-rich(异步路径:call() 返回 null,resolveRich 富对象 → .then 全字段断言)+ promise-resolve-rich-sync(同步路径:call() 返回 promise,callNativeSync 直接返回完整对象) |
ets + js | ✅ 自动化 |
| 场景二:promise reject 数据完整回传 | promise-reject-completeness(异步路径:call() 返回 null)+ promise-reject-completeness-sync(同步路径:call() 返回 promise)——均含普通对象 errMsg/errorCode/嵌套/中文/数组 + 集成 error 对象还原为真实 Error 实例含附加字段 |
ets + js | ✅ 自动化 |
| 场景二:error 回传 stack 边界策略 | promise-reject-stack-policy(reject 集成 error 对象 → error 还原为 Error 实例:native 层 stack 不注入(toJsonSafeValue 序列化排除 + restoreValue 对 stack/topstack key 跳过,不含 .ets 路径/序列化层/native 构造点);JS 层 stack 自然保留(new Error 还原生成的栈不再主动删除,stack 非空且含 JS 侧帧)) |
ets | ✅ 自动化 |
| 场景二:同步失败错误透传 | callnative-error-sync(嵌套 JSON/中文/转义字符) |
js | ✅ 自动化 |
| 场景二 2.3:调用来源归因 | callorigin-runscript(rootId=0)+ card-assert-js(卡片 event/rootId>0) |
js + card | ✅ 自动化 |
| 场景二:宿主 API 层 | has-sync-smoke(has_apis.js 同步链路 + storage 往返) |
js | ✅ PASS |
| 场景三:EnvAPI | envapi-set(ETS 冒烟)+ envapi-roundtrip(JS has.env 活对象两阶段) |
ets | ✅ 自动化 |
| 场景三:onCardEvent 事件分发 | oncard-event-broadcast(ETS 广播)+ UI「发事件」 |
ets | ✅ PASS |
| 场景三方式三:回调链路 | invoke-callback-roundtrip(success/fail/complete 序列断言) |
ets | ✅ 自动化 |
| 场景六:Skill API 执行 | skill-api-echo(echo=42)+ skill-api-error(未注册/抛错/isError 路径) |
ets | ✅ 自动化 |
| 场景七:异常上报 | js-exception-report(可捕获)+ js-uncaught-exception(引擎级 exceptionHandler)+ card-init-throw(J-03 入口异常 -205) |
js + ets + card | ✅ 自动化 |
| 场景八:DFX 监控 | dfx-startup(onStartupReport,含 10s 兜底) |
ets | ✅ PASS |
| 场景九:Timer Policy | timer-policy-runscript(runScript 放行)+ card-assert-js(卡片默认拒绝)+ card-assert-timer-allow(allowTimer 特批) |
js + card | ✅ 自动化 |
| 引擎初始化 | engine-ready |
ets | ✅ PASS |
真机回归结果(2026-08-26,27 用例):
runAllTests: done total=27 pass=27 fail=0 skip=0 error=0 timeout=0历史回归重点全部有自动化用例:宏任务归因(rootId 清零/卡片打标)、同步错误信息完整透传、 Timer Policy 卡片级管控(默认拒绝 / allowTimer 特批)、J-03 入口异常上报、REPEAT_LOAD 去重、 EnvAPI 活对象广播、invokeAsyncCallback 一次性回调语义、URL 加载规范(file:// / mem:// / 未知 scheme)。
TODO:场景四自定义组件渲染、场景五全页面引擎、executeSkillApi 成功路径更多 API 断言。
六、测试台 UI 用法
打开 App(Engine Test Lab):
- 用例列表:按能力分类展示,点选进入详情
- 运行全部 / 运行此用例:执行并实时更新状态(PASS/FAIL/TIMEOUT/ERROR)
- 参数编辑:选中用例后编辑
paramsJson(JSON 文本) - 卡片预览(card 用例):底部 FlexView 实时渲染(如 apidemos minimal 卡片,深色 flex 布局);「重载卡片」用新参数重建
- 发事件(onCardEvent 链路):输入事件类型 + 数据 → 经
__ascfCallbacks.onCardEvent广播到 JS 的 instanceEventBus → 测试台预注册的通配监听器(engine-tests/lib/event-listener.js)接收 → 日志面板实时显示「收到事件 data=...」,验证 ETS→C++→JS→监听器全链路 - 临时 JS 控制台:输入任意 JS 文本执行(写入沙箱 files/engine-tests-console/ 经 file:// 加载,无需重装 HAP)——适合快速验证引擎 API 或调试
- 运行日志:面板滚动输出,含
[TEST]标记(与命令行回归同源)
注意:
FlexViewRef.sendEvent(命令式)与onCardEvent(广播)是两条不同链路——sendEvent 走 EventDispatcher.receiveNativeEvent(需卡片组件主动监听逻辑事件,现有 demo 卡片均未实现);onCardEvent 走 instanceEventBus(卡片viewCtx.on(type, cb)监听,测试台预注册通配监听器回报日志)。测试台「发事件」采用 onCardEvent 链路以提供可见反馈。
七、hilog 标记约定(命令行回归解析依据)
[TEST] RUNNING suite total=N[TEST] PASS|FAIL case=<id> name=<name> msg=<...>[TEST] runAllTests: done total=N pass=N fail=N ...- 脚本
scripts/build.sh test-engine轮询hilog -x抓取并汇总。
八、签名与 bundle
- 复用
com.enjoy.now.hmosbundleName 与 ascf-agent 签名(开发期覆盖安装可接受;如需与 ascf-agent 共存,改AppScope/app.json5的 bundleName 并重新签名) - 引擎 HAR 依赖:根
oh-package.json5override@flexui/engine→../../flexui-engine/output/flexui_engine.har(构建前需存在)