UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

143 lines (131 loc) 4.88 kB
import {retryInterval} from 'asyncbox'; import _ from 'lodash'; import {errors} from 'appium/driver'; import {util, imageUtil} from 'appium/support'; import type {XCUITestDriver} from '../driver'; import type {Simulator} from 'appium-ios-simulator'; import type {Element} from '@appium/types'; /** * Takes a screenshot of the current screen. * * @returns Base64-encoded screenshot data */ export async function getScreenshot(this: XCUITestDriver): Promise<string> { if (this.isWebContext()) { const webScreenshotMode = (await this.settings.getSettings()).webScreenshotMode; switch (_.toLower(webScreenshotMode)) { case 'page': case 'viewport': return await this.remote.captureScreenshot({ coordinateSystem: _.capitalize(webScreenshotMode) as 'Viewport' | 'Page', }); case 'native': case undefined: case null: break; default: this.log.warn( `The webScreenshotMode setting value '${webScreenshotMode}' is not known. ` + `Supported values are: page, viewport and native. Falling back to the native mode.`, ); break; } } const getScreenshotFromWDA = async (): Promise<string> => { this.log.debug(`Taking screenshot with WDA`); const data = await this.proxyCommand('/screenshot', 'GET'); if (!_.isString(data)) { throw new Error(`Unable to take screenshot. WDA returned '${JSON.stringify(data)}'`); } return data; }; // if we've specified an mjpeg server, use that if (this.mjpegStream) { this.log.info(`mjpeg video stream provided, returning latest frame as screenshot`); const data = await this.mjpegStream.lastChunkPNGBase64(); if (data) { return data; } this.log.warn( 'Tried to get screenshot from active MJPEG stream, but there ' + 'was no data yet. Falling back to regular screenshot methods.', ); } try { return await getScreenshotFromWDA(); } catch (err: any) { this.log.warn(`Error getting screenshot: ${err.message}`); } // simulator attempt if (this.isSimulator()) { this.log.info(`Falling back to 'simctl io screenshot' API`); const payload = await (this.device as Simulator).simctl.getScreenshot(); if (!payload) { throw new errors.UnableToCaptureScreen(); } return payload; } // Retry for real devices only. Fail fast on Simulator if simctl does not work as expected return (await retryInterval(2, 1000, getScreenshotFromWDA)) as string; } /** * Takes a screenshot of a specific element. * * @param el - Element to capture * @returns Base64-encoded screenshot data */ export async function getElementScreenshot( this: XCUITestDriver, el: Element<string> | string, ): Promise<string> { el = util.unwrapElement(el); if (this.isWebContext()) { const atomsElement = this.getAtomsElement(el); const {width, height} = await this.executeAtom('get_size', [atomsElement]); if (!width || !height) { throw new errors.UnableToCaptureScreen('Cannot take a screenshot of a zero-size element'); } const {x, y} = await this.executeAtom('get_top_left_coordinates', [atomsElement]); return await this.remote.captureScreenshot({rect: {x, y, width, height}}); } const data = await this.proxyCommand(`/element/${el}/screenshot`, 'GET'); if (!_.isString(data)) { throw new errors.UnableToCaptureScreen( `Unable to take an element screenshot. WDA returned: ${JSON.stringify(data)}`, ); } return data; } /** * Takes a screenshot of the current viewport. * * @returns Base64-encoded screenshot data */ export async function getViewportScreenshot(this: XCUITestDriver): Promise<string> { if (this.isWebContext()) { return await this.remote.captureScreenshot(); } const screenshot = await this.getScreenshot(); // if we don't have a status bar, there's nothing to crop, so we can avoid // extra calls and return straight away if ((await this.getStatusBarHeight()) === 0) { return screenshot; } const sharp = imageUtil.requireSharp(); const {width, height} = await sharp(Buffer.from(screenshot, 'base64')).metadata(); if (!width || !height) { throw new errors.UnableToCaptureScreen('The device screenshot is empty'); } this.log.debug(`Screenshot dimensions: ${width}x${height}`); const region = await this.getViewportRect(); if (region.width + region.left > width) { this.log.info('Viewport region exceeds screenshot width, adjusting region to fit'); region.width = width - region.left; } if (region.height + region.top > height) { this.log.info('Viewport region exceeds screenshot height, adjusting region to fit'); region.height = height - region.top; } this.log.debug(`Calculated viewport rect: ${JSON.stringify(region)}`); return await imageUtil.cropBase64Image(screenshot, region); }