UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

187 lines 7.52 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IOSCrashLog = void 0; const support_1 = require("appium/support"); const asyncbox_1 = require("asyncbox"); const node_path_1 = __importDefault(require("node:path")); const utils_1 = require("../../utils"); const crash_reports_client_1 = require("../crash-reports-client"); const ios_log_1 = require("./ios-log"); const helpers_1 = require("./helpers"); // The file format has been changed from '.crash' to '.ips' since Monterey. const CRASH_REPORTS_GLOB_PATTERN = '**/*.@(crash|ips)'; // The size of a single diagnostic report might be hundreds of kilobytes. // Thus we do not want to store too many items in the memory at once. const MAX_RECENT_ITEMS = 20; /** * Collects iOS/tvOS crash logs for BiDi `log.entryAdded` / classic log APIs. * * - **Simulator:** reads `~/Library/Logs/DiagnosticReports` and filters by simulator UDID. * - **Real device:** uses RemoteXPC (`appium-ios-remotexpc`) when `useRemoteXPC` is true; if the client * cannot be created, collection is skipped and errors are logged (no session failure). */ class IOSCrashLog extends ios_log_1.IOSLog { _udid; _useRemoteXPC; _realDeviceClient; _logDir; _sim; _recentCrashFiles; _started; /** * @param opts - Provide `udid` for a real device or `sim` for a Simulator (mutually exclusive by usage). */ constructor(opts) { super({ log: opts.log, maxBufferSize: MAX_RECENT_ITEMS, }); this._udid = opts.udid; this._sim = opts.sim; this._useRemoteXPC = opts.useRemoteXPC ?? false; this._realDeviceClient = null; this._logDir = this._isRealDevice() ? null : node_path_1.default.resolve(process.env.HOME || '/', 'Library', 'Logs', 'DiagnosticReports'); this._recentCrashFiles = []; this._started = false; } get isCapturing() { return this._started; } /** Records the current crash file snapshot so only new reports appear in {@link IOSCrashLog.getLogs}. */ async startCapture() { this._recentCrashFiles = await this._listCrashFiles(); this._started = true; } /** Stops polling and closes any real-device {@link CrashReportsClient}. */ async stopCapture() { this._started = false; // Clean up the client connection if (this._realDeviceClient) { await this._realDeviceClient.close(); this._realDeviceClient = null; } } /** * @returns New crash log entries since the last successful poll (bounded by {@link MAX_RECENT_ITEMS}). */ async getLogs() { const crashFiles = (await this._listCrashFiles()).slice(-MAX_RECENT_ITEMS); const recentCrashFiles = new Set(this._recentCrashFiles); const diffFiles = crashFiles.filter((file) => !recentCrashFiles.has(file)); if ((0, utils_1.isEmpty)(diffFiles)) { return []; } this.log.debug(`Found ${support_1.util.pluralize('fresh crash report', diffFiles.length, true)}`); await this._serializeCrashes(diffFiles); this._recentCrashFiles = crashFiles; return super.getLogs(); } _serializeEntry(value) { return value; } _deserializeEntry(value) { const [message, timestamp] = value; return (0, helpers_1.toLogEntry)(message, timestamp); } /** Reads crash file contents and {@link IOSLog.broadcast}s them as `[text, mtime]` tuples. */ async _serializeCrashes(paths) { const tmpRoot = await support_1.tempDir.openDir(); try { for (const filePath of paths) { let fullPath = filePath; if (this._isRealDevice()) { const fileName = filePath; try { await this._realDeviceClient.exportCrash(fileName, tmpRoot); } catch (e) { this.log.warn(`Cannot export the crash report '${fileName}'. Skipping it. ` + `Original error: ${e.message}`); return; } fullPath = node_path_1.default.join(tmpRoot, fileName); } const { ctime } = await support_1.fs.stat(fullPath); this.broadcast([await support_1.fs.readFile(fullPath, 'utf8'), ctime.getTime()]); } } finally { await support_1.fs.rimraf(tmpRoot); } } /** * Lazily creates a {@link CrashReportsClient} and lists `.ips` basenames on the device. * * @returns Empty array if RemoteXPC setup or listing fails (logged). The client is reset after * listing errors so a later poll can recreate it. Never throws to callers. */ async _gatherFromRealDevice() { if (!this._realDeviceClient) { try { this._realDeviceClient = await crash_reports_client_1.CrashReportsClient.create(this._udid, this._useRemoteXPC); } catch (err) { this.log.error(`Failed to create crash reports client: ${err.message}. ` + `Skipping crash logs collection for real devices.`); return []; } } try { return await this._realDeviceClient.listCrashes(); } catch (err) { this.log.error(`Failed to list crash reports on device: ${err.message}. ` + `Skipping this poll; the next poll will attempt to reconnect.`); const client = this._realDeviceClient; this._realDeviceClient = null; if (client) { try { await client.close(); } catch { // ignore secondary teardown errors } } return []; } } /** Glob diagnostic reports and keep files whose content references the simulator UDID. */ async _gatherFromSimulator() { if (!this._logDir || !this._sim || !(await support_1.fs.exists(this._logDir))) { this.log.debug(`Crash reports root '${this._logDir}' does not exist. Got nothing to gather.`); return []; } const foundFiles = await support_1.fs.glob(CRASH_REPORTS_GLOB_PATTERN, { cwd: this._logDir, absolute: true, }); const simUdid = this._sim.udid; // For Simulator only include files, that contain current UDID return await (0, asyncbox_1.asyncfilter)(foundFiles, async (filePath) => { try { return await (0, helpers_1.grepFile)(filePath, simUdid, { caseInsensitive: true }); } catch (err) { this.log.warn(err); return false; } }); } /** Dispatches to real-device RemoteXPC listing or simulator filesystem globbing. */ async _listCrashFiles() { return this._isRealDevice() ? await this._gatherFromRealDevice() : await this._gatherFromSimulator(); } _isRealDevice() { return Boolean(this._udid); } } exports.IOSCrashLog = IOSCrashLog; exports.default = IOSCrashLog; //# sourceMappingURL=ios-crash-log.js.map