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,24 +1,34 @@
|
|
|
1
1
|
import { fs, plist } from '@appium/support';
|
|
2
|
-
import { exec } from 'teen_process';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import
|
|
2
|
+
import { exec, SubProcess } from 'teen_process';
|
|
3
|
+
import path, { dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { log } from './logger';
|
|
5
6
|
import _ from 'lodash';
|
|
6
7
|
import { WDA_RUNNER_BUNDLE_ID, PLATFORM_NAME_TVOS } from './constants';
|
|
7
8
|
import B from 'bluebird';
|
|
8
|
-
import _fs from 'fs';
|
|
9
|
+
import _fs from 'node:fs';
|
|
9
10
|
import { waitForCondition } from 'asyncbox';
|
|
10
|
-
import { arch } from 'os';
|
|
11
|
+
import { arch } from 'node:os';
|
|
12
|
+
import type { DeviceInfo } from './types';
|
|
11
13
|
|
|
12
14
|
const PROJECT_FILE = 'project.pbxproj';
|
|
13
15
|
|
|
16
|
+
// Get current filename - works in both CommonJS and ESM
|
|
17
|
+
const currentFilename =
|
|
18
|
+
typeof __filename !== 'undefined'
|
|
19
|
+
? __filename
|
|
20
|
+
: fileURLToPath(new Function('return import.meta.url')());
|
|
21
|
+
|
|
22
|
+
const currentDirname = dirname(currentFilename);
|
|
23
|
+
|
|
14
24
|
/**
|
|
15
25
|
* Calculates the path to the current module's root folder
|
|
16
26
|
*
|
|
17
27
|
* @returns {string} The full path to module root
|
|
18
28
|
* @throws {Error} If the current module root folder cannot be determined
|
|
19
29
|
*/
|
|
20
|
-
const getModuleRoot = _.memoize(function getModuleRoot () {
|
|
21
|
-
let currentDir =
|
|
30
|
+
const getModuleRoot = _.memoize(function getModuleRoot (): string {
|
|
31
|
+
let currentDir = currentDirname;
|
|
22
32
|
let isAtFsRoot = false;
|
|
23
33
|
while (!isAtFsRoot) {
|
|
24
34
|
const manifestPath = path.join(currentDir, 'package.json');
|
|
@@ -36,24 +46,7 @@ const getModuleRoot = _.memoize(function getModuleRoot () {
|
|
|
36
46
|
|
|
37
47
|
export const BOOTSTRAP_PATH = getModuleRoot();
|
|
38
48
|
|
|
39
|
-
async function
|
|
40
|
-
const args = [
|
|
41
|
-
'-if', // case insensitive, full cmdline match
|
|
42
|
-
pattern,
|
|
43
|
-
];
|
|
44
|
-
try {
|
|
45
|
-
const {stdout} = await exec('pgrep', args);
|
|
46
|
-
return stdout.split(/\s+/)
|
|
47
|
-
.map((x) => parseInt(x, 10))
|
|
48
|
-
.filter(_.isInteger)
|
|
49
|
-
.map((x) => `${x}`);
|
|
50
|
-
} catch (err) {
|
|
51
|
-
log.debug(`'pgrep ${args.join(' ')}' didn't detect any matching processes. Return code: ${err.code}`);
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function killAppUsingPattern (pgrepPattern) {
|
|
49
|
+
export async function killAppUsingPattern (pgrepPattern: string): Promise<void> {
|
|
57
50
|
const signals = [2, 15, 9];
|
|
58
51
|
for (const signal of signals) {
|
|
59
52
|
const matchedPids = await getPIDsUsingPattern(pgrepPattern);
|
|
@@ -63,7 +56,7 @@ async function killAppUsingPattern (pgrepPattern) {
|
|
|
63
56
|
const args = [`-${signal}`, ...matchedPids];
|
|
64
57
|
try {
|
|
65
58
|
await exec('kill', args);
|
|
66
|
-
} catch (err) {
|
|
59
|
+
} catch (err: any) {
|
|
67
60
|
log.debug(`kill ${args.join(' ')} -> ${err.message}`);
|
|
68
61
|
}
|
|
69
62
|
if (signal === _.last(signals)) {
|
|
@@ -94,36 +87,27 @@ async function killAppUsingPattern (pgrepPattern) {
|
|
|
94
87
|
|
|
95
88
|
/**
|
|
96
89
|
* Return true if the platformName is tvOS
|
|
97
|
-
* @param
|
|
98
|
-
* @returns
|
|
90
|
+
* @param platformName The name of the platorm
|
|
91
|
+
* @returns Return true if the platformName is tvOS
|
|
99
92
|
*/
|
|
100
|
-
function isTvOS (platformName) {
|
|
93
|
+
export function isTvOS (platformName: string): boolean {
|
|
101
94
|
return _.toLower(platformName) === _.toLower(PLATFORM_NAME_TVOS);
|
|
102
95
|
}
|
|
103
96
|
|
|
104
|
-
async function replaceInFile (file, find, replace) {
|
|
105
|
-
let contents = await fs.readFile(file, 'utf8');
|
|
106
|
-
|
|
107
|
-
let newContents = contents.replace(find, replace);
|
|
108
|
-
if (newContents !== contents) {
|
|
109
|
-
await fs.writeFile(file, newContents, 'utf8');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
97
|
/**
|
|
114
98
|
* Update WebDriverAgentRunner project bundle ID with newBundleId.
|
|
115
99
|
* This method assumes project file is in the correct state.
|
|
116
|
-
* @param
|
|
117
|
-
* @param
|
|
100
|
+
* @param agentPath - Path to the .xcodeproj directory.
|
|
101
|
+
* @param newBundleId the new bundle ID used to update.
|
|
118
102
|
*/
|
|
119
|
-
async function updateProjectFile (agentPath, newBundleId) {
|
|
120
|
-
|
|
103
|
+
export async function updateProjectFile (agentPath: string, newBundleId: string): Promise<void> {
|
|
104
|
+
const projectFilePath = path.resolve(agentPath, PROJECT_FILE);
|
|
121
105
|
try {
|
|
122
106
|
// Assuming projectFilePath is in the correct state, create .old from projectFilePath
|
|
123
107
|
await fs.copyFile(projectFilePath, `${projectFilePath}.old`);
|
|
124
108
|
await replaceInFile(projectFilePath, new RegExp(_.escapeRegExp(WDA_RUNNER_BUNDLE_ID), 'g'), newBundleId);
|
|
125
109
|
log.debug(`Successfully updated '${projectFilePath}' with bundle id '${newBundleId}'`);
|
|
126
|
-
} catch (err) {
|
|
110
|
+
} catch (err: any) {
|
|
127
111
|
log.debug(`Error updating project file: ${err.message}`);
|
|
128
112
|
log.warn(`Unable to update project file '${projectFilePath}' with ` +
|
|
129
113
|
`bundle id '${newBundleId}'. WebDriverAgent may not start`);
|
|
@@ -132,9 +116,9 @@ async function updateProjectFile (agentPath, newBundleId) {
|
|
|
132
116
|
|
|
133
117
|
/**
|
|
134
118
|
* Reset WebDriverAgentRunner project bundle ID to correct state.
|
|
135
|
-
* @param
|
|
119
|
+
* @param agentPath - Path to the .xcodeproj directory.
|
|
136
120
|
*/
|
|
137
|
-
async function resetProjectFile (agentPath) {
|
|
121
|
+
export async function resetProjectFile (agentPath: string): Promise<void> {
|
|
138
122
|
const projectFilePath = path.join(agentPath, PROJECT_FILE);
|
|
139
123
|
try {
|
|
140
124
|
// restore projectFilePath from .old file
|
|
@@ -143,7 +127,7 @@ async function resetProjectFile (agentPath) {
|
|
|
143
127
|
}
|
|
144
128
|
await fs.mv(`${projectFilePath}.old`, projectFilePath);
|
|
145
129
|
log.debug(`Successfully reset '${projectFilePath}' with bundle id '${WDA_RUNNER_BUNDLE_ID}'`);
|
|
146
|
-
} catch (err) {
|
|
130
|
+
} catch (err: any) {
|
|
147
131
|
log.debug(`Error resetting project file: ${err.message}`);
|
|
148
132
|
log.warn(`Unable to reset project file '${projectFilePath}' with ` +
|
|
149
133
|
`bundle id '${WDA_RUNNER_BUNDLE_ID}'. WebDriverAgent has been ` +
|
|
@@ -151,27 +135,24 @@ async function resetProjectFile (agentPath) {
|
|
|
151
135
|
}
|
|
152
136
|
}
|
|
153
137
|
|
|
154
|
-
async function setRealDeviceSecurity (keychainPath, keychainPassword) {
|
|
138
|
+
export async function setRealDeviceSecurity (keychainPath: string, keychainPassword: string): Promise<void> {
|
|
155
139
|
log.debug('Setting security for iOS device');
|
|
156
140
|
await exec('security', ['-v', 'list-keychains', '-s', keychainPath]);
|
|
157
141
|
await exec('security', ['-v', 'unlock-keychain', '-p', keychainPassword, keychainPath]);
|
|
158
142
|
await exec('security', ['set-keychain-settings', '-t', '3600', '-l', keychainPath]);
|
|
159
143
|
}
|
|
160
144
|
|
|
161
|
-
/**
|
|
162
|
-
* Information of the device under test
|
|
163
|
-
* @typedef {import('./types').DeviceInfo} DeviceInfo
|
|
164
|
-
*/
|
|
165
|
-
|
|
166
145
|
/**
|
|
167
146
|
* Arguments for setting xctestrun file
|
|
168
|
-
* @typedef {Object} XctestrunFileArgs
|
|
169
|
-
* @property {DeviceInfo} deviceInfo - Information of the device under test
|
|
170
|
-
* @property {string} sdkVersion - The Xcode SDK version of OS.
|
|
171
|
-
* @property {string} bootstrapPath - The folder path containing xctestrun file.
|
|
172
|
-
* @property {number|string} wdaRemotePort - The remote port WDA is listening on.
|
|
173
|
-
* @property {string} [wdaBindingIP] - The IP address to bind to. If not given, it binds to all interfaces.
|
|
174
147
|
*/
|
|
148
|
+
export interface XctestrunFileArgs {
|
|
149
|
+
deviceInfo: DeviceInfo;
|
|
150
|
+
sdkVersion: string;
|
|
151
|
+
bootstrapPath: string;
|
|
152
|
+
wdaRemotePort: number | string;
|
|
153
|
+
wdaBindingIP?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
175
156
|
/**
|
|
176
157
|
* Creates xctestrun file per device & platform version.
|
|
177
158
|
* We expects to have WebDriverAgentRunner_iphoneos${sdkVersion|platformVersion}-arm64.xctestrun for real device
|
|
@@ -180,13 +161,13 @@ async function setRealDeviceSecurity (keychainPath, keychainPassword) {
|
|
|
180
161
|
* e.g. Xcode which has iOS SDK Version 12.2 on an intel Mac host machine generates WebDriverAgentRunner_iphonesimulator.2-x86_64.xctestrun
|
|
181
162
|
* even if the cap has platform version 11.4
|
|
182
163
|
*
|
|
183
|
-
* @param
|
|
184
|
-
* @return
|
|
164
|
+
* @param args
|
|
165
|
+
* @return returns xctestrunFilePath for given device
|
|
185
166
|
* @throws if WebDriverAgentRunner_iphoneos${sdkVersion|platformVersion}-arm64.xctestrun for real device
|
|
186
167
|
* or WebDriverAgentRunner_iphonesimulator${sdkVersion|platformVersion}-x86_64.xctestrun for simulator is not found @bootstrapPath,
|
|
187
168
|
* then it will throw a file not found exception
|
|
188
169
|
*/
|
|
189
|
-
async function setXctestrunFile (args) {
|
|
170
|
+
export async function setXctestrunFile (args: XctestrunFileArgs): Promise<string> {
|
|
190
171
|
const {deviceInfo, sdkVersion, bootstrapPath, wdaRemotePort, wdaBindingIP} = args;
|
|
191
172
|
const xctestrunFilePath = await getXctestrunFilePath(deviceInfo, sdkVersion, bootstrapPath);
|
|
192
173
|
const xctestRunContent = await plist.parsePlistFile(xctestrunFilePath);
|
|
@@ -199,12 +180,12 @@ async function setXctestrunFile (args) {
|
|
|
199
180
|
|
|
200
181
|
/**
|
|
201
182
|
* Return the WDA object which appends existing xctest runner content
|
|
202
|
-
* @param
|
|
203
|
-
* @param
|
|
204
|
-
* @param
|
|
205
|
-
* @return
|
|
183
|
+
* @param platformName - The name of the platform
|
|
184
|
+
* @param wdaRemotePort - The remote port number
|
|
185
|
+
* @param wdaBindingIP - The IP address to bind to. If not given, it binds to all interfaces.
|
|
186
|
+
* @return returns a runner object which has USE_PORT and optionally USE_IP
|
|
206
187
|
*/
|
|
207
|
-
function getAdditionalRunContent (platformName, wdaRemotePort, wdaBindingIP) {
|
|
188
|
+
export function getAdditionalRunContent (platformName: string, wdaRemotePort: number | string, wdaBindingIP?: string): Record<string, any> {
|
|
208
189
|
const runner = `WebDriverAgentRunner${isTvOS(platformName) ? '_tvOS' : ''}`;
|
|
209
190
|
return {
|
|
210
191
|
[runner]: {
|
|
@@ -219,19 +200,18 @@ function getAdditionalRunContent (platformName, wdaRemotePort, wdaBindingIP) {
|
|
|
219
200
|
|
|
220
201
|
/**
|
|
221
202
|
* Return the path of xctestrun if it exists
|
|
222
|
-
* @param
|
|
223
|
-
* @param
|
|
224
|
-
* @param
|
|
225
|
-
* @returns {Promise<string>}
|
|
203
|
+
* @param deviceInfo
|
|
204
|
+
* @param sdkVersion - The Xcode SDK version of OS.
|
|
205
|
+
* @param bootstrapPath - The folder path containing xctestrun file.
|
|
226
206
|
*/
|
|
227
|
-
async function getXctestrunFilePath (deviceInfo, sdkVersion, bootstrapPath) {
|
|
207
|
+
export async function getXctestrunFilePath (deviceInfo: DeviceInfo, sdkVersion: string, bootstrapPath: string): Promise<string> {
|
|
228
208
|
// First try the SDK path, for Xcode 10 (at least)
|
|
229
|
-
const sdkBased = [
|
|
209
|
+
const sdkBased: [string, string] = [
|
|
230
210
|
path.resolve(bootstrapPath, `${deviceInfo.udid}_${sdkVersion}.xctestrun`),
|
|
231
211
|
sdkVersion,
|
|
232
212
|
];
|
|
233
213
|
// Next try Platform path, for earlier Xcode versions
|
|
234
|
-
const platformBased = [
|
|
214
|
+
const platformBased: [string, string] = [
|
|
235
215
|
path.resolve(bootstrapPath, `${deviceInfo.udid}_${deviceInfo.platformVersion}.xctestrun`),
|
|
236
216
|
deviceInfo.platformVersion,
|
|
237
217
|
];
|
|
@@ -261,11 +241,11 @@ async function getXctestrunFilePath (deviceInfo, sdkVersion, bootstrapPath) {
|
|
|
261
241
|
|
|
262
242
|
/**
|
|
263
243
|
* Return the name of xctestrun file
|
|
264
|
-
* @param
|
|
265
|
-
* @param
|
|
266
|
-
* @return
|
|
244
|
+
* @param deviceInfo
|
|
245
|
+
* @param version - The Xcode SDK version of OS.
|
|
246
|
+
* @return returns xctestrunFilePath for given device
|
|
267
247
|
*/
|
|
268
|
-
function getXctestrunFileName (deviceInfo, version) {
|
|
248
|
+
export function getXctestrunFileName (deviceInfo: DeviceInfo, version: string): string {
|
|
269
249
|
const archSuffix = deviceInfo.isRealDevice
|
|
270
250
|
? `os${version}-arm64`
|
|
271
251
|
: `simulator${version}-${arch() === 'arm64' ? 'arm64' : 'x86_64'}`;
|
|
@@ -274,12 +254,8 @@ function getXctestrunFileName (deviceInfo, version) {
|
|
|
274
254
|
|
|
275
255
|
/**
|
|
276
256
|
* Ensures the process is killed after the timeout
|
|
277
|
-
*
|
|
278
|
-
* @param {string} name
|
|
279
|
-
* @param {import('teen_process').SubProcess} proc
|
|
280
|
-
* @returns {Promise<void>}
|
|
281
257
|
*/
|
|
282
|
-
async function killProcess (name, proc) {
|
|
258
|
+
export async function killProcess (name: string, proc: SubProcess | null | undefined): Promise<void> {
|
|
283
259
|
if (!proc || !proc.isRunning) {
|
|
284
260
|
return;
|
|
285
261
|
}
|
|
@@ -290,7 +266,7 @@ async function killProcess (name, proc) {
|
|
|
290
266
|
try {
|
|
291
267
|
await proc.stop('SIGTERM', 1000);
|
|
292
268
|
return;
|
|
293
|
-
} catch (err) {
|
|
269
|
+
} catch (err: any) {
|
|
294
270
|
if (!err.message.includes(`Process didn't end after`)) {
|
|
295
271
|
throw err;
|
|
296
272
|
}
|
|
@@ -300,7 +276,7 @@ async function killProcess (name, proc) {
|
|
|
300
276
|
log.info(`Sending 'SIGKILL'...`);
|
|
301
277
|
try {
|
|
302
278
|
await proc.stop('SIGKILL');
|
|
303
|
-
} catch (err) {
|
|
279
|
+
} catch (err: any) {
|
|
304
280
|
if (err.message.includes('not currently running')) {
|
|
305
281
|
// the process ended but for some reason we were not informed
|
|
306
282
|
return;
|
|
@@ -310,21 +286,16 @@ async function killProcess (name, proc) {
|
|
|
310
286
|
}
|
|
311
287
|
|
|
312
288
|
/**
|
|
313
|
-
* Generate a random integer.
|
|
314
|
-
*
|
|
315
|
-
* @return {number} A random integer number in range [low, hight). `low`` is inclusive and `high` is exclusive.
|
|
289
|
+
* Generate a random integer in range [low, high). `low` is inclusive and `high` is exclusive.
|
|
316
290
|
*/
|
|
317
|
-
function randomInt (low, high) {
|
|
291
|
+
export function randomInt (low: number, high: number): number {
|
|
318
292
|
return Math.floor(Math.random() * (high - low) + low);
|
|
319
293
|
}
|
|
320
294
|
|
|
321
295
|
/**
|
|
322
|
-
* Retrieves WDA upgrade timestamp
|
|
323
|
-
*
|
|
324
|
-
* @return {Promise<number?>} The UNIX timestamp of the package manifest. The manifest only gets modified on
|
|
325
|
-
* package upgrade.
|
|
296
|
+
* Retrieves WDA upgrade timestamp. The manifest only gets modified on package upgrade.
|
|
326
297
|
*/
|
|
327
|
-
async function getWDAUpgradeTimestamp () {
|
|
298
|
+
export async function getWDAUpgradeTimestamp (): Promise<number | null> {
|
|
328
299
|
const packageManifest = path.resolve(getModuleRoot(), 'package.json');
|
|
329
300
|
if (!await fs.exists(packageManifest)) {
|
|
330
301
|
return null;
|
|
@@ -335,11 +306,8 @@ async function getWDAUpgradeTimestamp () {
|
|
|
335
306
|
|
|
336
307
|
/**
|
|
337
308
|
* Kills running XCTest processes for the particular device.
|
|
338
|
-
*
|
|
339
|
-
* @param {string} udid - The device UDID.
|
|
340
|
-
* @param {boolean} isSimulator - Equals to true if the current device is a Simulator
|
|
341
309
|
*/
|
|
342
|
-
async function resetTestProcesses (udid, isSimulator) {
|
|
310
|
+
export async function resetTestProcesses (udid: string, isSimulator: boolean): Promise<void> {
|
|
343
311
|
const processPatterns = [`xcodebuild.*${udid}`];
|
|
344
312
|
if (isSimulator) {
|
|
345
313
|
processPatterns.push(`${udid}.*XCTRunner`);
|
|
@@ -355,21 +323,21 @@ async function resetTestProcesses (udid, isSimulator) {
|
|
|
355
323
|
* It is also possible to apply additional filtering based on the
|
|
356
324
|
* process command line.
|
|
357
325
|
*
|
|
358
|
-
* @param
|
|
359
|
-
* @param
|
|
326
|
+
* @param port - The port number.
|
|
327
|
+
* @param filteringFunc - Optional lambda function, which
|
|
360
328
|
* receives command line string of the particular process
|
|
361
329
|
* listening on given port, and is expected to return
|
|
362
330
|
* either true or false to include/exclude the corresponding PID
|
|
363
331
|
* from the resulting array.
|
|
364
|
-
* @returns
|
|
332
|
+
* @returns - the list of matched process ids.
|
|
365
333
|
*/
|
|
366
|
-
async function getPIDsListeningOnPort (port, filteringFunc = null) {
|
|
367
|
-
const result = [];
|
|
334
|
+
export async function getPIDsListeningOnPort (port: string | number, filteringFunc: ((cmdline: string) => boolean | Promise<boolean>) | null = null): Promise<string[]> {
|
|
335
|
+
const result: string[] = [];
|
|
368
336
|
try {
|
|
369
337
|
// This only works since Mac OS X El Capitan
|
|
370
338
|
const {stdout} = await exec('lsof', ['-ti', `tcp:${port}`]);
|
|
371
339
|
result.push(...(stdout.trim().split(/\n+/)));
|
|
372
|
-
} catch (e) {
|
|
340
|
+
} catch (e: any) {
|
|
373
341
|
if (e.code !== 1) {
|
|
374
342
|
// code 1 means no processes. Other errors need reporting
|
|
375
343
|
log.debug(`Error getting processes listening on port '${port}': ${e.stderr || e.message}`);
|
|
@@ -381,10 +349,10 @@ async function getPIDsListeningOnPort (port, filteringFunc = null) {
|
|
|
381
349
|
return result;
|
|
382
350
|
}
|
|
383
351
|
return await B.filter(result, async (pid) => {
|
|
384
|
-
let stdout;
|
|
352
|
+
let stdout: string;
|
|
385
353
|
try {
|
|
386
354
|
({stdout} = await exec('ps', ['-p', pid, '-o', 'command']));
|
|
387
|
-
} catch (e) {
|
|
355
|
+
} catch (e: any) {
|
|
388
356
|
if (e.code === 1) {
|
|
389
357
|
// The process does not exist anymore, there's nothing to filter
|
|
390
358
|
return false;
|
|
@@ -395,9 +363,31 @@ async function getPIDsListeningOnPort (port, filteringFunc = null) {
|
|
|
395
363
|
});
|
|
396
364
|
}
|
|
397
365
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
366
|
+
// Private functions
|
|
367
|
+
|
|
368
|
+
async function getPIDsUsingPattern (pattern: string): Promise<string[]> {
|
|
369
|
+
const args = [
|
|
370
|
+
'-if', // case insensitive, full cmdline match
|
|
371
|
+
pattern,
|
|
372
|
+
];
|
|
373
|
+
try {
|
|
374
|
+
const {stdout} = await exec('pgrep', args);
|
|
375
|
+
return stdout.split(/\s+/)
|
|
376
|
+
.map((x) => parseInt(x, 10))
|
|
377
|
+
.filter(_.isInteger)
|
|
378
|
+
.map((x) => `${x}`);
|
|
379
|
+
} catch (err: any) {
|
|
380
|
+
log.debug(`'pgrep ${args.join(' ')}' didn't detect any matching processes. Return code: ${err.code}`);
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function replaceInFile (file: string, find: string | RegExp, replace: string): Promise<void> {
|
|
386
|
+
const contents = await fs.readFile(file, 'utf8');
|
|
387
|
+
|
|
388
|
+
const newContents = contents.replace(find, replace);
|
|
389
|
+
if (newContents !== contents) {
|
|
390
|
+
await fs.writeFile(file, newContents, 'utf8');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|