AI_UIAutomation/utils/common/device-settings.helper.ts

260 lines
9.9 KiB
TypeScript

import { DeviceDriver } from '../../drivers/types';
import { sleep, waitForElement, waitForSource, scrollUntilFound } from './element.helper';
export async function enterDeviceSettings(driver: DeviceDriver, deviceKeyword: string): Promise<boolean> {
let deviceEl: string | null = null;
if (driver.platform === 'android') {
deviceEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${deviceKeyword}")`);
if (!deviceEl) {
deviceEl = await driver.findElementRaw('-android uiautomator',
`new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().textContains("${deviceKeyword}"))`);
}
} else {
deviceEl = await driver.findElementRaw('predicate string', `name CONTAINS "${deviceKeyword}" AND type == "XCUIElementTypeCell"`);
if (!deviceEl) {
await driver.scrollDown(250);
await sleep(800);
deviceEl = await driver.findElementRaw('predicate string', `name CONTAINS "${deviceKeyword}" AND type == "XCUIElementTypeCell"`);
}
if (!deviceEl) {
deviceEl = await driver.findElementRaw('predicate string', `name CONTAINS "${deviceKeyword}"`);
}
}
if (!deviceEl) return false;
await driver.tapElement(deviceEl);
await sleep(2000);
if (driver.platform === 'ios') {
const settingsEl = await waitForElement(driver, 'name', 'Settings', 5000);
if (!settingsEl) return false;
await driver.tapElement(settingsEl);
await sleep(1500);
}
return true;
}
export async function scrollToAndTap(driver: DeviceDriver, name: string): Promise<boolean> {
if (driver.platform === 'android') {
const el = await driver.findElementRaw('-android uiautomator',
`new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text("${name}"))`);
if (!el) return false;
await driver.tapElement(el);
return true;
}
// iOS
let el = await driver.findElementRaw('name', name);
if (el) {
const rect = await driver.getElementRect(el);
if (rect.y > 750) {
await driver.scrollDown(300);
await sleep(500);
el = await driver.findElementRaw('name', name);
}
} else {
await driver.scrollDown(300);
await sleep(500);
el = await driver.findElementRaw('name', name);
}
if (!el) return false;
const rect = await driver.getElementRect(el);
await driver.tap(rect.x + rect.width / 2, rect.y + rect.height / 2);
return true;
}
export async function enterEditInfo(driver: DeviceDriver, deviceName?: string): Promise<boolean> {
if (driver.platform === 'android') {
// Try resource ID first (older app versions)
let baseInfo = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/layoutBaseInfo');
if (baseInfo) {
await driver.tapElement(baseInfo);
await sleep(2000);
return true;
}
// Newer versions: tap the device name text at top of settings
if (deviceName) {
const nameEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${deviceName}")`);
if (nameEl) {
await driver.tapElement(nameEl);
await sleep(2000);
return true;
}
}
return false;
}
// iOS
let nameEl: string | null = null;
if (deviceName) {
nameEl = await driver.findElementRaw('name', deviceName);
}
if (!nameEl) {
nameEl = await driver.findElementRaw('predicate string', 'type == "XCUIElementTypeStaticText" AND visible == true AND y < 200 AND y > 100');
}
if (!nameEl) return false;
const rect = await driver.getElementRect(nameEl);
await driver.tap(rect.x + rect.width / 2, rect.y + rect.height / 2);
await sleep(1000);
return true;
}
export async function renameDevice(
driver: DeviceDriver,
newName: string,
confirm: 'save' | 'cancel' = 'save'
): Promise<boolean> {
if (driver.platform === 'android') {
// Try resource ID first (older app versions)
let nameRow = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/sivDeviceName');
if (!nameRow) {
// Newer versions: on Edit Info page, tap "Name" row or the current device name
nameRow = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Name")');
}
if (!nameRow) return false;
await driver.tapElement(nameRow);
await sleep(1500);
// Find the edit text in the rename dialog
let editText = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/dialogEditText');
if (!editText) {
editText = await driver.findElementRaw('-android uiautomator',
'new UiSelector().className("android.widget.EditText")');
}
if (!editText) return false;
// Clear existing text
const clearBtn = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/dialogClearText');
if (clearBtn) {
await driver.tapElement(clearBtn);
} else {
await driver.clearText(editText);
}
await sleep(200);
await driver.typeText(editText, newName);
await sleep(300);
if (confirm === 'save') {
let okEl = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/editDialogOk');
if (!okEl) okEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")');
if (!okEl) okEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("OK")');
if (!okEl) okEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Confirm")');
if (okEl) await driver.tapElement(okEl);
} else {
let cancelEl = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/editDialogCancel');
if (!cancelEl) cancelEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Cancel")');
if (cancelEl) await driver.tapElement(cancelEl);
}
await sleep(2000);
return true;
}
// iOS
const nameLabel = await driver.findElementRaw('name', 'Name');
if (!nameLabel) return false;
const nameRect = await driver.getElementRect(nameLabel);
await driver.tap(nameRect.x + nameRect.width + 100, nameRect.y + nameRect.height / 2);
await sleep(1500);
const textFields = await driver.findElementsRaw('class name', 'XCUIElementTypeTextField');
if (textFields.length === 0) return false;
const clearEl = await driver.findElementRaw('name', 'Clear text');
if (clearEl) await driver.tapElement(clearEl);
await sleep(200);
await driver.typeText(textFields[0], newName);
await sleep(300);
if (confirm === 'save') {
const yesEl = await driver.findElementRaw('name', 'Yes');
if (yesEl) await driver.tapElement(yesEl);
await waitForSource(driver, newName, 5000);
} else {
const cancelEl = await driver.findElementRaw('name', 'Cancel');
if (cancelEl) await driver.tapElement(cancelEl);
}
await sleep(1000);
return true;
}
export async function changeDeviceRoom(driver: DeviceDriver): Promise<{ success: boolean; roomCount: number }> {
if (driver.platform === 'android') {
// Try resource ID first (older app versions)
let roomRow = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/sivDeviceRoom');
if (!roomRow) {
// Newer versions: tap "Room" text on Edit Info page
roomRow = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Room")');
}
if (!roomRow) return { success: false, roomCount: 0 };
await driver.tapElement(roomRow);
await sleep(2000);
// Find room options
let rooms = await driver.findElementsRaw('id', 'com.theswitchbot.switchbot:id/clSelectRoom');
if (rooms.length === 0) {
// Try finding clickable room items by class
rooms = await driver.findElementsRaw('-android uiautomator',
'new UiSelector().className("android.view.ViewGroup").clickable(true)');
}
if (rooms.length === 0) return { success: false, roomCount: 0 };
await driver.tapElement(rooms[0]);
await sleep(500);
const saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")');
if (saveEl) await driver.tapElement(saveEl);
await sleep(2000);
return { success: true, roomCount: rooms.length };
}
// iOS
const roomLabel = await driver.findElementRaw('name', 'Room');
if (!roomLabel) return { success: false, roomCount: 0 };
const roomRect = await driver.getElementRect(roomLabel);
await driver.tap(roomRect.x + roomRect.width + 50, roomRect.y + roomRect.height / 2);
await waitForSource(driver, 'Select Room', 5000);
const cells = await driver.findElementsRaw('class name', 'XCUIElementTypeCell');
if (cells.length === 0) return { success: false, roomCount: 0 };
await driver.tapElement(cells[0]);
await sleep(500);
const saveEl = await driver.findElementRaw('name', 'Save');
if (saveEl) await driver.tapElement(saveEl);
await sleep(2000);
return { success: true, roomCount: cells.length };
}
export async function changeDeviceIcon(driver: DeviceDriver): Promise<boolean> {
if (driver.platform === 'android') {
const source = await driver.getSource();
const hasIcon = source.includes('Icon') || source.includes('Display Type');
if (!hasIcon) return false;
const iconFL = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/iconSelectFL');
if (iconFL) {
await driver.tapElement(iconFL);
await sleep(1500);
const src2 = await driver.getSource();
if (src2.includes('Choose Icon')) {
await driver.goBack();
await sleep(1000);
}
}
return true;
}
// iOS
const iconSection = await driver.findElementRaw('name', 'Icon');
if (!iconSection) return false;
const iconRect = await driver.getElementRect(iconSection);
await driver.tap(iconRect.x + iconRect.width / 2, iconRect.y + iconRect.height / 2);
await waitForSource(driver, 'Choose Icon', 5000);
const cells = await driver.findElementsRaw('class name', 'XCUIElementTypeCell');
if (cells.length < 2) return false;
await driver.tapElement(cells[1]);
await sleep(1000);
await driver.tapElement(cells[0]);
await sleep(800);
return true;
}