AI_UIAutomation/tests/aihub/aihub_connect.test.ts

287 lines
10 KiB
TypeScript

import { describe, it, beforeAll, afterAll, expect } from 'vitest';
import { DeviceDriver } from '../../drivers/types';
import { createDriver } from '../../drivers/factory';
import { TestReporter } from '../../utils/test-reporter';
import {
sleep,
addDeviceViaBLE,
isDeviceOnHomepage,
} 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';
const WIFI_SSID = process.env.WIFI_SSID || 'SwitchBot_Office';
const WIFI_PASSWORD = process.env.WIFI_PASSWORD || '';
describe('AIHub Connect - 添加AI Hub设备', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('AIHub_Connect', driver.platform.toUpperCase());
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
async function inputWiFiCredentials(driver: DeviceDriver): Promise<void> {
await sleep(3000);
const source = await driver.getSource();
if (!source.includes('Wi-Fi') && !source.includes('WiFi') && !source.includes('SSID') && !source.includes('Network')) {
return;
}
if (driver.platform === 'ios') {
const ssidField = await driver.findElementRaw('predicate string',
'type == "XCUIElementTypeTextField" AND (value CONTAINS "SSID" OR value CONTAINS "Wi-Fi" OR value CONTAINS "Network" OR value == "")');
if (ssidField) {
await driver.tapElement(ssidField);
await sleep(500);
await driver.clearText(ssidField);
await driver.typeText(ssidField, WIFI_SSID);
await sleep(500);
}
const pwdField = await driver.findElementRaw('predicate string',
'type == "XCUIElementTypeSecureTextField" OR (type == "XCUIElementTypeTextField" AND value CONTAINS "Password")');
if (pwdField) {
await driver.tapElement(pwdField);
await sleep(500);
await driver.clearText(pwdField);
await driver.typeText(pwdField, WIFI_PASSWORD);
await sleep(500);
}
} else {
const ssidField = await driver.findElementRaw('-android uiautomator',
'new UiSelector().className("android.widget.EditText").instance(0)');
if (ssidField) {
await driver.tapElement(ssidField);
await sleep(300);
await driver.clearText(ssidField);
await driver.typeText(ssidField, WIFI_SSID);
await sleep(500);
}
const pwdField = await driver.findElementRaw('-android uiautomator',
'new UiSelector().className("android.widget.EditText").instance(1)');
if (pwdField) {
await driver.tapElement(pwdField);
await sleep(300);
await driver.clearText(pwdField);
await driver.typeText(pwdField, WIFI_PASSWORD);
await sleep(500);
}
}
const nextBtnNames = ['Next', 'Connect', 'Join', 'OK', 'Confirm'];
for (const btn of nextBtnNames) {
let el: string | null = null;
if (driver.platform === 'ios') {
el = await driver.findElementRaw('name', btn);
} else {
el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${btn}")`);
}
if (el) {
await driver.tapElement(el);
await sleep(5000);
break;
}
}
}
function getAIHubAddOptions() {
return {
categoryName: 'AI Hub',
deviceKeyword: AIHUB_NAME,
scanTimeout: 30000,
skipScanStep: true,
skipNextButton: true,
connectionKeywords: [
'Initial Setup', 'Start Using', 'Done', 'added successfully',
'Got it', 'cloud service', 'Pick a room', 'Display Type',
'Wi-Fi', 'WiFi', 'SSID', 'Network', 'Configure Wi-Fi',
],
preSelectSteps: async (d: DeviceDriver) => {
const el = await d.findElementRaw('name', 'Connect Device');
if (el) {
await d.clickElement(el);
await sleep(2000);
}
const agreeEl = await d.findElementRaw('name', 'Agree');
if (agreeEl) {
await d.clickElement(agreeEl);
await sleep(3000);
}
},
postConnectionSteps: async (d: DeviceDriver) => {
await inputWiFiCredentials(d);
},
};
}
it('通过BLE添加AI Hub 6C设备', { timeout: 180000 }, async () => {
const start = Date.now();
try {
const alreadyExists = await isDeviceOnHomepage(driver, AIHUB_NAME);
if (alreadyExists) {
console.log(`${AIHUB_NAME}已在首页,跳过重新添加`);
reporter.record(`添加${AIHUB_NAME}`, 'PASS', Date.now() - start, `${AIHUB_NAME}已存在, 无需重新添加`);
return;
}
const result = await addDeviceViaBLE(driver, getAIHubAddOptions());
expect(result).toBe(true);
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
reporter.record(`添加${AIHUB_NAME}`, 'PASS', Date.now() - start, `${AIHUB_NAME}添加成功, 耗时${elapsed}s`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`添加${AIHUB_NAME}`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
const SETTINGS_ICON = driver.platform === 'android' ? { x: 999, y: 175 } : { x: 361, y: 70 };
const BACK_BTN = driver.platform === 'android' ? { x: 50, y: 88 } : { x: 39, y: 70 };
async function navigateToHubFunctionPage(): Promise<boolean> {
await driver.goBackToHomepage();
await sleep(1000);
if (driver.platform === 'android') {
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);
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;
}
return false;
}
// 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 removeHubFromSettings(): Promise<boolean> {
// Enter Hub function page
const entered = await navigateToHubFunctionPage();
if (!entered) return false;
// Tap settings icon (top-right)
await driver.tap(SETTINGS_ICON.x, SETTINGS_ICON.y);
await sleep(3000);
// Find and tap Delete (scroll down to find it)
let delEl: string | null = null;
for (let i = 0; i < 8; i++) {
if (driver.platform === 'ios') {
delEl = await driver.findElementRaw('predicate string', 'name == "Delete" AND visible == true');
} else {
delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")');
}
if (delEl) break;
await driver.scrollDown(300);
await sleep(800);
}
if (!delEl) return false;
await driver.tapElement(delEl);
await sleep(3000);
// Confirm deletion - tap OK button in confirmation dialog
const confirmNames = ['OK', 'Confirm', 'Delete', 'Yes'];
for (const name of confirmNames) {
let el: string | null = null;
if (driver.platform === 'ios') {
el = await driver.findElementRaw('name', name);
} else {
el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${name}")`);
}
if (el) {
await driver.tapElement(el);
await sleep(5000);
break;
}
}
// Navigate back to homepage manually (avoid goBackToHomepage misdetecting "Home Assistant" as homepage)
for (let i = 0; i < 8; i++) {
const s = await driver.getSource();
const isHome = s.includes('Add') && s.includes('More') && s.includes('Home')
&& !s.includes('Home Assistant') && !s.includes('Device Settings');
if (isHome) break;
await driver.tap(BACK_BTN.x, BACK_BTN.y);
await sleep(2000);
}
await sleep(2000);
const homeSource = await driver.getSource();
return !homeSource.includes(AIHUB_NAME);
}
it('删除AI Hub 6C设备并恢复', { timeout: 180000 }, async () => {
const start = Date.now();
try {
const exists = await isDeviceOnHomepage(driver, AIHUB_NAME);
if (!exists) {
console.log(`${AIHUB_NAME}不在首页,跳过删除测试`);
reporter.record(`删除${AIHUB_NAME}`, 'PASS', Date.now() - start, `${AIHUB_NAME}不存在, 无需删除`);
return;
}
const removed = await removeHubFromSettings();
expect(removed).toBe(true);
// Data recovery: re-add the device
const reAdded = await addDeviceViaBLE(driver, getAIHubAddOptions());
expect(reAdded).toBe(true);
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
reporter.record(`删除${AIHUB_NAME}`, 'PASS', Date.now() - start, `删除并重新添加${AIHUB_NAME}成功, 耗时${elapsed}s`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`删除${AIHUB_NAME}`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});