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 { getDeviceName } from '../../config/device.config'; import { sleep, enterDeviceSettings, renameDevice, changeDeviceRoom, navigateToTimerPage, addTimer, deleteAllTimers, navigateToFirmwarePage, checkFirmwareVersion, navigateToDeviceInfo, getDeviceInfo, scrollToAndTap, waitForSource, enterEditInfo, addDeviceViaBLE, isDeviceOnHomepage, createScene, executeSceneFromHomepage, deleteScene, ensureHomeTab, } from '../../utils/common'; import * as dotenv from 'dotenv'; import * as path from 'path'; dotenv.config({ path: path.resolve(__dirname, '../../.env') }); const deviceName = getDeviceName('fan', 'FAN_DEVICE'); // 必测项控制锚点: 无电池款 Circulator Fan 首页控制开关 BLE 15975#JBszgxRS / WiFi 15974#KNyzKhn6 const PROTO = process.env.PROTO === 'wifi' ? 'wifi' : 'ble'; const CTRL_FAN = PROTO === 'wifi' ? '[P0][ONES:15974#KNyzKhn6][wifi]' : '[P0][ONES:15975#JBszgxRS][ble]'; describe('Fan Control - 风扇控制页', () => { let driver: DeviceDriver; let reporter: TestReporter; beforeAll(async () => { driver = createDriver(); await driver.createSession(); reporter = new TestReporter('Fan_Control', driver.platform.toUpperCase()); }); beforeEach(async () => { await driver.dismissPopupIfPresent(); await driver.goBackToHomepage(); await driver.dismissPopupIfPresent(); }); afterAll(async () => { reporter.generate(); await driver.destroySession(); }); async function enterFanControl(): Promise { let el = await driver.findElementRaw('name', deviceName); if (!el) { await driver.scrollDown(250); await sleep(1000); el = await driver.findElementRaw('name', deviceName); } if (!el) throw new Error(`找不到${deviceName}卡片`); await driver.tapElement(el); await sleep(3000); } it('打开开关', async () => { const start = Date.now(); try { await enterFanControl(); const powerEl = await driver.findElementRaw('name', 'Power') || await driver.findElementRaw('name', 'power') || await driver.findElementRaw('name', 'ON/OFF'); if (!powerEl) throw new Error('Power按钮未找到'); await driver.tapElement(powerEl); await sleep(3000); const source = await driver.getSource(); const isOn = source.includes('ON') || source.includes('Normal') || source.includes('Speed'); console.log('风扇开启:', isOn); reporter.record(`${CTRL_FAN} 打开开关`, 'PASS', Date.now() - start, `Power已点击, ON=${isOn}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record(`${CTRL_FAN} 打开开关`, 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('切换模式', async () => { const start = Date.now(); try { await enterFanControl(); const sourceBefore = await driver.getSource(); const modesBefore = ['Normal', 'Natural', 'Sleep'].filter(m => sourceBefore.includes(m)); // Try tapping different mode buttons const naturalEl = await driver.findElementRaw('name', 'Natural') || await driver.findElementRaw('name', 'Natural Wind'); const sleepEl = await driver.findElementRaw('name', 'Sleep') || await driver.findElementRaw('name', 'Sleep Mode'); const normalEl = await driver.findElementRaw('name', 'Normal') || await driver.findElementRaw('name', 'Normal Mode'); let tappedMode = ''; if (naturalEl) { await driver.tapElement(naturalEl); tappedMode = 'Natural'; } else if (sleepEl) { await driver.tapElement(sleepEl); tappedMode = 'Sleep'; } else if (normalEl) { await driver.tapElement(normalEl); tappedMode = 'Normal'; } else { throw new Error('未找到模式按钮(Normal/Natural/Sleep)'); } await sleep(3000); const sourceAfter = await driver.getSource(); const modeChanged = sourceAfter.includes(tappedMode); console.log(`切换模式: ${tappedMode}, 结果: ${modeChanged}`); reporter.record('切换模式', 'PASS', Date.now() - start, `切换至${tappedMode}, changed=${modeChanged}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('切换模式', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('降低风速', async () => { const start = Date.now(); try { await enterFanControl(); const minusEl = await driver.findElementRaw('name', '-') || await driver.findElementRaw('name', 'minus') || await driver.findElementRaw('name', 'speed down') || await driver.findElementRaw('name', 'Speed Down'); if (!minusEl) throw new Error('减速按钮未找到'); await driver.tapElement(minusEl); await sleep(2000); const source = await driver.getSource(); console.log('降低风速完成'); reporter.record('降低风速', 'PASS', Date.now() - start, '风速-按钮已点击'); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('降低风速', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('增加风速', async () => { const start = Date.now(); try { await enterFanControl(); const plusEl = await driver.findElementRaw('name', '+') || await driver.findElementRaw('name', 'plus') || await driver.findElementRaw('name', 'speed up') || await driver.findElementRaw('name', 'Speed Up'); if (!plusEl) throw new Error('加速按钮未找到'); await driver.tapElement(plusEl); await sleep(2000); const source = await driver.getSource(); console.log('增加风速完成'); reporter.record('增加风速', 'PASS', Date.now() - start, '风速+按钮已点击'); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('增加风速', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('小夜灯亮度', async () => { const start = Date.now(); try { await enterFanControl(); // Scroll down to find Night Light section await driver.scrollDown(300); await sleep(1000); const nightLightEl = await driver.findElementRaw('name', 'Night Light') || await driver.findElementRaw('name', 'night light') || await driver.findElementRaw('name', 'Nightlight'); if (!nightLightEl) throw new Error('Night Light区域未找到'); const rect = await driver.getElementRect(nightLightEl); // Swipe on brightness slider area (below the Night Light label) const sliderY = rect.y + rect.height + 40; await driver.swipe(rect.x + 50, sliderY, rect.x + 200, sliderY, 500); await sleep(2000); const source = await driver.getSource(); console.log('小夜灯亮度调节完成'); reporter.record('小夜灯亮度', 'PASS', Date.now() - start, '亮度滑块已调节'); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('小夜灯亮度', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('关闭小夜灯', async () => { const start = Date.now(); try { await enterFanControl(); await driver.scrollDown(300); await sleep(1000); const nightLightEl = await driver.findElementRaw('name', 'Night Light') || await driver.findElementRaw('name', 'night light') || await driver.findElementRaw('name', 'Nightlight'); if (!nightLightEl) throw new Error('Night Light区域未找到'); const rect = await driver.getElementRect(nightLightEl); // Tap the switch/toggle near Night Light (right side) await driver.tap(rect.x + rect.width + 50, rect.y + rect.height / 2); await sleep(2000); const source = await driver.getSource(); const isOff = source.includes('OFF') || !source.includes('brightness'); console.log('小夜灯关闭:', isOff); reporter.record('关闭小夜灯', 'PASS', Date.now() - start, `小夜灯OFF=${isOff}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('关闭小夜灯', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('水平摆风', async () => { const start = Date.now(); try { await enterFanControl(); await driver.scrollDown(300); await sleep(1000); const swingEl = await driver.findElementRaw('name', 'Horizontal Swing') || await driver.findElementRaw('name', 'Horizontal') || await driver.findElementRaw('name', 'H Swing'); if (!swingEl) throw new Error('Horizontal Swing未找到'); await driver.tapElement(swingEl); await sleep(2000); const source = await driver.getSource(); console.log('水平摆风已切换'); reporter.record('水平摆风', 'PASS', Date.now() - start, 'Horizontal Swing已点击'); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('水平摆风', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('开启自动摆风', async () => { const start = Date.now(); try { await enterFanControl(); await driver.scrollDown(300); await sleep(1000); const swingEl = await driver.findElementRaw('name', 'Swing') || await driver.findElementRaw('name', 'Auto Swing') || await driver.findElementRaw('name', 'Oscillation'); if (!swingEl) throw new Error('Swing/Auto Swing按钮未找到'); await driver.tapElement(swingEl); await sleep(2000); const source = await driver.getSource(); const isOn = source.includes('ON') || source.includes('Swing'); console.log('自动摆风开启:', isOn); reporter.record('开启自动摆风', 'PASS', Date.now() - start, `Swing ON=${isOn}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('开启自动摆风', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('开启延迟', async () => { const start = Date.now(); try { await enterFanControl(); const tapped = await scrollToAndTap(driver, 'Delay'); if (!tapped) throw new Error('Delay选项未找到'); await sleep(2000); // Set delay time - confirm const source = await driver.getSource(); const hasTimePicker = source.includes('hour') || source.includes('min') || source.includes('Hour') || source.includes('Min'); console.log('延迟设置页:', hasTimePicker); // Tap confirm/save const confirmEl = await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'OK') || await driver.findElementRaw('name', 'Save'); if (confirmEl) { await driver.tapElement(confirmEl); await sleep(2000); } reporter.record('开启延迟', 'PASS', Date.now() - start, `Delay页面=${hasTimePicker}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('开启延迟', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('定时器添加', async () => { const start = Date.now(); try { await enterFanControl(); const navOk = await navigateToTimerPage(driver); expect(navOk).toBe(true); await sleep(2000); const added = await addTimer(driver); expect(added).toBe(true); const source = await driver.getSource(); const hasTimer = source.includes('Only once') || source.includes('Timer') || source.includes('Schedule'); console.log('定时器添加:', hasTimer); reporter.record('定时器添加', 'PASS', Date.now() - start, `定时器添加成功, hasTimer=${hasTimer}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('定时器添加', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('定时器删除', async () => { const start = Date.now(); try { await enterFanControl(); const navOk = await navigateToTimerPage(driver); expect(navOk).toBe(true); await sleep(2000); const deleted = await deleteAllTimers(driver); expect(deleted).toBe(true); console.log('定时器已全部删除'); reporter.record('定时器删除', 'PASS', Date.now() - start, '所有定时器已删除'); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('定时器删除', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('关闭开关', async () => { const start = Date.now(); try { await enterFanControl(); const powerEl = await driver.findElementRaw('name', 'Power') || await driver.findElementRaw('name', 'power') || await driver.findElementRaw('name', 'ON/OFF'); if (!powerEl) throw new Error('Power按钮未找到'); await driver.tapElement(powerEl); await sleep(3000); const source = await driver.getSource(); const isOff = source.includes('OFF') || source.includes('off') || source.includes('Disconnected'); console.log('风扇关闭:', isOff); reporter.record(`${CTRL_FAN} 关闭开关`, 'PASS', Date.now() - start, `Power OFF=${isOff}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record(`${CTRL_FAN} 关闭开关`, 'FAIL', Date.now() - start, e.message, ss); throw e; } }); });