AI_UIAutomation/tests/switch/relay_switch_connect.test.ts

170 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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, expect } from 'vitest';
import { DeviceDriver } from '../../drivers/types';
import { createDriver } from '../../drivers/factory';
import { TestReporter } from '../../utils/test-reporter';
import {
sleep,
addDeviceWithSerialPairing,
findDeviceNameOnHomepage,
saveAddedDevice,
configureHubWifi,
resetBluetooth,
enterPairingMode,
} from '../../utils/common';
import { getWifiCredentials } from '../../config/wifi.config';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
// Relay Switch 1PM:WiFi 产品(relay ch48),添加目录在 "Power & Switch" 分组、与 Plug Mini 同组。
// Relay Switch 有 1PM/1/2PM 多个物理型号,**共用添加用例 ONES:302288,取 1PM 为代表**(见 ones-anchor.helper)。
// 流程:选品类 "Relay Switch" → 二级选型号 "Relay Switch 1PM" → 跳接线引导 → 长按配对键(ch48,2s)→ 连接 → WiFi 配网。
const CATEGORY = process.env.RELAY_SWITCH_CATEGORY || 'Relay Switch';
const SUB_MODEL = process.env.RELAY_SWITCH_SUBMODEL || 'Relay Switch 1'; // 二级型号(目录里点选)
const deviceName = process.env.RELAY_SWITCH_DEVICE || 'Relay Switch 1';
// ⚠️ 实测物理设备(ch48)上报名为 "Relay Switch 1 32"(非 1PM)→ 用 "Relay Switch 1" 前缀匹配真实名,否则首页校验找不到
const NAME_PATTERN = process.env.RELAY_SWITCH_PATTERN || 'Relay Switch 1\\s*[0-9A-Za-z]{0,4}';
// 继电器配对键设备(通道):relay_switch=ch48(Relay Switch 1) / relay_switch_1pm=ch59 / relay_switch_2pm=ch58
const RELAY_DEVICE = process.env.RELAY_SWITCH_RELAY || 'relay_switch';
const ADD_ANCHOR = '[P0][ONES:302288]';
describe('RelaySwitch Connect - 添加Relay Switch(串口配对 + WiFi配网)', () => {
let driver: DeviceDriver;
let reporter: TestReporter;
beforeAll(async () => {
driver = createDriver();
await driver.createSession();
reporter = new TestReporter('RelaySwitch_Connect', driver.platform.toUpperCase());
});
afterAll(async () => {
reporter.generate();
await driver.destroySession();
});
it(`${ADD_ANCHOR} 通过BLE+WiFi添加Relay Switch`, { timeout: 360000 }, async () => {
const start = Date.now();
const wifi = getWifiCredentials();
try {
const existing = await findDeviceNameOnHomepage(driver, NAME_PATTERN);
if (existing) {
console.log(`${existing}已在首页,跳过重新添加`);
saveAddedDevice('relay_switch', existing);
reporter.record(`${ADD_ANCHOR} 添加Relay Switch`, 'SKIP', Date.now() - start, `${existing}已存在`);
return;
}
// BLE 配对前复位手机蓝牙(关→开),根治偶发配对/连接不上
// await resetBluetooth(driver);
// 串口指令(继电器按键)放在**添加流程之前**:先让 Relay Switch 进入广播(~10min 窗口),
// 确保后续 Connect Device 扫描能发现它。流程内不再按(skipInitialPress),避免重复按把设备切出广播。
console.log('[RelaySwitch] 流程前按继电器配对键,让设备进入广播...');
await enterPairingMode(RELAY_DEVICE).catch((e) => console.log('relay 预按键失败:', e.message));
await sleep(2500);
const result = await addDeviceWithSerialPairing(driver, {
categoryName: CATEGORY,
categoryScrollHint: 0,
deviceKeyword: deviceName,
relayDevice: RELAY_DEVICE, // ch48(默认) / ch59(1PM) / ch58(2PM)
registryCategory: RELAY_DEVICE,
namePattern: NAME_PATTERN,
repressOnRetry: false,
skipInitialPress: true, // 已在流程前按过 → 配对循环内不再按
pairRetries: 2,
pairPollIters: 16,
// 选 "Relay Switch" 后:① 二级选型号(默认 Relay Switch 1PM)② 接线设置引导页(AC/DC等)点 "Skip this guide" 跳过
// → 到连接页(按键2s + Connect Device,由 helper 配对+tapNext 处理)
beforePairing: async (d) => {
await sleep(1500);
// 打印二级型号目录(防型号名与实际不符;1PM/1/2PM 等多型号都在此页)。两端通用:Android text= / iOS name=label=
const catTexts = [...new Set((await d.getSource()).match(/(?:text|name|label)="[^"]*Relay[^"]*"/g) || [])].join(' | ');
console.log('[RelaySwitch] 二级型号目录:', catTexts);
// 选 SUB_MODEL:**精确等值匹配**(text() 全等)——目录里 "Relay Switch 1PM" 排在 "Relay Switch 1" 之前,
// 若用 "Relay Switch 1.*" 会贪婪命中 1PM(选错型号→扫描签名不符→扫不到)。故先 exact,再退回全等正则。
let sub = await d.findElementRaw('-android uiautomator', `new UiSelector().text("${SUB_MODEL}")`).catch(() => null)
|| await d.findElementRaw('-android uiautomator', `new UiSelector().textMatches("(?i)${SUB_MODEL}")`).catch(() => null);
if (sub) { await d.tapElement(sub); console.log(`[RelaySwitch] 选型号 ${SUB_MODEL}(精确)`); await sleep(2000); }
else console.log(`[RelaySwitch] ⚠️未找到型号 ${SUB_MODEL}(看上面目录确认真实名)`);
if (process.env.RS_DUMP) {
const s = await d.getSource();
console.log('[RS_DUMP] 选型号后页面文案:', [...new Set((s.match(/(?:text|content-desc)="[^"]{2,32}"/g) || []))].slice(0, 30).join(' | '));
}
// 2PM(可能其他型号)多一步**选应用/开关类型**:"Switch & Socket"(开关插座) vs "Cover & Shade"(窗帘卷帘)。
// 开关走 "Switch & Socket";其他型号无此页则跳过。
for (let i = 0; i < 3; i++) {
const st = await d.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Switch & Socket")').catch(() => null)
|| await d.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Switch &")').catch(() => null);
if (!st) break;
await d.tapElement(st); console.log('[RelaySwitch] 选开关类型 Switch & Socket'); await sleep(2000);
// 选完可能有 Next/Confirm
const nx = await d.findElementRaw('-android uiautomator', 'new UiSelector().textMatches("Next|Confirm|OK|Continue")').catch(() => null);
if (nx) { await d.tapElement(nx); await sleep(1500); }
break;
}
for (let i = 0; i < 3; i++) {
const skip = await d.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Skip this guide")').catch(() => null);
if (!skip) break;
await d.tapElement(skip); await sleep(2000);
}
},
// 连接后流程(用户方案):点 Connect Device 发起扫描 → Select Device 没扫到 → 返回上一页 → 再点 Connect Device 重扫,**循环 3 次**。
// 设备已在流程前按继电器进入广播(~10min),3 轮重扫应能发现。扫到 → 选中 → Next → 配网。
postConnectSteps: async (d) => {
const tapByRect = async (el: string) => {
const r = await d.getElementRect(el).catch(() => null);
if (r && r.width > 0) { await d.tap(Math.round(r.x + r.width / 2), Math.round(r.y + r.height / 2)); return true; }
await d.tapElement(el).catch(() => {}); return true;
};
const findConnectDevice = async () =>
await d.findElementRaw('-android uiautomator', 'new UiSelector().descriptionContains("Connect Device")').catch(() => null)
|| await d.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Connect Device")').catch(() => null)
|| (d.platform === 'ios' ? await d.findElementRaw('name', 'Connect Device').catch(() => null) : null);
const findScannedDev = async () => // 扫描列表里的设备项(实测名如 "Relay Switch 1 32")
await d.findElementRaw('-android uiautomator', 'new UiSelector().textMatches("(?i)Relay Switch [0-9].*")').catch(() => null)
|| (d.platform === 'ios' ? await d.findElementRaw('predicate string', 'name CONTAINS "Relay Switch 1"').catch(() => null) : null);
let selected = false;
for (let round = 0; round < 3 && !selected; round++) {
// a) 在 Connect Device 页 → 点它发起 BLE 扫描
const cd = await findConnectDevice();
if (cd) { await tapByRect(cd); console.log(`[RelaySwitch] 点 Connect Device 发起扫描(第${round + 1}/3 轮)`); await sleep(4000); }
// b) 等扫描结果:出现设备项=选中;No device found=本轮失败;已到配网=直接成功
for (let t = 0; t < 10 && !selected; t++) {
const s = await d.getSource();
const dev = await findScannedDev();
if (dev && /Select Device/i.test(s)) { await tapByRect(dev); console.log(`[RelaySwitch] 扫到设备,选中(第${round + 1} 轮)`); await sleep(1500); selected = true; break; }
if (/Configure Wi-?Fi|Wi-?Fi Settings|输入设备名/i.test(s)) { selected = true; break; }
if (/No device found/i.test(s)) { console.log(`[RelaySwitch] 第${round + 1} 轮 No device found`); break; }
await sleep(2000);
}
if (selected) break;
// c) 没扫到 → 返回上一页(回 Connect Device 页),下一轮重扫
if (round < 2) { console.log(`[RelaySwitch] 第${round + 1} 轮未扫到,返回上一页重扫`); await d.goBack().catch(() => {}); await sleep(2500); }
}
if (!selected) console.log('[RelaySwitch] ⚠3 轮重扫仍未发现设备');
// 选中后点 Next → 进配网
const n = await d.findElementRaw('-android uiautomator', 'new UiSelector().text("Next")').catch(() => null);
if (n) { await tapByRect(n); await sleep(3500); }
await configureHubWifi(d, wifi);
},
connectionKeywords: [
'Select Device', 'Configure Wi-Fi', 'Wi-Fi Settings', 'Connecting', 'Pick a room', 'Done',
'added successfully', 'Added successfully', 'Initial Setup',
],
});
expect(result).toBe(true);
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
reporter.record(`${ADD_ANCHOR} 添加Relay Switch`, 'PASS', Date.now() - start, `添加成功(WiFi:${wifi.ssid}), 耗时${elapsed}s`);
} catch (e: any) {
const ss = await driver.screenshot().catch(() => '');
reporter.record(`${ADD_ANCHOR} 添加Relay Switch`, 'FAIL', Date.now() - start, e.message, ss);
throw e;
}
});
});