appium-xcuitest-driver 11.2.3 → 11.3.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 +12 -0
- package/npm-shrinkwrap.json +45 -33
- package/package.json +3 -2
- package/scripts/download-wda-sim.mjs +16 -73
- package/scripts/download-wda.mjs +167 -0
- package/scripts/tunnel-creation.mjs +51 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [11.3.0](https://github.com/appium/appium-xcuitest-driver/compare/v11.2.4...v11.3.0) (2026-05-09)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add download-wda command ([#2835](https://github.com/appium/appium-xcuitest-driver/issues/2835)) ([1f568b4](https://github.com/appium/appium-xcuitest-driver/commit/1f568b4823a75ede182afdc7c82b73193f554765))
|
|
6
|
+
|
|
7
|
+
## [11.2.4](https://github.com/appium/appium-xcuitest-driver/compare/v11.2.3...v11.2.4) (2026-05-08)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Tunnels cleanup ([#2834](https://github.com/appium/appium-xcuitest-driver/issues/2834)) ([e17218c](https://github.com/appium/appium-xcuitest-driver/commit/e17218c49111c500bef98aeee8d3739aafdfeb24))
|
|
12
|
+
|
|
1
13
|
## [11.2.3](https://github.com/appium/appium-xcuitest-driver/compare/v11.2.2...v11.2.3) (2026-05-07)
|
|
2
14
|
|
|
3
15
|
### Miscellaneous Chores
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-xcuitest-driver",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.3.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.3.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@appium/strongbox": "^1.0.0-rc.1",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"ws": "^8.13.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@appium/docutils": "^2.
|
|
38
|
+
"@appium/docutils": "^2.4.0",
|
|
39
39
|
"@appium/eslint-config-appium-ts": "^3.0.0",
|
|
40
40
|
"@appium/tsconfig": "^1.0.0-rc.1",
|
|
41
41
|
"@appium/types": "^1.0.0-rc.1",
|
|
@@ -240,6 +240,18 @@
|
|
|
240
240
|
"sharp": "0.34.5"
|
|
241
241
|
}
|
|
242
242
|
},
|
|
243
|
+
"node_modules/@appium/support/node_modules/semver": {
|
|
244
|
+
"version": "7.7.4",
|
|
245
|
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
|
246
|
+
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
|
247
|
+
"license": "ISC",
|
|
248
|
+
"bin": {
|
|
249
|
+
"semver": "bin/semver.js"
|
|
250
|
+
},
|
|
251
|
+
"engines": {
|
|
252
|
+
"node": ">=10"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
243
255
|
"node_modules/@appium/tsconfig": {
|
|
244
256
|
"version": "1.1.2",
|
|
245
257
|
"resolved": "https://registry.npmjs.org/@appium/tsconfig/-/tsconfig-1.1.2.tgz",
|
|
@@ -551,9 +563,9 @@
|
|
|
551
563
|
}
|
|
552
564
|
},
|
|
553
565
|
"node_modules/appium-ios-device": {
|
|
554
|
-
"version": "3.1.
|
|
555
|
-
"resolved": "https://registry.npmjs.org/appium-ios-device/-/appium-ios-device-3.1.
|
|
556
|
-
"integrity": "sha512-
|
|
566
|
+
"version": "3.1.13",
|
|
567
|
+
"resolved": "https://registry.npmjs.org/appium-ios-device/-/appium-ios-device-3.1.13.tgz",
|
|
568
|
+
"integrity": "sha512-W10U63ISs2z/OwrkvRkSpGJeLc3qeBfIBhKd33jLaaDZE3VNR3yD7vLVLBfXxVaaM6UFOqx3yL5BtoeYltsmYQ==",
|
|
557
569
|
"license": "Apache-2.0",
|
|
558
570
|
"dependencies": {
|
|
559
571
|
"@appium/support": "^7.2.2",
|
|
@@ -595,12 +607,12 @@
|
|
|
595
607
|
}
|
|
596
608
|
},
|
|
597
609
|
"node_modules/appium-ios-simulator": {
|
|
598
|
-
"version": "8.1.
|
|
599
|
-
"resolved": "https://registry.npmjs.org/appium-ios-simulator/-/appium-ios-simulator-8.1.
|
|
600
|
-
"integrity": "sha512-
|
|
610
|
+
"version": "8.1.3",
|
|
611
|
+
"resolved": "https://registry.npmjs.org/appium-ios-simulator/-/appium-ios-simulator-8.1.3.tgz",
|
|
612
|
+
"integrity": "sha512-ED3dNbxTO7YC/8UNKECY2mvePELtTKp9AXj+qwccK0UOMPWLURPszB1vVmZA5UGIykyeOeHBVcKbkSk709R8Gg==",
|
|
601
613
|
"license": "Apache-2.0",
|
|
602
614
|
"dependencies": {
|
|
603
|
-
"@appium/support": "^7.
|
|
615
|
+
"@appium/support": "^7.2.2",
|
|
604
616
|
"@xmldom/xmldom": "^0.x",
|
|
605
617
|
"appium-xcode": "^6.0.0",
|
|
606
618
|
"async-lock": "^1.0.0",
|
|
@@ -655,9 +667,9 @@
|
|
|
655
667
|
}
|
|
656
668
|
},
|
|
657
669
|
"node_modules/appium-webdriveragent": {
|
|
658
|
-
"version": "12.2.
|
|
659
|
-
"resolved": "https://registry.npmjs.org/appium-webdriveragent/-/appium-webdriveragent-12.2.
|
|
660
|
-
"integrity": "sha512
|
|
670
|
+
"version": "12.2.2",
|
|
671
|
+
"resolved": "https://registry.npmjs.org/appium-webdriveragent/-/appium-webdriveragent-12.2.2.tgz",
|
|
672
|
+
"integrity": "sha512-/GbzAjEHaaNgBUK8eVnmtf8s2fudjyCi55IBoEzv0sFg/188Bo+wsJfQhECjrk9CdhkpiwjsHAlJGLUy+iEpIQ==",
|
|
661
673
|
"license": "Apache-2.0",
|
|
662
674
|
"dependencies": {
|
|
663
675
|
"@appium/base-driver": "^10.3.0",
|
|
@@ -667,7 +679,7 @@
|
|
|
667
679
|
"appium-ios-simulator": "^8.0.0",
|
|
668
680
|
"async-lock": "^1.0.0",
|
|
669
681
|
"asyncbox": "^6.1.0",
|
|
670
|
-
"axios": "^1.
|
|
682
|
+
"axios": "^1.16.0",
|
|
671
683
|
"teen_process": "^4.0.7"
|
|
672
684
|
},
|
|
673
685
|
"engines": {
|
|
@@ -676,12 +688,12 @@
|
|
|
676
688
|
}
|
|
677
689
|
},
|
|
678
690
|
"node_modules/appium-xcode": {
|
|
679
|
-
"version": "6.2.
|
|
680
|
-
"resolved": "https://registry.npmjs.org/appium-xcode/-/appium-xcode-6.2.
|
|
681
|
-
"integrity": "sha512-
|
|
691
|
+
"version": "6.2.2",
|
|
692
|
+
"resolved": "https://registry.npmjs.org/appium-xcode/-/appium-xcode-6.2.2.tgz",
|
|
693
|
+
"integrity": "sha512-THnFNNPW2M680OzSdyHHcvF46CusrvW+/t+zCvVA+MK/sO1AE22GIHoUScbl8HeRxVI9gf1SsXravNSh8OoNaA==",
|
|
682
694
|
"license": "Apache-2.0",
|
|
683
695
|
"dependencies": {
|
|
684
|
-
"@appium/support": "^7.
|
|
696
|
+
"@appium/support": "^7.2.2",
|
|
685
697
|
"asyncbox": "^6.0.1",
|
|
686
698
|
"semver": "^7.0.0",
|
|
687
699
|
"teen_process": "^4.0.4"
|
|
@@ -1062,9 +1074,9 @@
|
|
|
1062
1074
|
}
|
|
1063
1075
|
},
|
|
1064
1076
|
"node_modules/brace-expansion": {
|
|
1065
|
-
"version": "5.0.
|
|
1066
|
-
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.
|
|
1067
|
-
"integrity": "sha512-
|
|
1077
|
+
"version": "5.0.6",
|
|
1078
|
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
|
1079
|
+
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
|
1068
1080
|
"license": "MIT",
|
|
1069
1081
|
"dependencies": {
|
|
1070
1082
|
"balanced-match": "^4.0.2"
|
|
@@ -1953,9 +1965,9 @@
|
|
|
1953
1965
|
}
|
|
1954
1966
|
},
|
|
1955
1967
|
"node_modules/get-east-asian-width": {
|
|
1956
|
-
"version": "1.
|
|
1957
|
-
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.
|
|
1958
|
-
"integrity": "sha512-
|
|
1968
|
+
"version": "1.6.0",
|
|
1969
|
+
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
|
1970
|
+
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
|
1959
1971
|
"license": "MIT",
|
|
1960
1972
|
"engines": {
|
|
1961
1973
|
"node": ">=18"
|
|
@@ -2780,9 +2792,9 @@
|
|
|
2780
2792
|
}
|
|
2781
2793
|
},
|
|
2782
2794
|
"node_modules/node-devicectl": {
|
|
2783
|
-
"version": "1.3.
|
|
2784
|
-
"resolved": "https://registry.npmjs.org/node-devicectl/-/node-devicectl-1.3.
|
|
2785
|
-
"integrity": "sha512-
|
|
2795
|
+
"version": "1.3.1",
|
|
2796
|
+
"resolved": "https://registry.npmjs.org/node-devicectl/-/node-devicectl-1.3.1.tgz",
|
|
2797
|
+
"integrity": "sha512-hjjE/1RuvQ7BnjHqTfi4yhKGVVcTo/rEPdVzj1sIIQk29cvdsa1yXqfM4v4tQNiRcL2lMMqbO1hSkCBVAJ/w1g==",
|
|
2786
2798
|
"license": "Apache-2.0",
|
|
2787
2799
|
"dependencies": {
|
|
2788
2800
|
"@appium/logger": "^2.0.0-rc.1",
|
|
@@ -2806,9 +2818,9 @@
|
|
|
2806
2818
|
}
|
|
2807
2819
|
},
|
|
2808
2820
|
"node_modules/node-simctl": {
|
|
2809
|
-
"version": "8.2.
|
|
2810
|
-
"resolved": "https://registry.npmjs.org/node-simctl/-/node-simctl-8.2.
|
|
2811
|
-
"integrity": "sha512-
|
|
2821
|
+
"version": "8.2.3",
|
|
2822
|
+
"resolved": "https://registry.npmjs.org/node-simctl/-/node-simctl-8.2.3.tgz",
|
|
2823
|
+
"integrity": "sha512-udjYoXRsApFQP0fggKwGWFWUqinJKY11zIUmMxMv8Q4sfMLV0Uu9iavRUfiB3QOmJvL4WAXTaHv+tZvkiT+nyQ==",
|
|
2812
2824
|
"license": "Apache-2.0",
|
|
2813
2825
|
"dependencies": {
|
|
2814
2826
|
"@appium/logger": "^2.0.0-rc.1",
|
|
@@ -3450,9 +3462,9 @@
|
|
|
3450
3462
|
"optional": true
|
|
3451
3463
|
},
|
|
3452
3464
|
"node_modules/semver": {
|
|
3453
|
-
"version": "7.
|
|
3454
|
-
"resolved": "https://registry.npmjs.org/semver/-/semver-7.
|
|
3455
|
-
"integrity": "sha512-
|
|
3465
|
+
"version": "7.8.0",
|
|
3466
|
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
|
3467
|
+
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
|
3456
3468
|
"license": "ISC",
|
|
3457
3469
|
"bin": {
|
|
3458
3470
|
"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.3.0",
|
|
12
12
|
"author": "Appium Contributors",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"repository": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"build-wda": "./scripts/build-wda.mjs",
|
|
35
35
|
"open-wda": "./scripts/open-wda.mjs",
|
|
36
36
|
"tunnel-creation": "./scripts/tunnel-creation.mjs",
|
|
37
|
+
"download-wda": "./scripts/download-wda.mjs",
|
|
37
38
|
"download-wda-sim": "./scripts/download-wda-sim.mjs",
|
|
38
39
|
"image-mounter": "./scripts/image-mounter.mjs",
|
|
39
40
|
"list-real-devices": "./scripts/list-real-devices.mjs",
|
|
@@ -143,7 +144,7 @@
|
|
|
143
144
|
"appium": "^3.0.0-rc.2"
|
|
144
145
|
},
|
|
145
146
|
"devDependencies": {
|
|
146
|
-
"@appium/docutils": "^2.
|
|
147
|
+
"@appium/docutils": "^2.4.0",
|
|
147
148
|
"@appium/eslint-config-appium-ts": "^3.0.0",
|
|
148
149
|
"@appium/tsconfig": "^1.0.0-rc.1",
|
|
149
150
|
"@appium/types": "^1.0.0-rc.1",
|
|
@@ -1,80 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import _ from 'lodash';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
1
|
+
import {getWDAPrebuiltPackage} from './download-wda.mjs';
|
|
5
2
|
import {Command} from 'commander';
|
|
3
|
+
import { deprecate } from 'node:util';
|
|
6
4
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const destZip = (/** @type {string} */ platform) => {
|
|
11
|
-
const scheme = `WebDriverAgentRunner${_.toLower(platform) === 'tvos' ? '_tvOS' : ''}`;
|
|
12
|
-
return `${scheme}-Build-Sim-${os.arch() === 'arm64' ? 'arm64' : 'x86_64'}.zip`;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Return installed appium-webdriveragent package version
|
|
17
|
-
* @returns {Promise<string>}
|
|
18
|
-
*/
|
|
19
|
-
async function webdriveragentPkgVersion() {
|
|
20
|
-
const moduleRoot = node.getModuleRootSync('appium-xcuitest-driver', import.meta.url);
|
|
21
|
-
if (!moduleRoot) {
|
|
22
|
-
throw new Error('Cannot resolve module root for appium-xcuitest-driver');
|
|
23
|
-
}
|
|
24
|
-
const pkgPath = path.join(
|
|
25
|
-
moduleRoot,
|
|
26
|
-
'node_modules',
|
|
27
|
-
'appium-webdriveragent',
|
|
28
|
-
'package.json'
|
|
29
|
-
);
|
|
30
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
31
|
-
return String(pkg.version);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Prepare the working root directory.
|
|
36
|
-
* @param {string} outdir
|
|
37
|
-
* @returns {Promise<string>} Root directory to download and unzip.
|
|
38
|
-
*/
|
|
39
|
-
async function prepareRootDir(outdir) {
|
|
40
|
-
const destDir = path.resolve(process.cwd(), outdir);
|
|
41
|
-
if (await fs.exists(destDir)) {
|
|
42
|
-
throw new Error(`${destDir} already exists`);
|
|
43
|
-
}
|
|
44
|
-
await fs.mkdir(destDir, {recursive: true});
|
|
45
|
-
return destDir;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @param {DownloadOptions} options
|
|
50
|
-
*/
|
|
51
|
-
async function getWDAPrebuiltPackage(options) {
|
|
52
|
-
const destDir = await prepareRootDir(options.outdir);
|
|
53
|
-
const zipFileName = destZip(options.platform);
|
|
54
|
-
const wdaVersion = await webdriveragentPkgVersion();
|
|
55
|
-
const urlToDownload = wdaUrl(wdaVersion, zipFileName);
|
|
56
|
-
const downloadedZipFile = path.join(destDir, zipFileName);
|
|
57
|
-
try {
|
|
58
|
-
log.info(`Downloading ${urlToDownload}`);
|
|
59
|
-
await net.downloadFile(urlToDownload, downloadedZipFile);
|
|
60
|
-
|
|
61
|
-
log.info(`Unpacking ${downloadedZipFile} into ${destDir}`);
|
|
62
|
-
await zip.extractAllTo(downloadedZipFile, destDir);
|
|
63
|
-
|
|
64
|
-
log.info(`Deleting ${downloadedZipFile}`);
|
|
65
|
-
} finally {
|
|
66
|
-
if (await fs.exists(downloadedZipFile)) {
|
|
67
|
-
await fs.unlink(downloadedZipFile);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
5
|
+
const DEPRECATION_MESSAGE =
|
|
6
|
+
"[DEPRECATED] 'download-wda-sim' is deprecated. " +
|
|
7
|
+
"Use 'appium driver run xcuitest download-wda -- --kind=sim --platform=<platform> --outdir=<outdir>' instead.";
|
|
71
8
|
|
|
72
9
|
async function main() {
|
|
73
10
|
const program = new Command();
|
|
74
11
|
|
|
12
|
+
const oldHandler = deprecate(
|
|
13
|
+
async (options) => {
|
|
14
|
+
await getWDAPrebuiltPackage({...options, kind: 'sim'});
|
|
15
|
+
},
|
|
16
|
+
DEPRECATION_MESSAGE
|
|
17
|
+
);
|
|
18
|
+
|
|
75
19
|
program
|
|
76
20
|
.name('appium driver run xcuitest download-wda-sim')
|
|
77
21
|
.description('Download a prebuilt WebDriverAgentRunner for iOS/tvOS simulator')
|
|
22
|
+
.addHelpText('beforeAll', `${DEPRECATION_MESSAGE}\n\n`)
|
|
78
23
|
.requiredOption('--outdir <path>', 'Destination directory to download and unpack into')
|
|
79
24
|
.requiredOption(
|
|
80
25
|
'--platform <platform>',
|
|
@@ -86,14 +31,12 @@ async function main() {
|
|
|
86
31
|
`
|
|
87
32
|
EXAMPLES:
|
|
88
33
|
# Download WDA for iOS simulator
|
|
89
|
-
appium driver run xcuitest download-wda-sim --outdir ./wda-sim --platform iOS
|
|
34
|
+
appium driver run xcuitest download-wda-sim -- --outdir ./wda-sim --platform iOS
|
|
90
35
|
|
|
91
36
|
# Download WDA for tvOS simulator
|
|
92
|
-
appium driver run xcuitest download-wda-sim --outdir ./wda-sim-tvos --platform tvOS`,
|
|
37
|
+
appium driver run xcuitest download-wda-sim -- --outdir ./wda-sim-tvos --platform tvOS`,
|
|
93
38
|
)
|
|
94
|
-
.action(
|
|
95
|
-
await getWDAPrebuiltPackage(options);
|
|
96
|
-
});
|
|
39
|
+
.action(oldHandler);
|
|
97
40
|
|
|
98
41
|
await program.parseAsync(process.argv);
|
|
99
42
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {fs, logger, zip, net, node} from 'appium/support.js';
|
|
2
|
+
import {constants as fsConstants, promises as fsPromises} from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {pathToFileURL} from 'node:url';
|
|
6
|
+
import {Command} from 'commander';
|
|
7
|
+
|
|
8
|
+
const log = logger.getLogger('download-wda');
|
|
9
|
+
const WDA_KIND_REAL = 'real';
|
|
10
|
+
const WDA_KIND_SIM = 'sim';
|
|
11
|
+
|
|
12
|
+
const wdaUrl = (/** @type {string} */ version, /** @type {string} */ zipFileName) =>
|
|
13
|
+
`https://github.com/appium/WebDriverAgent/releases/download/v${version}/${zipFileName}`;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Download and unpack a prebuilt WDA package for the given platform and kind.
|
|
17
|
+
* @param {DownloadOptions} options
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
export async function getWDAPrebuiltPackage(options) {
|
|
21
|
+
const kind = normalizeKind(options.kind);
|
|
22
|
+
const destDir = await prepareRootDir(options.outdir);
|
|
23
|
+
const zipFileName = destZip(options.platform, kind);
|
|
24
|
+
const wdaVersion = await getWebdriveragentPkgVersion();
|
|
25
|
+
const urlToDownload = wdaUrl(wdaVersion, zipFileName);
|
|
26
|
+
const downloadedZipFile = path.join(destDir, zipFileName);
|
|
27
|
+
try {
|
|
28
|
+
log.info(`Downloading ${urlToDownload}`);
|
|
29
|
+
await net.downloadFile(urlToDownload, downloadedZipFile);
|
|
30
|
+
|
|
31
|
+
log.info(`Unpacking ${downloadedZipFile} into ${destDir}`);
|
|
32
|
+
await zip.extractAllTo(downloadedZipFile, destDir);
|
|
33
|
+
|
|
34
|
+
log.info(`Deleting ${downloadedZipFile}`);
|
|
35
|
+
} finally {
|
|
36
|
+
if (await fs.exists(downloadedZipFile)) {
|
|
37
|
+
await fs.unlink(downloadedZipFile);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const destZip = (/** @type {string} */ platform, /** @type {WDAKind} */ kind) => {
|
|
43
|
+
const scheme = `WebDriverAgentRunner${String(platform).toLowerCase() === 'tvos' ? '_tvOS' : ''}`;
|
|
44
|
+
if (kind === WDA_KIND_SIM) {
|
|
45
|
+
return `${scheme}-Build-Sim-${os.arch() === 'arm64' ? 'arm64' : 'x86_64'}.zip`;
|
|
46
|
+
}
|
|
47
|
+
return `${scheme}-Runner.zip`;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize the kind value, ensuring it is either 'real' or 'sim'. Default to 'real' if undefined.
|
|
52
|
+
* @param {string | undefined} kind
|
|
53
|
+
* @returns {WDAKind}
|
|
54
|
+
*/
|
|
55
|
+
function normalizeKind(kind) {
|
|
56
|
+
const normalized = String(kind || WDA_KIND_REAL).toLowerCase();
|
|
57
|
+
if (![WDA_KIND_REAL, WDA_KIND_SIM].includes(normalized)) {
|
|
58
|
+
throw new Error(`Unsupported kind '${kind}'. Supported values are '${WDA_KIND_REAL}' and '${WDA_KIND_SIM}'`);
|
|
59
|
+
}
|
|
60
|
+
return /** @type {WDAKind} */ (normalized);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Return installed appium-webdriveragent package version
|
|
65
|
+
* @returns {Promise<string>}
|
|
66
|
+
*/
|
|
67
|
+
async function getWebdriveragentPkgVersion() {
|
|
68
|
+
const moduleRoot = node.getModuleRootSync('appium-xcuitest-driver', import.meta.url);
|
|
69
|
+
if (!moduleRoot) {
|
|
70
|
+
throw new Error('Cannot resolve module root for appium-xcuitest-driver');
|
|
71
|
+
}
|
|
72
|
+
const pkgPath = path.join(
|
|
73
|
+
moduleRoot,
|
|
74
|
+
'node_modules',
|
|
75
|
+
'appium-webdriveragent',
|
|
76
|
+
'package.json'
|
|
77
|
+
);
|
|
78
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
79
|
+
if (!pkg.version || typeof pkg.version !== 'string') {
|
|
80
|
+
throw new Error(`Cannot find version in ${pkgPath}`);
|
|
81
|
+
}
|
|
82
|
+
return pkg.version;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Prepare the working root directory.
|
|
87
|
+
* @param {string} outdir
|
|
88
|
+
* @returns {Promise<string>} Root directory to download and unzip.
|
|
89
|
+
*/
|
|
90
|
+
async function prepareRootDir(outdir) {
|
|
91
|
+
const destDir = path.resolve(process.cwd(), outdir);
|
|
92
|
+
if (await fs.exists(destDir)) {
|
|
93
|
+
throw new Error(`${destDir} already exists`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const parentDir = path.dirname(destDir);
|
|
97
|
+
try {
|
|
98
|
+
await fsPromises.access(parentDir, fsConstants.W_OK);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
throw new Error(`Parent directory '${parentDir}' is not writable`, {
|
|
101
|
+
cause: err,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await fs.mkdir(destDir, {recursive: true});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
109
|
+
throw new Error(`Cannot create directory '${destDir}': ${message}`, {
|
|
110
|
+
cause: err,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return destDir;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function main() {
|
|
117
|
+
const program = new Command();
|
|
118
|
+
|
|
119
|
+
program
|
|
120
|
+
.name('appium driver run xcuitest download-wda')
|
|
121
|
+
.description('Download a prebuilt WebDriverAgentRunner for iOS/tvOS real devices or simulators')
|
|
122
|
+
.requiredOption('--outdir <path>', 'Destination directory to download and unpack into')
|
|
123
|
+
.requiredOption(
|
|
124
|
+
'--platform <platform>',
|
|
125
|
+
'Target platform (e.g. iOS or tvOS)',
|
|
126
|
+
(value) => value,
|
|
127
|
+
)
|
|
128
|
+
.option(
|
|
129
|
+
'--kind <kind>',
|
|
130
|
+
`Target package type: ${WDA_KIND_REAL} (real devices) or ${WDA_KIND_SIM} (simulators). Default: ${WDA_KIND_REAL}`,
|
|
131
|
+
)
|
|
132
|
+
.addHelpText(
|
|
133
|
+
'after',
|
|
134
|
+
`
|
|
135
|
+
EXAMPLES:
|
|
136
|
+
# Download WDA for iOS real device (default)
|
|
137
|
+
appium driver run xcuitest download-wda -- --outdir ./wda-real --platform iOS
|
|
138
|
+
|
|
139
|
+
# Download WDA for tvOS simulator
|
|
140
|
+
appium driver run xcuitest download-wda -- --outdir ./wda-sim-tvos --platform tvOS --kind sim`,
|
|
141
|
+
)
|
|
142
|
+
.action(async (options) => {
|
|
143
|
+
await getWDAPrebuiltPackage({
|
|
144
|
+
...options,
|
|
145
|
+
kind: options.kind ?? WDA_KIND_REAL,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await program.parseAsync(process.argv);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const isMainModule =
|
|
153
|
+
Boolean(process.argv[1]) && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
154
|
+
if (isMainModule) {
|
|
155
|
+
await main();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @typedef {'real' | 'sim'} WDAKind
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @typedef {Object} DownloadOptions
|
|
164
|
+
* @property {string} outdir
|
|
165
|
+
* @property {string} platform
|
|
166
|
+
* @property {WDAKind | undefined} [kind]
|
|
167
|
+
*/
|
|
@@ -41,6 +41,8 @@ class TunnelCreator {
|
|
|
41
41
|
this._tunnelRegistryPort = 42314;
|
|
42
42
|
/** @type {import('appium-ios-remotexpc').TunnelRegistry | null} */
|
|
43
43
|
this._registry = null;
|
|
44
|
+
/** @type {import('appium-ios-remotexpc').TunnelRegistryServer | null} */
|
|
45
|
+
this._registryServer = null;
|
|
44
46
|
/** @type {Map<string, import('appium-ios-remotexpc').UsbmuxDevice>} */
|
|
45
47
|
this._usbDevices = new Map();
|
|
46
48
|
/** @type {Map<string, Promise<void>>} */
|
|
@@ -75,6 +77,10 @@ class TunnelCreator {
|
|
|
75
77
|
return this._registry;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
get registryServer() {
|
|
81
|
+
return this._registryServer;
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
set packetStreamBasePort(port) {
|
|
79
85
|
this._packetStreamBasePort = port;
|
|
80
86
|
}
|
|
@@ -97,6 +103,13 @@ class TunnelCreator {
|
|
|
97
103
|
this._registry = value;
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
/**
|
|
107
|
+
* @param {import('appium-ios-remotexpc').TunnelRegistryServer | null} value
|
|
108
|
+
*/
|
|
109
|
+
set registryServer(value) {
|
|
110
|
+
this._registryServer = value;
|
|
111
|
+
}
|
|
112
|
+
|
|
100
113
|
/**
|
|
101
114
|
* @param {number | null} maxAttempts - null disables retries; 0 means unlimited retries
|
|
102
115
|
* @param {number} intervalMs
|
|
@@ -171,12 +184,29 @@ class TunnelCreator {
|
|
|
171
184
|
async cleanup() {
|
|
172
185
|
this._isCleaningUp = true;
|
|
173
186
|
log.warn('Cleaning up tunnel resources...');
|
|
187
|
+
/** @type {Error[]} */
|
|
188
|
+
const cleanupErrors = [];
|
|
189
|
+
const recordCleanupError = (message, err) => {
|
|
190
|
+
const wrapped = err instanceof Error ? err : new Error(String(err));
|
|
191
|
+
cleanupErrors.push(new Error(message, {cause: wrapped}));
|
|
192
|
+
log.warn(`${message}: ${wrapped.message}`);
|
|
193
|
+
};
|
|
194
|
+
|
|
174
195
|
while (this._registryWatcherStops.length > 0) {
|
|
175
196
|
const stop = this._registryWatcherStops.pop();
|
|
176
197
|
try {
|
|
177
198
|
await stop?.();
|
|
178
199
|
} catch (err) {
|
|
179
|
-
|
|
200
|
+
recordCleanupError('Failed to stop tunnel registry watcher', err);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (this._registryServer) {
|
|
204
|
+
try {
|
|
205
|
+
await this._registryServer.stop();
|
|
206
|
+
} catch (err) {
|
|
207
|
+
recordCleanupError('Failed to stop tunnel registry server', err);
|
|
208
|
+
} finally {
|
|
209
|
+
this._registryServer = null;
|
|
180
210
|
}
|
|
181
211
|
}
|
|
182
212
|
|
|
@@ -194,7 +224,7 @@ class TunnelCreator {
|
|
|
194
224
|
await server.stop();
|
|
195
225
|
log.info(`Closed packet stream server for device ${udid}`);
|
|
196
226
|
} catch (err) {
|
|
197
|
-
|
|
227
|
+
recordCleanupError(`Failed to close packet stream server for device ${udid}`, err);
|
|
198
228
|
}
|
|
199
229
|
}),
|
|
200
230
|
);
|
|
@@ -216,9 +246,18 @@ class TunnelCreator {
|
|
|
216
246
|
})();
|
|
217
247
|
|
|
218
248
|
await Promise.allSettled([closeUsbPacketStreamServers, closeAppleTVTunnels]);
|
|
249
|
+
try {
|
|
250
|
+
await TunnelManager.closeAllTunnels();
|
|
251
|
+
} catch (err) {
|
|
252
|
+
recordCleanupError('Failed to close managed tunnel(s)', err);
|
|
253
|
+
}
|
|
219
254
|
await Promise.allSettled([...this._reconnectTasks.values()]);
|
|
220
255
|
|
|
221
|
-
|
|
256
|
+
if (cleanupErrors.length > 0) {
|
|
257
|
+
throw new AggregateError(cleanupErrors, 'Tunnel cleanup encountered errors');
|
|
258
|
+
} else {
|
|
259
|
+
log.info('Cleanup completed.');
|
|
260
|
+
}
|
|
222
261
|
}
|
|
223
262
|
|
|
224
263
|
/**
|
|
@@ -914,6 +953,9 @@ function setupCleanupHandlers(tunnelCreator) {
|
|
|
914
953
|
await tunnelCreator.cleanup();
|
|
915
954
|
} catch (err) {
|
|
916
955
|
log.warn(`Error during tunnel cleanup: ${err?.message ?? err}`);
|
|
956
|
+
if (!process.exitCode) {
|
|
957
|
+
process.exitCode = 1;
|
|
958
|
+
}
|
|
917
959
|
}
|
|
918
960
|
};
|
|
919
961
|
|
|
@@ -923,7 +965,8 @@ function setupCleanupHandlers(tunnelCreator) {
|
|
|
923
965
|
if (process.exitCode == null) {
|
|
924
966
|
// Follow conventional POSIX exit codes for signals where possible.
|
|
925
967
|
if (signal === 'SIGINT') {
|
|
926
|
-
|
|
968
|
+
// SIGINT is typically sent by Ctrl+C, so we exit with code 0 to indicate success.
|
|
969
|
+
process.exitCode = 0;
|
|
927
970
|
} else if (signal === 'SIGTERM') {
|
|
928
971
|
process.exitCode = 143;
|
|
929
972
|
} else {
|
|
@@ -1093,7 +1136,10 @@ async function main() {
|
|
|
1093
1136
|
return;
|
|
1094
1137
|
}
|
|
1095
1138
|
|
|
1096
|
-
await startTunnelRegistryServer(
|
|
1139
|
+
tunnelCreator.registryServer = await startTunnelRegistryServer(
|
|
1140
|
+
registry,
|
|
1141
|
+
tunnelCreator.tunnelRegistryPort,
|
|
1142
|
+
);
|
|
1097
1143
|
tunnelCreator._attachTunnelRegistryLifecycleWatch(watchTunnelRegistrySockets, usbResults, {
|
|
1098
1144
|
onTunnelDead: async ({udid}) => {
|
|
1099
1145
|
tunnelCreator._reconnectTunnelByUdid(udid);
|