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