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 { 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 { // 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 { await driver.tap(screenWidth / 2, 200); await sleep(1000); } async function tapTab(tabName: string): Promise { 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 { await driver.tap(270, 137); } async function tapSpeakerButton(): Promise { await driver.tap(324, 147); } async function tapPauseButton(): Promise { await driver.tap(28, 350); } async function tapScreenshotButton(): Promise { await driver.tap(111, 350); } async function tapTalkButton(): Promise { await driver.tap(195, 350); } async function tapRecordButton(): Promise { await driver.tap(278, 350); } async function tapFullscreenButton(): Promise { await driver.tap(362, 350); } async function tapGridButton(): Promise { 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; } }); });