import { DeviceDriver } from '../../drivers/types'; import { sleep, waitForElement, waitForSource } from './element.helper'; import { ensureHomeTab, navigateToAddPage, navigateToManageScenes } from './navigation.helper'; export interface SceneConfig { name: string; deviceKeyword: string; action: string; } export async function createScene(driver: DeviceDriver, config: SceneConfig): Promise { const { name, deviceKeyword, action } = config; const opened = await navigateToAddPage(driver, 'Scene'); if (!opened) return false; if (driver.platform === 'android') { const addAction = await driver.findElementRaw('-android uiautomator', 'new UiSelector().textContains("Add action")'); if (!addAction) { console.log('FAIL: no Add action'); return false; } await driver.tapElement(addAction); await sleep(2000); const smartDevices = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Smart Devices")'); if (!smartDevices) { console.log('FAIL: no Smart Devices'); return false; } await driver.tapElement(smartDevices); let deviceEl: string | null = null; for (let i = 0; i < 15; i++) { await sleep(1000); deviceEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${deviceKeyword}")`); if (deviceEl) break; } if (!deviceEl) { console.log(`FAIL: no ${deviceKeyword} in device list`); return false; } await driver.tapElement(deviceEl); await sleep(2000); let actionEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${action}")`); if (!actionEl) { actionEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Presses once")'); } if (!actionEl) { console.log('FAIL: no action option'); return false; } await driver.tapElement(actionEl); await sleep(2000); const editText = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Enter Scene name")'); if (editText) { await driver.tapElement(editText); await sleep(300); await driver.typeText(editText, name); await sleep(300); await driver.goBack(); await sleep(500); } const saveEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Save")'); if (!saveEl) { console.log('FAIL: no Save'); return false; } await driver.tapElement(saveEl); await sleep(3000); return true; } // iOS const addAction = await driver.findElementRaw('predicate string', 'name BEGINSWITH "Add action" AND name CONTAINS "E.g."'); if (!addAction) { console.log('FAIL: no Add action'); return false; } const aaRect = await driver.getElementRect(addAction); await driver.tap(aaRect.x + aaRect.width / 2, aaRect.y + aaRect.height / 2); const smartDevices = await waitForElement(driver, 'predicate string', 'name CONTAINS "Smart Devices"', 6000); if (!smartDevices) { console.log('FAIL: no Smart Devices'); return false; } await driver.tapElement(smartDevices); const deviceEl = await waitForElement(driver, 'predicate string', `name CONTAINS "${deviceKeyword}"`, 6000); if (!deviceEl) { console.log(`FAIL: no ${deviceKeyword}`); return false; } await driver.tapElement(deviceEl); const actionEl = await waitForElement(driver, 'name', action, 5000); if (!actionEl) { // Fallback: if Bot is in Press mode, "Turns off/on" won't exist const pressEl = await driver.findElementRaw('name', 'Presses once'); if (!pressEl) { console.log('FAIL: no action ' + action); return false; } await driver.tapElement(pressEl); } else { await driver.tapElement(actionEl); } await sleep(1500); const tfs = await driver.findElementsRaw('class name', 'XCUIElementTypeTextField'); if (tfs.length > 0) { await driver.tapElement(tfs[0]); await sleep(300); await driver.typeText(tfs[0], name); await sleep(300); const done = await driver.findElementRaw('name', 'Done'); if (done) { await driver.tapElement(done); } else { await driver.tap(195, 400); } await sleep(1000); } const saveEl = await driver.findElementRaw('name', 'Save'); if (!saveEl) { console.log('FAIL: no Save'); return false; } await driver.tapElement(saveEl); await sleep(3000); return true; } export async function executeSceneFromHomepage(driver: DeviceDriver, sceneName: string): Promise { if (driver.platform === 'android') { await driver.goBackToHomepage(); await sleep(500); try { const homeTab = await driver.findElementRaw('accessibility id', 'Home'); if (homeTab) { await driver.tapElement(homeTab); await sleep(1000); } } catch { /* stale */ } for (let i = 0; i < 5; i++) { const sceneEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${sceneName}")`); if (sceneEl) { await driver.tapElement(sceneEl); await sleep(1500); return true; } await driver.scrollDown(300); await sleep(800); } return false; } // iOS const source = await driver.getSource(); if (!source.includes(sceneName)) { await driver.scrollUp(200); await sleep(800); } const sceneEl = await driver.findElementRaw('predicate string', `name CONTAINS "${sceneName}"`); if (sceneEl) { await driver.tapElement(sceneEl); await sleep(1500); return true; } return false; } export async function deleteScene(driver: DeviceDriver, sceneName: string): Promise { if (driver.platform === 'android') { try { const homeTab = await driver.findElementRaw('accessibility id', 'Home'); if (homeTab) { await driver.tapElement(homeTab); await sleep(1000); } } catch { /* stale */ } const moreBtn = await driver.findElementRaw('id', 'com.theswitchbot.switchbot:id/moreBto'); if (!moreBtn) return false; await driver.tapElement(moreBtn); await sleep(1500); const manageEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Manage Scenes")'); if (!manageEl) return false; await driver.tapElement(manageEl); let sceneEl: string | null = null; for (let i = 0; i < 15; i++) { await sleep(1000); sceneEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${sceneName}")`); if (sceneEl) break; } if (!sceneEl) return false; await driver.tapElement(sceneEl); await sleep(2000); let delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")'); if (!delEl) { await driver.scrollDown(300); await sleep(500); delEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")'); } if (!delEl) return false; await driver.tapElement(delEl); await sleep(2000); const confirmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")'); if (confirmEl) { await driver.tapElement(confirmEl); await sleep(1500); } return true; } // iOS await driver.tap(347, 48); await sleep(1500); const manageEl = await driver.findElementRaw('name', 'Manage Scenes'); if (!manageEl) return false; await driver.tapElement(manageEl); await sleep(2000); const source = await driver.getSource(); if (!source.includes(sceneName)) return false; const myScenesEl = await driver.findElementRaw('name', 'My Scenes'); if (!myScenesEl) return false; const msRect = await driver.getElementRect(myScenesEl); await driver.tap(195, msRect.y + msRect.height + 40); await sleep(2000); const editSource = await driver.getSource(); if (!editSource.includes('Edit Scene')) return false; const delEl = await driver.findElementRaw('name', 'Delete'); if (!delEl) return false; await driver.tapElement(delEl); await sleep(1500); const delConfirm = await driver.findElementRaw('name', 'Delete'); if (delConfirm) { await driver.tapElement(delConfirm); await sleep(1500); } return true; } export async function verifySceneInLogs(driver: DeviceDriver, sceneName: string): Promise { if (driver.platform === 'android') { const navigated = await navigateToManageScenes(driver); if (!navigated) return false; // Find the scene in Manage Scenes list and tap to enter scene detail let sceneEl: string | null = null; for (let i = 0; i < 5; i++) { sceneEl = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${sceneName}")`); if (sceneEl) break; await driver.scrollDown(200); await sleep(800); } if (!sceneEl) return false; await driver.tapElement(sceneEl); await sleep(2000); // Tap top-right button to enter scene execution details/logs const moreBtn = await driver.findElementRaw('-android uiautomator', 'new UiSelector().resourceId("com.theswitchbot.switchbot:id/ivRight")'); if (moreBtn) { await driver.tapElement(moreBtn); await sleep(2000); } const source = await driver.getSource(); return source.includes('Success') || source.includes('success') || source.includes('Executed') || source.includes(sceneName); } // iOS: navigate to Manage Scenes await driver.tap(347, 48); await sleep(1500); const manageEl = await driver.findElementRaw('name', 'Manage Scenes'); if (!manageEl) return false; await driver.tapElement(manageEl); await sleep(2000); // Find the scene and tap to enter detail const sceneEl = await driver.findElementRaw('predicate string', `name CONTAINS "${sceneName}"`); if (!sceneEl) return false; await driver.tapElement(sceneEl); await sleep(2000); // Tap the top-right button to see execution history/status const navBar = await driver.findElementRaw('class name', 'XCUIElementTypeNavigationBar'); if (navBar) { const navRect = await driver.getElementRect(navBar); // Top-right corner of navigation bar await driver.tap(navRect.x + navRect.width - 30, navRect.y + navRect.height / 2); await sleep(2000); } const source = await driver.getSource(); return source.includes('Success') || source.includes('success') || source.includes('Executed') || source.includes(sceneName); }