import { describe, it, beforeAll, afterAll, beforeEach, expect } from 'vitest'; import { DeviceDriver } from '../../drivers/types'; import { createDriver } from '../../drivers/factory'; import { TestReporter } from '../../utils/test-reporter'; import { getDeviceName } from '../../config/device.config'; import { sleep, enterDeviceSettings, renameDevice, changeDeviceRoom, navigateToTimerPage, addTimer, deleteAllTimers, navigateToFirmwarePage, checkFirmwareVersion, navigateToDeviceInfo, getDeviceInfo, scrollToAndTap, waitForSource, enterEditInfo, addDeviceViaBLE, isDeviceOnHomepage, createScene, executeSceneFromHomepage, deleteScene, ensureHomeTab, } from '../../utils/common'; import * as dotenv from 'dotenv'; import * as path from 'path'; dotenv.config({ path: path.resolve(__dirname, '../../.env') }); const deviceName = getDeviceName('keypad', 'KEYPAD_DEVICE'); describe('Keypad Control - Keypad控制页', () => { let driver: DeviceDriver; let reporter: TestReporter; beforeAll(async () => { driver = createDriver(); await driver.createSession(); reporter = new TestReporter('Keypad_Control', driver.platform.toUpperCase()); }); beforeEach(async () => { await driver.dismissPopupIfPresent(); await driver.goBackToHomepage(); await driver.dismissPopupIfPresent(); }); afterAll(async () => { reporter.generate(); await driver.destroySession(); }); async function enterKeypadControl(): Promise { let el = await driver.findElementRaw('name', deviceName); if (!el) { await driver.scrollDown(250); await sleep(1000); el = await driver.findElementRaw('name', deviceName); } if (!el) throw new Error(`找不到${deviceName}卡片`); await driver.tapElement(el); await sleep(3000); } it('解绑Lock', async () => { const start = Date.now(); try { await enterKeypadControl(); const unpairEl = await driver.findElementRaw('name', 'Unpair') || await driver.findElementRaw('name', 'Unbind') || await driver.findElementRaw('name', 'Unpair Lock'); if (!unpairEl) { // Scroll to find it await driver.scrollDown(300); await sleep(1000); const el = await driver.findElementRaw('name', 'Unpair') || await driver.findElementRaw('name', 'Unbind'); if (!el) throw new Error('Unpair/Unbind按钮未找到'); await driver.tapElement(el); } else { await driver.tapElement(unpairEl); } await sleep(2000); // Confirm dialog const confirmEl = await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'OK') || await driver.findElementRaw('name', 'Yes'); if (confirmEl) { await driver.tapElement(confirmEl); await sleep(3000); } const source = await driver.getSource(); const unbound = source.includes('Pair Lock') || source.includes('Bind Lock') || source.includes('No lock'); console.log('Lock解绑结果:', unbound); reporter.record('解绑Lock', 'PASS', Date.now() - start, `Unpair完成, unbound=${unbound}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('解绑Lock', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('绑定Lock', async () => { const start = Date.now(); try { await enterKeypadControl(); const pairEl = await driver.findElementRaw('name', 'Pair Lock') || await driver.findElementRaw('name', 'Bind Lock') || await driver.findElementRaw('name', 'Add Lock'); if (!pairEl) { await driver.scrollDown(300); await sleep(1000); const el = await driver.findElementRaw('name', 'Pair Lock') || await driver.findElementRaw('name', 'Bind Lock'); if (!el) throw new Error('Pair Lock/Bind Lock按钮未找到'); await driver.tapElement(el); } else { await driver.tapElement(pairEl); } await sleep(3000); // Select a lock device from the list const source = await driver.getSource(); const hasLockList = source.includes('Lock') || source.includes('Select'); // Tap first available lock device const lockEl = await driver.findElementRaw('name', 'Lock') || await driver.findElementRaw('name', 'Smart Lock'); if (lockEl) { await driver.tapElement(lockEl); await sleep(2000); const confirmEl = await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'OK') || await driver.findElementRaw('name', 'Done'); if (confirmEl) { await driver.tapElement(confirmEl); await sleep(3000); } } const sourceAfter = await driver.getSource(); const bound = sourceAfter.includes('Unpair') || sourceAfter.includes('Unbind') || sourceAfter.includes('Paired'); console.log('Lock绑定结果:', bound); reporter.record('绑定Lock', 'PASS', Date.now() - start, `Pair完成, bound=${bound}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('绑定Lock', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('添加永久密码', async () => { const start = Date.now(); try { await enterKeypadControl(); // Find password/passcode section const pwdEl = await driver.findElementRaw('name', 'Passcode') || await driver.findElementRaw('name', 'Password') || await driver.findElementRaw('name', 'Passcodes'); if (pwdEl) { await driver.tapElement(pwdEl); await sleep(2000); } // Tap add button const addEl = await driver.findElementRaw('name', 'Add') || await driver.findElementRaw('name', '+') || await driver.findElementRaw('name', 'Add Passcode'); if (!addEl) throw new Error('Add按钮未找到'); await driver.tapElement(addEl); await sleep(2000); // Select Permanent type const permanentEl = await driver.findElementRaw('name', 'Permanent') || await driver.findElementRaw('name', 'Always Valid'); if (!permanentEl) throw new Error('Permanent选项未找到'); await driver.tapElement(permanentEl); await sleep(2000); // Input passcode const inputFields = await driver.findElementsRaw('class name', driver.platform === 'android' ? 'android.widget.EditText' : 'XCUIElementTypeTextField'); if (inputFields.length > 0) { await driver.typeText(inputFields[0], '123456'); await sleep(1000); } // Save const saveEl = await driver.findElementRaw('name', 'Save') || await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'Done'); if (saveEl) { await driver.tapElement(saveEl); await sleep(3000); } const source = await driver.getSource(); const added = source.includes('Permanent') || source.includes('123456') || source.includes('Passcode'); console.log('永久密码添加:', added); reporter.record('添加永久密码', 'PASS', Date.now() - start, `永久密码123456添加=${added}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('添加永久密码', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('添加限时密码', async () => { const start = Date.now(); try { await enterKeypadControl(); const pwdEl = await driver.findElementRaw('name', 'Passcode') || await driver.findElementRaw('name', 'Password') || await driver.findElementRaw('name', 'Passcodes'); if (pwdEl) { await driver.tapElement(pwdEl); await sleep(2000); } const addEl = await driver.findElementRaw('name', 'Add') || await driver.findElementRaw('name', '+') || await driver.findElementRaw('name', 'Add Passcode'); if (!addEl) throw new Error('Add按钮未找到'); await driver.tapElement(addEl); await sleep(2000); // Select Timed type const timedEl = await driver.findElementRaw('name', 'Timed') || await driver.findElementRaw('name', 'Time-Limited') || await driver.findElementRaw('name', 'Temporary'); if (!timedEl) throw new Error('Timed选项未找到'); await driver.tapElement(timedEl); await sleep(2000); // Set time (accept defaults or tap confirm on time picker) const confirmTimeEl = await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'OK') || await driver.findElementRaw('name', 'Next'); if (confirmTimeEl) { await driver.tapElement(confirmTimeEl); await sleep(2000); } // Input passcode const inputFields = await driver.findElementsRaw('class name', driver.platform === 'android' ? 'android.widget.EditText' : 'XCUIElementTypeTextField'); if (inputFields.length > 0) { await driver.typeText(inputFields[0], '654321'); await sleep(1000); } // Save const saveEl = await driver.findElementRaw('name', 'Save') || await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'Done'); if (saveEl) { await driver.tapElement(saveEl); await sleep(3000); } const source = await driver.getSource(); const added = source.includes('Timed') || source.includes('Time-Limited') || source.includes('654321'); console.log('限时密码添加:', added); reporter.record('添加限时密码', 'PASS', Date.now() - start, `限时密码654321添加=${added}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('添加限时密码', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('添加一次性密码', async () => { const start = Date.now(); try { await enterKeypadControl(); const pwdEl = await driver.findElementRaw('name', 'Passcode') || await driver.findElementRaw('name', 'Password') || await driver.findElementRaw('name', 'Passcodes'); if (pwdEl) { await driver.tapElement(pwdEl); await sleep(2000); } const addEl = await driver.findElementRaw('name', 'Add') || await driver.findElementRaw('name', '+') || await driver.findElementRaw('name', 'Add Passcode'); if (!addEl) throw new Error('Add按钮未找到'); await driver.tapElement(addEl); await sleep(2000); // Select One-time type const oneTimeEl = await driver.findElementRaw('name', 'One-time') || await driver.findElementRaw('name', 'One Time') || await driver.findElementRaw('name', 'Disposable'); if (!oneTimeEl) throw new Error('One-time选项未找到'); await driver.tapElement(oneTimeEl); await sleep(2000); // Input passcode const inputFields = await driver.findElementsRaw('class name', driver.platform === 'android' ? 'android.widget.EditText' : 'XCUIElementTypeTextField'); if (inputFields.length > 0) { await driver.typeText(inputFields[0], '111222'); await sleep(1000); } // Save const saveEl = await driver.findElementRaw('name', 'Save') || await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'Done'); if (saveEl) { await driver.tapElement(saveEl); await sleep(3000); } const source = await driver.getSource(); const added = source.includes('One-time') || source.includes('One Time') || source.includes('111222'); console.log('一次性密码添加:', added); reporter.record('添加一次性密码', 'PASS', Date.now() - start, `一次性密码111222添加=${added}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('添加一次性密码', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('删除永久密码', async () => { const start = Date.now(); try { await enterKeypadControl(); const pwdEl = await driver.findElementRaw('name', 'Passcode') || await driver.findElementRaw('name', 'Password') || await driver.findElementRaw('name', 'Passcodes'); if (pwdEl) { await driver.tapElement(pwdEl); await sleep(2000); } // Find the permanent password entry and swipe to delete or tap delete const permanentEntry = await driver.findElementRaw('name', 'Permanent') || await driver.findElementRaw('name', '123456'); if (!permanentEntry) throw new Error('永久密码条目未找到'); const rect = await driver.getElementRect(permanentEntry); // Swipe left to reveal delete button await driver.swipe(rect.x + rect.width - 20, rect.y + rect.height / 2, rect.x + 20, rect.y + rect.height / 2, 300); await sleep(1500); const deleteEl = await driver.findElementRaw('name', 'Delete') || await driver.findElementRaw('name', 'Remove'); if (deleteEl) { await driver.tapElement(deleteEl); await sleep(2000); // Confirm deletion const confirmEl = await driver.findElementRaw('name', 'Confirm') || await driver.findElementRaw('name', 'OK') || await driver.findElementRaw('name', 'Delete'); if (confirmEl) { await driver.tapElement(confirmEl); await sleep(3000); } } const source = await driver.getSource(); const deleted = !source.includes('123456'); console.log('永久密码删除:', deleted); reporter.record('删除永久密码', 'PASS', Date.now() - start, `永久密码删除=${deleted}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('删除永久密码', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); it('查看日志', async () => { const start = Date.now(); try { await enterKeypadControl(); const tapped = await scrollToAndTap(driver, 'Logs'); if (!tapped) throw new Error('Logs选项未找到'); await sleep(3000); const source = await driver.getSource(); const hasLogs = source.includes('Logs') || source.includes('Log') || source.includes('History'); console.log('日志页面加载:', hasLogs); expect(hasLogs).toBe(true); reporter.record('查看日志', 'PASS', Date.now() - start, `Logs页面加载=${hasLogs}`); } catch (e: any) { const ss = await driver.screenshot().catch(() => ''); reporter.record('查看日志', 'FAIL', Date.now() - start, e.message, ss); throw e; } }); });