UNPKG

appium-android-driver

Version:

Android UiAutomator and Chrome support for Appium

196 lines 8.03 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', /** * * @param {import('../driver').AndroidDriver} self * @returns */ getter: (self) => /** @type {ADB} */ (self.adb).getLogcatLogs(), }, bugreport: { description: `'adb bugreport' output for advanced issues diagnostic`, /** * * @param {import('../driver').AndroidDriver} self * @returns */ getter: async (self) => { const output = await /** @type {ADB} */ (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', /** * * @param {import('../driver').AndroidDriver} self * @returns */ 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. * * @this {import('../driver').AndroidDriver} * @returns {Promise<void>} */ async function mobileStartLogsBroadcast() { const server = /** @type {import('@appium/types').AppiumServer} */ (this.server); const pathname = WEBSOCKET_ENDPOINT(/** @type {string} */ (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.connection?.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, /** @type {import('@appium/types').WSServer} */ (wss)); } /** * Stops the previously started logcat broadcasting wesocket server. * This method will return immediately if no server is running. * * @this {import('../driver').AndroidDriver} * @returns {Promise<void>} */ async function mobileStopLogsBroadcast() { const pathname = WEBSOCKET_ENDPOINT(/** @type {string} */ (this.sessionId)); const server = /** @type {import('@appium/types').AppiumServer} */ (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); } /** * @this {import('../driver').AndroidDriver} * @returns {Promise<string[]>} */ 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 = /** @type {string[]} */ (await /** @type {import('appium-chromedriver').Chromedriver} */ (this.chromedriver).jwproxy.command('/log/types', 'GET')); return [...nativeLogTypes, ...webLogTypes]; } return nativeLogTypes; } /** * https://w3c.github.io/webdriver-bidi/#event-log-entryAdded * * @template {import('node:events').EventEmitter} EE * @this {import('../driver').AndroidDriver} * @param {EE} logEmitter * @param {BiDiListenerProperties} properties * @returns {[EE, LogListener]} */ function assignBiDiLogListener(logEmitter, properties) { const { type, context = helpers_1.NATIVE_WIN, srcEventName = 'output', entryTransformer, } = properties; const listener = (/** @type {import('../utils').LogEntry} */ 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]; } /** * @this {import('../driver').AndroidDriver} * @param {string} logType * @returns {Promise<any>} */ async function getLog(logType) { if (this.isWebContext() && !lodash_1.default.keys(this.supportedLogTypes).includes(logType)) { return await /** @type {import('appium-chromedriver').Chromedriver} */ (this.chromedriver).jwproxy.command('/log', 'POST', { type: logType }); } // XXX why doesn't `super` work here? return await driver_1.BaseDriver.prototype.getLog.call(this, logType); } // #region Internal helpers /** * @param {string} sessionId * @returns {string} */ const WEBSOCKET_ENDPOINT = (sessionId) => `${driver_1.DEFAULT_WS_PATHNAME_PREFIX}/session/${sessionId}/appium/device/logcat`; // #endregion /** * @typedef {import('appium-adb').ADB} ADB */ /** * @typedef {Object} BiDiListenerProperties * @property {string} type * @property {string} [srcEventName='output'] * @property {string} [context=NATIVE_WIN] * @property {(x: Object) => import('../utils').LogEntry} [entryTransformer] */ /** @typedef {(logEntry: import('../utils').LogEntry) => any} LogListener */ //# sourceMappingURL=log.js.map