diff --git a/lib/commands/applescript.js b/lib/commands/applescript.js index 24d7c56..0571ebe 100644 --- a/lib/commands/applescript.js +++ b/lib/commands/applescript.js @@ -33,10 +33,10 @@ export async function macosExecAppleScript (opts = {}) { timeout, } = opts; if (!script && !command) { - this.log.errorAndThrow('AppleScript script/command must not be empty'); + throw this.log.errorWithException('AppleScript script/command must not be empty'); } if (/\n/.test(/** @type {string} */(command))) { - this.log.errorAndThrow('AppleScript commands cannot contain line breaks'); + throw this.log.errorWithException('AppleScript commands cannot contain line breaks'); } // 'command' has priority over 'script' const shouldRunScript = !command; diff --git a/lib/commands/record-screen.js b/lib/commands/record-screen.js index 6d85e02..b3ae012 100644 --- a/lib/commands/record-screen.js +++ b/lib/commands/record-screen.js @@ -1,7 +1,6 @@ import _ from 'lodash'; import { waitForCondition } from 'asyncbox'; import { util, fs, net, tempDir } from 'appium/support'; -import log from '../logger'; import { SubProcess } from 'teen_process'; import B from 'bluebird'; @@ -17,6 +16,7 @@ const DEFAULT_PRESET = 'veryfast'; /** * + * @this {Mac2Driver} * @param {string} localFile * @param {string?} remotePath * @param {import('@appium/types').StringRecord} [uploadOptions={}] @@ -25,7 +25,7 @@ const DEFAULT_PRESET = 'veryfast'; async function uploadRecordedMedia (localFile, remotePath = null, uploadOptions = {}) { if (_.isEmpty(remotePath) || _.isNil(remotePath)) { const {size} = await fs.stat(localFile); - log.debug(`The size of the resulting screen recording is ${util.toReadableSizeString(size)}`); + this.log.debug(`The size of the resulting screen recording is ${util.toReadableSizeString(size)}`); return (await util.toInMemoryBase64(localFile)).toString(); } @@ -43,17 +43,29 @@ async function uploadRecordedMedia (localFile, remotePath = null, uploadOptions return ''; } -async function requireFfmpegPath () { +/** + * @param {import('@appium/types').AppiumLogger} log + */ +async function requireFfmpegPath (log) { try { return await fs.which(FFMPEG_BINARY); } catch (e) { - log.errorAndThrow(`${FFMPEG_BINARY} has not been found in PATH. ` + - `Please make sure it is installed`); + throw log.errorWithException( + `${FFMPEG_BINARY} has not been found in PATH. ` + + `Please make sure it is installed` + ); } } class ScreenRecorder { - constructor (videoPath, opts = {}) { + /** + * + * @param {string} videoPath + * @param {import('@appium/types').AppiumLogger} log + * @param {ScreenRecorderOptions} opts + */ + constructor (videoPath, log, opts) { + this._log = log; this._videoPath = videoPath; this._process = null; this._fps = (opts.fps && opts.fps > 0) ? opts.fps : DEFAULT_FPS; @@ -77,7 +89,7 @@ class ScreenRecorder { async _enforceTermination () { if (this._process && this.isRunning()) { - log.debug('Force-stopping the currently running video recording'); + this._log.debug('Force-stopping the currently running video recording'); try { await this._process.stop('SIGKILL'); } catch (ign) {} @@ -91,8 +103,9 @@ class ScreenRecorder { } async start () { - const ffmpeg = await requireFfmpegPath(); + const ffmpeg = await requireFfmpegPath(this._log); + /** @type {string[]} */ const args = [ '-loglevel', 'error', '-t', `${this._timeLimit}`, @@ -100,7 +113,7 @@ class ScreenRecorder { ...(this._captureCursor ? ['-capture_cursor', '1'] : []), ...(this._captureClicks ? ['-capture_mouse_clicks', '1'] : []), '-framerate', `${this._fps}`, - '-i', this._deviceId, + '-i', `${this._deviceId}`, '-vcodec', 'libx264', '-preset', this._preset, '-tune', 'zerolatency', @@ -112,25 +125,26 @@ class ScreenRecorder { ...(this._videoFilter ? ['-filter:v', this._videoFilter] : []), ]; + /** @type {string[]} */ const fullCmd = [ ffmpeg, ...args, this._videoPath, ]; this._process = new SubProcess(fullCmd[0], fullCmd.slice(1)); - log.debug(`Starting ${FFMPEG_BINARY}: ${util.quote(fullCmd)}`); + this._log.debug(`Starting ${FFMPEG_BINARY}: ${util.quote(fullCmd)}`); this._process.on('output', (stdout, stderr) => { if (_.trim(stdout || stderr)) { - log.debug(`[${FFMPEG_BINARY}] ${stdout || stderr}`); + this._log.debug(`[${FFMPEG_BINARY}] ${stdout || stderr}`); } }); this._process.once('exit', async (code, signal) => { this._process = null; if (code === 0) { - log.debug('Screen recording exited without errors'); + this._log.debug('Screen recording exited without errors'); } else { await this._enforceTermination(); - log.warn(`Screen recording exited with error code ${code}, signal ${signal}`); + this._log.warn(`Screen recording exited with error code ${code}, signal ${signal}`); } }); await this._process.start(0); @@ -149,10 +163,12 @@ class ScreenRecorder { }); } catch (e) { await this._enforceTermination(); - log.errorAndThrow(`The expected screen record file '${this._videoPath}' does not exist. ` + - `Check the server log for more details`); + throw this._log.errorWithException( + `The expected screen record file '${this._videoPath}' does not exist. ` + + `Check the server log for more details` + ); } - log.info(`The video recording has started. Will timeout in ${util.pluralize('second', this._timeLimit, true)}`); + this._log.info(`The video recording has started. Will timeout in ${util.pluralize('second', this._timeLimit, true)}`); } async stop (force = false) { @@ -161,7 +177,7 @@ class ScreenRecorder { } if (!this.isRunning()) { - log.debug('Screen recording is not running. Returning the recent result'); + this._log.debug('Screen recording is not running. Returning the recent result'); return await this.getVideoPath(); } @@ -215,12 +231,12 @@ export async function startRecordingScreen (options) { } if (this._screenRecorder?.isRunning?.()) { - log.debug('The screen recording is already running'); + this.log.debug('The screen recording is already running'); if (!forceRestart) { - log.debug('Doing nothing'); + this.log.debug('Doing nothing'); return; } - log.debug('Forcing the active screen recording to stop'); + this.log.debug('Forcing the active screen recording to stop'); await this._screenRecorder.stop(true); } this._screenRecorder = null; @@ -229,7 +245,7 @@ export async function startRecordingScreen (options) { prefix: util.uuidV4().substring(0, 8), suffix: `.${DEFAULT_EXT}`, }); - this._screenRecorder = new ScreenRecorder(videoPath, { + this._screenRecorder = new ScreenRecorder(videoPath, this.log, { fps: parseInt(`${fps}`, 10), timeLimit: parseInt(`${timeLimit}`, 10), preset, @@ -260,19 +276,30 @@ export async function startRecordingScreen (options) { */ export async function stopRecordingScreen (options = {}) { if (!this._screenRecorder) { - log.debug('No screen recording has been started. Doing nothing'); + this.log.debug('No screen recording has been started. Doing nothing'); return ''; } - log.debug('Retrieving the resulting video data'); + this.log.debug('Retrieving the resulting video data'); const videoPath = await this._screenRecorder.stop(); if (!videoPath) { - log.debug('No video data is found. Returning an empty string'); + this.log.debug('No video data is found. Returning an empty string'); return ''; } - return await uploadRecordedMedia(videoPath, options.remotePath, options); + return await uploadRecordedMedia.bind(this)(videoPath, options.remotePath, options); }; /** * @typedef {import('../driver').Mac2Driver} Mac2Driver */ + +/** + * @typedef {Object} ScreenRecorderOptions + * @property {number} [fps] + * @property {string|number} deviceId + * @property {string} [preset] + * @property {boolean} [captureCursor] + * @property {boolean} [captureClicks] + * @property {string} [videoFilter] + * @property {number} [timeLimit] + */ \ No newline at end of file