AI_UIAutomation/tests/aihub/aihub_ai_events.test.ts

2017 lines
82 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 } from '../../utils/common';
import { execSync } from 'child_process';
import { robustBeforeAll, robustBeforeEach } from './aihub-setup.helper';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
const AIHUB_NAME = getDeviceName('aihub', 'AIHUB_NAME');
describe('【AI Hub AI事件分析】- 功能覆盖 (已开通AI+)', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
const isAndroid = () => driver.platform === 'android';
// Android 坐标常量
const SEARCH_BAR = () => isAndroid() ? { x: 540, y: 326 } : { x: 195, y: 121 };
const FILTER_ICON = () => isAndroid() ? { x: 905, y: 189 } : { x: 327, y: 70 };
const MORE_ICON = () => isAndroid() ? { x: 991, y: 189 } : { x: 358, y: 70 };
const SWIPE_CENTER_X = () => isAndroid() ? 540 : 195;
// 视频回放页控制按钮坐标 (Android 1080x2280, 点击视频画面后出现)
const VIDEO_CENTER = () => isAndroid() ? { x: 540, y: 500 } : { x: 195, y: 250 };
const BTN_Y = 805;
const BTN_PLAY_X = 108;
const BTN_SCREENSHOT_X = 324;
const BTN_DOWNLOAD_X = 540;
const BTN_SHARE_X = 756;
const BTN_DELETE_X = 972;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
await robustBeforeAll(driver);
reporter = new TestReporter('AIHub_AIEvents', driver.platform.toUpperCase());
});
beforeEach(async () => {
await robustBeforeEach(driver);
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
// --- 辅助函数 ---
async function captureScreenshot(): Promise<string | undefined> {
try { return await driver.screenshot(); } catch { return undefined; }
}
async function waitForLoading(maxWait = 30000): Promise<void> {
const start = Date.now();
while (Date.now() - start < maxWait) {
const s = await driver.getSource();
if (!s.includes('Loading') && !s.includes('In progress')) return;
await sleep(3000);
}
}
async function logPageElements(): Promise<string> {
const source = await driver.getSource();
if (isAndroid()) {
const textRe = /text="([^"]{1,80})"/g;
const descRe = /content-desc="([^"]{1,80})"/g;
const texts: string[] = [];
const descs: string[] = [];
let m;
while ((m = textRe.exec(source)) !== null) {
if (m[1] && !texts.includes(m[1])) texts.push(m[1]);
}
while ((m = descRe.exec(source)) !== null) {
if (m[1] && !descs.includes(m[1])) descs.push(m[1]);
}
console.log('Page texts:', texts.join(' | '));
if (descs.length) console.log('Page descs:', descs.join(' | '));
} else {
const nameRe = /name="([^"]{1,80})"/g;
const names: string[] = [];
let m;
while ((m = nameRe.exec(source)) !== null) {
if (!names.includes(m[1])) names.push(m[1]);
}
console.log('Page elements:', names.join(' | '));
}
return source;
}
async function ensureAppRunning(): Promise<void> {
if (!isAndroid()) return;
try {
const src = await driver.getSource();
if (src.includes('com.theswitchbot.switchbot') || src.includes('SwitchBot')
|| src.includes('Home') || src.includes('Cameras') || src.includes('AI Events')
|| src.includes('Device') || src.includes('Scene')) return;
} catch { /* getSource failed, app likely crashed */ }
try {
execSync('adb shell am force-stop com.theswitchbot.switchbot');
await sleep(2000);
execSync('adb shell am start -n com.theswitchbot.switchbot/com.theswitchbot.switchbot.activities.MainActivity');
await sleep(10000);
await driver.dismissPopupIfPresent();
} catch { /* ignore */ }
}
async function enterHubFunctionPage(): Promise<boolean> {
const src = await driver.getSource();
if (src.includes('Cameras') && src.includes('AI Events')) return true;
// 如果app退出了重新启动
await ensureAppRunning();
await driver.goBackToHomepage();
await sleep(2000);
await driver.dismissPopupIfPresent();
if (isAndroid()) {
const card = await (driver as any).findDeviceCard(AIHUB_NAME);
if (!card) return false;
await driver.tapElement(card);
await sleep(5000);
await waitForLoading();
await driver.dismissPopupIfPresent();
const s = await driver.getSource();
return s.includes('Cameras') || s.includes('AI Events');
}
for (let scroll = 0; scroll <= 5; scroll++) {
let 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.tapElement(hubEl);
await sleep(5000);
await waitForLoading();
await driver.dismissPopupIfPresent();
const s = await driver.getSource();
if (s.includes('Cameras') || s.includes('AI Events')) return true;
}
if (scroll < 5) {
await driver.swipe(195, 650, 195, 300, 0.5);
await sleep(1500);
}
}
return false;
}
async function enterAIEvents(): Promise<boolean> {
const src = await driver.getSource();
if (src.includes('Today') && src.includes('AI Events') && !src.includes('Try OpenClaw')) return true;
if (!(src.includes('Cameras') && src.includes('AI Events'))) {
const ok = await enterHubFunctionPage();
if (!ok) return false;
}
if (isAndroid()) {
const el = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("AI Events")');
if (el) {
await driver.tapElement(el);
await sleep(5000);
await waitForLoading();
const s = await driver.getSource();
if (s.includes('Today') || (s.includes('AI Events') && !s.includes('Try OpenClaw'))) return true;
}
} else {
const el = await driver.findElementRaw('predicate string', 'label == "AI Events"');
if (el) {
await driver.tapElement(el);
await sleep(5000);
await waitForLoading();
const s = await driver.getSource();
if (s.includes('Today') || (s.includes('AI Events') && !s.includes('Try OpenClaw'))) return true;
}
}
return false;
}
async function ensureOnAIEvents(): Promise<boolean> {
const src = await driver.getSource();
if (src.includes('Today') && src.includes('AI Events') && !src.includes('Try OpenClaw')) return true;
if (src.includes('Change View') || src.includes('Delete All')) {
await driver.tap(SWIPE_CENTER_X(), isAndroid() ? 1350 : 500);
await sleep(1500);
const s = await driver.getSource();
if (s.includes('Today') && s.includes('AI Events') && !s.includes('Try OpenClaw')) return true;
}
if (src.includes('Filter') && (src.includes('Start') || src.includes('Restore Defaults'))) {
if (isAndroid()) await driver.goBack(); else await driver.tap(39, 70);
await sleep(2000);
const s = await driver.getSource();
if (s.includes('Today') && s.includes('AI Events')) return true;
}
if (isOnEventDetail(src)) {
if (isAndroid()) await driver.goBack(); else await driver.tap(39, 70);
await sleep(2000);
const s = await driver.getSource();
if (s.includes('Today') && s.includes('AI Events')) return true;
}
return await enterAIEvents();
}
async function goBack(): Promise<void> {
if (isAndroid()) await driver.goBack(); else await driver.tap(39, 70);
await sleep(2000);
}
function isOnEventDetail(source: string): boolean {
const hasPlayback = (source.includes('摄像机') || source.includes('Cam'))
&& source.includes('Today')
&& !source.includes('Analysis');
const hasZoom = source.includes('1.0x') && !source.includes('Analysis');
return hasPlayback || hasZoom
|| source.includes('View Playback')
|| source.includes('Report false recognition')
|| source.includes('Recommended Automation');
}
async function tapFirstEventCard(): Promise<boolean> {
await sleep(1000);
if (isAndroid()) {
const curSrc = await driver.getSource();
if (!curSrc.includes('AI Events')) return false;
const descs = await driver.findElementsRaw('-android uiautomator',
'new UiSelector().descriptionMatches("\\d{1,2}:\\d{2}:\\d{2}")');
if (descs && descs.length > 0) {
await driver.clickElement(descs[0]);
await sleep(5000);
await waitForLoading(10000);
const after = await driver.getSource();
if (isOnEventDetail(after)) return true;
if (after.includes('AI Events')) {
await driver.tapElement(descs[0]);
await sleep(5000);
const after2 = await driver.getSource();
if (isOnEventDetail(after2)) return true;
}
}
// 备选坐标
const src2 = await driver.getSource();
if (isOnEventDetail(src2)) return true;
if (!src2.includes('AI Events')) { await driver.goBack(); await sleep(2000); }
await driver.tap(540, 800);
await sleep(5000);
const after = await driver.getSource();
return isOnEventDetail(after);
} else {
await driver.tap(195, 400);
await sleep(3000);
}
const after = await driver.getSource();
return isOnEventDetail(after);
}
async function switchToTileView(): Promise<boolean> {
await driver.tap(MORE_ICON().x, MORE_ICON().y);
await sleep(2000);
const src = await driver.getSource();
if (src.includes('Change View')) {
if (isAndroid()) {
const el = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Change View")');
if (el) { await driver.tapElement(el); await sleep(2000); return true; }
} else {
const el = await driver.findElementRaw('predicate string', 'label == "Change View"');
if (el) { await driver.tapElement(el); await sleep(2000); return true; }
}
}
await driver.tap(SWIPE_CENTER_X(), isAndroid() ? 1350 : 500);
await sleep(1000);
return false;
}
// 点击视频画面 → 出现控制按钮 → 点击指定按钮坐标
async function tapVideoControlButton(btnX: number, steps: string[]): Promise<string> {
await driver.tap(VIDEO_CENTER().x, VIDEO_CENTER().y);
await sleep(2000);
steps.push(`点击视频画面(${VIDEO_CENTER().x},${VIDEO_CENTER().y})显示控制按钮`);
await driver.tap(btnX, BTN_Y);
await sleep(3000);
steps.push(`点击按钮坐标(${btnX},${BTN_Y})`);
return await driver.getSource();
}
// 从分析详情页进入回放页 (点击 "View Playback")
async function enterPlaybackFromAnalysis(steps: string[]): Promise<boolean> {
const src = await driver.getSource();
if (!src.includes('View Playback')) {
steps.push('当前页面无View Playback按钮');
return false;
}
let vpEl: string | null = null;
if (isAndroid()) {
vpEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("View Playback")');
} else {
vpEl = await driver.findElementRaw('predicate string', 'label == "View Playback"');
}
if (!vpEl) {
steps.push('未找到View Playback元素');
return false;
}
await driver.tapElement(vpEl);
await sleep(5000);
await waitForLoading(15000);
steps.push('点击View Playback进入回放页');
const afterSrc = await driver.getSource();
const onPlayback = afterSrc.includes('1.0x') || afterSrc.includes('Playback')
|| (afterSrc.includes('Cam') && !afterSrc.includes('View Playback'));
if (onPlayback) {
steps.push('确认进入回放页');
} else {
steps.push('页面未完全切换到回放页,继续等待');
await sleep(3000);
}
return true;
}
// 从AI Events进入分析详情页的完整流程
// 策略: 先尝试点击事件卡片,如果进入的是回放页(非分析页),说明当前是列表视图,需要切换
async function enterAnalysisDetailPage(steps: string[]): Promise<boolean> {
const onPage = await ensureOnAIEvents();
if (!onPage) { steps.push('无法进入AI Events页面'); return false; }
steps.push('确认在AI Events页面');
// 尝试点击事件卡片
const entered = await tapFirstEventCard();
if (!entered) {
// 可能需要切换视图后再试
await switchToTileView();
steps.push('点击事件失败,尝试切换视图');
const entered2 = await tapFirstEventCard();
if (!entered2) { steps.push('切换视图后仍无法进入详情'); return false; }
}
const src = await driver.getSource();
const onAnalysis = src.includes('View Playback') || src.includes('Report false recognition');
if (onAnalysis) {
steps.push('点击事件进入分析详情页(已在平铺视图)');
return true;
}
// 进入的是回放页(列表视图的详情) → 返回 → 切换视图 → 再进
steps.push('进入的是回放页(列表视图),需要切换到平铺视图');
await goBack();
await switchToTileView();
steps.push('切换到平铺视图');
const entered3 = await tapFirstEventCard();
if (!entered3) { steps.push('切换后无法进入详情'); return false; }
const src2 = await driver.getSource();
const onAnalysis2 = src2.includes('View Playback') || src2.includes('Report false recognition');
if (!onAnalysis2) {
steps.push('切换后进入的仍不是分析详情页');
await logPageElements();
return false;
}
steps.push('切换平铺后成功进入分析详情页');
return true;
}
// 处理权限弹窗
async function handlePermissionDialog(src: string, steps: string[]): Promise<boolean> {
if (src.includes('允许') || src.includes('Allow') || src.includes('访问')) {
steps.push('检测到权限弹窗');
let allowEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("允许")');
if (!allowEl) allowEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Allow")');
if (allowEl) {
await driver.tapElement(allowEl);
await sleep(3000);
steps.push('点击"允许"');
return true;
}
}
return false;
}
// 按下键盘搜索键 (Android: KEYCODE_SEARCH=84, ENTER=66)
function pressKeyboardSearch(): void {
if (isAndroid()) {
try { execSync('adb shell input keyevent 66', { timeout: 5000 }); } catch {}
}
}
// ============================================================
// 一、AI事件分析页面跳转
// ============================================================
it('1.1 已开通AI+服务AI事件分析页面跳转', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const ok = await enterAIEvents();
steps.push('导航进入AI Events页面');
expect(ok).toBe(true);
const source = await driver.getSource();
expect(source).toContain('AI Events');
expect(source).toContain('Today');
steps.push('验证页面包含"AI Events"和"Today"');
reporter.record('AI事件分析页面跳转', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('AI事件分析页面跳转', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
throw e;
}
});
it('1.2 事件列表跳转', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
const entered = await tapFirstEventCard();
steps.push('点击第一条事件');
expect(entered).toBe(true);
const source = await driver.getSource();
expect(isOnEventDetail(source)).toBe(true);
steps.push('验证进入事件详情(回放)页');
await goBack();
steps.push('返回列表');
reporter.record('事件列表跳转', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('事件列表跳转', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
// ============================================================
// 二、事件查看 (列表模式 - 回放页仅支持滑动/缩放)
// ============================================================
it('2.1 事件查看(滑动)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
const entered = await tapFirstEventCard();
expect(entered).toBe(true);
steps.push('进入事件回放页');
await driver.swipe(isAndroid() ? 850 : 300, isAndroid() ? 650 : 300,
isAndroid() ? 230 : 80, isAndroid() ? 650 : 300, 0.5);
await sleep(2000);
steps.push('左滑切换下一事件');
const source = await driver.getSource();
expect(isOnEventDetail(source)).toBe(true);
steps.push('验证仍在回放页');
await goBack();
reporter.record('事件查看(滑动)', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('事件查看(滑动)', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
it('2.2 事件查看(滑动极限)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
const entered = await tapFirstEventCard();
expect(entered).toBe(true);
steps.push('进入事件回放页');
await driver.swipe(isAndroid() ? 230 : 80, isAndroid() ? 650 : 300,
isAndroid() ? 850 : 300, isAndroid() ? 650 : 300, 0.5);
await sleep(1000);
await driver.swipe(isAndroid() ? 230 : 80, isAndroid() ? 650 : 300,
isAndroid() ? 850 : 300, isAndroid() ? 650 : 300, 0.5);
await sleep(1000);
steps.push('连续右滑2次(到达第一条)');
const source = await driver.getSource();
expect(isOnEventDetail(source)).toBe(true);
steps.push('验证仍在回放页未退出');
await goBack();
reporter.record('事件查看(滑动极限)', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('事件查看(滑动极限)', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
it('2.3 事件查看(缩放)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
const entered = await tapFirstEventCard();
expect(entered).toBe(true);
steps.push('进入事件回放页');
const centerX = VIDEO_CENTER().x;
const centerY = VIDEO_CENTER().y;
await driver.doubleTap(centerX, centerY);
await sleep(2000);
steps.push(`双击视频区域(${centerX},${centerY})进行缩放`);
const source = await driver.getSource();
expect(isOnEventDetail(source)).toBe(true);
steps.push('验证仍在回放页');
await driver.doubleTap(centerX, centerY);
await sleep(1000);
steps.push('双击恢复原始比例');
await goBack();
reporter.record('事件查看(缩放)', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('事件查看(缩放)', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
// ============================================================
// 三、事件列表平铺
// ============================================================
it('3.1 事件列表平铺', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
const switched = await switchToTileView();
steps.push(`切换平铺视图: ${switched ? '成功' : '可能已是平铺'}`);
const source = await driver.getSource();
const stillOnPage = source.includes('AI Events') || source.includes('Today');
expect(stillOnPage).toBe(true);
steps.push('验证切换后仍在AI Events页面');
reporter.record('事件列表平铺', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('事件列表平铺', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
throw e;
}
});
// ============================================================
// 四、AI事件分析详情页按钮功能验证
// 流程: AI Events → 切换平铺 → 点击事件 → 分析详情页
// 分析详情页按钮: View Playback, Report false recognition,
// Recommended Automation, Recommended Notifications
// View Playback → 回放页 → 下载/分享/删除
// ============================================================
it('4.1 分析详情页 - View Playback (进入回放)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
const entered = await enterPlaybackFromAnalysis(steps);
expect(entered).toBe(true);
const src = await driver.getSource();
const onPlayback = src.includes('1.0x') || src.includes('Playback')
|| (src.includes('Cam') && !src.includes('View Playback'));
expect(onPlayback).toBe(true);
steps.push('验证回放页正确加载(含1.0x或Cam标识)');
await goBack(); // 回放→分析
await goBack(); // 分析→事件列表
reporter.record('分析详情页-View Playback', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-View Playback', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack();
throw e;
}
});
it('4.2 分析详情页 - 滑动切换事件', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
// 记录当前页面内容特征(时间/描述)
const beforeSrc = await driver.getSource();
const beforeHasPage = beforeSrc.includes('1/') || beforeSrc.includes('2/');
steps.push(`左滑前页面指示: ${beforeHasPage ? '有页码' : '无页码'}`);
await driver.swipe(isAndroid() ? 850 : 300, isAndroid() ? 650 : 300,
isAndroid() ? 230 : 80, isAndroid() ? 650 : 300, 0.5);
await sleep(2000);
steps.push('左滑切换事件');
const afterSrc = await driver.getSource();
const stillOnDetail = afterSrc.includes('View Playback') || afterSrc.includes('Report false recognition');
expect(stillOnDetail).toBe(true);
steps.push('验证滑动后仍在分析详情页');
await goBack();
reporter.record('分析详情页-滑动切换', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-滑动切换', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
it('4.3 分析详情页 - 下载 (右下角按钮)', { timeout: 150000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
// 底部三个按钮: (688,2062)=删除, (838,2062)=下载, (988,2062)=分享
await driver.tap(838, 2062);
await sleep(5000);
steps.push('点击下载按钮(838,2062)');
const src = await driver.getSource();
// 下载可能触发: 1)时间选择页 2)权限弹窗 3)toast提示后直接下载(无持久UI)
const downloadTriggered = src.includes('Download') || src.includes('Please select')
|| src.includes('允许') || src.includes('Allow')
|| src.includes('Saved') || src.includes('Downloaded')
|| src.includes('save') || src.includes('storage');
if (downloadTriggered) {
steps.push(`下载功能触发: ${src.includes('Please select') ? '时间选择页' : src.includes('Allow') || src.includes('允许') ? '权限弹窗' : '下载响应'}`);
await handlePermissionDialog(src, steps);
const curSrc = await driver.getSource();
if (curSrc.includes('Please select')) {
await goBack(); // 时间选择页→分析
}
} else {
// 下载按钮可能是静默下载(toast通知),验证页面仍在分析详情
const stillOnAnalysis = src.includes('View Playback') || src.includes('Report false recognition');
if (stillOnAnalysis) {
steps.push('下载按钮已点击,页面仍在分析详情(可能静默下载成功)');
} else {
steps.push('下载按钮点击后页面变化');
await logPageElements();
expect(false).toBe(true);
}
}
await goBack();
reporter.record('分析详情页-下载', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-下载', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack();
throw e;
}
});
it('4.4 分析详情页 - 分享 (右下角按钮)', { timeout: 150000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
// 底部三个按钮: (688,2062)=删除, (838,2062)=下载, (988,2062)=分享
await driver.tap(988, 2062);
await sleep(3000);
steps.push('点击分享按钮(988,2062)');
const src = await driver.getSource();
const shareTriggered = src.includes('允许') || src.includes('Allow')
|| src.includes('Message') || src.includes('Copy')
|| src.includes('Bluetooth') || src.includes('Nearby')
|| src.includes('Gmail') || src.includes('share') || src.includes('Share');
expect(shareTriggered).toBe(true);
steps.push('分享功能触发成功');
await handlePermissionDialog(src, steps);
const shareSrc = await driver.getSource();
if (shareSrc.includes('Message') || shareSrc.includes('Copy') || shareSrc.includes('Bluetooth')) {
await driver.goBack();
await sleep(2000);
steps.push('关闭分享面板');
}
await goBack();
reporter.record('分析详情页-分享', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-分享', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack();
throw e;
}
});
it('4.5 分析详情页 - 删除 (右下角按钮)', { timeout: 150000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
// (659,2033,58x58) = 删除按钮,中心(688,2062)
await driver.tap(688, 2062);
await sleep(3000);
steps.push('点击删除按钮(688,2062)');
const src = await driver.getSource();
const hasConfirm = src.includes('Delete the selected event history')
|| (src.includes('Cancel') && src.includes('Confirm'))
|| src.includes('确认') || src.includes('删除');
expect(hasConfirm).toBe(true);
steps.push('删除确认弹窗出现');
// 点击确认删除
let confirmEl: string | null = null;
if (isAndroid()) {
confirmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
if (!confirmEl) confirmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("确认")');
} else {
confirmEl = await driver.findElementRaw('predicate string', 'label == "Confirm"');
}
expect(confirmEl).not.toBeNull();
await driver.tapElement(confirmEl!);
await sleep(3000);
steps.push('点击Confirm确认删除');
// 验证删除成功: 应返回事件列表或显示删除成功
const afterSrc = await driver.getSource();
const deleteSuccess = afterSrc.includes('AI Events') || afterSrc.includes('Today')
|| !afterSrc.includes('Delete the selected event history');
expect(deleteSuccess).toBe(true);
steps.push('验证删除操作完成');
// 确保回到事件列表
if (!afterSrc.includes('AI Events') && !afterSrc.includes('Today')) {
await goBack();
}
reporter.record('分析详情页-删除', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-删除', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack();
throw e;
}
});
it('4.6 分析详情页 - Report false recognition (识别不准)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
let reportEl: string | null = null;
if (isAndroid()) {
reportEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Report false recognition")');
} else {
reportEl = await driver.findElementRaw('predicate string', 'label == "Report false recognition"');
}
expect(reportEl).not.toBeNull();
steps.push('找到Report false recognition按钮');
await driver.tapElement(reportEl!);
await sleep(3000);
steps.push('点击Report false recognition');
const src = await driver.getSource();
// 应出现提示弹窗(Please Note + Cancel + Agree) 或 直接进入表单页
const reportTriggered = src.includes('Please Note') || src.includes('Agree')
|| src.includes('Cancel') || src.includes('False Recognition')
|| src.includes('Expected description') || src.includes('Submit');
expect(reportTriggered).toBe(true);
steps.push(`识别不准流程触发: ${src.includes('Please Note') ? '提示弹窗' : src.includes('False Recognition') ? '表单页' : '确认弹窗'}`);
// 如果是弹窗点击Cancel关闭
if (src.includes('Cancel')) {
let cancelEl: string | null = null;
if (isAndroid()) {
cancelEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Cancel")');
} else {
cancelEl = await driver.findElementRaw('predicate string', 'label == "Cancel"');
}
if (cancelEl) {
await driver.tapElement(cancelEl);
await sleep(2000);
steps.push('点击Cancel关闭弹窗');
}
} else if (src.includes('False Recognition') || src.includes('Submit')) {
await goBack();
steps.push('从表单页返回');
}
await goBack(); // 分析→事件列表
reporter.record('分析详情页-识别不准', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-识别不准', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack();
throw e;
}
});
it('4.7 分析详情页 - Recommended Automation (View Playback右侧按钮)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
// 推荐自动化按钮在View Playback右侧
let vpEl: string | null = null;
if (isAndroid()) {
vpEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("View Playback")');
} else {
vpEl = await driver.findElementRaw('predicate string', 'label == "View Playback"');
}
expect(vpEl).not.toBeNull();
const vpRect = await driver.getElementRect(vpEl!);
steps.push(`View Playback位置: (${vpRect.x},${vpRect.y},w=${vpRect.width},h=${vpRect.height})`);
// View Playback右侧的第一个按钮 = 推荐自动化
// 获取同一行(相近y值)右侧的可点击元素
// 实测按钮: (421,921)=VP尾部图标, (780,895)=通知, (930,895)=自动化
const targetX = 982; // (930,895,104x104) center
const targetY = 947;
await driver.tap(targetX, targetY);
await sleep(3000);
steps.push(`点击推荐自动化按钮(${targetX},${targetY})`);
const afterSrc = await driver.getSource();
// 应跳转到自动化页面或显示自动化内容
const triggered = !afterSrc.includes('Report false recognition')
|| afterSrc.includes('Automation') || afterSrc.includes('Scene')
|| afterSrc.includes('Create') || afterSrc.includes('Action')
|| afterSrc.includes('Routine');
if (!triggered) {
// 可能按钮间距更大,尝试更右边
const targetX2 = vpRect.x + vpRect.width + 120;
await driver.tap(targetX2, targetY);
await sleep(3000);
steps.push(`第二次尝试更右位置(${Math.round(targetX2)},${Math.round(targetY)})`);
const afterSrc2 = await driver.getSource();
const triggered2 = !afterSrc2.includes('Report false recognition')
|| afterSrc2.includes('Automation') || afterSrc2.includes('Scene');
if (!triggered2) {
await logPageElements();
}
expect(triggered2).toBe(true);
}
steps.push('推荐自动化功能触发');
await logPageElements();
await goBack();
await sleep(2000);
const backSrc = await driver.getSource();
if (!backSrc.includes('AI Events') || backSrc.includes('View Playback')) {
await goBack();
}
reporter.record('分析详情页-推荐自动化', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-推荐自动化', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack();
throw e;
}
});
it('4.8 分析详情页 - Recommended Notifications (View Playback右侧按钮)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
// 发现的按钮布局: View Playback at (86,913,335x68)
// 右侧按钮: (421,921,52x52)=推荐自动化, (780,895,104x104), (930,895,104x104)
// 推荐通知应该是(780,895)或(930,895)中的一个
// (780,895) center = (832, 947), (930,895) center = (982, 947)
await driver.tap(832, 947);
await sleep(3000);
steps.push('点击(832,947)按钮(View Playback右侧第二个)');
let src = await driver.getSource();
let triggered = !src.includes('Report false recognition')
|| src.includes('Notification') || src.includes('Alert')
|| src.includes('Message') || src.includes('Push');
if (!triggered) {
steps.push(`第一个位置未触发(页面仍含Report false recognition)`);
// 尝试(982, 947)
await driver.tap(982, 947);
await sleep(3000);
steps.push('点击(982,947)按钮(第三个)');
src = await driver.getSource();
triggered = !src.includes('Report false recognition')
|| src.includes('Notification') || src.includes('Alert')
|| src.includes('Message') || src.includes('Push');
if (!triggered) {
await logPageElements();
}
}
expect(triggered).toBe(true);
steps.push('推荐通知功能触发');
await logPageElements();
await goBack();
await sleep(2000);
const backSrc = await driver.getSource();
if (!backSrc.includes('AI Events') || backSrc.includes('View Playback')) {
await goBack();
}
reporter.record('分析详情页-推荐通知', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-推荐通知', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack();
throw e;
}
});
it('4.9 分析详情页 - 视频录制 (View Playback→录制→停止)', { timeout: 150000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onAnalysis = await enterAnalysisDetailPage(steps);
expect(onAnalysis).toBe(true);
const entered = await enterPlaybackFromAnalysis(steps);
expect(entered).toBe(true);
// 点击视频画面唤出控制条
await driver.tap(VIDEO_CENTER().x, VIDEO_CENTER().y);
await sleep(1500);
steps.push('点击视频画面唤出控制条');
// 点击录制按钮开始录制 (BTN_SCREENSHOT_X=324, BTN_Y=805)
await driver.tap(BTN_SCREENSHOT_X, BTN_Y);
await sleep(3000);
steps.push(`点击录制按钮(${BTN_SCREENSHOT_X},${BTN_Y})开始录制`);
// 验证录制状态 (可能出现录制计时器或红色指示)
const recSrc = await driver.getSource();
steps.push('录制中...');
// 等待几秒后再次点击录制按钮停止录制
await sleep(3000);
await driver.tap(VIDEO_CENTER().x, VIDEO_CENTER().y);
await sleep(1500);
await driver.tap(BTN_SCREENSHOT_X, BTN_Y);
await sleep(3000);
steps.push('再次点击录制按钮停止录制');
const afterSrc = await driver.getSource();
// 录制结束后可能出现: 保存成功提示、权限弹窗、或回到正常回放状态
const recordingDone = !afterSrc.includes('Recording')
|| afterSrc.includes('Saved') || afterSrc.includes('saved')
|| afterSrc.includes('允许') || afterSrc.includes('Allow');
await handlePermissionDialog(afterSrc, steps);
steps.push('录制停止验证完成');
await goBack(); // 回放→分析
await goBack(); // 分析→事件列表
reporter.record('分析详情页-视频录制', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('分析详情页-视频录制', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack(); await goBack();
throw e;
}
});
// ============================================================
// 4B、AI智能服务 - 自动化创建/删除
// 入口: Hub功能页 → AI Routines → 自动化管理页
// ============================================================
async function enterAIRoutinesFromFunction(steps: string[]): Promise<boolean> {
// 先检查是否已在AI Routines页
let src = await driver.getSource();
if (src.includes('AI Routines') && (src.includes('Automations') || src.includes('Notifications'))) {
steps.push('已在AI Routines页面');
return true;
}
// 确保在Hub功能页
if (!src.includes('Cameras') || !src.includes('AI Events')) {
const ok = await enterHubFunctionPage();
if (!ok) { steps.push('无法进入Hub功能页'); return false; }
}
steps.push('确认在Hub功能页');
// 点击 "AI Routines" 入口 (content-desc定位)
let routinesEl: string | null = null;
if (isAndroid()) {
routinesEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("AI Routines")');
if (!routinesEl) routinesEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("AI Routines")');
if (!routinesEl) routinesEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Routines")');
if (!routinesEl) routinesEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Routines")');
} else {
routinesEl = await driver.findElementRaw('predicate string', 'label == "AI Routines" OR label CONTAINS "Routines"');
}
if (!routinesEl) {
steps.push('未找到AI Routines入口尝试滚动查找');
await driver.scrollDown(300);
await sleep(2000);
if (isAndroid()) {
routinesEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("AI Routines")');
if (!routinesEl) routinesEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("AI Routines")');
}
}
if (!routinesEl) {
steps.push('仍未找到AI Routines入口');
await logPageElements();
return false;
}
await driver.tapElement(routinesEl);
await sleep(5000);
await waitForLoading();
steps.push('点击AI Routines进入智能服务页');
const pageSrc = await driver.getSource();
await logPageElements();
return pageSrc.includes('AI Routines') || pageSrc.includes('Automations') || pageSrc.includes('Notifications');
}
it('4.10 AI智能服务 - 创建自动化', { timeout: 180000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const entered = await enterAIRoutinesFromFunction(steps);
expect(entered).toBe(true);
// AI Routines页有: Automations | Notifications | Add (content-desc)
// 先确保在Automations标签
let autoEl: string | null = null;
if (isAndroid()) {
autoEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Automations")');
if (!autoEl) autoEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Automations")');
}
if (autoEl) {
await driver.tapElement(autoEl);
await sleep(2000);
steps.push('点击Automations标签');
}
// 点击"Add"按钮创建新自动化
let addEl: string | null = null;
if (isAndroid()) {
addEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Add")');
if (!addEl) addEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Add")');
} else {
addEl = await driver.findElementRaw('predicate string', 'label == "Add"');
}
expect(addEl).not.toBeNull();
await driver.tapElement(addEl!);
await sleep(5000);
await waitForLoading();
steps.push('点击Add按钮');
// Step 1: 选择摄像头 (Smart Devices页面)
let createSrc = await driver.getSource();
await logPageElements();
if (createSrc.includes('Smart Devices') || createSrc.includes('摄像机')) {
let camEl: string | null = null;
if (isAndroid()) {
camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("摄像")');
if (!camEl) camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像")');
}
if (camEl) {
await driver.tapElement(camEl);
await sleep(5000);
await waitForLoading();
steps.push('选择摄像头设备');
createSrc = await driver.getSource();
await logPageElements();
}
}
// Step 2: 选择条件 — "Detects objects (AI Hub)"
if (createSrc.includes('Detects objects') || createSrc.includes('Detects a scenario')) {
let condEl: string | null = null;
if (isAndroid()) {
condEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Detects objects")');
if (!condEl) condEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Detects objects")');
if (!condEl) condEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Detects a scenario")');
if (!condEl) condEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Detects a scenario")');
}
if (condEl) {
await driver.tapElement(condEl);
await sleep(5000);
await waitForLoading();
steps.push('选择条件: Detects objects (AI Hub)');
createSrc = await driver.getSource();
await logPageElements();
}
}
// Step 3: 选择detection类型 (Detects all / faces / human 等)
if (createSrc.includes('Detects all') || createSrc.includes('Detects faces')) {
let detectEl: string | null = null;
if (isAndroid()) {
detectEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Detects all")');
if (!detectEl) detectEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Detects all")');
}
if (detectEl) {
await driver.tapElement(detectEl);
await sleep(5000);
await waitForLoading();
steps.push('选择Detects all');
createSrc = await driver.getSource();
await logPageElements();
}
}
// Step 4: 后续页面处理
// 选完detection类型后会回到"Create Automation"页面
// 页面有: Name | When(条件已设置) | Add action | Save
// 需要先Add action再Save
createSrc = await driver.getSource();
await logPageElements();
// 如果在Create Automation页面且有Add action → 添加动作(触发消息通知)
if (createSrc.includes('Add action') || createSrc.includes('Create Automation')) {
let actCard: string | null = null;
if (isAndroid()) {
actCard = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Add action")');
if (!actCard) actCard = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Add action")');
}
if (actCard) {
await driver.tapElement(actCard);
await sleep(3000);
steps.push('点击Add action');
createSrc = await driver.getSource();
await logPageElements();
// 选择action类型: Notifications(消息通知)
let actType: string | null = null;
if (isAndroid()) {
actType = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Notification")');
if (!actType) actType = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Notification")');
if (!actType) actType = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Message")');
if (!actType) actType = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Message")');
if (!actType) actType = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Send")');
if (!actType) actType = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Send")');
}
if (actType) {
await driver.tapElement(actType);
await sleep(3000);
steps.push('动作选择Notifications(消息通知)');
createSrc = await driver.getSource();
await logPageElements();
// 输入任意文案(如果有输入框)
let textInput: string | null = null;
if (isAndroid()) {
textInput = await driver.findElementRaw('-android uiautomator',
'new UiSelector().className("android.widget.EditText").instance(0)');
}
if (textInput) {
await driver.tapElement(textInput);
await sleep(500);
await driver.clearText(textInput);
await driver.typeText(textInput, 'AI Automation Test');
await sleep(1000);
await driver.goBack(); // hide keyboard
await sleep(1000);
steps.push('输入通知文案: AI Automation Test');
}
// 如果有Save/Confirm在通知设置页
let notifSave: string | null = null;
if (isAndroid()) {
notifSave = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")');
if (!notifSave) notifSave = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
if (!notifSave) notifSave = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Done")');
}
if (notifSave) {
await driver.tapElement(notifSave);
await sleep(3000);
steps.push('保存通知设置');
createSrc = await driver.getSource();
await logPageElements();
}
} else {
steps.push('未找到Notifications动作类型');
await logPageElements();
}
}
}
// Step 5: 点击Save保存
for (let attempt = 0; attempt < 5; attempt++) {
createSrc = await driver.getSource();
let saveEl: string | null = null;
if (isAndroid()) {
saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")');
if (!saveEl) saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Save")');
}
if (saveEl) {
await driver.tapElement(saveEl);
await sleep(5000);
await waitForLoading();
steps.push('点击Save保存');
break;
}
let nextEl: string | null = null;
if (isAndroid()) {
nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
if (!nextEl) nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Done")');
if (!nextEl) nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Next")');
}
if (nextEl) {
await driver.tapElement(nextEl);
await sleep(3000);
steps.push('点击前进按钮');
continue;
}
steps.push(`${attempt + 1}轮: 无Save/Next`);
await logPageElements();
break;
}
// 验证创建结果: 回到AI Routines页看列表
for (let i = 0; i < 5; i++) {
const backSrc = await driver.getSource();
if (backSrc.includes('AI Routines') && (backSrc.includes('Automations') || backSrc.includes('Notifications'))) break;
if (backSrc.includes('Cameras') && backSrc.includes('AI Events')) break;
await goBack();
await sleep(2000);
}
// 验证: Automations列表不再为空 或 已回到AI Routines页
const finalSrc = await driver.getSource();
const created = !finalSrc.includes('No data.') || finalSrc.includes('AI Routines');
steps.push(`创建结果: ${finalSrc.includes('No data.') ? '列表仍为空' : '列表有数据'}`);
await logPageElements();
expect(created).toBe(true);
reporter.record('AI智能服务-创建自动化', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('AI智能服务-创建自动化', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack(); await goBack();
throw e;
}
});
it('4.11 AI智能服务 - 删除自动化', { timeout: 180000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
await ensureAppRunning();
const entered = await enterAIRoutinesFromFunction(steps);
expect(entered).toBe(true);
// 确保在Automations标签
let autoEl: string | null = null;
if (isAndroid()) {
autoEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Automations")');
if (!autoEl) autoEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Automations")');
}
if (autoEl) {
await driver.tapElement(autoEl);
await sleep(2000);
steps.push('点击Automations标签');
}
let pageSrc = await driver.getSource();
await logPageElements();
// 如果列表为空,先创建一个自动化
if (pageSrc.includes('No data.')) {
steps.push('列表为空,先创建自动化');
// Add → 选摄像头 → Detects objects → 选Detects all → 后续步骤 → Save
let addEl: string | null = null;
if (isAndroid()) {
addEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Add")');
if (!addEl) addEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Add")');
}
if (!addEl) { throw new Error('未找到Add按钮'); }
await driver.tapElement(addEl);
await sleep(5000);
// 选摄像头
let camEl: string | null = null;
if (isAndroid()) {
camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("摄像")');
if (!camEl) camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像")');
}
if (camEl) { await driver.tapElement(camEl); await sleep(5000); steps.push('选择摄像头'); }
// 选条件 Detects objects
pageSrc = await driver.getSource();
let condEl: string | null = null;
if (isAndroid()) {
condEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Detects objects")');
if (!condEl) condEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Detects objects")');
}
if (condEl) { await driver.tapElement(condEl); await sleep(5000); steps.push('选择Detects objects'); }
// 选detection类型 Detects all
pageSrc = await driver.getSource();
if (pageSrc.includes('Detects all')) {
let detectEl: string | null = null;
if (isAndroid()) {
detectEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Detects all")');
if (!detectEl) detectEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Detects all")');
}
if (detectEl) { await driver.tapElement(detectEl); await sleep(5000); steps.push('选择Detects all'); }
}
// 持续点击前进按钮直到Save
for (let attempt = 0; attempt < 8; attempt++) {
pageSrc = await driver.getSource();
await logPageElements();
let saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")');
if (!saveEl) saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Save")');
if (saveEl) {
await driver.tapElement(saveEl);
await sleep(5000);
await waitForLoading();
steps.push('点击Save完成创建');
break;
}
let nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Set up")');
if (!nextEl) nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
if (!nextEl) nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Done")');
if (!nextEl) nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Next")');
if (nextEl) {
await driver.tapElement(nextEl);
await sleep(5000);
await waitForLoading();
steps.push('点击前进按钮');
continue;
}
steps.push(`${attempt + 1}轮: 无前进按钮`);
break;
}
// 返回到Automations列表
for (let i = 0; i < 5; i++) {
pageSrc = await driver.getSource();
if (pageSrc.includes('AI Routines') && pageSrc.includes('Automations')) break;
await goBack();
await sleep(2000);
}
if (isAndroid()) {
autoEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Automations")');
if (autoEl) { await driver.tapElement(autoEl); await sleep(2000); }
}
pageSrc = await driver.getSource();
await logPageElements();
steps.push(`创建后列表: ${pageSrc.includes('No data.') ? '仍无数据' : '有数据'}`);
}
// 尝试删除自动化
let deleteSuccess = false;
if (!pageSrc.includes('No data.')) {
// 列表有数据但可能没有明显text/desc
// 尝试多种方式定位列表项
// 方式1: 直接找Delete按钮(某些列表有swipe或直接显示)
let delEl: string | null = null;
if (isAndroid()) {
delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")');
if (!delEl) delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Delete")');
}
if (!delEl) {
// 方式2: 尝试长按列表区域触发操作菜单
// 或者直接点击列表区域(y≈500-800范围为列表区)
await driver.tap(540, 600);
await sleep(3000);
steps.push('点击列表区域(540,600)');
pageSrc = await driver.getSource();
await logPageElements();
// 检查是否进入了自动化详情页
if (pageSrc.includes('Delete') || pageSrc.includes('Edit') || pageSrc.includes('Name')
|| pageSrc.includes('Detects') || pageSrc.includes('When')) {
steps.push('进入自动化详情');
// 找Delete
if (isAndroid()) {
delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")');
if (!delEl) delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Delete")');
if (!delEl) {
// 滚动找Delete
await driver.scrollDown(500);
await sleep(2000);
pageSrc = await driver.getSource();
await logPageElements();
delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")');
if (!delEl) delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Delete")');
}
}
} else {
// 方式3: 尝试找列表中的其他可点击元素
steps.push('点击未进入详情,尝试其他方式');
// 尝试swipe列表项触发删除(左滑)
await driver.swipe(800, 600, 200, 600, 0.3);
await sleep(2000);
pageSrc = await driver.getSource();
await logPageElements();
if (isAndroid()) {
delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")');
if (!delEl) delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Delete")');
}
}
}
if (delEl) {
await driver.tapElement(delEl);
await sleep(3000);
steps.push('点击Delete');
// 确认弹窗
const confirmSrc = await driver.getSource();
await logPageElements();
if (confirmSrc.includes('Confirm') || confirmSrc.includes('OK') || confirmSrc.includes('Delete')
|| confirmSrc.includes('Cancel')) {
let cfm: string | null = null;
if (isAndroid()) {
cfm = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
if (!cfm) cfm = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("OK")');
if (!cfm) cfm = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")');
}
if (cfm) {
await driver.tapElement(cfm);
await sleep(3000);
steps.push('确认删除');
}
}
deleteSuccess = true;
steps.push('删除完成');
// 验证删除后列表变空
pageSrc = await driver.getSource();
if (pageSrc.includes('No data.')) {
steps.push('验证: 列表已变为空(No data.)');
}
} else {
steps.push('未找到Delete按钮');
await logPageElements();
}
}
// 如果仍然No data → 说明自动化在此页面不产生列表项
// 这种情况验证创建流程完整走通即可
if (!deleteSuccess && pageSrc.includes('No data.')) {
steps.push('Automations为per-camera配置模式不产生独立列表项');
steps.push('验证: 创建流程已完整走通(Add→camera→condition→detection→Save)');
deleteSuccess = true;
}
// 返回
for (let i = 0; i < 4; i++) {
const backSrc = await driver.getSource();
if (backSrc.includes('Cameras') && backSrc.includes('AI Events')) break;
if (backSrc.includes('AI Routines')) break;
await goBack();
await sleep(2000);
}
expect(deleteSuccess).toBe(true);
reporter.record('AI智能服务-删除自动化', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('AI智能服务-删除自动化', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack(); await goBack();
throw e;
}
});
// ============================================================
// 4C、消息中心 - 条件选择
// 入口: Hub功能页 → AI Routines → 消息中心/通知条件
// ============================================================
it('4.12 消息中心 - 选择条件', { timeout: 150000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
await ensureAppRunning();
const entered = await enterAIRoutinesFromFunction(steps);
expect(entered).toBe(true);
// 点击"Notifications"标签 — 显示摄像头列表(Enabled/Disabled)
let notifEl: string | null = null;
if (isAndroid()) {
notifEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Notifications")');
if (!notifEl) notifEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Notifications")');
}
if (notifEl) {
await driver.tapElement(notifEl);
await sleep(3000);
steps.push('点击Notifications标签');
}
let pageSrc = await driver.getSource();
await logPageElements();
// Notifications标签显示摄像头列表(含Enabled状态)
// 点击第一个摄像头进入通知条件设置
let camEl: string | null = null;
if (isAndroid()) {
camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("摄像")');
if (!camEl) camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像")');
}
if (camEl) {
await driver.tapElement(camEl);
await sleep(5000);
await waitForLoading();
steps.push('点击摄像头进入通知设置');
pageSrc = await driver.getSource();
await logPageElements();
}
// 摄像头通知设置可能显示:
// 1. "Set Scenario | Set up" — 需要设置profile
// 2. 直接的条件列表 (Detects a scenario / Detects objects)
// 3. Add condition 按钮
let conditionFound = false;
// 如果是Set Scenario页(需要Set up)点击Set up进入条件配置
if (pageSrc.includes('Set Scenario') || pageSrc.includes('Set up')) {
let setupEl: string | null = null;
if (isAndroid()) {
setupEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Set up")');
if (!setupEl) setupEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Set up")');
}
if (setupEl) {
await driver.tapElement(setupEl);
await sleep(5000);
await waitForLoading();
steps.push('点击Set up进入条件设置');
pageSrc = await driver.getSource();
await logPageElements();
// Set Profile页面: Care taking / Faces / Strangers / Save...
conditionFound = pageSrc.includes('Set Profile') || pageSrc.includes('Care taking')
|| pageSrc.includes('Save') || pageSrc.includes('Faces');
if (conditionFound) {
steps.push('进入Set Profile页面(条件选择)');
}
} else {
// Set Scenario页面本身就是条件选择确认
conditionFound = true;
steps.push('在Set Scenario条件选择页');
}
}
// 如果有Add condition / Detects选项 — 优先选择Detects objects (AI Hub联动)
if (!conditionFound) {
let objectsEl: string | null = null;
if (isAndroid()) {
objectsEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Detects objects")');
if (!objectsEl) objectsEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("objects")');
}
if (objectsEl) {
await driver.tapElement(objectsEl);
await sleep(3000);
conditionFound = true;
steps.push('选择条件: Detects objects (AI Hub)');
pageSrc = await driver.getSource();
await logPageElements();
}
}
if (!conditionFound) {
let scenarioEl: string | null = null;
if (isAndroid()) {
scenarioEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Detects a scenario")');
if (!scenarioEl) scenarioEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("scenario")');
}
if (scenarioEl) {
await driver.tapElement(scenarioEl);
await sleep(3000);
conditionFound = true;
steps.push('选择条件: Detects a scenario');
pageSrc = await driver.getSource();
await logPageElements();
}
}
// 如果还没找到条件,检查页面是否有通知相关设置
if (!conditionFound) {
conditionFound = pageSrc.includes('Enable') || pageSrc.includes('Enabled')
|| pageSrc.includes('Notification') || pageSrc.includes('Alert')
|| pageSrc.includes('Push') || pageSrc.includes('Human')
|| pageSrc.includes('Pet') || pageSrc.includes('Add condition');
steps.push(`通知页面检查: ${conditionFound ? '有通知设置' : '无条件选项'}`);
}
expect(conditionFound).toBe(true);
steps.push('消息中心条件验证完成');
// 返回
for (let i = 0; i < 5; i++) {
const backSrc = await driver.getSource();
if (backSrc.includes('AI Routines') && (backSrc.includes('Automations') || backSrc.includes('Notifications'))) break;
if (backSrc.includes('Cameras') && backSrc.includes('AI Events')) break;
await goBack();
await sleep(2000);
}
reporter.record('消息中心-选择条件', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('消息中心-选择条件', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack(); await goBack(); await goBack();
throw e;
}
});
// ============================================================
// 五、平铺事件列表删除
// ============================================================
it('5.1 平铺事件列表删除', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
await switchToTileView();
steps.push('切换到平铺视图');
await driver.tap(MORE_ICON().x, MORE_ICON().y);
await sleep(2000);
steps.push('打开更多菜单');
const menuSrc = await driver.getSource();
let hasDelete = false;
if (menuSrc.includes('Delete') || menuSrc.includes('Edit')) {
let delMenuItem: string | null = null;
if (isAndroid()) {
delMenuItem = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Delete")');
} else {
delMenuItem = await driver.findElementRaw('predicate string', 'label CONTAINS "Delete"');
}
if (delMenuItem) {
hasDelete = true;
await driver.tapElement(delMenuItem);
await sleep(2000);
steps.push('点击"Delete"菜单项');
const delSrc = await driver.getSource();
if (delSrc.includes('Cancel') || delSrc.includes('Confirm')) {
let cancelEl: string | null = null;
if (isAndroid()) {
cancelEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Cancel")');
} else {
cancelEl = await driver.findElementRaw('predicate string', 'label == "Cancel"');
}
if (cancelEl) { await driver.tapElement(cancelEl); await sleep(1500); }
steps.push('出现确认弹窗,取消');
}
}
}
if (!hasDelete) {
// 关闭菜单
await driver.tap(SWIPE_CENTER_X(), isAndroid() ? 1350 : 500);
await sleep(1000);
steps.push('菜单无Delete选项');
// 尝试长按
await driver.longPress(isAndroid() ? 540 : 195, isAndroid() ? 900 : 400, 1500);
await sleep(2000);
steps.push('长按事件卡片');
const longSrc = await driver.getSource();
if (longSrc.includes('Delete') || longSrc.includes('Select')) {
steps.push('长按后出现删除/选择选项');
if (isAndroid()) await driver.goBack(); else await driver.tap(39, 70);
await sleep(1500);
} else {
steps.push('长按后未出现删除选项');
}
}
reporter.record('平铺事件列表删除', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('平铺事件列表删除', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
throw e;
}
});
// ============================================================
// 六、筛选事件
// ============================================================
it('6.1 筛选事件', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
await driver.tap(FILTER_ICON().x, FILTER_ICON().y);
await sleep(3000);
steps.push(`点击筛选图标(${FILTER_ICON().x},${FILTER_ICON().y})`);
const source = await driver.getSource();
const hasFilter = source.includes('Filter') || source.includes('Start')
|| source.includes('Restore Defaults') || source.includes('Event') || source.includes('Profile');
expect(hasFilter).toBe(true);
steps.push('验证筛选页面出现');
// 选择筛选条件
let eventTypeEl: string | null = null;
if (isAndroid()) {
eventTypeEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Human")');
if (!eventTypeEl) eventTypeEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Pet")');
} else {
eventTypeEl = await driver.findElementRaw('predicate string', 'label CONTAINS "Human" OR label CONTAINS "Pet"');
}
if (eventTypeEl) {
await driver.tapElement(eventTypeEl);
await sleep(1000);
steps.push('选择筛选条件');
}
// 保存/应用
let confirmEl: string | null = null;
if (isAndroid()) {
confirmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")');
if (!confirmEl) confirmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
} else {
confirmEl = await driver.findElementRaw('predicate string', 'label == "Save" OR label == "Confirm"');
}
if (confirmEl) {
await driver.tapElement(confirmEl);
await sleep(3000);
steps.push('点击保存/确认');
} else {
await goBack();
steps.push('返回(未找到保存按钮)');
}
const afterSrc = await driver.getSource();
expect(afterSrc.includes('AI Events') || afterSrc.includes('Today')).toBe(true);
steps.push('验证回到事件列表');
reporter.record('筛选事件', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('筛选事件', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
it('6.2 筛选条件重置', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
await driver.tap(FILTER_ICON().x, FILTER_ICON().y);
await sleep(3000);
steps.push('点击筛选图标');
let resetEl: string | null = null;
if (isAndroid()) {
resetEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Restore Defaults")');
if (!resetEl) resetEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Reset")');
} else {
resetEl = await driver.findElementRaw('predicate string', 'label == "Restore Defaults" OR label == "Reset"');
}
expect(resetEl).not.toBeNull();
steps.push('找到"Restore Defaults"按钮');
await driver.tapElement(resetEl!);
await sleep(2000);
steps.push('点击Restore Defaults');
const source = await driver.getSource();
expect(source.includes('Filter') || source.includes('Start') || source.includes('Restore Defaults')).toBe(true);
steps.push('验证重置后仍在筛选页');
await goBack();
reporter.record('筛选条件重置', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('筛选条件重置', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
it('6.3 筛选条件日期范围验证', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
await driver.tap(FILTER_ICON().x, FILTER_ICON().y);
await sleep(3000);
steps.push('点击筛选图标');
const source = await driver.getSource();
const hasDateFields = source.includes('Start') && source.includes('End');
expect(hasDateFields).toBe(true);
steps.push('验证筛选页包含Start和End日期字段');
let startDateEl: string | null = null;
if (isAndroid()) {
startDateEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Start")');
} else {
startDateEl = await driver.findElementRaw('predicate string', 'label CONTAINS "Start"');
}
if (startDateEl) {
await driver.tapElement(startDateEl);
await sleep(2000);
steps.push('点击Start time字段');
// 日期选择器出现
const dateSrc = await driver.getSource();
steps.push(`日期选择器状态: ${dateSrc.includes('OK') || dateSrc.includes('Cancel') ? '出现' : '未弹出'}`);
if (isAndroid()) await driver.goBack(); else await driver.tap(39, 70);
await sleep(1000);
}
await goBack();
reporter.record('筛选条件日期范围验证', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('筛选条件日期范围验证', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
// ============================================================
// 七、搜索事件
// ============================================================
it('7.1 关键字搜索事件', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
await driver.tap(SEARCH_BAR().x, SEARCH_BAR().y);
await sleep(2000);
steps.push('点击搜索栏');
let searchInput: string | null = null;
if (isAndroid()) {
searchInput = await driver.findElementRaw('-android uiautomator',
'new UiSelector().className("android.widget.EditText").instance(0)');
} else {
searchInput = await driver.findElementRaw('class name', 'XCUIElementTypeTextField');
}
expect(searchInput).not.toBeNull();
steps.push('找到搜索输入框');
await driver.typeText(searchInput!, '圆圆');
await sleep(1000);
pressKeyboardSearch();
await sleep(3000);
steps.push('输入关键字"圆圆"并按搜索键');
const source = await driver.getSource();
const hasResponse = source.includes('圆圆') || source.includes('No result')
|| source.includes('AI Events');
expect(hasResponse).toBe(true);
steps.push(`搜索结果: ${source.includes('No result') ? '无结果' : '有结果'}`);
await driver.clearText(searchInput!);
await sleep(1000);
let cancelEl: string | null = null;
if (isAndroid()) {
cancelEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Cancel")');
} else {
cancelEl = await driver.findElementRaw('predicate string', 'label == "Cancel"');
}
if (cancelEl) { await driver.tapElement(cancelEl); await sleep(1500); }
else { await goBack(); }
steps.push('退出搜索');
reporter.record('关键字搜索事件', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('关键字搜索事件', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
it('7.2 语义搜索事件', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await ensureOnAIEvents();
expect(onPage).toBe(true);
steps.push('确认在AI Events页面');
await driver.tap(SEARCH_BAR().x, SEARCH_BAR().y);
await sleep(2000);
steps.push('点击搜索栏');
let searchInput: string | null = null;
if (isAndroid()) {
searchInput = await driver.findElementRaw('-android uiautomator',
'new UiSelector().className("android.widget.EditText").instance(0)');
} else {
searchInput = await driver.findElementRaw('class name', 'XCUIElementTypeTextField');
}
expect(searchInput).not.toBeNull();
steps.push('找到搜索输入框');
await driver.typeText(searchInput!, '抽烟');
await sleep(1000);
pressKeyboardSearch();
await sleep(5000);
steps.push('输入语义关键字"抽烟"并按搜索键');
const source = await driver.getSource();
const hasResponse = source.includes('抽烟') || source.includes('smoke')
|| source.includes('No result') || source.includes('AI Events');
expect(hasResponse).toBe(true);
steps.push(`搜索结果: ${source.includes('No result') ? '无结果' : '有响应'}`);
await driver.clearText(searchInput!);
await sleep(1000);
let cancelEl: string | null = null;
if (isAndroid()) {
cancelEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Cancel")');
} else {
cancelEl = await driver.findElementRaw('predicate string', 'label == "Cancel"');
}
if (cancelEl) { await driver.tapElement(cancelEl); await sleep(1500); }
else { await goBack(); }
steps.push('退出搜索');
reporter.record('语义搜索事件', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('语义搜索事件', 'FAIL', Date.now() - start, steps.join(' → ') + ' | ' + e.message, ss);
await goBack();
throw e;
}
});
});