AI_UIAutomation/utils/common/scene.helper.ts

274 lines
10 KiB
TypeScript

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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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);
}