import { DeviceDriver } from '../../drivers/types'; import { sleep, waitForElement, waitForSource, scrollUntilFound } from './element.helper'; export async function enterDeviceSettings(driver: DeviceDriver, deviceKeyword: string): Promise { 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 { 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 { 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 { 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 { 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; }