appium-webdriveragent 10.5.4 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/Scripts/{build-webdriveragent.js → build-webdriveragent.mjs} +13 -7
- package/Scripts/{fetch-prebuilt-wda.js → fetch-prebuilt-wda.mjs} +17 -9
- package/Scripts/update-wda-version.mjs +42 -0
- package/WebDriverAgentLib/Info.plist +2 -2
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -2
- package/build/index.js.map +1 -1
- package/build/lib/check-dependencies.d.ts +2 -7
- package/build/lib/check-dependencies.d.ts.map +1 -1
- package/build/lib/check-dependencies.js +0 -11
- package/build/lib/check-dependencies.js.map +1 -1
- package/build/lib/constants.d.ts +12 -12
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js +14 -26
- package/build/lib/constants.js.map +1 -1
- package/build/lib/logger.d.ts +1 -2
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js +2 -2
- package/build/lib/logger.js.map +1 -1
- package/build/lib/no-session-proxy.d.ts +5 -5
- package/build/lib/no-session-proxy.d.ts.map +1 -1
- package/build/lib/no-session-proxy.js +0 -1
- package/build/lib/no-session-proxy.js.map +1 -1
- package/build/lib/utils.d.ts +53 -96
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +124 -110
- package/build/lib/utils.js.map +1 -1
- package/build/lib/webdriveragent.d.ts +123 -170
- package/build/lib/webdriveragent.d.ts.map +1 -1
- package/build/lib/webdriveragent.js +319 -349
- package/build/lib/webdriveragent.js.map +1 -1
- package/build/lib/xcodebuild.d.ts +66 -77
- package/build/lib/xcodebuild.d.ts.map +1 -1
- package/build/lib/xcodebuild.js +123 -92
- package/build/lib/xcodebuild.js.map +1 -1
- package/build/test/functional/desired.d.ts +3 -0
- package/build/test/functional/desired.d.ts.map +1 -0
- package/build/test/functional/desired.js +9 -0
- package/build/test/functional/desired.js.map +1 -0
- package/build/test/functional/helpers/simulator.d.ts +5 -0
- package/build/test/functional/helpers/simulator.d.ts.map +1 -0
- package/build/test/functional/helpers/simulator.js +39 -0
- package/build/test/functional/helpers/simulator.js.map +1 -0
- package/build/test/functional/webdriveragent-e2e-specs.d.ts +2 -0
- package/build/test/functional/webdriveragent-e2e-specs.d.ts.map +1 -0
- package/build/test/functional/webdriveragent-e2e-specs.js +133 -0
- package/build/test/functional/webdriveragent-e2e-specs.js.map +1 -0
- package/build/test/unit/utils-specs.d.ts +2 -0
- package/build/test/unit/utils-specs.d.ts.map +1 -0
- package/build/test/unit/utils-specs.js +161 -0
- package/build/test/unit/utils-specs.js.map +1 -0
- package/build/test/unit/webdriveragent-specs.d.ts +2 -0
- package/build/test/unit/webdriveragent-specs.d.ts.map +1 -0
- package/build/test/unit/webdriveragent-specs.js +424 -0
- package/build/test/unit/webdriveragent-specs.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/index.ts +1 -1
- package/lib/{check-dependencies.js → check-dependencies.ts} +4 -13
- package/lib/constants.ts +18 -0
- package/lib/logger.ts +3 -0
- package/lib/{no-session-proxy.js → no-session-proxy.ts} +4 -5
- package/lib/{utils.js → utils.ts} +101 -111
- package/lib/{webdriveragent.js → webdriveragent.ts} +353 -390
- package/lib/{xcodebuild.js → xcodebuild.ts} +145 -114
- package/package.json +7 -9
- package/lib/constants.js +0 -24
- package/lib/logger.js +0 -5
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { waitForCondition } from 'asyncbox';
|
|
2
2
|
import _ from 'lodash';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import url from 'url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import url from 'node:url';
|
|
5
5
|
import B from 'bluebird';
|
|
6
6
|
import { JWProxy } from '@appium/base-driver';
|
|
7
7
|
import { fs, util, plist } from '@appium/support';
|
|
8
|
-
import
|
|
8
|
+
import type { AppiumLogger, StringRecord } from '@appium/types';
|
|
9
|
+
import { log as defaultLogger } from './logger';
|
|
9
10
|
import { NoSessionProxy } from './no-session-proxy';
|
|
10
11
|
import {
|
|
11
12
|
getWDAUpgradeTimestamp, resetTestProcesses, getPIDsListeningOnPort, BOOTSTRAP_PATH
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
} from './constants';
|
|
21
22
|
import {Xctest} from 'appium-ios-device';
|
|
22
23
|
import {strongbox} from '@appium/strongbox';
|
|
24
|
+
import type { WebDriverAgentArgs, AppleDevice } from './types';
|
|
23
25
|
|
|
24
26
|
const WDA_LAUNCH_TIMEOUT = 60 * 1000;
|
|
25
27
|
const WDA_AGENT_PORT = 8100;
|
|
@@ -28,23 +30,46 @@ const SHARED_RESOURCES_GUARD = new AsyncLock();
|
|
|
28
30
|
const RECENT_MODULE_VERSION_ITEM_NAME = 'recentWdaModuleVersion';
|
|
29
31
|
|
|
30
32
|
export class WebDriverAgent {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
bootstrapPath: string;
|
|
34
|
+
agentPath: string;
|
|
35
|
+
readonly args: WebDriverAgentArgs;
|
|
36
|
+
private readonly log: AppiumLogger;
|
|
37
|
+
readonly device: AppleDevice;
|
|
38
|
+
readonly platformVersion?: string;
|
|
39
|
+
readonly platformName?: string;
|
|
40
|
+
readonly iosSdkVersion?: string;
|
|
41
|
+
readonly host?: string;
|
|
42
|
+
readonly isRealDevice: boolean;
|
|
43
|
+
private readonly wdaBundlePath?: string;
|
|
44
|
+
private readonly wdaLocalPort?: number;
|
|
45
|
+
readonly wdaRemotePort: number;
|
|
46
|
+
readonly wdaBaseUrl: string;
|
|
47
|
+
readonly wdaBindingIP?: string;
|
|
48
|
+
private readonly prebuildWDA?: boolean;
|
|
49
|
+
webDriverAgentUrl?: string;
|
|
50
|
+
started: boolean;
|
|
51
|
+
private readonly wdaConnectionTimeout?: number;
|
|
52
|
+
private readonly useXctestrunFile?: boolean;
|
|
53
|
+
private readonly usePrebuiltWDA?: boolean;
|
|
54
|
+
private readonly derivedDataPath?: string;
|
|
55
|
+
private readonly mjpegServerPort?: number;
|
|
56
|
+
updatedWDABundleId?: string;
|
|
57
|
+
private readonly wdaLaunchTimeout: number;
|
|
58
|
+
private readonly usePreinstalledWDA?: boolean;
|
|
59
|
+
private xctestApiClient?: Xctest | null;
|
|
60
|
+
private readonly updatedWDABundleIdSuffix: string;
|
|
61
|
+
private _xcodebuild?: XcodeBuild | null;
|
|
62
|
+
noSessionProxy?: NoSessionProxy;
|
|
63
|
+
jwproxy?: JWProxy;
|
|
64
|
+
proxyReqRes?: any;
|
|
65
|
+
private _url?: url.UrlWithStringQuery;
|
|
39
66
|
|
|
40
67
|
/**
|
|
41
|
-
*
|
|
42
|
-
* @param
|
|
43
|
-
* @param
|
|
68
|
+
* Creates a new WebDriverAgent instance.
|
|
69
|
+
* @param args - Configuration arguments for WebDriverAgent
|
|
70
|
+
* @param log - Optional logger instance
|
|
44
71
|
*/
|
|
45
|
-
constructor (
|
|
46
|
-
this.xcodeVersion = xcodeVersion;
|
|
47
|
-
|
|
72
|
+
constructor (args: WebDriverAgentArgs, log: AppiumLogger | null = null) {
|
|
48
73
|
this.args = _.clone(args);
|
|
49
74
|
this.log = log ?? defaultLogger;
|
|
50
75
|
|
|
@@ -54,8 +79,6 @@ export class WebDriverAgent {
|
|
|
54
79
|
this.iosSdkVersion = args.iosSdkVersion;
|
|
55
80
|
this.host = args.host;
|
|
56
81
|
this.isRealDevice = !!args.realDevice;
|
|
57
|
-
/** @deprecated We'll stop supporting idb */
|
|
58
|
-
this.idb = args.device.idb;
|
|
59
82
|
this.wdaBundlePath = args.wdaBundlePath;
|
|
60
83
|
|
|
61
84
|
this.setWDAPaths(args.bootstrapPath, args.agentPath);
|
|
@@ -88,9 +111,9 @@ export class WebDriverAgent {
|
|
|
88
111
|
this.xctestApiClient = null;
|
|
89
112
|
this.updatedWDABundleIdSuffix = args.updatedWDABundleIdSuffix ?? DEFAULT_TEST_BUNDLE_SUFFIX;
|
|
90
113
|
|
|
91
|
-
this.
|
|
114
|
+
this._xcodebuild = this.canSkipXcodebuild
|
|
92
115
|
? null
|
|
93
|
-
: new XcodeBuild(this.
|
|
116
|
+
: new XcodeBuild(this.device, {
|
|
94
117
|
platformVersion: this.platformVersion,
|
|
95
118
|
platformName: this.platformName,
|
|
96
119
|
iosSdkVersion: this.iosSdkVersion,
|
|
@@ -120,46 +143,44 @@ export class WebDriverAgent {
|
|
|
120
143
|
|
|
121
144
|
/**
|
|
122
145
|
* Return true if the session does not need xcodebuild.
|
|
123
|
-
* @returns
|
|
146
|
+
* @returns Whether the session needs/has xcodebuild.
|
|
124
147
|
*/
|
|
125
|
-
get canSkipXcodebuild () {
|
|
148
|
+
get canSkipXcodebuild (): boolean {
|
|
126
149
|
// Use this.args.webDriverAgentUrl to guarantee
|
|
127
150
|
// the capabilities set gave the `appium:webDriverAgentUrl`.
|
|
128
151
|
return this.usePreinstalledWDA || !!this.args.webDriverAgentUrl;
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Get the xcodebuild instance. Throws if not initialized.
|
|
156
|
+
* @returns The XcodeBuild instance
|
|
157
|
+
* @throws Error if xcodebuild is not available
|
|
158
|
+
*/
|
|
159
|
+
get xcodebuild (): XcodeBuild {
|
|
160
|
+
if (!this._xcodebuild) {
|
|
161
|
+
throw new Error('xcodebuild is not available');
|
|
162
|
+
}
|
|
163
|
+
return this._xcodebuild;
|
|
164
|
+
}
|
|
165
|
+
|
|
131
166
|
/**
|
|
132
167
|
* Return bundle id for WebDriverAgent to launch the WDA.
|
|
133
168
|
* The primary usage is with 'this.usePreinstalledWDA'.
|
|
134
169
|
* It adds `.xctrunner` as suffix by default but 'this.updatedWDABundleIdSuffix'
|
|
135
170
|
* lets skip it.
|
|
136
171
|
*
|
|
137
|
-
* @returns
|
|
172
|
+
* @returns Bundle ID for Xctest.
|
|
138
173
|
*/
|
|
139
|
-
get bundleIdForXctest () {
|
|
174
|
+
get bundleIdForXctest (): string {
|
|
140
175
|
return `${this.updatedWDABundleId ? this.updatedWDABundleId : WDA_RUNNER_BUNDLE_ID}${this.updatedWDABundleIdSuffix}`;
|
|
141
176
|
}
|
|
142
177
|
|
|
143
178
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*/
|
|
147
|
-
setWDAPaths (bootstrapPath, agentPath) {
|
|
148
|
-
// allow the user to specify a place for WDA. This is undocumented and
|
|
149
|
-
// only here for the purposes of testing development of WDA
|
|
150
|
-
this.bootstrapPath = bootstrapPath || BOOTSTRAP_PATH;
|
|
151
|
-
this.log.info(`Using WDA path: '${this.bootstrapPath}'`);
|
|
152
|
-
|
|
153
|
-
// for backward compatibility we need to be able to specify agentPath too
|
|
154
|
-
this.agentPath = agentPath || path.resolve(this.bootstrapPath, 'WebDriverAgent.xcodeproj');
|
|
155
|
-
this.log.info(`Using WDA agent: '${this.agentPath}'`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* @returns {Promise<void>}
|
|
179
|
+
* Cleans up obsolete cached processes from previous WDA sessions
|
|
180
|
+
* that are listening on the same port but belong to different devices.
|
|
160
181
|
*/
|
|
161
|
-
async cleanupObsoleteProcesses () {
|
|
162
|
-
const obsoletePids = await getPIDsListeningOnPort(
|
|
182
|
+
async cleanupObsoleteProcesses (): Promise<void> {
|
|
183
|
+
const obsoletePids = await getPIDsListeningOnPort(this.url.port as string,
|
|
163
184
|
(cmdLine) => cmdLine.includes('/WebDriverAgentRunner') &&
|
|
164
185
|
!cmdLine.toLowerCase().includes(this.device.udid.toLowerCase()));
|
|
165
186
|
|
|
@@ -173,268 +194,23 @@ export class WebDriverAgent {
|
|
|
173
194
|
`from previous WDA sessions. Cleaning them up`);
|
|
174
195
|
try {
|
|
175
196
|
await exec('kill', obsoletePids);
|
|
176
|
-
} catch (e) {
|
|
197
|
+
} catch (e: any) {
|
|
177
198
|
this.log.warn(`Failed to kill obsolete cached process${obsoletePids.length === 1 ? '' : 'es'} '${obsoletePids}'. ` +
|
|
178
199
|
`Original error: ${e.message}`);
|
|
179
200
|
}
|
|
180
201
|
}
|
|
181
202
|
|
|
182
203
|
/**
|
|
183
|
-
*
|
|
184
|
-
* @
|
|
185
|
-
* @throws {Error} If there was invalid response code or body
|
|
204
|
+
* Gets the base path for the WebDriverAgent URL.
|
|
205
|
+
* @returns The base path (empty string if root path)
|
|
186
206
|
*/
|
|
187
|
-
|
|
188
|
-
return !!(await this.getStatus());
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @returns {string}
|
|
193
|
-
*/
|
|
194
|
-
get basePath () {
|
|
207
|
+
get basePath (): string {
|
|
195
208
|
if (this.url.path === '/') {
|
|
196
209
|
return '';
|
|
197
210
|
}
|
|
198
211
|
return this.url.path || '';
|
|
199
212
|
}
|
|
200
213
|
|
|
201
|
-
/**
|
|
202
|
-
* Return current running WDA's status like below
|
|
203
|
-
* {
|
|
204
|
-
* "state": "success",
|
|
205
|
-
* "os": {
|
|
206
|
-
* "name": "iOS",
|
|
207
|
-
* "version": "11.4",
|
|
208
|
-
* "sdkVersion": "11.3"
|
|
209
|
-
* },
|
|
210
|
-
* "ios": {
|
|
211
|
-
* "simulatorVersion": "11.4",
|
|
212
|
-
* "ip": "172.254.99.34"
|
|
213
|
-
* },
|
|
214
|
-
* "build": {
|
|
215
|
-
* "time": "Jun 24 2018 17:08:21",
|
|
216
|
-
* "productBundleIdentifier": "com.facebook.WebDriverAgentRunner"
|
|
217
|
-
* }
|
|
218
|
-
* }
|
|
219
|
-
*
|
|
220
|
-
* @param {number} [timeoutMs=0] If the given timeoutMs is zero or negative number,
|
|
221
|
-
* this function will return the response of `/status` immediately. If the given timeoutMs,
|
|
222
|
-
* this function will try to get the response of `/status` up to the timeoutMs.
|
|
223
|
-
* @return {Promise<import('@appium/types').StringRecord|null>} State Object
|
|
224
|
-
* @throws {Error} If there was an error within timeoutMs timeout.
|
|
225
|
-
* No error is raised if zero or negative number for the timeoutMs.
|
|
226
|
-
*/
|
|
227
|
-
async getStatus (timeoutMs = 0) {
|
|
228
|
-
const noSessionProxy = new NoSessionProxy({
|
|
229
|
-
server: this.url.hostname,
|
|
230
|
-
port: this.url.port,
|
|
231
|
-
base: this.basePath,
|
|
232
|
-
timeout: 3000,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
const sendGetStatus = async () => await /** @type import('@appium/types').StringRecord */ (noSessionProxy.command('/status', 'GET'));
|
|
236
|
-
|
|
237
|
-
if (_.isNil(timeoutMs) || timeoutMs <= 0) {
|
|
238
|
-
try {
|
|
239
|
-
return await sendGetStatus();
|
|
240
|
-
} catch (err) {
|
|
241
|
-
this.log.debug(`WDA is not listening at '${this.url.href}'. Original error:: ${err.message}`);
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
let lastError = null;
|
|
247
|
-
let status = null;
|
|
248
|
-
try {
|
|
249
|
-
await waitForCondition(async () => {
|
|
250
|
-
try {
|
|
251
|
-
status = await sendGetStatus();
|
|
252
|
-
return true;
|
|
253
|
-
} catch (err) {
|
|
254
|
-
lastError = err;
|
|
255
|
-
}
|
|
256
|
-
return false;
|
|
257
|
-
}, {
|
|
258
|
-
waitMs: timeoutMs,
|
|
259
|
-
intervalMs: 300,
|
|
260
|
-
});
|
|
261
|
-
} catch (err) {
|
|
262
|
-
this.log.debug(`Failed to get the status endpoint in ${timeoutMs} ms. ` +
|
|
263
|
-
`The last error while accessing ${this.url.href}: ${lastError}. Original error:: ${err.message}.`);
|
|
264
|
-
throw new Error(`WDA was not ready in ${timeoutMs} ms.`);
|
|
265
|
-
}
|
|
266
|
-
return status;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Uninstall WDAs from the test device.
|
|
271
|
-
* Over Xcode 11, multiple WDA can be in the device since Xcode 11 generates different WDA.
|
|
272
|
-
* Appium does not expect multiple WDAs are running on a device.
|
|
273
|
-
*
|
|
274
|
-
* @returns {Promise<void>}
|
|
275
|
-
*/
|
|
276
|
-
async uninstall () {
|
|
277
|
-
try {
|
|
278
|
-
const bundleIds = await this.device.getUserInstalledBundleIdsByBundleName(WDA_CF_BUNDLE_NAME);
|
|
279
|
-
if (_.isEmpty(bundleIds)) {
|
|
280
|
-
this.log.debug('No WDAs on the device.');
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
this.log.debug(`Uninstalling WDAs: '${bundleIds}'`);
|
|
285
|
-
for (const bundleId of bundleIds) {
|
|
286
|
-
await this.device.removeApp(bundleId);
|
|
287
|
-
}
|
|
288
|
-
} catch (e) {
|
|
289
|
-
this.log.debug(e);
|
|
290
|
-
this.log.warn(`WebDriverAgent uninstall failed. Perhaps, it is already uninstalled? ` +
|
|
291
|
-
`Original error: ${e.message}`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async _cleanupProjectIfFresh () {
|
|
296
|
-
if (this.canSkipXcodebuild) {
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const packageInfo = JSON.parse(await fs.readFile(path.join(BOOTSTRAP_PATH, 'package.json'), 'utf8'));
|
|
301
|
-
const box = strongbox(packageInfo.name);
|
|
302
|
-
let boxItem = box.getItem(RECENT_MODULE_VERSION_ITEM_NAME);
|
|
303
|
-
if (!boxItem) {
|
|
304
|
-
const timestampPath = path.resolve(process.env.HOME ?? '', WDA_UPGRADE_TIMESTAMP_PATH);
|
|
305
|
-
if (await fs.exists(timestampPath)) {
|
|
306
|
-
// TODO: It is probably a bit ugly to hardcode the recent version string,
|
|
307
|
-
// TODO: hovewer it should do the job as a temporary transition trick
|
|
308
|
-
// TODO: to switch from a hardcoded file path to the strongbox usage.
|
|
309
|
-
try {
|
|
310
|
-
boxItem = await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, '5.0.0');
|
|
311
|
-
} catch (e) {
|
|
312
|
-
this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
} else {
|
|
316
|
-
this.log.info('There is no need to perform the project cleanup. A fresh install has been detected');
|
|
317
|
-
try {
|
|
318
|
-
await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, packageInfo.version);
|
|
319
|
-
} catch (e) {
|
|
320
|
-
this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
|
|
321
|
-
}
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
let recentModuleVersion = await boxItem.read();
|
|
327
|
-
try {
|
|
328
|
-
recentModuleVersion = util.coerceVersion(recentModuleVersion, true);
|
|
329
|
-
} catch (e) {
|
|
330
|
-
this.log.warn(`The persisted module version string has been damaged: ${e.message}`);
|
|
331
|
-
this.log.info(`Updating it to '${packageInfo.version}' assuming the project clenup is not needed`);
|
|
332
|
-
await boxItem.write(packageInfo.version);
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (util.compareVersions(recentModuleVersion, '>=', packageInfo.version)) {
|
|
337
|
-
this.log.info(
|
|
338
|
-
`WebDriverAgent does not need a cleanup. The project sources are up to date ` +
|
|
339
|
-
`(${recentModuleVersion} >= ${packageInfo.version})`
|
|
340
|
-
);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
this.log.info(
|
|
345
|
-
`Cleaning up the WebDriverAgent project after the module upgrade has happened ` +
|
|
346
|
-
`(${recentModuleVersion} < ${packageInfo.version})`
|
|
347
|
-
);
|
|
348
|
-
try {
|
|
349
|
-
// @ts-ignore xcodebuild should be set
|
|
350
|
-
await this.xcodebuild.cleanProject();
|
|
351
|
-
await boxItem.write(packageInfo.version);
|
|
352
|
-
} catch (e) {
|
|
353
|
-
this.log.warn(`Cannot perform WebDriverAgent project cleanup. Original error: ${e.message}`);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* @typedef {Object} LaunchWdaViaDeviceCtlOptions
|
|
360
|
-
* @property {Record<string, string|number>} [env] environment variables for the launching WDA process
|
|
361
|
-
*/
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Launch WDA with preinstalled package with 'xcrun devicectl device process launch'.
|
|
365
|
-
* The WDA package must be prepared properly like published via
|
|
366
|
-
* https://github.com/appium/WebDriverAgent/releases
|
|
367
|
-
* with proper sign for this case.
|
|
368
|
-
*
|
|
369
|
-
* When we implement launching XCTest service via appium-ios-device,
|
|
370
|
-
* this implementation can be replaced with it.
|
|
371
|
-
*
|
|
372
|
-
* @param {LaunchWdaViaDeviceCtlOptions} [opts={}] launching WDA with devicectl command options.
|
|
373
|
-
* @return {Promise<void>}
|
|
374
|
-
*/
|
|
375
|
-
async _launchViaDevicectl(opts = {}) {
|
|
376
|
-
const {env} = opts;
|
|
377
|
-
|
|
378
|
-
await this.device.devicectl.launchApp(
|
|
379
|
-
this.bundleIdForXctest, { env, terminateExisting: true }
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Launch WDA with preinstalled package without xcodebuild.
|
|
385
|
-
* @param {string} sessionId Launch WDA and establish the session with this sessionId
|
|
386
|
-
* @return {Promise<import('@appium/types').StringRecord|null>} State Object
|
|
387
|
-
* @throws {Error} If there was an error within timeoutMs timeout.
|
|
388
|
-
* No error is raised if zero or negative number for the timeoutMs.
|
|
389
|
-
*/
|
|
390
|
-
async launchWithPreinstalledWDA(sessionId) {
|
|
391
|
-
const xctestEnv = {
|
|
392
|
-
USE_PORT: this.wdaLocalPort || WDA_AGENT_PORT,
|
|
393
|
-
WDA_PRODUCT_BUNDLE_IDENTIFIER: this.bundleIdForXctest
|
|
394
|
-
};
|
|
395
|
-
if (this.mjpegServerPort) {
|
|
396
|
-
xctestEnv.MJPEG_SERVER_PORT = this.mjpegServerPort;
|
|
397
|
-
}
|
|
398
|
-
if (this.wdaBindingIP) {
|
|
399
|
-
xctestEnv.USE_IP = this.wdaBindingIP;
|
|
400
|
-
}
|
|
401
|
-
this.log.info('Launching WebDriverAgent on the device without xcodebuild');
|
|
402
|
-
if (this.isRealDevice) {
|
|
403
|
-
// Current method to launch WDA process can be done via 'xcrun devicectl',
|
|
404
|
-
// but it has limitation about the WDA preinstalled package.
|
|
405
|
-
// https://github.com/appium/appium/issues/19206#issuecomment-2014182674
|
|
406
|
-
if (this.platformVersion && util.compareVersions(this.platformVersion, '>=', '17.0')) {
|
|
407
|
-
await this._launchViaDevicectl({env: xctestEnv});
|
|
408
|
-
} else {
|
|
409
|
-
this.xctestApiClient = new Xctest(this.device.udid, this.bundleIdForXctest, null, {env: xctestEnv});
|
|
410
|
-
await this.xctestApiClient.start();
|
|
411
|
-
}
|
|
412
|
-
} else {
|
|
413
|
-
await this.device.simctl.exec('launch', {
|
|
414
|
-
args: [
|
|
415
|
-
'--terminate-running-process',
|
|
416
|
-
this.device.udid,
|
|
417
|
-
this.bundleIdForXctest,
|
|
418
|
-
],
|
|
419
|
-
env: xctestEnv,
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
this.setupProxies(sessionId);
|
|
424
|
-
let status;
|
|
425
|
-
try {
|
|
426
|
-
status = await this.getStatus(this.wdaLaunchTimeout);
|
|
427
|
-
} catch {
|
|
428
|
-
throw new Error(
|
|
429
|
-
`Failed to start the preinstalled WebDriverAgent in ${this.wdaLaunchTimeout} ms. ` +
|
|
430
|
-
`The WebDriverAgent might not be properly built or the device might be locked. ` +
|
|
431
|
-
`The 'appium:wdaLaunchTimeout' capability modifies the timeout.`
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
this.started = true;
|
|
435
|
-
return status;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
214
|
/**
|
|
439
215
|
* Return current running WDA's status like below after launching WDA
|
|
440
216
|
* {
|
|
@@ -454,11 +230,9 @@ export class WebDriverAgent {
|
|
|
454
230
|
* }
|
|
455
231
|
* }
|
|
456
232
|
*
|
|
457
|
-
* @param
|
|
458
|
-
* @return {Promise<any?>} State Object
|
|
459
|
-
* @throws {Error} If there was invalid response code or body
|
|
233
|
+
* @param sessionId Launch WDA and establish the session with this sessionId
|
|
460
234
|
*/
|
|
461
|
-
async launch (sessionId) {
|
|
235
|
+
async launch (sessionId: string): Promise<StringRecord | null> {
|
|
462
236
|
if (this.webDriverAgentUrl) {
|
|
463
237
|
this.log.info(`Using provided WebdriverAgent at '${this.webDriverAgentUrl}'`);
|
|
464
238
|
this.url = this.webDriverAgentUrl;
|
|
@@ -481,7 +255,7 @@ export class WebDriverAgent {
|
|
|
481
255
|
|
|
482
256
|
// useXctestrunFile and usePrebuiltWDA use existing dependencies
|
|
483
257
|
// It depends on user side
|
|
484
|
-
if (this.
|
|
258
|
+
if (this.useXctestrunFile || this.usePrebuiltWDA) {
|
|
485
259
|
this.log.info('Skipped WDA project cleanup according to the provided capabilities');
|
|
486
260
|
} else {
|
|
487
261
|
const synchronizationKey = path.normalize(this.bootstrapPath);
|
|
@@ -492,77 +266,43 @@ export class WebDriverAgent {
|
|
|
492
266
|
// We need to provide WDA local port, because it might be occupied
|
|
493
267
|
await resetTestProcesses(this.device.udid, !this.isRealDevice);
|
|
494
268
|
|
|
495
|
-
if (this.
|
|
496
|
-
|
|
269
|
+
if (!this.noSessionProxy) {
|
|
270
|
+
throw new Error('noSessionProxy is not available');
|
|
497
271
|
}
|
|
498
|
-
|
|
499
|
-
// @ts-ignore xcodebuild should be set
|
|
500
272
|
await this.xcodebuild.init(this.noSessionProxy);
|
|
501
273
|
|
|
502
274
|
// Start the xcodebuild process
|
|
503
275
|
if (this.prebuildWDA) {
|
|
504
|
-
// @ts-ignore xcodebuild should be set
|
|
505
276
|
await this.xcodebuild.prebuild();
|
|
506
277
|
}
|
|
507
|
-
|
|
508
|
-
return await this.xcodebuild.start();
|
|
278
|
+
return await this.xcodebuild.start() as StringRecord | null;
|
|
509
279
|
}
|
|
510
280
|
|
|
511
281
|
/**
|
|
512
|
-
*
|
|
513
|
-
*
|
|
282
|
+
* Checks if the WebDriverAgent source is fresh by verifying
|
|
283
|
+
* that required resource files exist.
|
|
284
|
+
* @returns `true` if source is fresh (all required files exist), `false` otherwise
|
|
514
285
|
*/
|
|
515
|
-
async
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
};
|
|
522
|
-
if (this.mjpegServerPort) {
|
|
523
|
-
env.MJPEG_SERVER_PORT = this.mjpegServerPort;
|
|
524
|
-
}
|
|
525
|
-
if (this.wdaBindingIP) {
|
|
526
|
-
env.USE_IP = this.wdaBindingIP;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
return await this.idb.runXCUITest(wdaBundleId, wdaBundleId, testBundleId, {env});
|
|
286
|
+
async isSourceFresh (): Promise<boolean> {
|
|
287
|
+
const existsPromises = [
|
|
288
|
+
'Resources',
|
|
289
|
+
`Resources${path.sep}WebDriverAgent.bundle`,
|
|
290
|
+
].map((subPath) => fs.exists(path.resolve(this.bootstrapPath, subPath)));
|
|
291
|
+
return (await B.all(existsPromises)).some((v) => v === false);
|
|
530
292
|
}
|
|
531
293
|
|
|
532
|
-
|
|
533
|
-
*
|
|
534
|
-
* @param {string} wdaBundlePath
|
|
535
|
-
* @returns {Promise<string>}
|
|
536
|
-
*/
|
|
537
|
-
async parseBundleId (wdaBundlePath) {
|
|
294
|
+
private async parseBundleId (wdaBundlePath: string): Promise<string> {
|
|
538
295
|
const infoPlistPath = path.join(wdaBundlePath, 'Info.plist');
|
|
539
296
|
const infoPlist = await plist.parsePlist(await fs.readFile(infoPlistPath));
|
|
540
297
|
if (!infoPlist.CFBundleIdentifier) {
|
|
541
298
|
throw new Error(`Could not find bundle id in '${infoPlistPath}'`);
|
|
542
299
|
}
|
|
543
|
-
return infoPlist.CFBundleIdentifier;
|
|
300
|
+
return infoPlist.CFBundleIdentifier as string;
|
|
544
301
|
}
|
|
545
302
|
|
|
546
|
-
|
|
547
|
-
* @deprecated We'll stop using idb
|
|
548
|
-
* @returns {Promise<{wdaBundleId: string, testBundleId: string, wdaBundlePath: string}>}
|
|
549
|
-
*/
|
|
550
|
-
async prepareWDA () {
|
|
551
|
-
const wdaBundlePath = this.wdaBundlePath || await this.fetchWDABundle();
|
|
552
|
-
const wdaBundleId = await this.parseBundleId(wdaBundlePath);
|
|
553
|
-
if (!await this.device.isAppInstalled(wdaBundleId)) {
|
|
554
|
-
await this.device.installApp(wdaBundlePath);
|
|
555
|
-
}
|
|
556
|
-
const testBundleId = await this.idb.installXCTestBundle(path.join(wdaBundlePath, 'PlugIns', 'WebDriverAgentRunner.xctest'));
|
|
557
|
-
return {wdaBundleId, testBundleId, wdaBundlePath};
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* @returns {Promise<string>}
|
|
562
|
-
*/
|
|
563
|
-
async fetchWDABundle () {
|
|
303
|
+
private async fetchWDABundle (): Promise<string> {
|
|
564
304
|
if (!this.derivedDataPath) {
|
|
565
|
-
return await bundleWDASim(
|
|
305
|
+
return await bundleWDASim(this.xcodebuild);
|
|
566
306
|
}
|
|
567
307
|
const wdaBundlePaths = await fs.glob(`${this.derivedDataPath}/**/*${WDA_RUNNER_APP}/`, {
|
|
568
308
|
absolute: true,
|
|
@@ -573,23 +313,8 @@ export class WebDriverAgent {
|
|
|
573
313
|
return wdaBundlePaths[0];
|
|
574
314
|
}
|
|
575
315
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
*/
|
|
579
|
-
async isSourceFresh () {
|
|
580
|
-
const existsPromises = [
|
|
581
|
-
'Resources',
|
|
582
|
-
`Resources${path.sep}WebDriverAgent.bundle`,
|
|
583
|
-
].map((subPath) => fs.exists(path.resolve(/** @type {String} */ (this.bootstrapPath), subPath)));
|
|
584
|
-
return (await B.all(existsPromises)).some((v) => v === false);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* @param {string} sessionId
|
|
589
|
-
* @returns {void}
|
|
590
|
-
*/
|
|
591
|
-
setupProxies (sessionId) {
|
|
592
|
-
const proxyOpts = {
|
|
316
|
+
private setupProxies (sessionId: string): void {
|
|
317
|
+
const proxyOpts: any = {
|
|
593
318
|
log: this.log,
|
|
594
319
|
server: this.url.hostname ?? undefined,
|
|
595
320
|
port: parseInt(this.url.port ?? '', 10) || undefined,
|
|
@@ -610,9 +335,10 @@ export class WebDriverAgent {
|
|
|
610
335
|
}
|
|
611
336
|
|
|
612
337
|
/**
|
|
613
|
-
*
|
|
338
|
+
* Stops the WebDriverAgent session and cleans up resources.
|
|
339
|
+
* Handles both preinstalled WDA and xcodebuild-based sessions.
|
|
614
340
|
*/
|
|
615
|
-
async quit () {
|
|
341
|
+
async quit (): Promise<void> {
|
|
616
342
|
if (this.usePreinstalledWDA) {
|
|
617
343
|
this.log.info('Stopping the XCTest session');
|
|
618
344
|
if (this.xctestApiClient) {
|
|
@@ -621,14 +347,16 @@ export class WebDriverAgent {
|
|
|
621
347
|
} else {
|
|
622
348
|
try {
|
|
623
349
|
await this.device.simctl.terminateApp(this.bundleIdForXctest);
|
|
624
|
-
} catch (e) {
|
|
350
|
+
} catch (e: any) {
|
|
625
351
|
this.log.warn(e.message);
|
|
626
352
|
}
|
|
627
353
|
}
|
|
628
354
|
} else if (!this.args.webDriverAgentUrl) {
|
|
629
355
|
this.log.info('Shutting down sub-processes');
|
|
630
|
-
|
|
631
|
-
|
|
356
|
+
if (this._xcodebuild) {
|
|
357
|
+
await this.xcodebuild.quit();
|
|
358
|
+
await this.xcodebuild.reset();
|
|
359
|
+
}
|
|
632
360
|
} else {
|
|
633
361
|
this.log.debug('Do not stop xcodebuild nor XCTest session ' +
|
|
634
362
|
'since the WDA session is managed by outside this driver.');
|
|
@@ -648,9 +376,12 @@ export class WebDriverAgent {
|
|
|
648
376
|
}
|
|
649
377
|
|
|
650
378
|
/**
|
|
651
|
-
*
|
|
379
|
+
* Gets the WebDriverAgent URL.
|
|
380
|
+
* Constructs the URL from webDriverAgentUrl if provided, otherwise
|
|
381
|
+
* builds it from wdaBaseUrl, wdaBindingIP, and wdaLocalPort.
|
|
382
|
+
* @returns The parsed URL object
|
|
652
383
|
*/
|
|
653
|
-
get url () {
|
|
384
|
+
get url (): url.UrlWithStringQuery {
|
|
654
385
|
if (!this._url) {
|
|
655
386
|
if (this.webDriverAgentUrl) {
|
|
656
387
|
this._url = url.parse(this.webDriverAgentUrl);
|
|
@@ -664,46 +395,47 @@ export class WebDriverAgent {
|
|
|
664
395
|
}
|
|
665
396
|
|
|
666
397
|
/**
|
|
667
|
-
*
|
|
668
|
-
* @
|
|
398
|
+
* Sets the WebDriverAgent URL.
|
|
399
|
+
* @param _url - The URL string to parse and set
|
|
669
400
|
*/
|
|
670
|
-
set url (_url) {
|
|
401
|
+
set url (_url: string) {
|
|
671
402
|
this._url = url.parse(_url);
|
|
672
403
|
}
|
|
673
404
|
|
|
674
405
|
/**
|
|
675
|
-
*
|
|
406
|
+
* Gets whether WebDriverAgent has fully started.
|
|
407
|
+
* @returns `true` if WDA has started, `false` otherwise
|
|
676
408
|
*/
|
|
677
|
-
get fullyStarted () {
|
|
409
|
+
get fullyStarted (): boolean {
|
|
678
410
|
return this.started;
|
|
679
411
|
}
|
|
680
412
|
|
|
681
413
|
/**
|
|
682
|
-
*
|
|
683
|
-
* @
|
|
414
|
+
* Sets whether WebDriverAgent has fully started.
|
|
415
|
+
* @param started - `true` if WDA has started, `false` otherwise
|
|
684
416
|
*/
|
|
685
|
-
set fullyStarted (started) {
|
|
417
|
+
set fullyStarted (started: boolean) {
|
|
686
418
|
this.started = started ?? false;
|
|
687
419
|
}
|
|
688
420
|
|
|
689
421
|
/**
|
|
690
|
-
*
|
|
422
|
+
* Retrieves the Xcode derived data path for WebDriverAgent.
|
|
423
|
+
* @returns The derived data path, or `undefined` if xcodebuild is skipped
|
|
691
424
|
*/
|
|
692
|
-
async retrieveDerivedDataPath () {
|
|
425
|
+
async retrieveDerivedDataPath (): Promise<string | undefined> {
|
|
693
426
|
if (this.canSkipXcodebuild) {
|
|
694
427
|
return;
|
|
695
428
|
}
|
|
696
|
-
return await
|
|
429
|
+
return await this.xcodebuild.retrieveDerivedDataPath();
|
|
697
430
|
}
|
|
698
431
|
|
|
699
432
|
/**
|
|
700
433
|
* Reuse running WDA if it has the same bundle id with updatedWDABundleId.
|
|
701
434
|
* Or reuse it if it has the default id without updatedWDABundleId.
|
|
702
435
|
* Uninstall it if the method faces an exception for the above situation.
|
|
703
|
-
* @returns {Promise<void>}
|
|
704
436
|
*/
|
|
705
|
-
async setupCaching () {
|
|
706
|
-
const status = await this.getStatus();
|
|
437
|
+
async setupCaching (): Promise<void> {
|
|
438
|
+
const status = await this.getStatus(0);
|
|
707
439
|
if (!status || !status.build) {
|
|
708
440
|
this.log.debug('WDA is currently not running. There is nothing to cache');
|
|
709
441
|
return;
|
|
@@ -712,7 +444,7 @@ export class WebDriverAgent {
|
|
|
712
444
|
const {
|
|
713
445
|
productBundleIdentifier,
|
|
714
446
|
upgradedAt,
|
|
715
|
-
} = status.build;
|
|
447
|
+
} = status.build as any;
|
|
716
448
|
// for real device
|
|
717
449
|
if (util.hasValue(productBundleIdentifier) && util.hasValue(this.updatedWDABundleId) && this.updatedWDABundleId !== productBundleIdentifier) {
|
|
718
450
|
this.log.info(`Will uninstall running WDA since it has different bundle id. The actual value is '${productBundleIdentifier}'.`);
|
|
@@ -742,12 +474,243 @@ export class WebDriverAgent {
|
|
|
742
474
|
|
|
743
475
|
/**
|
|
744
476
|
* Quit and uninstall running WDA.
|
|
745
|
-
* @returns {Promise<void>}
|
|
746
477
|
*/
|
|
747
|
-
async quitAndUninstall () {
|
|
478
|
+
async quitAndUninstall (): Promise<void> {
|
|
748
479
|
await this.quit();
|
|
749
480
|
await this.uninstall();
|
|
750
481
|
}
|
|
751
|
-
}
|
|
752
482
|
|
|
753
|
-
|
|
483
|
+
private setWDAPaths (bootstrapPath?: string, agentPath?: string): void {
|
|
484
|
+
// allow the user to specify a place for WDA. This is undocumented and
|
|
485
|
+
// only here for the purposes of testing development of WDA
|
|
486
|
+
this.bootstrapPath = bootstrapPath || BOOTSTRAP_PATH;
|
|
487
|
+
this.log.info(`Using WDA path: '${this.bootstrapPath}'`);
|
|
488
|
+
|
|
489
|
+
// for backward compatibility we need to be able to specify agentPath too
|
|
490
|
+
this.agentPath = agentPath || path.resolve(this.bootstrapPath, 'WebDriverAgent.xcodeproj');
|
|
491
|
+
this.log.info(`Using WDA agent: '${this.agentPath}'`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private async isRunning (): Promise<boolean> {
|
|
495
|
+
return !!(await this.getStatus());
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Return current running WDA's status like below
|
|
500
|
+
* {
|
|
501
|
+
* "state": "success",
|
|
502
|
+
* "os": {
|
|
503
|
+
* "name": "iOS",
|
|
504
|
+
* "version": "11.4",
|
|
505
|
+
* "sdkVersion": "11.3"
|
|
506
|
+
* },
|
|
507
|
+
* "ios": {
|
|
508
|
+
* "simulatorVersion": "11.4",
|
|
509
|
+
* "ip": "172.254.99.34"
|
|
510
|
+
* },
|
|
511
|
+
* "build": {
|
|
512
|
+
* "time": "Jun 24 2018 17:08:21",
|
|
513
|
+
* "productBundleIdentifier": "com.facebook.WebDriverAgentRunner"
|
|
514
|
+
* }
|
|
515
|
+
* }
|
|
516
|
+
*
|
|
517
|
+
* @param timeoutMs If zero or negative, returns immediately. Otherwise, waits up to timeoutMs.
|
|
518
|
+
*/
|
|
519
|
+
private async getStatus (timeoutMs: number = 0): Promise<StringRecord | null> {
|
|
520
|
+
const noSessionProxy = new NoSessionProxy({
|
|
521
|
+
server: this.url.hostname ?? undefined,
|
|
522
|
+
port: parseInt(this.url.port ?? '', 10) || undefined,
|
|
523
|
+
base: this.basePath,
|
|
524
|
+
timeout: 3000,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const sendGetStatus = async () => await noSessionProxy.command('/status', 'GET') as StringRecord;
|
|
528
|
+
|
|
529
|
+
if (_.isNil(timeoutMs) || timeoutMs <= 0) {
|
|
530
|
+
try {
|
|
531
|
+
return await sendGetStatus();
|
|
532
|
+
} catch (err: any) {
|
|
533
|
+
this.log.debug(`WDA is not listening at '${this.url.href}'. Original error:: ${err.message}`);
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
let lastError: any = null;
|
|
539
|
+
let status: StringRecord | null = null;
|
|
540
|
+
try {
|
|
541
|
+
await waitForCondition(async () => {
|
|
542
|
+
try {
|
|
543
|
+
status = await sendGetStatus();
|
|
544
|
+
return true;
|
|
545
|
+
} catch (err) {
|
|
546
|
+
lastError = err;
|
|
547
|
+
}
|
|
548
|
+
return false;
|
|
549
|
+
}, {
|
|
550
|
+
waitMs: timeoutMs,
|
|
551
|
+
intervalMs: 300,
|
|
552
|
+
});
|
|
553
|
+
} catch (err: any) {
|
|
554
|
+
this.log.debug(`Failed to get the status endpoint in ${timeoutMs} ms. ` +
|
|
555
|
+
`The last error while accessing ${this.url.href}: ${lastError}. Original error:: ${err.message}.`);
|
|
556
|
+
throw new Error(`WDA was not ready in ${timeoutMs} ms.`);
|
|
557
|
+
}
|
|
558
|
+
return status;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Uninstall WDAs from the test device.
|
|
563
|
+
* Over Xcode 11, multiple WDA can be in the device since Xcode 11 generates different WDA.
|
|
564
|
+
* Appium does not expect multiple WDAs are running on a device.
|
|
565
|
+
*/
|
|
566
|
+
private async uninstall (): Promise<void> {
|
|
567
|
+
try {
|
|
568
|
+
const bundleIds = await this.device.getUserInstalledBundleIdsByBundleName(WDA_CF_BUNDLE_NAME);
|
|
569
|
+
if (_.isEmpty(bundleIds)) {
|
|
570
|
+
this.log.debug('No WDAs on the device.');
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
this.log.debug(`Uninstalling WDAs: '${bundleIds}'`);
|
|
575
|
+
for (const bundleId of bundleIds) {
|
|
576
|
+
await this.device.removeApp(bundleId);
|
|
577
|
+
}
|
|
578
|
+
} catch (e: any) {
|
|
579
|
+
this.log.debug(e);
|
|
580
|
+
this.log.warn(`WebDriverAgent uninstall failed. Perhaps, it is already uninstalled? ` +
|
|
581
|
+
`Original error: ${e.message}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private async _cleanupProjectIfFresh (): Promise<void> {
|
|
586
|
+
if (this.canSkipXcodebuild) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const packageInfo = JSON.parse(await fs.readFile(path.join(BOOTSTRAP_PATH, 'package.json'), 'utf8'));
|
|
591
|
+
const box = strongbox(packageInfo.name);
|
|
592
|
+
let boxItem = box.getItem(RECENT_MODULE_VERSION_ITEM_NAME);
|
|
593
|
+
if (!boxItem) {
|
|
594
|
+
const timestampPath = path.resolve(process.env.HOME ?? '', WDA_UPGRADE_TIMESTAMP_PATH);
|
|
595
|
+
if (await fs.exists(timestampPath)) {
|
|
596
|
+
// TODO: It is probably a bit ugly to hardcode the recent version string,
|
|
597
|
+
// TODO: hovewer it should do the job as a temporary transition trick
|
|
598
|
+
// TODO: to switch from a hardcoded file path to the strongbox usage.
|
|
599
|
+
try {
|
|
600
|
+
boxItem = await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, '5.0.0');
|
|
601
|
+
} catch (e: any) {
|
|
602
|
+
this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
} else {
|
|
606
|
+
this.log.info('There is no need to perform the project cleanup. A fresh install has been detected');
|
|
607
|
+
try {
|
|
608
|
+
await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, packageInfo.version);
|
|
609
|
+
} catch (e: any) {
|
|
610
|
+
this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
let recentModuleVersion = await boxItem.read();
|
|
617
|
+
try {
|
|
618
|
+
recentModuleVersion = util.coerceVersion(recentModuleVersion, true);
|
|
619
|
+
} catch (e: any) {
|
|
620
|
+
this.log.warn(`The persisted module version string has been damaged: ${e.message}`);
|
|
621
|
+
this.log.info(`Updating it to '${packageInfo.version}' assuming the project clenup is not needed`);
|
|
622
|
+
await boxItem.write(packageInfo.version);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (util.compareVersions(recentModuleVersion, '>=', packageInfo.version)) {
|
|
627
|
+
this.log.info(
|
|
628
|
+
`WebDriverAgent does not need a cleanup. The project sources are up to date ` +
|
|
629
|
+
`(${recentModuleVersion} >= ${packageInfo.version})`
|
|
630
|
+
);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
this.log.info(
|
|
635
|
+
`Cleaning up the WebDriverAgent project after the module upgrade has happened ` +
|
|
636
|
+
`(${recentModuleVersion} < ${packageInfo.version})`
|
|
637
|
+
);
|
|
638
|
+
try {
|
|
639
|
+
await this.xcodebuild.cleanProject();
|
|
640
|
+
await boxItem.write(packageInfo.version);
|
|
641
|
+
} catch (e: any) {
|
|
642
|
+
this.log.warn(`Cannot perform WebDriverAgent project cleanup. Original error: ${e.message}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Launch WDA with preinstalled package with 'xcrun devicectl device process launch'.
|
|
648
|
+
* The WDA package must be prepared properly like published via
|
|
649
|
+
* https://github.com/appium/WebDriverAgent/releases
|
|
650
|
+
* with proper sign for this case.
|
|
651
|
+
*
|
|
652
|
+
* When we implement launching XCTest service via appium-ios-device,
|
|
653
|
+
* this implementation can be replaced with it.
|
|
654
|
+
*
|
|
655
|
+
* @param opts launching WDA with devicectl command options.
|
|
656
|
+
*/
|
|
657
|
+
private async _launchViaDevicectl(opts: {env?: Record<string, string | number>} = {}): Promise<void> {
|
|
658
|
+
const {env} = opts;
|
|
659
|
+
|
|
660
|
+
await this.device.devicectl.launchApp(
|
|
661
|
+
this.bundleIdForXctest, { env, terminateExisting: true }
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Launch WDA with preinstalled package without xcodebuild.
|
|
667
|
+
* @param sessionId Launch WDA and establish the session with this sessionId
|
|
668
|
+
*/
|
|
669
|
+
private async launchWithPreinstalledWDA(sessionId: string): Promise<StringRecord | null> {
|
|
670
|
+
const xctestEnv: Record<string, string | number> = {
|
|
671
|
+
USE_PORT: this.wdaLocalPort || WDA_AGENT_PORT,
|
|
672
|
+
WDA_PRODUCT_BUNDLE_IDENTIFIER: this.bundleIdForXctest
|
|
673
|
+
};
|
|
674
|
+
if (this.mjpegServerPort) {
|
|
675
|
+
xctestEnv.MJPEG_SERVER_PORT = this.mjpegServerPort;
|
|
676
|
+
}
|
|
677
|
+
if (this.wdaBindingIP) {
|
|
678
|
+
xctestEnv.USE_IP = this.wdaBindingIP;
|
|
679
|
+
}
|
|
680
|
+
this.log.info('Launching WebDriverAgent on the device without xcodebuild');
|
|
681
|
+
if (this.isRealDevice) {
|
|
682
|
+
// Current method to launch WDA process can be done via 'xcrun devicectl',
|
|
683
|
+
// but it has limitation about the WDA preinstalled package.
|
|
684
|
+
// https://github.com/appium/appium/issues/19206#issuecomment-2014182674
|
|
685
|
+
if (this.platformVersion && util.compareVersions(this.platformVersion, '>=', '17.0')) {
|
|
686
|
+
await this._launchViaDevicectl({env: xctestEnv});
|
|
687
|
+
} else {
|
|
688
|
+
this.xctestApiClient = new Xctest(this.device.udid, this.bundleIdForXctest, null, {env: xctestEnv});
|
|
689
|
+
await this.xctestApiClient.start();
|
|
690
|
+
}
|
|
691
|
+
} else {
|
|
692
|
+
await this.device.simctl.exec('launch', {
|
|
693
|
+
args: [
|
|
694
|
+
'--terminate-running-process',
|
|
695
|
+
this.device.udid,
|
|
696
|
+
this.bundleIdForXctest,
|
|
697
|
+
],
|
|
698
|
+
env: xctestEnv,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
this.setupProxies(sessionId);
|
|
703
|
+
let status: StringRecord | null;
|
|
704
|
+
try {
|
|
705
|
+
status = await this.getStatus(this.wdaLaunchTimeout);
|
|
706
|
+
} catch {
|
|
707
|
+
throw new Error(
|
|
708
|
+
`Failed to start the preinstalled WebDriverAgent in ${this.wdaLaunchTimeout} ms. ` +
|
|
709
|
+
`The WebDriverAgent might not be properly built or the device might be locked. ` +
|
|
710
|
+
`The 'appium:wdaLaunchTimeout' capability modifies the timeout.`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
this.started = true;
|
|
714
|
+
return status;
|
|
715
|
+
}
|
|
716
|
+
}
|