How to use _enforceTermination method in Appium Xcuitest Driver

Best JavaScript code snippet using appium-xcuitest-driver

Run Appium Xcuitest Driver automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Sign up Free
_

record-screen.js

Source: record-screen.js Github

copy
1import _ from 'lodash';
2import { waitForCondition } from 'asyncbox';
3import { util, fs, net, tempDir } from '@appium/support';
4import log from '../logger';
5import { SubProcess } from 'teen_process';
6import B from 'bluebird';
7
8
9const commands = {};
10
11const RETRY_PAUSE = 300;
12const RETRY_TIMEOUT = 5000;
13const DEFAULT_TIME_LIMIT = 60 * 10; // 10 minutes
14const PROCESS_SHUTDOWN_TIMEOUT = 10 * 1000;
15const DEFAULT_EXT = 'mp4';
16const FFMPEG_BINARY = 'ffmpeg';
17const DEFAULT_FPS = 15;
18const DEFAULT_PRESET = 'veryfast';
19
20
21async function uploadRecordedMedia (localFile, remotePath = null, uploadOptions = {}) {
22  if (_.isEmpty(remotePath)) {
23    const {size} = await fs.stat(localFile);
24    log.debug(`The size of the resulting screen recording is ${util.toReadableSizeString(size)}`);
25    return (await util.toInMemoryBase64(localFile)).toString();
26  }
27
28  const {user, pass, method, headers, fileFieldName, formFields} = uploadOptions;
29  const options = {
30    method: method || 'PUT',
31    headers,
32    fileFieldName,
33    formFields,
34  };
35  if (user && pass) {
36    options.auth = {user, pass};
37  }
38  await net.uploadFile(localFile, remotePath, options);
39  return '';
40}
41
42async function requireFfmpegPath () {
43  try {
44    return await fs.which(FFMPEG_BINARY);
45  } catch (e) {
46    log.errorAndThrow(`${FFMPEG_BINARY} has not been found in PATH. ` +
47      `Please make sure it is installed`);
48  }
49}
50
51class ScreenRecorder {
52  constructor (videoPath, opts = {}) {
53    this._videoPath = videoPath;
54    this._process = null;
55    this._fps = (opts.fps && opts.fps > 0) ? opts.fps : DEFAULT_FPS;
56    this._deviceId = opts.deviceId;
57    this._captureCursor = opts.captureCursor;
58    this._captureClicks = opts.captureClicks;
59    this._preset = opts.preset || DEFAULT_PRESET;
60    this._videoFilter = opts.videoFilter;
61    this._timeLimit = (opts.timeLimit && opts.timeLimit > 0)
62      ? opts.timeLimit
63      : DEFAULT_TIME_LIMIT;
64  }
65
66  async getVideoPath () {
67    return (await fs.exists(this._videoPath)) ? this._videoPath : '';
68  }
69
70  isRunning () {
71    return !!(this._process?.isRunning);
72  }
73
74  async _enforceTermination () {
75    if (this._process && this.isRunning()) {
76      log.debug('Force-stopping the currently running video recording');
77      try {
78        await this._process.stop('SIGKILL');
79      } catch (ign) {}
80    }
81    this._process = null;
82    const videoPath = await this.getVideoPath();
83    if (videoPath) {
84      await fs.rimraf(videoPath);
85    }
86    return '';
87  }
88
89  async start () {
90    const ffmpeg = await requireFfmpegPath();
91
92    const args = [
93      '-loglevel', 'error',
94      '-t', `${this._timeLimit}`,
95      '-f', 'avfoundation',
96      ...(this._captureCursor ? ['-capture_cursor', '1'] : []),
97      ...(this._captureClicks ? ['-capture_mouse_clicks', '1'] : []),
98      '-framerate', `${this._fps}`,
99      '-i', this._deviceId,
100      '-vcodec', 'libx264',
101      '-preset', this._preset,
102      '-tune', 'zerolatency',
103      '-pix_fmt', 'yuv420p',
104      '-movflags', '+faststart',
105      '-fflags', 'nobuffer',
106      '-f', DEFAULT_EXT,
107      '-r', `${this._fps}`,
108      ...(this._videoFilter ? ['-filter:v', this._videoFilter] : []),
109    ];
110
111    const fullCmd = [
112      ffmpeg,
113      ...args,
114      this._videoPath,
115    ];
116    this._process = new SubProcess(fullCmd[0], fullCmd.slice(1));
117    log.debug(`Starting ${FFMPEG_BINARY}: ${util.quote(fullCmd)}`);
118    this._process.on('output', (stdout, stderr) => {
119      if (_.trim(stdout || stderr)) {
120        log.debug(`[${FFMPEG_BINARY}] ${stdout || stderr}`);
121      }
122    });
123    this._process.once('exit', async (code, signal) => {
124      this._process = null;
125      if (code === 0) {
126        log.debug('Screen recording exited without errors');
127      } else {
128        await this._enforceTermination();
129        log.warn(`Screen recording exited with error code ${code}, signal ${signal}`);
130      }
131    });
132    await this._process.start(0);
133    try {
134      await waitForCondition(async () => {
135        if (await this.getVideoPath()) {
136          return true;
137        }
138        if (!this._process) {
139          throw new Error(`${FFMPEG_BINARY} process died unexpectedly`);
140        }
141        return false;
142      }, {
143        waitMs: RETRY_TIMEOUT,
144        intervalMs: RETRY_PAUSE,
145      });
146    } catch (e) {
147      await this._enforceTermination();
148      log.errorAndThrow(`The expected screen record file '${this._videoPath}' does not exist. ` +
149        `Check the server log for more details`);
150    }
151    log.info(`The video recording has started. Will timeout in ${util.pluralize('second', this._timeLimit, true)}`);
152  }
153
154  async stop (force = false) {
155    if (force) {
156      return await this._enforceTermination();
157    }
158
159    if (!this.isRunning()) {
160      log.debug('Screen recording is not running. Returning the recent result');
161      return await this.getVideoPath();
162    }
163
164    return new B((resolve, reject) => {
165      const timer = setTimeout(async () => {
166        await this._enforceTermination();
167        reject(new Error(`Screen recording has failed to exit after ${PROCESS_SHUTDOWN_TIMEOUT}ms`));
168      }, PROCESS_SHUTDOWN_TIMEOUT);
169
170      this._process.once('exit', async (code, signal) => {
171        clearTimeout(timer);
172        if (code === 0) {
173          resolve(await this.getVideoPath());
174        } else {
175          reject(new Error(`Screen recording exited with error code ${code}, signal ${signal}`));
176        }
177      });
178
179      this._process.proc.stdin.write('q');
180      this._process.proc.stdin.end();
181    });
182  }
183}
184
185
186/**
187 * @typedef {Object} StartRecordingOptions
188 *
189 * @property {?string} videoFilter - The video filter spec to apply for ffmpeg.
190 * See https://trac.ffmpeg.org/wiki/FilteringGuide for more details on the possible values.
191 * Example: Set it to `scale=ifnot(gte(iw\,1024)\,iw\,1024):-2` in order to limit the video width
192 * to 1024px. The height will be adjusted automatically to match the actual ratio.
193 * @property {number|string} fps [15] - The count of frames per second in the resulting video.
194 * The greater fps it has the bigger file size is.
195 * @property {string} preset [veryfast] - One of the supported encoding presets. Possible values are:
196 * - ultrafast
197 * - superfast
198 * - veryfast
199 * - faster
200 * - fast
201 * - medium
202 * - slow
203 * - slower
204 * - veryslow
205 * A preset is a collection of options that will provide a certain encoding speed to compression ratio.
206 * A slower preset will provide better compression (compression is quality per filesize).
207 * This means that, for example, if you target a certain file size or constant bit rate, you will achieve better
208 * quality with a slower preset. Read https://trac.ffmpeg.org/wiki/Encode/H.264 for more details.
209 * @property {boolean} captureCursor [false] - Whether to capture the mouse cursor while recording
210 * the screen
211 * @property {boolean} captureClicks [false] - Whether to capture mouse clicks while recording the
212 * screen
213 * @property {!string|number} deviceId - Screen device index to use for the recording.
214 * The list of available devices could be retrieved using
215 * `ffmpeg -f avfoundation -list_devices true -i` command.
216 * @property {string|number} timeLimit [600] - The maximum recording time, in seconds. The default
217 * value is 600 seconds (10 minutes).
218 * @property {boolean} forceRestart [true] - Whether to ignore the call if a screen recording is currently running
219 * (`false`) or to start a new recording immediately and terminate the existing one if running (`true`).
220 */
221
222/**
223 * Record the display in background while the automated test is running.
224 * This method requires FFMPEG (https://www.ffmpeg.org/download.html) to be installed
225 * and present in PATH. Also, the Appium process must be allowed to access screen recording
226 * in System Preferences->Security & Privacy->Screen Recording.
227 * The resulting video uses H264 codec and is ready to be played by media players built-in into web browsers.
228 *
229 * @param {?StartRecordingOptions} options - The available options.
230 * @throws {Error} If screen recording has failed to start or is not supported on the device under test.
231 */
232commands.startRecordingScreen = async function startRecordingScreen (options = {}) {
233  const {
234    timeLimit,
235    videoFilter,
236    fps,
237    preset,
238    captureCursor,
239    captureClicks,
240    deviceId,
241    forceRestart = true,
242  } = options;
243
244  if (_.isNil(deviceId)) {
245    throw new Error(`'deviceId' option must be provided. Run 'ffmpeg -f avfoundation -list_devices true -i' ` +
246      'to fetch the list of available device ids');
247  }
248
249  if (this._screenRecorder?.isRunning?.()) {
250    log.debug('The screen recording is already running');
251    if (!forceRestart) {
252      log.debug('Doing nothing');
253      return;
254    }
255    log.debug('Forcing the active screen recording to stop');
256    await this._screenRecorder.stop(true);
257  }
258  this._screenRecorder = null;
259
260  const videoPath = await tempDir.path({
261    prefix: util.uuidV4().substring(0, 8),
262    suffix: `.${DEFAULT_EXT}`,
263  });
264  this._screenRecorder = new ScreenRecorder(videoPath, {
265    fps: parseInt(fps, 10),
266    timeLimit: parseInt(timeLimit, 10),
267    preset,
268    captureCursor,
269    captureClicks,
270    videoFilter,
271    deviceId,
272  });
273  try {
274    await this._screenRecorder.start();
275  } catch (e) {
276    this._screenRecorder = null;
277    throw e;
278  }
279};
280
281/**
282 * @typedef {Object} StopRecordingOptions
283 *
284 * @property {?string} remotePath - The path to the remote location, where the resulting video should be uploaded.
285 * The following protocols are supported: http/https, ftp.
286 * Null or empty string value (the default setting) means the content of resulting
287 * file should be encoded as Base64 and passed as the endpoint response value.
288 * An exception will be thrown if the generated media file is too big to
289 * fit into the available process memory.
290 * @property {?string} user - The name of the user for the remote authentication.
291 * @property {?string} pass - The password for the remote authentication.
292 * @property {?string} method - The http multipart upload method name. The 'PUT' one is used by default.
293 * @property {?Object} headers - Additional headers mapping for multipart http(s) uploads
294 * @property {?string} fileFieldName [file] - The name of the form field, where the file content BLOB should be stored for
295 *                                            http(s) uploads
296 * @property {?Object|Array<Pair>} formFields - Additional form fields for multipart http(s) uploads
297 */
298
299/**
300 * Stop recording the screen.
301 * If no screen recording has been started before then the method returns an empty string.
302 *
303 * @param {?StopRecordingOptions} options - The available options.
304 * @returns {string} Base64-encoded content of the recorded media file if 'remotePath'
305 * parameter is falsy or an empty string.
306 * @throws {Error} If there was an error while getting the name of a media file
307 * or the file content cannot be uploaded to the remote location
308 * or screen recording is not supported on the device under test.
309 */
310commands.stopRecordingScreen = async function stopRecordingScreen (options = {}) {
311  if (!this._screenRecorder) {
312    log.debug('No screen recording has been started. Doing nothing');
313    return '';
314  }
315
316  log.debug('Retrieving the resulting video data');
317  const videoPath = await this._screenRecorder.stop();
318  if (!videoPath) {
319    log.debug('No video data is found. Returning an empty string');
320    return '';
321  }
322  return await uploadRecordedMedia(videoPath, options.remotePath, options);
323};
324
325export default commands;
326
Full Screen

performance.js

Source: performance.js Github

copy
1import _ from 'lodash';
2import path from 'path';
3import { fs, zip, logger, util, tempDir } from '@appium/support';
4import { SubProcess, exec } from 'teen_process';
5import { encodeBase64OrUpload } from '../utils';
6import { waitForCondition } from 'asyncbox';
7import B from 'bluebird';
8
9const commands = {};
10
11const PERF_RECORD_FEAT_NAME = 'perf_record';
12const PERF_RECORD_SECURITY_MESSAGE = 'Performance measurement requires relaxing security for simulator. ' +
13  `Please set '--relaxed-security' or '--allow-insecure' with '${PERF_RECORD_FEAT_NAME}' ` +
14  'referencing https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/security.md for more details.';
15const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
16const STOP_TIMEOUT_MS = 3 * 60 * 1000;
17const STARTUP_TIMEOUT_MS = 60 * 1000;
18const DEFAULT_PROFILE_NAME = 'Activity Monitor';
19const DEFAULT_EXT = '.trace';
20const DEFAULT_PID = 'current';
21const INSTRUMENTS = 'instruments';
22const XCRUN = 'xcrun';
23const XCTRACE = 'xctrace';
24
25
26async function requireXctrace () {
27  let xcrunPath;
28  try {
29    xcrunPath = await fs.which(XCRUN);
30  } catch (e) {
31    throw new Error(`${XCRUN} has not been found in PATH. ` +
32      `Please make sure XCode development tools are installed`);
33  }
34  try {
35    await exec(xcrunPath, [XCTRACE, 'help']);
36  } catch (e) {
37    throw new Error(`${XCTRACE} is not available for the active Xcode version. ` +
38      `Please make sure XCode is up to date. Original error: ${e.stderr || e.message}`);
39  }
40  return xcrunPath;
41}
42
43async function requireInstruments () {
44  try {
45    return await fs.which(INSTRUMENTS);
46  } catch (e) {
47    throw new Error(`${INSTRUMENTS} has not been found in PATH. ` +
48      `Please make sure XCode development tools are installed`);
49  }
50}
51
52
53class PerfRecorder {
54  constructor (reportRoot, udid, opts = {}) {
55    this._process = null;
56    this._zippedReportPath = '';
57    this._timeout = (opts.timeout && opts.timeout > 0) ? opts.timeout : DEFAULT_TIMEOUT_MS;
58    this._profileName = opts.profileName || DEFAULT_PROFILE_NAME;
59    this._reportPath = path.resolve(reportRoot,
60      `appium_perf__${this._profileName.replace(/\W/g, '_')}__${Date.now()}${DEFAULT_EXT}`);
61    this._pid = opts.pid;
62    this._udid = udid;
63    this._logger = logger.getLogger(
64      `${_.truncate(this._profileName, {length: 10})}@${this._udid.substring(0, 8)}`);
65    this._archivePromise = null;
66  }
67
68  get profileName () {
69    return this._profileName;
70  }
71
72  async getOriginalReportPath () {
73    return (await fs.exists(this._reportPath)) ? this._reportPath : '';
74  }
75
76  async getZippedReportPath () {
77    // This is to prevent possible race conditions, because the archive operation
78    // could be pretty time-intensive
79    if (!this._archivePromise) {
80      this._archivePromise = (async () => {
81        const originalReportPath = await this.getOriginalReportPath();
82        if (!originalReportPath) {
83          return '';
84        }
85        const zippedReportPath = await tempDir.path({
86          prefix: path.parse(originalReportPath).name,
87          suffix: '.zip'
88        });
89        await zip.toArchive(zippedReportPath, {
90          cwd: path.dirname(this._reportPath),
91        });
92        await fs.rimraf(path.dirname(this._reportPath));
93        this._zippedReportPath = zippedReportPath;
94        return this._zippedReportPath;
95      })();
96    }
97    return await this._archivePromise;
98  }
99
100  isRunning () {
101    return !!(this._process?.isRunning);
102  }
103
104  async _enforceTermination () {
105    if (this._process && this.isRunning()) {
106      this._logger.debug('Force-stopping the currently running perf recording');
107      try {
108        await this._process.stop('SIGKILL');
109      } catch (ign) {}
110    }
111    this._process = null;
112    const performCleanup = async () => {
113      try {
114        await B.all([this._zippedReportPath, path.dirname(this._reportPath)]
115          .filter(Boolean).map((x) => fs.rimraf(x)));
116      } catch (e) {
117        this._logger.warn(e.message);
118      }
119    };
120    if (this._archivePromise) {
121      this._archivePromise
122        // eslint-disable-next-line promise/prefer-await-to-then
123        .finally(async () => {
124          await performCleanup();
125          this._archivePromise = null;
126        })
127        // eslint-disable-next-line promise/prefer-await-to-then
128        .catch(() => {});
129    }
130    await performCleanup();
131    return '';
132  }
133
134  async start () {
135    let binaryPath;
136    try {
137      binaryPath = await requireXctrace();
138    } catch (e) {
139      this._logger.debug(e.message);
140      this._logger.warn(`Defaulting to ${INSTRUMENTS} usage`);
141      binaryPath = await requireInstruments();
142    }
143
144    const args = [];
145    const toolName = path.basename(binaryPath) === XCRUN ? XCTRACE : INSTRUMENTS;
146    if (toolName === XCTRACE) {
147      args.push(
148        XCTRACE, 'record',
149        '--device', this._udid,
150        '--template', this._profileName,
151        '--output', this._reportPath,
152        '--time-limit', `${this._timeout}ms`,
153      );
154      if (this._pid) {
155        args.push('--attach', `${this._pid}`);
156      } else {
157        args.push('--all-processes');
158      }
159    } else {
160      // https://help.apple.com/instruments/mac/current/#/devb14ffaa5
161      args.push(
162        '-w', this._udid,
163        '-t', this._profileName,
164        '-D', this._reportPath,
165        '-l', `${this._timeout}`,
166      );
167      if (this._pid) {
168        args.push('-p', `${this._pid}`);
169      }
170    }
171    const fullCmd = [binaryPath, ...args];
172    this._process = new SubProcess(fullCmd[0], fullCmd.slice(1));
173    this._archivePromise = null;
174    this._logger.debug(`Starting performance recording: ${util.quote(fullCmd)}`);
175    this._process.on('output', (stdout, stderr) => {
176      if (_.trim(stdout || stderr)) {
177        this._logger.debug(`[${toolName}] ${stdout || stderr}`);
178      }
179    });
180    this._process.once('exit', async (code, signal) => {
181      this._process = null;
182      if (code === 0) {
183        this._logger.debug('Performance recording exited without errors');
184        try {
185          // cache zipped report
186          await this.getZippedReportPath();
187        } catch (e) {
188          this._logger.warn(e);
189        }
190      } else {
191        await this._enforceTermination();
192        this._logger.warn(`Performance recording exited with error code ${code}, signal ${signal}`);
193      }
194    });
195    await this._process.start(0);
196    try {
197      await waitForCondition(async () => {
198        if (await this.getOriginalReportPath()) {
199          return true;
200        }
201        if (!this._process) {
202          throw new Error(`${toolName} process died unexpectedly`);
203        }
204        return false;
205      }, {
206        waitMs: STARTUP_TIMEOUT_MS,
207        intervalMs: 500,
208      });
209    } catch (e) {
210      await this._enforceTermination();
211      const listProfilesCommand = toolName === XCTRACE
212        ? `${XCRUN} ${XCTRACE} list templates`
213        : `${INSTRUMENTS} -s`;
214      this._logger.errorAndThrow(`There is no ${DEFAULT_EXT} file found for performance profile ` +
215        `'${this._profileName}'. Make sure the profile is supported on this device. ` +
216        `You could use '${listProfilesCommand}' command to see the list of all available profiles. ` +
217        `Check the server log for more details`);
218    }
219    this._logger.info(`The performance recording has started. Will timeout in ${this._timeout}ms`);
220  }
221
222  async stop (force = false) {
223    if (force) {
224      return await this._enforceTermination();
225    }
226
227    if (!this.isRunning()) {
228      this._logger.debug('Performance recording is not running. Returning the recent result');
229      return await this.getZippedReportPath();
230    }
231
232    try {
233      await this._process.stop('SIGINT', STOP_TIMEOUT_MS);
234    } catch (e) {
235      this._logger.errorAndThrow(`Performance recording has failed to exit after ${STOP_TIMEOUT_MS}ms`);
236    }
237    return await this.getZippedReportPath();
238  }
239}
240
241
242/**
243 * @typedef {Object} StartPerfRecordOptions
244 *
245 * @property {?number|string} timeout [300000] - The maximum count of milliseconds to record the profiling information.
246 * @property {?string} profileName [Activity Monitor] - The name of existing performance profile to apply.
247 *                                                      Can also contain the full path to the chosen template on the server file system.
248 *                                                      Note, that not all profiles are supported on mobile devices.
249 * @property {?string|number} pid - The ID of the process to measure the performance for.
250 *                                  Set it to `current` in order to measure the performance of
251 *                                  the process, which belongs to the currently active application.
252 *                                  All processes running on the device are measured if
253 *                                  pid is unset (the default setting).
254 */
255
256/**
257 * Starts performance profiling for the device under test.
258 * Relaxing security is mandatory for simulators. It can always work for real devices.
259 *
260 * Since XCode 14 the method tries to use `xctrace` tool to record performance stats.
261 * The `instruments` developer utility is used as a fallback for this purpose if `xctrace`
262 * is not available.
263 * It is possible to record multiple profiles at the same time.
264 * Read https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Recording,Pausing,andStoppingTraces.html
265 * for more details.
266 *
267 * @param {?StartPerfRecordOptions} opts - The set of possible start record options
268 */
269commands.mobileStartPerfRecord = async function mobileStartPerfRecord (opts = {}) {
270  if (!this.isFeatureEnabled(PERF_RECORD_FEAT_NAME) && !this.isRealDevice()) {
271    this.log.errorAndThrow(PERF_RECORD_SECURITY_MESSAGE);
272  }
273
274  const {
275    timeout = DEFAULT_TIMEOUT_MS,
276    profileName = DEFAULT_PROFILE_NAME,
277    pid,
278  } = opts;
279
280  if (!_.isEmpty(this._perfRecorders)) {
281    for (const recorder of this._perfRecorders.filter((x) => x.profileName === profileName)) {
282      if (recorder.isRunning()) {
283        this.log.debug(`Performance recorder for '${profileName}' on device '${this.opts.device.udid}' ` +
284          ` is already running. Doing nothing`);
285        return;
286      }
287      _.pull(this._perfRecorders, recorder);
288      await recorder.stop(true);
289    }
290  }
291
292  let realPid;
293  if (pid) {
294    if (_.toLower(pid) === DEFAULT_PID) {
295      const appInfo = await this.proxyCommand('/wda/activeAppInfo', 'GET');
296      realPid = appInfo.pid;
297    } else {
298      realPid = pid;
299    }
300  }
301  const recorder = new PerfRecorder(await tempDir.openDir(), this.opts.device.udid, {
302    timeout: parseInt(timeout, 10),
303    profileName,
304    pid: parseInt(realPid, 10),
305  });
306  await recorder.start();
307  this._perfRecorders = [...(this._perfRecorders || []), recorder];
308};
309
310/**
311 * @typedef {Object} StopRecordingOptions
312 *
313 * @property {?string} remotePath - The path to the remote location, where the resulting zipped .trace file should be uploaded.
314 *                                  The following protocols are supported: http/https, ftp.
315 *                                  Null or empty string value (the default setting) means the content of resulting
316 *                                  file should be zipped, encoded as Base64 and passed as the endpoint response value.
317 *                                  An exception will be thrown if the generated file is too big to
318 *                                  fit into the available process memory.
319 * @property {?string} user - The name of the user for the remote authentication. Only works if `remotePath` is provided.
320 * @property {?string} pass - The password for the remote authentication. Only works if `remotePath` is provided.
321 * @property {?string} method [PUT] - The http multipart upload method name. Only works if `remotePath` is provided.
322 * @property {?string} profileName [Activity Monitor] - The name of an existing performance profile for which the recording has been made.
323 * @property {?Object} headers - Additional headers mapping for multipart http(s) uploads
324 * @property {?string} fileFieldName [file] - The name of the form field, where the file content BLOB should be stored for
325 *                                            http(s) uploads
326 * @property {?Object|Array<Pair>} formFields - Additional form fields for multipart http(s) uploads
327 */
328
329/**
330 * Stops performance profiling for the device under test.
331 * The resulting file in .trace format can be either returned
332 * directly as base64-encoded zip archive or uploaded to a remote location
333 * (such files can be pretty large). Afterwards it is possible to unarchive and
334 * open such file with Xcode Dev Tools.
335 *
336 * @param {?StopRecordingOptions} opts - The set of possible stop record options
337 * @return {string} Either an empty string if the upload was successful or base-64 encoded
338 * content of zipped .trace file.
339 * @throws {Error} If no performance recording with given profile name/device udid combination
340 * has been started before or the resulting .trace file has not been generated properly.
341 */
342commands.mobileStopPerfRecord = async function mobileStopPerfRecord (opts = {}) {
343  if (!this.isFeatureEnabled(PERF_RECORD_FEAT_NAME) && !this.isRealDevice()) {
344    this.log.errorAndThrow(PERF_RECORD_SECURITY_MESSAGE);
345  }
346
347  if (_.isEmpty(this._perfRecorders)) {
348    this.log.info('No performance recorders have been started. Doing nothing');
349    return '';
350  }
351
352  const {
353    profileName = DEFAULT_PROFILE_NAME,
354    remotePath,
355  } = opts;
356
357  const recorders = this._perfRecorders.filter((x) => x.profileName === profileName);
358  if (_.isEmpty(recorders)) {
359    this.log.errorAndThrow(`There are no records for performance profile '${profileName}' ` +
360      `and device ${this.opts.device.udid}. Have you started the profiling before?`);
361  }
362
363  const recorder = _.first(recorders);
364  const resultPath = await recorder.stop();
365  if (!await fs.exists(resultPath)) {
366    this.log.errorAndThrow(`There is no ${DEFAULT_EXT} file found for performance profile '${profileName}' ` +
367      `and device ${this.opts.device.udid}. Make sure the selected profile is supported on this device`);
368  }
369
370  const result = await encodeBase64OrUpload(resultPath, remotePath, opts);
371  _.pull(this._perfRecorders, recorder);
372  await fs.rimraf(resultPath);
373  return result;
374};
375
376
377export { commands };
378export default commands;
379
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Appium Xcuitest Driver on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)