Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support usePreinstalledWDA on simulators #2345

Merged
merged 7 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/reference/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ about capabilities, refer to the [Appium documentation](https://appium.io/docs/e
|`appium:xcodeOrgId`|Apple developer team identifier string. Must be used in conjunction with `xcodeSigningId` to take effect.|`JWL241K123`|
|`appium:xcodeSigningId`|String representing a signing certificate. Must be used in conjunction with `xcodeOrgId`. This is usually just `Apple Development` or `iPhone Developer`, so the default (if not included) is `iPhone Developer`|`Apple Developer`|
|`appium:xcodeConfigFile`|Full path to an optional Xcode configuration file that specifies the code signing identity and team for running the `WebDriverAgent` on the real device.|`/path/to/myconfig.xcconfig`|
|`appium:updatedWDABundleId`|Bundle id to update WDA to before building and launching on real devices. This bundle id _must_ be associated with a valid provisioning profile.|`io.appium.WebDriverAgentRunner`|
|`appium:updatedWDABundleId`|Bundle id to update WDA to before building and launching it. This bundle id _must_ be associated with a valid provisioning profile. The default value is `com.facebook.WebDriverAgentRunner`. |`io.appium.WebDriverAgentRunner`|
|`appium:keychainPath`|Full path to the private development key exported from the system keychain. Used in conjunction with `keychainPassword` when testing on real devices.|`/path/to/MyPrivateKey.p12`|
|`appium:keychainPassword`|Password for unlocking keychain specified in `keychainPath`.|`super awesome password`|
|`appium:derivedDataPath`| Use along with *usePrebuiltWDA* capability and choose where to search for the existing WDA app. If the capability is not set then Xcode will store the derived data in the default root taken from preferences.|
Expand All @@ -61,8 +61,8 @@ about capabilities, refer to the [Appium documentation](https://appium.io/docs/e
|`appium:showXcodeLog`|Whether to display the output of the Xcode command used to run the tests. If this is `true`, there will be **lots** of extra logging at startup. Defaults to `false`|`true`|
|`appium:iosInstallPause`|Time in milliseconds to pause between installing the application and starting `WebDriverAgent` on the device. Used particularly for larger applications. Defaults to `0`|`8000`|
|`appium:usePrebuiltWDA`|Skips the build phase of running the WDA app. Building is then the responsibility of the user. Only works for Xcode 8+. Defaults to `false`.|`true`|
|`appium:prebuiltWDAPath`| The path to a WebDriverAgentRunner application package to be installed with `appium:usePreinstalledWDA` capability for real devices. The package's bundle id will be used over `appium:updatedWDABundleId`. |`/path/to/WebDriverAgentRunner-Runner.app`|
|`appium:usePreinstalledWDA`| Whether to launch a preinstalled WebDriverAgentRunner application using a custom XCTest API client (via `com.apple.instruments` service) instead of running `xcodebuild` for real devices. If `appium:prebuiltWDAPath` is provided, XCUITest driver will install it before launching the application. The preinstalled WebDriverAgent package must have been built by Xcode 12+. The default target bundle identifier is `com.facebook.WebDriverAgentRunner.xctrunner`. If `appium:updatedWDABundleId` is provided, `<appium:updatedWDABundleId>.xctrunner` will be launched. Please read [Run Preinstalled WebDriverAgentRunner](../guides/run-preinstalled-wda.md) for more details. Defaults to `false`. |`true` or `false`|
|`appium:prebuiltWDAPath`| The full path to the prebuilt WebDriverAgent-Runner application package to be installed if `appium:usePreinstalledWDA` capability is enabled. The package's bundle identifier could be customized via `appium:updatedWDABundleId` capability. |`/path/to/WebDriverAgentRunner-Runner.app`|
|`appium:usePreinstalledWDA`| Whether to launch a preinstalled WebDriverAgentRunner application using a custom XCTest API client (via `com.apple.instruments` service) instead of running `xcodebuild` for real devices or simulators via simctl tool (since driver version 7.4.0). If `appium:prebuiltWDAPath` is provided, XCUITest driver will install WebDriverAgent-Runner app from the given path before launching the application. The preinstalled WebDriverAgent package must be built by Xcode 12+. The default target bundle identifier is `com.facebook.WebDriverAgentRunner.xctrunner`, although it could be customized by providing the `appium:updatedWDABundleId` capability value (the `.xctrunner` suffix is added automatically). Please read [Run Preinstalled WebDriverAgentRunner](../guides/run-preinstalled-wda.md) for more details. Defaults to `false`. |`true` or `false`|
|`appium:shouldUseSingletonTestManager`|Use default proxy for test management within `WebDriverAgent`. Setting this to `false` sometimes helps with socket hangup problems. Defaults to `true`.|`false`|
|`appium:waitForIdleTimeout`|The amount of time in float seconds to wait until the application under test is idling. XCTest requires the app's main thread to be idling in order to execute any action on it, so WDA might not even start/freeze if the app under test is constantly hogging the main thread. The default value is `10` (seconds). Setting it to zero disables idling checks completely (not recommended) and has the same effect as setting `waitForQuiescence` to `false`. Available since Appium 1.20.0. |`1`|
|`appium:useXctestrunFile`|Use Xctestrun file to launch WDA. It will search for such file in `bootstrapPath`. Expected name of file is `WebDriverAgentRunner_iphoneos<sdkVersion>-arm64.xctestrun` for real device and `WebDriverAgentRunner_iphonesimulator<sdkVersion>-x86_64.xctestrun` for simulator. One can do `build-for-testing` for `WebDriverAgent` project for simulator and real device and then you will see [Product Folder like this](./assets/images/useXctestrunFile.png) and you need to copy content of this folder at `bootstrapPath` location. Since this capability expects that you have already built `WDA` project, it neither checks whether you have necessary dependencies to build `WDA` nor will it try to build project. Defaults to `false`. _Tips: `Xcodebuild` builds for the target platform version. We'd recommend you to build with minimal OS version which you'd like to run as the original WDA module. e.g. If you build WDA for 12.2, the module cannot run on iOS 11.4 because of loading some module error on simulator. A module built with 11.4 can work on iOS 12.2. (This is xcodebuild's expected behaviour.)_ |`true`|
Expand Down
114 changes: 55 additions & 59 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {WebDriverAgent} from 'appium-webdriveragent';
import {BaseDriver, DeviceSettings, errors} from 'appium/driver';
import {fs, mjpeg, util, timing} from 'appium/support';
import AsyncLock from 'async-lock';
import {retry, retryInterval} from 'asyncbox';
import {retryInterval} from 'asyncbox';
import B from 'bluebird';
import _ from 'lodash';
import {LRUCache} from 'lru-cache';
Expand Down Expand Up @@ -65,7 +65,7 @@ import {
} from './utils';

const SHUTDOWN_OTHER_FEAT_NAME = 'shutdown_other_sims';
const CUSTOMIZE_RESULT_BUNDPE_PATH = 'customize_result_bundle_path';
const CUSTOMIZE_RESULT_BUNDLE_PATH = 'customize_result_bundle_path';

const SUPPORTED_EXTENSIONS = [IPA_EXT, APP_EXT];
const MAX_ARCHIVE_SCAN_DEPTH = 1;
Expand Down Expand Up @@ -631,7 +631,7 @@ class XCUITestDriver extends BaseDriver {
}

// @ts-expect-error - do not assign arbitrary properties to `this.opts`
await this.startWda(this.opts.sessionId, realDevice);
await this.startWda(this.opts.sessionId);

if (this.opts.orientation) {
await this.setInitialOrientation(this.opts.orientation);
Expand Down Expand Up @@ -708,9 +708,8 @@ class XCUITestDriver extends BaseDriver {
/**
* Start WebDriverAgentRunner
* @param {string} sessionId - The id of the target session to launch WDA with.
* @param {boolean} realDevice - Equals to true if the test target device is a real device.
*/
async startWda(sessionId, realDevice) {
async startWda(sessionId) {
// Don't cleanup the processes if webDriverAgentUrl is set
if (!util.hasValue(this.wda.webDriverAgentUrl)) {
await this.wda.cleanupObsoleteProcesses();
Expand Down Expand Up @@ -744,21 +743,11 @@ class XCUITestDriver extends BaseDriver {
);
}

if (this.opts.usePreinstalledWDA) {
if (!this.isRealDevice()) {
throw new Error(
`'usePreinstalledWDA' capability is only supported for real devices. ` +
`'useXctestrunFile' or 'usePrebuiltWDA' may help to get similar errort on Simulators.`,
);
}
// below will be only for real devices if the caps has "this.opts.usePreinstalledWDA"

if (this.opts.prebuiltWDAPath && !(await fs.exists(this.opts.prebuiltWDAPath))) {
throw new Error(
`'${this.opts.prebuiltWDAPath}' provided as 'prebuiltWDAPath' capability did not exist. ` +
`Please make sure if the path exits at the given path.`,
);
}
if (this.opts.usePreinstalledWDA && this.opts.prebuiltWDAPath && !(await fs.exists(this.opts.prebuiltWDAPath))) {
throw new Error(
`The prebuilt WebDriverAgent app at '${this.opts.prebuiltWDAPath}' provided as 'prebuiltWDAPath' ` +
`capability value does not exist or is not accessible`
);
}

return await SHARED_RESOURCES_GUARD.acquire(synchronizationKey, async () => {
Expand Down Expand Up @@ -789,7 +778,7 @@ class XCUITestDriver extends BaseDriver {

// Used in the following WDA build
if (this.opts.resultBundlePath) {
this.assertFeatureEnabled(CUSTOMIZE_RESULT_BUNDPE_PATH);
this.assertFeatureEnabled(CUSTOMIZE_RESULT_BUNDLE_PATH);
}

const startupRetries =
Expand Down Expand Up @@ -818,47 +807,17 @@ class XCUITestDriver extends BaseDriver {
}
try {
if (this.opts.usePreinstalledWDA) {
// Stop the existing process before starting a new one to start a fresh WDA process every session.
await this.mobileKillApp(this.wda.bundleIdForXctest);

if (this.opts.prebuiltWDAPath) {
const candidateBundleId = await extractBundleId(this.opts.prebuiltWDAPath);
this.wda.updatedWDABundleId = candidateBundleId.replace('.xctrunner', '');
this.log.info(`Installing prebuilt WDA ${this.opts.prebuiltWDAPath}`);
// Note: The CFBundleVersion in the test bundle was always 1.
// It may not be able to compare with the installed versio.
await installToRealDevice(
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
this.opts.device,
this.opts.prebuiltWDAPath,
candidateBundleId,
{
skipUninstall: true,
timeout: this.opts.appPushTimeout,
strategy: this.opts.appInstallStrategy,
},
);
}
await this.preparePreinstalledWda();
}

// Over Xcode 10 will often try to access the app from its staging
// directory before fully moving it there, and fail. Retrying once
// immediately helps
this.cachedWdaStatus = await retry(
2,
this.wda.launch.bind(this.wda),
sessionId,
realDevice,
);
this.cachedWdaStatus = await this.wda.launch(sessionId);
} catch (err) {
this.logEvent('wdaStartFailed');
this.log.debug(err.stack);
retryCount++;
let errorMsg = `Unable to launch WebDriverAgent because of xcodebuild failure: ${err.message}`;
let errorMsg = `Unable to launch WebDriverAgent. Original error: ${err.message}`;
if (this.isRealDevice()) {
errorMsg +=
`. Make sure you follow the tutorial at ${WDA_REAL_DEV_TUTORIAL_URL}. ` +
`Try to remove the WebDriverAgentRunner application from the device if it is installed ` +
`and reboot the device.`;
errorMsg += `. Make sure you follow the tutorial at ${WDA_REAL_DEV_TUTORIAL_URL}`;
}
if (this.opts.usePreinstalledWDA) {
// In case the bundle id process start got failed because of
Expand All @@ -867,8 +826,8 @@ class XCUITestDriver extends BaseDriver {
// Mostly it failed to start the WDA process as no the bundle id
// e.g. '<bundle id of WDA> not found on device <udid>'
throw new Error(
`Unable to launch WebDriverAgent because of failure: ${err.message}. ` +
`Please make sure if the ${this.wda.bundleIdForXctest} exists and it is launchable. ` +
`Unable to launch WebDriverAgent. Original error: ${err.message}. ` +
`Make sure the application ${this.wda.bundleIdForXctest} exists and it is launchable. ` +
`${WDA_REAL_DEV_TUTORIAL_URL} may help to complete the preparation.`,
);
} else {
Expand All @@ -893,7 +852,7 @@ class XCUITestDriver extends BaseDriver {
shortCircuitError = err;
return;
}
let errorMsg = `Unable to start WebDriverAgent session because of an unexpected failure: ${err.message}`;
let errorMsg = `Unable to start WebDriverAgent session. Original error: ${err.message}`;
if (this.isRealDevice() && _.includes(err.message, 'xcodebuild')) {
errorMsg += ` Make sure you follow the tutorial at ${WDA_REAL_DEV_TUTORIAL_URL}.`;
}
Expand Down Expand Up @@ -1802,6 +1761,43 @@ class XCUITestDriver extends BaseDriver {
);
}

async preparePreinstalledWda() {
if (this.isRealDevice()) {
// Stop the existing process before starting a new one to start a fresh WDA process every session.
await this.mobileKillApp(this.wda.bundleIdForXctest);
}

if (!this.opts.prebuiltWDAPath) {
return;
}

const candidateBundleId = await extractBundleId(this.opts.prebuiltWDAPath);
this.wda.updatedWDABundleId = candidateBundleId.replace('.xctrunner', '');
this.log.info(
`Installing prebuilt WDA at '${this.opts.prebuiltWDAPath}'. ` +
`Bundle identifier: ${candidateBundleId}.`
);
// @ts-expect-error - do not assign arbitrary properties to `this.opts`
const device = this.opts.device;

// Note: The CFBundleVersion in the test bundle was always 1.
// It may not be able to compare with the installed versio.
if (this.isRealDevice()) {
await installToRealDevice(
device,
this.opts.prebuiltWDAPath,
candidateBundleId,
{
skipUninstall: true,
timeout: this.opts.appPushTimeout,
strategy: this.opts.appInstallStrategy,
},
);
} else {
await installToSimulator(device, this.opts.prebuiltWDAPath, candidateBundleId);
}
}

/*---------------+
| ACTIVEAPPINFO |
+---------------+*/
Expand Down
14 changes: 4 additions & 10 deletions lib/simulator-management.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Simctl from 'node-simctl';
import {resetTestProcesses} from 'appium-webdriveragent';
import _ from 'lodash';
import log from './logger';
import {util} from 'appium/support';
import {util, timing} from 'appium/support';
import {UDID_AUTO, normalizePlatformName} from './utils';

const APPIUM_SIM_PREFIX = 'appiumTest';
Expand Down Expand Up @@ -233,15 +233,9 @@ async function installToSimulator(
}

log.debug(`Installing '${app}' on Simulator with UUID '${device.udid}'...`);
try {
await device.installApp(app);
} catch (e) {
// it sometimes fails on Xcode 10 because of a race condition
log.info(`Got an error on '${app}' install: ${e.message}`);
log.info('Retrying application install');
await device.installApp(app);
}
log.debug('The app has been installed successfully.');
const timer = new timing.Timer().start();
await device.installApp(app);
log.info(`The app has been successfully installed in ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
}

async function shutdownOtherSimulators(currentDevice) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"appium-ios-device": "^2.5.4",
"appium-ios-simulator": "^5.5.1",
"appium-remote-debugger": "^11.0.0",
"appium-webdriveragent": "^7.1.1",
"appium-webdriveragent": "^7.2.0",
"appium-xcode": "^5.1.4",
"async-lock": "^1.4.0",
"asyncbox": "^3.0.0",
Expand Down
Loading