diff --git a/CHANGELOG.md b/CHANGELOG.md index c950b347e..6741a36c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## [7.26.4](https://github.com/appium/appium-xcuitest-driver/compare/v7.26.3...v7.26.4) (2024-09-17) + +### Miscellaneous Chores + +* **deps-dev:** bump sinon from 18.0.1 to 19.0.2 ([#2469](https://github.com/appium/appium-xcuitest-driver/issues/2469)) ([e737dde](https://github.com/appium/appium-xcuitest-driver/commit/e737dde53880b70536440cec84037ad58ed7f4ab)) + +## [7.26.3](https://github.com/appium/appium-xcuitest-driver/compare/v7.26.2...v7.26.3) (2024-09-13) + +### Bug Fixes + +* Strip colors from server logs ([#2466](https://github.com/appium/appium-xcuitest-driver/issues/2466)) ([661f9d3](https://github.com/appium/appium-xcuitest-driver/commit/661f9d3f0fcb0d483de137fc542de141f077b1dc)) + +## [7.26.2](https://github.com/appium/appium-xcuitest-driver/compare/v7.26.1...v7.26.2) (2024-09-07) + +### Miscellaneous Chores + +* Set static event realm ([a08ce4f](https://github.com/appium/appium-xcuitest-driver/commit/a08ce4ffb3e73e21fed4d7a28d0004bcaa899bde)) + +## [7.26.1](https://github.com/appium/appium-xcuitest-driver/compare/v7.26.0...v7.26.1) (2024-09-06) + +### Bug Fixes + +* safari process management will be done by WDA for real devices ([#2464](https://github.com/appium/appium-xcuitest-driver/issues/2464)) ([18eddc3](https://github.com/appium/appium-xcuitest-driver/commit/18eddc3c48a8361182511a14e46915d6db71cda2)) + +## [7.26.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.25.0...v7.26.0) (2024-09-05) + +### Features + +* Publish different log types over BiDi ([#2458](https://github.com/appium/appium-xcuitest-driver/issues/2458)) ([29e9d5e](https://github.com/appium/appium-xcuitest-driver/commit/29e9d5e18c9fe2523dd586d7a32fdce870a81ff0)) + ## [7.25.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.24.18...v7.25.0) (2024-08-31) ### Features diff --git a/docs/guides/troubleshooting.md b/docs/guides/troubleshooting.md index 35cfd0da0..878bab81b 100644 --- a/docs/guides/troubleshooting.md +++ b/docs/guides/troubleshooting.md @@ -49,6 +49,25 @@ XCUITest driver offers a couple of approaches to handle them: [`mobile: activeAppInfo`](../reference/execute-methods.md#mobile-activateappinfo) helps to understand what application (bundleId) is considered as active for the XCUITest driver. +## Interact with dialogs managed by `com.apple.ContactsUI.LimitedAccessPromptView` + +iOS 18 introduced a new process named `com.apple.ContactsUI.LimitedAccessPromptView`. See [this issue](https://github.com/appium/appium/issues/20591) for more details. +As of XCUITest driver v7.26.4, the only workaround to interact with views available through the process is the below method: + +- `defaultActiveApplication` setting in [Settings](../reference/settings.md). + - e.g. With the [Appium Ruby client](https://github.com/appium/ruby_lib_core) + ```ruby + # Interacting with the test target + driver.settings.update({defaultActiveApplication: "com.apple.ContactsUI.LimitedAccessPromptView"}) + # to accept the alert + driver.find_element("accessibility_id", "Select Contacts").click + driver.settings.update({defaultActiveApplication: "auto"}) + # keep interacting with the test target + ``` + +The `com.apple.ContactsUI.LimitedAccessPromptView` process can get elements available through `com.apple.springboard`, such as several system permission dialogs. +iOS 18+ devices may be possible to use `com.apple.ContactsUI.LimitedAccessPromptView` to interact with elements managed either by `com.apple.ContactsUI.LimitedAccessPromptView` or `com.apple.springboard`. + ## Leftover Application Data on Real Devices There might be a situation where application data is present on the real device, even if the diff --git a/docs/reference/bidi.md b/docs/reference/bidi.md new file mode 100644 index 000000000..dbfd76915 --- /dev/null +++ b/docs/reference/bidi.md @@ -0,0 +1,47 @@ +--- +title: BiDi Protocol Support +--- + +XCUITest driver has partial support of the [BiDi Protocol](https://w3c.github.io/webdriver-bidi/) since version 7.26.0. +Only events and commands mentioned below are supported. +All other entities described in the spec throw not implemented errors. + +# Supported Events + +## log.entryAdded + +This event is emitted if the driver retrieves a new entry for any of the below log types. Logs collection might be disabled by the `appium:skipLogCapture` capability. + +### crashlog + +Events are emitted for both emulator and real devices. The latter only works if [py-ios-device](https://github.com/YueChen-C/py-ios-device) is installed on the server host. Each event contains a particular device crash report entry. +Events are always emitted with the `NATIVE_APP` context. + +### syslog + +Events are emitted for both emulator and real devices. Each event contains a single device system log line. +Events are always emitted with the `NATIVE_APP` context. + +### safariConsole + +Events are emitted for both emulator and real devices. Each event contains a single Safari console log line. +Events are always emitted with the appropriate web context name from which they were generated. +Events are only emitted if the `appium:showSafariConsoleLog` capability value is provided. + +### safariNetwork + +Events are emitted for both emulator and real devices. Each event contains a single Safari network log line. +Events are always emitted with the appropriate web context name from which they were generated. +Events are only emitted if the `appium:showSafariNetworkLog` capability value is provided. + +### performance + +Events are emitted for both emulator and real devices. Each event contains a single Safari performance log line. +Events are always emitted with the appropriate web context name from which they were generated. +Events are only emitted if the `appium:enablePerformanceLogging` capability value is provided. + +### server + +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. diff --git a/docs/reference/capabilities.md b/docs/reference/capabilities.md index d69f59b03..413828fbe 100644 --- a/docs/reference/capabilities.md +++ b/docs/reference/capabilities.md @@ -80,6 +80,7 @@ about capabilities, refer to the [Appium documentation](https://appium.io/docs/e |`appium:simpleIsVisibleCheck`|Use native methods for determining visibility of elements. In some cases this takes a long time. Setting this capability to `false` will cause the system to use the position and size of elements to make sure they are visible on the screen. This can, however, lead to false results in some situations. Defaults to `false`. | `true`, `false`| | **Deprecated** `appium:waitForQuiescence`| It allows to turn on/off waiting for application quiescence in `WebDriverAgent`, while performing queries. The default value is `true`. You can avoid [this kind of issues](https://github.com/appium/appium/issues/11132) if you turn it off. Consider using `waitForIdleTimeout` capability instead for this purpose since Appium 1.20.0 | `false` | |`appium:mjpegServerPort`|The port number on which WDA broadcasts screenshots stream encoded into MJPEG format from the device under test. It might be necessary to change this value if the default port is busy because of other tests running in parallel. Default value: `9100`|`12000`| +|`appium:mjpegScreenshotUrl` | The URL of a service that provides realtime device screenshots in MJPEG format. If provided then the actual command to retrieve a screenshot will be requesting pictures from this service rather than directly from the server. Appium does not handle port forward etc to the URL. | `http://:9100` | |`appium:screenshotQuality`| Changes the initial quality of display screenshots. This capability affects the screenshoting speed and the actual quality of resulting screenshots. Before version 5.4.0 of WebDriverAgent possible values were: `0`, `1` (default), `2`, where `0` abbreviates lossless PNG, `1` is a high-quality JPEG and `2` is a low-quality JPEG. In the version 5.4.0 one more mode has been added (`3`), which is now the default one. It abbreviates lossless HEIC with fallback to PNG if the device does not support hardware-accelerated HEIC encoding. You can also change the value of screenshotQuality in [settings](settings.md). | `2` | |`appium:autoAcceptAlerts`| Accept all iOS alerts automatically if they pop up. This includes privacy access permission alerts (location, contacts, photos). Default is `false`. |`true` or `false`| |`appium:autoDismissAlerts`| Dismiss all iOS alerts automatically if they pop up. This includes privacy access permission alerts (location, contacts, photos). Default is `false`. |`true` or `false`| diff --git a/lib/commands/context.js b/lib/commands/context.js index b2ba44286..f7f0fda2d 100644 --- a/lib/commands/context.js +++ b/lib/commands/context.js @@ -3,8 +3,8 @@ import {errors, isErrorType} from 'appium/driver'; import {util, timing} from 'appium/support'; import IOSPerformanceLog from '../device-log/ios-performance-log'; import _ from 'lodash'; +import { NATIVE_WIN } from '../utils'; -const NATIVE_WIN = 'NATIVE_APP'; const WEBVIEW_WIN = 'WEBVIEW'; const WEBVIEW_BASE = `${WEBVIEW_WIN}_`; const DEFAULT_REMOTE_DEBUGGER_CONNECT_TIMEOUT_MS = 5000; @@ -556,12 +556,18 @@ const commands = { // attempt to start performance logging, if requested if (this.opts.enablePerformanceLogging && this.remote) { - this.log.debug(`Starting performance log on '${this.curContext}'`); - this.logs.performance = new IOSPerformanceLog({ - remoteDebugger: this.remote, - log: this.log, - }); - await this.logs.performance.startCapture(); + const context = this.curContext; + this.log.debug(`Starting performance log on '${context}'`); + [this.logs.performance,] = this.assignBiDiLogListener( + new IOSPerformanceLog({ + remoteDebugger: this.remote, + log: this.log, + }), { + type: 'performance', + context, + } + ); + await this.logs.performance?.startCapture(); } // start safari logging if the logs handlers are active diff --git a/lib/commands/log.js b/lib/commands/log.js index 87a1ad622..439e0adf6 100644 --- a/lib/commands/log.js +++ b/lib/commands/log.js @@ -4,11 +4,11 @@ import {DEFAULT_WS_PATHNAME_PREFIX} from 'appium/driver'; import {IOSCrashLog} from '../device-log/ios-crash-log'; import {IOSSimulatorLog} from '../device-log/ios-simulator-log'; import {IOSDeviceLog} from '../device-log/ios-device-log'; -import log from '../logger'; 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'; /** * Determines the websocket endpoint based on the `sessionId` @@ -17,9 +17,22 @@ import { toLogEntry } from '../device-log/helpers'; */ const WEBSOCKET_ENDPOINT = (sessionId) => `${DEFAULT_WS_PATHNAME_PREFIX}/session/${sessionId}/appium/device/syslog`; - +const COLOR_CODE_PATTERN = /\u001b\[(\d+(;\d+)*)?m/g; // eslint-disable-line no-control-regex const GET_SERVER_LOGS_FEATURE = 'get_server_logs'; +/** + * + * @param {Object} x + * @returns {import('./types').LogEntry} + */ +function nativeLogEntryToSeleniumEntry (x) { + const msg = _.isEmpty(x.prefix) ? x.message : `[${x.prefix}] ${x.message}`; + return toLogEntry( + _.replace(msg, COLOR_CODE_PATTERN, ''), + /** @type {any} */ (x).timestamp ?? Date.now() + ); +} + /** * @type {import('@appium/types').LogDefRecord} * @privateRemarks The return types for these getters should be specified @@ -52,10 +65,7 @@ const SUPPORTED_LOG_TYPES = { */ getter: (self) => { self.assertFeatureEnabled(GET_SERVER_LOGS_FEATURE); - return log.unwrap().record.map((x) => toLogEntry( - _.isEmpty(x.prefix) ? x.message : `[${x.prefix}] ${x.message}`, - /** @type {any} */ (x).timestamp ?? Date.now() - )); + return self.log.unwrap().record.map(nativeLogEntryToSeleniumEntry); }, }, }; @@ -97,44 +107,107 @@ 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} */ async startLogCapture() { this.logs = this.logs || {}; if (!_.isUndefined(this.logs.syslog) && this.logs.syslog.isCapturing) { - log.warn('Trying to start iOS log capture but it has already started!'); + this.log.warn('Trying to start iOS log capture but it has already started!'); return true; } + if (_.isUndefined(this.logs.syslog)) { - this.logs.crashlog = new IOSCrashLog({ - sim: /** @type {import('appium-ios-simulator').Simulator} */ (this.device), - udid: this.isRealDevice() ? this.opts.udid : undefined, - log: this.log, - }); - this.logs.syslog = this.isRealDevice() - ? new IOSDeviceLog({ - udid: this.opts.udid, - showLogs: this.opts.showIOSLog, - log: this.log, - }) - : new IOSSimulatorLog({ + [this.logs.crashlog,] = this.assignBiDiLogListener( + new IOSCrashLog({ sim: /** @type {import('appium-ios-simulator').Simulator} */ (this.device), - showLogs: this.opts.showIOSLog, - iosSimulatorLogsPredicate: this.opts.iosSimulatorLogsPredicate, + udid: this.isRealDevice() ? this.opts.udid : undefined, log: this.log, - }); + }), { + type: 'crashlog', + } + ); + [this.logs.syslog,] = this.assignBiDiLogListener( + this.isRealDevice() + ? new IOSDeviceLog({ + udid: this.opts.udid, + showLogs: this.opts.showIOSLog, + log: this.log, + }) + : new IOSSimulatorLog({ + sim: /** @type {import('appium-ios-simulator').Simulator} */ (this.device), + showLogs: this.opts.showIOSLog, + iosSimulatorLogsPredicate: this.opts.iosSimulatorLogsPredicate, + log: this.log, + }), + { + type: 'syslog', + } + ); if (_.isBoolean(this.opts.showSafariConsoleLog)) { - this.logs.safariConsole = new SafariConsoleLog({ - showLogs: this.opts.showSafariConsoleLog, - log: this.log, - }); + [this.logs.safariConsole,] = this.assignBiDiLogListener( + new SafariConsoleLog({ + showLogs: this.opts.showSafariConsoleLog, + log: this.log, + }), { + type: 'safariConsole', + } + ); } if (_.isBoolean(this.opts.showSafariNetworkLog)) { - this.logs.safariNetwork = new SafariNetworkLog({ - showLogs: this.opts.showSafariNetworkLog, - log: this.log, - }); + [this.logs.safariNetwork,] = this.assignBiDiLogListener( + new SafariNetworkLog({ + showLogs: this.opts.showSafariNetworkLog, + log: this.log, + }), { + type: 'safariNetwork', + } + ); + } + if (this.isFeatureEnabled(GET_SERVER_LOGS_FEATURE)) { + [, this._bidiServerLogListener] = this.assignBiDiLogListener( + this.log.unwrap(), { + type: 'server', + srcEventName: 'log', + entryTransformer: nativeLogEntryToSeleniumEntry, + } + ); } } @@ -143,15 +216,15 @@ export default { const promises = [ (async () => { try { - await this.logs.syslog.startCapture(); + await this.logs.syslog?.startCapture(); didStartSyslog = true; this.eventEmitter.emit('syslogStarted', this.logs.syslog); } catch (err) { - log.debug(err.stack); - log.warn(`Continuing without capturing device logs: ${err.message}`); + this.log.debug(err.stack); + this.log.warn(`Continuing without capturing device logs: ${err.message}`); } })(), - this.logs.crashlog.startCapture(), + this.logs.crashlog?.startCapture() ?? B.resolve(), ]; await B.all(promises); @@ -179,13 +252,13 @@ export default { ).getWebSocketHandlers(pathname), ) ) { - log.debug( + this.log.debug( `The system logs broadcasting web socket server is already listening at ${pathname}`, ); return; } - log.info(`Assigning system logs broadcasting web socket server to ${pathname}`); + this.log.info(`Assigning system logs broadcasting web socket server to ${pathname}`); // https://github.com/websockets/ws/blob/master/doc/ws.md const wss = new WebSocket.Server({ noServer: true, @@ -195,9 +268,9 @@ export default { const remoteIp = _.isEmpty(req.headers['x-forwarded-for']) ? req.connection?.remoteAddress : req.headers['x-forwarded-for']; - log.debug(`Established a new system logs listener web socket connection from ${remoteIp}`); + this.log.debug(`Established a new system logs listener web socket connection from ${remoteIp}`); } else { - log.debug('Established a new system logs listener web socket connection'); + this.log.debug('Established a new system logs listener web socket connection'); } if (_.isEmpty(this._syslogWebsocketListener)) { @@ -207,11 +280,11 @@ export default { } }; } - this.logs.syslog.on('output', this._syslogWebsocketListener); + this.logs.syslog?.on('output', this._syslogWebsocketListener); ws.on('close', (code, reason) => { if (!_.isEmpty(this._syslogWebsocketListener)) { - this.logs.syslog.removeListener('output', this._syslogWebsocketListener); + this.logs.syslog?.removeListener('output', this._syslogWebsocketListener); this._syslogWebsocketListener = null; } @@ -222,7 +295,7 @@ export default { if (!_.isEmpty(reason)) { closeMsg += ` Reason: ${reason.toString()}.`; } - log.debug(closeMsg); + this.log.debug(closeMsg); }); }); await /** @type {AppiumServer} */ (this.server).addWebSocketHandler( @@ -243,7 +316,7 @@ export default { return; } - log.debug('Stopping the system logs broadcasting web socket server'); + this.log.debug('Stopping the system logs broadcasting web socket server'); await /** @type {AppiumServer} */ (this.server).removeWebSocketHandler(pathname); }, }; @@ -259,3 +332,11 @@ export default { /** * @typedef {import('@appium/types').AppiumServer} AppiumServer */ + +/** + * @typedef {Object} BiDiListenerProperties + * @property {string} type + * @property {string} [srcEventName='output'] + * @property {string} [context=NATIVE_WIN] + * @property {(x: Object) => import('./types').LogEntry} [entryTransformer] + */ diff --git a/lib/commands/types.ts b/lib/commands/types.ts index 2b7ec7544..acb255596 100644 --- a/lib/commands/types.ts +++ b/lib/commands/types.ts @@ -572,3 +572,5 @@ export interface LogEntry { level: string, message: string; } + +export type LogListener = (logEntry: LogEntry) => any; diff --git a/lib/doctor/optional-checks.js b/lib/doctor/optional-checks.js index 3b0850a3e..03f7ee29d 100644 --- a/lib/doctor/optional-checks.js +++ b/lib/doctor/optional-checks.js @@ -39,7 +39,7 @@ export const optionalIdbCheck = new OptionalIdbCommandCheck(); /** @satisfies {import('@appium/types').IDoctorCheck} */ export class OptionalApplesimutilsCommandCheck { - README_LINK = 'https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-setpermission'; + README_LINK = 'https://github.com/appium/appium-xcuitest-driver/blob/master/docs/reference/execute-methods.md#mobile-setpermission'; async diagnose() { const applesimutilsPath = await resolveExecutablePath('applesimutils'); diff --git a/lib/driver.js b/lib/driver.js index 66375ecff..a02376a38 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -266,6 +266,12 @@ export class XCUITestDriver extends BaseDriver { /** @type {import('appium-remote-debugger').RemoteDebugger|null} */ remote; + /** @type {DriverLogs} */ + logs; + + /** @type {import('./commands/types').LogListener|undefined} */ + _bidiServerLogListener; + /** * * @param {XCUITestDriverOpts} opts @@ -315,6 +321,7 @@ export class XCUITestDriver extends BaseDriver { this._audioRecorder = null; this.appInfosCache = new AppInfosCache(this.log); this.remote = null; + this.doesSupportBidi = true; } async onSettingsUpdate(key, value) { @@ -977,10 +984,12 @@ export class XCUITestDriver extends BaseDriver { } } - if (!_.isEmpty(this.logs)) { - await this.logs.syslog.stopCapture(); - this.logs = {}; + await this.logs.syslog?.stopCapture(); + _.values(this.logs).forEach((x) => x.removeAllListeners()); + if (this._bidiServerLogListener) { + this.log.unwrap().off('log', this._bidiServerLogListener); } + this.logs = {}; if (this.mjpegStream) { this.log.info('Closing MJPEG stream'); @@ -2008,6 +2017,7 @@ 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; @@ -2181,3 +2191,12 @@ export default XCUITestDriver; * @typedef {import('appium-xcode').XcodeVersion} XcodeVersion * @typedef {import('appium-ios-simulator').Simulator} Simulator */ + +/** + * @typedef {Object} DriverLogs + * @property {import('./device-log/ios-device-log').IOSDeviceLog|import('./device-log/ios-simulator-log').IOSSimulatorLog} [syslog] + * @property {import('./device-log/ios-crash-log').IOSCrashLog} [crashlog] + * @property {import('./device-log/safari-console-log').SafariConsoleLog} [safariConsole] + * @property {import('./device-log/safari-network-log').SafariNetworkLog} [safariNetwork] + * @property {import('./device-log/ios-performance-log').IOSPerformanceLog} [performance] + */ diff --git a/lib/real-device.js b/lib/real-device.js index 9d84b696b..af1998bfa 100644 --- a/lib/real-device.js +++ b/lib/real-device.js @@ -314,18 +314,11 @@ export class RealDevice { * @param {import('./driver').XCUITestDriverOpts} opts * @returns {Promise} */ - async reset({bundleId, fullReset, platformVersion}) { - if (!bundleId) { - return; - } - - if (bundleId === SAFARI_BUNDLE_ID) { - this.log.debug('Reset requested. About to terminate Safari'); - await this.terminateApp(bundleId, String(platformVersion)); - return; - } - - if (!fullReset) { + async reset({bundleId, fullReset}) { + if (!bundleId || !fullReset || bundleId === SAFARI_BUNDLE_ID) { + // Safari cannot be removed as system app. + // Safari process handling will be managed by WDA + // with noReset, forceAppLaunch or shouldTerminateApp capabilities. return; } diff --git a/lib/utils.js b/lib/utils.js index 501b7c19d..918eee3a1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -20,6 +20,8 @@ const XCTEST_LOG_FILES_PATTERNS = [ /^StandardOutputAndStandardError\.txt$/i, ]; 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? diff --git a/mkdocs.yml b/mkdocs.yml index 54731fc30..b10b0a140 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,7 @@ nav: - preparation/prov-profile-generic-manual.md - Reference: - reference/scripts.md + - reference/bidi.md - Server Configuration: - reference/server-args.md - reference/security-flags.md diff --git a/package.json b/package.json index 92ac9d0cd..64c8b628b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "xcuitest", "xctest" ], - "version": "7.25.0", + "version": "7.26.4", "author": "Appium Contributors", "license": "Apache-2.0", "repository": { @@ -158,7 +158,7 @@ "rimraf": "^5.0.1", "semantic-release": "^24.0.0", "sharp": "^0.x", - "sinon": "^18.0.0", + "sinon": "^19.0.2", "ts-node": "^10.9.1", "type-fest": "^4.1.0", "typescript": "^5.4.2",