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

280 lines
9.5 KiB
TypeScript

import { DeviceDriver } from '../../drivers/types';
import { sleep, waitForElement, waitForSource, scrollUntilFound } from './element.helper';
import { ensureHomeTab, navigateToAddPage, navigateThroughWizard } from './navigation.helper';
export interface AddDeviceOptions {
categoryName: string;
deviceKeyword: string;
scanTimeout?: number;
wizardButtons?: string[];
connectionKeywords?: string[];
preSelectSteps?: (driver: DeviceDriver) => Promise<void>;
postConnectionSteps?: (driver: DeviceDriver) => Promise<void>;
skipNextButton?: boolean;
skipScanStep?: boolean;
}
const DEFAULT_CONNECTION_KEYWORDS = [
'Initial Setup', 'Start Using', 'Done', 'added successfully',
'Got it', 'cloud service', 'Pick a room', 'Display Type',
];
const DEFAULT_WIZARD_BUTTONS = ['Use now', 'Start Using', 'Done', 'Got it', 'OK', 'Skip', 'Next'];
export async function isDeviceOnHomepage(driver: DeviceDriver, keyword: string): Promise<boolean> {
await ensureHomeTab(driver);
let source = await driver.getSource();
if (source.includes(keyword)) return true;
await driver.scrollDown(300);
await sleep(800);
source = await driver.getSource();
return source.includes(keyword);
}
export async function selectDeviceCategory(driver: DeviceDriver, categoryName: string): Promise<boolean> {
if (driver.platform === 'android') {
let el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${categoryName}")`);
if (!el) {
await driver.scrollDown(300);
await sleep(800);
el = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${categoryName}")`);
}
if (!el) { console.log(`FAIL: no ${categoryName} option`); return false; }
await driver.tapElement(el);
await sleep(3000);
const src = await driver.getSource();
return src.includes(categoryName) || src.includes('Select') || src.includes('Scanning') || src.includes('searching');
}
// iOS - scroll to reveal category, check visibility, then click
for (let i = 0; i < 8; i++) {
let el = await driver.findElementRaw('predicate string',
`name == "${categoryName}" AND type == "XCUIElementTypeStaticText"`);
if (!el) {
el = await driver.findElementRaw('predicate string',
`name == "${categoryName}" AND type == "XCUIElementTypeOther"`);
}
if (!el) {
el = await driver.findElementRaw('name', categoryName);
}
if (el) {
const visible = await driver.getElementAttribute(el, 'visible');
if (String(visible) !== 'true' && String(visible) !== '1') {
await driver.scrollDown(400);
await sleep(800);
continue;
}
await driver.clickElement(el);
await sleep(3000);
return true;
}
await driver.scrollDown(400);
await sleep(800);
}
console.log(`FAIL: no ${categoryName} option`);
return false;
}
export async function waitForDeviceInScan(
driver: DeviceDriver,
deviceKeyword: string,
timeout = 30000
): Promise<string | null> {
if (driver.platform === 'android') {
const start = Date.now();
while (Date.now() - start < timeout) {
const el = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${deviceKeyword}")`);
if (el) return el;
await sleep(1000);
}
return null;
}
// iOS
return await waitForElement(driver, 'name', deviceKeyword, timeout);
}
export async function waitForConnection(
driver: DeviceDriver,
keywords?: string[],
timeout = 30000
): Promise<boolean> {
const connectionKeywords = keywords || DEFAULT_CONNECTION_KEYWORDS;
const start = Date.now();
while (Date.now() - start < timeout) {
const source = await driver.getSource();
for (const kw of connectionKeywords) {
if (source.includes(kw)) return true;
}
await sleep(2000);
}
return false;
}
export async function addDeviceViaBLE(driver: DeviceDriver, options: AddDeviceOptions): Promise<boolean> {
const {
categoryName,
deviceKeyword,
scanTimeout = 30000,
wizardButtons = DEFAULT_WIZARD_BUTTONS,
connectionKeywords,
preSelectSteps,
postConnectionSteps,
skipNextButton = false,
skipScanStep = false,
} = options;
// Step 1: Navigate to Add Device page
const addDevicePage = await navigateToAddPage(driver, 'Device');
if (!addDevicePage) { console.log('FAIL: cannot navigate to Add Device'); return false; }
// Step 2: Select device category
const catSelected = await selectDeviceCategory(driver, categoryName);
if (!catSelected) { console.log(`FAIL: cannot select category ${categoryName}`); return false; }
// Optional: product-specific pre-select steps
if (preSelectSteps) await preSelectSteps(driver);
// Step 3: Wait for device in BLE scan (skip for auto-connect devices like AI Hub)
if (!skipScanStep) {
const deviceEl = await waitForDeviceInScan(driver, deviceKeyword, scanTimeout);
if (!deviceEl) {
console.log(`FAIL: ${deviceKeyword} not found in BLE scan`);
return false;
}
await driver.tapElement(deviceEl);
await sleep(500);
// Step 4: Tap Next (some products skip this)
if (!skipNextButton) {
if (driver.platform === 'android') {
const nextEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Next")');
if (nextEl) await driver.tapElement(nextEl);
} else {
const nextEl = await driver.findElementRaw('name', 'Next');
if (nextEl) await driver.tapElement(nextEl);
}
await sleep(2000);
}
}
// Step 5: Wait for connection
const connected = await waitForConnection(driver, connectionKeywords);
if (!connected) { console.log('FAIL: connection timeout'); return false; }
// Step 5.5: Post-connection steps (e.g. WiFi setup for cameras)
if (postConnectionSteps) await postConnectionSteps(driver);
// Step 6: Handle Initial Setup (iOS)
if (driver.platform === 'ios') {
const source = await driver.getSource();
if (source.includes('Initial Setup')) {
const setupNext = await driver.findElementRaw('name', 'Next');
if (setupNext) { await driver.tapElement(setupNext); await sleep(2000); }
}
}
// Step 7: Navigate through wizard
await navigateThroughWizard(driver, wizardButtons);
await driver.dismissPopupIfPresent();
// Step 8: Verify on homepage
await ensureHomeTab(driver);
await sleep(2000);
for (let i = 0; i < 5; i++) {
const source = await driver.getSource();
if (source.includes(deviceKeyword)) return true;
await driver.scrollDown(400);
await sleep(800);
}
return false;
}
export async function removeDeviceFromAccount(driver: DeviceDriver, keyword: string): Promise<boolean> {
await ensureHomeTab(driver);
if (driver.platform === 'android') {
let botCard = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${keyword}")`);
if (!botCard) {
await driver.scrollDown(300);
await sleep(800);
botCard = await driver.findElementRaw('-android uiautomator', `new UiSelector().textContains("${keyword}")`);
}
if (!botCard) return false;
await driver.tapElement(botCard);
await sleep(2000);
// Scroll to find Delete button
let delEl: string | null = null;
for (let i = 0; i < 5; i++) {
delEl = await driver.findElementRaw('-android uiautomator',
'new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text("Delete"))');
if (delEl) break;
await driver.scrollDown(300);
await sleep(800);
}
if (!delEl) { console.log('Delete button not found'); return false; }
await driver.tapElement(delEl);
await sleep(3000);
// Confirmation dialog
let confirmed = false;
for (let retry = 0; retry < 3; retry++) {
const src = await driver.getSource();
if (src.includes('Cancel') && src.includes('deleting this device')) {
const confirmEl = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")');
if (confirmEl) {
await driver.tapElement(confirmEl);
confirmed = true;
break;
}
}
await sleep(1000);
}
if (!confirmed) { console.log('Delete confirmation not found'); return false; }
await sleep(3000);
await driver.goBackToHomepage();
await sleep(1000);
const homeSource = await driver.getSource();
return !homeSource.includes(keyword);
}
// iOS
let botCard = await driver.findElementRaw('predicate string', `name CONTAINS "${keyword}"`);
if (!botCard) {
await driver.scrollDown(300);
await sleep(800);
botCard = await driver.findElementRaw('predicate string', `name CONTAINS "${keyword}"`);
}
if (!botCard) return false;
await driver.tapElement(botCard);
await sleep(1500);
const settingsEl = await waitForElement(driver, 'name', 'Settings', 5000);
if (!settingsEl) return false;
await driver.tapElement(settingsEl);
await sleep(2000);
let delEl: string | null = null;
for (let i = 0; i < 3; i++) {
delEl = await driver.findElementRaw('predicate string', 'name == "Delete" AND visible == true');
if (delEl) break;
await driver.scrollDown(300);
await sleep(800);
}
if (!delEl) { console.log('Delete button not found'); return false; }
await driver.tapElement(delEl);
await sleep(2000);
const okEl = await waitForElement(driver, 'name', 'OK', 5000);
if (!okEl) { console.log('Delete confirmation not found'); return false; }
await driver.tapElement(okEl);
await sleep(3000);
await driver.goBackToHomepage();
await sleep(1000);
const homeSource = await driver.getSource();
return !homeSource.includes(keyword);
}