diff --git a/docs/reference/bidi.md b/docs/reference/bidi.md index dbfd76915..9841ec3f2 100644 --- a/docs/reference/bidi.md +++ b/docs/reference/bidi.md @@ -45,3 +45,19 @@ Events are only emitted if the `appium:enablePerformanceLogging` capability valu Events are emitted for both emulator and real devices. Each event contains a single Appium server log line. Events are always emitted with the `NATIVE_APP` context. Events are only emitted if the `get_server_logs` server security feature is enabled. + +## appium.contextUpdate + +This event is emitted upon the context change, either explicit or implicit. +The event is always emitted upon new session initialization. +See the [GitHub feature ticket](https://github.com/appium/appium/issues/20741) for more details. + +The event contains the following params: + +### name + +Contains the actual name of the new context, for example `NATIVE_APP`. + +### type + +Either `NATIVE` or `WEB` depending on which context is currently active in the driver session. diff --git a/lib/commands/bidi/constants.ts b/lib/commands/bidi/constants.ts new file mode 100644 index 000000000..cc35fc151 --- /dev/null +++ b/lib/commands/bidi/constants.ts @@ -0,0 +1,3 @@ +export const BIDI_EVENT_NAME = 'bidiEvent'; +export const CONTEXT_UPDATED_EVENT = 'appium.contextUpdated'; +export const LOG_ENTRY_ADDED_EVENT = 'log.entryAdded'; diff --git a/lib/commands/bidi/models.ts b/lib/commands/bidi/models.ts new file mode 100644 index 000000000..c4116e47c --- /dev/null +++ b/lib/commands/bidi/models.ts @@ -0,0 +1,30 @@ +import type { LogEntryAddedEvent, ContextUpdatedEvent } from './types'; +import { NATIVE_WIN } from '../../utils'; +import { CONTEXT_UPDATED_EVENT, LOG_ENTRY_ADDED_EVENT } from './constants'; +import type { LogEntry } from '../types'; + +export function makeContextUpdatedEvent(contextName: string): ContextUpdatedEvent { + return { + method: CONTEXT_UPDATED_EVENT, + params: { + name: contextName, + type: contextName === NATIVE_WIN ? 'NATIVE' : 'WEB', + }, + }; +} + +export function makeLogEntryAddedEvent(entry: LogEntry, context: string, type: string): LogEntryAddedEvent { + return { + context, + method: LOG_ENTRY_ADDED_EVENT, + params: { + type, + level: entry.level, + source: { + realm: '', + }, + text: entry.message, + timestamp: entry.timestamp, + }, + }; +} diff --git a/lib/commands/bidi/types.ts b/lib/commands/bidi/types.ts new file mode 100644 index 000000000..e19e00f42 --- /dev/null +++ b/lib/commands/bidi/types.ts @@ -0,0 +1,29 @@ +interface BiDiEvent { + method: string; + params: TParams; +}; + +interface LogEntrySource { + realm: string; +} + +interface LogEntryAddedEventParams { + type: string; + level: string; + source: LogEntrySource; + text: string; + timestamp: number; +} + +// https://w3c.github.io/webdriver-bidi/#event-log-entryAdded +export interface LogEntryAddedEvent extends BiDiEvent { + context: string; +} + +interface ContentUpdatedParams { + name: string; + type: 'NATIVE' | 'WEB'; +} + +// https://github.com/appium/appium/issues/20741 +export interface ContextUpdatedEvent extends BiDiEvent {} diff --git a/lib/commands/context.js b/lib/commands/context.js index ad1a541d9..6041baa7f 100644 --- a/lib/commands/context.js +++ b/lib/commands/context.js @@ -4,6 +4,9 @@ import {util, timing} from 'appium/support'; import IOSPerformanceLog from '../device-log/ios-performance-log'; import _ from 'lodash'; import { NATIVE_WIN } from '../utils'; +import { makeContextUpdatedEvent } from './bidi/models'; +import { BIDI_EVENT_NAME } from './bidi/constants'; +import { assignBiDiLogListener } from './log'; const WEBVIEW_WIN = 'WEBVIEW'; const WEBVIEW_BASE = `${WEBVIEW_WIN}_`; @@ -345,6 +348,7 @@ const extensions = { (async () => { try { await /** @type {RemoteDebugger} */ (this.remote).selectPage(appIdKey, parseInt(newPage, 10)); + await notifyBiDiContextChange.bind(this)(); } catch (e) { this.log.warn(`Failed to select page: ${e.message}`); this.curContext = oldContext; @@ -372,6 +376,7 @@ const helpers = { } await this.remote.disconnect(); this.curContext = null; + await notifyBiDiContextChange.bind(this)(); this.curWebFrames = []; this.remote = null; }, @@ -519,6 +524,7 @@ const commands = { if (isNativeContext(strName)) { // switching into the native context this.curContext = null; + await notifyBiDiContextChange.bind(this)(); return; } @@ -548,6 +554,7 @@ const commands = { try { this.selectingNewPage = true; await (/** @type {RemoteDebugger} */ (this.remote)).selectPage(appIdKey, pageIdKey, skipReadyCheck); + await notifyBiDiContextChange.bind(this)(); } catch (err) { this.curContext = this.curWindowHandle = oldContext; throw err; @@ -559,7 +566,7 @@ const commands = { if (this.opts.enablePerformanceLogging && this.remote) { const context = this.curContext; this.log.debug(`Starting performance log on '${context}'`); - [this.logs.performance,] = this.assignBiDiLogListener( + [this.logs.performance,] = assignBiDiLogListener.bind(this)( new IOSPerformanceLog({ remoteDebugger: this.remote, log: this.log, @@ -699,6 +706,17 @@ function isUrlIgnored(url, safariIgnoreWebHostnames) { return false; } +/** + * https://github.com/appium/appium/issues/20741 + * + * @this {XCUITestDriver} + * @returns {Promise} + */ +export async function notifyBiDiContextChange() { + const name = await this.getCurrentContext(); + this.eventEmitter.emit(BIDI_EVENT_NAME, makeContextUpdatedEvent(name)); +} + export default {...helpers, ...extensions, ...commands}; /** diff --git a/lib/commands/log.js b/lib/commands/log.js index 558815541..5210456cc 100644 --- a/lib/commands/log.js +++ b/lib/commands/log.js @@ -8,7 +8,9 @@ import WebSocket from 'ws'; import SafariConsoleLog from '../device-log/safari-console-log'; import SafariNetworkLog from '../device-log/safari-network-log'; import { toLogEntry } from '../device-log/helpers'; -import { NATIVE_WIN, BIDI_EVENT_NAME } from '../utils'; +import { NATIVE_WIN } from '../utils'; +import { BIDI_EVENT_NAME } from './bidi/constants'; +import { makeLogEntryAddedEvent } from './bidi/models'; /** * Determines the websocket endpoint based on the `sessionId` @@ -107,42 +109,6 @@ export default { ); }, - /** - * https://w3c.github.io/webdriver-bidi/#event-log-entryAdded - * - * @template {import('node:events').EventEmitter} EE - * @this {XCUITestDriver} - * @param {EE} logEmitter - * @param {BiDiListenerProperties} properties - * @returns {[EE, import('./types').LogListener]} - */ - assignBiDiLogListener (logEmitter, properties) { - const { - type, - context = NATIVE_WIN, - srcEventName = 'output', - entryTransformer, - } = properties; - const listener = (/** @type {import('./types').LogEntry} */ logEntry) => { - const finalEntry = entryTransformer ? entryTransformer(logEntry) : logEntry; - this.eventEmitter.emit(BIDI_EVENT_NAME, { - context, - method: 'log.entryAdded', - params: { - type, - level: finalEntry.level, - source: { - realm: '', - }, - text: finalEntry.message, - timestamp: finalEntry.timestamp, - }, - }); - }; - logEmitter.on(srcEventName, listener); - return [logEmitter, listener]; - }, - /** * @this {XCUITestDriver} */ @@ -154,7 +120,7 @@ export default { } if (_.isUndefined(this.logs.syslog)) { - [this.logs.crashlog,] = this.assignBiDiLogListener( + [this.logs.crashlog,] = assignBiDiLogListener.bind(this)( new IOSCrashLog({ sim: /** @type {import('appium-ios-simulator').Simulator} */ (this.device), udid: this.isRealDevice() ? this.opts.udid : undefined, @@ -163,7 +129,7 @@ export default { type: 'crashlog', } ); - [this.logs.syslog,] = this.assignBiDiLogListener( + [this.logs.syslog,] = assignBiDiLogListener.bind(this)( this.isRealDevice() ? new IOSDeviceLog({ udid: this.opts.udid, @@ -182,7 +148,7 @@ export default { } ); if (_.isBoolean(this.opts.showSafariConsoleLog)) { - [this.logs.safariConsole,] = this.assignBiDiLogListener( + [this.logs.safariConsole,] = assignBiDiLogListener.bind(this)( new SafariConsoleLog({ showLogs: this.opts.showSafariConsoleLog, log: this.log, @@ -192,7 +158,7 @@ export default { ); } if (_.isBoolean(this.opts.showSafariNetworkLog)) { - [this.logs.safariNetwork,] = this.assignBiDiLogListener( + [this.logs.safariNetwork,] = assignBiDiLogListener.bind(this)( new SafariNetworkLog({ showLogs: this.opts.showSafariNetworkLog, log: this.log, @@ -202,7 +168,7 @@ export default { ); } if (this.isFeatureEnabled(GET_SERVER_LOGS_FEATURE)) { - [, this._bidiServerLogListener] = this.assignBiDiLogListener( + [, this._bidiServerLogListener] = assignBiDiLogListener.bind(this)( this.log.unwrap(), { type: 'server', srcEventName: 'log', @@ -323,14 +289,32 @@ export default { }; /** - * @typedef {import('../driver').XCUITestDriver} XCUITestDriver + * https://w3c.github.io/webdriver-bidi/#event-log-entryAdded + * + * @template {import('node:events').EventEmitter} EE + * @this {XCUITestDriver} + * @param {EE} logEmitter + * @param {BiDiListenerProperties} properties + * @returns {[EE, import('./types').LogListener]} */ +export function assignBiDiLogListener (logEmitter, properties) { + const { + type, + context = NATIVE_WIN, + srcEventName = 'output', + entryTransformer, + } = properties; + const listener = (/** @type {import('./types').LogEntry} */ logEntry) => { + const finalEntry = entryTransformer ? entryTransformer(logEntry) : logEntry; + this.eventEmitter.emit(BIDI_EVENT_NAME, makeLogEntryAddedEvent(finalEntry, context, type)); + }; + logEmitter.on(srcEventName, listener); + return [logEmitter, listener]; +} /** + * @typedef {import('../driver').XCUITestDriver} XCUITestDriver * @typedef {keyof typeof SUPPORTED_LOG_TYPES} XCUITestDriverLogTypes - */ - -/** * @typedef {import('@appium/types').AppiumServer} AppiumServer */ diff --git a/lib/driver.js b/lib/driver.js index a02376a38..563f41946 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -62,6 +62,7 @@ import { translateDeviceName, } from './utils'; import { AppInfosCache } from './app-infos-cache'; +import { notifyBiDiContextChange } from './commands/context'; const SHUTDOWN_OTHER_FEAT_NAME = 'shutdown_other_sims'; const CUSTOMIZE_RESULT_BUNDLE_PATH = 'customize_result_bundle_path'; @@ -660,6 +661,9 @@ export class XCUITestDriver extends BaseDriver { if (this.isSafari() || this.opts.autoWebview) { await this.activateRecentWebview(); + } else { + // We want to always setup the initial context value upon session startup + await notifyBiDiContextChange.bind(this)(); } if (this.isSafari()) { if (shouldSetInitialSafariUrl(this.opts)) { @@ -2017,7 +2021,6 @@ export class XCUITestDriver extends BaseDriver { extractLogs = commands.logExtensions.extractLogs; supportedLogTypes = commands.logExtensions.supportedLogTypes; startLogCapture = commands.logExtensions.startLogCapture; - assignBiDiLogListener = commands.logExtensions.assignBiDiLogListener; mobileStartLogsBroadcast = commands.logExtensions.mobileStartLogsBroadcast; mobileStopLogsBroadcast = commands.logExtensions.mobileStopLogsBroadcast; diff --git a/lib/utils.js b/lib/utils.js index 918eee3a1..bc50e8844 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -21,7 +21,6 @@ const XCTEST_LOG_FILES_PATTERNS = [ ]; const XCTEST_LOGS_CACHE_FOLDER_PREFIX = 'com.apple.dt.XCTest'; export const NATIVE_WIN = 'NATIVE_APP'; -export const BIDI_EVENT_NAME = 'bidiEvent'; /** * @privateRemarks Is the minimum version really Xcode 7.3?