UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

266 lines 11.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.supportedLogTypes = void 0; exports.extractLogs = extractLogs; exports.startLogCapture = startLogCapture; exports.mobileStartLogsBroadcast = mobileStartLogsBroadcast; exports.mobileStopLogsBroadcast = mobileStopLogsBroadcast; exports.assignBiDiLogListener = assignBiDiLogListener; const lodash_1 = __importDefault(require("lodash")); const bluebird_1 = __importDefault(require("bluebird")); const driver_1 = require("appium/driver"); const ios_crash_log_1 = require("../device/log/ios-crash-log"); const ios_simulator_log_1 = require("../device/log/ios-simulator-log"); const ios_device_log_1 = require("../device/log/ios-device-log"); const ws_1 = __importDefault(require("ws")); const safari_console_log_1 = require("../device/log/safari-console-log"); const safari_network_log_1 = require("../device/log/safari-network-log"); const helpers_1 = require("../device/log/helpers"); const utils_1 = require("../utils"); const constants_1 = require("./bidi/constants"); const models_1 = require("./bidi/models"); /** * Determines the websocket endpoint based on the `sessionId` */ const WEBSOCKET_ENDPOINT = (sessionId) => `${driver_1.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'; /** * @privateRemarks The return types for these getters should be specified */ const SUPPORTED_LOG_TYPES = { syslog: { description: 'System Logs - Device logs for iOS applications on real devices and simulators', getter: async (self) => await self.extractLogs('syslog', self.logs), }, crashlog: { description: 'Crash Logs - Crash reports for iOS applications on real devices and simulators', getter: async (self) => await self.extractLogs('crashlog', self.logs), }, performance: { description: 'Performance Logs - Debug Timelines on real devices and simulators', getter: async (self) => await self.extractLogs('performance', self.logs), }, safariConsole: { description: 'Safari Console Logs - data written to the JS console in Safari', getter: async (self) => await self.extractLogs('safariConsole', self.logs), }, safariNetwork: { description: 'Safari Network Logs - information about network operations undertaken by Safari', getter: async (self) => await self.extractLogs('safariNetwork', self.logs), }, server: { description: 'Appium server logs', getter: (self) => { self.assertFeatureEnabled(GET_SERVER_LOGS_FEATURE); return self.log.unwrap().record.map(nativeLogEntryToSeleniumEntry); }, }, }; const LOG_NAMES_TO_CAPABILITY_NAMES_MAP = { safariConsole: 'showSafariConsoleLog', safariNetwork: 'showSafariNetworkLog', enablePerformanceLogging: 'enablePerformanceLogging', }; exports.supportedLogTypes = SUPPORTED_LOG_TYPES; /** * Extracts logs of the specified type from the logs container. * * @param logType - The type of log to extract * @param logsContainer - Container holding log objects * @returns The extracted logs * @throws {Error} If logs are not available or the log type is not found */ async function extractLogs(logType, logsContainer = {}) { // make sure that we have logs at all // otherwise it's not been initialized if (lodash_1.default.isEmpty(logsContainer)) { throw new Error('No logs currently available. Is the device/simulator started?'); } // If logs captured successfully send response with data, else send error const logObject = logsContainer[logType]; if (logObject) { return await logObject.getLogs(); } if (logType in LOG_NAMES_TO_CAPABILITY_NAMES_MAP) { throw new Error(`${logType} logs are not enabled. Make sure you've set a proper value ` + `to the 'appium:${LOG_NAMES_TO_CAPABILITY_NAMES_MAP[logType]}' capability.`); } throw new Error(`No logs of type '${logType}' found. Supported log types are: ${lodash_1.default.keys(SUPPORTED_LOG_TYPES)}.`); } /** * Starts capturing iOS system logs. * * Initializes and starts capturing syslog and crashlog. Optionally starts Safari console and network logs * if the corresponding capabilities are enabled. * * @returns `true` if syslog capture started successfully; `false` otherwise */ async function startLogCapture() { this.logs = this.logs || {}; if (!lodash_1.default.isUndefined(this.logs.syslog) && this.logs.syslog.isCapturing) { this.log.warn('Trying to start iOS log capture but it has already started!'); return true; } if (lodash_1.default.isUndefined(this.logs.syslog)) { [this.logs.crashlog,] = assignBiDiLogListener.bind(this)(new ios_crash_log_1.IOSCrashLog({ sim: this.device, udid: this.isRealDevice() ? this.opts.udid : undefined, log: this.log, }), { type: 'crashlog', }); [this.logs.syslog,] = assignBiDiLogListener.bind(this)(this.isRealDevice() ? new ios_device_log_1.IOSDeviceLog({ udid: this.opts.udid, showLogs: this.opts.showIOSLog, log: this.log, }) : new ios_simulator_log_1.IOSSimulatorLog({ sim: this.device, showLogs: this.opts.showIOSLog, iosSimulatorLogsPredicate: this.opts.iosSimulatorLogsPredicate, simulatorLogLevel: this.opts.simulatorLogLevel, log: this.log, iosSyslogFile: this.opts.iosSyslogFile }), { type: 'syslog', }); if (lodash_1.default.isBoolean(this.opts.showSafariConsoleLog)) { [this.logs.safariConsole,] = assignBiDiLogListener.bind(this)(new safari_console_log_1.SafariConsoleLog({ showLogs: this.opts.showSafariConsoleLog, log: this.log, }), { type: 'safariConsole', }); } if (lodash_1.default.isBoolean(this.opts.showSafariNetworkLog)) { [this.logs.safariNetwork,] = assignBiDiLogListener.bind(this)(new safari_network_log_1.SafariNetworkLog({ showLogs: this.opts.showSafariNetworkLog, log: this.log, }), { type: 'safariNetwork', }); } if (this.isFeatureEnabled(GET_SERVER_LOGS_FEATURE)) { [, this._bidiServerLogListener] = assignBiDiLogListener.bind(this)(this.log.unwrap(), { type: 'server', srcEventName: 'log', entryTransformer: nativeLogEntryToSeleniumEntry, }); } } let didStartSyslog = false; const promises = [ (async () => { try { await this.logs.syslog?.startCapture(); didStartSyslog = true; this.eventEmitter.emit('syslogStarted', this.logs.syslog); } catch (err) { this.log.debug(err.stack); this.log.warn(`Continuing without capturing device logs: ${err.message}`); } })(), this.logs.crashlog?.startCapture() ?? bluebird_1.default.resolve(), ]; await bluebird_1.default.all(promises); return didStartSyslog; } /** * Starts an iOS system logs broadcast websocket. * * The websocket listens on the same host and port as Appium. The endpoint created is `/ws/session/:sessionId:/appium/syslog`. * * If the websocket is already running, this command does nothing. * * Each connected websocket listener will receive syslog lines as soon as they are visible to Appium. * @see https://appiumpro.com/editions/55-using-mobile-execution-commands-to-continuously-stream-device-logs-with-appium */ async function mobileStartLogsBroadcast() { const pathname = WEBSOCKET_ENDPOINT(this.sessionId); if (!lodash_1.default.isEmpty(await this.server.getWebSocketHandlers(pathname))) { this.log.debug(`The system logs broadcasting web socket server is already listening at ${pathname}`); return; } 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 ws_1.default.Server({ noServer: true, }); wss.on('connection', (ws, req) => { if (req) { const remoteIp = lodash_1.default.isEmpty(req.headers['x-forwarded-for']) ? req.connection?.remoteAddress : req.headers['x-forwarded-for']; this.log.debug(`Established a new system logs listener web socket connection from ${remoteIp}`); } else { this.log.debug('Established a new system logs listener web socket connection'); } if (lodash_1.default.isEmpty(this._syslogWebsocketListener)) { this._syslogWebsocketListener = (logRecord) => { if (ws?.readyState === ws_1.default.OPEN) { ws.send(logRecord.message); } }; } this.logs.syslog?.on('output', this._syslogWebsocketListener); ws.on('close', (code, reason) => { if (!lodash_1.default.isEmpty(this._syslogWebsocketListener)) { this.logs.syslog?.removeListener('output', this._syslogWebsocketListener); this._syslogWebsocketListener = null; } let closeMsg = 'System logs listener web socket is closed.'; if (!lodash_1.default.isEmpty(code)) { closeMsg += ` Code: ${code}.`; } if (!lodash_1.default.isEmpty(reason)) { closeMsg += ` Reason: ${reason.toString()}.`; } this.log.debug(closeMsg); }); }); await this.server.addWebSocketHandler(pathname, wss); } /** * Stops the syslog broadcasting websocket server previously started by `mobile: startLogsBroadcast`. * * If no websocket server is running, this command does nothing. */ async function mobileStopLogsBroadcast() { const pathname = WEBSOCKET_ENDPOINT(this.sessionId); if (lodash_1.default.isEmpty(await this.server.getWebSocketHandlers(pathname))) { return; } this.log.debug('Stopping the system logs broadcasting web socket server'); await this.server.removeWebSocketHandler(pathname); } /** * Assigns a BiDi log listener to the given log emitter. * * https://w3c.github.io/webdriver-bidi/#event-log-entryAdded * * @template EE extends EventEmitter * @param logEmitter - The event emitter to attach the listener to * @param properties - Configuration for the BiDi listener * @returns A tuple containing the log emitter and the listener function */ function assignBiDiLogListener(logEmitter, properties) { const { type, context = utils_1.NATIVE_WIN, srcEventName = 'output', entryTransformer, } = properties; const listener = (logEntry) => { const finalEntry = entryTransformer ? entryTransformer(logEntry) : logEntry; this.eventEmitter.emit(constants_1.BIDI_EVENT_NAME, (0, models_1.makeLogEntryAddedEvent)(finalEntry, context, type)); }; logEmitter.on(srcEventName, listener); return [logEmitter, listener]; } function nativeLogEntryToSeleniumEntry(x) { const msg = lodash_1.default.isEmpty(x.prefix) ? x.message : `[${x.prefix}] ${x.message}`; return (0, helpers_1.toLogEntry)(lodash_1.default.replace(msg, COLOR_CODE_PATTERN, ''), x.timestamp ?? Date.now()); } //# sourceMappingURL=log.js.map