1214 lines
50 KiB
TypeScript
1214 lines
50 KiB
TypeScript
import { describe, it, beforeAll, afterAll, beforeEach, expect } from 'vitest';
|
||
import { HubShowDriver } from '../../drivers/hubshow-driver';
|
||
import { TestReporter } from '../../utils/test-reporter';
|
||
import { sleep } from '../../utils/common';
|
||
import {
|
||
createHubShowDriver,
|
||
waitForLoading,
|
||
ensureOnSecurityPage,
|
||
enterPlaybackPage,
|
||
enterCameraLive,
|
||
enterDoorbellLive,
|
||
enterDailyReport,
|
||
logPageSource,
|
||
} from './hubshow-setup.helper';
|
||
|
||
describe('AI Hub Show 安防+回放 - 固件测试', () => {
|
||
let driver: HubShowDriver;
|
||
let reporter: TestReporter;
|
||
|
||
beforeAll(async () => {
|
||
driver = createHubShowDriver();
|
||
await driver.createSession();
|
||
reporter = new TestReporter('AIHubShow_Security', 'ANDROID');
|
||
});
|
||
|
||
afterAll(async () => {
|
||
reporter.generate();
|
||
await driver.destroySession();
|
||
});
|
||
|
||
beforeEach(async () => {
|
||
await driver.dismissPopupIfPresent();
|
||
});
|
||
|
||
// ============================================================
|
||
// 安防首页 (Security Homepage) Tests
|
||
// ============================================================
|
||
|
||
it('安防首页-点击安防icon进入', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('从主页点击安防icon');
|
||
await driver.goBackToHomepage();
|
||
await sleep(2000);
|
||
|
||
const secEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("安防")');
|
||
if (!secEl) {
|
||
const secEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Security")');
|
||
expect(secEn).not.toBeNull();
|
||
await driver.tapElement(secEn!);
|
||
} else {
|
||
await driver.tapElement(secEl);
|
||
}
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
|
||
steps.push('验证进入安防首页');
|
||
const source = await driver.getSource();
|
||
const onSecurityPage = source.includes('安防') || source.includes('Security') || source.includes('Camera');
|
||
expect(onSecurityPage).toBe(true);
|
||
|
||
reporter.record('安防首页-点击安防icon进入', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-点击安防icon进入', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-页面显示', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('验证页面元素');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
// Verify camera feed area and playback button exist
|
||
const hasCamera = source.includes('摄像头') || source.includes('Camera') || source.includes('cam');
|
||
const hasPlayback = source.includes('回放') || source.includes('Playback');
|
||
expect(hasCamera || hasPlayback).toBe(true);
|
||
|
||
reporter.record('安防首页-页面显示', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-页面显示', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-空状态', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
// SKIP: 需要未绑定任何摄像头的特殊硬件状态
|
||
reporter.record('安防首页-空状态', 'SKIP', Date.now() - start, '需未绑定摄像头设备状态,无法自动化');
|
||
});
|
||
|
||
it('安防首页-设备离线', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
// SKIP: 需要设备离线的特殊状态
|
||
reporter.record('安防首页-设备离线', 'SKIP', Date.now() - start, '需设备离线状态,无法自动化');
|
||
});
|
||
|
||
it('安防首页-宫格布局(1个设备)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('检查宫格布局-1设备');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
// With 1 device bound, verify single camera feed is displayed
|
||
const hasCameraFeed = source.includes('摄像头') || source.includes('Camera') || source.includes('cam');
|
||
expect(hasCameraFeed).toBe(true);
|
||
|
||
reporter.record('安防首页-宫格布局(1个设备)', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-宫格布局(1个设备)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-宫格布局(2个设备)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('检查宫格布局-2设备');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
// Verify layout displays at least 2 camera feeds
|
||
const cameraEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().descriptionContains("camera")');
|
||
const camTextEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const totalCams = cameraEls.length + camTextEls.length;
|
||
steps.push(`检测到 ${totalCams} 个摄像头元素`);
|
||
|
||
// If less than 2 cameras, record but don't fail (depends on binding state)
|
||
if (totalCams < 2) {
|
||
reporter.record('安防首页-宫格布局(2个设备)', 'SKIP', Date.now() - start, '当前绑定设备数不足2个');
|
||
return;
|
||
}
|
||
expect(totalCams).toBeGreaterThanOrEqual(2);
|
||
|
||
reporter.record('安防首页-宫格布局(2个设备)', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-宫格布局(2个设备)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-宫格布局(3个设备)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('检查宫格布局-3设备');
|
||
const source = await driver.getSource();
|
||
|
||
const cameraEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().descriptionContains("camera")');
|
||
const camTextEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const totalCams = cameraEls.length + camTextEls.length;
|
||
steps.push(`检测到 ${totalCams} 个摄像头元素`);
|
||
|
||
if (totalCams < 3) {
|
||
reporter.record('安防首页-宫格布局(3个设备)', 'SKIP', Date.now() - start, '当前绑定设备数不足3个');
|
||
return;
|
||
}
|
||
expect(totalCams).toBeGreaterThanOrEqual(3);
|
||
|
||
reporter.record('安防首页-宫格布局(3个设备)', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-宫格布局(3个设备)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-宫格布局(4个设备)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('检查宫格布局-4设备');
|
||
const source = await driver.getSource();
|
||
|
||
const cameraEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().descriptionContains("camera")');
|
||
const camTextEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const totalCams = cameraEls.length + camTextEls.length;
|
||
steps.push(`检测到 ${totalCams} 个摄像头元素`);
|
||
|
||
if (totalCams < 4) {
|
||
reporter.record('安防首页-宫格布局(4个设备)', 'SKIP', Date.now() - start, '当前绑定设备数不足4个');
|
||
return;
|
||
}
|
||
expect(totalCams).toBeGreaterThanOrEqual(4);
|
||
|
||
reporter.record('安防首页-宫格布局(4个设备)', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-宫格布局(4个设备)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-点击大图进入回放', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('点击大图区域');
|
||
// Tap on the main camera feed area (center of screen upper half)
|
||
const size = await driver.getWindowSize();
|
||
await driver.tap(size.width / 2, size.height * 0.3);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
|
||
steps.push('验证进入回放页面');
|
||
const source = await driver.getSource();
|
||
const onPlayback = source.includes('回放') || source.includes('Playback') || source.includes('事件');
|
||
expect(onPlayback).toBe(true);
|
||
|
||
// Go back to security page for next test
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('安防首页-点击大图进入回放', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-点击大图进入回放', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-点击回放按钮进入回放', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('点击回放按钮');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
|
||
steps.push('验证进入回放页面');
|
||
const source = await driver.getSource();
|
||
const onPlayback = source.includes('回放') || source.includes('Playback') || source.includes('暂停') || source.includes('播放');
|
||
expect(onPlayback).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('安防首页-点击回放按钮进入回放', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-点击回放按钮进入回放', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-点击摄像头实时视频', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('点击摄像头进入实时画面');
|
||
// Find and tap camera element
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
if (!camEl) {
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
expect(camEn).not.toBeNull();
|
||
await driver.tapElement(camEn!);
|
||
} else {
|
||
await driver.tapElement(camEl);
|
||
}
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
|
||
steps.push('验证进入实时页面');
|
||
const source = await driver.getSource();
|
||
const onLive = source.includes('实时') || source.includes('Live') || source.includes('警报') || source.includes('Alarm') || source.includes('静音');
|
||
expect(onLive).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('安防首页-点击摄像头实时视频', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-点击摄像头实时视频', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('安防首页-点击门铃实时视频', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('点击门铃进入实时画面');
|
||
const entered = await enterDoorbellLive(driver);
|
||
if (!entered) {
|
||
reporter.record('安防首页-点击门铃实时视频', 'SKIP', Date.now() - start, '未检测到门铃设备');
|
||
return;
|
||
}
|
||
|
||
steps.push('验证进入门铃实时页面');
|
||
const source = await driver.getSource();
|
||
const onDoorbell = source.includes('门铃') || source.includes('Doorbell') || source.includes('快捷回复');
|
||
expect(onDoorbell).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('安防首页-点击门铃实时视频', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('安防首页-点击门铃实时视频', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 回放页面 (Playback) Tests
|
||
// ============================================================
|
||
|
||
it('回放页面-页面显示', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
|
||
steps.push('验证回放页面元素');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
const hasPlaybackUI = source.includes('回放') || source.includes('Playback') ||
|
||
source.includes('暂停') || source.includes('播放') || source.includes('Pause') || source.includes('Play');
|
||
expect(hasPlaybackUI).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-页面显示', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-页面显示', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-播放暂停', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('点击暂停按钮');
|
||
const pauseEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("暂停")');
|
||
const pauseEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Pause")');
|
||
const playEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("播放")');
|
||
const playEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Play")');
|
||
|
||
const targetEl = pauseEl || pauseEn || playEl || playEn;
|
||
expect(targetEl).not.toBeNull();
|
||
await driver.tapElement(targetEl!);
|
||
await sleep(2000);
|
||
|
||
steps.push('验证播放状态切换');
|
||
const sourceAfter = await driver.getSource();
|
||
// After tapping, the opposite state should appear
|
||
const stateChanged = sourceAfter.includes('播放') || sourceAfter.includes('Play') ||
|
||
sourceAfter.includes('暂停') || sourceAfter.includes('Pause');
|
||
expect(stateChanged).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-播放暂停', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-播放暂停', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-上一个事件', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('点击上一个事件按钮');
|
||
const prevEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("上一个")');
|
||
const prevEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Previous")');
|
||
const prevIcon = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("prev")');
|
||
const targetEl = prevEl || prevEn || prevIcon;
|
||
|
||
if (!targetEl) {
|
||
reporter.record('回放页面-上一个事件', 'SKIP', Date.now() - start, '未找到上一个事件按钮');
|
||
await driver.goBack();
|
||
return;
|
||
}
|
||
|
||
await driver.tapElement(targetEl);
|
||
await sleep(3000);
|
||
|
||
steps.push('验证事件切换');
|
||
const source = await driver.getSource();
|
||
const hasContent = source.includes('回放') || source.includes('Playback') || source.includes('事件');
|
||
expect(hasContent).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-上一个事件', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-上一个事件', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-下一个事件', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('点击下一个事件按钮');
|
||
const nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("下一个")');
|
||
const nextEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Next")');
|
||
const nextIcon = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("next")');
|
||
const targetEl = nextEl || nextEn || nextIcon;
|
||
|
||
if (!targetEl) {
|
||
reporter.record('回放页面-下一个事件', 'SKIP', Date.now() - start, '未找到下一个事件按钮');
|
||
await driver.goBack();
|
||
return;
|
||
}
|
||
|
||
await driver.tapElement(targetEl);
|
||
await sleep(3000);
|
||
|
||
steps.push('验证事件切换');
|
||
const source = await driver.getSource();
|
||
const hasContent = source.includes('回放') || source.includes('Playback') || source.includes('事件');
|
||
expect(hasContent).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-下一个事件', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-下一个事件', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-边界状态(最新)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('检查最新事件时上一个按钮状态');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
// At the latest event, "previous" button should be disabled or not present
|
||
const prevEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("上一个")');
|
||
const prevEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Previous")');
|
||
const targetEl = prevEl || prevEn;
|
||
|
||
if (targetEl) {
|
||
const enabled = await driver.getElementAttribute(targetEl, 'enabled');
|
||
steps.push(`上一个按钮 enabled=${enabled}`);
|
||
// At the latest event, prev should be disabled
|
||
// Note: this depends on being at the latest event boundary
|
||
}
|
||
|
||
steps.push('边界状态验证完成');
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-边界状态(最新)', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-边界状态(最新)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-边界状态(最早)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('检查最早事件时下一个按钮状态');
|
||
// Navigate to earliest event by tapping "next" multiple times
|
||
const source = await driver.getSource();
|
||
|
||
const nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("下一个")');
|
||
const nextEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Next")');
|
||
const targetEl = nextEl || nextEn;
|
||
|
||
if (targetEl) {
|
||
const enabled = await driver.getElementAttribute(targetEl, 'enabled');
|
||
steps.push(`下一个按钮 enabled=${enabled}`);
|
||
}
|
||
|
||
steps.push('边界状态验证完成');
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-边界状态(最早)', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-边界状态(最早)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-无事件', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
// SKIP: 需要无事件记录的特殊状态
|
||
reporter.record('回放页面-无事件', 'SKIP', Date.now() - start, '需无事件记录状态,无法自动化确保');
|
||
});
|
||
|
||
it('回放页面-点击全部事件', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('点击全部事件按钮');
|
||
const allEventsEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("全部事件")');
|
||
const allEventsEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("All Events")');
|
||
const targetEl = allEventsEl || allEventsEn;
|
||
expect(targetEl).not.toBeNull();
|
||
await driver.tapElement(targetEl!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
|
||
steps.push('验证进入事件列表');
|
||
const source = await driver.getSource();
|
||
const onEventList = source.includes('事件') || source.includes('Event');
|
||
expect(onEventList).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-点击全部事件', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-点击全部事件', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-点击实时画面', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('点击实时画面按钮');
|
||
const liveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("实时")');
|
||
const liveEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Live")');
|
||
const targetEl = liveEl || liveEn;
|
||
expect(targetEl).not.toBeNull();
|
||
await driver.tapElement(targetEl!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
|
||
steps.push('验证进入实时画面');
|
||
const source = await driver.getSource();
|
||
const onLive = source.includes('实时') || source.includes('Live') || source.includes('警报') || source.includes('Alarm');
|
||
expect(onLive).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('回放页面-点击实时画面', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-点击实时画面', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('回放页面-点击返回', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入回放页面');
|
||
const entered = await enterPlaybackPage(driver);
|
||
expect(entered).toBe(true);
|
||
await sleep(2000);
|
||
|
||
steps.push('点击返回');
|
||
const backEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("返回")');
|
||
const backEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Back")');
|
||
if (backEl) {
|
||
await driver.tapElement(backEl);
|
||
} else if (backEn) {
|
||
await driver.tapElement(backEn);
|
||
} else {
|
||
await driver.goBack();
|
||
}
|
||
await sleep(2000);
|
||
|
||
steps.push('验证返回到安防首页');
|
||
const source = await driver.getSource();
|
||
const onSecurity = source.includes('安防') || source.includes('Security') || source.includes('回放') || source.includes('Camera');
|
||
expect(onSecurity).toBe(true);
|
||
|
||
reporter.record('回放页面-点击返回', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('回放页面-点击返回', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 摄像头实时 (Camera Live) Tests
|
||
// ============================================================
|
||
|
||
it('摄像头实时-页面显示', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入摄像头实时页面');
|
||
const entered = await enterCameraLive(driver);
|
||
if (!entered) {
|
||
// Try direct navigation from security page
|
||
const onSec = await ensureOnSecurityPage(driver);
|
||
expect(onSec).toBe(true);
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const target = camEl || camEn;
|
||
expect(target).not.toBeNull();
|
||
await driver.tapElement(target!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
}
|
||
|
||
steps.push('验证实时页面元素');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
const hasLiveUI = source.includes('实时') || source.includes('Live') ||
|
||
source.includes('警报') || source.includes('Alarm') ||
|
||
source.includes('静音') || source.includes('Mute');
|
||
expect(hasLiveUI).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('摄像头实时-页面显示', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('摄像头实时-页面显示', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('摄像头实时-滑动控制角度', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入摄像头实时页面');
|
||
const entered = await enterCameraLive(driver);
|
||
if (!entered) {
|
||
const onSec = await ensureOnSecurityPage(driver);
|
||
expect(onSec).toBe(true);
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const target = camEl || camEn;
|
||
expect(target).not.toBeNull();
|
||
await driver.tapElement(target!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
}
|
||
|
||
steps.push('在实时画面上滑动控制角度');
|
||
const size = await driver.getWindowSize();
|
||
const centerX = size.width / 2;
|
||
const centerY = size.height * 0.35;
|
||
|
||
// Swipe left
|
||
await driver.swipe(centerX + 100, centerY, centerX - 100, centerY, 0.5);
|
||
await sleep(2000);
|
||
|
||
// Swipe up
|
||
await driver.swipe(centerX, centerY + 80, centerX, centerY - 80, 0.5);
|
||
await sleep(2000);
|
||
|
||
steps.push('滑动操作完成');
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('摄像头实时-滑动控制角度', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('摄像头实时-滑动控制角度', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('摄像头实时-方向控制圆盘', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入摄像头实时页面');
|
||
const entered = await enterCameraLive(driver);
|
||
if (!entered) {
|
||
const onSec = await ensureOnSecurityPage(driver);
|
||
expect(onSec).toBe(true);
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const target = camEl || camEn;
|
||
expect(target).not.toBeNull();
|
||
await driver.tapElement(target!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
}
|
||
|
||
steps.push('查找方向控制圆盘');
|
||
const dirEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("方向")');
|
||
const dirEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("direction")');
|
||
const padEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("control")');
|
||
|
||
if (dirEl || dirEn || padEl) {
|
||
const controlEl = (dirEl || dirEn || padEl)!;
|
||
const rect = await driver.getElementRect(controlEl);
|
||
const cx = rect.x + rect.width / 2;
|
||
const cy = rect.y + rect.height / 2;
|
||
|
||
steps.push('点击方向控制上/下/左/右');
|
||
// Tap up
|
||
await driver.tap(cx, cy - rect.height * 0.35);
|
||
await sleep(1500);
|
||
// Tap down
|
||
await driver.tap(cx, cy + rect.height * 0.35);
|
||
await sleep(1500);
|
||
// Tap left
|
||
await driver.tap(cx - rect.width * 0.35, cy);
|
||
await sleep(1500);
|
||
// Tap right
|
||
await driver.tap(cx + rect.width * 0.35, cy);
|
||
await sleep(1500);
|
||
} else {
|
||
steps.push('未找到方向圆盘控件,尝试屏幕坐标控制');
|
||
const size = await driver.getWindowSize();
|
||
// Direction pad usually in bottom portion of live view
|
||
const padCenterX = size.width / 2;
|
||
const padCenterY = size.height * 0.75;
|
||
await driver.tap(padCenterX, padCenterY - 60);
|
||
await sleep(1500);
|
||
await driver.tap(padCenterX, padCenterY + 60);
|
||
await sleep(1500);
|
||
}
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('摄像头实时-方向控制圆盘', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('摄像头实时-方向控制圆盘', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('摄像头实时-警报开启', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入摄像头实时页面');
|
||
const entered = await enterCameraLive(driver);
|
||
if (!entered) {
|
||
const onSec = await ensureOnSecurityPage(driver);
|
||
expect(onSec).toBe(true);
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const target = camEl || camEn;
|
||
expect(target).not.toBeNull();
|
||
await driver.tapElement(target!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
}
|
||
|
||
steps.push('查找警报按钮');
|
||
const alarmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("警报")');
|
||
const alarmEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Alarm")');
|
||
const alarmDesc = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("警报")');
|
||
const targetEl = alarmEl || alarmEn || alarmDesc;
|
||
expect(targetEl).not.toBeNull();
|
||
|
||
steps.push('开启警报');
|
||
await driver.tapElement(targetEl!);
|
||
await sleep(2000);
|
||
|
||
const source = await driver.getSource();
|
||
// Verify alarm state changed (look for "on" or alarm active indicator)
|
||
steps.push('警报操作完成');
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('摄像头实时-警报开启', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('摄像头实时-警报开启', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('摄像头实时-警报关闭', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入摄像头实时页面');
|
||
const entered = await enterCameraLive(driver);
|
||
if (!entered) {
|
||
const onSec = await ensureOnSecurityPage(driver);
|
||
expect(onSec).toBe(true);
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const target = camEl || camEn;
|
||
expect(target).not.toBeNull();
|
||
await driver.tapElement(target!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
}
|
||
|
||
steps.push('查找警报按钮');
|
||
const alarmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("警报")');
|
||
const alarmEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Alarm")');
|
||
const alarmDesc = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("警报")');
|
||
const targetEl = alarmEl || alarmEn || alarmDesc;
|
||
expect(targetEl).not.toBeNull();
|
||
|
||
steps.push('关闭警报');
|
||
await driver.tapElement(targetEl!);
|
||
await sleep(2000);
|
||
|
||
steps.push('警报关闭操作完成');
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('摄像头实时-警报关闭', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('摄像头实时-警报关闭', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('摄像头实时-静音切换', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入摄像头实时页面');
|
||
const entered = await enterCameraLive(driver);
|
||
if (!entered) {
|
||
const onSec = await ensureOnSecurityPage(driver);
|
||
expect(onSec).toBe(true);
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const target = camEl || camEn;
|
||
expect(target).not.toBeNull();
|
||
await driver.tapElement(target!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
}
|
||
|
||
steps.push('查找静音按钮');
|
||
const muteEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("静音")');
|
||
const muteEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Mute")');
|
||
const muteDesc = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("静音")');
|
||
const muteDescEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("mute")');
|
||
const targetEl = muteEl || muteEn || muteDesc || muteDescEn;
|
||
expect(targetEl).not.toBeNull();
|
||
|
||
steps.push('切换静音状态');
|
||
await driver.tapElement(targetEl!);
|
||
await sleep(2000);
|
||
|
||
// Tap again to toggle back
|
||
const muteEl2 = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("静音")');
|
||
const muteEn2 = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Mute")');
|
||
const muteDesc2 = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("静音")');
|
||
const muteDescEn2 = await driver.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("mute")');
|
||
const targetEl2 = muteEl2 || muteEn2 || muteDesc2 || muteDescEn2;
|
||
if (targetEl2) {
|
||
await driver.tapElement(targetEl2);
|
||
await sleep(1500);
|
||
}
|
||
|
||
steps.push('静音切换完成');
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('摄像头实时-静音切换', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('摄像头实时-静音切换', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('摄像头实时-点击全部事件', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('进入摄像头实时页面');
|
||
const entered = await enterCameraLive(driver);
|
||
if (!entered) {
|
||
const onSec = await ensureOnSecurityPage(driver);
|
||
expect(onSec).toBe(true);
|
||
const camEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const target = camEl || camEn;
|
||
expect(target).not.toBeNull();
|
||
await driver.tapElement(target!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
}
|
||
|
||
steps.push('点击全部事件');
|
||
const allEventsEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("全部事件")');
|
||
const allEventsEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("All Events")');
|
||
const targetEl = allEventsEl || allEventsEn;
|
||
expect(targetEl).not.toBeNull();
|
||
await driver.tapElement(targetEl!);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
|
||
steps.push('验证进入事件列表');
|
||
const source = await driver.getSource();
|
||
const onEventList = source.includes('事件') || source.includes('Event');
|
||
expect(onEventList).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('摄像头实时-点击全部事件', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('摄像头实时-点击全部事件', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 已开通安防首页 (AI+ Subscribed Security Homepage) Tests
|
||
// ============================================================
|
||
|
||
it('【已开通】安防首页-混合模式页面显示', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('验证已开通AI+的混合模式页面');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
// AI+ subscribed page should show enhanced features
|
||
const hasAIFeatures = source.includes('AI') || source.includes('智能') ||
|
||
source.includes('家居日报') || source.includes('Smart Report') ||
|
||
source.includes('事件描述') || source.includes('摄像头');
|
||
if (!hasAIFeatures) {
|
||
reporter.record('【已开通】安防首页-混合模式页面显示', 'SKIP', Date.now() - start, '当前设备未开通AI+服务');
|
||
return;
|
||
}
|
||
|
||
steps.push('混合模式页面显示正常');
|
||
reporter.record('【已开通】安防首页-混合模式页面显示', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('【已开通】安防首页-混合模式页面显示', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('【已开通】安防首页-家居日报显示', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('检查家居日报是否显示');
|
||
const source = await driver.getSource();
|
||
const hasReport = source.includes('家居日报') || source.includes('Smart Report') || source.includes('Daily Report');
|
||
if (!hasReport) {
|
||
reporter.record('【已开通】安防首页-家居日报显示', 'SKIP', Date.now() - start, '当前页面无家居日报(可能未开通AI+)');
|
||
return;
|
||
}
|
||
|
||
steps.push('家居日报显示正常');
|
||
reporter.record('【已开通】安防首页-家居日报显示', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('【已开通】安防首页-家居日报显示', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('【已开通】安防首页-点击家居日报', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('点击家居日报');
|
||
const entered = await enterDailyReport(driver);
|
||
if (!entered) {
|
||
reporter.record('【已开通】安防首页-点击家居日报', 'SKIP', Date.now() - start, '未找到家居日报入口(可能未开通AI+)');
|
||
return;
|
||
}
|
||
|
||
steps.push('验证进入家居日报页面');
|
||
const source = await driver.getSource();
|
||
const onReport = source.includes('日报') || source.includes('Report') || source.includes('今日') || source.includes('Today');
|
||
expect(onReport).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('【已开通】安防首页-点击家居日报', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('【已开通】安防首页-点击家居日报', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('【已开通】安防首页-最新事件描述文案', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('检查最新事件描述文案');
|
||
const source = await driver.getSource();
|
||
|
||
// AI+ should show event description text on security homepage
|
||
const hasEventDesc = source.includes('事件') || source.includes('Event') ||
|
||
source.includes('检测到') || source.includes('Detected') ||
|
||
source.includes('有人') || source.includes('Person') ||
|
||
source.includes('运动') || source.includes('Motion');
|
||
if (!hasEventDesc) {
|
||
reporter.record('【已开通】安防首页-最新事件描述文案', 'SKIP', Date.now() - start, '未找到事件描述文案(可能未开通AI+或无事件)');
|
||
return;
|
||
}
|
||
|
||
steps.push('事件描述文案显示正常');
|
||
reporter.record('【已开通】安防首页-最新事件描述文案', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('【已开通】安防首页-最新事件描述文案', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('【已开通】安防首页-全部事件入口', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('查找全部事件入口');
|
||
const allEventsEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("全部事件")');
|
||
const allEventsEn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("All Events")');
|
||
const targetEl = allEventsEl || allEventsEn;
|
||
|
||
if (!targetEl) {
|
||
reporter.record('【已开通】安防首页-全部事件入口', 'SKIP', Date.now() - start, '未找到全部事件入口');
|
||
return;
|
||
}
|
||
|
||
steps.push('点击全部事件');
|
||
await driver.tapElement(targetEl);
|
||
await sleep(3000);
|
||
await waitForLoading(driver);
|
||
|
||
steps.push('验证进入事件列表');
|
||
const source = await driver.getSource();
|
||
const onEvents = source.includes('事件') || source.includes('Event');
|
||
expect(onEvents).toBe(true);
|
||
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
|
||
reporter.record('【已开通】安防首页-全部事件入口', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('【已开通】安防首页-全部事件入口', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('【已开通】安防首页-1~4个摄像头布局', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
const steps: string[] = [];
|
||
try {
|
||
steps.push('导航到安防首页');
|
||
const onPage = await ensureOnSecurityPage(driver);
|
||
expect(onPage).toBe(true);
|
||
|
||
steps.push('检查摄像头布局');
|
||
const source = await driver.getSource();
|
||
logPageSource(source);
|
||
|
||
// Count camera feed elements
|
||
const cameraEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().descriptionContains("camera")');
|
||
const camTextEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().textContains("摄像头")');
|
||
const camEnEls = await driver.findElementsRaw('-android uiautomator', 'new UiSelector().textContains("Camera")');
|
||
const totalCams = new Set([...cameraEls, ...camTextEls, ...camEnEls]).size;
|
||
steps.push(`检测到 ${totalCams} 个摄像头`);
|
||
|
||
if (totalCams === 0) {
|
||
reporter.record('【已开通】安防首页-1~4个摄像头布局', 'SKIP', Date.now() - start, '未检测到摄像头元素');
|
||
return;
|
||
}
|
||
|
||
// Verify layout adapts to camera count (1-4)
|
||
expect(totalCams).toBeGreaterThanOrEqual(1);
|
||
expect(totalCams).toBeLessThanOrEqual(4);
|
||
|
||
steps.push(`${totalCams}个摄像头布局显示正常`);
|
||
reporter.record('【已开通】安防首页-1~4个摄像头布局', 'PASS', Date.now() - start, steps.join(' → '));
|
||
} catch (e: any) {
|
||
const ss = await driver.screenshot().catch(() => '');
|
||
reporter.record('【已开通】安防首页-1~4个摄像头布局', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
});
|