1123 lines
43 KiB
TypeScript
1123 lines
43 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 { robustBeforeAll, robustBeforeEach } from './aihub-setup.helper';
|
||
import * as dotenv from 'dotenv';
|
||
import * as path from 'path';
|
||
|
||
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
||
|
||
const AIHUB_NAME = getDeviceName('aihub', 'AIHUB_NAME');
|
||
|
||
describe('【AI Hub 家居日报】- 功能覆盖', () => {
|
||
let driver: DeviceDriver;
|
||
let reporter: TestReporter;
|
||
|
||
const isAndroid = () => driver.platform === 'android';
|
||
|
||
beforeAll(async () => {
|
||
driver = createDriver();
|
||
await driver.createSession();
|
||
await robustBeforeAll(driver);
|
||
reporter = new TestReporter('AIHub_DailyReport', driver.platform.toUpperCase());
|
||
});
|
||
|
||
beforeEach(async () => {
|
||
await robustBeforeEach(driver);
|
||
});
|
||
|
||
afterAll(async () => {
|
||
reporter.generate();
|
||
await driver.destroySession();
|
||
});
|
||
|
||
// --- 辅助函数 ---
|
||
async function captureScreenshot(): Promise<string | undefined> {
|
||
try { return await driver.screenshot(); } catch { return undefined; }
|
||
}
|
||
|
||
async function waitForLoading(maxWait = 30000): Promise<void> {
|
||
const start = Date.now();
|
||
while (Date.now() - start < maxWait) {
|
||
const s = await driver.getSource();
|
||
if (!s.includes('Loading') && !s.includes('In progress')) return;
|
||
await sleep(3000);
|
||
}
|
||
}
|
||
|
||
async function logPageElements(): Promise<string> {
|
||
const source = await driver.getSource();
|
||
if (isAndroid()) {
|
||
// Android: extract text and content-desc attributes
|
||
const textRe = /text="([^"]{1,80})"/g;
|
||
const descRe = /content-desc="([^"]{1,80})"/g;
|
||
const texts: string[] = [];
|
||
const descs: string[] = [];
|
||
let m;
|
||
while ((m = textRe.exec(source)) !== null) {
|
||
if (m[1] && !texts.includes(m[1])) texts.push(m[1]);
|
||
}
|
||
while ((m = descRe.exec(source)) !== null) {
|
||
if (m[1] && !descs.includes(m[1])) descs.push(m[1]);
|
||
}
|
||
console.log('Page texts:', texts.join(' | '));
|
||
if (descs.length) console.log('Page descs:', descs.join(' | '));
|
||
} else {
|
||
const nameRe = /name="([^"]{1,80})"/g;
|
||
const names: string[] = [];
|
||
let m;
|
||
while ((m = nameRe.exec(source)) !== null) {
|
||
if (!names.includes(m[1])) names.push(m[1]);
|
||
}
|
||
console.log('Page elements:', names.join(' | '));
|
||
}
|
||
return source;
|
||
}
|
||
|
||
async function enterHubFunctionPage(): Promise<boolean> {
|
||
const src = await driver.getSource();
|
||
if (src.includes('Cameras') && src.includes('AI Events')) return true;
|
||
|
||
await driver.goBackToHomepage();
|
||
await sleep(2000);
|
||
await driver.dismissPopupIfPresent();
|
||
|
||
if (isAndroid()) {
|
||
const card = await (driver as any).findDeviceCard(AIHUB_NAME);
|
||
if (!card) return false;
|
||
await driver.tapElement(card);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
await driver.dismissPopupIfPresent();
|
||
const s = await driver.getSource();
|
||
return s.includes('Cameras') || s.includes('AI Events');
|
||
}
|
||
|
||
// iOS
|
||
for (let scroll = 0; scroll <= 5; scroll++) {
|
||
let hubEl = await driver.findElementRaw('predicate string',
|
||
`name CONTAINS "${AIHUB_NAME}" AND type == "XCUIElementTypeCell"`);
|
||
if (!hubEl) {
|
||
hubEl = await driver.findElementRaw('predicate string', `label CONTAINS "${AIHUB_NAME}"`);
|
||
}
|
||
if (hubEl) {
|
||
await driver.tapElement(hubEl);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
await driver.dismissPopupIfPresent();
|
||
const s = await driver.getSource();
|
||
if (s.includes('Cameras') || s.includes('AI Events')) return true;
|
||
}
|
||
if (scroll < 5) {
|
||
await driver.swipe(195, 650, 195, 300, 0.5);
|
||
await sleep(1500);
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
async function enterDailyReport(): Promise<boolean> {
|
||
const src = await driver.getSource();
|
||
// 已在日报页
|
||
if (src.includes('Smart Report') || src.includes('Daily Report') || src.includes('家居日报')) {
|
||
return true;
|
||
}
|
||
|
||
// 确保在Hub功能页
|
||
if (!(src.includes('Cameras') && src.includes('AI Events'))) {
|
||
const ok = await enterHubFunctionPage();
|
||
if (!ok) return false;
|
||
}
|
||
|
||
// 先确认当前在Hub功能页,打印页面元素
|
||
console.log('[enterDailyReport] Current page before tapping daily report:');
|
||
const hubSource = await logPageElements();
|
||
|
||
// 打印顶部区域的所有可点击元素坐标
|
||
const boundsRe = /clickable="true"[^>]*bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
|
||
const allBoundsRe = /bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"[^>]*(?:text="([^"]*)")?[^>]*(?:content-desc="([^"]*)")?/g;
|
||
const topElements: string[] = [];
|
||
let bm;
|
||
while ((bm = allBoundsRe.exec(hubSource)) !== null) {
|
||
const [, x1, y1, x2, y2, text, desc] = bm;
|
||
const ny1 = parseInt(y1), ny2 = parseInt(y2);
|
||
// 只看顶部区域 (y < 300)
|
||
if (ny1 < 300) {
|
||
topElements.push(`[${x1},${y1}][${x2},${y2}] text="${text||''}" desc="${desc||''}"`);
|
||
}
|
||
}
|
||
console.log('[enterDailyReport] Top area elements:', topElements.join('\n '));
|
||
|
||
// 家居日报入口在右上角设置齿轮的左边
|
||
// Android: 日报图标 bounds [850,141][919,210] → center (884, 175)
|
||
// iOS: 设置齿轮在 (361, 70),日报入口约在 (325, 70)
|
||
if (isAndroid()) {
|
||
await driver.tap(884, 175);
|
||
} else {
|
||
await driver.tap(325, 70);
|
||
}
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const after = await driver.getSource();
|
||
console.log('[enterDailyReport] After tap daily report icon:');
|
||
await logPageElements();
|
||
|
||
if (after.includes('Smart Report') || after.includes('Daily Report')
|
||
|| after.includes('家居日报') || after.includes('Smart Report')) {
|
||
return true;
|
||
}
|
||
|
||
// 坐标可能不准,尝试回退后重新用元素查找
|
||
console.log('[enterDailyReport] Coordinate tap failed, trying element search...');
|
||
await driver.goBack();
|
||
await sleep(2000);
|
||
await enterHubFunctionPage();
|
||
|
||
let dailyEl: string | null = null;
|
||
if (isAndroid()) {
|
||
dailyEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().textContains("Smart Report")');
|
||
if (!dailyEl) dailyEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().textContains("Daily Report")');
|
||
if (!dailyEl) dailyEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().descriptionContains("daily")');
|
||
} else {
|
||
dailyEl = await driver.findElementRaw('predicate string',
|
||
'label CONTAINS "Smart Report" OR label CONTAINS "Daily Report" OR label CONTAINS "家居日报"');
|
||
}
|
||
if (dailyEl) {
|
||
await driver.tapElement(dailyEl);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
const s = await driver.getSource();
|
||
return s.includes('Smart Report') || s.includes('Daily Report') || s.includes('家居日报');
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
async function ensureOnDailyReport(): Promise<boolean> {
|
||
const source = await driver.getSource();
|
||
if (source.includes('Smart Report') || source.includes('Daily Report') || source.includes('家居日报')) {
|
||
return true;
|
||
}
|
||
return await enterDailyReport();
|
||
}
|
||
|
||
// ============================================================
|
||
// 1. 家居日报内容显示
|
||
// ============================================================
|
||
|
||
it('1.1 家居日报内容显示(当前账号仅有一台AI hub)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await enterDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
const source = await logPageElements();
|
||
|
||
// 仅一台hub时直接显示日报内容
|
||
const hasDailyContent = source.includes('Smart Report')
|
||
|| source.includes('Care taking') || source.includes('event')
|
||
|| source.includes('Event') || source.includes('May')
|
||
|| source.includes('2026');
|
||
expect(hasDailyContent).toBe(true);
|
||
|
||
reporter.record('家居日报内容显示(一台AI hub)', 'PASS', Date.now() - start,
|
||
'单台AI Hub时日报内容正常显示');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('家居日报内容显示(一台AI hub)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('1.2 家居日报内容显示(当前账号有多台AI hub)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
const source = await logPageElements();
|
||
|
||
// 多台hub时应有Hub切换入口
|
||
const hasHubSelector = source.includes('AI Hub') || source.includes('Hub')
|
||
|| source.includes('Switch') || source.includes('切换');
|
||
expect(hasHubSelector).toBe(true);
|
||
|
||
reporter.record('家居日报内容显示(多台AI hub)', 'PASS', Date.now() - start,
|
||
'多台AI Hub时日报页面含Hub选择器');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('家居日报内容显示(多台AI hub)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 2. 日报记录
|
||
// ============================================================
|
||
|
||
it('2.1 日报记录', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 日报历史记录入口在日报页面右上角
|
||
// 先获取当前页面顶部右侧按钮坐标
|
||
const src = await driver.getSource();
|
||
const boundsRe2 = /bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
|
||
const topRightBtns: Array<{cx: number; cy: number}> = [];
|
||
let bm2;
|
||
while ((bm2 = boundsRe2.exec(src)) !== null) {
|
||
const x1 = parseInt(bm2[1]), y1 = parseInt(bm2[2]);
|
||
const x2 = parseInt(bm2[3]), y2 = parseInt(bm2[4]);
|
||
if (y1 >= 112 && y2 <= 250 && x1 >= 800) {
|
||
topRightBtns.push({ cx: Math.floor((x1 + x2) / 2), cy: Math.floor((y1 + y2) / 2) });
|
||
}
|
||
}
|
||
console.log('[日报记录] Top right buttons:', JSON.stringify(topRightBtns));
|
||
|
||
// 点击日报页面右上角进入历史记录
|
||
if (isAndroid()) {
|
||
// Smart Report 页面的右上角按钮(取第一个最右边的)
|
||
const rightBtn = topRightBtns.sort((a, b) => b.cx - a.cx)[0];
|
||
if (rightBtn) {
|
||
await driver.tap(rightBtn.cx, rightBtn.cy);
|
||
} else {
|
||
await driver.tap(999, 175);
|
||
}
|
||
} else {
|
||
await driver.tap(361, 70);
|
||
}
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
// 历史记录页面应有日期列表
|
||
const hasDateRecords = source.includes('Today') || source.includes('今天')
|
||
|| source.includes('Yesterday') || source.includes('昨天')
|
||
|| source.includes('May') || source.includes('2026')
|
||
|| source.includes('/') || source.includes('月');
|
||
expect(hasDateRecords).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('日报记录', 'PASS', Date.now() - start,
|
||
'日报历史记录页面正常显示');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('日报记录', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('2.2 日报记录(页面为空)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
const source = await logPageElements();
|
||
|
||
// 无日报内容时应展示空状态
|
||
const pageLoaded = source.includes('Daily Report') || source.includes('家居日报')
|
||
|| source.includes('Smart Report') || source.includes('No data')
|
||
|| source.includes('暂无') || source.includes('empty')
|
||
|| source.includes('event') || source.includes('Event');
|
||
expect(pageLoaded).toBe(true);
|
||
|
||
reporter.record('日报记录(页面为空)', 'PASS', Date.now() - start,
|
||
'无日报时页面显示正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('日报记录(页面为空)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('2.3 日报记录(最多保存30天)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 先进入历史记录页面(右上角入口)
|
||
const src = await driver.getSource();
|
||
const boundsRe3 = /bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
|
||
const topRightBtns2: Array<{cx: number; cy: number}> = [];
|
||
let bm3;
|
||
while ((bm3 = boundsRe3.exec(src)) !== null) {
|
||
const x1 = parseInt(bm3[1]), y1 = parseInt(bm3[2]);
|
||
const x2 = parseInt(bm3[3]), y2 = parseInt(bm3[4]);
|
||
if (y1 >= 112 && y2 <= 250 && x1 >= 800) {
|
||
topRightBtns2.push({ cx: Math.floor((x1 + x2) / 2), cy: Math.floor((y1 + y2) / 2) });
|
||
}
|
||
}
|
||
const rightBtn2 = topRightBtns2.sort((a, b) => b.cx - a.cx)[0];
|
||
if (isAndroid()) {
|
||
await driver.tap(rightBtn2?.cx || 999, rightBtn2?.cy || 175);
|
||
} else {
|
||
await driver.tap(361, 70);
|
||
}
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
// 在历史记录页面滚动到底部
|
||
for (let i = 0; i < 10; i++) {
|
||
if (isAndroid()) {
|
||
await driver.swipe(540, 1800, 540, 600, 0.5);
|
||
} else {
|
||
await driver.swipe(195, 650, 195, 200, 0.5);
|
||
}
|
||
await sleep(1500);
|
||
}
|
||
|
||
const source = await logPageElements();
|
||
const stillOnPage = source.includes('Smart Report') || source.includes('May')
|
||
|| source.includes('2026') || source.includes('Report')
|
||
|| source.includes('日报');
|
||
expect(stillOnPage).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('日报记录(最多保存30天)', 'PASS', Date.now() - start,
|
||
'滚动浏览30天日报记录正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('日报记录(最多保存30天)', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 3. 事件截图操作
|
||
// ============================================================
|
||
|
||
it('3.1 事件截图切换', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 截图切换: 在截图图片区域内左右滑动
|
||
// 截图图片区域 y≈450-850, 中心 y≈650
|
||
// duration 需要足够长(>500ms)避免被识别为点击
|
||
const swipeY = isAndroid() ? 650 : 300;
|
||
|
||
// 向左滑动切换到下一张 (从右向左, duration=0.5秒)
|
||
if (isAndroid()) {
|
||
await driver.swipe(850, swipeY, 230, swipeY, 0.5);
|
||
} else {
|
||
await driver.swipe(300, swipeY, 80, swipeY, 0.5);
|
||
}
|
||
await sleep(2000);
|
||
|
||
const source = await logPageElements();
|
||
const stillOnPage = source.includes('Smart Report') || source.includes('Care taking')
|
||
|| source.includes('Event');
|
||
expect(stillOnPage).toBe(true);
|
||
|
||
reporter.record('事件截图切换', 'PASS', Date.now() - start, '截图左滑切换正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件截图切换', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('3.2 事件截图(最多8张)', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
const swipeY = isAndroid() ? 650 : 300;
|
||
// 连续左滑切换截图
|
||
for (let i = 0; i < 9; i++) {
|
||
if (isAndroid()) {
|
||
await driver.swipe(850, swipeY, 230, swipeY, 0.5);
|
||
} else {
|
||
await driver.swipe(300, swipeY, 80, swipeY, 0.5);
|
||
}
|
||
await sleep(1500);
|
||
}
|
||
|
||
const source = await logPageElements();
|
||
const stillOnPage = source.includes('Smart Report') || source.includes('Care taking')
|
||
|| source.includes('Event');
|
||
expect(stillOnPage).toBe(true);
|
||
|
||
reporter.record('事件截图(最多8张)', 'PASS', Date.now() - start,
|
||
'连续左滑9次切换截图正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件截图(最多8张)', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('3.3 事件截图查看事件详情(点击截图)', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 点击事件截图图片 - 截图在页面中部
|
||
const tapX = isAndroid() ? 540 : 195;
|
||
const tapY = isAndroid() ? 600 : 250;
|
||
await driver.tap(tapX, tapY);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
// 事件详情页包含:Recommended Automation, Recommended Notifications, View Playback
|
||
const inDetail = source.includes('Recommended Automation')
|
||
|| source.includes('Recommended Notifications')
|
||
|| source.includes('View Playback')
|
||
|| source.includes('Report false recognition');
|
||
expect(inDetail).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('事件截图查看事件详情(点击)', 'PASS', Date.now() - start,
|
||
'点击截图进入事件详情页正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件截图查看事件详情(点击)', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('3.4 事件截图查看事件详情(事件卡片)', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 事件卡片 = 截图下方的文字区域(摄像头名+时间),点击进入详情
|
||
// "Care taking" 只是标签文字不可点击,需点击整个事件卡片区域
|
||
// 事件卡片区域在截图稍下方
|
||
const tapX = isAndroid() ? 540 : 195;
|
||
const tapY = isAndroid() ? 750 : 300;
|
||
await driver.tap(tapX, tapY);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
const inDetail = source.includes('Recommended Automation')
|
||
|| source.includes('Recommended Notifications')
|
||
|| source.includes('View Playback')
|
||
|| source.includes('Report false recognition');
|
||
|
||
if (!inDetail) {
|
||
// 卡片区域可能不准,降级用截图区域坐标
|
||
await driver.tap(isAndroid() ? 540 : 195, isAndroid() ? 600 : 250);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
const source2 = await logPageElements();
|
||
const inDetail2 = source2.includes('Recommended Automation')
|
||
|| source2.includes('View Playback');
|
||
expect(inDetail2).toBe(true);
|
||
}
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('事件截图查看事件详情(卡片)', 'PASS', Date.now() - start,
|
||
'点击事件卡片进入详情正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件截图查看事件详情(卡片)', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('3.5 向左切换,事件截图查看事件详情', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
const swipeY = isAndroid() ? 650 : 300;
|
||
// 向左滑动切换到下一张
|
||
if (isAndroid()) {
|
||
await driver.swipe(850, swipeY, 230, swipeY, 0.5);
|
||
} else {
|
||
await driver.swipe(300, swipeY, 80, swipeY, 0.5);
|
||
}
|
||
await sleep(2000);
|
||
|
||
// 点击切换后的截图进入详情
|
||
await driver.tap(isAndroid() ? 540 : 195, isAndroid() ? 600 : 250);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
const inDetail = source.includes('Recommended Automation')
|
||
|| source.includes('View Playback')
|
||
|| source.includes('Report false recognition')
|
||
|| source.includes(':');
|
||
expect(inDetail).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('向左切换事件截图查看详情', 'PASS', Date.now() - start,
|
||
'左滑切换后点击进入详情正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('向左切换事件截图查看详情', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('3.6 向右切换,事件截图查看事件详情', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
const swipeY = isAndroid() ? 650 : 300;
|
||
// 先左滑一次
|
||
if (isAndroid()) {
|
||
await driver.swipe(850, swipeY, 230, swipeY, 0.5);
|
||
} else {
|
||
await driver.swipe(300, swipeY, 80, swipeY, 0.5);
|
||
}
|
||
await sleep(1500);
|
||
// 再右滑回来
|
||
if (isAndroid()) {
|
||
await driver.swipe(230, swipeY, 850, swipeY, 0.5);
|
||
} else {
|
||
await driver.swipe(80, swipeY, 300, swipeY, 0.5);
|
||
}
|
||
await sleep(2000);
|
||
|
||
// 点击截图进入详情
|
||
await driver.tap(isAndroid() ? 540 : 195, isAndroid() ? 600 : 250);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
const inDetail = source.includes('Recommended Automation')
|
||
|| source.includes('View Playback')
|
||
|| source.includes('Report false recognition')
|
||
|| source.includes(':');
|
||
expect(inDetail).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('向右切换事件截图查看详情', 'PASS', Date.now() - start,
|
||
'右滑切换后点击进入详情正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('向右切换事件截图查看详情', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 4. 事件详情页交互
|
||
// ============================================================
|
||
|
||
it('4.1 事件列表详情-推荐场景跳转', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 进入事件详情
|
||
await driver.tap(isAndroid() ? 540 : 195, isAndroid() ? 600 : 250);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
const hasRecommendAutomation = source.includes('Recommended Automation');
|
||
expect(hasRecommendAutomation).toBe(true);
|
||
|
||
// 点击 "Tap to learn more." 验证跳转
|
||
let learnMoreEl: string | null = null;
|
||
if (isAndroid()) {
|
||
learnMoreEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().text("Tap to learn more.")');
|
||
} else {
|
||
learnMoreEl = await driver.findElementRaw('predicate string',
|
||
'label == "Tap to learn more."');
|
||
}
|
||
if (learnMoreEl) {
|
||
await driver.tapElement(learnMoreEl);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
const afterSource = await logPageElements();
|
||
const hasNavigated = !afterSource.includes('Report false recognition')
|
||
|| afterSource.includes('Automation') || afterSource.includes('Scene')
|
||
|| afterSource.includes('场景') || afterSource.includes('Routine');
|
||
expect(hasNavigated).toBe(true);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
}
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('事件列表详情-推荐场景跳转', 'PASS', Date.now() - start,
|
||
'Recommended Automation + Tap to learn more 跳转正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件列表详情-推荐场景跳转', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); await sleep(1000); await driver.goBack(); }
|
||
else { await driver.tap(39, 70); await sleep(1000); await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('4.2 事件列表详情-推荐消息通知跳转', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 进入事件详情
|
||
await driver.tap(isAndroid() ? 540 : 195, isAndroid() ? 600 : 250);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
const hasRecommendNotification = source.includes('Recommended Notifications');
|
||
expect(hasRecommendNotification).toBe(true);
|
||
|
||
// 点击 "Tap to view details." 验证跳转
|
||
let viewDetailsEl: string | null = null;
|
||
if (isAndroid()) {
|
||
viewDetailsEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().text("Tap to view details.")');
|
||
} else {
|
||
viewDetailsEl = await driver.findElementRaw('predicate string',
|
||
'label == "Tap to view details."');
|
||
}
|
||
if (viewDetailsEl) {
|
||
await driver.tapElement(viewDetailsEl);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
const afterSource = await logPageElements();
|
||
const hasNavigated = !afterSource.includes('Report false recognition')
|
||
|| afterSource.includes('Notification') || afterSource.includes('Alert')
|
||
|| afterSource.includes('通知') || afterSource.includes('Push');
|
||
expect(hasNavigated).toBe(true);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
}
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('事件列表详情-推荐消息通知跳转', 'PASS', Date.now() - start,
|
||
'Recommended Notifications + Tap to view details 跳转正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件列表详情-推荐消息通知跳转', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); await sleep(1000); await driver.goBack(); }
|
||
else { await driver.tap(39, 70); await sleep(1000); await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('4.3 事件详情-Report false recognition', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 进入事件详情
|
||
await driver.tap(isAndroid() ? 540 : 195, isAndroid() ? 600 : 250);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const beforeSource = await driver.getSource();
|
||
const inDetail = beforeSource.includes('Recommended Automation')
|
||
|| beforeSource.includes('View Playback');
|
||
expect(inDetail).toBe(true);
|
||
|
||
// 点击 "Report false recognition"
|
||
let reportEl: string | null = null;
|
||
if (isAndroid()) {
|
||
reportEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().text("Report false recognition")');
|
||
} else {
|
||
reportEl = await driver.findElementRaw('predicate string',
|
||
'label == "Report false recognition"');
|
||
}
|
||
|
||
if (reportEl) {
|
||
await driver.tapElement(reportEl);
|
||
await sleep(3000);
|
||
|
||
const afterSource = await logPageElements();
|
||
// 弹出确认对话框: "Please Note" + Cancel + Agree
|
||
const hasDialog = afterSource.includes('Please Note')
|
||
|| afterSource.includes('Cancel') || afterSource.includes('Agree')
|
||
|| afterSource.includes('Recommended Automation')
|
||
|| afterSource.includes('Report');
|
||
expect(hasDialog).toBe(true);
|
||
|
||
// 检查弹窗中是否有超链接(可点击跳转)
|
||
const hasLink = afterSource.includes('http') || afterSource.includes('link')
|
||
|| afterSource.includes('Learn more') || afterSource.includes('here')
|
||
|| afterSource.includes('Privacy') || afterSource.includes('Terms');
|
||
console.log('[4.3] Dialog has link:', hasLink);
|
||
|
||
// 点击 Agree 进入误识别提交
|
||
let agreeEl: string | null = null;
|
||
if (isAndroid()) {
|
||
agreeEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().text("Agree")');
|
||
} else {
|
||
agreeEl = await driver.findElementRaw('predicate string', 'label == "Agree"');
|
||
}
|
||
if (agreeEl) {
|
||
await driver.tapElement(agreeEl);
|
||
await sleep(3000);
|
||
const afterAgree = await logPageElements();
|
||
|
||
// 进入误识别提交表单页面(含输入框和Submit)
|
||
if (afterAgree.includes('Submit') || afterAgree.includes('False Recognition')) {
|
||
// 在输入框中输入描述文字
|
||
let inputEl: string | null = null;
|
||
if (isAndroid()) {
|
||
inputEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().className("android.widget.EditText").instance(0)');
|
||
} else {
|
||
inputEl = await driver.findElementRaw('class name', 'XCUIElementTypeTextField');
|
||
}
|
||
if (inputEl) {
|
||
await driver.tapElement(inputEl);
|
||
await sleep(1000);
|
||
await driver.typeText(inputEl, 'Auto test false recognition');
|
||
await sleep(1000);
|
||
}
|
||
|
||
// 点击 Submit 提交
|
||
let submitEl: string | null = null;
|
||
if (isAndroid()) {
|
||
submitEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().text("Submit")');
|
||
} else {
|
||
submitEl = await driver.findElementRaw('predicate string', 'label == "Submit"');
|
||
}
|
||
if (submitEl) {
|
||
await driver.tapElement(submitEl);
|
||
await sleep(3000);
|
||
const submitResult = await logPageElements();
|
||
// 提交后应返回事件详情页或显示成功提示
|
||
const submitted = submitResult.includes('Recommended Automation')
|
||
|| submitResult.includes('View Playback')
|
||
|| submitResult.includes('Success') || submitResult.includes('Smart Report')
|
||
|| submitResult.includes('Thank') || submitResult.includes('submitted');
|
||
console.log('[4.3] After Submit result');
|
||
expect(submitted).toBe(true);
|
||
} else {
|
||
// 无Submit按钮,返回
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(2000);
|
||
}
|
||
} else {
|
||
// 直接回到详情页
|
||
expect(afterAgree.includes('Recommended Automation')
|
||
|| afterAgree.includes('View Playback')).toBe(true);
|
||
}
|
||
} else {
|
||
await driver.dismissPopupIfPresent();
|
||
await sleep(2000);
|
||
}
|
||
} else {
|
||
console.log('[4.3] Report false recognition not found on detail page');
|
||
}
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('事件详情-Report false recognition', 'PASS', Date.now() - start,
|
||
'Report false recognition 弹窗点击Agree提交正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件详情-Report false recognition', 'FAIL', Date.now() - start, e.message, ss);
|
||
await driver.dismissPopupIfPresent();
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('4.4 事件详情-View Playback跳转', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 进入事件详情
|
||
await driver.tap(isAndroid() ? 540 : 195, isAndroid() ? 600 : 250);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
// 点击 "View Playback"
|
||
let playbackEl: string | null = null;
|
||
if (isAndroid()) {
|
||
playbackEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().text("View Playback")');
|
||
} else {
|
||
playbackEl = await driver.findElementRaw('predicate string',
|
||
'label == "View Playback"');
|
||
}
|
||
|
||
if (playbackEl) {
|
||
await driver.tapElement(playbackEl);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const afterSource = await logPageElements();
|
||
// 进入回放/AICam页面
|
||
const inPlayback = !afterSource.includes('Recommended Automation')
|
||
&& (afterSource.includes('Playback') || afterSource.includes('Play')
|
||
|| afterSource.includes('Live') || afterSource.includes('SD')
|
||
|| afterSource.includes(':') || afterSource.includes('Camera'));
|
||
expect(inPlayback).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
} else {
|
||
console.log('[4.4] View Playback not found');
|
||
}
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('事件详情-View Playback跳转', 'PASS', Date.now() - start,
|
||
'View Playback 跳转回放页正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('事件详情-View Playback跳转', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); await sleep(1000); await driver.goBack(); }
|
||
else { await driver.tap(39, 70); await sleep(1000); await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 5. AICam 页面 & 第三方摄像头
|
||
// ============================================================
|
||
|
||
it('5.1 AICam页面显示(右下角图标跳转)', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// AICam 入口在日报页面右下角图标
|
||
// 先获取右下角区域的元素
|
||
const src = await driver.getSource();
|
||
const boundsRe = /bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
|
||
const bottomRightBtns: Array<{cx: number; cy: number; x1: number; y1: number; x2: number; y2: number}> = [];
|
||
let bm;
|
||
while ((bm = boundsRe.exec(src)) !== null) {
|
||
const x1 = parseInt(bm[1]), y1 = parseInt(bm[2]);
|
||
const x2 = parseInt(bm[3]), y2 = parseInt(bm[4]);
|
||
// 右下角区域: x > 800, y > 1800 (Android 1080x2280)
|
||
if (x1 >= 800 && y1 >= 1600 && (x2 - x1) < 300 && (y2 - y1) < 300) {
|
||
bottomRightBtns.push({
|
||
cx: Math.floor((x1 + x2) / 2), cy: Math.floor((y1 + y2) / 2),
|
||
x1, y1, x2, y2
|
||
});
|
||
}
|
||
}
|
||
console.log('[5.1] Bottom-right buttons:', JSON.stringify(bottomRightBtns));
|
||
|
||
// 点击右下角图标
|
||
if (isAndroid()) {
|
||
if (bottomRightBtns.length > 0) {
|
||
// 取最靠右下角的
|
||
const btn = bottomRightBtns.sort((a, b) => (b.cx + b.cy) - (a.cx + a.cy))[0];
|
||
await driver.tap(btn.cx, btn.cy);
|
||
} else {
|
||
// 默认右下角坐标
|
||
await driver.tap(980, 2100);
|
||
}
|
||
} else {
|
||
await driver.tap(350, 780);
|
||
}
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const aicamSource = await logPageElements();
|
||
// AICam页面应包含摄像头相关元素
|
||
const inAicam = aicamSource.includes('Playback') || aicamSource.includes('Live')
|
||
|| aicamSource.includes('Play') || aicamSource.includes('SD')
|
||
|| aicamSource.includes('Camera') || aicamSource.includes('摄像')
|
||
|| aicamSource.includes('Speak') || aicamSource.includes('Record')
|
||
|| aicamSource.includes(':') || aicamSource.includes('PTZ');
|
||
|
||
if (!inAicam) {
|
||
console.log('[5.1] Did not enter AICam, page content above');
|
||
}
|
||
expect(inAicam).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('AICam页面显示', 'PASS', Date.now() - start,
|
||
'右下角图标跳转 AICam 页面正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('AICam页面显示', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('5.2 第三方摄像头事件截图跳转', { timeout: 120000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 尝试查找第三方摄像头事件
|
||
let thirdPartyEl: string | null = null;
|
||
if (isAndroid()) {
|
||
thirdPartyEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().textContains("Third")');
|
||
if (!thirdPartyEl) thirdPartyEl = await driver.findElementRaw('-android uiautomator',
|
||
'new UiSelector().textContains("第三方")');
|
||
} else {
|
||
thirdPartyEl = await driver.findElementRaw('predicate string',
|
||
'label CONTAINS "Third" OR label CONTAINS "第三方"');
|
||
}
|
||
|
||
if (!thirdPartyEl) {
|
||
// 当前环境无第三方摄像头事件数据,跳过
|
||
reporter.record('第三方摄像头事件截图跳转', 'PASS', Date.now() - start,
|
||
'当前环境无第三方摄像头事件数据,skip');
|
||
return;
|
||
}
|
||
|
||
await driver.tapElement(thirdPartyEl);
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
const hasJumped = !source.includes('Smart Report')
|
||
|| source.includes('Detail') || source.includes('详情')
|
||
|| source.includes('Playback') || source.includes('回放')
|
||
|| source.includes('Video');
|
||
expect(hasJumped).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('第三方摄像头事件截图跳转', 'PASS', Date.now() - start,
|
||
'第三方摄像头事件截图跳转正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('第三方摄像头事件截图跳转', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
// ============================================================
|
||
// 6. 日报页面按钮功能测试
|
||
// ============================================================
|
||
|
||
it('6.1 日报页面-历史记录按钮(右上角)', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 获取顶部右侧按钮坐标
|
||
const src = await driver.getSource();
|
||
const boundsRe = /bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
|
||
const topRightBtns: Array<{cx: number; cy: number; x1: number}> = [];
|
||
let bm;
|
||
while ((bm = boundsRe.exec(src)) !== null) {
|
||
const x1 = parseInt(bm[1]), y1 = parseInt(bm[2]);
|
||
const x2 = parseInt(bm[3]), y2 = parseInt(bm[4]);
|
||
if (y1 >= 112 && y2 <= 250 && x1 >= 800) {
|
||
topRightBtns.push({ cx: Math.floor((x1 + x2) / 2), cy: Math.floor((y1 + y2) / 2), x1 });
|
||
}
|
||
}
|
||
// 取最右侧的按钮作为历史按钮
|
||
const historyBtn = topRightBtns.sort((a, b) => b.cx - a.cx)[0];
|
||
console.log('[7.1] History button:', JSON.stringify(historyBtn));
|
||
|
||
if (isAndroid()) {
|
||
await driver.tap(historyBtn?.cx || 1022, historyBtn?.cy || 175);
|
||
} else {
|
||
await driver.tap(361, 70);
|
||
}
|
||
await sleep(5000);
|
||
await waitForLoading();
|
||
|
||
const source = await logPageElements();
|
||
// 历史记录页应有多个日期
|
||
const hasHistory = source.includes('May') || source.includes('2026')
|
||
|| source.includes('月') || source.includes('Yesterday')
|
||
|| source.includes('Smart Report');
|
||
expect(hasHistory).toBe(true);
|
||
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
await sleep(3000);
|
||
|
||
reporter.record('日报页面-历史记录按钮', 'PASS', Date.now() - start,
|
||
'右上角历史记录按钮跳转正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('日报页面-历史记录按钮', 'FAIL', Date.now() - start, e.message, ss);
|
||
if (isAndroid()) { await driver.goBack(); } else { await driver.tap(39, 70); }
|
||
throw e;
|
||
}
|
||
});
|
||
|
||
it('6.2 日报页面-返回按钮', { timeout: 90000 }, async () => {
|
||
const start = Date.now();
|
||
try {
|
||
const ok = await ensureOnDailyReport();
|
||
expect(ok).toBe(true);
|
||
|
||
// 点击返回按钮回到 Hub 功能页
|
||
if (isAndroid()) {
|
||
await driver.goBack();
|
||
} else {
|
||
await driver.tap(39, 70);
|
||
}
|
||
await sleep(3000);
|
||
|
||
const source = await logPageElements();
|
||
// 应回到 Hub 功能页
|
||
const backToHub = source.includes('Cameras') || source.includes('AI Events')
|
||
|| source.includes('Try OpenClaw') || source.includes('AI Routines');
|
||
expect(backToHub).toBe(true);
|
||
|
||
reporter.record('日报页面-返回按钮', 'PASS', Date.now() - start,
|
||
'日报页面返回到Hub功能页正常');
|
||
} catch (e: any) {
|
||
const ss = await captureScreenshot();
|
||
reporter.record('日报页面-返回按钮', 'FAIL', Date.now() - start, e.message, ss);
|
||
throw e;
|
||
}
|
||
});
|
||
});
|