UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

952 lines 92.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.XCUITestDriver = void 0; const appium_idb_1 = __importDefault(require("appium-idb")); const appium_ios_simulator_1 = require("appium-ios-simulator"); const appium_webdriveragent_1 = require("appium-webdriveragent"); const driver_1 = require("appium/driver"); const support_1 = require("appium/support"); const async_lock_1 = __importDefault(require("async-lock")); const asyncbox_1 = require("asyncbox"); const bluebird_1 = __importDefault(require("bluebird")); const lodash_1 = __importDefault(require("lodash")); const lru_cache_1 = require("lru-cache"); const node_events_1 = __importDefault(require("node:events")); const node_path_1 = __importDefault(require("node:path")); const node_url_1 = __importDefault(require("node:url")); const app_utils_1 = require("./app-utils"); const commands_1 = __importDefault(require("./commands")); const desired_caps_1 = require("./desired-caps"); const device_connections_factory_1 = require("./device-connections-factory"); const execute_method_map_1 = require("./execute-method-map"); const method_map_1 = require("./method-map"); const py_ios_device_client_1 = require("./real-device-clients/py-ios-device-client"); const real_device_management_1 = require("./real-device-management"); const real_device_1 = require("./real-device"); const simulator_management_1 = require("./simulator-management"); const utils_1 = require("./utils"); const app_infos_cache_1 = require("./app-infos-cache"); const context_1 = require("./commands/context"); const SHUTDOWN_OTHER_FEAT_NAME = 'shutdown_other_sims'; const CUSTOMIZE_RESULT_BUNDLE_PATH = 'customize_result_bundle_path'; const defaultServerCaps = { webStorageEnabled: false, locationContextEnabled: false, browserName: '', platform: 'MAC', javascriptEnabled: true, databaseEnabled: false, takesScreenshot: true, networkConnectionEnabled: false, }; const WDA_SIM_STARTUP_RETRIES = 2; const WDA_REAL_DEV_STARTUP_RETRIES = 1; const WDA_REAL_DEV_TUTORIAL_URL = 'https://appium.github.io/appium-xcuitest-driver/latest/preparation/real-device-config/'; const WDA_STARTUP_RETRY_INTERVAL = 10000; const DEFAULT_SETTINGS = { nativeWebTap: false, nativeWebTapStrict: false, useJSONSource: false, webScreenshotMode: 'native', shouldUseCompactResponses: true, elementResponseAttributes: 'type,label', // Read https://github.com/appium/WebDriverAgent/blob/master/WebDriverAgentLib/Utilities/FBConfiguration.m for following settings' values mjpegServerScreenshotQuality: 25, mjpegServerFramerate: 10, screenshotQuality: 1, mjpegScalingFactor: 100, // set `reduceMotion` to `null` so that it will be verified but still set either true/false reduceMotion: null, pageSourceExcludedAttributes: '' }; // This lock assures, that each driver session does not // affect shared resources of the other parallel sessions const SHARED_RESOURCES_GUARD = new async_lock_1.default(); const WEB_ELEMENTS_CACHE_SIZE = 500; const SUPPORTED_ORIENATIONS = ['LANDSCAPE', 'PORTRAIT']; const DEFAULT_MJPEG_SERVER_PORT = 9100; /* eslint-disable no-useless-escape */ /** @type {import('@appium/types').RouteMatcher[]} */ const NO_PROXY_NATIVE_LIST = [ ['DELETE', /window/], ['GET', /^\/session\/[^\/]+$/], ['GET', /alert_text/], ['GET', /alert\/[^\/]+/], ['GET', /appium/], ['GET', /attribute/], ['GET', /context/], ['GET', /location/], ['GET', /log/], ['GET', /screenshot/], ['GET', /size/], ['GET', /source/], ['GET', /timeouts$/], ['GET', /url/], ['GET', /window/], ['POST', /accept_alert/], ['POST', /actions$/], ['DELETE', /actions$/], ['POST', /alert_text/], ['POST', /alert\/[^\/]+/], ['POST', /appium/], ['POST', /appium\/device\/is_locked/], ['POST', /appium\/device\/lock/], ['POST', /appium\/device\/unlock/], ['POST', /back/], ['POST', /clear/], ['POST', /context/], ['POST', /dismiss_alert/], ['POST', /element\/active/], // MJSONWP get active element should proxy ['POST', /element$/], ['POST', /elements$/], ['POST', /execute/], ['POST', /keys/], ['POST', /log/], ['POST', /receive_async_response/], // always, in case context switches while waiting ['POST', /session\/[^\/]+\/location/], // geo location, but not element location ['POST', /shake/], ['POST', /timeouts/], ['POST', /url/], ['POST', /value/], ['POST', /window/], ['DELETE', /cookie/], ['GET', /cookie/], ['POST', /cookie/], ]; const NO_PROXY_WEB_LIST = /** @type {import('@appium/types').RouteMatcher[]} */ ([ ['GET', /attribute/], ['GET', /element/], ['GET', /text/], ['GET', /title/], ['POST', /clear/], ['POST', /click/], ['POST', /element/], ['POST', /forward/], ['POST', /frame/], ['POST', /keys/], ['POST', /refresh/], ]).concat(NO_PROXY_NATIVE_LIST); /* eslint-enable no-useless-escape */ const MEMOIZED_FUNCTIONS = ['getStatusBarHeight', 'getDevicePixelRatio', 'getScreenInfo']; // Capabilities that do not have xcodebuild process const CAP_NAMES_NO_XCODEBUILD_REQUIRED = ['webDriverAgentUrl', 'usePreinstalledWDA']; const BUNDLE_VERSION_PATTERN = /CFBundleVersion\s+=\s+"?([^(;|")]+)/; /** * @implements {ExternalDriver<XCUITestDriverConstraints, FullContext|string>} * @extends {BaseDriver<XCUITestDriverConstraints>} * @privateRemarks **This class should be considered "final"**. It cannot be extended * due to use of public class field assignments. If extending this class becomes a hard requirement, refer to the implementation of `BaseDriver` on how to do so. */ class XCUITestDriver extends driver_1.BaseDriver { /** * * @param {XCUITestDriverOpts} opts * @param {boolean} shouldValidateCaps */ constructor(opts = /** @type {XCUITestDriverOpts} */ ({}), shouldValidateCaps = true) { super(opts, shouldValidateCaps); /*---------------+ | ACTIVEAPPINFO | +---------------+*/ this.mobileGetActiveAppInfo = commands_1.default.activeAppInfoExtensions.mobileGetActiveAppInfo; /*-------+ | ALERT | +-------+*/ this.getAlertText = commands_1.default.alertExtensions.getAlertText; this.setAlertText = commands_1.default.alertExtensions.setAlertText; this.postAcceptAlert = commands_1.default.alertExtensions.postAcceptAlert; this.postDismissAlert = commands_1.default.alertExtensions.postDismissAlert; this.getAlertButtons = commands_1.default.alertExtensions.getAlertButtons; this.mobileHandleAlert = commands_1.default.alertExtensions.mobileHandleAlert; /*---------------+ | APPMANAGEMENT | +---------------+*/ this.mobileInstallApp = commands_1.default.appManagementExtensions.mobileInstallApp; this.mobileIsAppInstalled = commands_1.default.appManagementExtensions.mobileIsAppInstalled; this.mobileRemoveApp = commands_1.default.appManagementExtensions.mobileRemoveApp; this.mobileLaunchApp = commands_1.default.appManagementExtensions.mobileLaunchApp; this.mobileTerminateApp = commands_1.default.appManagementExtensions.mobileTerminateApp; this.mobileActivateApp = commands_1.default.appManagementExtensions.mobileActivateApp; this.mobileKillApp = commands_1.default.appManagementExtensions.mobileKillApp; this.mobileQueryAppState = commands_1.default.appManagementExtensions.mobileQueryAppState; this.installApp = commands_1.default.appManagementExtensions.installApp; this.activateApp = commands_1.default.appManagementExtensions.activateApp; this.isAppInstalled = commands_1.default.appManagementExtensions.isAppInstalled; // @ts-ignore it must return boolean this.terminateApp = commands_1.default.appManagementExtensions.terminateApp; this.queryAppState = commands_1.default.appManagementExtensions.queryAppState; this.mobileListApps = commands_1.default.appManagementExtensions.mobileListApps; this.mobileClearApp = commands_1.default.appManagementExtensions.mobileClearApp; /*------------+ | APPEARANCE | +------------+*/ this.mobileSetAppearance = commands_1.default.appearanceExtensions.mobileSetAppearance; this.mobileGetAppearance = commands_1.default.appearanceExtensions.mobileGetAppearance; /*------------+ | INCREASE CONTRAST | +------------+*/ this.mobileSetIncreaseContrast = commands_1.default.increaseContrastExtensions.mobileSetIncreaseContrast; this.mobileGetIncreaseContrast = commands_1.default.increaseContrastExtensions.mobileGetIncreaseContrast; /*------------+ | CONTENT SIZE | +------------+*/ this.mobileSetContentSize = commands_1.default.contentSizeExtensions.mobileSetContentSize; this.mobileGetContentSize = commands_1.default.contentSizeExtensions.mobileGetContentSize; /*------------+ | AUDIT | +------------+*/ this.mobilePerformAccessibilityAudit = commands_1.default.auditExtensions.mobilePerformAccessibilityAudit; /*---------+ | BATTERY | +---------+*/ this.mobileGetBatteryInfo = commands_1.default.batteryExtensions.mobileGetBatteryInfo; /*-----------+ | BIOMETRIC | +-----------+*/ this.mobileEnrollBiometric = commands_1.default.biometricExtensions.mobileEnrollBiometric; this.mobileSendBiometricMatch = commands_1.default.biometricExtensions.mobileSendBiometricMatch; this.mobileIsBiometricEnrolled = commands_1.default.biometricExtensions.mobileIsBiometricEnrolled; /*-------------+ | CERTIFICATE | +-------------+*/ this.mobileInstallCertificate = commands_1.default.certificateExtensions.mobileInstallCertificate; this.mobileListCertificates = commands_1.default.certificateExtensions.mobileListCertificates; this.mobileRemoveCertificate = commands_1.default.certificateExtensions.mobileRemoveCertificate; /*-----------+ | CLIPBOARD | +-----------+*/ this.setClipboard = commands_1.default.clipboardExtensions.setClipboard; this.getClipboard = commands_1.default.clipboardExtensions.getClipboard; /*-----------+ | CONDITION | +-----------+*/ this.listConditionInducers = commands_1.default.conditionExtensions.listConditionInducers; this.enableConditionInducer = commands_1.default.conditionExtensions.enableConditionInducer; this.disableConditionInducer = commands_1.default.conditionExtensions.disableConditionInducer; /*---------+ | CONTEXT | +---------+*/ this.getContexts = commands_1.default.contextExtensions.getContexts; this.getCurrentContext = commands_1.default.contextExtensions.getCurrentContext; // @ts-ignore This is OK this.getWindowHandle = commands_1.default.contextExtensions.getWindowHandle; this.getWindowHandles = commands_1.default.contextExtensions.getWindowHandles; this.setContext = commands_1.default.contextExtensions.setContext; // @ts-ignore This is OK this.setWindow = commands_1.default.contextExtensions.setWindow; this.activateRecentWebview = commands_1.default.contextExtensions.activateRecentWebview; this.connectToRemoteDebugger = commands_1.default.contextExtensions.connectToRemoteDebugger; this.getContextsAndViews = commands_1.default.contextExtensions.getContextsAndViews; this.listWebFrames = commands_1.default.contextExtensions.listWebFrames; this.mobileGetContexts = commands_1.default.contextExtensions.mobileGetContexts; this.onPageChange = commands_1.default.contextExtensions.onPageChange; this.useNewSafari = commands_1.default.contextExtensions.useNewSafari; this.getCurrentUrl = commands_1.default.contextExtensions.getCurrentUrl; this.getNewRemoteDebugger = commands_1.default.contextExtensions.getNewRemoteDebugger; this.getRecentWebviewContextId = commands_1.default.contextExtensions.getRecentWebviewContextId; this.isWebContext = commands_1.default.contextExtensions.isWebContext; this.isWebview = commands_1.default.contextExtensions.isWebview; this.setCurrentUrl = commands_1.default.contextExtensions.setCurrentUrl; this.stopRemote = commands_1.default.contextExtensions.stopRemote; /*------------+ | DEVICEINFO | +------------+*/ this.mobileGetDeviceInfo = commands_1.default.deviceInfoExtensions.mobileGetDeviceInfo; /*---------+ | ELEMENT | +---------+*/ this.elementDisplayed = commands_1.default.elementExtensions.elementDisplayed; this.elementEnabled = commands_1.default.elementExtensions.elementEnabled; this.elementSelected = commands_1.default.elementExtensions.elementSelected; this.getName = commands_1.default.elementExtensions.getName; this.getNativeAttribute = commands_1.default.elementExtensions.getNativeAttribute; this.getAttribute = commands_1.default.elementExtensions.getAttribute; this.getProperty = commands_1.default.elementExtensions.getProperty; this.getText = commands_1.default.elementExtensions.getText; this.getElementRect = commands_1.default.elementExtensions.getElementRect; this.getLocation = commands_1.default.elementExtensions.getLocation; this.getLocationInView = commands_1.default.elementExtensions.getLocationInView; this.getSize = commands_1.default.elementExtensions.getSize; /** @deprecated */ this.setValueImmediate = commands_1.default.elementExtensions.setValueImmediate; this.setValue = commands_1.default.elementExtensions.setValue; this.setValueWithWebAtom = commands_1.default.elementExtensions.setValueWithWebAtom; this.keys = commands_1.default.elementExtensions.keys; this.clear = commands_1.default.elementExtensions.clear; this.getContentSize = commands_1.default.elementExtensions.getContentSize; this.getNativeRect = commands_1.default.elementExtensions.getNativeRect; /*---------+ | EXECUTE | +---------+*/ this.receiveAsyncResponse = commands_1.default.executeExtensions.receiveAsyncResponse; this.execute = commands_1.default.executeExtensions.execute; this.executeAsync = commands_1.default.executeExtensions.executeAsync; this.executeMobile = commands_1.default.executeExtensions.executeMobile; this.mobileSimctl = commands_1.default.simctl.mobileSimctl; /*--------------+ | FILEMOVEMENT | +--------------+*/ this.pushFile = commands_1.default.fileMovementExtensions.pushFile; this.mobilePushFile = commands_1.default.fileMovementExtensions.mobilePushFile; this.pullFile = commands_1.default.fileMovementExtensions.pullFile; this.mobilePullFile = commands_1.default.fileMovementExtensions.mobilePullFile; this.mobileDeleteFolder = commands_1.default.fileMovementExtensions.mobileDeleteFolder; this.mobileDeleteFile = commands_1.default.fileMovementExtensions.mobileDeleteFile; this.pullFolder = commands_1.default.fileMovementExtensions.pullFolder; this.mobilePullFolder = commands_1.default.fileMovementExtensions.mobilePullFolder; /*--------+ | MEMORY | +--------+*/ this.mobileSendMemoryWarning = commands_1.default.memoryExtensions.mobileSendMemoryWarning; /*------+ | FIND | +------+*/ this.findElOrEls = commands_1.default.findExtensions.findElOrEls; this.findNativeElementOrElements = commands_1.default.findExtensions.findNativeElementOrElements; this.doNativeFind = commands_1.default.findExtensions.doNativeFind; this.getFirstVisibleChild = commands_1.default.findExtensions.getFirstVisibleChild; /*---------+ | GENERAL | +---------+*/ this.active = commands_1.default.generalExtensions.active; this.background = commands_1.default.appManagementExtensions.background; this.touchId = commands_1.default.generalExtensions.touchId; this.toggleEnrollTouchId = commands_1.default.generalExtensions.toggleEnrollTouchId; this.getWindowSize = commands_1.default.generalExtensions.getWindowSize; this.getDeviceTime = commands_1.default.generalExtensions.getDeviceTime; this.mobileGetDeviceTime = commands_1.default.generalExtensions.mobileGetDeviceTime; this.getWindowRect = commands_1.default.generalExtensions.getWindowRect; this.getStrings = commands_1.default.appStringsExtensions.getStrings; this.removeApp = commands_1.default.generalExtensions.removeApp; this.launchApp = commands_1.default.generalExtensions.launchApp; this.closeApp = commands_1.default.generalExtensions.closeApp; this.setUrl = commands_1.default.generalExtensions.setUrl; this.getViewportRect = commands_1.default.generalExtensions.getViewportRect; this.getScreenInfo = commands_1.default.generalExtensions.getScreenInfo; this.getStatusBarHeight = commands_1.default.generalExtensions.getStatusBarHeight; this.getDevicePixelRatio = commands_1.default.generalExtensions.getDevicePixelRatio; this.mobilePressButton = commands_1.default.generalExtensions.mobilePressButton; this.mobileSiriCommand = commands_1.default.generalExtensions.mobileSiriCommand; /*-------------+ | GEOLOCATION | +-------------+*/ this.mobileGetSimulatedLocation = commands_1.default.geolocationExtensions.mobileGetSimulatedLocation; this.mobileSetSimulatedLocation = commands_1.default.geolocationExtensions.mobileSetSimulatedLocation; this.mobileResetSimulatedLocation = commands_1.default.geolocationExtensions.mobileResetSimulatedLocation; /*---------+ | GESTURE | +---------+*/ this.mobileShake = commands_1.default.gestureExtensions.mobileShake; this.click = commands_1.default.gestureExtensions.click; this.releaseActions = commands_1.default.gestureExtensions.releaseActions; this.performActions = commands_1.default.gestureExtensions.performActions; this.nativeClick = commands_1.default.gestureExtensions.nativeClick; this.mobileScrollToElement = commands_1.default.gestureExtensions.mobileScrollToElement; this.mobileScroll = commands_1.default.gestureExtensions.mobileScroll; this.mobileSwipe = commands_1.default.gestureExtensions.mobileSwipe; this.mobilePinch = commands_1.default.gestureExtensions.mobilePinch; this.mobileDoubleTap = commands_1.default.gestureExtensions.mobileDoubleTap; this.mobileTwoFingerTap = commands_1.default.gestureExtensions.mobileTwoFingerTap; this.mobileTouchAndHold = commands_1.default.gestureExtensions.mobileTouchAndHold; this.mobileTap = commands_1.default.gestureExtensions.mobileTap; this.mobileDragFromToForDuration = commands_1.default.gestureExtensions.mobileDragFromToForDuration; this.mobileDragFromToWithVelocity = commands_1.default.gestureExtensions.mobileDragFromToWithVelocity; this.mobileTapWithNumberOfTaps = commands_1.default.gestureExtensions.mobileTapWithNumberOfTaps; this.mobileForcePress = commands_1.default.gestureExtensions.mobileForcePress; this.mobileSelectPickerWheelValue = commands_1.default.gestureExtensions.mobileSelectPickerWheelValue; this.mobileRotateElement = commands_1.default.gestureExtensions.mobileRotateElement; /*-------+ | IOHID | +-------+*/ this.mobilePerformIoHidEvent = commands_1.default.iohidExtensions.mobilePerformIoHidEvent; /*-----------+ | KEYCHAINS | +-----------+*/ this.mobileClearKeychains = commands_1.default.keychainsExtensions.mobileClearKeychains; /*----------+ | KEYBOARD | +----------+*/ this.hideKeyboard = commands_1.default.keyboardExtensions.hideKeyboard; this.mobileHideKeyboard = commands_1.default.keyboardExtensions.mobileHideKeyboard; this.isKeyboardShown = commands_1.default.keyboardExtensions.isKeyboardShown; this.mobileKeys = commands_1.default.keyboardExtensions.mobileKeys; /*--------------+ | LOCALIZATION | +--------------+*/ this.mobileConfigureLocalization = commands_1.default.localizationExtensions.mobileConfigureLocalization; /*----------+ | LOCATION | +----------+*/ this.getGeoLocation = commands_1.default.locationExtensions.getGeoLocation; this.setGeoLocation = commands_1.default.locationExtensions.setGeoLocation; this.mobileResetLocationService = commands_1.default.locationExtensions.mobileResetLocationService; /*------+ | LOCK | +------+*/ this.lock = commands_1.default.lockExtensions.lock; this.unlock = commands_1.default.lockExtensions.unlock; this.isLocked = commands_1.default.lockExtensions.isLocked; /*-----+ | LOG | +-----+*/ this.extractLogs = commands_1.default.logExtensions.extractLogs; this.supportedLogTypes = commands_1.default.logExtensions.supportedLogTypes; this.startLogCapture = commands_1.default.logExtensions.startLogCapture; this.mobileStartLogsBroadcast = commands_1.default.logExtensions.mobileStartLogsBroadcast; this.mobileStopLogsBroadcast = commands_1.default.logExtensions.mobileStopLogsBroadcast; /*------------+ | NAVIGATION | +------------+*/ this.back = commands_1.default.navigationExtensions.back; this.forward = commands_1.default.navigationExtensions.forward; this.closeWindow = commands_1.default.navigationExtensions.closeWindow; this.nativeBack = commands_1.default.navigationExtensions.nativeBack; this.mobileDeepLink = commands_1.default.navigationExtensions.mobileDeepLink; /*---------------+ | NOTIFICATIONS | +---------------+*/ this.mobilePushNotification = commands_1.default.notificationsExtensions.mobilePushNotification; this.mobileExpectNotification = commands_1.default.notificationsExtensions.mobileExpectNotification; /*------------+ | PASTEBOARD | +------------+*/ this.mobileSetPasteboard = commands_1.default.pasteboardExtensions.mobileSetPasteboard; this.mobileGetPasteboard = commands_1.default.pasteboardExtensions.mobileGetPasteboard; /*------+ | PCAP | +------+*/ this.mobileStartPcap = commands_1.default.pcapExtensions.mobileStartPcap; this.mobileStopPcap = commands_1.default.pcapExtensions.mobileStopPcap; /*-------------+ | PERFORMANCE | +-------------+*/ this.mobileStartPerfRecord = commands_1.default.performanceExtensions.mobileStartPerfRecord; this.mobileStopPerfRecord = commands_1.default.performanceExtensions.mobileStopPerfRecord; /*-------------+ | PERMISSIONS | +-------------+*/ this.mobileResetPermission = commands_1.default.permissionsExtensions.mobileResetPermission; this.mobileGetPermission = commands_1.default.permissionsExtensions.mobileGetPermission; this.mobileSetPermissions = commands_1.default.permissionsExtensions.mobileSetPermissions; /*-------------+ | PROXYHELPER | +-------------+*/ this.proxyCommand = commands_1.default.proxyHelperExtensions.proxyCommand; /*-------------+ | RECORDAUDIO | +-------------+*/ this.startAudioRecording = commands_1.default.recordAudioExtensions.startAudioRecording; this.stopAudioRecording = commands_1.default.recordAudioExtensions.stopAudioRecording; /*--------------+ | RECORDSCREEN | +--------------+*/ this._recentScreenRecorder = commands_1.default.recordScreenExtensions._recentScreenRecorder; this.startRecordingScreen = commands_1.default.recordScreenExtensions.startRecordingScreen; this.stopRecordingScreen = commands_1.default.recordScreenExtensions.stopRecordingScreen; /*-------------+ | SCREENSHOTS | +-------------+*/ this.getScreenshot = commands_1.default.screenshotExtensions.getScreenshot; this.getElementScreenshot = commands_1.default.screenshotExtensions.getElementScreenshot; this.getViewportScreenshot = commands_1.default.screenshotExtensions.getViewportScreenshot; /*--------+ | SOURCE | +--------+*/ this.getPageSource = commands_1.default.sourceExtensions.getPageSource; this.mobileGetSource = commands_1.default.sourceExtensions.mobileGetSource; /*----------+ | TIMEOUTS | +----------+*/ this.pageLoadTimeoutW3C = commands_1.default.timeoutExtensions.pageLoadTimeoutW3C; this.pageLoadTimeoutMJSONWP = commands_1.default.timeoutExtensions.pageLoadTimeoutMJSONWP; this.scriptTimeoutW3C = commands_1.default.timeoutExtensions.scriptTimeoutW3C; this.scriptTimeoutMJSONWP = commands_1.default.timeoutExtensions.scriptTimeoutMJSONWP; this.asyncScriptTimeout = commands_1.default.timeoutExtensions.asyncScriptTimeout; this.setPageLoadTimeout = commands_1.default.timeoutExtensions.setPageLoadTimeout; this.setAsyncScriptTimeout = commands_1.default.timeoutExtensions.setAsyncScriptTimeout; /*-----+ | WEB | +-----+*/ this.setFrame = commands_1.default.webExtensions.setFrame; this.getCssProperty = commands_1.default.webExtensions.getCssProperty; this.submit = commands_1.default.webExtensions.submit; this.refresh = commands_1.default.webExtensions.refresh; this.getUrl = commands_1.default.webExtensions.getUrl; this.title = commands_1.default.webExtensions.title; this.getCookies = commands_1.default.webExtensions.getCookies; this.setCookie = commands_1.default.webExtensions.setCookie; this.deleteCookie = commands_1.default.webExtensions.deleteCookie; this.deleteCookies = commands_1.default.webExtensions.deleteCookies; this._deleteCookie = commands_1.default.webExtensions._deleteCookie; this.cacheWebElement = commands_1.default.webExtensions.cacheWebElement; this.cacheWebElements = commands_1.default.webExtensions.cacheWebElements; this.executeAtom = commands_1.default.webExtensions.executeAtom; this.executeAtomAsync = commands_1.default.webExtensions.executeAtomAsync; this.getAtomsElement = commands_1.default.webExtensions.getAtomsElement; this.convertElementsForAtoms = commands_1.default.webExtensions.convertElementsForAtoms; this.getElementId = commands_1.default.webExtensions.getElementId; this.hasElementId = commands_1.default.webExtensions.hasElementId; this.findWebElementOrElements = commands_1.default.webExtensions.findWebElementOrElements; this.clickWebCoords = commands_1.default.webExtensions.clickWebCoords; this.getSafariIsIphone = commands_1.default.webExtensions.getSafariIsIphone; this.getSafariDeviceSize = commands_1.default.webExtensions.getSafariDeviceSize; this.getSafariIsNotched = commands_1.default.webExtensions.getSafariIsNotched; this.getExtraTranslateWebCoordsOffset = commands_1.default.webExtensions.getExtraTranslateWebCoordsOffset; this.getExtraNativeWebTapOffset = commands_1.default.webExtensions.getExtraNativeWebTapOffset; this.nativeWebTap = commands_1.default.webExtensions.nativeWebTap; this.translateWebCoords = commands_1.default.webExtensions.translateWebCoords; this.checkForAlert = commands_1.default.webExtensions.checkForAlert; this.waitForAtom = commands_1.default.webExtensions.waitForAtom; this.mobileWebNav = commands_1.default.webExtensions.mobileWebNav; this.getWdaLocalhostRoot = commands_1.default.webExtensions.getWdaLocalhostRoot; this.mobileCalibrateWebToRealCoordinatesTranslation = commands_1.default.webExtensions.mobileCalibrateWebToRealCoordinatesTranslation; this.mobileUpdateSafariPreferences = commands_1.default.webExtensions.mobileUpdateSafariPreferences; /*--------+ | XCTEST | +--------+*/ this.mobileRunXCTest = commands_1.default.xctestExtensions.mobileRunXCTest; this.mobileInstallXCTestBundle = commands_1.default.xctestExtensions.mobileInstallXCTestBundle; this.mobileListXCTestBundles = commands_1.default.xctestExtensions.mobileListXCTestBundles; this.mobileListXCTestsInTestBundle = commands_1.default.xctestExtensions.mobileListXCTestsInTestBundle; /*----------------------+ | XCTEST SCREEN RECORD | +---------------------+*/ this.mobileStartXctestScreenRecording = commands_1.default.xctestRecordScreenExtensions.mobileStartXctestScreenRecording; this.mobileGetXctestScreenRecordingInfo = commands_1.default.xctestRecordScreenExtensions.mobileGetXctestScreenRecordingInfo; this.mobileStopXctestScreenRecording = commands_1.default.xctestRecordScreenExtensions.mobileStopXctestScreenRecording; this.locatorStrategies = [ 'xpath', 'id', 'name', 'class name', '-ios predicate string', '-ios class chain', 'accessibility id', 'css selector', ]; this.webLocatorStrategies = [ 'link text', 'css selector', 'tag name', 'link text', 'partial link text', ]; this.curWebFrames = []; this._perfRecorders = []; this.desiredCapConstraints = desired_caps_1.desiredCapConstraints; this.webElementsCache = new lru_cache_1.LRUCache({ max: WEB_ELEMENTS_CACHE_SIZE, }); this.webviewCalibrationResult = null; this._waitingAtoms = { count: 0, alertNotifier: new node_events_1.default(), alertMonitor: bluebird_1.default.resolve(), }; this.resetIos(); this.settings = new driver_1.DeviceSettings(DEFAULT_SETTINGS, this.onSettingsUpdate.bind(this)); this.logs = {}; this._trafficCapture = null; // memoize functions here, so that they are done on a per-instance basis for (const fn of MEMOIZED_FUNCTIONS) { this[fn] = lodash_1.default.memoize(this[fn]); } this.lifecycleData = {}; this._audioRecorder = null; this.appInfosCache = new app_infos_cache_1.AppInfosCache(this.log); this.remote = null; this.doesSupportBidi = true; } async onSettingsUpdate(key, value) { // skip sending the update request to the WDA nor saving it in opts // to not spend unnecessary time. if (['pageSourceExcludedAttributes'].includes(key)) { return; } if (key !== 'nativeWebTap' && key !== 'nativeWebTapStrict') { return await this.proxyCommand('/appium/settings', 'POST', { settings: { [key]: value }, }); } this.opts[key] = !!value; } resetIos() { this.opts = this.opts || {}; // @ts-ignore this is ok this.wda = null; this.jwpProxyActive = false; this.proxyReqRes = null; this.safari = false; this.cachedWdaStatus = null; this.curWebFrames = []; this._currentUrl = null; this.curContext = null; this.xcodeVersion = undefined; this.contexts = []; this.implicitWaitMs = 0; this.pageLoadMs = 6000; this.landscapeWebCoordsOffset = 0; this.remote = null; this._conditionInducerService = null; this.webElementsCache = new lru_cache_1.LRUCache({ max: WEB_ELEMENTS_CACHE_SIZE, }); this._waitingAtoms = { count: 0, alertNotifier: new node_events_1.default(), alertMonitor: bluebird_1.default.resolve(), }; } get driverData() { // TODO fill out resource info here return {}; } async getStatus() { const status = { ready: true, message: 'The driver is ready to accept new connections', build: await (0, utils_1.getDriverInfo)(), }; if (this.cachedWdaStatus) { status.wda = this.cachedWdaStatus; } return status; } mergeCliArgsToOpts() { let didMerge = false; // this.cliArgs should never include anything we do not expect. for (const [key, value] of Object.entries(this.cliArgs ?? {})) { if (lodash_1.default.has(this.opts, key)) { this.log.info(`CLI arg '${key}' with value '${value}' overwrites value '${this.opts[key]}' sent in via caps)`); didMerge = true; } this.opts[key] = value; } return didMerge; } /** * @returns {Simulator|RealDevice} */ get device() { return this._device; } isXcodebuildNeeded() { return !(CAP_NAMES_NO_XCODEBUILD_REQUIRED.some((x) => Boolean(this.opts[x]))); } async createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData) { try { let [sessionId, caps] = await super.createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData); // merge cli args to opts, and if we did merge any, revalidate opts to ensure the final set // is also consistent if (this.mergeCliArgsToOpts()) { this.validateDesiredCaps({ ...caps, ...this.cliArgs }); } await this.start(); // merge server capabilities + desired capabilities caps = Object.assign({}, defaultServerCaps, caps); // update the udid with what is actually used caps.udid = this.opts.udid; // ensure we track nativeWebTap capability as a setting as well if (lodash_1.default.has(this.opts, 'nativeWebTap')) { await this.updateSettings({ nativeWebTap: this.opts.nativeWebTap }); } // ensure we track nativeWebTapStrict capability as a setting as well if (lodash_1.default.has(this.opts, 'nativeWebTapStrict')) { await this.updateSettings({ nativeWebTapStrict: this.opts.nativeWebTapStrict }); } // ensure we track useJSONSource capability as a setting as well if (lodash_1.default.has(this.opts, 'useJSONSource')) { await this.updateSettings({ useJSONSource: this.opts.useJSONSource }); } /** @type {import('appium-webdriveragent').WDASettings} */ let wdaSettings = { elementResponseAttributes: DEFAULT_SETTINGS.elementResponseAttributes, shouldUseCompactResponses: DEFAULT_SETTINGS.shouldUseCompactResponses, }; if ('elementResponseAttributes' in this.opts && lodash_1.default.isString(this.opts.elementResponseAttributes)) { wdaSettings.elementResponseAttributes = this.opts.elementResponseAttributes; } if ('shouldUseCompactResponses' in this.opts && lodash_1.default.isBoolean(this.opts.shouldUseCompactResponses)) { wdaSettings.shouldUseCompactResponses = this.opts.shouldUseCompactResponses; } if ('mjpegServerScreenshotQuality' in this.opts && lodash_1.default.isNumber(this.opts.mjpegServerScreenshotQuality)) { wdaSettings.mjpegServerScreenshotQuality = this.opts.mjpegServerScreenshotQuality; } if ('mjpegServerFramerate' in this.opts && lodash_1.default.isNumber(this.opts.mjpegServerFramerate)) { wdaSettings.mjpegServerFramerate = this.opts.mjpegServerFramerate; } if (lodash_1.default.has(this.opts, 'screenshotQuality')) { this.log.info(`Setting the quality of phone screenshot: '${this.opts.screenshotQuality}'`); wdaSettings.screenshotQuality = this.opts.screenshotQuality; } // ensure WDA gets our defaults instead of whatever its own might be await this.updateSettings(wdaSettings); await this.handleMjpegOptions(); return /** @type {[string, import('@appium/types').DriverCaps<XCUITestDriverConstraints>]} */ ([ sessionId, caps, ]); } catch (e) { this.log.error(JSON.stringify(e)); await this.deleteSession(); throw e; } } /** * Handles MJPEG server-related capabilities * @returns {Promise<void>} */ async handleMjpegOptions() { await this.allocateMjpegServerPort(); // turn on mjpeg stream reading if requested if (this.opts.mjpegScreenshotUrl) { this.log.info(`Starting MJPEG stream reading URL: '${this.opts.mjpegScreenshotUrl}'`); this.mjpegStream = new support_1.mjpeg.MJpegStream(this.opts.mjpegScreenshotUrl); await this.mjpegStream.start(); } } /** * Allocates and configures port forwarding for the MJPEG server * @returns {Promise<void>} * @throws {Error} If port forwarding fails and mjpegServerPort capability value is provided explicitly */ async allocateMjpegServerPort() { const mjpegServerPort = this.opts.mjpegServerPort || DEFAULT_MJPEG_SERVER_PORT; this.log.debug(`Forwarding MJPEG server port ${mjpegServerPort} to local port ${mjpegServerPort}`); try { await device_connections_factory_1.DEVICE_CONNECTIONS_FACTORY.requestConnection(this.opts.udid, mjpegServerPort, { devicePort: mjpegServerPort, usePortForwarding: this.isRealDevice(), }); } catch (error) { if (lodash_1.default.isUndefined(this.opts.mjpegServerPort)) { this.log.warn(`Cannot forward the device port ${DEFAULT_MJPEG_SERVER_PORT} to the local port ${DEFAULT_MJPEG_SERVER_PORT}. ` + `Certain features, like MJPEG-based screen recording, will be unavailable during this session. ` + `Try to customize the value of 'mjpegServerPort' capability as a possible solution`); } else { this.log.debug(error.stack); throw new Error(`Cannot ensure MJPEG broadcast functionality by forwarding the local port ${mjpegServerPort} ` + `requested by the 'mjpegServerPort' capability to the device port ${mjpegServerPort}. ` + `Original error: ${error}`); } } } /** * Returns the default URL for Safari browser * @returns {string} The default URL */ getDefaultUrl() { // Setting this to some external URL slows down the session init return `${this.getWdaLocalhostRoot()}/health`; } async start() { this.opts.noReset = !!this.opts.noReset; this.opts.fullReset = !!this.opts.fullReset; await (0, utils_1.printUser)(); this._iosSdkVersion = null; // For WDA and xcodebuild const { device, udid, realDevice } = await this.determineDevice(); this.log.info(`Determining device to run tests on: udid: '${udid}', real device: ${realDevice}`); this._device = device; this.opts.udid = udid; if (this.opts.simulatorDevicesSetPath) { if (realDevice) { this.log.info(`The 'simulatorDevicesSetPath' capability is only supported for Simulator devices`); } else { this.log.info(`Setting simulator devices set path to '${this.opts.simulatorDevicesSetPath}'`); ( /** @type {Simulator} */(this.device)).devicesSetPath = this.opts.simulatorDevicesSetPath; } } // at this point if there is no platformVersion, get it from the device if (!this.opts.platformVersion) { this.opts.platformVersion = await this.device.getPlatformVersion(); this.log.info(`No platformVersion specified. Using device version: '${this.opts.platformVersion}'`); } const normalizedVersion = (0, utils_1.normalizePlatformVersion)(this.opts.platformVersion); if (this.opts.platformVersion !== normalizedVersion) { this.log.info(`Normalized platformVersion capability value '${this.opts.platformVersion}' to '${normalizedVersion}'`); this.opts.platformVersion = normalizedVersion; } this.caps.platformVersion = this.opts.platformVersion; if (lodash_1.default.isEmpty(this.xcodeVersion) && (this.isXcodebuildNeeded() || this.isSimulator())) { // no `webDriverAgentUrl`, or on a simulator, so we need an Xcode version this.xcodeVersion = await (0, utils_1.getAndCheckXcodeVersion)(); } this.logEvent('xcodeDetailsRetrieved'); if (lodash_1.default.toLower(this.opts.browserName) === 'safari') { this.log.info('Safari test requested'); this.safari = true; this.opts.app = undefined; this.opts.processArguments = this.opts.processArguments || {}; real_device_management_1.applySafariStartupArgs.bind(this)(); this.opts.bundleId = app_utils_1.SAFARI_BUNDLE_ID; this._currentUrl = this.opts.safariInitialUrl || this.getDefaultUrl(); } else if (this.opts.app || this.opts.bundleId) { await this.configureApp(); } this.logEvent('appConfigured'); // fail very early if the app doesn't actually exist // or if bundle id doesn't point to an installed app if (this.opts.app) { await (0, utils_1.checkAppPresent)(this.opts.app); if (!this.opts.bundleId) { this.opts.bundleId = await this.appInfosCache.extractBundleId(this.opts.app); } } await this.runReset(); this.wda = new appium_webdriveragent_1.WebDriverAgent( /** @type {import('appium-xcode').XcodeVersion} */ (this.xcodeVersion), { ...this.opts, device: this.device, realDevice: this.isRealDevice(), iosSdkVersion: this._iosSdkVersion, reqBasePath: this.basePath, }, // @ts-ignore this is ok this.log); // Derived data path retrieval is an expensive operation // We could start that now in background and get the cached result // whenever it is needed // eslint-disable-next-line promise/prefer-await-to-then this.wda.retrieveDerivedDataPath().catch((e) => this.log.debug(e)); const memoizedLogInfo = lodash_1.default.memoize(() => { this.log.info("'skipLogCapture' is set. Skipping starting logs such as crash, system, safari console and safari network."); }); const startLogCapture = async () => { if (this.opts.skipLogCapture) { memoizedLogInfo(); return false; } const result = await this.startLogCapture(); if (result) { this.logEvent('logCaptureStarted'); } return result; }; const isLogCaptureStarted = await startLogCapture(); this.log.info(`Setting up ${this.isRealDevice() ? 'real device' : 'simulator'}`); if (this.isSimulator()) { await this.initSimulator(); if (!isLogCaptureStarted) { // Retry log capture if Simulator was not running before await startLogCapture(); } } else if (this.opts.customSSLCert) { await new py_ios_device_client_1.Pyidevice({ udid, log: this.log, }).installProfile({ payload: this.opts.customSSLCert }); this.logEvent('customCertInstalled'); } await this.installAUT(); // if we only have bundle identifier and no app, fail if it is not already installed if (!this.opts.app && this.opts.bundleId && !this.isSafari() && !(await this.device.isAppInstalled(this.opts.bundleId))) { throw this.log.errorWithException(`App with bundle identifier '${this.opts.bundleId}' unknown`); } if (this.isSimulator()) { if (this.opts.permissions) { this.log.debug('Setting the requested permissions before WDA is started'); for (const [bundleId, permissionsMapping] of lodash_1.default.toPairs(JSON.parse(this.opts.permissions))) { await /** @type {Simulator} */ (this.device).setPermissions(bundleId, permissionsMapping); } } // TODO: Deprecate and remove this block together with calendarAccessAuthorized capability if (lodash_1.default.isBoolean(this.opts.calendarAccessAuthorized)) { this.log.warn(`The 'calendarAccessAuthorized' capability is deprecated and will be removed soon. ` + `Consider using 'permissions' one instead with 'calendar' key`); const methodName = `${this.opts.calendarAccessAuthorized ? 'enable' : 'disable'}CalendarAccess`; await this.device[methodName](this.opts.bundleId); } } await this.startWda(); if (lodash_1.default.isString(this.opts.orientation)) { await this.setInitialOrientation(this.opts.orientation); this.logEvent('orientationSet'); } if (this.isSafari() || this.opts.autoWebview) { await this.activateRecentWebview(); } else { // We want to always setup the initial context value upon session startup await context_1.notifyBiDiContextChange.bind(this)(); } if (this.isSafari()) { if ((0, utils_1.shouldSetInitialSafariUrl)(this.opts)) { this.log.info(`About to set the initial Safari URL to '${this.getCurrentUrl()}'`); if (lodash_1.default.isNil(this.opts.safariInitialUrl) && lodash_1.default.isNil(this.opts.initialDeeplinkUrl)) { this.log.info(`Use the 'safariInitialUrl' capability to customize it`); } ; await this.setUrl(this.getCurrentUrl()); } else { const currentUrl = await this.getUrl(); this.log.info(`Current URL: ${currentUrl}`); this.setCurrentUrl(currentUrl); } } } /** * Start the simulator and initialize based on capabilities */ async initSimulator() { const device = /** @type {Simulator} */ (this.device); if (this.opts.shutdownOtherSimulators) { this.assertFeatureEnabled(SHUTDOWN_OTHER_FEAT_NAME); await simulator_management_1.shutdownOtherSimulators.bind(this)(); } await this.startSim(); if (this.opts.customSSLCert) { // Simulator must be booted in order to call this helper await device.addCertificate(this.opts.customSSLCert); this.logEvent('customCertInstalled'); } if (await simulator_management_1.setSafariPrefs.bind(this)()) { this.log.debug('Safari preferences have been updated'); } if (await simulator_management_1.setLocalizationPrefs.bind(this)()) { this.log.debug('Localization preferences have been updated'); } /** @type {Promise[]} */ const promises = ['reduceMotion', 'reduceTransparency', 'autoFillPasswords'] .filter((optName) => lodash_1.default.isBoolean(this.opts[optName])) .map((optName) => { this.log.info(`Setting ${optName} to ${this.opts[optName]}`); return device[`set${lodash_1.default.upperFirst(optName)}`](this.opts[optName]); }); await bluebird_1.default.all(promises); if (this.opts.launchWithIDB) { try { const idb = new appium_idb_1.default({ udid: this.opts.udid }); await idb.connect(); device.idb = idb; } catch (e) { this.log.debug(e.stack); this.log.warn(`idb will not be used for Simulator interaction. Original error: ${e.message}`); } } this.logEvent('simStarted'); } /** * Start WebDriverAgentRunner */ async startWda() { // Don't cleanup the processes if webDriverAgentUrl is set if (!support_1.util.hasValue(this.wda.webDriverAgentUrl)) { await this.wda.cleanupObsoleteProcesses(); } const usePortForwarding = this.isRealDevice() && !this.wda.webDriverAgentUrl && (0, utils_1.isLocalHost)(this.wda.wdaBaseUrl); await device_connections_factory_1.DEVICE_CONNECTIONS_FACTORY.requestConnection(this.opts.udid, this.wda.url.port, { devicePort: usePortForwarding ? this.wda.wdaR