AI_UIAutomation/tests/humidifier/humidifier2_card.test.ts

177 lines
9.2 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, waitForSource, tapHomeCard, tapHomeCardTopRight, getDeviceStatus, FindHomeCardOptions, onesCtrl } from '../../utils/common';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
// Evaporative Humidifier(口头"加湿器2",WiFi):控制必测=首页卡片快捷控制。
// 开/关:直接点首页卡片右上角开关钮(通用方法 tapHomeCardTopRight,定位卡容器内 controlBtn,无需开浮层)。
// 切模式:点卡片弹浮层(顶部状态字 + 无文案电源圆键 + 模式行 Level/Sleep/Humidity/Auto + More);切模式需先开机。
// 状态读取:首页卡副标题镜像设备态(关=Off/待机/风干;开=模式名/数值),用通用入口 getDeviceStatus 两端统一读取+结构化。ONES 控制号待补,先 [P0] 占位。
const CARD_KEYWORD = process.env.HUMIDIFIER2_CARD || 'Evaporative Humidifier';
const ADD_ANCHOR = onesCtrl('humidifier', 'Evaporative Humidifier') || '[P0]'; // step26 Humidifier2(15975#3PmZMk1s)
// 读状态轻量 options:不回顶/不重复清弹框/不重点 Home tab —— 供轮询与"浮层开着读背景卡态"用(默认 options 会回顶并清掉控制浮层)。
const LIGHT_READ: FindHomeCardOptions = { resetPosition: 'none', ensureHome: false, dismissPopups: false };
describe('Humidifier2 Card - 首页卡片控制(Evaporative Humidifier)', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('Humidifier2_Card', driver.platform.toUpperCase());
});
beforeEach(async () => {
await driver.dismissPopupIfPresent();
await driver.goBackToHomepage();
await driver.dismissPopupIfPresent();
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
// 点卡片 → 弹快捷控制浮层。用通用 tapHomeCard(findHomeCard 定位 + settleCardRect 等列表停稳 + tapElement 点击),
// 替代手搓的 getRect+tap 中心:首页设备多、列表惯性滚动时手搓坐标会点偏 → 浮层不弹/误进功能页(Level 档位曾因此 text("Level")=null)。
async function openCardPopup(): Promise<void> {
await tapHomeCard(driver, CARD_KEYWORD);
const ok = await waitForSource(driver, 'More', 6000) || await waitForSource(driver, 'Level', 3000);
if (!ok) throw new Error('卡片快捷控制浮层未弹出');
}
// 无文案电源圆键:浮层是固定底部弹层,电源键在屏中线、状态字下方/模式行上方。
// 实测 1080x2400:电源键中心 ≈ (540, 865)。按屏宽高比例算,兼容分辨率。
async function tapPower(): Promise<void> {
let w = 1080, h = 2400;
try { const s: any = await (driver as any).getWindowSize?.(); if (s?.width) { w = s.width; h = s.height; } } catch { /* 默认 1080x2400 */ }
const px = Math.round(w / 2);
const py = Math.round(h * 0.36); // 2400*0.36≈864
console.log(`点电源键 (${px}, ${py})`);
await driver.tap(px, py);
}
// 当前是否关机:通用入口 getDeviceStatus(定位卡片→读副标题→结构化)。关=power off / 离线 / 待机 / 风干(Drying filter);开=模式名/数值。
// 轮询或浮层开着读背景卡态时传 LIGHT_READ,避免回顶滚动/清掉控制浮层。
async function isOff(opts: FindHomeCardOptions = {}): Promise<boolean> {
const st = await getDeviceStatus(driver, CARD_KEYWORD, opts);
console.log(`状态字="${st.raw}" power=${st.power} online=${st.online}`);
return st.power === 'off' || !st.online || /待机|Drying|风干/i.test(st.raw);
}
// 轮询直到目标态(设备走 WiFi,状态同步可能需几秒)
async function pollState(wantOff: boolean, ms = 15000): Promise<boolean> {
const deadline = Date.now() + ms;
while (Date.now() < deadline) {
if ((await isOff(LIGHT_READ)) === wantOff) return true;
await sleep(1500);
}
return (await isOff(LIGHT_READ)) === wantOff;
}
it(`${ADD_ANCHOR} 首页卡片开机`, async () => {
const start = Date.now();
try {
// isOff() 默认 options 会滚动定位卡片;找不到卡片时下面的 tapHomeCardTopRight 会抛"找不到卡片"。
if (!(await isOff())) { await tapHomeCardTopRight(driver, CARD_KEYWORD); await pollState(true); }
await tapHomeCardTopRight(driver, CARD_KEYWORD);
const on = await pollState(false);
console.log(`开机后 isOff=${!on}`);
expect(on).toBe(true);
reporter.record(`${ADD_ANCHOR} 首页卡片开机`, 'PASS', Date.now() - start, '首页卡片开关钮开机成功(状态→运行)');
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ADD_ANCHOR} 首页卡片开机`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it(`${ADD_ANCHOR} 首页卡片切换模式(Sleep)`, async () => {
const start = Date.now();
try {
await openCardPopup();
// 切换模式前需先开机
if (await isOff(LIGHT_READ)) { await tapPower(); await pollState(false); }
const el = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Sleep")').catch(() => null);
expect(el).not.toBeNull();
await driver.tapElement(el!);
await sleep(2500);
console.log('开机后切 Sleep 模式成功');
reporter.record(`${ADD_ANCHOR} 首页卡片切换模式(Sleep)`, 'PASS', Date.now() - start, '先开机再切 Sleep 成功');
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ADD_ANCHOR} 首页卡片切换模式(Sleep)`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it(`${ADD_ANCHOR} 首页卡片 Level 模式切换档位(1-4)`, async () => {
const start = Date.now();
try {
await openCardPopup();
if (await isOff(LIGHT_READ)) { await tapPower(); await pollState(false); }
const lvl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Level")').catch(() => null);
expect(lvl).not.toBeNull();
await driver.tapElement(lvl!); await sleep(2000);
// Level 模式展开后出现档位 1/2/3/4,依次切换(从 2 起切,避免与默认重复)
const gears: string[] = [];
for (const g of ['2', '3', '4', '1']) {
const ge = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${g}")`).catch(() => null);
if (ge) { await driver.tapElement(ge); await sleep(1500); gears.push(g); }
}
console.log(`Level 档位已切换: ${gears.join('/')}`);
expect(gears.length).toBeGreaterThanOrEqual(3);
reporter.record(`${ADD_ANCHOR} 首页卡片 Level 档位切换(1-4)`, 'PASS', Date.now() - start, `切换档位 ${gears.join('/')} 成功`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ADD_ANCHOR} 首页卡片 Level 档位切换(1-4)`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it(`${ADD_ANCHOR} 首页卡片 Humidity/Auto 可用性(需绑温湿度计)`, async () => {
const start = Date.now();
try {
await openCardPopup();
if (await isOff(LIGHT_READ)) { await tapPower(); await pollState(false); }
// Humidity/Auto 需先在设置页绑定温湿度计才可用;未绑定时置灰(enabled=false)。两种结果都算 PASS,记录实测状态。
const results: string[] = [];
for (const m of ['Humidity', 'Auto']) {
const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${m}")`).catch(() => null);
expect(el).not.toBeNull();
const enabled = (await driver.getElementAttribute(el!, 'enabled').catch(() => 'true')) === 'true';
if (enabled) { await driver.tapElement(el!); await sleep(2000); results.push(`${m}=可用(已切换)`); }
else { results.push(`${m}=置灰(未绑温湿度计)`); }
}
console.log(results.join(', '));
reporter.record(`${ADD_ANCHOR} 首页卡片 Humidity/Auto 可用性`, 'PASS', Date.now() - start, results.join(', '));
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ADD_ANCHOR} 首页卡片 Humidity/Auto 可用性`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it(`${ADD_ANCHOR} 首页卡片关机`, async () => {
const start = Date.now();
try {
if (await isOff()) { await tapHomeCardTopRight(driver, CARD_KEYWORD); await pollState(false); }
await tapHomeCardTopRight(driver, CARD_KEYWORD);
const off = await pollState(true);
console.log(`关机后 isOff=${off}`);
expect(off).toBe(true);
reporter.record(`${ADD_ANCHOR} 首页卡片关机`, 'PASS', Date.now() - start, '首页卡片开关钮关机成功(状态→Off)');
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ADD_ANCHOR} 首页卡片关机`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});