182 lines
7.7 KiB
TypeScript
182 lines
7.7 KiB
TypeScript
import { DeviceDriver } from '../../drivers/types';
|
|
import { sleep, waitForElement, waitForSource } from './element.helper';
|
|
import { execSync } from 'child_process';
|
|
import { APP_CONFIG } from '../../config/app.config';
|
|
|
|
/**
|
|
* 强制重启 Android App(force-stop + 重新拉起)—— 清掉卡住的弹框/崩溃页/残留向导,让首页"+"恢复可用。
|
|
* 夜间 soak 实测:curtain3 连接失败弹框未处理会一直挡住"+",导致后续整轮导航失败。
|
|
*/
|
|
export async function restartAndroidApp(driver: DeviceDriver): Promise<void> {
|
|
const pkg = APP_CONFIG.android.appPackage;
|
|
try {
|
|
execSync(`adb shell am force-stop ${pkg}`, { stdio: 'ignore' });
|
|
await sleep(1500);
|
|
await driver.activateApp(pkg).catch(() => {
|
|
try { execSync(`adb shell monkey -p ${pkg} -c android.intent.category.LAUNCHER 1`, { stdio: 'ignore' }); } catch { /* ignore */ }
|
|
});
|
|
await sleep(6000);
|
|
await driver.dismissPopupIfPresent();
|
|
await ensureHomeTab(driver);
|
|
await driver.dismissPopupIfPresent();
|
|
} catch (e: any) {
|
|
console.log('restartAndroidApp 异常:', e.message);
|
|
}
|
|
}
|
|
|
|
|
|
export async function ensureHomeTab(driver: DeviceDriver): Promise<void> {
|
|
await driver.goBackToHomepage();
|
|
await sleep(600);
|
|
if (driver.platform === 'android') {
|
|
try {
|
|
const homeTab = await driver.findElementRaw('accessibility id', 'Home');
|
|
if (homeTab) { await driver.tapElement(homeTab); await sleep(600); }
|
|
} catch { /* element may be stale after navigation */ }
|
|
} else {
|
|
const homeTab = await driver.findElementRaw('predicate string', 'name BEGINSWITH "HomeScene"');
|
|
if (homeTab) { await driver.tapElement(homeTab); await sleep(1000); }
|
|
}
|
|
}
|
|
|
|
export async function navigateToAddPage(
|
|
driver: DeviceDriver,
|
|
type: 'Device' | 'Scene'
|
|
): Promise<boolean> {
|
|
if (driver.platform === 'android') {
|
|
const addBtoId = 'com.theswitchbot.switchbot:id/addBto';
|
|
const menuText = type === 'Device' ? 'Add Device' : 'Add Scene';
|
|
// 最多 3 次。快路:直接找"+";在则我们已在可加设备的首页,跳过 dismiss/ensureHomeTab 恢复(省 ~6s)。
|
|
// 找不到"+"才恢复(首轮轻量:dismiss+ensureHomeTab;再不行才重启 App 清残留弹框)。
|
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
let addBtn = await driver.findElementRaw('id', addBtoId);
|
|
if (!addBtn) {
|
|
await driver.dismissPopupIfPresent();
|
|
await ensureHomeTab(driver);
|
|
await driver.dismissPopupIfPresent();
|
|
if (attempt > 0) await restartAndroidApp(driver); // 轻量恢复无效 → 重启清残留弹框
|
|
addBtn = await driver.findElementRaw('id', addBtoId);
|
|
}
|
|
if (addBtn) {
|
|
await driver.tapElement(addBtn);
|
|
// 轮询菜单出现(替代固定 sleep1000),出现即走
|
|
let menuEl: string | null = null;
|
|
for (let i = 0; i < 8 && !menuEl; i++) {
|
|
menuEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${menuText}")`);
|
|
if (!menuEl) await sleep(250);
|
|
}
|
|
if (menuEl) {
|
|
await driver.tapElement(menuEl);
|
|
// 轮询目标页加载(替代固定 sleep1500)
|
|
for (let i = 0; i < 8; i++) {
|
|
const src = await driver.getSource();
|
|
const ok = type === 'Device' ? (src.includes('Bot') || src.includes('Add Device')) : src.includes('Create Scene');
|
|
if (ok) return true;
|
|
await sleep(300);
|
|
}
|
|
}
|
|
}
|
|
console.log(`navigateToAddPage 第${attempt + 1}次未成(addBto/菜单缺失或残留弹框)...`);
|
|
}
|
|
console.log('FAIL: cannot navigate to Add Device(重试后仍失败)');
|
|
return false;
|
|
}
|
|
|
|
// iOS
|
|
const addBtn = await driver.findElementRaw('name', 'Add');
|
|
if (!addBtn) { console.log('FAIL: Add button not found'); return false; }
|
|
|
|
if (type === 'Scene') {
|
|
await driver.tapElement(addBtn);
|
|
await sleep(1500);
|
|
await driver.dismissPopupIfPresent();
|
|
const addSceneEl = await driver.findElementRaw('name', 'Add Scene');
|
|
if (!addSceneEl) { console.log('FAIL: no Add Scene'); return false; }
|
|
const rect = await driver.getElementRect(addSceneEl);
|
|
await driver.tap(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
const found = await waitForElement(driver, 'predicate string', 'name CONTAINS "Create Scene"', 8000);
|
|
if (!found) { console.log('FAIL: Create Scene not loaded'); return false; }
|
|
await driver.dismissPopupIfPresent();
|
|
return true;
|
|
}
|
|
|
|
// iOS - Add Device
|
|
const addRect = await driver.getElementRect(addBtn);
|
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
await driver.tap(addRect.x + addRect.width / 2, addRect.y + addRect.height / 2);
|
|
await sleep(1500);
|
|
const addDevice = await driver.findElementRaw('name', 'Add Device');
|
|
if (addDevice) {
|
|
const rect = await driver.getElementRect(addDevice);
|
|
await driver.tap(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
return await waitForSource(driver, 'Add Device', 5000);
|
|
}
|
|
}
|
|
console.log('FAIL: no Add Device in menu');
|
|
return false;
|
|
}
|
|
|
|
export async function navigateToManageScenes(driver: DeviceDriver): Promise<boolean> {
|
|
if (driver.platform === 'android') {
|
|
await ensureHomeTab(driver);
|
|
const moreBtn = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/moreBto');
|
|
if (!moreBtn) { console.log('FAIL: moreBto not found'); return false; }
|
|
await driver.tapElement(moreBtn);
|
|
await sleep(1500);
|
|
|
|
const manageEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Manage Scenes")');
|
|
if (!manageEl) { console.log('FAIL: no Manage Scenes'); return false; }
|
|
await driver.tapElement(manageEl);
|
|
await sleep(2000);
|
|
return true;
|
|
}
|
|
|
|
// iOS
|
|
await driver.tap(347, 48);
|
|
await sleep(1500);
|
|
const manageEl = await driver.findElementRaw('name', 'Manage Scenes');
|
|
if (!manageEl) { console.log('FAIL: no Manage Scenes'); return false; }
|
|
await driver.tapElement(manageEl);
|
|
await sleep(2000);
|
|
return true;
|
|
}
|
|
|
|
export async function navigateThroughWizard(
|
|
driver: DeviceDriver,
|
|
buttonNames = ['Use now', 'Start Using', 'Done', 'Got it', 'OK', 'Skip', 'Next'],
|
|
maxSteps = 6
|
|
): Promise<boolean> {
|
|
if (driver.platform === 'android') {
|
|
for (let i = 0; i < maxSteps; i++) {
|
|
const src = await driver.getSource(); // 每轮只取一次整页,避免重复 getSource
|
|
// 到首页:有底部导航/首页列表,且无添加向导标记
|
|
const onWizard = /Add (Bot|Meter|Curtain|Remote|Outdoor|Lock|Plug|Hub|Keypad|Fan|Light|Sensor|Device)|Added successfully|Install|Initial Setup|Select Mode|Disclaimers|Calibrat/i.test(src);
|
|
const onHome = src.includes('homeRecyclerView') || src.includes('addBto') ||
|
|
src.includes('content-desc="Home"') || src.includes('Automations') || src.includes('Add a Device');
|
|
if (!onWizard && onHome) return true;
|
|
// 从已取的 src 判断当前页存在哪个向导按钮,只查这一个(省去逐个 7 次查询)
|
|
const btn = buttonNames.find(b => src.includes(`text="${b}"`));
|
|
if (btn) {
|
|
const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${btn}")`);
|
|
if (el) { await driver.tapElement(el); await sleep(1200); continue; }
|
|
}
|
|
// 没有可点按钮:可能已完成或加载中,短等再判
|
|
await sleep(700);
|
|
}
|
|
return await driver.isOnHomepage();
|
|
}
|
|
|
|
// iOS
|
|
for (let i = 0; i < maxSteps; i++) {
|
|
const source = await driver.getSource();
|
|
if (source.includes('XCUIElementTypeTabBar') && !source.includes('Add Bot')) return true;
|
|
let tapped = false;
|
|
for (const btn of buttonNames) {
|
|
const el = await driver.findElementRaw('name', btn);
|
|
if (el) { await driver.tapElement(el); await sleep(2000); tapped = true; break; }
|
|
}
|
|
if (!tapped) break;
|
|
}
|
|
return true;
|
|
}
|