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 { 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 { 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 { 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 { 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 { 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; } }); });