AI_UIAutomation/tests/camera/camera_control.test.ts

1069 lines
36 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 { getDeviceName } from '../../config/device.config';
import { sleep } from '../../utils/common';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
const deviceName = getDeviceName('camera', 'CAMERA_DEVICE');
describe('Camera Control - 摄像头功能页操作', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
let screenWidth = 390;
let screenHeight = 844;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('Camera_Control', driver.platform.toUpperCase());
const size = await driver.getWindowSize();
screenWidth = size.width;
screenHeight = size.height;
});
beforeEach(async () => {
await driver.dismissPopupIfPresent();
await driver.goBackToHomepage();
await driver.dismissPopupIfPresent();
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
async function findCameraCard(): Promise<string | null> {
if (driver.platform === 'ios') {
const predicates = [
`name CONTAINS "${deviceName}" AND type == "XCUIElementTypeCell"`,
'name CONTAINS "Camera" AND type == "XCUIElementTypeCell"',
'name CONTAINS "Pan Tilt" AND type == "XCUIElementTypeCell"',
];
for (const pred of predicates) {
const elems = await driver.findElementsRaw('predicate string', pred);
if (elems.length > 0) return elems[0];
}
await driver.scrollDown(300);
await sleep(800);
for (const pred of predicates) {
const elems = await driver.findElementsRaw('predicate string', pred);
if (elems.length > 0) return elems[0];
}
} else {
const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${deviceName}")`);
if (el) return el;
const el2 = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
if (el2) return el2;
}
return null;
}
async function enterControlPage(waitForVideo = true): Promise<void> {
// Check if already on camera page
const currentSource = await driver.getSource();
const isOnCamera = (currentSource.includes('Direction') && currentSource.includes('Events') && currentSource.includes('Playback'))
|| (currentSource.includes('KB/S') && currentSource.includes('Direction'))
|| (currentSource.includes('Device online') && currentSource.includes('Direction'));
if (isOnCamera) return;
const cardId = await findCameraCard();
if (cardId) {
await driver.tapElement(cardId);
} else {
// Camera card has no accessible name — tap below "Cameras" header button
for (let attempt = 0; attempt < 2; attempt++) {
if (attempt === 0) {
await driver.swipe(screenWidth / 2, 200, screenWidth / 2, 600, 0.3);
await sleep(500);
} else {
await driver.swipe(screenWidth / 2, 500, screenWidth / 2, 200, 0.5);
await sleep(800);
}
const camerasBtn = await driver.findElementRaw('predicate string',
'name == "Cameras" AND type == "XCUIElementTypeButton"');
if (camerasBtn) {
const rect = await driver.getElementRect(camerasBtn);
await driver.tap(screenWidth / 2, rect.y + rect.height + 100);
await sleep(5000);
const s = await driver.getSource();
if (s.includes('Direction') || s.includes('KB/S')) return;
await driver.tap(screenWidth / 2, rect.y + rect.height + 150);
await sleep(5000);
const s2 = await driver.getSource();
if (s2.includes('Direction') || s2.includes('KB/S')) return;
}
}
throw new Error('找不到摄像头卡片');
}
if (!waitForVideo) {
// Just wait for page to appear (for Features/Direction tabs that don't need video)
await sleep(5000);
return;
}
// Wait for video stream to fully load (KB/S indicator appears when stream is active)
const start = Date.now();
while (Date.now() - start < 20000) {
const source = await driver.getSource();
if (source.includes('Disconnected') || source.includes('Connection failed')) {
throw new Error('摄像头未连接');
}
if (source.includes('KB/S')) {
await sleep(2000);
return;
}
await sleep(1500);
}
// Even if KB/S not found, continue if Device online
const source = await driver.getSource();
if (source.includes('Device online') || source.includes('设备在线')) {
await sleep(2000);
return;
}
throw new Error('视频流加载超时');
}
async function tapVideoArea(): Promise<void> {
await driver.tap(screenWidth / 2, 200);
await sleep(1000);
}
async function tapTab(tabName: string): Promise<void> {
const el = await driver.findElementRaw('name', tabName);
if (el) {
await driver.tapElement(el);
await sleep(2000);
} else {
throw new Error(`Tab "${tabName}" not found`);
}
}
// Video overlay control buttons (icon-only, positioned by coordinates)
// Top row (y≈137): HD(257,137), Speaker(314,137), Grid(354,137)
// Bottom row (y≈338): Pause(16,338), Screenshot(99,338), Talk(183,338), Record(266,338), Fullscreen(350,338)
async function tapHDButton(): Promise<void> {
await driver.tap(270, 137);
}
async function tapSpeakerButton(): Promise<void> {
await driver.tap(324, 147);
}
async function tapPauseButton(): Promise<void> {
await driver.tap(28, 350);
}
async function tapScreenshotButton(): Promise<void> {
await driver.tap(111, 350);
}
async function tapTalkButton(): Promise<void> {
await driver.tap(195, 350);
}
async function tapRecordButton(): Promise<void> {
await driver.tap(278, 350);
}
async function tapFullscreenButton(): Promise<void> {
await driver.tap(362, 350);
}
async function tapGridButton(): Promise<void> {
await driver.tap(354, 137);
}
// ==================== 四宫格 ====================
it('功能页打开四宫格', async () => {
const start = Date.now();
try {
await enterControlPage();
await tapVideoArea();
// Tap grid button (top-right, next to speaker)
await tapGridButton();
await sleep(3000);
// In quad view, look for Add button to add another camera
const addEl = await driver.findElementRaw('name', 'Add')
|| await driver.findElementRaw('name', '添加')
|| await driver.findElementRaw('name', '+');
if (addEl) {
await driver.tapElement(addEl);
await sleep(3000);
// Should show device list to select camera — pick one if available
const source = await driver.getSource();
const cells = await driver.findElementsRaw('predicate string',
'type == "XCUIElementTypeCell" AND visible == true');
if (cells.length > 0) {
// Tap first available camera device
await driver.tapElement(cells[0]);
await sleep(3000);
}
// Go back if on device selection page
const sourceAfter = await driver.getSource();
if (!sourceAfter.includes('KB/S') && !sourceAfter.includes('Direction')) {
await driver.tap(33, 69);
await sleep(2000);
}
}
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 {
await enterControlPage();
await tapVideoArea();
// Open grid view
await tapGridButton();
await sleep(3000);
// Tap grid button again or tap a specific camera to return to single view
await tapVideoArea();
await tapGridButton();
await sleep(3000);
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 {
await enterControlPage();
await tapVideoArea();
const hdEl = await driver.findElementRaw('name', 'HD');
const sdEl = await driver.findElementRaw('name', 'SD');
if (hdEl) {
await driver.tapElement(hdEl);
await sleep(8000);
await tapVideoArea();
const sdAfter = await driver.findElementRaw('name', 'SD');
const detail = sdAfter ? 'HD → SD' : 'HD → (toggled)';
reporter.record('切换画质', 'PASS', Date.now() - start, detail);
} else if (sdEl) {
await driver.tapElement(sdEl);
await sleep(8000);
await tapVideoArea();
const hdAfter = await driver.findElementRaw('name', 'HD');
const detail = hdAfter ? 'SD → HD' : 'SD → (toggled)';
reporter.record('切换画质', 'PASS', Date.now() - start, detail);
} else {
// Fallback: tap by coordinate
await tapHDButton();
await sleep(8000);
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 {
await enterControlPage();
await tapVideoArea();
// Tap speaker button (top row, icon only)
await tapSpeakerButton();
await sleep(3000);
// Tap again to toggle back
await tapVideoArea();
await tapSpeakerButton();
await sleep(2000);
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 {
await enterControlPage();
await tapVideoArea();
await tapScreenshotButton();
await sleep(3000);
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 {
await enterControlPage();
await tapVideoArea();
// Start recording
await tapRecordButton();
await sleep(5000);
// Stop recording
await tapVideoArea();
await tapRecordButton();
await sleep(2000);
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 {
await enterControlPage();
await tapVideoArea();
// Pause
await tapPauseButton();
await sleep(3000);
// Resume
await tapVideoArea();
await tapPauseButton();
await sleep(3000);
const source = await driver.getSource();
const isLive = !source.includes('Disconnected');
expect(isLive).toBe(true);
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 {
await enterControlPage();
await tapVideoArea();
// Start talk
await tapTalkButton();
await sleep(5000);
// Stop talk
await tapVideoArea();
await tapTalkButton();
await sleep(2000);
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 {
await enterControlPage(false);
await tapTab('Features');
const nvEl = await driver.findElementRaw('name', 'Night Vision');
expect(nvEl).not.toBeNull();
await driver.tapElement(nvEl!);
await sleep(2000);
// Select "On" / "Always on"
const onEl = await driver.findElementRaw('name', 'On') || await driver.findElementRaw('name', 'Always on');
if (onEl) {
await driver.tapElement(onEl);
await sleep(3000);
}
reporter.record('夜视-打开', 'PASS', Date.now() - start, 'Night Vision=On');
} 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 {
await enterControlPage(false);
await tapTab('Features');
const nvEl = await driver.findElementRaw('name', 'Night Vision');
expect(nvEl).not.toBeNull();
await driver.tapElement(nvEl!);
await sleep(2000);
const offEl = await driver.findElementRaw('name', 'Off');
if (offEl) {
await driver.tapElement(offEl);
await sleep(3000);
}
reporter.record('夜视-关闭', 'PASS', Date.now() - start, 'Night Vision=Off');
} 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 {
await enterControlPage(false);
await tapTab('Features');
const nvEl = await driver.findElementRaw('name', 'Night Vision');
expect(nvEl).not.toBeNull();
await driver.tapElement(nvEl!);
await sleep(2000);
const autoEl = await driver.findElementRaw('name', 'Auto');
if (autoEl) {
await driver.tapElement(autoEl);
await sleep(3000);
}
reporter.record('夜视-自动', 'PASS', Date.now() - start, 'Night Vision=Auto');
} 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 {
await enterControlPage(false);
await tapTab('Features');
const motionEl = await driver.findElementRaw('name', 'Motion Detection')
|| await driver.findElementRaw('name', 'Motion');
expect(motionEl).not.toBeNull();
await driver.tapElement(motionEl!);
await sleep(3000);
reporter.record('移动监测开关', 'PASS', Date.now() - start, 'Motion Detection切换完成');
} 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 {
await enterControlPage(false);
await tapTab('Features');
const trackEl = await driver.findElementRaw('name', 'Motion Tracking');
if (!trackEl) {
reporter.record('移动追踪', 'SKIP', Date.now() - start, '当前设备不支持Motion Tracking');
return;
}
await driver.tapElement(trackEl);
await sleep(3000);
reporter.record('移动追踪', 'PASS', Date.now() - start, 'Motion Tracking切换完成');
} 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 {
await enterControlPage(false);
await tapTab('Features');
const alarmEl = await driver.findElementRaw('name', 'Sound Alert')
|| await driver.findElementRaw('name', 'Sound Alarm');
if (!alarmEl) {
reporter.record('声音报警', 'SKIP', Date.now() - start, '当前设备不支持Sound Alert');
return;
}
await driver.tapElement(alarmEl);
await sleep(3000);
// Toggle back
await driver.tapElement(alarmEl);
await sleep(2000);
reporter.record('声音报警', 'PASS', Date.now() - start, 'Sound Alert开/关完成');
} 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 {
await enterControlPage(false);
await tapTab('Direction');
// PTZ controls - swipe in the direction pad area (center of screen below video)
const centerX = screenWidth / 2;
const centerY = 500;
// Swipe up
await driver.swipe(centerX, centerY, centerX, centerY - 80, 0.5);
await sleep(2000);
// Swipe left
await driver.swipe(centerX, centerY, centerX - 80, centerY, 0.5);
await sleep(2000);
// Swipe down
await driver.swipe(centerX, centerY, centerX, centerY + 80, 0.5);
await sleep(2000);
// Swipe right
await driver.swipe(centerX, centerY, centerX + 80, centerY, 0.5);
await sleep(2000);
reporter.record('方向操作', 'PASS', Date.now() - start, 'PTZ方向控制完成');
} 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 {
await enterControlPage(false);
await tapTab('Features');
const privacyEl = await driver.findElementRaw('name', 'Privacy Mode')
|| await driver.findElementRaw('name', 'Privacy');
expect(privacyEl).not.toBeNull();
await driver.tapElement(privacyEl!);
await sleep(5000);
// Verify privacy mode state changed
let source = await driver.getSource();
const privacyOn = source.includes('Privacy') || source.includes('Turn on') || source.includes('turned off');
console.log('隐私模式:', privacyOn);
// If privacy mode turned on, restore by tapping again or "Turn on" button
if (source.includes('Turn on')) {
const turnOnEl = await driver.findElementRaw('name', 'Turn on');
if (turnOnEl) {
await driver.tapElement(turnOnEl);
await sleep(5000);
}
} else {
// Toggle back
const privacyEl2 = await driver.findElementRaw('name', 'Privacy Mode')
|| await driver.findElementRaw('name', 'Privacy');
if (privacyEl2) {
await driver.tapElement(privacyEl2);
await sleep(5000);
}
}
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 {
await enterControlPage();
await tapVideoArea();
await tapFullscreenButton();
await sleep(3000);
// In fullscreen/landscape mode - tap center to show controls, then tap exit button
const size = await driver.getWindowSize();
await driver.tap(size.width / 2, size.height / 2);
await sleep(1000);
// Try tapping the fullscreen/exit button (typically top-right in landscape)
await driver.tap(size.width - 40, 40);
await sleep(2000);
// If still in landscape, force back to portrait
const sizeAfter = await driver.getWindowSize();
if (sizeAfter.width > sizeAfter.height) {
// Tap back area in landscape
await driver.tap(40, 40);
await sleep(2000);
}
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 {
await enterControlPage();
// Double-tap video area to zoom in
const centerX = screenWidth / 2;
const videoY = 200;
await driver.doubleTap(centerX, videoY);
await sleep(3000);
// Double-tap again to zoom back out
await driver.doubleTap(centerX, videoY);
await sleep(3000);
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;
}
});
// ==================== PTZ预置位 ====================
it('功能页PTZ预置位', async () => {
const start = Date.now();
try {
await enterControlPage(false);
await tapTab('Direction');
await sleep(2000);
// Check if Presets section is visible
const source = await driver.getSource();
if (!source.includes('Presets')) {
reporter.record('PTZ预置位', 'SKIP', Date.now() - start, 'Direction页无Presets区域');
return;
}
// Find the "+" add button: it's below the Presets header area
// In empty state, there's a large button in the center and a hint text
const presetsEl = await driver.findElementRaw('name', 'Presets');
if (!presetsEl) {
reporter.record('PTZ预置位', 'SKIP', Date.now() - start, '未找到Presets元素');
return;
}
// Look for the hint text to locate the add button precisely
const hintEl = await driver.findElementRaw('predicate string',
'name CONTAINS "Tap" AND name CONTAINS "add"');
if (hintEl) {
// The "+" button is above the hint text, in the center
const hintRect = await driver.getElementRect(hintEl);
await driver.tap(screenWidth / 2, hintRect.y - 50);
} else {
// Find buttons in the presets area below the header
const presetsRect = await driver.getElementRect(presetsEl);
const buttons = await driver.findElementsRaw('class name', 'XCUIElementTypeButton');
let addBtn: string | null = null;
for (const btn of buttons) {
const rect = await driver.getElementRect(btn);
if (rect.y > presetsRect.y + 50 && rect.y < presetsRect.y + 200
&& rect.width > 40 && rect.height > 40) {
addBtn = btn;
break;
}
}
if (addBtn) {
const addRect = await driver.getElementRect(addBtn);
await driver.tap(addRect.x + addRect.width / 2, addRect.y + addRect.height / 2);
} else {
// Last resort: tap center below Presets
await driver.tap(screenWidth / 2, presetsRect.y + 130);
}
}
await sleep(2000);
// Check if "Add Preset" dialog appeared
let yesEl = await driver.findElementRaw('name', 'Yes');
if (!yesEl) {
// Try tapping the small "+" icon next to Presets header (right side)
const presetsRect = await driver.getElementRect(presetsEl);
await driver.tap(screenWidth - 30, presetsRect.y + presetsRect.height / 2);
await sleep(2000);
yesEl = await driver.findElementRaw('name', 'Yes');
}
if (!yesEl) {
reporter.record('PTZ预置位', 'SKIP', Date.now() - start, '未弹出Add Preset对话框');
return;
}
// Tap Yes to add preset
await driver.tapElement(yesEl);
await sleep(8000);
// Verify preset was added
const sourceAfterAdd = await driver.getSource();
if (sourceAfterAdd.includes('You have not added')) {
reporter.record('PTZ预置位', 'SKIP', Date.now() - start, '预置位添加失败');
return;
}
// Find the newly added preset (e.g. "Presets1")
const presetEl = await driver.findElementRaw('predicate string',
'name BEGINSWITH "Presets" AND name != "Presets" AND type == "XCUIElementTypeStaticText"');
if (!presetEl) {
reporter.record('PTZ预置位', 'PASS', Date.now() - start, '预置位添加完成(未找到名称元素)');
return;
}
// Rename: Long press the preset
const presetRect = await driver.getElementRect(presetEl);
const cx = presetRect.x + presetRect.width / 2;
const cy = presetRect.y + presetRect.height / 2;
await driver.longPress(cx, cy, 2);
await sleep(1500);
const renameEl = await driver.findElementRaw('name', 'Rename');
if (renameEl) {
await driver.tapElement(renameEl);
await sleep(2000);
// Clear and type new name
const clearEl = await driver.findElementRaw('name', 'Clear text');
if (clearEl) await driver.tapElement(clearEl);
await sleep(300);
const textFields = await driver.findElementsRaw('class name', 'XCUIElementTypeTextField');
if (textFields.length > 0) {
await driver.typeText(textFields[0], 'TestPreset');
await sleep(500);
}
// Confirm rename
const yesRename = await driver.findElementRaw('name', 'Yes');
if (yesRename) {
await driver.tapElement(yesRename);
await sleep(3000);
}
}
// Delete: Long press again
await sleep(2000);
const presetEl2 = await driver.findElementRaw('predicate string',
'name CONTAINS "Preset" AND name != "Presets" AND type == "XCUIElementTypeStaticText"');
if (presetEl2) {
const presetRect2 = await driver.getElementRect(presetEl2);
await driver.longPress(presetRect2.x + presetRect2.width / 2, presetRect2.y + presetRect2.height / 2, 2);
await sleep(1500);
const deleteEl = await driver.findElementRaw('name', 'Delete');
if (deleteEl) {
await driver.tapElement(deleteEl);
await sleep(1500);
const confirmEl = await driver.findElementRaw('name', 'Confirm');
if (confirmEl) {
await driver.tapElement(confirmEl);
await sleep(3000);
}
}
}
// Verify deletion
const finalSource = await driver.getSource();
const deleted = finalSource.includes('You have not added') || !finalSource.includes('TestPreset');
reporter.record('PTZ预置位', 'PASS', Date.now() - start, `预置位添加/重命名/删除完成, 删除=${deleted}`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('PTZ预置位', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ==================== 翻转画面 ====================
it('功能页翻转画面', async () => {
const start = Date.now();
try {
await enterControlPage(false);
// Navigate to Settings by tapping the gear icon (top-right of nav bar)
await driver.tap(362, 69);
await sleep(3000);
// Verify we're on Settings page
let source = await driver.getSource();
if (!source.includes('More Features') && !source.includes('Motion Detection')) {
// Not on Settings page, try finding Settings element
const settingsEl = await driver.findElementRaw('name', 'Settings');
if (settingsEl) {
await driver.tapElement(settingsEl);
await sleep(2000);
source = await driver.getSource();
}
}
if (!source.includes('More Features')) {
// Scroll to find More Features
await driver.scrollDown(300);
await sleep(1000);
source = await driver.getSource();
}
// Tap "More Features"
const moreFeaturesEl = await driver.findElementRaw('name', 'More Features');
if (!moreFeaturesEl) {
reporter.record('翻转画面', 'SKIP', Date.now() - start, '未找到More Features');
return;
}
await driver.tapElement(moreFeaturesEl);
await sleep(2000);
// Tap "Video Display"
const videoDisplayEl = await driver.findElementRaw('name', 'Video Display');
if (!videoDisplayEl) {
reporter.record('翻转画面', 'SKIP', Date.now() - start, '未找到Video Display');
return;
}
await driver.tapElement(videoDisplayEl);
await sleep(2000);
// Find and toggle "Flip Screen"
const flipEl = await driver.findElementRaw('name', 'Flip Screen');
if (flipEl) {
// Find the switch associated with Flip Screen
const switches = await driver.findElementsRaw('class name', 'XCUIElementTypeSwitch');
let flipSwitch: string | null = null;
for (const sw of switches) {
const name = await driver.getElementAttribute(sw, 'name');
if (name && name.includes('Flip')) {
flipSwitch = sw;
break;
}
}
if (!flipSwitch && switches.length > 0) {
// Use the last switch (Flip Screen is typically the last item)
flipSwitch = switches[switches.length - 1];
}
if (flipSwitch) {
await driver.tapElement(flipSwitch);
await sleep(5000);
// Toggle back
await driver.tapElement(flipSwitch);
await sleep(3000);
reporter.record('翻转画面', 'PASS', Date.now() - start, 'Flip Screen切换完成');
} else {
// Try tapping the element directly
await driver.tapElement(flipEl);
await sleep(5000);
await driver.tapElement(flipEl);
await sleep(3000);
reporter.record('翻转画面', 'PASS', Date.now() - start, 'Flip Screen点击完成');
}
} else {
reporter.record('翻转画面', 'SKIP', Date.now() - start, '未找到Flip Screen');
}
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('翻转画面', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ==================== LED指示灯 ====================
it('功能页LED指示灯开关', async () => {
const start = Date.now();
try {
await enterControlPage(false);
// Navigate to Settings by tapping gear icon (top-right of nav bar)
await driver.tap(362, 69);
await sleep(3000);
// Verify we're on Settings page
let source = await driver.getSource();
if (!source.includes('More Features') && !source.includes('Motion Detection')) {
const settingsEl = await driver.findElementRaw('name', 'Settings');
if (settingsEl) {
await driver.tapElement(settingsEl);
await sleep(2000);
source = await driver.getSource();
}
}
if (!source.includes('More Features')) {
await driver.scrollDown(300);
await sleep(1000);
}
// Tap "More Features"
const moreFeaturesEl = await driver.findElementRaw('name', 'More Features');
if (!moreFeaturesEl) {
reporter.record('LED指示灯', 'SKIP', Date.now() - start, '未找到More Features');
return;
}
await driver.tapElement(moreFeaturesEl);
await sleep(2000);
// Find "Indicator Light" and toggle its switch
const indicatorEl = await driver.findElementRaw('name', 'Indicator Light');
if (!indicatorEl) {
reporter.record('LED指示灯', 'SKIP', Date.now() - start, '当前设备不支持Indicator Light');
return;
}
// Find the switch for Indicator Light
const switches = await driver.findElementsRaw('class name', 'XCUIElementTypeSwitch');
let ledSwitch: string | null = null;
for (const sw of switches) {
const name = await driver.getElementAttribute(sw, 'name');
if (name && name.includes('Indicator')) {
ledSwitch = sw;
break;
}
}
if (!ledSwitch && switches.length > 0) {
ledSwitch = switches[0];
}
if (ledSwitch) {
await driver.tapElement(ledSwitch);
await sleep(5000);
// Toggle back
await driver.tapElement(ledSwitch);
await sleep(3000);
reporter.record('LED指示灯', 'PASS', Date.now() - start, 'Indicator Light切换完成');
} else {
// Try tapping the element directly
await driver.tapElement(indicatorEl);
await sleep(3000);
reporter.record('LED指示灯', 'PASS', Date.now() - start, 'Indicator Light点击完成');
}
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('LED指示灯', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ==================== 移动监测灵敏度 ====================
it('功能页移动监测灵敏度', async () => {
const start = Date.now();
try {
await enterControlPage(false);
await tapTab('Features');
// First find and tap Motion Detection to enter its settings
const motionEl = await driver.findElementRaw('name', 'Motion Detection')
|| await driver.findElementRaw('name', 'Motion');
if (!motionEl) {
reporter.record('移动监测灵敏度', 'SKIP', Date.now() - start, '未找到Motion Detection');
return;
}
// Check if there's a sensitivity sub-option (may need long press or right-arrow)
const rect = await driver.getElementRect(motionEl);
// Tap the right side of the element (usually has a > arrow for settings)
await driver.tap(rect.x + rect.width - 20, rect.y + rect.height / 2);
await sleep(2000);
const source = await driver.getSource();
const hasSensitivity = source.includes('Sensitivity') || source.includes('灵敏度')
|| source.includes('High') || source.includes('Medium') || source.includes('Low');
if (hasSensitivity) {
// Try selecting a different sensitivity level
const medEl = await driver.findElementRaw('name', 'Medium')
|| await driver.findElementRaw('name', '中');
if (medEl) {
await driver.tapElement(medEl);
await sleep(2000);
}
reporter.record('移动监测灵敏度', 'PASS', Date.now() - start, '灵敏度设置完成');
} else {
// Toggle motion detection on/off as fallback
await driver.tapElement(motionEl);
await sleep(2000);
reporter.record('移动监测灵敏度', 'SKIP', Date.now() - start, '无灵敏度子设置');
}
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('移动监测灵敏度', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});