UNPKG

appium-android-driver

Version:

Android UiAutomator and Chrome support for Appium

171 lines 7.11 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.mobileStartLogsBroadcast = mobileStartLogsBroadcast; exports.mobileStopLogsBroadcast = mobileStopLogsBroadcast; exports.getLogTypes = getLogTypes; exports.assignBiDiLogListener = assignBiDiLogListener; exports.getLog = getLog; const driver_1 = require("appium/driver"); const lodash_1 = __importDefault(require("lodash")); const node_os_1 = __importDefault(require("node:os")); const ws_1 = __importDefault(require("ws")); const utils_1 = require("../utils"); const helpers_1 = require("./context/helpers"); const constants_1 = require("./bidi/constants"); const models_1 = require("./bidi/models"); exports.supportedLogTypes = { logcat: { description: 'Logs for Android applications on real device and emulators via ADB', getter: (self) => self.adb.getLogcatLogs(), }, bugreport: { description: `'adb bugreport' output for advanced issues diagnostic`, getter: async (self) => { const output = await self.adb.bugreport(); const timestamp = Date.now(); return output.split(node_os_1.default.EOL).map((x) => (0, utils_1.toLogRecord)(timestamp, x)); }, }, server: { description: 'Appium server logs', getter: (self) => { self.assertFeatureEnabled(utils_1.GET_SERVER_LOGS_FEATURE); return self.log.unwrap().record.map(utils_1.nativeLogEntryToSeleniumEntry); }, }, }; /** * Starts Android logcat broadcast websocket on the same host and port * where Appium server is running at `/ws/session/:sessionId:/appium/logcat` endpoint. The method * will return immediately if the web socket is already listening. * * Each connected websocket listener will receive logcat log lines * as soon as they are visible to Appium. * * @returns Promise that resolves when the logcat broadcasting websocket is started. */ async function mobileStartLogsBroadcast() { const server = this.server; const pathname = WEBSOCKET_ENDPOINT(this.sessionId); if (!lodash_1.default.isEmpty(await server.getWebSocketHandlers(pathname))) { this.log.debug(`The logcat broadcasting web socket server is already listening at ${pathname}`); return; } this.log.info(`Starting logcat broadcasting on web socket server ` + `${JSON.stringify(server.address())} 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.socket.remoteAddress : req.headers['x-forwarded-for']; this.log.debug(`Established a new logcat listener web socket connection from ${remoteIp}`); } else { this.log.debug('Established a new logcat listener web socket connection'); } if (lodash_1.default.isEmpty(this._logcatWebsocketListener)) { this._logcatWebsocketListener = (logRecord) => { if (ws?.readyState === ws_1.default.OPEN) { ws.send(logRecord.message); } }; } this.adb.setLogcatListener(this._logcatWebsocketListener); ws.on('close', (code, reason) => { if (!lodash_1.default.isEmpty(this._logcatWebsocketListener)) { try { this.adb.removeLogcatListener(this._logcatWebsocketListener); } catch { } this._logcatWebsocketListener = undefined; } let closeMsg = 'Logcat 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 server.addWebSocketHandler(pathname, wss); } /** * Stops the previously started logcat broadcasting wesocket server. * This method will return immediately if no server is running. * * @returns Promise that resolves when the logcat broadcasting websocket is stopped. */ async function mobileStopLogsBroadcast() { const pathname = WEBSOCKET_ENDPOINT(this.sessionId); const server = this.server; if (lodash_1.default.isEmpty(await server.getWebSocketHandlers(pathname))) { return; } this.log.debug(`Stopping logcat broadcasting on web socket server ` + `${JSON.stringify(server.address())} to ${pathname}`); await server.removeWebSocketHandler(pathname); } /** * Gets the list of available log types. * * @returns Promise that resolves to an array of log type names. */ async function getLogTypes() { // XXX why doesn't `super` work here? const nativeLogTypes = await driver_1.BaseDriver.prototype.getLogTypes.call(this); if (this.isWebContext()) { const webLogTypes = await this.chromedriver.jwproxy.command('/log/types', 'GET'); return [...nativeLogTypes, ...webLogTypes]; } return nativeLogTypes; } /** * Assigns a BiDi log listener to an event emitter. * * https://w3c.github.io/webdriver-bidi/#event-log-entryAdded * * @template EE The event emitter type. * @param logEmitter The event emitter to attach the listener to. * @param properties The BiDi listener properties. * @returns A tuple containing the event emitter and the listener function. */ function assignBiDiLogListener(logEmitter, properties) { const { type, context = helpers_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]; } /** * Gets logs of a specific type. * * @param logType The type of logs to retrieve. * @returns Promise that resolves to the logs for the specified type. */ async function getLog(logType) { if (this.isWebContext() && !lodash_1.default.keys(this.supportedLogTypes).includes(logType)) { return await this.chromedriver.jwproxy.command('/log', 'POST', { type: logType }); } return await driver_1.BaseDriver.prototype.getLog.call(this, logType); } // #region Internal helpers /** * Generates the websocket endpoint path for logcat broadcasting. * * @param sessionId The session ID. * @returns The websocket endpoint path. */ const WEBSOCKET_ENDPOINT = (sessionId) => `${driver_1.DEFAULT_WS_PATHNAME_PREFIX}/session/${sessionId}/appium/device/logcat`; //# sourceMappingURL=log.js.map