274 lines
10 KiB
TypeScript
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);
|
|
}
|