appium-xcuitest-driver 11.12.2 → 11.13.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 +6 -0
- package/npm-shrinkwrap.json +29 -17
- package/package.json +3 -3
- package/scripts/lib/options.mjs +12 -0
- package/scripts/lib/progress.mjs +44 -0
- package/scripts/lib/root.mjs +15 -0
- package/scripts/pair-appletv.mjs +86 -13
- package/scripts/tunnel-creation.mjs +30 -80
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [11.13.0](https://github.com/appium/appium-xcuitest-driver/compare/v11.12.2...v11.13.0) (2026-06-20)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* Make Apple TV discovery timeout configurable ([#2882](https://github.com/appium/appium-xcuitest-driver/issues/2882)) ([e9e006b](https://github.com/appium/appium-xcuitest-driver/commit/e9e006b5de99930e41602dc5e540ad0d3a375244))
|
|
6
|
+
|
|
1
7
|
## [11.12.2](https://github.com/appium/appium-xcuitest-driver/compare/v11.12.1...v11.12.2) (2026-06-19)
|
|
2
8
|
|
|
3
9
|
### Bug Fixes
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-xcuitest-driver",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.13.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "appium-xcuitest-driver",
|
|
9
|
-
"version": "11.
|
|
9
|
+
"version": "11.13.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@appium/css-locator-to-native": "^1.0.1",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@semantic-release/changelog": "^6.0.3",
|
|
42
42
|
"@semantic-release/git": "^10.0.1",
|
|
43
43
|
"@types/mocha": "^10.0.1",
|
|
44
|
-
"@types/node": "^
|
|
44
|
+
"@types/node": "^26.0.0",
|
|
45
45
|
"@types/portscanner": "^2.1.1",
|
|
46
46
|
"chai": "^6.0.0",
|
|
47
47
|
"chai-as-promised": "^8.0.0",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"npm": ">=10"
|
|
64
64
|
},
|
|
65
65
|
"optionalDependencies": {
|
|
66
|
-
"appium-ios-remotexpc": "^5.1.
|
|
66
|
+
"appium-ios-remotexpc": "^5.1.5"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"appium": "^3.0.0-rc.2"
|
|
@@ -288,6 +288,18 @@
|
|
|
288
288
|
"url": "https://opencollective.com/libvips"
|
|
289
289
|
}
|
|
290
290
|
},
|
|
291
|
+
"node_modules/@appium/support/node_modules/semver": {
|
|
292
|
+
"version": "7.8.4",
|
|
293
|
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
|
|
294
|
+
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
|
|
295
|
+
"license": "ISC",
|
|
296
|
+
"bin": {
|
|
297
|
+
"semver": "bin/semver.js"
|
|
298
|
+
},
|
|
299
|
+
"engines": {
|
|
300
|
+
"node": ">=10"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
291
303
|
"node_modules/@appium/support/node_modules/sharp": {
|
|
292
304
|
"version": "0.35.1",
|
|
293
305
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.1.tgz",
|
|
@@ -513,13 +525,13 @@
|
|
|
513
525
|
}
|
|
514
526
|
},
|
|
515
527
|
"node_modules/appium-ios-remotexpc": {
|
|
516
|
-
"version": "5.1.
|
|
517
|
-
"resolved": "https://registry.npmjs.org/appium-ios-remotexpc/-/appium-ios-remotexpc-5.1.
|
|
518
|
-
"integrity": "sha512
|
|
528
|
+
"version": "5.1.6",
|
|
529
|
+
"resolved": "https://registry.npmjs.org/appium-ios-remotexpc/-/appium-ios-remotexpc-5.1.6.tgz",
|
|
530
|
+
"integrity": "sha512-PNuFaoL5X0bL04RRNqb6EmEtUYRsd6rmMHm9biHmvtBWPeq7WIBdlFkB5Z5qOyiOjb0TXE9WdvGwzw47QcFUJw==",
|
|
519
531
|
"license": "Apache-2.0",
|
|
520
532
|
"optional": true,
|
|
521
533
|
"dependencies": {
|
|
522
|
-
"@appium/strongbox": "^1.
|
|
534
|
+
"@appium/strongbox": "^1.1.2",
|
|
523
535
|
"@appium/support": "^7.2.2",
|
|
524
536
|
"@xmldom/xmldom": "^0.x",
|
|
525
537
|
"appium-ios-tuntap": "^1.0.0",
|
|
@@ -537,9 +549,9 @@
|
|
|
537
549
|
}
|
|
538
550
|
},
|
|
539
551
|
"node_modules/appium-ios-simulator": {
|
|
540
|
-
"version": "8.2.
|
|
541
|
-
"resolved": "https://registry.npmjs.org/appium-ios-simulator/-/appium-ios-simulator-8.2.
|
|
542
|
-
"integrity": "sha512-
|
|
552
|
+
"version": "8.2.3",
|
|
553
|
+
"resolved": "https://registry.npmjs.org/appium-ios-simulator/-/appium-ios-simulator-8.2.3.tgz",
|
|
554
|
+
"integrity": "sha512-naM9JLNT7uZv+IVCMNMtifgiRbiNvRZ0e1SHPo2xPa4dF0vKlqSDdA7WVFguh3V+Sfty3L6HCVY6VRdbyQ+2Ew==",
|
|
543
555
|
"license": "Apache-2.0",
|
|
544
556
|
"dependencies": {
|
|
545
557
|
"@appium/support": "^7.2.2",
|
|
@@ -597,9 +609,9 @@
|
|
|
597
609
|
}
|
|
598
610
|
},
|
|
599
611
|
"node_modules/appium-webdriveragent": {
|
|
600
|
-
"version": "14.2.
|
|
601
|
-
"resolved": "https://registry.npmjs.org/appium-webdriveragent/-/appium-webdriveragent-14.2.
|
|
602
|
-
"integrity": "sha512-
|
|
612
|
+
"version": "14.2.1",
|
|
613
|
+
"resolved": "https://registry.npmjs.org/appium-webdriveragent/-/appium-webdriveragent-14.2.1.tgz",
|
|
614
|
+
"integrity": "sha512-iHFi+ViRzBOeIjSZZ+R94lVUdGCHBplA8GmDhNxd/0PGh0IfuAlfvz8n3/7AvApebpgjMArKcowGXILo0SwE1A==",
|
|
603
615
|
"license": "Apache-2.0",
|
|
604
616
|
"dependencies": {
|
|
605
617
|
"@appium/base-driver": "^10.3.0",
|
|
@@ -3063,9 +3075,9 @@
|
|
|
3063
3075
|
"optional": true
|
|
3064
3076
|
},
|
|
3065
3077
|
"node_modules/semver": {
|
|
3066
|
-
"version": "7.8.
|
|
3067
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.
|
|
3068
|
-
"integrity": "sha512-
|
|
3078
|
+
"version": "7.8.5",
|
|
3079
|
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
|
3080
|
+
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
|
3069
3081
|
"license": "ISC",
|
|
3070
3082
|
"bin": {
|
|
3071
3083
|
"semver": "bin/semver.js"
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"xcuitest",
|
|
9
9
|
"xctest"
|
|
10
10
|
],
|
|
11
|
-
"version": "11.
|
|
11
|
+
"version": "11.13.0",
|
|
12
12
|
"author": "Appium Contributors",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"repository": {
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"ws": "^8.13.0"
|
|
108
108
|
},
|
|
109
109
|
"optionalDependencies": {
|
|
110
|
-
"appium-ios-remotexpc": "^5.1.
|
|
110
|
+
"appium-ios-remotexpc": "^5.1.5"
|
|
111
111
|
},
|
|
112
112
|
"scripts": {
|
|
113
113
|
"build": "tsc -b",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"@semantic-release/changelog": "^6.0.3",
|
|
152
152
|
"@semantic-release/git": "^10.0.1",
|
|
153
153
|
"@types/mocha": "^10.0.1",
|
|
154
|
-
"@types/node": "^
|
|
154
|
+
"@types/node": "^26.0.0",
|
|
155
155
|
"@types/portscanner": "^2.1.1",
|
|
156
156
|
"chai": "^6.0.0",
|
|
157
157
|
"chai-as-promised": "^8.0.0",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} value
|
|
3
|
+
* @param {string} label
|
|
4
|
+
* @returns {number}
|
|
5
|
+
*/
|
|
6
|
+
export function parsePositiveIntegerOption(value, label) {
|
|
7
|
+
const num = Number.parseInt(value, 10);
|
|
8
|
+
if (!Number.isFinite(num) || num <= 0) {
|
|
9
|
+
throw new Error(`Invalid ${label}: ${value}. Expected a positive integer.`);
|
|
10
|
+
}
|
|
11
|
+
return num;
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logs a timeout-based progress bar while an operation without native progress callbacks is running.
|
|
3
|
+
*
|
|
4
|
+
* @param {{log: {info: (message: string) => void}, label: string, startedAt: number, timeoutMs: number, barWidth: number, intervalMs: number}} opts
|
|
5
|
+
* @returns {{succeed: (message?: string) => void, fail: (message?: string) => void}}
|
|
6
|
+
*/
|
|
7
|
+
export function startTimeoutProgressLogger({log, label, startedAt, timeoutMs, barWidth, intervalMs}) {
|
|
8
|
+
/** @type {NodeJS.Timeout | null} */
|
|
9
|
+
let timer = null;
|
|
10
|
+
let isStopped = false;
|
|
11
|
+
|
|
12
|
+
const logProgress = (status, isComplete = false) => {
|
|
13
|
+
const elapsedMs = performance.now() - startedAt;
|
|
14
|
+
const boundedElapsedMs = Math.min(elapsedMs, timeoutMs);
|
|
15
|
+
const progress = isComplete ? 1 : boundedElapsedMs / timeoutMs;
|
|
16
|
+
const filledWidth = Math.round(progress * barWidth);
|
|
17
|
+
const emptyWidth = barWidth - filledWidth;
|
|
18
|
+
const bar = `${'#'.repeat(filledWidth)}${'-'.repeat(emptyWidth)}`;
|
|
19
|
+
log.info(`${label}: [${bar}]${status && status !== 'waiting' ? ` - ${status}` : ''}`);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const stop = (status, isComplete = false) => {
|
|
23
|
+
if (isStopped) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
isStopped = true;
|
|
27
|
+
if (timer) {
|
|
28
|
+
clearInterval(timer);
|
|
29
|
+
timer = null;
|
|
30
|
+
}
|
|
31
|
+
logProgress(status, isComplete);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
logProgress('waiting');
|
|
35
|
+
timer = setInterval(() => {
|
|
36
|
+
logProgress('waiting');
|
|
37
|
+
}, intervalMs);
|
|
38
|
+
timer.unref?.();
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
succeed: (message = 'done') => stop(message, true),
|
|
42
|
+
fail: (message = 'failed') => stop(message),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensures a driver helper script is run with elevated privileges on platforms that expose getuid.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} scriptName
|
|
5
|
+
*/
|
|
6
|
+
export function assertRoot(scriptName) {
|
|
7
|
+
if (typeof process.getuid !== 'function') {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (process.getuid() !== 0) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`This script must be run as root (e.g. sudo appium driver run xcuitest ${scriptName} ...).`,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/scripts/pair-appletv.mjs
CHANGED
|
@@ -16,27 +16,26 @@
|
|
|
16
16
|
* - Device name (e.g. "Living Room")
|
|
17
17
|
* - Device identifier (e.g. "AA:BB:CC:DD:EE:FF")
|
|
18
18
|
* - Device index (e.g. "0", "1", "2")
|
|
19
|
+
* --discovery-timeout-ms <ms>
|
|
20
|
+
* Apple TV pairing discovery timeout in milliseconds
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
23
|
import {logger} from 'appium/support.js';
|
|
22
24
|
import {Command} from 'commander';
|
|
23
25
|
import {AppleTVPairingService, UserInputService} from 'appium-ios-remotexpc';
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
import {parsePositiveIntegerOption} from './lib/options.mjs';
|
|
28
|
+
import {startTimeoutProgressLogger} from './lib/progress.mjs';
|
|
29
|
+
import {assertRoot} from './lib/root.mjs';
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
throw new Error(
|
|
33
|
-
'This script must be run as root (e.g. sudo appium driver run xcuitest pair-appletv ...).',
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
31
|
+
const log = logger.getLogger('AppleTVPairing');
|
|
32
|
+
const APPLETV_PAIRING_DISCOVERY_PROGRESS_INTERVAL_MS = 1000;
|
|
33
|
+
const DEFAULT_APPLETV_PAIRING_DISCOVERY_TIMEOUT_MS =
|
|
34
|
+
Number(process.env.APPLETV_DISCOVERY_TIMEOUT) || 10_000;
|
|
35
|
+
const APPLETV_PAIRING_DISCOVERY_PROGRESS_BAR_WIDTH = 24;
|
|
37
36
|
|
|
38
37
|
async function main() {
|
|
39
|
-
assertRoot();
|
|
38
|
+
assertRoot('pair-appletv');
|
|
40
39
|
const program = new Command();
|
|
41
40
|
program
|
|
42
41
|
.name('appium driver run xcuitest pair-appletv')
|
|
@@ -44,6 +43,12 @@ async function main() {
|
|
|
44
43
|
.option(
|
|
45
44
|
'-d, --device <selector>',
|
|
46
45
|
'Apple TV device selector (name, identifier, or index)',
|
|
46
|
+
)
|
|
47
|
+
.option(
|
|
48
|
+
'--discovery-timeout-ms <ms>',
|
|
49
|
+
'Apple TV pairing discovery timeout in milliseconds',
|
|
50
|
+
(value) => parsePositiveIntegerOption(value, 'discovery timeout'),
|
|
51
|
+
DEFAULT_APPLETV_PAIRING_DISCOVERY_TIMEOUT_MS,
|
|
47
52
|
);
|
|
48
53
|
|
|
49
54
|
program.parse(process.argv);
|
|
@@ -52,9 +57,25 @@ async function main() {
|
|
|
52
57
|
const userInput = new UserInputService();
|
|
53
58
|
const pairingService = new AppleTVPairingService(userInput);
|
|
54
59
|
|
|
55
|
-
const
|
|
60
|
+
const devices = await discoverAppleTVPairingDevices(
|
|
61
|
+
pairingService,
|
|
62
|
+
options.discoveryTimeoutMs,
|
|
63
|
+
);
|
|
64
|
+
if (devices.length === 0) {
|
|
65
|
+
log.info(getNoAppleTVPairingDevicesMessage());
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = await pairingService.discoverAndPair(options.device, {
|
|
70
|
+
devices,
|
|
71
|
+
discoveryTimeoutMs: options.discoveryTimeoutMs,
|
|
72
|
+
});
|
|
56
73
|
|
|
57
74
|
if (!result.success) {
|
|
75
|
+
if (isNoAppleTVPairingDevicesFoundError(result.error)) {
|
|
76
|
+
log.info(getNoAppleTVPairingDevicesMessage());
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
58
79
|
throw result.error ?? new Error('Pairing failed');
|
|
59
80
|
}
|
|
60
81
|
|
|
@@ -64,5 +85,57 @@ async function main() {
|
|
|
64
85
|
);
|
|
65
86
|
}
|
|
66
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @param {import('appium-ios-remotexpc').AppleTVPairingService} pairingService
|
|
90
|
+
* @param {number} discoveryTimeoutMs
|
|
91
|
+
* @returns {Promise<AppleTVDevice[]>}
|
|
92
|
+
*/
|
|
93
|
+
async function discoverAppleTVPairingDevices(pairingService, discoveryTimeoutMs) {
|
|
94
|
+
const startedAt = performance.now();
|
|
95
|
+
const pairingDiscoveryProgress = startTimeoutProgressLogger({
|
|
96
|
+
log,
|
|
97
|
+
label: 'Waiting for Apple TV pairing discovery',
|
|
98
|
+
startedAt,
|
|
99
|
+
timeoutMs: discoveryTimeoutMs,
|
|
100
|
+
barWidth: APPLETV_PAIRING_DISCOVERY_PROGRESS_BAR_WIDTH,
|
|
101
|
+
intervalMs: APPLETV_PAIRING_DISCOVERY_PROGRESS_INTERVAL_MS,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const devices = await pairingService.discoverDevices({
|
|
106
|
+
timeoutMs: discoveryTimeoutMs,
|
|
107
|
+
});
|
|
108
|
+
pairingDiscoveryProgress.succeed(
|
|
109
|
+
`Apple TV pairing discovery completed: ${devices.length} device(s) found`,
|
|
110
|
+
);
|
|
111
|
+
return devices;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
pairingDiscoveryProgress.fail('Apple TV pairing discovery failed');
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {unknown} err
|
|
120
|
+
* @returns {boolean}
|
|
121
|
+
*/
|
|
122
|
+
function isNoAppleTVPairingDevicesFoundError(err) {
|
|
123
|
+
return (
|
|
124
|
+
err instanceof Error &&
|
|
125
|
+
(err.message === getNoAppleTVPairingDevicesMessage() ||
|
|
126
|
+
('code' in err && err.code === 'NO_DEVICES'))
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @returns {string}
|
|
132
|
+
*/
|
|
133
|
+
function getNoAppleTVPairingDevicesMessage() {
|
|
134
|
+
return 'No Apple TV pairing devices found. Please ensure your Apple TV is on the same network and in pairing mode.';
|
|
135
|
+
}
|
|
67
136
|
|
|
68
137
|
await main();
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @typedef {import('appium-ios-remotexpc').AppleTVDevice} AppleTVDevice
|
|
141
|
+
*/
|
|
@@ -24,19 +24,28 @@ import {
|
|
|
24
24
|
import {strongbox, BaseItem} from '@appium/strongbox';
|
|
25
25
|
import {Command} from 'commander';
|
|
26
26
|
|
|
27
|
+
import {parsePositiveIntegerOption} from './lib/options.mjs';
|
|
28
|
+
import {startTimeoutProgressLogger} from './lib/progress.mjs';
|
|
29
|
+
import {assertRoot} from './lib/root.mjs';
|
|
30
|
+
|
|
27
31
|
const log = logger.getLogger('TunnelCreation');
|
|
28
32
|
const TUNNEL_REGISTRY_PORT = 'tunnelRegistryPort';
|
|
29
33
|
const DEFAULT_TUNNEL_REGISTRY_PORT = 42314;
|
|
30
34
|
const WIRELESS_APPLETV_DISCOVERY_PROGRESS_INTERVAL_MS = 1000;
|
|
31
|
-
const
|
|
35
|
+
const DEFAULT_WIRELESS_APPLETV_DISCOVERY_TIMEOUT_MS = 10_000;
|
|
32
36
|
const WIRELESS_APPLETV_DISCOVERY_PROGRESS_BAR_WIDTH = 24;
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* TunnelCreator class for managing tunnel creation and related operations (USB and optional Apple TV over WiFi).
|
|
36
40
|
*/
|
|
37
41
|
class TunnelCreator {
|
|
38
|
-
|
|
42
|
+
/**
|
|
43
|
+
* @param {{appleTVDiscoveryTimeoutMs?: number}} [opts]
|
|
44
|
+
*/
|
|
45
|
+
constructor(opts = {}) {
|
|
39
46
|
this._tunnelRegistryPort = DEFAULT_TUNNEL_REGISTRY_PORT;
|
|
47
|
+
this._appleTVDiscoveryTimeoutMs =
|
|
48
|
+
opts.appleTVDiscoveryTimeoutMs ?? DEFAULT_WIRELESS_APPLETV_DISCOVERY_TIMEOUT_MS;
|
|
40
49
|
/** @type {import('appium-ios-remotexpc').TunnelRegistryServer | null} */
|
|
41
50
|
this._registryServer = null;
|
|
42
51
|
/** @type {import('appium-ios-remotexpc').TunnelReadinessCoordinator} */
|
|
@@ -412,13 +421,14 @@ class TunnelCreator {
|
|
|
412
421
|
|
|
413
422
|
/**
|
|
414
423
|
* @param {string[] | undefined} specificDeviceIds
|
|
415
|
-
* @returns {{startedAt: number, promise: Promise<{devices: AppleTVDevice[] | null, error: unknown | null}>}}
|
|
424
|
+
* @returns {{startedAt: number, timeoutMs: number, promise: Promise<{devices: AppleTVDevice[] | null, error: unknown | null}>}}
|
|
416
425
|
*/
|
|
417
426
|
prefetchAppleTVDevices(specificDeviceIds) {
|
|
418
427
|
const startedAt = performance.now();
|
|
419
428
|
if (specificDeviceIds && specificDeviceIds.length > 0) {
|
|
420
429
|
return {
|
|
421
430
|
startedAt,
|
|
431
|
+
timeoutMs: this._appleTVDiscoveryTimeoutMs,
|
|
422
432
|
promise: Promise.resolve({devices: null, error: null}),
|
|
423
433
|
};
|
|
424
434
|
}
|
|
@@ -426,7 +436,7 @@ class TunnelCreator {
|
|
|
426
436
|
const promise = (async () => {
|
|
427
437
|
try {
|
|
428
438
|
const devices = await tunnelService.discoverDevices({
|
|
429
|
-
timeoutMs:
|
|
439
|
+
timeoutMs: this._appleTVDiscoveryTimeoutMs,
|
|
430
440
|
});
|
|
431
441
|
return {devices, error: null};
|
|
432
442
|
} catch (err) {
|
|
@@ -437,7 +447,7 @@ class TunnelCreator {
|
|
|
437
447
|
} catch {}
|
|
438
448
|
}
|
|
439
449
|
})();
|
|
440
|
-
return {startedAt, promise};
|
|
450
|
+
return {startedAt, timeoutMs: this._appleTVDiscoveryTimeoutMs, promise};
|
|
441
451
|
}
|
|
442
452
|
|
|
443
453
|
/**
|
|
@@ -454,7 +464,7 @@ class TunnelCreator {
|
|
|
454
464
|
|
|
455
465
|
const startResult = await tunnelService.startTunnel(undefined, udid, {
|
|
456
466
|
devices: prefetchedDevice ? [prefetchedDevice] : undefined,
|
|
457
|
-
discoveryTimeoutMs:
|
|
467
|
+
discoveryTimeoutMs: this._appleTVDiscoveryTimeoutMs,
|
|
458
468
|
});
|
|
459
469
|
if (!startResult.tcpSocket) {
|
|
460
470
|
throw new Error('Apple TV TCP socket to listener port not established');
|
|
@@ -740,14 +750,15 @@ function isNoAppleTVDevicesFoundError(err) {
|
|
|
740
750
|
}
|
|
741
751
|
|
|
742
752
|
/**
|
|
743
|
-
* @param {{startedAt: number, promise: Promise<{devices: AppleTVDevice[] | null, error: unknown | null}>}} discovery
|
|
753
|
+
* @param {{startedAt: number, timeoutMs: number, promise: Promise<{devices: AppleTVDevice[] | null, error: unknown | null}>}} discovery
|
|
744
754
|
* @returns {Promise<AppleTVDevice[] | null>}
|
|
745
755
|
*/
|
|
746
756
|
async function waitForAppleTVDiscovery(discovery) {
|
|
747
757
|
const wirelessDiscoveryProgress = startTimeoutProgressLogger({
|
|
758
|
+
log,
|
|
748
759
|
label: 'Waiting for wireless Apple TV discovery',
|
|
749
760
|
startedAt: discovery.startedAt,
|
|
750
|
-
timeoutMs:
|
|
761
|
+
timeoutMs: discovery.timeoutMs,
|
|
751
762
|
barWidth: WIRELESS_APPLETV_DISCOVERY_PROGRESS_BAR_WIDTH,
|
|
752
763
|
intervalMs: WIRELESS_APPLETV_DISCOVERY_PROGRESS_INTERVAL_MS,
|
|
753
764
|
});
|
|
@@ -775,51 +786,6 @@ async function sleep(ms) {
|
|
|
775
786
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
776
787
|
}
|
|
777
788
|
|
|
778
|
-
/**
|
|
779
|
-
* Logs a timeout-based progress bar while an operation without native progress callbacks is running.
|
|
780
|
-
*
|
|
781
|
-
* @param {{label: string, startedAt: number, timeoutMs: number, barWidth: number, intervalMs: number}} opts
|
|
782
|
-
* @returns {{succeed: (message?: string) => void, fail: (message?: string) => void}}
|
|
783
|
-
*/
|
|
784
|
-
function startTimeoutProgressLogger({label, startedAt, timeoutMs, barWidth, intervalMs}) {
|
|
785
|
-
/** @type {NodeJS.Timeout | null} */
|
|
786
|
-
let timer = null;
|
|
787
|
-
let isStopped = false;
|
|
788
|
-
|
|
789
|
-
const logProgress = (status, isComplete = false) => {
|
|
790
|
-
const elapsedMs = performance.now() - startedAt;
|
|
791
|
-
const boundedElapsedMs = Math.min(elapsedMs, timeoutMs);
|
|
792
|
-
const progress = isComplete ? 1 : boundedElapsedMs / timeoutMs;
|
|
793
|
-
const filledWidth = Math.round(progress * barWidth);
|
|
794
|
-
const emptyWidth = barWidth - filledWidth;
|
|
795
|
-
const bar = `${'#'.repeat(filledWidth)}${'-'.repeat(emptyWidth)}`;
|
|
796
|
-
log.info(`${label}: [${bar}]${status && status !== 'waiting' ? ` - ${status}` : ''}`);
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
const stop = (status, isComplete = false) => {
|
|
800
|
-
if (isStopped) {
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
isStopped = true;
|
|
804
|
-
if (timer) {
|
|
805
|
-
clearInterval(timer);
|
|
806
|
-
timer = null;
|
|
807
|
-
}
|
|
808
|
-
logProgress(status, isComplete);
|
|
809
|
-
};
|
|
810
|
-
|
|
811
|
-
logProgress('waiting');
|
|
812
|
-
timer = setInterval(() => {
|
|
813
|
-
logProgress('waiting');
|
|
814
|
-
}, intervalMs);
|
|
815
|
-
timer.unref?.();
|
|
816
|
-
|
|
817
|
-
return {
|
|
818
|
-
succeed: (message = 'done') => stop(message, true),
|
|
819
|
-
fail: (message = 'failed') => stop(message),
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
|
|
823
789
|
/**
|
|
824
790
|
* @param {string} value
|
|
825
791
|
* @param {string} label
|
|
@@ -846,19 +812,6 @@ function parseNonNegativeIntegerOption(value, label) {
|
|
|
846
812
|
return count;
|
|
847
813
|
}
|
|
848
814
|
|
|
849
|
-
/**
|
|
850
|
-
* @param {string} value
|
|
851
|
-
* @param {string} label
|
|
852
|
-
* @returns {number}
|
|
853
|
-
*/
|
|
854
|
-
function parsePositiveIntegerOption(value, label) {
|
|
855
|
-
const num = Number.parseInt(value, 10);
|
|
856
|
-
if (!Number.isFinite(num) || num <= 0) {
|
|
857
|
-
throw new Error(`Invalid ${label}: ${value}. Expected a positive integer.`);
|
|
858
|
-
}
|
|
859
|
-
return num;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
815
|
/**
|
|
863
816
|
* @param {string} value
|
|
864
817
|
* @param {string[]} previous
|
|
@@ -932,19 +885,8 @@ function setupCleanupHandlers(tunnelCreator) {
|
|
|
932
885
|
return cleanupOnce;
|
|
933
886
|
}
|
|
934
887
|
|
|
935
|
-
function assertRoot() {
|
|
936
|
-
if (typeof process.getuid !== 'function') {
|
|
937
|
-
return;
|
|
938
|
-
}
|
|
939
|
-
if (process.getuid() !== 0) {
|
|
940
|
-
throw new Error(
|
|
941
|
-
'This script must be run as root (e.g. sudo appium driver run xcuitest tunnel-creation ...).',
|
|
942
|
-
);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
888
|
async function main() {
|
|
947
|
-
assertRoot();
|
|
889
|
+
assertRoot('tunnel-creation');
|
|
948
890
|
const program = new Command();
|
|
949
891
|
program
|
|
950
892
|
.name('appium driver run xcuitest tunnel-creation')
|
|
@@ -968,6 +910,12 @@ async function main() {
|
|
|
968
910
|
collectStringValues,
|
|
969
911
|
[],
|
|
970
912
|
)
|
|
913
|
+
.option(
|
|
914
|
+
'--appletv-discovery-timeout-ms <ms>',
|
|
915
|
+
'Apple TV wireless discovery timeout in milliseconds',
|
|
916
|
+
(value) => parsePositiveIntegerOption(value, 'Apple TV discovery timeout'),
|
|
917
|
+
DEFAULT_WIRELESS_APPLETV_DISCOVERY_TIMEOUT_MS,
|
|
918
|
+
)
|
|
971
919
|
.option(
|
|
972
920
|
'--disconnect-retry-max-attempts <count>',
|
|
973
921
|
'Max tunnel recreation attempts after unexpected disconnect: 0 = unlimited; omit to disable retries',
|
|
@@ -989,7 +937,9 @@ async function main() {
|
|
|
989
937
|
const shouldRunUsbFlow = !hasRequestedAppleTVIds || hasRequestedUdids;
|
|
990
938
|
const shouldRunAppleTVFlow = !hasRequestedUdids || hasRequestedAppleTVIds;
|
|
991
939
|
|
|
992
|
-
const tunnelCreator = new TunnelCreator(
|
|
940
|
+
const tunnelCreator = new TunnelCreator({
|
|
941
|
+
appleTVDiscoveryTimeoutMs: options.appletvDiscoveryTimeoutMs,
|
|
942
|
+
});
|
|
993
943
|
const cleanupOnce = setupCleanupHandlers(tunnelCreator);
|
|
994
944
|
|
|
995
945
|
try {
|
|
@@ -1126,7 +1076,7 @@ await main();
|
|
|
1126
1076
|
*/
|
|
1127
1077
|
|
|
1128
1078
|
/**
|
|
1129
|
-
* @typedef {
|
|
1079
|
+
* @typedef {import('appium-ios-remotexpc').AppleTVDevice} AppleTVDevice
|
|
1130
1080
|
*/
|
|
1131
1081
|
|
|
1132
1082
|
/**
|