170 lines
10 KiB
TypeScript
170 lines
10 KiB
TypeScript
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;
|
||
}
|
||
});
|
||
});
|