跨端: 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`(看起来有值、其实指错地方)。判据当场抓到 ⇒ 改成按行取、值允许中文、空值不回退。
This commit is contained in:
95
client/harmony/entry/src/main/ets/model/DeviceProbe.ts
Normal file
95
client/harmony/entry/src/main/ets/model/DeviceProbe.ts
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 设备读回的**前置闸**(纯逻辑,无 `@ohos`)—— 判据能用 node 直接跑。
|
||||
*
|
||||
* 为什么需要它(pi 邮件 `971c58fa` §4):别的 agent 在同一个模拟器上跑它的 app,
|
||||
* 它的 `aa start` 会把前台抢走。如果我在那之后读控件树,**读到的是它的窗口**,
|
||||
* 而我的断言就会拿**别的 app 的布局**去判 —— 可能"通过",也可能红,**两者都不是在讲我们的界面**。
|
||||
* 后者是**假绿**:比"被打断"严重得多。
|
||||
*
|
||||
* 处置规则(pi 邮件 `82ae145b` 认可,`bf583b0f` 补样本):
|
||||
* 前台不是我们 ⇒ **不下断言**;而且**不许静默跳过**(跳过会在下一轮被读成"验过了")。
|
||||
* 正确状态是 **`未验`**,余额里那笔到期欠账**继续开着**。
|
||||
* 一句话:**"没验"和"验过了"之间不许有斜坡。**
|
||||
*/
|
||||
|
||||
/** 我们从 dump 里真正要用到的两样东西 */
|
||||
export interface ForegroundInfo {
|
||||
bundle: string;
|
||||
windowTitle: string;
|
||||
}
|
||||
|
||||
/** 闸的判决:**只有三种**,没有第四种"大概是我们" */
|
||||
export const VERDICT_OURS: string = 'ours';
|
||||
export const VERDICT_OTHER: string = 'other';
|
||||
export const VERDICT_UNVERIFIED: string = 'unverified';
|
||||
|
||||
/**
|
||||
* 从 dump 文本里取"谁在前台"。
|
||||
*
|
||||
* **取不到就返回 undefined**(空 dump / 截断 / 格式变了)—— 不猜、不兜底。
|
||||
* 空 dump 是最容易被误读的一种:**窗口还没起来**时它最常出现,
|
||||
* 而"没读到"极容易被当成"那个坏现象没有出现"(⇒ 假绿)。
|
||||
*/
|
||||
export function parseForeground(dump: string): ForegroundInfo | undefined {
|
||||
if (dump.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const bundle: string = firstMatch(dump, 'bundleName');
|
||||
const altBundle: string = bundle.length > 0 ? bundle : firstMatch(dump, 'bundle');
|
||||
if (altBundle.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const info: ForegroundInfo = { bundle: altBundle, windowTitle: firstMatch(dump, 'windowTitle') };
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取某个键的值(**限定在同一行内**)。
|
||||
*
|
||||
* ⚠️ 这里踩过一次坑,写下来免得再犯:第一版写的是 `key + '[^A-Za-z0-9_]*([A-Za-z0-9_.]+)'`,
|
||||
* 那个否定类**既接受非 ASCII 也接受换行** ⇒ 对 `windowTitle: 邮件\n abilityName: EntryAbility`
|
||||
* 它会一路吞掉中文标题和换行,然后**捕获下一行的键名** `abilityName`。
|
||||
* 于是"标题"读到的是别人的键名 —— 一个**看起来有值、其实指错地方**的结果(判据当场把它抓出来了)。
|
||||
* ⇒ 分隔符不许跨行:按行取,值里允许中文。**空值返回空串,绝不回退到别处找**。
|
||||
*/
|
||||
function firstMatch(text: string, key: string): string {
|
||||
const re = new RegExp(key + '\\s*[:=]\\s*([^\\n\\r]*)');
|
||||
const m = re.exec(text);
|
||||
if (m === null) {
|
||||
return '';
|
||||
}
|
||||
return m[1].trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判决"这份 dump 是不是我们的" —— **只许看 bundle,不许看标题/控件名/文案**。
|
||||
*
|
||||
* 理由(pi 邮件 `bf583b0f` §2):别人的合法 dump 里**完全可能**有与我们要判的控件同名、
|
||||
* 文案相同的项("邮件"这种通用词尤其容易)。按文案判 ⇒ 别人的 dump 会被当成我们的 ⇒ 静默放行。
|
||||
*/
|
||||
export function foregroundVerdict(dump: string, ourBundle: string): string {
|
||||
const info: ForegroundInfo | undefined = parseForeground(dump);
|
||||
if (info === undefined) {
|
||||
return VERDICT_UNVERIFIED;
|
||||
}
|
||||
return info.bundle === ourBundle ? VERDICT_OURS : VERDICT_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* 能不能在这份 dump 上做断言。
|
||||
* `false` ⇒ 调用侧**必须**把这条判据记成 `未验`(欠账继续开着),**不许**记成通过、也不许静默跳过。
|
||||
*/
|
||||
export function mayAssertOn(dump: string, ourBundle: string): boolean {
|
||||
return foregroundVerdict(dump, ourBundle) === VERDICT_OURS;
|
||||
}
|
||||
|
||||
/** 拿不到独占时的状态词 —— 统一到这里,免得各处自己编(编出来的词会互相读不懂) */
|
||||
export function unverifiedReason(verdict: string): string {
|
||||
if (verdict === VERDICT_OURS) {
|
||||
return '';
|
||||
}
|
||||
if (verdict === VERDICT_OTHER) {
|
||||
return '未验:前台是别的 app(拿不到独占窗口)—— 读到的界面不是我们的,断言会讲错对象';
|
||||
}
|
||||
return '未验:没读到前台是谁(空 dump / 截断 / 格式变了)—— **缺证据不等于没有那个现象**';
|
||||
}
|
||||
Reference in New Issue
Block a user