389 lines
14 KiB
TypeScript
389 lines
14 KiB
TypeScript
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<boolean> {
|
||
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<boolean> {
|
||
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;
|
||
}
|
||
});
|
||
});
|