AI_UIAutomation/tests/lock/lock_card.test.ts

123 lines
5.3 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 { sleep, onesCtrl, setupResilientHooks } from '../../utils/common';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
// 覆盖 Lock + Lock Pro 两台:首页卡片解锁/上锁,各自按型号取 ONES 锚点。
const LOCKS = [
{ name: process.env.LOCK_DEVICE || 'Lock 6D', model: 'Lock 6F' }, // 普通 Lock → 15975#5YEfmpJz
{ name: process.env.LOCK_PRO_DEVICE || 'Lock Pro DE', model: 'LockPro 7G' }, // Lock Pro → 15975#LnPNEJmS
];
describe('Lock Card - 首页卡片控制(Lock + Lock Pro)', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
setupResilientHooks(() => driver);
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('Lock_Card', driver.platform.toUpperCase());
});
beforeEach(async () => {
await driver.dismissPopupIfPresent();
await driver.goBackToHomepage();
await sleep(500);
await driver.dismissPopupIfPresent();
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
function parseStateAtColumn(src: string, colX: number): 'Locked' | 'Unlocked' | 'unknown' {
for (const n of src.split('<')) {
const t = (n.match(/text="(Locked|Unlocked)"/) || [])[1] as 'Locked' | 'Unlocked' | undefined;
const bx = (n.match(/bounds="\[(\d+),/) || [])[1];
if (t && bx && Math.abs(parseInt(bx, 10) - colX) < 120) return t;
}
return 'unknown';
}
async function homeLockInfo(name: string): Promise<{ state: 'Locked' | 'Unlocked' | 'unknown'; toggleX: number; toggleY: number } | null> {
const el = await driver.findElementRaw('-android uiautomator',
`new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().textContains("${name}"))`);
if (!el) return null;
const r = await driver.getElementRect(el);
const state = parseStateAtColumn(await driver.getSource(), r.x);
return { state, toggleX: r.x + r.width - 40, toggleY: r.y - 60 };
}
async function homeReadState(name: string): Promise<'Locked' | 'Unlocked' | 'unknown'> {
return (await homeLockInfo(name))?.state ?? 'unknown';
}
async function controlLockTo(name: string, target: 'Locked' | 'Unlocked'): Promise<'Locked' | 'Unlocked' | 'unknown'> {
const info = await homeLockInfo(name);
if (!info) throw new Error(`首页找不到${name}卡片`);
if (info.state === 'unknown') throw new Error(`读不到${name}卡片状态`);
if (info.state === target) return target;
await driver.tap(info.toggleX, info.toggleY); // 点开关钮
await sleep(2500);
// 仅"远程解锁"(未蓝牙直连)会弹二次确认框:勾"Do not show again"后点 Unlock。
if (target === 'Unlocked') {
const cb = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/cbNotShowAgain');
if (cb) {
await driver.tapElement(cb);
await sleep(500);
const btn = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/btnUnlock')
|| await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Unlock")');
if (btn) { await driver.tapElement(btn); console.log('已确认远程解锁'); }
}
}
await sleep(3000);
let st: 'Locked' | 'Unlocked' | 'unknown' = info.state;
for (let i = 0; i < 6 && st !== target; i++) {
st = await homeReadState(name);
if (st !== target) await sleep(2500);
}
return st;
}
// 对每台锁(Lock / Lock Pro)各跑 解锁 + 上锁,硬断言首页卡片状态翻转
for (const lock of LOCKS) {
const ANCHOR = onesCtrl('lock', lock.model);
it(`${ANCHOR} ${lock.name} 解锁`, { timeout: 120000 }, async () => {
const start = Date.now();
try {
const before = await homeReadState(lock.name);
expect(before).not.toBe('unknown');
const after = await controlLockTo(lock.name, 'Unlocked');
console.log(`${lock.name} 解锁: ${before}${after}`);
expect(after).toBe('Unlocked');
reporter.record(`${ANCHOR} ${lock.name} 解锁`, 'PASS', Date.now() - start, `首页卡片解锁,状态=${after}`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ANCHOR} ${lock.name} 解锁`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
it(`${ANCHOR} ${lock.name} 上锁`, { timeout: 120000 }, async () => {
const start = Date.now();
try {
const before = await homeReadState(lock.name);
expect(before).not.toBe('unknown');
const after = await controlLockTo(lock.name, 'Locked');
console.log(`${lock.name} 上锁: ${before}${after}`);
expect(after).toBe('Locked');
reporter.record(`${ANCHOR} ${lock.name} 上锁`, 'PASS', Date.now() - start, `首页卡片上锁,状态=${after}`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ANCHOR} ${lock.name} 上锁`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
}
});