diff --git a/lib/commands/find.js b/lib/commands/find.js index 5416d2634..6d5e9b167 100644 --- a/lib/commands/find.js +++ b/lib/commands/find.js @@ -41,6 +41,78 @@ export async function doFindElementOrEls(params) { ); } +/** + * Find a single element on a specific display. + * + * @param {Object} selector - Locator object, e.g. {id: 'com.xxx:id/title', text: 'Start'}. + * @param {number} displayIndex - Target display index (0 = primary, 1 = secondary, ...). + * @param {number} [timeout=1000] - Optional wait timeout in milliseconds. + * @param {string} [context] - Optional parent element ELEMENT ID for scoped search. + * @returns {Promise} - Promise resolving to a single element. + * @throws {Error} If displayIndex is not a number or element is not found within timeout. + */ +export async function mobileFindElementOnDisplay(selector, displayIndex, timeout, context) { + + const numericDisplayIndex = + typeof displayIndex === 'string' ? parseInt(displayIndex, 10) : displayIndex; + + if (typeof numericDisplayIndex !== 'number' || isNaN(numericDisplayIndex)) { + throw new Error('displayId must be provided and be a number'); + } + const waitTimeout = Math.max(timeout ?? 1000, 0); + + const params = { + selector, + displayIndex: numericDisplayIndex, + timeout: waitTimeout, + }; + + if (context) { + params.context = context; + } + + const uiautomator2 = this.uiautomator2; + const endpoint = `appium/element/on_display`; + + return await uiautomator2.jwproxy.command(endpoint, 'POST', params); +} + +/** + * Find multiple elements on a specific display. + * + * @param {Object} selector - Locator object, e.g. {className: 'android.widget.TextView'}. + * @param {number} displayIndex - Target display index (0 = primary, 1 = secondary, ...). + * @param {number} [timeout=1000] - Optional wait timeout in milliseconds. + * @param {string} [context] - Optional parent element ELEMENT ID for scoped search. + * @returns {Promise} - Promise resolving to an array of elements. + * @throws {Error} If displayIndex is not a number or elements are not found within timeout. + */ +export async function mobileFindElementsOnDisplay(selector, displayIndex, timeout, context) { + + const numericDisplayIndex = + typeof displayIndex === 'string' ? parseInt(displayIndex, 10) : displayIndex; + + if (typeof numericDisplayIndex !== 'number' || isNaN(numericDisplayIndex)) { + throw new Error('displayId must be provided and be a number'); + } + const waitTimeout = Math.max(timeout ?? 1000, 0); + + const params = { + selector, + displayIndex: numericDisplayIndex, + timeout: waitTimeout + }; + + if (context) { + params.context = context; + } + + const uiautomator2 = this.uiautomator2; + const endpoint = `appium/elements/on_display`; + + return await uiautomator2.jwproxy.command(endpoint, 'POST', params); +} + /** * @typedef {import('@appium/types').Element} Element * @typedef {import('../driver').AndroidUiautomator2Driver} AndroidUiautomator2Driver diff --git a/lib/commands/misc.js b/lib/commands/misc.js index 8bbd80c6c..fdfa5c834 100644 --- a/lib/commands/misc.js +++ b/lib/commands/misc.js @@ -72,6 +72,37 @@ export function suspendChromedriverProxy() { this.jwpProxyActive = true; } +/** + * Get information about all available displays + * @returns {Promise} List of display information + */ +export async function mobileGetDisplayInfo () { + return await this.uiautomator2.jwproxy.command( + '/appium/device/display_info', + 'GET' + ); +}; + +/** + * Get the page source for a specific display + * @this {AndroidUiautomator2Driver} + * @param {number} displayId + * @returns {Promise} + */ +export async function mobileGetPageSourceOnDisplay(displayId) { + let endpoint = '/appium/source/on_display'; + + if (displayId != null) { + const numericDisplayId = typeof displayId === 'string' ? parseInt(displayId, 10) : displayId; + if (isNaN(numericDisplayId)) { + throw new Error('displayId must be a number if provided'); + } + endpoint += `?displayId=${numericDisplayId}`; + } + + return String(await this.uiautomator2.jwproxy.command(endpoint, 'GET')); +} + /** * The list of available info entries can be found at * https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/handler/GetDeviceInfo.java diff --git a/lib/driver.ts b/lib/driver.ts index 02b72a990..abe8d9dd8 100644 --- a/lib/driver.ts +++ b/lib/driver.ts @@ -83,6 +83,8 @@ import { } from './commands/element'; import { doFindElementOrEls, + mobileFindElementOnDisplay, + mobileFindElementsOnDisplay, } from './commands/find'; import { mobileClickGesture, @@ -112,6 +114,8 @@ import { openNotifications, suspendChromedriverProxy, mobileGetDeviceInfo, + mobileGetDisplayInfo, + mobileGetPageSourceOnDisplay, } from './commands/misc'; import { setUrl, @@ -1029,6 +1033,8 @@ class AndroidUiautomator2Driver mobileReplaceElementValue = mobileReplaceElementValue; doFindElementOrEls = doFindElementOrEls; + mobileFindElementOnDisplay = mobileFindElementOnDisplay; + mobileFindElementsOnDisplay = mobileFindElementsOnDisplay; mobileClickGesture = mobileClickGesture; mobileDoubleClickGesture = mobileDoubleClickGesture; @@ -1055,6 +1061,8 @@ class AndroidUiautomator2Driver openNotifications = openNotifications; suspendChromedriverProxy = suspendChromedriverProxy as any; mobileGetDeviceInfo = mobileGetDeviceInfo; + mobileGetDisplayInfo = mobileGetDisplayInfo; + mobileGetPageSourceOnDisplay = mobileGetPageSourceOnDisplay; getClipboard = getClipboard; setClipboard = setClipboard; diff --git a/lib/execute-method-map.ts b/lib/execute-method-map.ts index 6f032b9e7..6b5111097 100644 --- a/lib/execute-method-map.ts +++ b/lib/execute-method-map.ts @@ -259,4 +259,27 @@ export const executeMethodMap = { 'mobile: getClipboard': { command: 'getClipboard', }, + 'mobile: findElementOnDisplay': { + command: 'mobileFindElementOnDisplay', + params: { + required: ['selector', 'displayIndex'], + optional: ['timeout', 'context'], + }, + }, + 'mobile: findElementsOnDisplay': { + command: 'mobileFindElementsOnDisplay', + params: { + required: ['selector', 'displayIndex'], + optional: ['timeout', 'context'], + }, + }, + 'mobile: getDisplayInfo': { + command: 'mobileGetDisplayInfo', + }, + 'mobile: getPageSourceOnDisplay': { + command: 'mobileGetPageSourceOnDisplay', + params: { + optional: ['displayIndex'], + }, + }, } as const satisfies ExecuteMethodMap;