UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

184 lines 7.08 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IOSSimulatorLog = void 0; const lodash_1 = __importDefault(require("lodash")); const teen_process_1 = require("teen_process"); const support_1 = require("appium/support"); const line_consuming_log_1 = require("./line-consuming-log"); const winston_1 = require("winston"); const promises_1 = __importDefault(require("node:fs/promises")); const EXECVP_ERROR_PATTERN = /execvp\(\)/; const LOG_STREAMING_PROCESS_NAME_PATTERN = /^com\.apple\.xpc\.launchd\.oneshot\.0x[0-f]+\.log$/; const START_TIMEOUT = 10000; class IOSSimulatorLog extends line_consuming_log_1.LineConsumingLog { sim; showLogs; predicate; logLevel; proc; iosSyslogFile; syslogLogger; constructor(opts) { super({ log: opts.log }); this.sim = opts.sim; this.showLogs = !!opts.showLogs; this.predicate = opts.iosSimulatorLogsPredicate; this.logLevel = opts.simulatorLogLevel; this.proc = null; this.iosSyslogFile = opts.iosSyslogFile; this.syslogLogger = null; } async startCapture() { if (lodash_1.default.isUndefined(this.sim.udid)) { throw new Error(`Log capture requires a sim udid`); } if (!(await this.sim.isRunning())) { throw new Error(`iOS Simulator with udid '${this.sim.udid}' is not running`); } if (this.iosSyslogFile && this.showLogs) { await this.clearExistingSyslog(); try { this.syslogLogger = (0, winston_1.createLogger)({ level: 'info', format: winston_1.format.combine(winston_1.format.timestamp(), winston_1.format.simple()), transports: [new winston_1.transports.File({ filename: this.iosSyslogFile })], exitOnError: false }); this.log.debug(`iOS syslog will be written to: '${this.iosSyslogFile}'`); } catch (e) { this.log.warn(`Could not set up iOS syslog logger for '${this.iosSyslogFile}': ${e.message}`); this.syslogLogger = null; } } const spawnArgs = ['log', 'stream', '--style', 'compact']; if (this.predicate) { spawnArgs.push('--predicate', this.predicate); } if (this.logLevel) { spawnArgs.push('--level', this.logLevel); } this.log.debug(`Starting log capture for iOS Simulator with udid '${this.sim.udid}' ` + `via simctl using the following arguments '${support_1.util.quote(spawnArgs)}'`); await this.cleanupObsoleteLogStreams(); try { this.proc = await this.sim.simctl.spawnSubProcess(spawnArgs); await this.finishStartingLogCapture(); } catch (e) { this.shutdownSyslogger(); throw new Error(`Simulator log capture failed. Original error: ${e.message}`); } } async stopCapture() { if (!this.proc) { return; } await this.killLogSubProcess(); this.proc = null; this.shutdownSyslogger(); } get isCapturing() { return Boolean(this.proc?.isRunning); } async clearExistingSyslog() { if (this.iosSyslogFile === undefined) { this.log.debug('iOS Syslog file path is not defined, skipping deletion.'); return; } try { await promises_1.default.unlink(this.iosSyslogFile); this.log.debug(`Existing iOS Syslog file: '${this.iosSyslogFile}' deleted.`); } catch (unlinkErr) { if (unlinkErr.code !== 'ENOENT') { this.log.warn(`Could not delete existing syslog file '${this.iosSyslogFile}': ${unlinkErr.message}`); } } } shutdownSyslogger() { if (this.syslogLogger) { this.log.debug(`Closing iOS syslog file: '${this.iosSyslogFile}'`); const fileTransport = this.syslogLogger.transports.find((t) => (t instanceof winston_1.transports.File) && (t.filename === this.iosSyslogFile)); if (fileTransport) { fileTransport.end?.(); this.syslogLogger.remove(fileTransport); } this.syslogLogger = null; } } onOutput(logRow, prefix = '') { this.broadcast(logRow); const space = prefix.length > 0 ? ' ' : ''; if (this.showLogs && !this.iosSyslogFile) { this.log.info(`[IOS_SYSLOG_ROW${space}${prefix}] ${logRow}`); } else if (this.iosSyslogFile && this.showLogs) { this.writeToSyslogFile(`[IOS_SYSLOG_ROW${space}${prefix}] ${logRow}`); } } /** * Writes the given log row to the dedicated iOS syslog file if the logger is active. * @param {string} logRow - The log line to write. * @private */ writeToSyslogFile(logRow) { if (this.syslogLogger && this.showLogs) { this.syslogLogger.info(logRow); } } async killLogSubProcess() { if (!this.proc?.isRunning) { return; } this.log.debug('Stopping iOS log capture'); try { await this.proc.stop('SIGTERM', 1000); } catch { if (!this.proc.isRunning) { return; } this.log.warn('Cannot stop log capture process. Sending SIGKILL'); await this.proc.stop('SIGKILL'); } } async finishStartingLogCapture() { if (!this.proc) { throw this.log.errorWithException('Could not capture simulator log'); } for (const streamName of ['stdout', 'stderr']) { this.proc.on(`line-${streamName}`, (line) => { this.onOutput(line, ...(streamName === 'stderr' ? ['STDERR'] : [])); }); } const startDetector = (stdout, stderr) => { if (EXECVP_ERROR_PATTERN.test(stderr)) { throw new Error('iOS log capture process failed to start'); } return Boolean(stdout || stderr); }; await this.proc.start(startDetector, START_TIMEOUT); } async cleanupObsoleteLogStreams() { const processes = await this.sim.ps(); const pids = processes .filter(({ name }) => LOG_STREAMING_PROCESS_NAME_PATTERN.test(name)) .map(({ pid }) => pid); if (lodash_1.default.isEmpty(pids)) { return; } try { await (0, teen_process_1.exec)('kill', pids.map(String)); } catch (e) { this.log.warn(`Could not terminate one or more obsolete log streams: ${e.stderr || e.message}`); } } } exports.IOSSimulatorLog = IOSSimulatorLog; exports.default = IOSSimulatorLog; //# sourceMappingURL=ios-simulator-log.js.map