146 lines
7.0 KiB
TypeScript
146 lines
7.0 KiB
TypeScript
import { describe, it, beforeAll, beforeEach, afterAll, expect } from 'vitest';
|
|
import { DeviceDriver } from '../../drivers/types';
|
|
import { createDriver } from '../../drivers/factory';
|
|
import { TestReporter } from '../../utils/test-reporter';
|
|
import { sleep, scrollToAndTap, onesCtrl, waitForHomeReady, waitForSource } from '../../utils/common';
|
|
import { setupResilientHooks } from "../../utils/common";
|
|
import * as dotenv from 'dotenv';
|
|
import * as path from 'path';
|
|
|
|
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
|
|
|
// 百叶帘:首页快捷弹窗控制(Fully open / Close Down)。锚点 BlindTilt 3C → 15975#3frMRDZj (step18)
|
|
const deviceName = process.env.BT_DEVICE || 'Blind Tilt 42';
|
|
const BT_MODEL = process.env.BT_MODEL || 'BlindTilt 3C';
|
|
const CTRL_BT = onesCtrl('curtain', BT_MODEL);
|
|
|
|
describe('Blind Tilt Control - 百叶帘控制(首页快捷弹窗)', () => {
|
|
let driver: DeviceDriver;
|
|
let reporter: TestReporter;
|
|
setupResilientHooks(() => driver);
|
|
|
|
beforeAll(async () => {
|
|
driver = createDriver();
|
|
await driver.createSession();
|
|
reporter = new TestReporter('BlindTilt_Control', driver.platform.toUpperCase());
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await driver.dismissPopupIfPresent();
|
|
await waitForHomeReady(driver, 25000); // 等 App 启动渲染完首页(SOAK 连跑首个用例 App 还在启动→找不到卡片失败)
|
|
await driver.goBackToHomepage();
|
|
await sleep(500);
|
|
await driver.dismissPopupIfPresent();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
reporter.generate();
|
|
await driver.destroySession();
|
|
});
|
|
|
|
async function openBtPopup(): Promise<void> {
|
|
await waitForHomeReady(driver, 25000); // App 启动中先等首页就绪,否则找不到卡片
|
|
if (driver.platform === 'ios') {
|
|
// iOS:滚动找 Blind Tilt cell 点中心弹浮层(scrollToAndTap 只滚1次+按 name== 精确匹配,卡片名是拼接的会漏)。参考 lock。
|
|
const { height: winH } = await driver.getWindowSize().catch(() => ({ height: 844 }));
|
|
let lastSig = '';
|
|
for (let r = 0; r < 14; r++) {
|
|
const cell = await driver.findElementRaw('predicate string', `type == "XCUIElementTypeCell" AND name CONTAINS "${deviceName}"`).catch(() => null);
|
|
if (cell) {
|
|
const rr = await driver.getElementRect(cell).catch(() => null);
|
|
if (rr && rr.width > 0 && rr.y > winH * 0.06 && rr.y + rr.height < winH * 0.92) {
|
|
await driver.tap(Math.round(rr.x + rr.width / 2), Math.round(rr.y + rr.height / 2));
|
|
await sleep(2500); return;
|
|
}
|
|
}
|
|
const sig = (await driver.getSource()).slice(-2500);
|
|
if (sig === lastSig) break;
|
|
lastSig = sig;
|
|
await driver.scrollDown(500); await sleep(800);
|
|
}
|
|
throw new Error(`找不到${deviceName}卡片`);
|
|
}
|
|
// Android:scrollToAndTap 已做「滚动定位 + 点击前重取新鲜句柄 + stale 重取」,不会再因 RN 刷新 stale 报找不到。
|
|
await waitForSource(driver, deviceName, 12000).catch(() => false); // 冷启动等卡片渲染出来再滚
|
|
const ok = await scrollToAndTap(driver, deviceName);
|
|
if (!ok) throw new Error(`找不到${deviceName}卡片`);
|
|
await sleep(2500);
|
|
}
|
|
|
|
// 读百叶帘倾斜状态:Fully open / Fully closed / "X% Opened up|down"。
|
|
function readBtState(src: string): string {
|
|
if (driver.platform === 'ios') {
|
|
// iOS:状态在 cell 名"<设备名><状态>"(如 "Blind Tilt 42Fully open")→ 取设备名后的状态,
|
|
// 避免误匹配浮层里的 Fully open/Close Down 按钮(它们 name 不带设备名前缀)。
|
|
const m = src.match(new RegExp(`"${deviceName}(Fully open|Fully closed[^"]*|[0-9]{1,3}% (?:Opened|Closed)[^"]*)"`));
|
|
return m ? m[1].trim() : 'unknown';
|
|
}
|
|
const m = src.match(/text="(Fully open|Fully closed[^"]*|[0-9]{1,3}% (?:Opened|Closed)[^"]*)"/);
|
|
return m ? m[1] : 'unknown';
|
|
}
|
|
|
|
async function tapBtnAndWait(btn: string, pred: (s: string) => boolean): Promise<string> {
|
|
if (driver.platform === 'ios') {
|
|
// iOS:Fully open / Close Down 是**文字标签**,真正可点的是其**上方约 50px 的圆形图标**(点标签本身无效)。
|
|
// "Fully open" 会出现两次(标题下的状态字 + 图标行的按钮标签)→ 取 y 最大(图标行)那个,点其上方图标。
|
|
const lbls = await driver.findElementsRaw('predicate string', `name == "${btn}" OR label == "${btn}"`).catch(() => [] as string[]);
|
|
let target: { cx: number; y: number } | null = null;
|
|
for (const l of lbls) {
|
|
const r = await driver.getElementRect(l).catch(() => null);
|
|
if (r && r.width > 0 && (!target || r.y > target.y)) target = { cx: Math.round(r.x + r.width / 2), y: r.y };
|
|
}
|
|
if (!target) throw new Error(`浮层无 ${btn} 按钮`);
|
|
await driver.tap(target.cx, Math.round(target.y - 50)); // 点标签上方圆形图标
|
|
} else {
|
|
const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${btn}")`);
|
|
expect(el).not.toBeNull();
|
|
await driver.tapElement(el!);
|
|
}
|
|
await sleep(8000); // 等百叶帘转动
|
|
// 弹窗状态不实时刷新 → 回首页重开弹窗读新鲜状态,轮询到目标
|
|
let st = 'unknown';
|
|
for (let i = 0; i < 6; i++) {
|
|
await driver.goBackToHomepage();
|
|
await sleep(1500);
|
|
await openBtPopup();
|
|
await sleep(3000); // 控制后状态同步需时间,重开弹窗后再等 3s 才读(否则读到旧状态误判失败)
|
|
st = readBtState(await driver.getSource());
|
|
if (pred(st)) break;
|
|
await sleep(2000);
|
|
}
|
|
return st;
|
|
}
|
|
|
|
it(`${CTRL_BT} 向下关闭(Close Down)`, { timeout: 150000 }, async () => {
|
|
const start = Date.now();
|
|
try {
|
|
await openBtPopup();
|
|
expect(readBtState(await driver.getSource())).not.toBe('unknown'); // 弹窗能读到倾斜状态
|
|
const after = await tapBtnAndWait('Close Down', s => /closed/i.test(s)); // 等到 "Fully closed"
|
|
console.log('Close Down →', after);
|
|
expect(/closed/i.test(after)).toBe(true); // 硬断言:已关闭(Fully closed)
|
|
reporter.record(`${CTRL_BT} 向下关闭`, 'PASS', Date.now() - start, `状态=${after}`);
|
|
} catch (e: any) {
|
|
const ss = await driver.screenshot().catch(() => '');
|
|
reporter.record(`${CTRL_BT} 向下关闭`, 'FAIL', Date.now() - start, e.message, ss);
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
it(`${CTRL_BT} 完全打开(Fully open)`, { timeout: 150000 }, async () => {
|
|
const start = Date.now();
|
|
try {
|
|
await openBtPopup();
|
|
expect(readBtState(await driver.getSource())).not.toBe('unknown');
|
|
const after = await tapBtnAndWait('Fully open', s => s === 'Fully open'); // 等到完全打开(水平)
|
|
console.log('Fully open →', after);
|
|
expect(after).toBe('Fully open'); // 硬断言:已完全打开
|
|
reporter.record(`${CTRL_BT} 完全打开`, 'PASS', Date.now() - start, `状态=${after}`);
|
|
} catch (e: any) {
|
|
const ss = await driver.screenshot().catch(() => '');
|
|
reporter.record(`${CTRL_BT} 完全打开`, 'FAIL', Date.now() - start, e.message, ss);
|
|
throw e;
|
|
}
|
|
});
|
|
});
|