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

fix xctestrun file detection when useXctestrunFile is true #903

Merged
merged 8 commits into from
Mar 19, 2019
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use this.opts.iosSdkVersion and do away with this.iosSdkVersion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to

// some things that commands imported from appium-ios-driver need
this.curWebFrames = [];
this.webElementIds = [];
this._currentUrl = null;
this.curContext = null;
this.xcodeVersion = {};
this.iosSdkVersion = null;
this.contexts = [];
this.implicitWaitMs = 0;
this.asynclibWaitMs = 0;
this.pageLoadMs = 6000;
this.landscapeWebCoordsOffset = 0;
, this.iosSdkVersion is imported from ios-driver. thus, I added this.opts.iosSdkVersion keeping this.iosSdkVersion to avoid affection to this.iosSdkVersion.

I replaced this.iosSdkVersion to this.opts.iosSdkVersion once and ran some tests without issues, but I thought the change should have created as another PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with that.

log.info(`iOS SDK Version set to '${this.iosSdkVersion}'`);
this.opts.iosSdkVersion = this.iosSdkVersion; // Pass to xcodebuild
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.iosSdkVersion is shared with ios-driver. Thus, I've added it as a part of opts since the opts is an argument of WDA.

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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}')`);
}
});
}));
});