UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

1,110 lines (1,109 loc) 85.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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_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 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 promises_1 = require("node:timers/promises"); const utils_1 = require("./utils"); const activeAppInfoCommands = __importStar(require("./commands/active-app-info")); const alertCommands = __importStar(require("./commands/alert")); const appManagementCommands = __importStar(require("./commands/app-management")); const appearanceCommands = __importStar(require("./commands/appearance")); const appStringsCommands = __importStar(require("./commands/app-strings")); const auditCommands = __importStar(require("./commands/audit")); const batteryCommands = __importStar(require("./commands/battery")); const biometricCommands = __importStar(require("./commands/biometric")); const certificateCommands = __importStar(require("./commands/certificate")); const clipboardCommands = __importStar(require("./commands/clipboard")); const conditionCommands = __importStar(require("./commands/condition")); const contentSizeCommands = __importStar(require("./commands/content-size")); const contextCommands = __importStar(require("./commands/context")); const deviceInfoCommands = __importStar(require("./commands/device-info")); const elementCommands = __importStar(require("./commands/element")); const executeCommands = __importStar(require("./commands/execute")); const fileMovementCommands = __importStar(require("./commands/file-movement")); const findCommands = __importStar(require("./commands/find")); const generalCommands = __importStar(require("./commands/general")); const geolocationCommands = __importStar(require("./commands/geolocation")); const gestureCommands = __importStar(require("./commands/gesture")); const iohidCommands = __importStar(require("./commands/iohid")); const keychainsCommands = __importStar(require("./commands/keychains")); const keyboardCommands = __importStar(require("./commands/keyboard")); const localizationCommands = __importStar(require("./commands/localization")); const locationCommands = __importStar(require("./commands/location")); const lockCommands = __importStar(require("./commands/lock")); const logCommands = __importStar(require("./commands/log")); const memoryCommands = __importStar(require("./commands/memory")); const navigationCommands = __importStar(require("./commands/navigation")); const notificationsCommands = __importStar(require("./commands/notifications")); const pasteboardCommands = __importStar(require("./commands/pasteboard")); const networkMonitorCommands = __importStar(require("./commands/network-monitor")); const performanceCommands = __importStar(require("./commands/performance")); const permissionsCommands = __importStar(require("./commands/permissions")); const proxyHelperCommands = __importStar(require("./commands/proxy-helper")); const recordAudioCommands = __importStar(require("./commands/record-audio")); const recordScreenCommands = __importStar(require("./commands/recordscreen")); const screenshotCommands = __importStar(require("./commands/screenshots")); const sourceCommands = __importStar(require("./commands/source")); const simctlCommands = __importStar(require("./commands/simctl")); const timeoutCommands = __importStar(require("./commands/timeouts")); const webCommands = __importStar(require("./commands/web")); const xctestCommands = __importStar(require("./commands/xctest")); const xctestRecordScreenCommands = __importStar(require("./commands/xctest-record-screen")); const increaseContrastCommands = __importStar(require("./commands/increase-contrast")); const desired_caps_1 = require("./desired-caps"); const device_connections_factory_1 = require("./device/device-connections-factory"); const execute_method_map_1 = require("./execute-method-map"); const method_map_1 = require("./method-map"); const real_device_management_1 = require("./device/real-device-management"); const simulator_management_1 = require("./device/simulator-management"); 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 */ 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', /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 = [ ['GET', /attribute/], ['GET', /element/], ['GET', /text/], ['GET', /title/], ['POST', /clear/], ['POST', /click/], ['POST', /element/], ['POST', /forward/], ['POST', /frame/], ['POST', /keys/], ['POST', /refresh/], ...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']; class XCUITestDriver extends driver_1.BaseDriver { static newMethodMap = method_map_1.newMethodMap; static executeMethodMap = execute_method_map_1.executeMethodMap; curWindowHandle; selectingNewPage; contexts; curContext; curWebFrames; webviewCalibrationResult; asyncWaitMs; _syslogWebsocketListener; _perfRecorders; webElementsCache; _conditionInducer; // Condition inducer facade that abstracts implementation details _isSafariIphone; _isSafariNotched; _waitingAtoms; lifecycleData; _audioRecorder; xcodeVersion; _networkMonitorSession; _recentScreenRecorder; _device; _iosSdkVersion; _wda; _remote; logs; _bidiServerLogListener; // Additional properties that were missing appInfosCache; doesSupportBidi; jwpProxyActive; proxyReqRes; safari; cachedWdaStatus; _currentUrl; pageLoadMs; landscapeWebCoordsOffset; mjpegStream; deviceConnectionsFactory; /*---------------+ | ACTIVEAPPINFO | +---------------+*/ mobileGetActiveAppInfo = activeAppInfoCommands.mobileGetActiveAppInfo; /*-------+ | ALERT | +-------+*/ getAlertText = alertCommands.getAlertText; setAlertText = alertCommands.setAlertText; postAcceptAlert = alertCommands.postAcceptAlert; postDismissAlert = alertCommands.postDismissAlert; getAlertButtons = alertCommands.getAlertButtons; mobileHandleAlert = alertCommands.mobileHandleAlert; /*---------------+ | APPMANAGEMENT | +---------------+*/ mobileInstallApp = appManagementCommands.mobileInstallApp; mobileIsAppInstalled = appManagementCommands.mobileIsAppInstalled; mobileRemoveApp = appManagementCommands.mobileRemoveApp; mobileLaunchApp = appManagementCommands.mobileLaunchApp; mobileTerminateApp = appManagementCommands.mobileTerminateApp; mobileActivateApp = appManagementCommands.mobileActivateApp; mobileKillApp = appManagementCommands.mobileKillApp; mobileQueryAppState = appManagementCommands.mobileQueryAppState; installApp = appManagementCommands.installApp; activateApp = appManagementCommands.activateApp; isAppInstalled = appManagementCommands.isAppInstalled; terminateApp = appManagementCommands.terminateApp; queryAppState = appManagementCommands.queryAppState; mobileListApps = appManagementCommands.mobileListApps; mobileClearApp = appManagementCommands.mobileClearApp; /*------------+ | APPEARANCE | +------------+*/ mobileSetAppearance = appearanceCommands.mobileSetAppearance; mobileGetAppearance = appearanceCommands.mobileGetAppearance; /*------------+ | INCREASE CONTRAST | +------------+*/ mobileSetIncreaseContrast = increaseContrastCommands.mobileSetIncreaseContrast; mobileGetIncreaseContrast = increaseContrastCommands.mobileGetIncreaseContrast; /*------------+ | CONTENT SIZE | +------------+*/ mobileSetContentSize = contentSizeCommands.mobileSetContentSize; mobileGetContentSize = contentSizeCommands.mobileGetContentSize; /*------------+ | AUDIT | +------------+*/ mobilePerformAccessibilityAudit = auditCommands.mobilePerformAccessibilityAudit; /*---------+ | BATTERY | +---------+*/ mobileGetBatteryInfo = batteryCommands.mobileGetBatteryInfo; /*-----------+ | BIOMETRIC | +-----------+*/ mobileEnrollBiometric = biometricCommands.mobileEnrollBiometric; mobileSendBiometricMatch = biometricCommands.mobileSendBiometricMatch; mobileIsBiometricEnrolled = biometricCommands.mobileIsBiometricEnrolled; /*-------------+ | CERTIFICATE | +-------------+*/ mobileInstallCertificate = certificateCommands.mobileInstallCertificate; mobileListCertificates = certificateCommands.mobileListCertificates; mobileRemoveCertificate = certificateCommands.mobileRemoveCertificate; /*-----------+ | CLIPBOARD | +-----------+*/ setClipboard = clipboardCommands.setClipboard; getClipboard = clipboardCommands.getClipboard; /*-----------+ | CONDITION | +-----------+*/ listConditionInducers = conditionCommands.listConditionInducers; enableConditionInducer = conditionCommands.enableConditionInducer; disableConditionInducer = conditionCommands.disableConditionInducer; /*---------+ | CONTEXT | +---------+*/ getContexts = contextCommands.getContexts; getCurrentContext = contextCommands.getCurrentContext; getWindowHandle = contextCommands.getWindowHandle; getWindowHandles = contextCommands.getWindowHandles; setContext = contextCommands.setContext; setWindow = contextCommands.setWindow; activateRecentWebview = contextCommands.activateRecentWebview; connectToRemoteDebugger = contextCommands.connectToRemoteDebugger; getContextsAndViews = contextCommands.getContextsAndViews; listWebFrames = contextCommands.listWebFrames; mobileGetContexts = contextCommands.mobileGetContexts; onPageChange = contextCommands.onPageChange; getCurrentUrl = contextCommands.getCurrentUrl; getNewRemoteDebugger = contextCommands.getNewRemoteDebugger; getRecentWebviewContextId = contextCommands.getRecentWebviewContextId; isWebContext = contextCommands.isWebContext; isWebview = contextCommands.isWebview; setCurrentUrl = contextCommands.setCurrentUrl; stopRemote = contextCommands.stopRemote; /*------------+ | DEVICEINFO | +------------+*/ mobileGetDeviceInfo = deviceInfoCommands.mobileGetDeviceInfo; /*---------+ | ELEMENT | +---------+*/ elementDisplayed = elementCommands.elementDisplayed; elementEnabled = elementCommands.elementEnabled; elementSelected = elementCommands.elementSelected; getName = elementCommands.getName; getNativeAttribute = elementCommands.getNativeAttribute; getAttribute = elementCommands.getAttribute; getProperty = elementCommands.getProperty; getText = elementCommands.getText; getElementRect = elementCommands.getElementRect; getLocation = elementCommands.getLocation; getLocationInView = elementCommands.getLocationInView; getSize = elementCommands.getSize; /** @deprecated */ setValueImmediate = elementCommands.setValueImmediate; setValue = elementCommands.setValue; setValueWithWebAtom = elementCommands.setValueWithWebAtom; keys = elementCommands.keys; clear = elementCommands.clear; getContentSize = elementCommands.getContentSize; getNativeRect = elementCommands.getNativeRect; /*---------+ | EXECUTE | +---------+*/ execute = executeCommands.execute; executeAsync = executeCommands.executeAsync; // Note: executeMobile is handled internally via execute method mobileSimctl = simctlCommands.mobileSimctl; /*--------------+ | FILEMOVEMENT | +--------------+*/ pushFile = fileMovementCommands.pushFile; mobilePushFile = fileMovementCommands.mobilePushFile; pullFile = fileMovementCommands.pullFile; mobilePullFile = fileMovementCommands.mobilePullFile; mobileDeleteFolder = fileMovementCommands.mobileDeleteFolder; mobileDeleteFile = fileMovementCommands.mobileDeleteFile; pullFolder = fileMovementCommands.pullFolder; mobilePullFolder = fileMovementCommands.mobilePullFolder; /*--------+ | MEMORY | +--------+*/ mobileSendMemoryWarning = memoryCommands.mobileSendMemoryWarning; /*------+ | FIND | +------+*/ findElOrEls = findCommands.findElOrEls; findNativeElementOrElements = findCommands.findNativeElementOrElements; doNativeFind = findCommands.doNativeFind; getFirstVisibleChild = findCommands.getFirstVisibleChild; /*---------+ | GENERAL | +---------+*/ active = generalCommands.active; background = appManagementCommands.background; touchId = generalCommands.touchId; toggleEnrollTouchId = generalCommands.toggleEnrollTouchId; getWindowSize = generalCommands.getWindowSize; getDeviceTime = generalCommands.getDeviceTime; mobileGetDeviceTime = generalCommands.mobileGetDeviceTime; getWindowRect = generalCommands.getWindowRect; getStrings = appStringsCommands.getStrings; removeApp = generalCommands.removeApp; launchApp = generalCommands.launchApp; closeApp = generalCommands.closeApp; setUrl = generalCommands.setUrl; getViewportRect = generalCommands.getViewportRect; getScreenInfo = generalCommands.getScreenInfo; getStatusBarHeight = generalCommands.getStatusBarHeight; getDevicePixelRatio = generalCommands.getDevicePixelRatio; mobilePressButton = generalCommands.mobilePressButton; mobileSiriCommand = generalCommands.mobileSiriCommand; /*-------------+ | GEOLOCATION | +-------------+*/ mobileGetSimulatedLocation = geolocationCommands.mobileGetSimulatedLocation; mobileSetSimulatedLocation = geolocationCommands.mobileSetSimulatedLocation; mobileResetSimulatedLocation = geolocationCommands.mobileResetSimulatedLocation; /*---------+ | GESTURE | +---------+*/ mobileShake = gestureCommands.mobileShake; click = gestureCommands.click; releaseActions = gestureCommands.releaseActions; performActions = gestureCommands.performActions; nativeClick = gestureCommands.nativeClick; mobileScrollToElement = gestureCommands.mobileScrollToElement; mobileScroll = gestureCommands.mobileScroll; mobileSwipe = gestureCommands.mobileSwipe; mobilePinch = gestureCommands.mobilePinch; mobileDoubleTap = gestureCommands.mobileDoubleTap; mobileTwoFingerTap = gestureCommands.mobileTwoFingerTap; mobileTouchAndHold = gestureCommands.mobileTouchAndHold; mobileTap = gestureCommands.mobileTap; mobileDragFromToForDuration = gestureCommands.mobileDragFromToForDuration; mobileDragFromToWithVelocity = gestureCommands.mobileDragFromToWithVelocity; mobileTapWithNumberOfTaps = gestureCommands.mobileTapWithNumberOfTaps; mobileForcePress = gestureCommands.mobileForcePress; mobileSelectPickerWheelValue = gestureCommands.mobileSelectPickerWheelValue; mobileRotateElement = gestureCommands.mobileRotateElement; /*-------+ | IOHID | +-------+*/ mobilePerformIoHidEvent = iohidCommands.mobilePerformIoHidEvent; /*-----------+ | KEYCHAINS | +-----------+*/ mobileClearKeychains = keychainsCommands.mobileClearKeychains; /*----------+ | KEYBOARD | +----------+*/ hideKeyboard = keyboardCommands.hideKeyboard; mobileHideKeyboard = keyboardCommands.mobileHideKeyboard; isKeyboardShown = keyboardCommands.isKeyboardShown; mobileKeys = keyboardCommands.mobileKeys; /*--------------+ | LOCALIZATION | +--------------+*/ mobileConfigureLocalization = localizationCommands.mobileConfigureLocalization; /*----------+ | LOCATION | +----------+*/ getGeoLocation = locationCommands.getGeoLocation; setGeoLocation = locationCommands.setGeoLocation; mobileResetLocationService = locationCommands.mobileResetLocationService; /*------+ | LOCK | +------+*/ lock = lockCommands.lock; unlock = lockCommands.unlock; isLocked = lockCommands.isLocked; /*-----+ | LOG | +-----+*/ extractLogs = logCommands.extractLogs; supportedLogTypes = logCommands.supportedLogTypes; startLogCapture = logCommands.startLogCapture; mobileStartLogsBroadcast = logCommands.mobileStartLogsBroadcast; mobileStopLogsBroadcast = logCommands.mobileStopLogsBroadcast; /*------------+ | NAVIGATION | +------------+*/ back = navigationCommands.back; forward = navigationCommands.forward; closeWindow = navigationCommands.closeWindow; nativeBack = navigationCommands.nativeBack; mobileDeepLink = navigationCommands.mobileDeepLink; /*---------------+ | NOTIFICATIONS | +---------------+*/ mobilePushNotification = notificationsCommands.mobilePushNotification; mobileExpectNotification = notificationsCommands.mobileExpectNotification; /*------------+ | PASTEBOARD | +------------+*/ mobileSetPasteboard = pasteboardCommands.mobileSetPasteboard; mobileGetPasteboard = pasteboardCommands.mobileGetPasteboard; /*------------------+ | NETWORK MONITOR | +------------------+*/ mobileStartNetworkMonitor = networkMonitorCommands.mobileStartNetworkMonitor; mobileStopNetworkMonitor = networkMonitorCommands.mobileStopNetworkMonitor; /*-------------+ | PERFORMANCE | +-------------+*/ mobileStartPerfRecord = performanceCommands.mobileStartPerfRecord; mobileStopPerfRecord = performanceCommands.mobileStopPerfRecord; /*-------------+ | PERMISSIONS | +-------------+*/ mobileResetPermission = permissionsCommands.mobileResetPermission; mobileGetPermission = permissionsCommands.mobileGetPermission; mobileSetPermissions = permissionsCommands.mobileSetPermissions; /*-------------+ | PROXYHELPER | +-------------+*/ proxyCommand = proxyHelperCommands.proxyCommand; /*-------------+ | RECORDAUDIO | +-------------+*/ startAudioRecording = recordAudioCommands.startAudioRecording; stopAudioRecording = recordAudioCommands.stopAudioRecording; /*--------------+ | RECORDSCREEN | +--------------+*/ // Note: _recentScreenRecorder is a property, not a function, so it's handled internally in recordscreen.js startRecordingScreen = recordScreenCommands.startRecordingScreen; stopRecordingScreen = recordScreenCommands.stopRecordingScreen; mobileStartScreenRecording = recordScreenCommands.mobileStartScreenRecording; mobileStopScreenRecording = recordScreenCommands.mobileStopScreenRecording; /*-------------+ | SCREENSHOTS | +-------------+*/ getScreenshot = screenshotCommands.getScreenshot; getElementScreenshot = screenshotCommands.getElementScreenshot; getViewportScreenshot = screenshotCommands.getViewportScreenshot; /*--------+ | SOURCE | +--------+*/ getPageSource = sourceCommands.getPageSource; mobileGetSource = sourceCommands.mobileGetSource; /*----------+ | TIMEOUTS | +----------+*/ pageLoadTimeoutW3C = timeoutCommands.pageLoadTimeoutW3C; pageLoadTimeoutMJSONWP = timeoutCommands.pageLoadTimeoutMJSONWP; scriptTimeoutW3C = timeoutCommands.scriptTimeoutW3C; scriptTimeoutMJSONWP = timeoutCommands.scriptTimeoutMJSONWP; asyncScriptTimeout = timeoutCommands.asyncScriptTimeout; setPageLoadTimeout = timeoutCommands.setPageLoadTimeout; setAsyncScriptTimeout = timeoutCommands.setAsyncScriptTimeout; /*-----+ | WEB | +-----+*/ setFrame = webCommands.setFrame; getCssProperty = webCommands.getCssProperty; submit = webCommands.submit; refresh = webCommands.refresh; getUrl = webCommands.getUrl; title = webCommands.title; getCookies = webCommands.getCookies; setCookie = webCommands.setCookie; deleteCookie = webCommands.deleteCookie; deleteCookies = webCommands.deleteCookies; cacheWebElement = webCommands.cacheWebElement; cacheWebElements = webCommands.cacheWebElements; executeAtom = webCommands.executeAtom; executeAtomAsync = webCommands.executeAtomAsync; getAtomsElement = webCommands.getAtomsElement; convertElementsForAtoms = webCommands.convertElementsForAtoms; getElementId = webCommands.getElementId; hasElementId = webCommands.hasElementId; findWebElementOrElements = webCommands.findWebElementOrElements; clickWebCoords = webCommands.clickWebCoords; getSafariIsIphone = webCommands.getSafariIsIphone; getSafariDeviceSize = webCommands.getSafariDeviceSize; getSafariIsNotched = webCommands.getSafariIsNotched; getExtraTranslateWebCoordsOffset = webCommands.getExtraTranslateWebCoordsOffset; getExtraNativeWebTapOffset = webCommands.getExtraNativeWebTapOffset; nativeWebTap = webCommands.nativeWebTap; translateWebCoords = webCommands.translateWebCoords; checkForAlert = webCommands.checkForAlert; waitForAtom = webCommands.waitForAtom; mobileWebNav = webCommands.mobileWebNav; getWdaLocalhostRoot = webCommands.getWdaLocalhostRoot; mobileCalibrateWebToRealCoordinatesTranslation = webCommands.mobileCalibrateWebToRealCoordinatesTranslation; mobileUpdateSafariPreferences = webCommands.mobileUpdateSafariPreferences; /*--------+ | XCTEST | +--------+*/ mobileRunXCTest = xctestCommands.mobileRunXCTest; mobileInstallXCTestBundle = xctestCommands.mobileInstallXCTestBundle; mobileListXCTestBundles = xctestCommands.mobileListXCTestBundles; /*----------------------+ | XCTEST SCREEN RECORD | +---------------------+*/ mobileStartXctestScreenRecording = xctestRecordScreenCommands.mobileStartXctestScreenRecording; mobileGetXctestScreenRecordingInfo = xctestRecordScreenCommands.mobileGetXctestScreenRecordingInfo; mobileStopXctestScreenRecording = xctestRecordScreenCommands.mobileStopXctestScreenRecording; constructor(opts, shouldValidateCaps = true) { super(opts, shouldValidateCaps); this.deviceConnectionsFactory = new device_connections_factory_1.DeviceConnectionsFactory(this.log); 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: undefined, alertMonitorAbortController: undefined, }; this.resetIos(); this.settings = new driver_1.DeviceSettings(DEFAULT_SETTINGS, this.onSettingsUpdate.bind(this)); this.logs = {}; this._networkMonitorSession = 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; this._wda = null; } // Getter methods get wda() { if (!this._wda) { throw new Error('WebDriverAgent is not initialized'); } return this._wda; } get remote() { if (!this._remote) { throw new Error('Remote debugger is not initialized'); } return this._remote; } get driverData() { // TODO fill out resource info here return {}; } get device() { return this._device; } // Override methods from BaseDriver async createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData) { try { const [sessionId, initialCaps] = await super.createSession(w3cCaps1, w3cCaps2, w3cCaps3, driverData); let caps = initialCaps; // 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 = { ...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 }); } const 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 [sessionId, caps]; } catch (e) { this.log.error(JSON.stringify(e)); await this.deleteSession(); throw e; } } async deleteSession(sessionId) { await utils_1.removeAllSessionWebSocketHandlers.bind(this)(); for (const recorder of lodash_1.default.compact([this._recentScreenRecorder, this._audioRecorder])) { await recorder.interrupt(true); await recorder.cleanup(); } await this._networkMonitorSession?.interrupt(); this._networkMonitorSession = null; if (!lodash_1.default.isEmpty(this._perfRecorders)) { await Promise.all(this._perfRecorders.map((x) => x.stop(true))); this._perfRecorders = []; } if (this._conditionInducer) { try { await this.disableConditionInducer(); } catch (err) { this.log.warn(`Cannot disable condition inducer: ${err.message}`); } } await this.stop(); if (this._wda && this.isXcodebuildNeeded()) { if (this.opts.clearSystemFiles) { let synchronizationKey = XCUITestDriver.name; const derivedDataPath = await this.wda.retrieveDerivedDataPath(); if (derivedDataPath) { synchronizationKey = node_path_1.default.normalize(derivedDataPath); } await SHARED_RESOURCES_GUARD.acquire(synchronizationKey, async () => { await (0, utils_1.clearSystemFiles)(this.wda); }); } else { this.log.debug('Not clearing log files. Use `clearSystemFiles` capability to turn on.'); } } if (this._remote) { this.log.debug('Found a remote debugger session. Removing...'); await this.stopRemote(); } if (this.opts.resetOnSessionStartOnly === false) { await this.runReset(true); } const simulatorDevice = this.isSimulator() ? this.device : null; if (simulatorDevice && this.lifecycleData.createSim) { this.log.debug(`Deleting simulator created for this run (udid: '${simulatorDevice.udid}')`); await simulator_management_1.shutdownSimulator.bind(this)(); await simulatorDevice.delete(); } const shouldResetLocationService = this.isRealDevice() && !!this.opts.resetLocationService; if (shouldResetLocationService) { try { await this.mobileResetLocationService(); } catch { /* Ignore this error since mobileResetLocationService already logged the error */ } } await this.logs.syslog?.stopCapture(); lodash_1.default.values(this.logs).forEach((x) => x?.removeAllListeners?.()); if (this._bidiServerLogListener) { this.log.unwrap().off('log', this._bidiServerLogListener); } this.logs = {}; if (this.mjpegStream) { this.log.info('Closing MJPEG stream'); this.mjpegStream.stop(); } this.resetIos(); await super.deleteSession(sessionId); } async executeCommand(cmd, ...args) { this.log.debug(`Executing command '${cmd}'`); // TODO: once this fix gets into base driver remove from here if (cmd === 'getStatus') { return await this.getStatus(); } return await super.executeCommand(cmd, ...args); } proxyActive() { return Boolean(this.jwpProxyActive); } getProxyAvoidList() { if (this.isWebview()) { return NO_PROXY_WEB_LIST; } return NO_PROXY_NATIVE_LIST; } canProxy() { return true; } validateLocatorStrategy(strategy) { super.validateLocatorStrategy(strategy, this.isWebContext()); } validateDesiredCaps(caps) { if (!super.validateDesiredCaps(caps)) { return false; } // make sure that the capabilities have one of `app` or `bundleId` if (lodash_1.default.toLower(caps.browserName) !== 'safari' && !caps.app && !caps.bundleId) { this.log.info('The desired capabilities include neither an app nor a bundleId. ' + 'WebDriverAgent will be started without the default app'); } if (!support_1.util.coerceVersion(String(caps.platformVersion), false)) { this.log.warn(`'platformVersion' capability ('${caps.platformVersion}') is not a valid version number. ` + `Consider fixing it or be ready to experience an inconsistent driver behavior.`); } const verifyProcessArgument = (processArguments) => { const { args, env } = processArguments; if (!lodash_1.default.isNil(args) && !lodash_1.default.isArray(args)) { throw this.log.errorWithException('processArguments.args must be an array of strings'); } if (!lodash_1.default.isNil(env) && !lodash_1.default.isPlainObject(env)) { throw this.log.errorWithException('processArguments.env must be an object <key,value> pair {a:b, c:d}'); } }; // `processArguments` should be JSON string or an object with arguments and/ environment details if (caps.processArguments) { if (lodash_1.default.isString(caps.processArguments)) { try { // try to parse the string as JSON caps.processArguments = JSON.parse(caps.processArguments); verifyProcessArgument(caps.processArguments); } catch (err) { throw this.log.errorWithException(`processArguments must be a JSON format or an object with format {args : [], env : {a:b, c:d}}. ` + `Both environment and argument can be null. Error: ${err}`); } } else if (lodash_1.default.isPlainObject(caps.processArguments)) { verifyProcessArgument(caps.processArguments); } else { throw this.log.errorWithException(`'processArguments must be an object, or a string JSON object with format {args : [], env : {a:b, c:d}}. ` + `Both environment and argument can be null.`); } } // there is no point in having `keychainPath` without `keychainPassword` if ((caps.keychainPath && !caps.keychainPassword) || (!caps.keychainPath && caps.keychainPassword)) { throw this.log.errorWithException(`If 'keychainPath' is set, 'keychainPassword' must also be set (and vice versa).`); } // `resetOnSessionStartOnly` should be set to true by default this.opts.resetOnSessionStartOnly = !support_1.util.hasValue(this.opts.resetOnSessionStartOnly) || this.opts.resetOnSessionStartOnly; this.opts.useNewWDA = support_1.util.hasValue(this.opts.useNewWDA) ? this.opts.useNewWDA : false; if (caps.commandTimeouts) { caps.commandTimeouts = (0, utils_1.normalizeCommandTimeouts)(caps.commandTimeouts); } if (lodash_1.default.isString(caps.webDriverAgentUrl)) { try { new URL(caps.webDriverAgentUrl); } catch { throw this.log.errorWithException(`'webDriverAgentUrl' capability is expected to contain a valid WebDriverAgent server URL. ` + `'${caps.webDriverAgentUrl}' is given instead`); } } if (caps.browserName) { if (caps.bundleId) { throw this.log.errorWithException(`'browserName' cannot be set together with 'bundleId' capability`); } // warn if the capabilities have both `app` and `browser, although this // is common with selenium grid if (caps.app) { this.log.warn(`The capabilities should generally not include both an 'app' and a 'browserName'`); } } if (caps.permissions) { try { for (const [bundleId, perms] of lodash_1.default.toPairs(JSON.parse(caps.permissions))) { if (!lodash_1.default.isString(bundleId)) { throw new Error(`'${JSON.stringify(bundleId)}' must be a string`); } if (!lodash_1.default.isPlainObject(perms)) { throw new Error(`'${JSON.stringify(perms)}' must be a JSON object`); } } } catch (e) { throw this.log.errorWithException(`'${caps.permissions}' is expected to be a valid object with format ` + `{"<bundleId1>": {"<serviceName1>": "<serviceStatus1>", ...}, ...}. Original error: ${e.message}`); } } if (caps.platformVersion && !support_1.util.coerceVersion(caps.platformVersion, false)) { throw this.log.errorWithException(`'platformVersion' must be a valid version number. ` + `'${caps.platformVersion}' is given instead.`); } // additionalWebviewBundleIds is an array, JSON array, or string if (caps.additionalWebviewBundleIds) { caps.additionalWebviewBundleIds = this.helpers.parseCapsArray(caps.additionalWebviewBundleIds); } // ignoredWebviewBundleIds is an array, JSON array, or string if (caps.ignoredWebviewBundleIds) { caps.ignoredWebviewBundleIds = this.helpers.parseCapsArray(caps.ignoredWebviewBundleIds); } // finally, return true since the superclass check passed, as did this return true; } // Utility methods isSafari() { return !!this.safari; } isRealDevice() { return 'devicectl' in (this.device ?? {}); } isSimulator() { return 'simctl' in (this.device ?? {}); } isXcodebuildNeeded() { return !CAP_NAMES_NO_XCODEBUILD_REQUIRED.some((x) => Boolean(this.opts[x])); } // Core driver methods 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; } 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; } 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(); } } async allocateMjpegServerPort() { const mjpegServerPort = Number(this.opts.mjpegServerPort || DEFAULT_MJPEG_SERVER_PORT); this.log.debug(`Forwarding MJPEG server port ${mjpegServerPort} to local port ${mjpegServerPort}`); try { await this.deviceConnectionsFactory.requestConnection(this.opts.udid, mjpegServerPort, { devicePort: mjpegServerPort, platformVersion: this.opts.platformVersion, 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}`, { cause: error }); } } } 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}'`); 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 = 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({ ...this.opts, device: this.device, realDevice: this.isRealDevice(), iosSdkVersion: this._iosSdkVersion ?? undefined, reqBasePath: this.basePath, }, 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 void (async () => { try { await 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();