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, enterDeviceSettings, renameDevice, changeDeviceRoom, navigateToFirmwarePage, checkFirmwareVersion, navigateToDeviceInfo, getDeviceInfo, scrollToAndTap, waitForSource, enterEditInfo, addDeviceViaBLE, isDeviceOnHomepage, } from '../../utils/common'; import * as dotenv from 'dotenv'; import * as path from 'path'; dotenv.config({ path: path.resolve(__dirname, '../../.env') }); const deviceName = getDeviceName('osc', 'OSC_DEVICE'); describe('OSC Control - 户外摄像头功能页操作', () => { let driver: DeviceDriver; let reporter: TestReporter; let screenWidth = 390; let screenHeight = 844; beforeAll(async () => { driver = createDriver(); await driver.createSession(); reporter = new TestReporter('OSC_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 "OSC" AND type == "XCUIElementTypeCell"', 'name CONTAINS "Outdoor" AND type == "XCUIElementTypeCell"', 'name CONTAINS "Spotlight" 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("OSC")'); 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) throw new Error('找不到OSC设备卡片'); await driver.tapElement(cardId); if (!waitForVideo) { await sleep(5000); return; } // Wait for video stream to fully load 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 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) // 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); } // ==================== 画质切换 ==================== 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 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; } }); });