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, enterEditInfo, renameDevice, changeDeviceRoom, scrollToAndTap, navigateToFirmwarePage, checkFirmwareVersion, } from '../../utils/common'; import * as dotenv from 'dotenv'; import * as path from 'path'; dotenv.config({ path: path.resolve(__dirname, '../../.env') }); const AIHUB_NAME = process.env.AIHUB_NAME || 'AI Hub 6C'; describe('AI Hub Settings - 设备设置页', () => { let driver: DeviceDriver; let reporter: TestReporter; const isAndroid = () => driver.platform === 'android'; const SETTINGS_ICON = () => isAndroid() ? { x: 999, y: 175 } : { x: 361, y: 70 }; const BACK_BTN = () => isAndroid() ? { x: 0, y: 0 } : { x: 39, y: 70 }; beforeAll(async () => { driver = createDriver(); await driver.createSession(); reporter = new TestReporter('AIHub_Settings', driver.platform.toUpperCase()); }); beforeEach(async () => { try { await driver.dismissPopupIfPresent(); await driver.goBackToHomepage(); await driver.dismissPopupIfPresent(); } catch { // Session may have crashed — recreate try { await driver.destroySession(); } catch {} await driver.createSession(); await sleep(3000); } }); afterAll(async () => { reporter.generate(); await driver.destroySession(); }); async function enterHubFunctionPage(): Promise { if (isAndroid()) { const hubEl = await (driver as any).findDeviceCard(AIHUB_NAME); if (!hubEl) return false; const rect = await driver.getElementRect(hubEl); await driver.tap(rect.x + 100, rect.y + 30); await sleep(6000); // Dismiss any popup that appears after entering hub page await driver.dismissPopupIfPresent(); await sleep(1000); const s = await driver.getSource(); if (s.includes('Try OpenClaw') || (s.includes('Cameras') && s.includes('AI Events'))) { return true; } await driver.tapElement(hubEl); await sleep(5000); await driver.dismissPopupIfPresent(); await sleep(1000); const s2 = await driver.getSource(); return s2.includes('Try OpenClaw') || (s2.includes('Cameras') && s2.includes('AI Events')); } // iOS path const maxScroll = 5; for (let i = 0; i <= maxScroll; i++) { let hubEl: string | null = null; hubEl = await driver.findElementRaw('predicate string', `name CONTAINS "${AIHUB_NAME}" AND type == "XCUIElementTypeCell"`); if (!hubEl) { hubEl = await driver.findElementRaw('predicate string', `label CONTAINS "${AIHUB_NAME}"`); } if (hubEl) { await driver.clickElement(hubEl); await sleep(5000); const s = await driver.getSource(); if (s.includes('Try OpenClaw') || (s.includes('Cameras') && s.includes('AI Events'))) { return true; } const rect = await driver.getElementRect(hubEl); await driver.tap(rect.x + rect.width / 2, rect.y + rect.height / 2); await sleep(5000); const s2 = await driver.getSource(); if (s2.includes('Try OpenClaw') || (s2.includes('Cameras') && s2.includes('AI Events'))) { return true; } } if (i < maxScroll) { await driver.swipe(195, 650, 195, 300, 0.5); await sleep(1500); } } return false; } async function enterHubSettings(): Promise { const entered = await enterHubFunctionPage(); if (!entered) return false; await driver.tap(SETTINGS_ICON().x, SETTINGS_ICON().y); await sleep(3000); const source = await driver.getSource(); if (source.includes('Settings') && source.includes('Device Settings')) return true; return !source.includes('Try OpenClaw') && !source.includes('AI Routines'); } it('进入AI Hub设置页', async () => { const start = Date.now(); try { const entered = await enterHubSettings(); expect(entered).toBe(true); const source = await driver.getSource(); const hasSettingsContent = source.includes('Firmware') || source.includes('Device Info') || source.includes('Delete') || source.includes('Wi-Fi') || source.includes(AIHUB_NAME) || source.includes('Cloud Service'); expect(hasSettingsContent).toBe(true); reporter.record('进入AI Hub设置页', 'PASS', Date.now() - start, '设置页正常打开'); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('进入AI Hub设置页', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('修改设备名称-保存还原', async () => { const start = Date.now(); try { const entered = await enterHubSettings(); expect(entered).toBe(true); const editOpened = await enterEditInfo(driver, AIHUB_NAME); expect(editOpened).toBe(true); const renamed = await renameDevice(driver, 'TestAIHub', 'save'); expect(renamed).toBe(true); let source = await driver.getSource(); expect(source).toContain('TestAIHub'); console.log('名称已修改为: TestAIHub'); const restored = await renameDevice(driver, AIHUB_NAME, 'save'); expect(restored).toBe(true); source = await driver.getSource(); expect(source).toContain(AIHUB_NAME); console.log('名称还原: true'); reporter.record('修改设备名称-保存还原', 'PASS', Date.now() - start, `${AIHUB_NAME} → TestAIHub → ${AIHUB_NAME} (还原=true)`); } 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 { const entered = await enterHubSettings(); expect(entered).toBe(true); const editOpened = await enterEditInfo(driver, AIHUB_NAME); expect(editOpened).toBe(true); const renamed = await renameDevice(driver, 'CancelTest', 'cancel'); expect(renamed).toBe(true); const source = await driver.getSource(); expect(source).toContain(AIHUB_NAME); reporter.record('修改设备名称-取消', 'PASS', Date.now() - start, `输入"CancelTest"后取消, 名称保持"${AIHUB_NAME}"`); } 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 { const entered = await enterHubSettings(); expect(entered).toBe(true); const editOpened = await enterEditInfo(driver, AIHUB_NAME); expect(editOpened).toBe(true); const result = await changeDeviceRoom(driver); expect(result.success).toBe(true); console.log('可选房间数:', result.roomCount); await changeDeviceRoom(driver); reporter.record('修改设备房间', 'PASS', Date.now() - start, `Select Room页面, ${result.roomCount}个房间可选`); } 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 { const entered = await enterHubSettings(); expect(entered).toBe(true); const navOk = await navigateToFirmwarePage(driver); if (!navOk) { reporter.record('固件升级页面信息', 'SKIP', Date.now() - start, '设备无固件升级入口'); return; } await sleep(3000); const version = await checkFirmwareVersion(driver); const detail = `固件版本=${version}`; console.log(detail); expect(version).not.toBe('unknown'); reporter.record('固件升级页面信息', 'PASS', Date.now() - start, detail); } 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 { const entered = await enterHubSettings(); expect(entered).toBe(true); let source = await driver.getSource(); let hasIndicator = source.includes('Indicator Light') || source.includes('LED'); // On Android AI Hub, Indicator Light is under "Device Settings" sub-page if (!hasIndicator && isAndroid()) { const deviceSettingsEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Device Settings")'); if (deviceSettingsEl) { await driver.tapElement(deviceSettingsEl); await sleep(3000); source = await driver.getSource(); hasIndicator = source.includes('Indicator Light') || source.includes('LED'); } } if (!hasIndicator) { await driver.scrollDown(300); await sleep(800); source = await driver.getSource(); hasIndicator = source.includes('Indicator Light') || source.includes('LED'); } let toggleEl: string | null = null; if (driver.platform === 'ios') { toggleEl = await driver.findElementRaw('predicate string', 'type == "XCUIElementTypeSwitch" AND visible == true'); } else { toggleEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().className("android.widget.Switch")'); } if (!toggleEl) { const tapped = await scrollToAndTap(driver, 'Indicator Light'); if (!tapped) { console.log('未找到指示灯开关,可能不支持'); reporter.record('指示灯开关切换', 'SKIP', Date.now() - start, '设备无指示灯开关选项(skip)'); return; } await sleep(2000); } else { await driver.tapElement(toggleEl); await sleep(2000); await driver.tapElement(toggleEl); await sleep(1000); } 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 { const entered = await enterHubSettings(); expect(entered).toBe(true); let source = await driver.getSource(); let found = source.includes('Network Settings') || source.includes('网络设置'); if (!found) { await driver.scrollDown(300); await sleep(800); source = await driver.getSource(); found = source.includes('Network Settings') || source.includes('网络设置'); } if (!found) { reporter.record('网络设置信息显示', 'SKIP', Date.now() - start, '未找到网络设置入口'); return; } const tapped = await scrollToAndTap(driver, 'Network Settings'); if (!tapped) { reporter.record('网络设置信息显示', 'SKIP', Date.now() - start, '网络设置入口不可点击'); return; } await sleep(3000); const netSource = await driver.getSource(); const hasWiFi = netSource.includes('Wi-Fi') || netSource.includes('SSID') || netSource.includes('IP') || netSource.includes('MAC') || netSource.includes('Ethernet') || netSource.includes('Wired'); const detail = `网络设置页加载成功, 含Wi-Fi/网络信息=${hasWiFi}`; console.log(detail); expect(hasWiFi).toBe(true); reporter.record('网络设置信息显示', 'PASS', Date.now() - start, detail); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('网络设置信息显示', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('Cloud Service显示', async () => { const start = Date.now(); try { const entered = await enterHubSettings(); expect(entered).toBe(true); let source = await driver.getSource(); let hasCloud = source.includes('Cloud Service') || source.includes('Third-party') || source.includes('Alexa') || source.includes('Google'); if (!hasCloud) { await driver.scrollDown(300); await sleep(800); source = await driver.getSource(); hasCloud = source.includes('Cloud Service') || source.includes('Third-party') || source.includes('Alexa') || source.includes('Google'); } if (!hasCloud) { console.log('Cloud Service选项不可见,可能需要更多滚动'); reporter.record('Cloud Service显示', 'SKIP', Date.now() - start, '页面无Cloud Service入口(skip)'); return; } const tapped = await scrollToAndTap(driver, 'Cloud Service'); if (tapped) { await sleep(3000); const cloudSource = await driver.getSource(); const hasCloudPage = cloudSource.includes('Alexa') || cloudSource.includes('Google') || cloudSource.includes('SmartThings') || cloudSource.includes('IFTTT') || cloudSource.includes('Cloud Service'); expect(hasCloudPage).toBe(true); } reporter.record('Cloud Service显示', 'PASS', Date.now() - start, 'Cloud Service页面正常'); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('Cloud Service显示', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); });