Skip to content

Commit

Permalink
fix xctestrun file detection when useXctestrunFile is true (#903)
Browse files Browse the repository at this point in the history
* fix xctestrun path

* tweak handking sdk version

* tuen a bit

* update caps in readme

* add a test to raise an error

* move control flow into string

* fix reviews

* add a tips for building module
  • Loading branch information
KazuCocoa committed Mar 19, 2019
1 parent d32e6a6 commit b87df4e
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 32 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -148,7 +148,7 @@ Differences noted here
|`calendarAccessAuthorized`|Set this to `true` if you want to enable calendar access on IOS Simulator with given bundleId. Set to `false`, if you want to disable calendar access on IOS Simulator with given bundleId. If not set, the calendar authorization status will not be set.|e.g., `true`|
|`isHeadless`|Set this capability to `true` if automated tests are running on Simulator and the device display is not needed to be visible. This only has an effect since Xcode9 and only for simulators. All running instances of Simulator UI are going to be automatically terminated if headless test is started. `false` is the default value.|e.g., `true`|
|`webkitDebugProxyPort`|Local port number used for communication with ios-webkit-debug-proxy. Only relevant for real devices. The default value equals to `27753`.|e.g. `20000`|
|`useXctestrunFile`|Use Xctestrun file to launch WDA. It will search for such file in `bootstrapPath`. Expected name of file is `WebDriverAgentRunner_iphoneos<platformVersion>-arm64.xctestrun` for real device and `WebDriverAgentRunner_iphonesimulator<platformVersion>-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](docs/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 check whether you have necessary dependencies to build `WDA` nor it try to build project. Defaults to `false`|e.g., `true`|
|`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](docs/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.)_ |e.g., `true`|
|`absoluteWebLocations`|This capability will direct the `Get Element Location` command, when used within webviews, to return coordinates which are relative to the origin of the page, rather than relative to the current scroll offset. This capability has no effect outside of webviews. Default `false`.|e.g., `true`|
|`simulatorWindowCenter`|Allows to explicitly set the coordinates of Simulator window center for Xcode9+ SDK. This capability only has an effect if Simulator window has not been opened yet for the current session before it started.|e.g. `{-100.0,100.0}` or `{500,500}`, spaces are not allowed|
|`useJSONSource`|Get JSON source from WDA and parse into XML on Appium server. This can be much faster, especially on large devices. Defaults to `false`.|e.g., `true`|
Expand Down
4 changes: 3 additions & 1 deletion lib/driver.js
Expand Up @@ -266,6 +266,7 @@ class XCUITestDriver extends BaseDriver {
this.opts.device = device;
this.opts.udid = udid;
this.opts.realDevice = realDevice;
this.opts.iosSdkVersion = null; // For WDA and xcodebuild

if (_.isEmpty(this.xcodeVersion) && (!this.opts.webDriverAgentUrl || !this.opts.realDevice)) {
// no `webDriverAgentUrl`, or on a simulator, so we need an Xcode version
Expand All @@ -274,7 +275,8 @@ class XCUITestDriver extends BaseDriver {
log.info(`Xcode version set to '${this.xcodeVersion.versionString}' ${tools}`);

this.iosSdkVersion = await getAndCheckIosSdkVersion();
log.info(`iOS SDK Version set to '${this.iosSdkVersion}'`);
this.opts.iosSdkVersion = this.iosSdkVersion; // Pass to xcodebuild
log.info(`iOS SDK Version set to '${this.opts.iosSdkVersion}'`);
}
this.logEvent('xcodeDetailsRetrieved');

Expand Down
92 changes: 63 additions & 29 deletions lib/wda/utils.js
Expand Up @@ -192,53 +192,87 @@ CODE_SIGN_IDENTITY = ${signingId}
return xcconfigPath;
}

/**
* Information of the device under test
* @typedef {Object} DeviceInfo
* @property {string} isRealDevice - Equals to true if the current device is a real device
* @property {string} udid - The device UDID.
* @property {string} platformVersion - The platform version of OS.
*/
/**
* Creates xctestrun file per device & platform version.
* We expects to have WebDriverAgentRunner_iphoneos${platformVersion}-arm64.xctestrun for real device
* and WebDriverAgentRunner_iphonesimulator${platformVersion}-x86_64.xctestrun for simulator located @bootstrapPath
* We expects to have WebDriverAgentRunner_iphoneos${sdkVersion|platformVersion}-arm64.xctestrun for real device
* and WebDriverAgentRunner_iphonesimulator${sdkVersion|platformVersion}-x86_64.xctestrun for simulator located @bootstrapPath
* Newer Xcode (Xcode 10.0 at least) generate xctestrun file following sdkVersion.
* e.g. Xcode which has iOS SDK Version 12.2 generate WebDriverAgentRunner_iphonesimulator.2-x86_64.xctestrun
* even if the cap has platform version 11.4
*
* @param {boolean} isRealDevice - Equals to true if the current device is a real device
* @param {string} udid - The device UDID.
* @param {string} platformVersion - The platform version of OS.
* @param {DeviceInfo}
* @param {string} sdkVersion - The Xcode SDK version of OS.
* @param {string} bootstrapPath - The folder path containing xctestrun file.
* @param {string} wdaRemotePort - The remote port WDA is listening on.
* @return {string} returns xctestrunFilePath for given device
* @throws if WebDriverAgentRunner_iphoneos${platformVersion}-arm64.xctestrun for real device
* or WebDriverAgentRunner_iphonesimulator${platformVersion}-x86_64.xctestrun for simulator is not found @bootstrapPath,
* @throws if WebDriverAgentRunner_iphoneos${sdkVersion|platformVersion}-arm64.xctestrun for real device
* or WebDriverAgentRunner_iphonesimulator${sdkVersion|platformVersion}-x86_64.xctestrun for simulator is not found @bootstrapPath,
* then it will throw file not found exception
*/
async function setXctestrunFile (isRealDevice, udid, platformVersion, bootstrapPath, wdaRemotePort) {
let xctestrunDeviceFileName = `${udid}_${platformVersion}.xctestrun`;
let xctestrunFilePath = path.resolve(bootstrapPath, xctestrunDeviceFileName);

if (!await fs.exists(xctestrunFilePath)) {
let xctestBaseFileName = isRealDevice ? `WebDriverAgentRunner_iphoneos${platformVersion}-arm64.xctestrun` :
`WebDriverAgentRunner_iphonesimulator${platformVersion}-x86_64.xctestrun`;
let originalXctestrunFile = path.resolve(bootstrapPath, xctestBaseFileName);
if (!await fs.exists(originalXctestrunFile)) {
log.errorAndThrow(`if you are using useXctestrunFile capability then you need to have ${originalXctestrunFile} file`);
}
// If this is first time run for given device, then first generate xctestrun file for device.
// We need to have a xctestrun file per device because we cant not have same wda port for all devices.
await fs.copyFile(originalXctestrunFile, xctestrunFilePath);
}

let xctestRunContent = await plist.parsePlistFile(xctestrunFilePath);

let updateWDAPort = {
async function setXctestrunFile (deviceInfo, sdkVersion, bootstrapPath, wdaRemotePort) {
const xctestrunFilePath = await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath);
const xctestRunContent = await plist.parsePlistFile(xctestrunFilePath);
const updateWDAPort = {
WebDriverAgentRunner: {
EnvironmentVariables: {
USE_PORT: wdaRemotePort
}
}
};

let newXctestRunContent = _.merge(xctestRunContent, updateWDAPort);
const newXctestRunContent = _.merge(xctestRunContent, updateWDAPort);
await plist.updatePlistFile(xctestrunFilePath, newXctestRunContent, true);

return xctestrunFilePath;
}

/**
* Return the path of xctestrun if it exists
* @param {DeviceInfo}
* @param {string} sdkVersion - The Xcode SDK version of OS.
* @param {string} bootstrapPath - The folder path containing xctestrun file.
*/
async function getXctestrunFilePath (deviceInfo, sdkVersion, bootstrapPath) {
// First try the SDK path, for Xcode 10 (at least)
const sdkBased = [
path.resolve(bootstrapPath, `${deviceInfo.udid}_${sdkVersion}.xctestrun`),
sdkVersion,
];
// Next try Platform path, for earlier Xcode versions
const platformBased = [
path.resolve(bootstrapPath, `${deviceInfo.udid}_${deviceInfo.platformVersion}.xctestrun`),
deviceInfo.platformVersion,
];

for (const [filePath, version] of [sdkBased, platformBased]) {
if (await fs.exists(filePath)) {
return filePath;
}
const originalXctestrunFile = path.resolve(bootstrapPath, getXctestrunFilePathName(deviceInfo.isRealDevice, version));
if (await fs.exists(originalXctestrunFile)) {
// If this is first time run for given device, then first generate xctestrun file for device.
// We need to have a xctestrun file **per device** because we cant not have same wda port for all devices.
await fs.copyFile(originalXctestrunFile, filePath);
return filePath;
}
}

log.errorAndThrow(`If you are using 'useXctestrunFile' capability then you ` +
`need to have a xctestrun file (expected: ` +
`'${path.resolve(bootstrapPath, getXctestrunFilePathName(deviceInfo.isRealDevice, sdkVersion))}')`);
}

function getXctestrunFilePathName (isRealDevice, version) {
return `WebDriverAgentRunner_iphone${isRealDevice ? `os${version}-arm64` : `simulator${version}-x86_64`}.xctestrun`;
}

async function killProcess (name, proc) {
if (proc && proc.proc) {
log.info(`Shutting down ${name} process (pid ${proc.proc.pid})`);
Expand Down Expand Up @@ -290,5 +324,5 @@ async function getWDAUpgradeTimestamp (bootstrapPath) {

export { updateProjectFile, resetProjectFile, checkForDependencies,
setRealDeviceSecurity, fixForXcode7, fixForXcode9,
generateXcodeConfigFile, setXctestrunFile, killProcess, randomInt, WDA_RUNNER_BUNDLE_ID,
getWDAUpgradeTimestamp };
generateXcodeConfigFile, setXctestrunFile, getXctestrunFilePath,
killProcess, randomInt, WDA_RUNNER_BUNDLE_ID, getWDAUpgradeTimestamp };
2 changes: 2 additions & 0 deletions lib/wda/webdriveragent.js
Expand Up @@ -27,6 +27,7 @@ class WebDriverAgent {

this.device = args.device;
this.platformVersion = args.platformVersion;
this.iosSdkVersion = args.iosSdkVersion;
this.host = args.host;
this.realDevice = !!args.realDevice;

Expand All @@ -48,6 +49,7 @@ class WebDriverAgent {

this.xcodebuild = new XcodeBuild(this.xcodeVersion, this.device, {
platformVersion: this.platformVersion,
iosSdkVersion: this.iosSdkVersion,
agentPath: this.agentPath,
bootstrapPath: this.bootstrapPath,
realDevice: this.realDevice,
Expand Down
4 changes: 3 additions & 1 deletion lib/wda/xcodebuild.js
Expand Up @@ -31,6 +31,7 @@ class XcodeBuild {
this.bootstrapPath = args.bootstrapPath;

this.platformVersion = args.platformVersion;
this.iosSdkVersion = args.iosSdkVersion;

this.showXcodeLog = !!args.showXcodeLog;

Expand Down Expand Up @@ -63,7 +64,8 @@ class XcodeBuild {
if (this.xcodeVersion.major <= 7) {
log.errorAndThrow('useXctestrunFile can only be used with xcode version 8 onwards');
}
this.xctestrunFilePath = await setXctestrunFile(this.realDevice, this.device.udid, this.platformVersion, this.bootstrapPath, this.wdaRemotePort);
const deviveInfo = {isRealDevice: this.realDevice, udid: this.device.udid, platformVersion: this.platformVersion};
this.xctestrunFilePath = await setXctestrunFile(deviveInfo, this.iosSdkVersion, this.bootstrapPath, this.wdaRemotePort);
return;
}

Expand Down
107 changes: 107 additions & 0 deletions test/unit/wda/utils-specs.js
@@ -0,0 +1,107 @@
import { getXctestrunFilePath } from '../../../lib/wda/utils';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { withMocks } from 'appium-test-support';
import { fs } from 'appium-support';
import path from 'path';
import { fail } from 'assert';

chai.should();
chai.use(chaiAsPromised);

describe('utils', function () {
describe('#getXctestrunFilePath', withMocks({fs}, function (mocks) {
const platformVersion = '12.0';
const sdkVersion = '12.2';
const udid = 'xxxxxyyyyyyzzzzzz';
const bootstrapPath = 'path/to/data';

afterEach(function () {
mocks.verify();
});

it('should return sdk based path with udid', async function () {
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`))
.returns(true);
mocks.fs.expects('copyFile')
.never();
const deviceInfo = {isRealDevice: true, udid, platformVersion};
await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath)
.should.eventually.equal(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`));
});

it('should return sdk based path without udid, copy them', async function () {
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`))
.returns(false);
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphoneos${sdkVersion}-arm64.xctestrun`))
.returns(true);
mocks.fs.expects('copyFile')
.withExactArgs(
path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphoneos${sdkVersion}-arm64.xctestrun`),
path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`)
)
.returns(true);
const deviceInfo = {isRealDevice: true, udid, platformVersion};
await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath)
.should.eventually.equal(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`));
});

it('should return platform based path', async function () {
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`))
.returns(false);
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-x86_64.xctestrun`))
.returns(false);
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`))
.returns(true);
mocks.fs.expects('copyFile')
.never();
const deviceInfo = {isRealDevice: false, udid, platformVersion};
await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath)
.should.eventually.equal(path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`));
});

it('should return platform based path without udid, copy them', async function () {
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${sdkVersion}.xctestrun`))
.returns(false);
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-x86_64.xctestrun`))
.returns(false);
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`))
.returns(false);
mocks.fs.expects('exists')
.withExactArgs(path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${platformVersion}-x86_64.xctestrun`))
.returns(true);
mocks.fs.expects('copyFile')
.withExactArgs(
path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${platformVersion}-x86_64.xctestrun`),
path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`)
)
.returns(true);

const deviceInfo = {isRealDevice: false, udid, platformVersion};
await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath)
.should.eventually.equal(path.resolve(`${bootstrapPath}/${udid}_${platformVersion}.xctestrun`));
});

it('should raise an exception because of no files', async function () {
const expected = path.resolve(`${bootstrapPath}/WebDriverAgentRunner_iphonesimulator${sdkVersion}-x86_64.xctestrun`);
mocks.fs.expects('exists').exactly(4).returns(false);

const deviceInfo = {isRealDevice: false, udid, platformVersion};
try {
await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath);
fail();
} catch (err) {
err.message.should.equal(`If you are using 'useXctestrunFile' capability then you need to have a xctestrun file (expected: '${expected}')`);
}
});
}));
});

0 comments on commit b87df4e

Please sign in to comment.