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`(看起来有值、其实指错地方)。判据当场抓到 ⇒ 改成按行取、值允许中文、空值不回退。
74 lines
3.6 KiB
JavaScript
74 lines
3.6 KiB
JavaScript
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 换成别人的就不是我们');
|
||
});
|