Skip to content

Commit

Permalink
feat: Add a BiDi event upon context change (#2494)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Nov 16, 2024
1 parent 3ba6c2b commit 09c824d
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 49 deletions.
16 changes: 16 additions & 0 deletions docs/reference/bidi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions lib/commands/bidi/constants.ts
Original file line number Diff line number Diff line change
@@ -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';
30 changes: 30 additions & 0 deletions lib/commands/bidi/models.ts
Original file line number Diff line number Diff line change
@@ -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,
},
};
}
29 changes: 29 additions & 0 deletions lib/commands/bidi/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
interface BiDiEvent<TParams> {
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<LogEntryAddedEventParams> {
context: string;
}

interface ContentUpdatedParams {
name: string;
type: 'NATIVE' | 'WEB';
}

// https://github.com/appium/appium/issues/20741
export interface ContextUpdatedEvent extends BiDiEvent<ContentUpdatedParams> {}
20 changes: 19 additions & 1 deletion lib/commands/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}_`;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -372,6 +376,7 @@ const helpers = {
}
await this.remote.disconnect();
this.curContext = null;
await notifyBiDiContextChange.bind(this)();
this.curWebFrames = [];
this.remote = null;
},
Expand Down Expand Up @@ -519,6 +524,7 @@ const commands = {
if (isNativeContext(strName)) {
// switching into the native context
this.curContext = null;
await notifyBiDiContextChange.bind(this)();
return;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -699,6 +706,17 @@ function isUrlIgnored(url, safariIgnoreWebHostnames) {
return false;
}

/**
* https://github.com/appium/appium/issues/20741
*
* @this {XCUITestDriver}
* @returns {Promise<void>}
*/
export async function notifyBiDiContextChange() {
const name = await this.getCurrentContext();
this.eventEmitter.emit(BIDI_EVENT_NAME, makeContextUpdatedEvent(name));
}

export default {...helpers, ...extensions, ...commands};

/**
Expand Down
76 changes: 30 additions & 46 deletions lib/commands/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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}
*/
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -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
*/

Expand Down
5 changes: 4 additions & 1 deletion lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;

Expand Down
1 change: 0 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down

0 comments on commit 09c824d

Please sign in to comment.