AI_UIAutomation/tests/camera/camera_control_fullscreen.t...

597 lines
20 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 FullScreen Control - 摄像头全屏操作', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
let screenWidth = 390;
let screenHeight = 844;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('Camera_FullScreen', 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];
}
} 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 enterCameraPage(): 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')) break;
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')) break;
}
}
const checkSource = await driver.getSource();
if (!checkSource.includes('Direction') && !checkSource.includes('KB/S')) {
throw new Error('找不到摄像头卡片');
}
return;
}
// Wait for video stream (KB/S indicator)
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);
}
const source = await driver.getSource();
if (source.includes('Device online') || source.includes('设备在线')) {
await sleep(2000);
return;
}
throw new Error('视频流加载超时');
}
async function enterFullscreen(): Promise<void> {
// Tap video area to show overlay controls
await driver.tap(screenWidth / 2, 250);
await sleep(800);
// Tap fullscreen button (bottom-right of video overlay, last icon in bottom row)
await driver.tap(screenWidth - 28, 350);
await sleep(3000);
// Verify we entered fullscreen (no Tab Bar)
const source = await driver.getSource();
if (source.includes('Tab Bar')) {
// Retry: tap video and fullscreen again
await driver.tap(screenWidth / 2, 250);
await sleep(800);
await driver.tap(screenWidth - 28, 350);
await sleep(3000);
}
}
function isFullscreenSource(source: string): boolean {
return source.includes('SwitchBot')
&& !source.includes('Tab Bar')
&& !source.includes('Events')
&& !source.includes('Direction')
&& !source.includes('Features');
}
async function exitFullscreen(): Promise<void> {
// Tap center to show controls
await driver.tap(screenWidth / 2, screenHeight / 2);
await sleep(500);
// Find first button (exit/back) and click it
const buttons = await driver.findElementsRaw('class name', 'XCUIElementTypeButton');
if (buttons.length > 0) {
await driver.tapElement(buttons[0]);
} else {
// Retry tap center
await driver.tap(screenWidth / 2, screenHeight / 2);
await sleep(500);
const btns2 = await driver.findElementsRaw('class name', 'XCUIElementTypeButton');
if (btns2.length > 0) await driver.tapElement(btns2[0]);
}
await sleep(2000);
// Verify we exited (should have Tab Bar again)
const source = await driver.getSource();
if (!source.includes('Tab Bar') && !source.includes('Events')) {
// Still in fullscreen, try once more
await driver.tap(screenWidth / 2, screenHeight / 2);
await sleep(500);
const btns3 = await driver.findElementsRaw('class name', 'XCUIElementTypeButton');
if (btns3.length > 0) await driver.tapElement(btns3[0]);
await sleep(2000);
}
}
async function tapFullscreenControl(position: 'pause' | 'screenshot' | 'talk' | 'record' | 'speaker'): Promise<void> {
// In fullscreen (CSS rotated), controls are on the left side vertically
// pause~(70,458), screenshot~(70,534), talk~(70,610), record~(70,686)
// speaker is on right side at ~(348,581)
const positions: Record<string, { x: number; y: number }> = {
pause: { x: 70, y: 458 },
screenshot: { x: 70, y: 534 },
talk: { x: 70, y: 610 },
record: { x: 70, y: 686 },
speaker: { x: 348, y: 581 },
};
// Tap center to show controls first
await driver.tap(screenWidth / 2, screenHeight / 2);
await sleep(500);
const pos = positions[position];
await driver.tap(pos.x, pos.y);
await sleep(1000);
}
// ==================== 全屏画质切换 ====================
it('全屏-切换画质', async () => {
const start = Date.now();
try {
await enterCameraPage();
await enterFullscreen();
// Find HD/SD element and tap to toggle
const hdEl = await driver.findElementRaw('name', 'HD');
const sdEl = await driver.findElementRaw('name', 'SD');
const qualityEl = hdEl || sdEl;
const beforeQuality = hdEl ? 'HD' : 'SD';
if (!qualityEl) {
reporter.record('全屏-切换画质', 'SKIP', Date.now() - start, '未找到画质按钮');
await exitFullscreen();
return;
}
await driver.tapElement(qualityEl);
await sleep(8000);
const hdEl2 = await driver.findElementRaw('name', 'HD');
const sdEl2 = await driver.findElementRaw('name', 'SD');
const afterQuality = hdEl2 ? 'HD' : (sdEl2 ? 'SD' : 'unknown');
await exitFullscreen();
reporter.record('全屏-切换画质', 'PASS', Date.now() - start, `${beforeQuality}${afterQuality}`);
} 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 enterCameraPage();
await enterFullscreen();
// Tap speaker button (right side)
await tapFullscreenControl('speaker');
await sleep(3000);
// Toggle back
await tapFullscreenControl('speaker');
await sleep(2000);
await exitFullscreen();
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 enterCameraPage();
await enterFullscreen();
await tapFullscreenControl('screenshot');
await sleep(3000);
await exitFullscreen();
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 enterCameraPage();
await enterFullscreen();
// Start recording
await tapFullscreenControl('record');
await sleep(5000);
// Stop recording
await tapFullscreenControl('record');
await sleep(2000);
await exitFullscreen();
reporter.record('全屏-录视频', 'PASS', Date.now() - start, '全屏录制并停止成功');
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('全屏-录视频', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ==================== 全屏对讲模式(单向) ====================
it('全屏-设置页切换对讲模式为单向', async () => {
const start = Date.now();
try {
const cardId = await findCameraCard();
if (!cardId) throw new Error('找不到摄像头卡片');
await driver.tapElement(cardId);
await sleep(5000);
// Look for Settings tab or gear icon (top-right area)
const settingsEl = await driver.findElementRaw('name', 'Settings');
if (!settingsEl) {
// Try tapping top-right gear icon
const buttons = await driver.findElementsRaw('class name', 'XCUIElementTypeButton');
let gearBtn: string | null = null;
for (const btn of buttons) {
const rect = await driver.getElementRect(btn);
if (rect.x > 330 && rect.y < 80 && rect.width < 40) {
gearBtn = btn;
break;
}
}
if (gearBtn) {
await driver.tapElement(gearBtn);
await sleep(2000);
} else {
reporter.record('全屏-设置单向对讲', 'SKIP', Date.now() - start, '未找到Settings入口');
return;
}
} else {
await driver.tapElement(settingsEl);
await sleep(2000);
}
const basicEl = await driver.findElementRaw('name', 'Basic Settings');
if (basicEl) {
await driver.tapElement(basicEl);
await sleep(1500);
const talkEl = await driver.findElementRaw('name', 'Talk Mode');
if (talkEl) {
await driver.tapElement(talkEl);
await sleep(1500);
const oneWayEl = await driver.findElementRaw('name', 'One-way');
if (oneWayEl) {
await driver.tapElement(oneWayEl);
await sleep(3000);
}
}
}
const source = await driver.getSource();
const confirmed = source.includes('One-way') || source.includes('Talk Mode');
reporter.record('全屏-设置单向对讲', 'PASS', Date.now() - start, `One-way设置=${confirmed}`);
} 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 enterCameraPage();
await enterFullscreen();
// Tap talk button (left side, 3rd from top)
await tapFullscreenControl('talk');
await sleep(5000);
// Stop talk
await tapFullscreenControl('talk');
await sleep(2000);
await exitFullscreen();
reporter.record('全屏-单向通话', 'PASS', Date.now() - start, '全屏单向通话完成');
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('全屏-单向通话', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ==================== 全屏对讲模式(双向) ====================
it('全屏-设置页切换对讲模式为双向', async () => {
const start = Date.now();
try {
const cardId = await findCameraCard();
if (!cardId) throw new Error('找不到摄像头卡片');
await driver.tapElement(cardId);
await sleep(5000);
const settingsEl = await driver.findElementRaw('name', 'Settings');
if (!settingsEl) {
const buttons = await driver.findElementsRaw('class name', 'XCUIElementTypeButton');
let gearBtn: string | null = null;
for (const btn of buttons) {
const rect = await driver.getElementRect(btn);
if (rect.x > 330 && rect.y < 80 && rect.width < 40) {
gearBtn = btn;
break;
}
}
if (gearBtn) {
await driver.tapElement(gearBtn);
await sleep(2000);
} else {
reporter.record('全屏-设置双向对讲', 'SKIP', Date.now() - start, '未找到Settings入口');
return;
}
} else {
await driver.tapElement(settingsEl);
await sleep(2000);
}
const basicEl = await driver.findElementRaw('name', 'Basic Settings');
if (basicEl) {
await driver.tapElement(basicEl);
await sleep(1500);
const talkEl = await driver.findElementRaw('name', 'Talk Mode');
if (talkEl) {
await driver.tapElement(talkEl);
await sleep(1500);
const twoWayEl = await driver.findElementRaw('name', 'Two-way');
if (twoWayEl) {
await driver.tapElement(twoWayEl);
await sleep(3000);
}
}
}
const source = await driver.getSource();
const confirmed = source.includes('Two-way') || source.includes('Talk Mode');
reporter.record('全屏-设置双向对讲', 'PASS', Date.now() - start, `Two-way设置=${confirmed}`);
} 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 enterCameraPage();
await enterFullscreen();
// Tap talk button
await tapFullscreenControl('talk');
await sleep(5000);
// Stop talk
await tapFullscreenControl('talk');
await sleep(2000);
await exitFullscreen();
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 enterCameraPage();
await enterFullscreen();
// Pause
await tapFullscreenControl('pause');
await sleep(3000);
// Resume
await tapFullscreenControl('pause');
await sleep(3000);
// Verify still in fullscreen and streaming
const source = await driver.getSource();
const stillFullscreen = isFullscreenSource(source);
await exitFullscreen();
// Verify not disconnected after exit
await sleep(1000);
const sourceAfter = await driver.getSource();
const isLive = !sourceAfter.includes('Disconnected');
reporter.record('全屏-播放暂停', 'PASS', Date.now() - start, `暂停/恢复完成, fullscreen=${stillFullscreen}, live=${isLive}`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('全屏-播放暂停', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ==================== 全屏方向操作(PTZ) ====================
it('全屏-方向操作', async () => {
const start = Date.now();
try {
await enterCameraPage();
await enterFullscreen();
// In fullscreen, swipe to control PTZ direction
const centerX = screenWidth / 2;
const centerY = screenHeight / 2;
// Swipe up (tilt up)
await driver.swipe(centerX, centerY, centerX, centerY - 100, 0.5);
await sleep(2000);
// Swipe left (pan left)
await driver.swipe(centerX, centerY, centerX - 100, centerY, 0.5);
await sleep(2000);
// Swipe down (tilt down)
await driver.swipe(centerX, centerY, centerX, centerY + 100, 0.5);
await sleep(2000);
// Swipe right (pan right)
await driver.swipe(centerX, centerY, centerX + 100, centerY, 0.5);
await sleep(2000);
await exitFullscreen();
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 enterCameraPage();
await enterFullscreen();
const centerX = screenWidth / 2;
const centerY = screenHeight / 2;
// Double-tap to zoom in
await driver.doubleTap(centerX, centerY);
await sleep(3000);
// Double-tap to zoom back out
await driver.doubleTap(centerX, centerY);
await sleep(3000);
await exitFullscreen();
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 enterCameraPage();
await enterFullscreen();
const centerX = screenWidth / 2;
const centerY = screenHeight / 2;
// Double-tap to zoom in
await driver.doubleTap(centerX, centerY);
await sleep(3000);
// Verify still in fullscreen
const source = await driver.getSource();
const stillFullscreen = isFullscreenSource(source);
// Double-tap to zoom back out
await driver.doubleTap(centerX, centerY);
await sleep(3000);
await exitFullscreen();
reporter.record('全屏-双击缩放', 'PASS', Date.now() - start, `全屏双击缩放完成, fullscreen=${stillFullscreen}`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record('全屏-双击缩放', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});