diff --git a/lib/commands/context/exports.js b/lib/commands/context/exports.js index 0d3e50b2..086771c6 100644 --- a/lib/commands/context/exports.js +++ b/lib/commands/context/exports.js @@ -1,7 +1,7 @@ /* eslint-disable require-await */ import {util} from '@appium/support'; import Chromedriver from 'appium-chromedriver'; -import {errors} from 'appium/driver'; +import {errors, PROTOCOLS} from 'appium/driver'; import _ from 'lodash'; import { CHROMIUM_WIN, @@ -18,8 +18,11 @@ import { } from './helpers'; import {APP_STATE} from '../app-management'; +// https://github.com/appium/appium/issues/20710 +const DEFAULT_NATIVE_WINDOW_HANDLE = '1'; + /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {Promise} */ export async function getCurrentContext() { @@ -29,7 +32,7 @@ export async function getCurrentContext() { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {Promise} */ export async function getContexts() { @@ -38,7 +41,7 @@ export async function getContexts() { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @param {string?} name * @returns {Promise} */ @@ -66,7 +69,7 @@ export async function setContext(name) { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @param {any} [opts={}] * @returns {Promise} */ @@ -82,7 +85,7 @@ export async function mobileGetContexts(opts = {}) { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @param {import('../types').WebviewsMapping[]} webviewsMapping * @returns {string[]} */ @@ -95,7 +98,7 @@ export function assignContexts(webviewsMapping) { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @param {string} name * @param {import('../types').WebviewsMapping[]} webviewsMapping * @returns {Promise} @@ -123,7 +126,7 @@ export async function switchContext(name, webviewsMapping) { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {string} */ export function defaultContextName() { @@ -131,7 +134,7 @@ export function defaultContextName() { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {string} */ export function defaultWebviewName() { @@ -139,17 +142,63 @@ export function defaultWebviewName() { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {boolean} */ export function isWebContext() { return this.curContext !== null && this.curContext !== NATIVE_WIN; } +/** + * @this {AndroidDriver} + * @returns {Promise} + */ +export async function getWindowHandle() { + if (!this.isWebContext()) { + return DEFAULT_NATIVE_WINDOW_HANDLE; + } + + const chromedriver = /** @type {Chromedriver} */ (this.chromedriver); + const isJwp = chromedriver.jwproxy.downstreamProtocol === PROTOCOLS.MJSONWP; + const endpoint = isJwp ? '/window_handle' : '/window/handle'; + return /** @type {string} */ (await chromedriver.jwproxy.command(endpoint, 'GET')); +} + +/** + * @this {AndroidDriver} + * @returns {Promise} + */ +export async function getWindowHandles() { + if (!this.isWebContext()) { + return [DEFAULT_NATIVE_WINDOW_HANDLE]; + } + + const chromedriver = /** @type {Chromedriver} */ (this.chromedriver); + const isJwp = chromedriver.jwproxy.downstreamProtocol === PROTOCOLS.MJSONWP; + const endpoint = isJwp ? '/window_handles' : '/window/handles'; + return /** @type {string[]} */ (await chromedriver.jwproxy.command(endpoint, 'GET')); +} + +/** + * @this {AndroidDriver} + * @param {string} handle + * @returns {Promise} + */ +export async function setWindow(handle) { + if (!this.isWebContext()) { + return; + } + + const chromedriver = /** @type {Chromedriver} */ (this.chromedriver); + const isJwp = chromedriver.jwproxy.downstreamProtocol === PROTOCOLS.MJSONWP; + const paramName = isJwp ? 'name' : 'handle'; + await chromedriver.jwproxy.command('/window', 'POST', {[paramName]: handle}); +} + /** * Turn on proxying to an existing Chromedriver session or a new one * - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @param {string} context * @param {import('../types').WebviewsMapping[]} webviewsMapping * @returns {Promise} @@ -255,7 +304,7 @@ export async function startChromedriverProxy(context, webviewsMapping) { /** * Stop proxying to any Chromedriver * - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {void} */ export function suspendChromedriverProxy() { @@ -268,7 +317,7 @@ export function suspendChromedriverProxy() { /** * Handle an out-of-band Chromedriver stop event * - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @param {string} context * @returns {Promise} */ @@ -293,7 +342,7 @@ export async function onChromedriverStop(context) { * Intentionally stop all the chromedrivers currently active, and ignore * their exit events * - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {Promise} */ export async function stopChromedriverProxies() { @@ -313,7 +362,7 @@ export async function stopChromedriverProxies() { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @param {string} viewName * @returns {boolean} */ @@ -322,7 +371,7 @@ export function isChromedriverContext(viewName) { } /** - * @this {import('../../driver').AndroidDriver} + * @this {AndroidDriver} * @returns {Promise} */ export async function startChromeSession() { @@ -376,4 +425,5 @@ export async function startChromeSession() { /** * @typedef {import('appium-adb').ADB} ADB + * @typedef {import('../../driver').AndroidDriver} AndroidDriver */ diff --git a/lib/driver.ts b/lib/driver.ts index 3f9b64cb..99259a92 100644 --- a/lib/driver.ts +++ b/lib/driver.ts @@ -35,6 +35,9 @@ import { suspendChromedriverProxy, startChromeSession, mobileGetContexts, + getWindowHandle, + getWindowHandles, + setWindow, } from './commands/context/exports'; import { getDeviceInfoFromCaps, @@ -365,6 +368,9 @@ class AndroidDriver isWebContext = isWebContext; mobileGetContexts = mobileGetContexts; setContext = setContext as any as (this: AndroidDriver, name?: string) => Promise; + setWindow = setWindow; + getWindowHandle = getWindowHandle; + getWindowHandles = getWindowHandles; getDeviceInfoFromCaps = getDeviceInfoFromCaps; createADB = createADB;