Files
MailUI4Agents/client/electron/test/harmony-deviceprobe.test.mjs
JianFeeeee e917b8782a 跨端: feat(设备闸): DeviceProbe.ts —— "读到别人的界面"是假绿来源,读之前先判"是谁的",拿不到就记未验
pi 邮件 `971c58fa` §4 / `bf583b0f` §2。别的 agent 在同模拟器上 `aa start` 会抢前台,
之后读到的控件树是**它的窗口** ⇒ 断言可能通过也可能红,**两者都不是在讲我们的界面**(前者=假绿)。

- `foregroundVerdict` 判决只有三种:`ours` / `other` / `unverified`;**只按 bundle 判,不许看标题/文案/控件名**
  (别人的合法 dump 里完全可能有同名控件与同文案,"邮件"这种通用词尤其容易撞);
- `parseForeground` 取不到就 **undefined**(空 dump / 截断 / 格式变了都不猜);
- `mayAssertOn === false` ⇒ 调用侧必须记 **`未验`**(欠账继续开着),**不许**记通过、**不许**静默跳过
  —— 跳过会在下一轮被读成"验过了";
- `unverifiedReason` 统一状态词,并写明"**缺证据 ≠ 没有那个现象**"。

判据喂 4 类样本(pi 指定的最易漏输入):① 我们的 dump ② **别人的合法 dump(含同名控件 + 同文案)**
③ 截断/畸形 ④ **空 dump**(窗口没起来时最常见,最容易被当成"坏现象不存在")。
另加一条只按 bundle 判的反证(把标题改成"像我们"也不许变 ours)。

**顺手抓到自己一个坑**:`firstMatch` 第一版的否定类既接受非 ASCII 也接受**换行**
⇒ 对 `windowTitle: 邮件\n abilityName: …` 会吞掉中文标题并**捕获下一行的键名**,
"标题"读到 `abilityName`(看起来有值、其实指错地方)。判据当场抓到 ⇒ 改成按行取、值允许中文、空值不回退。
2026-09-15 11:54:25 +08:00

74 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from 'node:test';
import assert from 'node:assert/strict';
import { dirname, join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
/*
设备读回的**前置闸**(pi 邮件 `971c58fa` §4 / `bf583b0f` §2)。
危险形状不是"读不到",而是**读到别人的界面却当成我们的**:
断言可能通过、也可能红,**两者都不是在讲我们的界面** —— 前者是假绿。
所以这条判据喂**四类样本**,其中最容易被漏的是「② 别人的合法 dump,且含与我们要判的控件同名/同文案的项」。
*/
const HERE = dirname(fileURLToPath(import.meta.url));
const MODEL = join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'ets', 'model');
const C = await import(pathToFileURL(join(MODEL, 'DeviceProbe.ts')).href);
const OURS = 'com.jianf.agentmail';
const THEIRS = 'com.example.homeagent';
// ① 我们的 dump(含我们要判的那个控件)
const dumpOurs = [
'MissionList:',
' mission #1',
' appType: APP',
' bundleName: com.jianf.agentmail',
' windowTitle: 邮件',
' abilityName: EntryAbility',
].join('\n');
// ② 别人的**合法** dump —— 而且**故意**带一项与我们要判的控件同名/同文案的东西
const dumpTheirs = [
'MissionList:',
' mission #1',
' appType: APP',
' bundleName: com.example.homeagent',
' windowTitle: 邮件', // 文案一样
' abilityName: EntryAbility', // ability 名也可能一样
' component: MailList', // 控件名一样
].join('\n');
test('★ 闸①:我们自己的 dump ⇒ ours(且能取出标题)', () => {
assert.equal(C.foregroundVerdict(dumpOurs, OURS), C.VERDICT_OURS);
assert.equal(C.mayAssertOn(dumpOurs, OURS), true);
assert.equal(C.parseForeground(dumpOurs).windowTitle, '邮件');
});
test('★ 闸②:别人的**合法** dump(含同名控件/同文案)⇒ other,绝不算我们的', () => {
assert.equal(C.foregroundVerdict(dumpTheirs, OURS), C.VERDICT_OTHER,
'这一条是最容易漏的输入:格式完全合法、控件名与文案都与我们相同 —— 按文案判就会静默放行');
assert.equal(C.mayAssertOn(dumpTheirs, OURS), false);
assert.equal(C.parseForeground(dumpTheirs).windowTitle, '邮件', '文案确实一样(所以判据只能看 bundle)');
assert.ok(C.unverifiedReason(C.VERDICT_OTHER).includes('未验'), '拿不到独占 ⇒ 状态词必须是"未验"');
});
test('★ 闸③:截断/畸形 dump ⇒ unverified(缺证据 ≠ 没有那个现象)', () => {
assert.equal(C.foregroundVerdict('MissionList:\n mission #1\n appType: APP', OURS), C.VERDICT_UNVERIFIED);
assert.equal(C.foregroundVerdict('{不是 dump', OURS), C.VERDICT_UNVERIFIED);
assert.equal(C.mayAssertOn('', OURS), false);
});
test('★ 闸④:空 dump ⇒ unverified(窗口还没起来时最容易出现,也最容易被当成"坏现象不存在")', () => {
assert.equal(C.foregroundVerdict('', OURS), C.VERDICT_UNVERIFIED);
assert.ok(C.unverifiedReason(C.VERDICT_UNVERIFIED).includes('缺证据'), '状态词必须点明"缺证据≠没有现象"');
});
test('★ 闸只按 bundle 判:把别人的 dump 改成"标题像我们"也不许变成 ours', () => {
const tricky = dumpTheirs.replace('邮件', '收件箱 · AgentMail');
assert.equal(C.foregroundVerdict(tricky, OURS), C.VERDICT_OTHER,
'标题/文案/控件名都是可以撞的,只有 bundle 是身份');
const swapped = dumpOurs.replace(OURS, THEIRS);
assert.equal(C.foregroundVerdict(swapped, OURS), C.VERDICT_OTHER, '反过来:bundle 换成别人的就不是我们');
});