AI_UIAutomation/tests/plug/plug_feature_control.test.ts

134 lines
6.4 KiB
TypeScript

import { describe, it, beforeAll, afterAll, beforeEach, expect } from 'vitest';
import { DeviceDriver } from '../../drivers/types';
import { createDriver } from '../../drivers/factory';
import { TestReporter } from '../../utils/test-reporter';
import { sleep, onesCtrl, setupResilientHooks, findHomeCard } from '../../utils/common';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
// Plug 功能页控制(在首页卡片控制 plug_card 之外补充):点卡**直接进功能页**(无弹窗/无 More) → 点大圆形电源键 开/关 → 读 On/Off 校验翻转。
// 功能页(实测 Plug EU):Power/Current/Voltage 统计 + 大圆形电源键(clickable ViewGroup 中心 ~540,849,内含 "On"/"Off" 状态字)。
// 覆盖三台:卡名按添加时区域改名 "Plug JP/US/EU"(见 plug_mini_connect)。锚点:JP/US 同 Plug Mini(PlugMini 5E),EU 为 eu Plug。
const PLUGS = [
{ name: process.env.PLUG_JP_DEVICE || 'Plug JP', model: 'PlugMini 5E' },
{ name: process.env.PLUG_US_DEVICE || 'Plug US', model: 'PlugMini 5E' },
{ name: process.env.PLUG_EU_DEVICE || 'Plug EU', model: 'eu Plug' },
];
describe('Plug Feature Control - 功能页控制(进设备页 开/关电源)', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
setupResilientHooks(() => driver);
const isIOS = () => driver.platform === 'ios';
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('Plug_Feature_Control', driver.platform.toUpperCase());
});
beforeEach(async () => {
await driver.dismissPopupIfPresent();
await driver.goBackToHomepage();
await sleep(500);
await driver.dismissPopupIfPresent();
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
// 读电源状态:电源键区域(y 600-1100)的 "On"/"Off" 文字。iOS 同名标签。
function readState(src: string): 'On' | 'Off' | 'unknown' {
if (isIOS()) {
const m = src.match(/(?:name|label|value)="(On|Off)"/);
return m ? (m[1] as 'On' | 'Off') : 'unknown';
}
for (const m of src.matchAll(/text="(On|Off)"[^>]*bounds="\[\d+,(\d+)\]\[\d+,\d+\]"/g)) {
const y = +m[2];
if (y > 550 && y < 1150) return m[1] as 'On' | 'Off';
}
return 'unknown';
}
// 点大圆形电源键:Android 用 clickable 容器(内含 On/Off 字)tapElement;兜底点中心坐标。iOS 找 Power/电源键。
async function tapPower(): Promise<boolean> {
if (!isIOS()) {
const cont = await driver.findElementRaw('-android uiautomator',
'new UiSelector().clickable(true).childSelector(new UiSelector().textMatches("On|Off"))').catch(() => null);
if (cont) { await driver.tapElement(cont); return true; }
const wh = await driver.getWindowSize().catch(() => ({ width: 1080, height: 2400 }));
await driver.tap(Math.round(wh.width * 0.5), Math.round(wh.height * 0.354)); // 电源键中心 ~540,849
return true;
}
// iOS:电源钮=中央 On/Off 圆形 tile(name/label 为 "On"/"Off")。**不能匹配 "Power"** —— 顶部有个
// "Power 0.0W" 统计标签(StaticText name="Power"),它在树里排在电源钮之前,谓词含 Power 会先命中它 →
// tapElement 点统计标签是空操作 → 状态永不翻转(旧 bug 根因)。故谓词只取 On/Off tile。
const p = await driver.findElementRaw('predicate string', 'name == "On" OR name == "Off"').catch(() => null);
if (p) { await driver.tapElement(p); return true; }
const wh = await driver.getWindowSize().catch(() => ({ width: 390, height: 844 }));
await driver.tap(Math.round(wh.width * 0.5), Math.round(wh.height * 0.42));
return true;
}
async function waitState(want: 'On' | 'Off'): Promise<'On' | 'Off' | 'unknown'> {
let st = readState(await driver.getSource());
for (let i = 0; i < 12 && st !== want; i++) { await sleep(1500); st = readState(await driver.getSource()); }
return st;
}
// 进插座功能页:点卡直接进(无弹窗)→ 等电源键状态(On/Off)出现
async function enterFeature(name: string): Promise<boolean> {
const card = await findHomeCard(driver, name);
if (!card) { console.log(`[PlugFeat] 首页找不到 ${name}`); return false; }
await driver.tapElement(card);
await sleep(2200);
for (let i = 0; i < 8; i++) {
if (readState(await driver.getSource()) !== 'unknown') return true;
await sleep(1000);
}
return readState(await driver.getSource()) !== 'unknown';
}
for (const plug of PLUGS) {
const CTRL = onesCtrl('plug', plug.model) || '[P0]';
it(`${CTRL} 功能页控制${plug.name}(开+关)`, { timeout: 120000 }, async () => {
const start = Date.now();
try {
if (!(await enterFeature(plug.name))) {
// 首页有该插座却进不去功能页 = 未正确执行 → FAIL(交给 catch);首页确无该插座(未添加/本平台无实体)→ 合法 SKIP。
await driver.goBackToHomepage().catch(() => {}); await sleep(1200);
if (await findHomeCard(driver, plug.name).catch(() => null)) throw new Error('进功能页失败(首页有该插座但进不去功能页,未正确执行)');
reporter.record(`${CTRL} 功能页控制${plug.name}`, 'SKIP', Date.now() - start, '首页无该插座(未添加/本平台无实体),跳过');
return;
}
const s0 = readState(await driver.getSource());
console.log(`[PlugFeat] ${plug.name} 初始: ${s0}`);
// 切到相反态
const target1 = s0 === 'On' ? 'Off' : 'On';
expect(await tapPower()).toBe(true);
const s1 = await waitState(target1);
console.log(`[PlugFeat] ${plug.name} 操作1后: ${s1}(目标${target1})`);
expect(s1).toBe(target1);
// 切回
const target2 = target1 === 'On' ? 'Off' : 'On';
await tapPower();
const s2 = await waitState(target2);
console.log(`[PlugFeat] ${plug.name} 操作2后: ${s2}(目标${target2})`);
expect(s2).toBe(target2);
reporter.record(`${CTRL} 功能页控制${plug.name}`, 'PASS', Date.now() - start, `功能页开/关均生效(${s0}${s1}${s2})`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${CTRL} 功能页控制${plug.name}`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
}
});