appium-android-driver
Version:
Android UiAutomator and Chrome support for Appium
171 lines • 7.11 kB
JavaScript
;
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