AI_UIAutomation/tests/platform/scene_operation.test.ts

117 lines
5.1 KiB
TypeScript

import { describe, it, beforeAll, afterAll, expect } from 'vitest';
import { execSync } from 'child_process';
import { DeviceDriver } from '../../drivers/types';
import { createDriver } from '../../drivers/factory';
import { TestReporter } from '../../utils/test-reporter';
import { setupResilientHooks } from "../../utils/common";
import {
sleep, ensureHomeTab, onesPlatform,
createAutomation, verifyAutomationExecuted, deleteAutomation,
createScene, executeSceneFromHomepage, verifySceneInLogs, deleteScene,
signInWithEmail, isLoggedIn,
} from '../../utils/common';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
const ANCHOR = `${onesPlatform(15976)} 场景操作`; // P0:场景操作验证(① 自动化 ② 场景)
/** 读设备当前时间 [h, m]。Android 用 adb;iOS 用 idevicedate(系统已切 24h)。 */
function deviceNow(platform: string): [number, number] {
if (platform === 'ios') {
try {
const out = execSync('idevicedate').toString().trim(); // 形如 "Tue Jun 23 20:42:30 CST 2026"
const m = out.match(/(\d{1,2}):(\d{2}):\d{2}/);
if (m) return [parseInt(m[1], 10), parseInt(m[2], 10)];
} catch { /* 兜底用本机时间 */ }
const d = new Date();
return [d.getHours(), d.getMinutes()];
}
const out = execSync('adb shell date +%H:%M').toString().trim().split(':');
return [parseInt(out[0], 10), parseInt(out[1], 10)];
}
/** 在 [h,m] 上加 addMin 分钟,返回 [h,m](跨小时进位)。 */
function plusMin(h: number, m: number, addMin: number): [number, number] {
const t = (h * 60 + m + addMin) % (24 * 60);
return [Math.floor(t / 60), t % 60];
}
describe('Platform - 场景操作(自动化 + 场景)', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
setupResilientHooks(() => driver);
const autoName = `AutoOp${Date.now() % 100000}`;
const sceneName = `SceneOp${Date.now() % 100000}`;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('Platform_SceneOp', driver.platform.toUpperCase());
// 复位到干净首页:上一轮可能残留场景创建模态/Smart Devices 页,直接 isLoggedIn 会在残留态导航挂死(120s hook 超时)。
await driver.restartApp?.().catch(() => {});
await driver.goBackToHomepage().catch(() => {});
if (!(await isLoggedIn(driver))) await signInWithEmail(driver);
}, 90000);
afterAll(async () => {
// 兜底清理
try { await deleteAutomation(driver, autoName); } catch { /**/ }
try { await ensureHomeTab(driver); await deleteScene(driver, sceneName); } catch { /**/ }
reporter.generate();
await driver.destroySession();
});
it(`${ANCHOR} ① 自动化创建与执行`, { timeout: 360000 }, async () => {
const start = Date.now();
try {
// 定时设为设备时间 + 2 分钟(创建较快,留出创建耗时后约 1 分钟内触发)
const [nh, nm] = deviceNow(driver.platform);
const [th, tm] = plusMin(nh, nm, 2);
console.log(`设备时间 ${nh}:${nm} → 定时 ${th}:${tm}`);
const created = await createAutomation(driver, { name: autoName, deviceKeyword: 'Bot 14', targetHour: th, targetMinute: tm, action: 'Turns off' });
expect(created).toBe(true);
// 等到点(目标时间 + 40s 让引擎触发+落日志)
const deadlineMin = th * 60 + tm;
for (let i = 0; i < 60; i++) {
const [ch, cm] = deviceNow(driver.platform);
if (ch * 60 + cm > deadlineMin) break;
await sleep(10000);
}
await sleep(40000);
const executed = await verifyAutomationExecuted(driver, autoName);
expect(executed).toBe(true);
await deleteAutomation(driver, autoName);
reporter.record(`${ANCHOR} ①自动化`, 'PASS', Date.now() - start, `自动化 ${autoName} 定时${th}:${tm} 触发并有日志,已删除`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ANCHOR} ①自动化`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it(`${ANCHOR} ② 场景创建与执行`, { timeout: 180000 }, async () => {
const start = Date.now();
try {
// 场景动作与自动化(Bot Turns off)区分:Bot Turns on(同设备不同动作)
const created = await createScene(driver, { name: sceneName, deviceKeyword: 'Bot 14', action: 'Turns on' });
expect(created).toBe(true);
await ensureHomeTab(driver);
const executed = await executeSceneFromHomepage(driver, sceneName);
expect(executed).toBe(true);
await sleep(6000);
await driver.goBackToHomepage();
const hasLog = await verifySceneInLogs(driver, sceneName);
expect(hasLog).toBe(true);
await deleteScene(driver, sceneName);
reporter.record(`${ANCHOR} ②场景`, 'PASS', Date.now() - start, `场景 ${sceneName} 执行并有日志,已删除`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ANCHOR} ②场景`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});