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') }); // Relay Switch 功能页控制(参照 plug):点卡**直接进功能页**(无弹窗/无 More) → 点大圆形电源键 开/关 → 读 On/Off 校验翻转。 // 功能页(实测 "Relay Switch 1 32" 1080x2400):大圆形电源键(clickable ViewGroup [367,314][713,737] 中心 ~540,525,内含 "On"/"Off")+ Schedules/Delay/Logs。 const deviceName = process.env.RELAY_SWITCH_DEVICE || 'Relay Switch 1'; const CTRL = onesCtrl('plug', deviceName) || '[P0]'; // 基础 Relay Switch 1 → step41 Relay1(A1TLxsFw);换 RELAY_SWITCH_DEVICE='Relay Switch 1PM' 取 1PM describe('Relay Switch 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('RelaySwitch_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 250-800)的 "On"/"Off" 文字 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 > 250 && y < 820) return m[1] as 'On' | 'Off'; } return 'unknown'; } // 点大圆形电源键:Android 用 clickable 容器(内含 On/Off 字)tapElement;兜底点中心坐标 (540,525)=W*0.5,H*0.219 async function tapPower(): Promise { 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.219)); 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.28)); 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(): Promise { const card = await findHomeCard(driver, deviceName); if (!card) { console.log(`[RSFeat] 首页找不到 ${deviceName}`); 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'; } it(`${CTRL} 功能页控制${deviceName}(开+关)`, { timeout: 120000 }, async () => { const start = Date.now(); try { if (!(await enterFeature())) { // 首页有该开关却进不去功能页 = 未正确执行 → FAIL(交给 catch);首页确无该开关(未添加/本平台无实体)→ 合法 SKIP。 await driver.goBackToHomepage().catch(() => {}); await sleep(1200); if (await findHomeCard(driver, deviceName).catch(() => null)) throw new Error('进功能页失败(首页有该开关但进不去功能页,未正确执行)'); reporter.record(`${CTRL} 功能页控制${deviceName}`, 'SKIP', Date.now() - start, '首页无该开关(未添加/本平台无实体),跳过'); return; } const s0 = readState(await driver.getSource()); console.log(`[RSFeat] 初始: ${s0}`); const target1 = s0 === 'On' ? 'Off' : 'On'; expect(await tapPower()).toBe(true); const s1 = await waitState(target1); console.log(`[RSFeat] 操作1后: ${s1}(目标${target1})`); expect(s1).toBe(target1); const target2 = target1 === 'On' ? 'Off' : 'On'; await tapPower(); const s2 = await waitState(target2); console.log(`[RSFeat] 操作2后: ${s2}(目标${target2})`); expect(s2).toBe(target2); reporter.record(`${CTRL} 功能页控制${deviceName}`, 'PASS', Date.now() - start, `功能页开/关均生效(${s0}→${s1}→${s2})`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record(`${CTRL} 功能页控制${deviceName}`, 'FAIL', Date.now() - start, e.message, ss); throw e; } }); });