AI_UIAutomation/tests/aihub/aihub_screen_casting.test.ts

810 lines
29 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { execSync } from 'child_process';
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 投屏设置】- Screen Casting Settings 功能覆盖', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
const isAndroid = () => driver.platform === 'android';
const SETTINGS_ICON = () => isAndroid() ? { x: 999, y: 175 } : { x: 361, y: 70 };
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
await robustBeforeAll(driver);
reporter = new TestReporter('AIHub_ScreenCasting', 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()) {
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 ensureAppRunning(): Promise<void> {
if (!isAndroid()) return;
try {
const src = await driver.getSource();
if (src.includes('com.theswitchbot.switchbot') || src.includes('SwitchBot')
|| src.includes('Home') || src.includes('Cameras') || src.includes('AI Events')) return;
} catch { /* app likely crashed */ }
try {
execSync('adb shell am force-stop com.theswitchbot.switchbot');
await sleep(2000);
execSync('adb shell am start -n com.theswitchbot.switchbot/.index.ui.SplashActivity');
await sleep(10000);
await driver.dismissPopupIfPresent();
} catch { /* ignore */ }
}
async function enterHubFunctionPage(): Promise<boolean> {
const src = await driver.getSource();
if (src.includes('Cameras') && src.includes('AI Events')) return true;
await ensureAppRunning();
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');
}
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 enterHubSettings(): Promise<boolean> {
const src = await driver.getSource();
if (src.includes('Motion Detection') || src.includes('Firmware')
|| src.includes('Do Not Disturb') || src.includes('Screen Casting')) {
return true;
}
const inHub = await enterHubFunctionPage();
if (!inHub) return false;
await driver.tap(SETTINGS_ICON().x, SETTINGS_ICON().y);
await sleep(5000);
await waitForLoading();
const settingSrc = await driver.getSource();
return settingSrc.includes('Motion Detection') || settingSrc.includes('Firmware')
|| settingSrc.includes('Do Not Disturb') || settingSrc.includes('Wi-Fi');
}
async function enterScreenCastingPage(): Promise<boolean> {
const steps: string[] = [];
// Check if already on Screen Casting settings page
const curSrc = await driver.getSource();
if (isScreenCastingPage(curSrc)) {
steps.push('已在投屏设置页');
console.log('ScreenCasting nav:', steps.join(' → '));
return true;
}
// If on edit/selection sub-page, go back
if (curSrc.includes('Save') && (curSrc.includes('Select camera') || curSrc.includes('Select Camera'))) {
await exitEditPage();
const afterSrc = await driver.getSource();
if (isScreenCastingPage(afterSrc)) {
steps.push('从选择页返回投屏设置');
console.log('ScreenCasting nav:', steps.join(' → '));
return true;
}
}
// Navigate from Hub settings
const inSettings = await enterHubSettings();
if (!inSettings) {
steps.push('无法进入Hub设置页');
console.log('ScreenCasting nav:', steps.join(' → '));
return false;
}
steps.push('已在Hub设置页');
// Find and tap "Extended Display Settings" (投屏设置的实际入口名)
let scEl: string | null = null;
if (isAndroid()) {
scEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Extended Display Settings")');
if (!scEl) scEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Extended Display")');
if (!scEl) scEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("投屏")');
} else {
scEl = await driver.findElementRaw('predicate string', 'label CONTAINS "Extended Display"');
if (!scEl) scEl = await driver.findElementRaw('predicate string', 'label CONTAINS "投屏"');
}
if (!scEl) {
for (let i = 0; i < 3; i++) {
await driver.scrollDown(300);
await sleep(2000);
if (isAndroid()) {
scEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Extended Display Settings")');
if (!scEl) scEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Extended Display")');
} else {
scEl = await driver.findElementRaw('predicate string', 'label CONTAINS "Extended Display"');
}
if (scEl) break;
}
}
if (!scEl) {
steps.push('未找到Extended Display Settings入口');
console.log('ScreenCasting nav:', steps.join(' → '));
await logPageElements();
return false;
}
await driver.tapElement(scEl);
await sleep(3000);
await waitForLoading();
steps.push('点击Screen Casting');
await driver.dismissPopupIfPresent();
const finalSrc = await driver.getSource();
console.log('ScreenCasting nav:', steps.join(' → '));
return isScreenCastingPage(finalSrc);
}
function isScreenCastingPage(source: string): boolean {
// 投屏设置页: 包含 "Extended Display Settings" + 三种布局模式
return source.includes('Extended Display Settings')
&& (source.includes('Standard Layout') || source.includes('Report Layout') || source.includes('Live'));
}
async function exitEditPage(): Promise<void> {
for (let attempt = 0; attempt < 3; attempt++) {
const curSrc = await driver.getSource();
if (isScreenCastingPage(curSrc)) return;
await driver.goBack();
await sleep(1500);
if (isAndroid()) {
const confirmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
if (confirmEl) { await driver.tapElement(confirmEl); await sleep(2000); return; }
const okEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("OK")');
if (okEl) { await driver.tapElement(okEl); await sleep(2000); return; }
}
}
}
async function getCurrentMode(source?: string): Promise<string> {
const src = source || await driver.getSource();
// Modes: Standard Layout (普通), Report Layout (混合/需AI+), Live (实时)
// 检测当前选中的模式 - 需根据实际UI判断(可能是高亮/选中状态)
// 暂时通过页面包含的模式名来推断
if (src.includes('Standard Layout')) return 'standard';
if (src.includes('Report Layout')) return 'report';
if (src.includes('Live')) return 'live';
return 'unknown';
}
async function selectMode(modeName: string): Promise<boolean> {
let modeEl: string | null = null;
if (isAndroid()) {
// 先尝试通过content-desc定位(因为content-desc包含完整描述)
modeEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${modeName}")`);
if (!modeEl) modeEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().descriptionContains("${modeName}")`);
} else {
modeEl = await driver.findElementRaw('predicate string', `label CONTAINS "${modeName}"`);
}
if (!modeEl) return false;
await driver.tapElement(modeEl);
await sleep(2000);
return true;
}
async function tapSave(): Promise<boolean> {
let saveEl: string | null = null;
if (isAndroid()) {
saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")');
if (!saveEl) saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().description("Save")');
} else {
saveEl = await driver.findElementRaw('predicate string', 'label == "Save"');
}
if (!saveEl) return false;
await driver.tapElement(saveEl);
await sleep(3000);
await waitForLoading();
return true;
}
async function countSelectedCameras(source?: string): Promise<number> {
const src = source || await driver.getSource();
// Count checked/selected cameras - look for checkmarks or selected state
if (isAndroid()) {
const checkedRe = /checked="true"/g;
let count = 0;
let m;
while ((m = checkedRe.exec(src)) !== null) count++;
return count;
}
return 0;
}
// --- 测试用例 ---
// ========== 1. 投屏设置页面显示 ==========
it('1.1 投屏设置页面显示(已绑定摄像头)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
const source = await logPageElements();
// 验证页面包含三种布局模式
const hasStandard = source.includes('Standard Layout');
const hasReport = source.includes('Report Layout');
const hasLive = source.includes('Live');
expect(hasStandard || hasReport || hasLive).toBe(true);
steps.push(`Standard=${hasStandard}, Report=${hasReport}, Live=${hasLive}`);
reporter.record('投屏设置页面显示(已绑定摄像头)', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('投屏设置页面显示(已绑定摄像头)', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it('1.2 投屏设置页面-三种模式选项及描述', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
const source = await driver.getSource();
// Standard Layout描述
const hasStandardDesc = source.includes('Arranges snapshots based on connected camera count');
// Report Layout描述
const hasReportDesc = source.includes('smart reports on the left side');
// Live描述
const hasLiveDesc = source.includes('live feeds from selected camera');
expect(hasStandardDesc).toBe(true);
steps.push(`Standard描述=${hasStandardDesc}, Report描述=${hasReportDesc}, Live描述=${hasLiveDesc}`);
reporter.record('投屏设置页面-模式描述', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('投屏设置页面-模式描述', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it('1.3 投屏设置页面-Report Layout需要AI+服务', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
const source = await driver.getSource();
// Report Layout显示需要AI+服务
const requiresAI = source.includes('AI+ service required') || source.includes('AI+');
expect(requiresAI).toBe(true);
steps.push(`AI+服务提示: ${requiresAI}`);
reporter.record('投屏设置-Report需AI+', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('投屏设置-Report需AI+', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ========== 2. 切换为Report Layout(混合模式) ==========
it('2.1 切换为Report Layout', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 点击Report Layout
const selected = await selectMode('Report Layout');
if (!selected) {
steps.push('未找到Report Layout选项');
await logPageElements();
reporter.record('切换为Report Layout', 'SKIP', Date.now() - start, steps.join(' → ') + ' [条件不满足skip]');
return;
}
steps.push('点击Report Layout');
await sleep(2000);
// 检查是否弹出AI+服务相关提示
const afterSrc = await driver.getSource();
if (afterSrc.includes('subscribe') || afterSrc.includes('Subscribe')
|| afterSrc.includes('service') || afterSrc.includes('enable')) {
steps.push('弹出AI+服务提示(需开通)');
await driver.goBack();
await sleep(2000);
} else {
steps.push('切换成功(AI+已开通)');
}
await logPageElements();
reporter.record('切换为Report Layout', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('切换为Report Layout', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it('2.2 切换为Report LayoutAI+服务开关为关)', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 点击Report Layout
const selected = await selectMode('Report Layout');
if (!selected) {
steps.push('未找到Report Layout选项');
reporter.record('Report Layout(开关关)', 'SKIP', Date.now() - start, steps.join(' → ') + ' [skip]');
return;
}
steps.push('点击Report Layout');
await sleep(2000);
const afterSrc = await driver.getSource();
await logPageElements();
// 记录点击后的状态变化
if (afterSrc.includes('AI+') || afterSrc.includes('service')
|| afterSrc.includes('Subscribe') || afterSrc.includes('enable')) {
steps.push('显示AI+服务相关提示');
// 关闭弹窗/返回
const gotIt = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Got it")');
if (gotIt) { await driver.tapElement(gotIt); await sleep(1000); }
else { await driver.goBack(); await sleep(2000); }
} else {
steps.push('无服务提示(可能已开通)');
}
reporter.record('Report Layout(开关关)', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('Report Layout(开关关)', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ========== 3. 切换为Standard Layout(普通模式) ==========
it('3.1 切换为Standard Layout', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 点击Standard Layout
const selected = await selectMode('Standard Layout');
if (!selected) {
steps.push('未找到Standard Layout选项');
await logPageElements();
expect(false).toBe(true);
return;
}
steps.push('点击Standard Layout');
await sleep(2000);
const afterSrc = await driver.getSource();
await logPageElements();
// 验证切换成功
steps.push('Standard Layout已选中');
reporter.record('切换为Standard Layout', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('切换为Standard Layout', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it('3.2 从Report Layout切换回Standard Layout', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 先切到Report Layout
const toReport = await selectMode('Report Layout');
if (toReport) {
steps.push('先切到Report Layout');
await sleep(2000);
const src = await driver.getSource();
if (src.includes('subscribe') || src.includes('Subscribe') || src.includes('service')) {
steps.push('AI+未开通,无法切到Report');
await driver.goBack();
await sleep(2000);
reporter.record('Report→Standard', 'SKIP', Date.now() - start, steps.join(' → ') + ' [AI+不满足skip]');
return;
}
}
// 切回Standard Layout
const toStandard = await selectMode('Standard Layout');
expect(toStandard).toBe(true);
steps.push('切回Standard Layout');
await sleep(2000);
const afterSrc = await driver.getSource();
steps.push(`页面包含Standard: ${afterSrc.includes('Standard Layout')}`);
reporter.record('Report→Standard', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('Report→Standard', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ========== 4. 切换为Live模式(实时模式) ==========
it('4.1 切换为Live模式', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 点击Live
const selected = await selectMode('Live');
if (!selected) {
steps.push('未找到Live选项');
await logPageElements();
reporter.record('切换为Live模式', 'SKIP', Date.now() - start, steps.join(' → ') + ' [无Live选项skip]');
return;
}
steps.push('点击Live');
await sleep(3000);
// Live模式可能弹出摄像头选择或直接切换
const afterSrc = await driver.getSource();
await logPageElements();
if (afterSrc.includes('Select') || afterSrc.includes('选择')) {
steps.push('进入摄像头选择页');
}
// 返回到投屏设置页
await exitEditPage();
reporter.record('切换为Live模式', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('切换为Live模式', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it('4.2 从Standard切换为Live模式', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 先确保在Standard Layout
await selectMode('Standard Layout');
await sleep(2000);
steps.push('确认Standard模式');
// 切换到Live
const selected = await selectMode('Live');
if (!selected) {
steps.push('未找到Live选项');
reporter.record('Standard→Live', 'SKIP', Date.now() - start, steps.join(' → ') + ' [skip]');
return;
}
steps.push('点击Live');
await sleep(3000);
const afterSrc = await driver.getSource();
await logPageElements();
steps.push('Live模式页面已加载');
// 返回
await exitEditPage();
reporter.record('Standard→Live', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('Standard→Live', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
// ========== 5. 实时模式摄像头选择限制 ==========
it('5.1 Live模式至少选择一个摄像头', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 切换到Live模式
const selected = await selectMode('Live');
if (!selected) {
steps.push('未找到Live选项');
reporter.record('Live至少选1摄像头', 'SKIP', Date.now() - start, steps.join(' → ') + ' [skip]');
return;
}
steps.push('切换到Live模式');
await sleep(3000);
const source = await driver.getSource();
await logPageElements();
if (isAndroid()) {
// 查找所有选中的checkbox
const checkboxes = await driver.findElementsRaw('-android uiautomator',
'new UiSelector().checked(true)');
if (checkboxes && checkboxes.length > 0) {
steps.push(`发现${checkboxes.length}个选中项`);
// 尝试取消全部
for (const cb of checkboxes) {
await driver.tapElement(cb);
await sleep(1000);
}
steps.push('取消全部选中');
await sleep(1000);
// 检查是否出现至少选1个的提示
const afterSrc = await driver.getSource();
const hasWarning = afterSrc.includes('at least') || afterSrc.includes('至少')
|| afterSrc.includes('minimum') || afterSrc.includes('select');
steps.push(`至少1个提示: ${hasWarning}`);
await logPageElements();
// 恢复:选中第一个
const firstCb = await driver.findElementRaw('-android uiautomator',
'new UiSelector().checkable(true).instance(0)');
if (firstCb) {
await driver.tapElement(firstCb);
await sleep(1000);
steps.push('恢复选中第1个');
}
} else {
steps.push('未发现checkbox元素,页面可能不同');
}
}
await exitEditPage();
reporter.record('Live至少选1摄像头', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('Live至少选1摄像头', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it('5.2 Live模式至多选择四个摄像头', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 切换到Live模式
const selected = await selectMode('Live');
if (!selected) {
steps.push('未找到Live选项');
reporter.record('Live至多选4摄像头', 'SKIP', Date.now() - start, steps.join(' → ') + ' [skip]');
return;
}
steps.push('切换到Live模式');
await sleep(3000);
if (isAndroid()) {
const allCheckable = await driver.findElementsRaw('-android uiautomator',
'new UiSelector().checkable(true)');
steps.push(`可选摄像头数: ${allCheckable ? allCheckable.length : 0}`);
if (allCheckable && allCheckable.length > 4) {
// 先选满4个
let selectedCount = 0;
for (const cb of allCheckable) {
const attr = await driver.getElementAttribute(cb, 'checked');
if (attr === 'true') selectedCount++;
}
steps.push(`当前已选: ${selectedCount}`);
if (selectedCount < 4) {
for (const cb of allCheckable) {
if (selectedCount >= 4) break;
const attr = await driver.getElementAttribute(cb, 'checked');
if (attr !== 'true') {
await driver.tapElement(cb);
await sleep(500);
selectedCount++;
}
}
}
// 尝试选第5个
let fifthCb: string | null = null;
for (const cb of allCheckable) {
const attr = await driver.getElementAttribute(cb, 'checked');
if (attr !== 'true') {
fifthCb = cb;
break;
}
}
if (fifthCb) {
await driver.tapElement(fifthCb);
await sleep(1000);
const afterSrc = await driver.getSource();
const hasMaxWarning = afterSrc.includes('maximum') || afterSrc.includes('至多')
|| afterSrc.includes('most') || afterSrc.includes('up to 4')
|| afterSrc.includes('4');
steps.push(`超4个限制提示: ${hasMaxWarning}`);
await logPageElements();
}
} else {
steps.push('摄像头不超过4个,无法测试上限');
}
}
await exitEditPage();
reporter.record('Live至多选4摄像头', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('Live至多选4摄像头', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it('5.3 Live模式取消保存', { timeout: 120000 }, async () => {
const start = Date.now();
const steps: string[] = [];
try {
const onPage = await enterScreenCastingPage();
expect(onPage).toBe(true);
steps.push('进入投屏设置页');
// 记录当前状态
const beforeSrc = await driver.getSource();
steps.push('记录当前状态');
// 切换到Live模式
const selected = await selectMode('Live');
if (!selected) {
steps.push('未找到Live选项');
reporter.record('Live取消保存', 'SKIP', Date.now() - start, steps.join(' → ') + ' [skip]');
return;
}
steps.push('点击Live');
await sleep(2000);
// 不保存,直接退出(goBack → Confirm退出)
await exitEditPage();
steps.push('退出不保存');
// 验证回到投屏设置页且模式未变
await sleep(2000);
const afterSrc = await driver.getSource();
if (afterSrc.includes('Extended Display Settings')) {
steps.push('已回到投屏设置页');
}
reporter.record('Live取消保存', 'PASS', Date.now() - start, steps.join(' → '));
} catch (e: any) {
const ss = await captureScreenshot();
reporter.record('Live取消保存', 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});