UNPKG

detox

Version:

E2E tests and automation for mobile

246 lines (196 loc) 8.22 kB
// @ts-nocheck const path = require('path'); const exec = require('child-process-promise').exec; const _ = require('lodash'); const temporaryPath = require('../../../../artifacts/utils/temporaryPath'); const DetoxRuntimeError = require('../../../../errors/DetoxRuntimeError'); const XCUITestRunner = require('../../../../ios/XCUITestRunner'); const { assertTraceDescription } = require('../../../../utils/assertArgument'); const getAbsoluteBinaryPath = require('../../../../utils/getAbsoluteBinaryPath'); const { actionDescription } = require('../../../../utils/invocationTraceDescriptions'); const log = require('../../../../utils/logger').child({ cat: 'device' }); const pressAnyKey = require('../../../../utils/pressAnyKey'); const traceInvocationCall = require('../../../../utils/traceInvocationCall').bind(null, log); const IosDriver = require('./IosDriver'); /** * @typedef SimulatorDriverDeps { DeviceDriverDeps } * @property applesimutils { AppleSimUtils } */ /** * @typedef SimulatorDriverProps * @property udid { String } The unique cross-OS identifier of the simulator * @property type { String } * @property bootArgs { Object } */ class SimulatorDriver extends IosDriver { /** * @param deps { SimulatorDriverDeps } * @param props { SimulatorDriverProps } */ constructor(deps, { udid, type, bootArgs, headless }) { super(deps); this.udid = udid; this._type = type; this._bootArgs = bootArgs; this._headless = headless; this._deviceName = `${udid} (${this._type})`; this._applesimutils = deps.applesimutils; } withAction(xcuitestRunner, action, traceDescription, ...params) { assertTraceDescription(traceDescription); const invocation = { ...(params.length !== 0 && { params }), type: 'systemAction', ...(this.index !== undefined && { systemAtIndex: this.index }), systemAction: action }; return traceInvocationCall(traceDescription, invocation, xcuitestRunner.execute(invocation)); } getExternalId() { return this.udid; } getDeviceName() { return this._deviceName; } async getBundleIdFromBinary(appPath) { appPath = getAbsoluteBinaryPath(appPath); try { const result = await exec(`/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "${path.join(appPath, 'Info.plist')}"`); const bundleId = _.trim(result.stdout); if (_.isEmpty(bundleId)) { throw new Error(); } return bundleId; } catch (ex) { throw new DetoxRuntimeError(`field CFBundleIdentifier not found inside Info.plist of app binary at ${appPath}`); } } async installApp(binaryPath) { await this._applesimutils.install(this.udid, getAbsoluteBinaryPath(binaryPath)); } async uninstallApp(bundleId) { const { udid } = this; await this.emitter.emit('beforeUninstallApp', { deviceId: udid, bundleId }); await this._applesimutils.uninstall(udid, bundleId); } async launchApp(bundleId, launchArgs, languageAndLocale) { const { udid } = this; await this.emitter.emit('beforeLaunchApp', { bundleId, deviceId: udid, launchArgs }); const pid = await this._applesimutils.launch(udid, bundleId, launchArgs, languageAndLocale); await this.emitter.emit('launchApp', { bundleId, deviceId: udid, launchArgs, pid }); return pid; } async waitForAppLaunch(bundleId, launchArgs, languageAndLocale) { const { udid } = this; await this.emitter.emit('beforeLaunchApp', { bundleId, deviceId: udid, launchArgs }); this._applesimutils.printLaunchHint(udid, bundleId, launchArgs, languageAndLocale); await pressAnyKey(); const pid = await this._applesimutils.getPid(udid, bundleId); if (Number.isNaN(pid)) { throw new DetoxRuntimeError({ message: `Failed to find a process corresponding to the app bundle identifier (${bundleId}).`, hint: `Make sure that the app is running on the device (${udid}), visually or via CLI:\n` + `xcrun simctl spawn ${this.udid} launchctl list | grep -F '${bundleId}'\n`, }); } else { log.info({}, `Found the app (${bundleId}) with process ID = ${pid}. Proceeding...`); } await this.emitter.emit('launchApp', { bundleId, deviceId: udid, launchArgs, pid }); return pid; } async terminate(bundleId) { const { udid } = this; await this.emitter.emit('beforeTerminateApp', { deviceId: udid, bundleId }); await this._applesimutils.terminate(udid, bundleId); await this.emitter.emit('terminateApp', { deviceId: udid, bundleId }); } async tap(point, shouldIgnoreStatusBar, _bundleId) { const xcuitestRunner = new XCUITestRunner({ runtimeDevice: { id: this.getExternalId(), _bundleId } }); let x = point?.x ?? 100; let y = point?.y ?? 100; const traceDescription = actionDescription.tap({ x, y }); return this.withAction(xcuitestRunner, 'coordinateTap', traceDescription, x.toString(), y.toString()); } async longPress(point, pressDuration, shouldIgnoreStatusBar, _bundleId) { const xcuitestRunner = new XCUITestRunner({ runtimeDevice: { id: this.getExternalId(), _bundleId } }); let x = point?.x ?? 100; let y = point?.y ?? 100; let _pressDuration = pressDuration ? (pressDuration / 1000) : 1; const traceDescription = actionDescription.longPress({ x, y }, _pressDuration); return this.withAction(xcuitestRunner, 'coordinateLongPress', traceDescription, x.toString(), y.toString(), _pressDuration.toString()); } async setBiometricEnrollment(yesOrNo) { await this._applesimutils.setBiometricEnrollment(this.udid, yesOrNo); } async matchFace() { await this._applesimutils.matchBiometric(this.udid, 'Face'); } async unmatchFace() { await this._applesimutils.unmatchBiometric(this.udid, 'Face'); } async matchFinger() { await this._applesimutils.matchBiometric(this.udid, 'Finger'); } async unmatchFinger() { await this._applesimutils.unmatchBiometric(this.udid, 'Finger'); } async sendToHome() { await this._applesimutils.sendToHome(this.udid); } async setLocation(lat, lon) { await this._applesimutils.setLocation(this.udid, lat, lon); } async setPermissions(bundleId, permissions) { await this._applesimutils.setPermissions(this.udid, bundleId, permissions); } async clearKeychain() { await this._applesimutils.clearKeychain(this.udid); } async resetContentAndSettings() { await this.emitter.emit('beforeShutdownDevice', { deviceId: this.udid }); await this._applesimutils.shutdown(this.udid); await this.emitter.emit('shutdownDevice', { deviceId: this.udid }); await this._applesimutils.resetContentAndSettings(this.udid); await this._applesimutils.boot(this.udid, this._bootArgs, this._headless); await this.emitter.emit('bootDevice', { deviceId: this.udid }); } getLogsPaths() { return this._applesimutils.getLogsPaths(this.udid); } async waitForActive() { return await this.client.waitForActive(); } async waitForBackground() { return await this.client.waitForBackground(); } async takeScreenshot(screenshotName) { const tempPath = await temporaryPath.for.png(); await this._applesimutils.takeScreenshot(this.udid, tempPath); await this.emitter.emit('createExternalArtifact', { pluginId: 'screenshot', artifactName: screenshotName || path.basename(tempPath, '.png'), artifactPath: tempPath, }); return tempPath; } async captureViewHierarchy(artifactName) { const viewHierarchyURL = temporaryPath.for.viewhierarchy(); await this.client.captureViewHierarchy({ viewHierarchyURL }); await this.emitter.emit('createExternalArtifact', { pluginId: 'uiHierarchy', artifactName: artifactName, artifactPath: viewHierarchyURL, }); return viewHierarchyURL; } async generateViewHierarchyXml(shouldInjectTestIds) { return await this.client.generateViewHierarchyXml({ shouldInjectTestIds }); } async setStatusBar(flags) { await this._applesimutils.statusBarOverride(this.udid, flags); } async resetStatusBar() { await this._applesimutils.statusBarReset(this.udid); } } module.exports = SimulatorDriver;