import { describe, it, beforeAll, beforeEach, afterAll, expect } from 'vitest'; import { DeviceDriver } from '../../drivers/types'; import { createDriver } from '../../drivers/factory'; import { TestReporter } from '../../utils/test-reporter'; import { sleep, onesCtrl, waitForHomeReady, findHomeCard, getHomeCardStatus } from '../../utils/common'; import { setupResilientHooks } from "../../utils/common"; import * as dotenv from 'dotenv'; import * as path from 'path'; dotenv.config({ path: path.resolve(__dirname, '../../.env') }); // 百叶帘:首页快捷弹窗控制(Fully open / Close Down)。锚点 BlindTilt 3C → 15975#3frMRDZj (step18) const deviceName = process.env.BT_DEVICE || 'Blind Tilt 42'; const BT_MODEL = process.env.BT_MODEL || 'BlindTilt 3C'; const CTRL_BT = onesCtrl('curtain', BT_MODEL); describe('Blind Tilt Control - 百叶帘控制(首页快捷弹窗)', () => { let driver: DeviceDriver; let reporter: TestReporter; setupResilientHooks(() => driver); beforeAll(async () => { driver = createDriver(); await driver.createSession(); reporter = new TestReporter('BlindTilt_Control', driver.platform.toUpperCase()); }); beforeEach(async () => { await driver.dismissPopupIfPresent(); await waitForHomeReady(driver, 25000); // 等 App 启动渲染完首页(SOAK 连跑首个用例 App 还在启动→找不到卡片失败) await driver.goBackToHomepage(); await sleep(500); await driver.dismissPopupIfPresent(); }); afterAll(async () => { reporter.generate(); await driver.destroySession(); }); // 点首页卡片开快捷弹窗(Fully open / Close Down + 状态)。统一用共享 findHomeCard 定位(两端一致), // 再按平台点卡开弹窗:iOS 点卡中心,Android 点卡名处(卡片可点区)。 async function openBtPopup(): Promise { const card = await findHomeCard(driver, deviceName); if (!card) throw new Error(`找不到${deviceName}卡片`); const rect = await driver.getElementRect(card).catch(() => null); if (!rect) throw new Error(`找不到${deviceName}卡片`); if (driver.platform === 'ios') { await driver.tap(Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2)); } else { await driver.tap(rect.x + Math.min(rect.width - 10, 60), rect.y); // 点卡名处开快捷弹窗 } // 等弹窗控件(Fully open / Close Down)渲染 for (let k = 0; k < 10; k++) { await sleep(500); if (/Close Down|Fully open/.test(await driver.getSource())) break; } } async function tapBtnAndWait(btn: string, pred: (s: string) => boolean): Promise { if (driver.platform === 'ios') { // iOS:Fully open / Close Down 是**文字标签**,真正可点的是其**上方约 50px 的圆形图标**(点标签本身无效)。 // "Fully open" 会出现两次(标题下的状态字 + 图标行的按钮标签)→ 取 y 最大(图标行)那个,点其上方图标。 const lbls = await driver.findElementsRaw('predicate string', `name == "${btn}" OR label == "${btn}"`).catch(() => [] as string[]); let target: { cx: number; y: number } | null = null; for (const l of lbls) { const r = await driver.getElementRect(l).catch(() => null); if (r && r.width > 0 && (!target || r.y > target.y)) target = { cx: Math.round(r.x + r.width / 2), y: r.y }; } if (!target) throw new Error(`浮层无 ${btn} 按钮`); await driver.tap(target.cx, Math.round(target.y - 50)); // 点标签上方圆形图标 } else { const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${btn}")`); expect(el).not.toBeNull(); await driver.tapElement(el!); } await sleep(8000); // 等百叶帘转动 // 统一读首页卡片状态(getHomeCardStatus 内部走 findHomeCard,会先收掉弹窗)。百叶帘倾斜态镜像在卡片副标题 // ("Fully open" / "Fully closed" / "NN% Opened up|Closed down")。**不再每轮回首页重开弹窗** —— 根治旧写法 // 轮询里反复 waitForHomeReady(25s)+waitForSource(12s)+滚到靠下卡 导致的整体超时(向下关闭曾 210s 超时)。 let st = 'unknown'; for (let i = 0; i < 10; i++) { // getHomeCardStatus 内部:findHomeCard 定位 + 对靠底卡自动上滑露出下方状态行(修复 BT 卡贴屏底读到 unknown)。 // 百叶帘倾斜态镜像在卡片副标题(Fully open / Fully closed / NN% Opened up|Closed down)。命中目标即停,不重开弹窗。 st = await getHomeCardStatus(driver, deviceName).catch(() => 'unknown'); if (pred(st)) break; await sleep(3000); } return st; } it(`${CTRL_BT} 向下关闭(Close Down)`, { timeout: 150000 }, async () => { const start = Date.now(); try { await openBtPopup(); expect(/Close Down|Fully open/.test(await driver.getSource())).toBe(true); // 弹窗已打开(有控制按钮) const after = await tapBtnAndWait('Close Down', s => /closed/i.test(s)); // 等到 "Fully closed" console.log('Close Down →', after); expect(/closed/i.test(after)).toBe(true); // 硬断言:已关闭(Fully closed) reporter.record(`${CTRL_BT} 向下关闭`, 'PASS', Date.now() - start, `状态=${after}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record(`${CTRL_BT} 向下关闭`, 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it(`${CTRL_BT} 完全打开(Fully open)`, { timeout: 150000 }, async () => { const start = Date.now(); try { await openBtPopup(); expect(/Close Down|Fully open/.test(await driver.getSource())).toBe(true); // 弹窗已打开(有控制按钮) const after = await tapBtnAndWait('Fully open', s => s === 'Fully open'); // 等到完全打开(水平) console.log('Fully open →', after); expect(after).toBe('Fully open'); // 硬断言:已完全打开 reporter.record(`${CTRL_BT} 完全打开`, 'PASS', Date.now() - start, `状态=${after}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record(`${CTRL_BT} 完全打开`, 'FAIL', Date.now() - start, e.message, ss); throw e; } }); });