AI_UIAutomation/tests/air_purifier/air_purifier_card.test.ts

162 lines
6.9 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 } from '../../utils/common';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
// Air Purifier(口头"空净",WiFi):控制必测=**首页卡片快捷控制浮层**(非功能页)。流程同加湿器2(Evaporative Humidifier)。
// 点首页卡片 → 弹浮层:顶部状态字(Off/运行) + **无文案电源圆键** + 模式行(Level/Auto/Pet/Sleep) + More。
// 实测 1080x2400:电源键中心 ≈ (540, 1247)=屏中线、h*0.52(浮层顶 y≈911,加湿器2 的 h*0.36 落在浮层外故不通用)。
// 模式键有文字,按 text 定位即可;切模式需先开机。ONES 控制号待补,先 [P0] 占位。
const CARD_KEYWORD = process.env.AIR_PURIFIER_CARD || 'Air Purifier';
const MODES = ['Level', 'Auto', 'Pet', 'Sleep']; // 实测浮层模式集合
const ADD_ANCHOR = '[P0]'; // TODO: 待补 Air Purifier 控制 ONES 编号
describe('AirPurifier Card - 首页卡片控制(Air Purifier)', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('AirPurifier_Card', driver.platform.toUpperCase());
});
beforeEach(async () => {
await driver.dismissPopupIfPresent();
await driver.goBackToHomepage();
await driver.dismissPopupIfPresent();
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
async function findCard(): Promise<string> {
for (let r = 0; r < 8; r++) {
const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${CARD_KEYWORD}")`).catch(() => null);
if (el) return el;
await driver.swipe(540, 1400, 540, 600, 0.4).catch(() => {});
await sleep(900);
}
throw new Error(`找不到${CARD_KEYWORD}卡片`);
}
// 点卡片中心 → 弹快捷控制浮层
async function openCardPopup(): Promise<void> {
const cardId = await findCard();
const rect = await driver.getElementRect(cardId);
await driver.tap(rect.x + rect.width / 2, rect.y + rect.height / 2);
await sleep(2000);
const ok = await waitForSource(driver, 'More', 6000);
if (!ok) throw new Error('卡片快捷控制浮层未弹出');
}
// 无文案电源圆键:浮层是固定底部弹层,电源键在屏中线、状态字下方/模式行上方。
// 实测 1080x2400 电源键中心 (540, 1247) ≈ (w/2, h*0.52)。
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.52);
console.log(`点电源键 (${px}, ${py})`);
await driver.tap(px, py);
}
// 当前是否关机:取首个含设备名(首页卡)后的状态字 —— 首页卡副标题镜像设备态:关=Off,开=模式/数值。
async function isOff(): Promise<boolean> {
const src = await driver.getSource();
const texts = Array.from(src.matchAll(/text="([^"]+)"/g)).map((m) => m[1]);
const i = texts.findIndex((t) => t.includes(CARD_KEYWORD));
const status = i >= 0 ? (texts[i + 1] || '') : '';
console.log(`状态字="${status}"`);
return /^Off$|Offline|待机/.test(status);
}
async function pollState(wantOff: boolean, ms = 15000): Promise<boolean> {
const deadline = Date.now() + ms;
while (Date.now() < deadline) {
if ((await isOff()) === wantOff) return true;
await sleep(1500);
}
return (await isOff()) === wantOff;
}
it(`${ADD_ANCHOR} 首页找到空净卡片`, async () => {
const start = Date.now();
try {
const cardId = await findCard();
const rect = await driver.getElementRect(cardId);
const detail = `位置: (${rect.x}, ${rect.y}) 尺寸: ${rect.width}x${rect.height}`;
console.log(`${CARD_KEYWORD} ${detail}`);
expect(rect.width).toBeGreaterThan(0);
reporter.record(`${ADD_ANCHOR} 首页找到空净卡片`, 'PASS', Date.now() - start, detail);
} 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} 首页卡片开机`, async () => {
const start = Date.now();
try {
await openCardPopup();
if (!(await isOff())) { await tapPower(); await pollState(true); }
await tapPower();
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} 首页卡片关机`, async () => {
const start = Date.now();
try {
await openCardPopup();
if (await isOff()) { await tapPower(); await pollState(false); }
await tapPower();
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;
}
});
it(`${ADD_ANCHOR} 首页卡片切换模式(Level/Auto/Pet/Sleep)`, async () => {
const start = Date.now();
try {
await openCardPopup();
// 切模式前需先开机(关机时模式键置灰)
if (await isOff()) { await tapPower(); await pollState(false); }
const switched: string[] = [];
for (const m of MODES) {
const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${m}")`).catch(() => null);
if (el) { await driver.tapElement(el); await sleep(2000); switched.push(m); }
else console.log(`未找到模式键: ${m}`);
}
console.log(`已切换模式: ${switched.join('/')}`);
expect(switched.length).toBe(MODES.length);
reporter.record(`${ADD_ANCHOR} 首页卡片切换模式`, 'PASS', Date.now() - start, `依次切换 ${switched.join('/')} 成功`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ADD_ANCHOR} 首页卡片切换模式`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});