appium-xcuitest-driver 10.41.0 → 10.42.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/build/lib/commands/xctest-record-screen.d.ts +12 -8
- package/build/lib/commands/xctest-record-screen.d.ts.map +1 -1
- package/build/lib/commands/xctest-record-screen.js +50 -13
- package/build/lib/commands/xctest-record-screen.js.map +1 -1
- package/build/lib/device/remotexpc-utils.d.ts +26 -0
- package/build/lib/device/remotexpc-utils.d.ts.map +1 -1
- package/build/lib/device/remotexpc-utils.js +133 -15
- package/build/lib/device/remotexpc-utils.js.map +1 -1
- package/build/lib/device/xctest-attachment-deletion-client.d.ts +37 -0
- package/build/lib/device/xctest-attachment-deletion-client.d.ts.map +1 -0
- package/build/lib/device/xctest-attachment-deletion-client.js +82 -0
- package/build/lib/device/xctest-attachment-deletion-client.js.map +1 -0
- package/lib/commands/xctest-record-screen.ts +67 -13
- package/lib/device/remotexpc-utils.ts +146 -20
- package/lib/device/xctest-attachment-deletion-client.ts +116 -0
- package/npm-shrinkwrap.json +11 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [10.42.0](https://github.com/appium/appium-xcuitest-driver/compare/v10.41.0...v10.42.0) (2026-04-06)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* Use remotexpc to delete stale native video recordings ([#2795](https://github.com/appium/appium-xcuitest-driver/issues/2795)) ([2efb765](https://github.com/appium/appium-xcuitest-driver/commit/2efb7656cce9fb5e625b2076ca834b021996d87a))
|
|
6
|
+
|
|
1
7
|
## [10.41.0](https://github.com/appium/appium-xcuitest-driver/compare/v10.40.2...v10.41.0) (2026-04-02)
|
|
2
8
|
|
|
3
9
|
### Features
|
|
@@ -4,10 +4,11 @@ import type { XcTestScreenRecordingInfo, XcTestScreenRecording } from './types';
|
|
|
4
4
|
/**
|
|
5
5
|
* Start a new screen recording via XCTest.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* On **real devices**, if **iOS 18+** and a new enough **appium-ios-remotexpc** (with
|
|
8
|
+
* **XCTestAttachment**) are present, the attachment is removed after stop and the
|
|
9
|
+
* `xctest_screen_record` insecure feature is **not** required.
|
|
10
|
+
* If deletion cannot be performed (older iOS, package missing, or too old), you must enable
|
|
11
|
+
* the `xctest_screen_record` insecure feature to start recording.
|
|
11
12
|
*
|
|
12
13
|
* If the recording is already running this API is a noop.
|
|
13
14
|
*
|
|
@@ -31,10 +32,13 @@ export declare function mobileGetXctestScreenRecordingInfo(this: XCUITestDriver)
|
|
|
31
32
|
* The resulting movie is returned as base-64 string or is uploaded to
|
|
32
33
|
* a remote location if corresponding options have been provided.
|
|
33
34
|
*
|
|
34
|
-
* The resulting movie is automatically deleted FOR SIMULATORS ONLY.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* The resulting movie is automatically deleted from the host temp file FOR SIMULATORS ONLY.
|
|
36
|
+
* On **real devices**, after a successful pull the driver removes the XCTest attachment via
|
|
37
|
+
* **appium-ios-remotexpc** when the same conditions hold as for starting without
|
|
38
|
+
* `xctest_screen_record` (iOS 18+, package present, **XCTestAttachment** export). Otherwise
|
|
39
|
+
* device-side delete is skipped. That deletion runs even if Base64 encoding or remote upload
|
|
40
|
+
* fails afterward (the original encode/upload error is still thrown); if both fail, delete errors
|
|
41
|
+
* are logged as warnings so the encode/upload failure remains primary.
|
|
38
42
|
*
|
|
39
43
|
* @since Xcode 15/iOS 17
|
|
40
44
|
* @param remotePath - The path to the remote location, where the resulting video should be
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xctest-record-screen.d.ts","sourceRoot":"","sources":["../../../lib/commands/xctest-record-screen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,WAAW,CAAC;AAG9C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAC,yBAAyB,EAAE,qBAAqB,EAAC,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"xctest-record-screen.d.ts","sourceRoot":"","sources":["../../../lib/commands/xctest-record-screen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,WAAW,CAAC;AAG9C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAC,yBAAyB,EAAE,qBAAqB,EAAC,MAAM,SAAS,CAAC;AAgE9E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gCAAgC,CACpD,IAAI,EAAE,cAAc,EACpB,GAAG,CAAC,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,yBAAyB,CAAC,CA2BpC;AAED;;;GAGG;AACH,wBAAsB,kCAAkC,CACtD,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAE3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,cAAc,EACpB,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,WAAW,EACrB,aAAa,CAAC,EAAE,MAAM,EACtB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAClD,MAAM,GAAE,KAAK,GAAG,MAAM,GAAG,OAAe,GACvC,OAAO,CAAC,qBAAqB,CAAC,CAqEhC"}
|
|
@@ -10,8 +10,10 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
10
10
|
const support_1 = require("appium/support");
|
|
11
11
|
const utils_1 = require("../utils");
|
|
12
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const xctest_attachment_deletion_client_1 = require("../device/xctest-attachment-deletion-client");
|
|
13
14
|
const MOV_EXT = '.mov';
|
|
14
|
-
|
|
15
|
+
/** Insecure feature when real-device XCTest recording is used without RemoteXPC attachment deletion. */
|
|
16
|
+
const XCTEST_SCREEN_RECORD_FEATURE = 'xctest_screen_record';
|
|
15
17
|
const DOMAIN_IDENTIFIER = 'com.apple.testmanagerd';
|
|
16
18
|
const DOMAIN_TYPE = 'appDataContainer';
|
|
17
19
|
const USERNAME = 'mobile';
|
|
@@ -64,10 +66,11 @@ async function retrieveXcTestScreenRecording(uuid) {
|
|
|
64
66
|
/**
|
|
65
67
|
* Start a new screen recording via XCTest.
|
|
66
68
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
69
|
+
* On **real devices**, if **iOS 18+** and a new enough **appium-ios-remotexpc** (with
|
|
70
|
+
* **XCTestAttachment**) are present, the attachment is removed after stop and the
|
|
71
|
+
* `xctest_screen_record` insecure feature is **not** required.
|
|
72
|
+
* If deletion cannot be performed (older iOS, package missing, or too old), you must enable
|
|
73
|
+
* the `xctest_screen_record` insecure feature to start recording.
|
|
71
74
|
*
|
|
72
75
|
* If the recording is already running this API is a noop.
|
|
73
76
|
*
|
|
@@ -79,10 +82,10 @@ async function retrieveXcTestScreenRecording(uuid) {
|
|
|
79
82
|
*/
|
|
80
83
|
async function mobileStartXctestScreenRecording(fps, codec) {
|
|
81
84
|
if (this.isRealDevice()) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const canDeleteAfterStop = await xctest_attachment_deletion_client_1.XctestAttachmentDeletionClient.isDeletionAvailable(this.opts.udid ?? '', this.opts.platformVersion ?? '', undefined, this.log);
|
|
86
|
+
if (!canDeleteAfterStop) {
|
|
87
|
+
this.assertFeatureEnabled(XCTEST_SCREEN_RECORD_FEATURE);
|
|
88
|
+
}
|
|
86
89
|
}
|
|
87
90
|
const opts = {};
|
|
88
91
|
if (lodash_1.default.isInteger(codec)) {
|
|
@@ -110,10 +113,13 @@ async function mobileGetXctestScreenRecordingInfo() {
|
|
|
110
113
|
* The resulting movie is returned as base-64 string or is uploaded to
|
|
111
114
|
* a remote location if corresponding options have been provided.
|
|
112
115
|
*
|
|
113
|
-
* The resulting movie is automatically deleted FOR SIMULATORS ONLY.
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
116
|
+
* The resulting movie is automatically deleted from the host temp file FOR SIMULATORS ONLY.
|
|
117
|
+
* On **real devices**, after a successful pull the driver removes the XCTest attachment via
|
|
118
|
+
* **appium-ios-remotexpc** when the same conditions hold as for starting without
|
|
119
|
+
* `xctest_screen_record` (iOS 18+, package present, **XCTestAttachment** export). Otherwise
|
|
120
|
+
* device-side delete is skipped. That deletion runs even if Base64 encoding or remote upload
|
|
121
|
+
* fails afterward (the original encode/upload error is still thrown); if both fail, delete errors
|
|
122
|
+
* are logged as warnings so the encode/upload failure remains primary.
|
|
117
123
|
*
|
|
118
124
|
* @since Xcode 15/iOS 17
|
|
119
125
|
* @param remotePath - The path to the remote location, where the resulting video should be
|
|
@@ -149,6 +155,8 @@ async function mobileStopXctestScreenRecording(remotePath, user, pass, headers,
|
|
|
149
155
|
...screenRecordingInfo,
|
|
150
156
|
payload: '', // Will be set below
|
|
151
157
|
};
|
|
158
|
+
let encodeOrUploadError;
|
|
159
|
+
let attachmentDeleteError;
|
|
152
160
|
try {
|
|
153
161
|
result.payload = await (0, utils_1.encodeBase64OrUpload)(videoPath, remotePath, {
|
|
154
162
|
user,
|
|
@@ -159,8 +167,37 @@ async function mobileStopXctestScreenRecording(remotePath, user, pass, headers,
|
|
|
159
167
|
method,
|
|
160
168
|
});
|
|
161
169
|
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
encodeOrUploadError = err;
|
|
172
|
+
}
|
|
162
173
|
finally {
|
|
163
174
|
await support_1.fs.rimraf(videoPath);
|
|
175
|
+
if (this.isRealDevice() && this.opts.udid) {
|
|
176
|
+
try {
|
|
177
|
+
const canDelete = await xctest_attachment_deletion_client_1.XctestAttachmentDeletionClient.isDeletionAvailable(this.opts.udid, this.opts.platformVersion ?? '', undefined, this.log);
|
|
178
|
+
if (canDelete) {
|
|
179
|
+
const deletionClient = await xctest_attachment_deletion_client_1.XctestAttachmentDeletionClient.create(this.opts.udid, this.opts.platformVersion ?? '');
|
|
180
|
+
await deletionClient.deleteAttachmentsByUuid([screenRecordingInfo.uuid]);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.log.debug('Skipping XCTest attachment deletion on device (RemoteXPC deletion not available for this session)');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (deleteErr) {
|
|
187
|
+
if (encodeOrUploadError === undefined) {
|
|
188
|
+
attachmentDeleteError = deleteErr;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.log.warn(`Could not delete XCTest attachment on device after stop (encode/upload had already failed): ${deleteErr?.message ?? deleteErr}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (encodeOrUploadError !== undefined) {
|
|
197
|
+
throw encodeOrUploadError;
|
|
198
|
+
}
|
|
199
|
+
if (attachmentDeleteError !== undefined) {
|
|
200
|
+
throw attachmentDeleteError;
|
|
164
201
|
}
|
|
165
202
|
return result;
|
|
166
203
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xctest-record-screen.js","sourceRoot":"","sources":["../../../lib/commands/xctest-record-screen.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"xctest-record-screen.js","sourceRoot":"","sources":["../../../lib/commands/xctest-record-screen.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Services, XCTestRunner } from 'appium-ios-remotexpc';
|
|
2
|
+
export type RemoteXPCEsmModule = typeof import('appium-ios-remotexpc');
|
|
2
3
|
/**
|
|
3
4
|
* Get the RemoteXPC Services module dynamically
|
|
4
5
|
*
|
|
@@ -10,6 +11,31 @@ import type { Services, XCTestRunner } from 'appium-ios-remotexpc';
|
|
|
10
11
|
* @throws {Error} If the module cannot be imported
|
|
11
12
|
*/
|
|
12
13
|
export declare function getRemoteXPCServices(): Promise<typeof Services>;
|
|
14
|
+
/**
|
|
15
|
+
* Try to load appium-ios-remotexpc without throwing (e.g. for optional features).
|
|
16
|
+
* Successful loads share the same cache as {@link getRemoteXPCServices}.
|
|
17
|
+
*
|
|
18
|
+
* If the package is **not installed** (resolution error for **appium-ios-remotexpc**), subsequent
|
|
19
|
+
* calls return `null` without re-importing. Other import failures are recorded via
|
|
20
|
+
* {@link getLastRemoteXPCOptionalImportError} and **do not** permanently disable retries.
|
|
21
|
+
*/
|
|
22
|
+
export declare function tryGetRemoteXPCServices(): Promise<typeof Services | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Whether {@link tryGetRemoteXPCServices} has determined that **appium-ios-remotexpc** is not
|
|
25
|
+
* installed (same process will not retry optional import).
|
|
26
|
+
*/
|
|
27
|
+
export declare function isRemoteXPCOptionalDependencyMissing(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Last error from an optional RemoteXPC `import()`, including transient failures. Cleared when a
|
|
30
|
+
* load succeeds. When {@link isRemoteXPCOptionalDependencyMissing} is `true`, this matches the
|
|
31
|
+
* stored missing-package error.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getLastRemoteXPCOptionalImportError(): Error | null;
|
|
34
|
+
/**
|
|
35
|
+
* Full **appium-ios-remotexpc** module after a successful optional load (same `import()` as
|
|
36
|
+
* {@link tryGetRemoteXPCServices}). Returns `null` if the package is missing or failed to load.
|
|
37
|
+
*/
|
|
38
|
+
export declare function tryGetRemoteXPCModule(): Promise<RemoteXPCEsmModule | null>;
|
|
13
39
|
/**
|
|
14
40
|
* Get the XCTestRunner class dynamically from appium-ios-remotexpc
|
|
15
41
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remotexpc-utils.d.ts","sourceRoot":"","sources":["../../../lib/device/remotexpc-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,QAAQ,EAAE,YAAY,EAAC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"remotexpc-utils.d.ts","sourceRoot":"","sources":["../../../lib/device/remotexpc-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,QAAQ,EAAE,YAAY,EAAC,MAAM,sBAAsB,CAAC;AAEjE,MAAM,MAAM,kBAAkB,GAAG,cAAc,sBAAsB,CAAC,CAAC;AAyEvE;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,QAAQ,CAAC,CAyBrE;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,QAAQ,GAAG,IAAI,CAAC,CAkC/E;AAED;;;GAGG;AACH,wBAAgB,oCAAoC,IAAI,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,KAAK,GAAG,IAAI,CAElE;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAGhF;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,YAAY,CAAC,CA6BzE"}
|
|
@@ -4,14 +4,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getRemoteXPCServices = getRemoteXPCServices;
|
|
7
|
+
exports.tryGetRemoteXPCServices = tryGetRemoteXPCServices;
|
|
8
|
+
exports.isRemoteXPCOptionalDependencyMissing = isRemoteXPCOptionalDependencyMissing;
|
|
9
|
+
exports.getLastRemoteXPCOptionalImportError = getLastRemoteXPCOptionalImportError;
|
|
10
|
+
exports.tryGetRemoteXPCModule = tryGetRemoteXPCModule;
|
|
7
11
|
exports.getXCTestRunnerClass = getXCTestRunnerClass;
|
|
8
12
|
const support_1 = require("appium/support");
|
|
9
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
14
|
const node_fs_1 = require("node:fs");
|
|
15
|
+
/**
|
|
16
|
+
* Full ESM namespace after a successful `import('appium-ios-remotexpc')` (e.g. **XCTestAttachment**).
|
|
17
|
+
* Set together with {@link cachedRemoteXPCServices}.
|
|
18
|
+
*/
|
|
19
|
+
let cachedRemoteXPCFullModule = null;
|
|
11
20
|
/**
|
|
12
21
|
* Cached RemoteXPC Services module
|
|
13
22
|
*/
|
|
14
23
|
let cachedRemoteXPCServices = null;
|
|
24
|
+
/**
|
|
25
|
+
* Set when **appium-ios-remotexpc** resolution failed in a way that is unlikely to succeed on
|
|
26
|
+
* retry in the same process (package not installed). Transient import errors do not set this.
|
|
27
|
+
*/
|
|
28
|
+
let remoteXpcModuleUnavailable = false;
|
|
29
|
+
/** Stored only when {@link remoteXpcModuleUnavailable} is set (missing-package case). */
|
|
30
|
+
let lastRemoteXpcImportError = null;
|
|
31
|
+
/**
|
|
32
|
+
* Most recent failed optional `import('appium-ios-remotexpc')` from {@link tryGetRemoteXPCServices}
|
|
33
|
+
* (including full-module backfill). Cleared when an optional load succeeds.
|
|
34
|
+
*/
|
|
35
|
+
let lastTryGetRemoteXPCImportError = null;
|
|
15
36
|
/**
|
|
16
37
|
* Cached XCTestRunner class
|
|
17
38
|
*/
|
|
@@ -20,6 +41,31 @@ let cachedXCTestRunnerClass = null;
|
|
|
20
41
|
* Module root and version cached at initialization
|
|
21
42
|
*/
|
|
22
43
|
const { moduleRoot, remoteXpcVersion } = fetchInstallInfo();
|
|
44
|
+
/**
|
|
45
|
+
* Whether `err` indicates the **appium-ios-remotexpc** package is not installed / not resolvable,
|
|
46
|
+
* as opposed to a transient or corrupt-install failure that may succeed on a later attempt.
|
|
47
|
+
*/
|
|
48
|
+
function isAppiumIosRemotexpcPackageMissingError(err) {
|
|
49
|
+
const msg = err.message;
|
|
50
|
+
return ((msg.includes('Cannot find module') && msg.includes('appium-ios-remotexpc')) ||
|
|
51
|
+
(msg.includes('Cannot find package') && msg.includes('appium-ios-remotexpc')));
|
|
52
|
+
}
|
|
53
|
+
function throwRemoteXPCImportError(err) {
|
|
54
|
+
if (err.message.includes('Cannot find module')) {
|
|
55
|
+
let errorMessage = 'Failed to import appium-ios-remotexpc module. ' +
|
|
56
|
+
'This module is required for iOS 18 and above device operations.';
|
|
57
|
+
if (moduleRoot && remoteXpcVersion) {
|
|
58
|
+
errorMessage +=
|
|
59
|
+
' Please install it by running: ' +
|
|
60
|
+
`cd "${moduleRoot}" && npm install "appium-ios-remotexpc@${remoteXpcVersion}".`;
|
|
61
|
+
}
|
|
62
|
+
errorMessage += ` Original error: ${err.message}`;
|
|
63
|
+
throw new Error(errorMessage);
|
|
64
|
+
}
|
|
65
|
+
throw new Error('Failed to import appium-ios-remotexpc module. ' +
|
|
66
|
+
'This module is required for iOS 18 and above device operations. ' +
|
|
67
|
+
`Original error: ${err.message}`);
|
|
68
|
+
}
|
|
23
69
|
/**
|
|
24
70
|
* Get the RemoteXPC Services module dynamically
|
|
25
71
|
*
|
|
@@ -32,30 +78,98 @@ const { moduleRoot, remoteXpcVersion } = fetchInstallInfo();
|
|
|
32
78
|
*/
|
|
33
79
|
async function getRemoteXPCServices() {
|
|
34
80
|
if (cachedRemoteXPCServices) {
|
|
81
|
+
if (!cachedRemoteXPCFullModule) {
|
|
82
|
+
try {
|
|
83
|
+
cachedRemoteXPCFullModule = (await import('appium-ios-remotexpc'));
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
throwRemoteXPCImportError(err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
lastTryGetRemoteXPCImportError = null;
|
|
35
90
|
return cachedRemoteXPCServices;
|
|
36
91
|
}
|
|
92
|
+
if (remoteXpcModuleUnavailable && lastRemoteXpcImportError) {
|
|
93
|
+
throwRemoteXPCImportError(lastRemoteXpcImportError);
|
|
94
|
+
}
|
|
37
95
|
try {
|
|
38
|
-
const remotexpcModule = await import('appium-ios-remotexpc');
|
|
96
|
+
const remotexpcModule = (await import('appium-ios-remotexpc'));
|
|
97
|
+
cachedRemoteXPCFullModule = remotexpcModule;
|
|
39
98
|
cachedRemoteXPCServices = remotexpcModule.Services;
|
|
99
|
+
lastTryGetRemoteXPCImportError = null;
|
|
40
100
|
return cachedRemoteXPCServices;
|
|
41
101
|
}
|
|
42
102
|
catch (err) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
103
|
+
throwRemoteXPCImportError(err);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Try to load appium-ios-remotexpc without throwing (e.g. for optional features).
|
|
108
|
+
* Successful loads share the same cache as {@link getRemoteXPCServices}.
|
|
109
|
+
*
|
|
110
|
+
* If the package is **not installed** (resolution error for **appium-ios-remotexpc**), subsequent
|
|
111
|
+
* calls return `null` without re-importing. Other import failures are recorded via
|
|
112
|
+
* {@link getLastRemoteXPCOptionalImportError} and **do not** permanently disable retries.
|
|
113
|
+
*/
|
|
114
|
+
async function tryGetRemoteXPCServices() {
|
|
115
|
+
if (cachedRemoteXPCServices) {
|
|
116
|
+
if (!cachedRemoteXPCFullModule) {
|
|
117
|
+
try {
|
|
118
|
+
cachedRemoteXPCFullModule = (await import('appium-ios-remotexpc'));
|
|
119
|
+
lastTryGetRemoteXPCImportError = null;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
lastTryGetRemoteXPCImportError = err;
|
|
123
|
+
/* ignore: tryGetRemoteXPCModule may still return null for XCTestAttachment callers */
|
|
51
124
|
}
|
|
52
|
-
errorMessage += ` Original error: ${error.message}`;
|
|
53
|
-
throw new Error(errorMessage);
|
|
54
125
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
126
|
+
else {
|
|
127
|
+
lastTryGetRemoteXPCImportError = null;
|
|
128
|
+
}
|
|
129
|
+
return cachedRemoteXPCServices;
|
|
130
|
+
}
|
|
131
|
+
if (remoteXpcModuleUnavailable) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const remotexpcModule = (await import('appium-ios-remotexpc'));
|
|
136
|
+
cachedRemoteXPCFullModule = remotexpcModule;
|
|
137
|
+
cachedRemoteXPCServices = remotexpcModule.Services;
|
|
138
|
+
lastTryGetRemoteXPCImportError = null;
|
|
139
|
+
return cachedRemoteXPCServices;
|
|
58
140
|
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const error = err;
|
|
143
|
+
lastTryGetRemoteXPCImportError = error;
|
|
144
|
+
if (isAppiumIosRemotexpcPackageMissingError(error)) {
|
|
145
|
+
lastRemoteXpcImportError = error;
|
|
146
|
+
remoteXpcModuleUnavailable = true;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Whether {@link tryGetRemoteXPCServices} has determined that **appium-ios-remotexpc** is not
|
|
153
|
+
* installed (same process will not retry optional import).
|
|
154
|
+
*/
|
|
155
|
+
function isRemoteXPCOptionalDependencyMissing() {
|
|
156
|
+
return remoteXpcModuleUnavailable;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Last error from an optional RemoteXPC `import()`, including transient failures. Cleared when a
|
|
160
|
+
* load succeeds. When {@link isRemoteXPCOptionalDependencyMissing} is `true`, this matches the
|
|
161
|
+
* stored missing-package error.
|
|
162
|
+
*/
|
|
163
|
+
function getLastRemoteXPCOptionalImportError() {
|
|
164
|
+
return lastTryGetRemoteXPCImportError;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Full **appium-ios-remotexpc** module after a successful optional load (same `import()` as
|
|
168
|
+
* {@link tryGetRemoteXPCServices}). Returns `null` if the package is missing or failed to load.
|
|
169
|
+
*/
|
|
170
|
+
async function tryGetRemoteXPCModule() {
|
|
171
|
+
await tryGetRemoteXPCServices();
|
|
172
|
+
return cachedRemoteXPCFullModule;
|
|
59
173
|
}
|
|
60
174
|
/**
|
|
61
175
|
* Get the XCTestRunner class dynamically from appium-ios-remotexpc
|
|
@@ -67,8 +181,12 @@ async function getXCTestRunnerClass() {
|
|
|
67
181
|
if (cachedXCTestRunnerClass) {
|
|
68
182
|
return cachedXCTestRunnerClass;
|
|
69
183
|
}
|
|
184
|
+
await getRemoteXPCServices();
|
|
185
|
+
const remotexpcModule = cachedRemoteXPCFullModule;
|
|
186
|
+
if (!remotexpcModule) {
|
|
187
|
+
throw new Error('appium-ios-remotexpc loaded Services but full module cache is missing; cannot load XCTestRunner.');
|
|
188
|
+
}
|
|
70
189
|
try {
|
|
71
|
-
const remotexpcModule = await import('appium-ios-remotexpc');
|
|
72
190
|
const XCTestRunnerClass = remotexpcModule.XCTestRunner;
|
|
73
191
|
if (typeof XCTestRunnerClass !== 'function') {
|
|
74
192
|
throw new Error('XCTestRunner is not exported from appium-ios-remotexpc. ' +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remotexpc-utils.js","sourceRoot":"","sources":["../../../lib/device/remotexpc-utils.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"remotexpc-utils.js","sourceRoot":"","sources":["../../../lib/device/remotexpc-utils.ts"],"names":[],"mappings":";;;;;AAwFA,oDAyBC;AAUD,0DAkCC;AAMD,oFAEC;AAOD,kFAEC;AAMD,sDAGC;AAQD,oDA6BC;AA5ND,4CAAoC;AACpC,0DAA6B;AAC7B,qCAAqC;AAKrC;;;GAGG;AACH,IAAI,yBAAyB,GAA8B,IAAI,CAAC;AAEhE;;GAEG;AACH,IAAI,uBAAuB,GAA2B,IAAI,CAAC;AAE3D;;;GAGG;AACH,IAAI,0BAA0B,GAAG,KAAK,CAAC;AAEvC,yFAAyF;AACzF,IAAI,wBAAwB,GAAiB,IAAI,CAAC;AAElD;;;GAGG;AACH,IAAI,8BAA8B,GAAiB,IAAI,CAAC;AAExD;;GAEG;AACH,IAAI,uBAAuB,GAA+B,IAAI,CAAC;AAE/D;;GAEG;AACH,MAAM,EAAC,UAAU,EAAE,gBAAgB,EAAC,GAAG,gBAAgB,EAAE,CAAC;AAE1D;;;GAGG;AACH,SAAS,uCAAuC,CAAC,GAAU;IACzD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC;IACxB,OAAO,CACL,CAAC,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;QAC5E,CAAC,GAAG,CAAC,QAAQ,CAAC,qBAAqB,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAC9E,CAAC;AACJ,CAAC;AAED,SAAS,yBAAyB,CAAC,GAAU;IAC3C,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC/C,IAAI,YAAY,GACd,gDAAgD;YAChD,iEAAiE,CAAC;QAEpE,IAAI,UAAU,IAAI,gBAAgB,EAAE,CAAC;YACnC,YAAY;gBACV,iCAAiC;oBACjC,OAAO,UAAU,0CAA0C,gBAAgB,IAAI,CAAC;QACpF,CAAC;QAED,YAAY,IAAI,oBAAoB,GAAG,CAAC,OAAO,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,IAAI,KAAK,CACb,gDAAgD;QAC9C,kEAAkE;QAClE,mBAAmB,GAAG,CAAC,OAAO,EAAE,CACnC,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACI,KAAK,UAAU,oBAAoB;IACxC,IAAI,uBAAuB,EAAE,CAAC;QAC5B,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,yBAAyB,GAAG,CAAC,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAuB,CAAC;YAC3F,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,yBAAyB,CAAC,GAAY,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;QACD,8BAA8B,GAAG,IAAI,CAAC;QACtC,OAAO,uBAAuB,CAAC;IACjC,CAAC;IACD,IAAI,0BAA0B,IAAI,wBAAwB,EAAE,CAAC;QAC3D,yBAAyB,CAAC,wBAAwB,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,CAAC,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAuB,CAAC;QACrF,yBAAyB,GAAG,eAAe,CAAC;QAC5C,uBAAuB,GAAG,eAAe,CAAC,QAAQ,CAAC;QACnD,8BAA8B,GAAG,IAAI,CAAC;QACtC,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,yBAAyB,CAAC,GAAY,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,uBAAuB;IAC3C,IAAI,uBAAuB,EAAE,CAAC;QAC5B,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,yBAAyB,GAAG,CAAC,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAuB,CAAC;gBACzF,8BAA8B,GAAG,IAAI,CAAC;YACxC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,8BAA8B,GAAG,GAAY,CAAC;gBAC9C,sFAAsF;YACxF,CAAC;QACH,CAAC;aAAM,CAAC;YACN,8BAA8B,GAAG,IAAI,CAAC;QACxC,CAAC;QACD,OAAO,uBAAuB,CAAC;IACjC,CAAC;IACD,IAAI,0BAA0B,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,CAAC,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAuB,CAAC;QACrF,yBAAyB,GAAG,eAAe,CAAC;QAC5C,uBAAuB,GAAG,eAAe,CAAC,QAAQ,CAAC;QACnD,8BAA8B,GAAG,IAAI,CAAC;QACtC,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,GAAY,CAAC;QAC3B,8BAA8B,GAAG,KAAK,CAAC;QACvC,IAAI,uCAAuC,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,wBAAwB,GAAG,KAAK,CAAC;YACjC,0BAA0B,GAAG,IAAI,CAAC;QACpC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,oCAAoC;IAClD,OAAO,0BAA0B,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,SAAgB,mCAAmC;IACjD,OAAO,8BAA8B,CAAC;AACxC,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,qBAAqB;IACzC,MAAM,uBAAuB,EAAE,CAAC;IAChC,OAAO,yBAAyB,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,oBAAoB;IACxC,IAAI,uBAAuB,EAAE,CAAC;QAC5B,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAED,MAAM,oBAAoB,EAAE,CAAC;IAC7B,MAAM,eAAe,GAAG,yBAAyB,CAAC;IAClD,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,kGAAkG,CACnG,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,iBAAiB,GAAG,eAAe,CAAC,YAAY,CAAC;QACvD,IAAI,OAAO,iBAAiB,KAAK,UAAU,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CACb,0DAA0D;gBACxD,4CAA4C,CAC/C,CAAC;QACJ,CAAC;QACD,uBAAuB,GAAG,iBAAiB,CAAC;QAC5C,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,GAAY,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,2DAA2D;YACzD,mBAAmB,KAAK,CAAC,OAAO,EAAE,CACrC,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB;IAIvB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,cAAI,CAAC,iBAAiB,CAAC,wBAAwB,EAAE,UAAU,CAAC,CAAC;QAC1E,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,eAAe,GAAG,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;YACxD,MAAM,kBAAkB,GAAG,IAAA,sBAAY,EAAC,eAAe,EAAE,MAAM,CAAC,CAAC;YACjE,IAAI,kBAAkB,EAAE,CAAC;gBACvB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;gBACnD,OAAO;oBACL,UAAU,EAAE,IAAI;oBAChB,gBAAgB,EAAE,WAAW,CAAC,oBAAoB,EAAE,CAAC,sBAAsB,CAAC;iBAC7E,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;IAED,OAAO,EAAC,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AppiumLogger } from '@appium/types';
|
|
2
|
+
type XCTestAttachmentCtor = new (udid: string) => {
|
|
3
|
+
delete(uuids: string[]): Promise<unknown>;
|
|
4
|
+
};
|
|
5
|
+
/** Subset of appium-ios-remotexpc exports used here (types may lag the runtime package). */
|
|
6
|
+
export type RemotexpcAttachmentModule = {
|
|
7
|
+
XCTestAttachment?: XCTestAttachmentCtor;
|
|
8
|
+
};
|
|
9
|
+
export interface IXctestAttachmentDeletionClient {
|
|
10
|
+
deleteAttachmentsByUuid(uuids: string[]): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Deletes XCTest screen-recording attachments on a real device via appium-ios-remotexpc
|
|
14
|
+
* (testmanagerd). Use {@link isDeletionAvailable} before start; {@link create} returns a client
|
|
15
|
+
* ready to delete (or throws if deletion cannot run).
|
|
16
|
+
*/
|
|
17
|
+
export declare class XctestAttachmentDeletionClient implements IXctestAttachmentDeletionClient {
|
|
18
|
+
private readonly udid;
|
|
19
|
+
private readonly XCTestAttachment;
|
|
20
|
+
private constructor();
|
|
21
|
+
/**
|
|
22
|
+
* Whether attachment deletion can run: **iOS 18+**, **appium-ios-remotexpc** loadable, and
|
|
23
|
+
* **XCTestAttachment** exported (new enough package).
|
|
24
|
+
*
|
|
25
|
+
* @param remotexpcModule - optional pre-loaded module (for unit tests)
|
|
26
|
+
* @param log - if set, warns when the package is present but too old (no **XCTestAttachment** export)
|
|
27
|
+
*/
|
|
28
|
+
static isDeletionAvailable(udid: string, platformVersion: string, remotexpcModule?: RemotexpcAttachmentModule | null, log?: AppiumLogger): Promise<boolean>;
|
|
29
|
+
/**
|
|
30
|
+
* @param remotexpcModule - optional pre-loaded module (for unit tests)
|
|
31
|
+
* @throws {Error} If real-device XCTest attachment deletion is not supported
|
|
32
|
+
*/
|
|
33
|
+
static create(udid: string, platformVersion: string, remotexpcModule?: RemotexpcAttachmentModule | null): Promise<XctestAttachmentDeletionClient>;
|
|
34
|
+
deleteAttachmentsByUuid(uuids: string[]): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=xctest-attachment-deletion-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xctest-attachment-deletion-client.d.ts","sourceRoot":"","sources":["../../../lib/device/xctest-attachment-deletion-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,eAAe,CAAC;AAQhD,KAAK,oBAAoB,GAAG,KAAK,IAAI,EAAE,MAAM,KAAK;IAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC;AAE9F,4FAA4F;AAC5F,MAAM,MAAM,yBAAyB,GAAG;IACtC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;CACzC,CAAC;AAEF,MAAM,WAAW,+BAA+B;IAC9C,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED;;;;GAIG;AACH,qBAAa,8BAA+B,YAAW,+BAA+B;IAElF,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAFnC,OAAO;IAKP;;;;;;OAMG;WACU,mBAAmB,CAC9B,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,EACvB,eAAe,CAAC,EAAE,yBAAyB,GAAG,IAAI,EAClD,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,OAAO,CAAC;IAoBnB;;;OAGG;WACU,MAAM,CACjB,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,EACvB,eAAe,CAAC,EAAE,yBAAyB,GAAG,IAAI,GACjD,OAAO,CAAC,8BAA8B,CAAC;IAsCpC,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAO9D"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.XctestAttachmentDeletionClient = void 0;
|
|
4
|
+
const remotexpc_utils_1 = require("./remotexpc-utils");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
const REMOTEXPC_UPGRADE_HINT = 'Upgrade appium-ios-remotexpc to a version that exports XCTestAttachment ' +
|
|
7
|
+
'(XCTest screen recording attachment deletion on real devices).';
|
|
8
|
+
/**
|
|
9
|
+
* Deletes XCTest screen-recording attachments on a real device via appium-ios-remotexpc
|
|
10
|
+
* (testmanagerd). Use {@link isDeletionAvailable} before start; {@link create} returns a client
|
|
11
|
+
* ready to delete (or throws if deletion cannot run).
|
|
12
|
+
*/
|
|
13
|
+
class XctestAttachmentDeletionClient {
|
|
14
|
+
udid;
|
|
15
|
+
XCTestAttachment;
|
|
16
|
+
constructor(udid, XCTestAttachment) {
|
|
17
|
+
this.udid = udid;
|
|
18
|
+
this.XCTestAttachment = XCTestAttachment;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether attachment deletion can run: **iOS 18+**, **appium-ios-remotexpc** loadable, and
|
|
22
|
+
* **XCTestAttachment** exported (new enough package).
|
|
23
|
+
*
|
|
24
|
+
* @param remotexpcModule - optional pre-loaded module (for unit tests)
|
|
25
|
+
* @param log - if set, warns when the package is present but too old (no **XCTestAttachment** export)
|
|
26
|
+
*/
|
|
27
|
+
static async isDeletionAvailable(udid, platformVersion, remotexpcModule, log) {
|
|
28
|
+
if (!udid?.trim() || !(0, utils_1.isIos18OrNewerPlatform)(platformVersion)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
let mod = remotexpcModule;
|
|
32
|
+
if (mod === undefined) {
|
|
33
|
+
mod = (await (0, remotexpc_utils_1.tryGetRemoteXPCModule)());
|
|
34
|
+
if (!mod) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (typeof mod?.XCTestAttachment === 'function') {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (mod && log) {
|
|
42
|
+
log.warn(REMOTEXPC_UPGRADE_HINT);
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @param remotexpcModule - optional pre-loaded module (for unit tests)
|
|
48
|
+
* @throws {Error} If real-device XCTest attachment deletion is not supported
|
|
49
|
+
*/
|
|
50
|
+
static async create(udid, platformVersion, remotexpcModule) {
|
|
51
|
+
if (!udid?.trim()) {
|
|
52
|
+
throw new Error('A device UDID is required for XCTest screen recording on a real device so attachments can be removed after stop.');
|
|
53
|
+
}
|
|
54
|
+
if (!(0, utils_1.isIos18OrNewerPlatform)(platformVersion)) {
|
|
55
|
+
throw new Error('XCTest screen recording on a real device requires iOS 18 or newer. ' +
|
|
56
|
+
'The driver removes recordings from the device after stop using appium-ios-remotexpc, which is only supported on iOS 18+.');
|
|
57
|
+
}
|
|
58
|
+
let mod = remotexpcModule;
|
|
59
|
+
if (mod === undefined) {
|
|
60
|
+
mod = (await (0, remotexpc_utils_1.tryGetRemoteXPCModule)());
|
|
61
|
+
if (!mod) {
|
|
62
|
+
throw new Error('appium-ios-remotexpc must be installed to use XCTest screen recording on a real device. ' +
|
|
63
|
+
'It is used to delete screen-recording attachments after stop.');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const XCTestAttachment = mod?.XCTestAttachment;
|
|
67
|
+
if (!mod || typeof XCTestAttachment !== 'function') {
|
|
68
|
+
throw new Error('The installed appium-ios-remotexpc package must export XCTestAttachment for real-device XCTest screen recording. ' +
|
|
69
|
+
REMOTEXPC_UPGRADE_HINT);
|
|
70
|
+
}
|
|
71
|
+
return new XctestAttachmentDeletionClient(udid.trim(), XCTestAttachment);
|
|
72
|
+
}
|
|
73
|
+
async deleteAttachmentsByUuid(uuids) {
|
|
74
|
+
if (!uuids.length) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const attachment = new this.XCTestAttachment(this.udid);
|
|
78
|
+
await attachment.delete(uuids);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.XctestAttachmentDeletionClient = XctestAttachmentDeletionClient;
|
|
82
|
+
//# sourceMappingURL=xctest-attachment-deletion-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xctest-attachment-deletion-client.js","sourceRoot":"","sources":["../../../lib/device/xctest-attachment-deletion-client.ts"],"names":[],"mappings":";;;AACA,uDAAwD;AACxD,oCAAgD;AAEhD,MAAM,sBAAsB,GAC1B,0EAA0E;IAC1E,gEAAgE,CAAC;AAanE;;;;GAIG;AACH,MAAa,8BAA8B;IAEtB;IACA;IAFnB,YACmB,IAAY,EACZ,gBAAsC;QADtC,SAAI,GAAJ,IAAI,CAAQ;QACZ,qBAAgB,GAAhB,gBAAgB,CAAsB;IACtD,CAAC;IAEJ;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAC9B,IAAY,EACZ,eAAuB,EACvB,eAAkD,EAClD,GAAkB;QAElB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAA,8BAAsB,EAAC,eAAe,CAAC,EAAE,CAAC;YAC9D,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,GAAG,GAAiD,eAAe,CAAC;QACxE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,GAAG,GAAG,CAAC,MAAM,IAAA,uCAAqB,GAAE,CAAqC,CAAC;YAC1E,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,IAAI,OAAO,GAAG,EAAE,gBAAgB,KAAK,UAAU,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;YACf,GAAG,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CACjB,IAAY,EACZ,eAAuB,EACvB,eAAkD;QAElD,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,kHAAkH,CACnH,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAA,8BAAsB,EAAC,eAAe,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CACb,qEAAqE;gBACnE,0HAA0H,CAC7H,CAAC;QACJ,CAAC;QAED,IAAI,GAAG,GAAiD,eAAe,CAAC;QACxE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,GAAG,GAAG,CAAC,MAAM,IAAA,uCAAqB,GAAE,CAAqC,CAAC;YAC1E,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CACb,0FAA0F;oBACxF,+DAA+D,CAClE,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,gBAAgB,GAAG,GAAG,EAAE,gBAAgB,CAAC;QAC/C,IAAI,CAAC,GAAG,IAAI,OAAO,gBAAgB,KAAK,UAAU,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,mHAAmH;gBACjH,sBAAsB,CACzB,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,8BAA8B,CACvC,IAAI,CAAC,IAAI,EAAE,EACX,gBAAwC,CACzC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,uBAAuB,CAAC,KAAe;QAC3C,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;CACF;AA3FD,wEA2FC"}
|
|
@@ -7,9 +7,11 @@ import type {Simulator} from 'appium-ios-simulator';
|
|
|
7
7
|
import type {RealDevice} from '../device/real-device-management';
|
|
8
8
|
import type {HTTPHeaders} from '@appium/types';
|
|
9
9
|
import type {XcTestScreenRecordingInfo, XcTestScreenRecording} from './types';
|
|
10
|
+
import {XctestAttachmentDeletionClient} from '../device/xctest-attachment-deletion-client';
|
|
10
11
|
|
|
11
12
|
const MOV_EXT = '.mov';
|
|
12
|
-
|
|
13
|
+
/** Insecure feature when real-device XCTest recording is used without RemoteXPC attachment deletion. */
|
|
14
|
+
const XCTEST_SCREEN_RECORD_FEATURE = 'xctest_screen_record';
|
|
13
15
|
const DOMAIN_IDENTIFIER = 'com.apple.testmanagerd';
|
|
14
16
|
const DOMAIN_TYPE = 'appDataContainer';
|
|
15
17
|
const USERNAME = 'mobile';
|
|
@@ -71,10 +73,11 @@ async function retrieveXcTestScreenRecording(this: XCUITestDriver, uuid: string)
|
|
|
71
73
|
/**
|
|
72
74
|
* Start a new screen recording via XCTest.
|
|
73
75
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
76
|
+
* On **real devices**, if **iOS 18+** and a new enough **appium-ios-remotexpc** (with
|
|
77
|
+
* **XCTestAttachment**) are present, the attachment is removed after stop and the
|
|
78
|
+
* `xctest_screen_record` insecure feature is **not** required.
|
|
79
|
+
* If deletion cannot be performed (older iOS, package missing, or too old), you must enable
|
|
80
|
+
* the `xctest_screen_record` insecure feature to start recording.
|
|
78
81
|
*
|
|
79
82
|
* If the recording is already running this API is a noop.
|
|
80
83
|
*
|
|
@@ -90,10 +93,15 @@ export async function mobileStartXctestScreenRecording(
|
|
|
90
93
|
codec?: number,
|
|
91
94
|
): Promise<XcTestScreenRecordingInfo> {
|
|
92
95
|
if (this.isRealDevice()) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
const canDeleteAfterStop = await XctestAttachmentDeletionClient.isDeletionAvailable(
|
|
97
|
+
this.opts.udid ?? '',
|
|
98
|
+
this.opts.platformVersion ?? '',
|
|
99
|
+
undefined,
|
|
100
|
+
this.log,
|
|
101
|
+
);
|
|
102
|
+
if (!canDeleteAfterStop) {
|
|
103
|
+
this.assertFeatureEnabled(XCTEST_SCREEN_RECORD_FEATURE);
|
|
104
|
+
}
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
const opts: {codec?: number; fps?: number} = {};
|
|
@@ -130,10 +138,13 @@ export async function mobileGetXctestScreenRecordingInfo(
|
|
|
130
138
|
* The resulting movie is returned as base-64 string or is uploaded to
|
|
131
139
|
* a remote location if corresponding options have been provided.
|
|
132
140
|
*
|
|
133
|
-
* The resulting movie is automatically deleted FOR SIMULATORS ONLY.
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
141
|
+
* The resulting movie is automatically deleted from the host temp file FOR SIMULATORS ONLY.
|
|
142
|
+
* On **real devices**, after a successful pull the driver removes the XCTest attachment via
|
|
143
|
+
* **appium-ios-remotexpc** when the same conditions hold as for starting without
|
|
144
|
+
* `xctest_screen_record` (iOS 18+, package present, **XCTestAttachment** export). Otherwise
|
|
145
|
+
* device-side delete is skipped. That deletion runs even if Base64 encoding or remote upload
|
|
146
|
+
* fails afterward (the original encode/upload error is still thrown); if both fail, delete errors
|
|
147
|
+
* are logged as warnings so the encode/upload failure remains primary.
|
|
137
148
|
*
|
|
138
149
|
* @since Xcode 15/iOS 17
|
|
139
150
|
* @param remotePath - The path to the remote location, where the resulting video should be
|
|
@@ -179,6 +190,8 @@ export async function mobileStopXctestScreenRecording(
|
|
|
179
190
|
...screenRecordingInfo,
|
|
180
191
|
payload: '', // Will be set below
|
|
181
192
|
};
|
|
193
|
+
let encodeOrUploadError: unknown;
|
|
194
|
+
let attachmentDeleteError: unknown;
|
|
182
195
|
try {
|
|
183
196
|
result.payload = await encodeBase64OrUpload(videoPath, remotePath, {
|
|
184
197
|
user,
|
|
@@ -188,8 +201,49 @@ export async function mobileStopXctestScreenRecording(
|
|
|
188
201
|
formFields,
|
|
189
202
|
method,
|
|
190
203
|
});
|
|
204
|
+
} catch (err) {
|
|
205
|
+
encodeOrUploadError = err;
|
|
191
206
|
} finally {
|
|
192
207
|
await fs.rimraf(videoPath);
|
|
208
|
+
if (this.isRealDevice() && this.opts.udid) {
|
|
209
|
+
try {
|
|
210
|
+
const canDelete = await XctestAttachmentDeletionClient.isDeletionAvailable(
|
|
211
|
+
this.opts.udid,
|
|
212
|
+
this.opts.platformVersion ?? '',
|
|
213
|
+
undefined,
|
|
214
|
+
this.log,
|
|
215
|
+
);
|
|
216
|
+
if (canDelete) {
|
|
217
|
+
const deletionClient = await XctestAttachmentDeletionClient.create(
|
|
218
|
+
this.opts.udid,
|
|
219
|
+
this.opts.platformVersion ?? '',
|
|
220
|
+
);
|
|
221
|
+
await deletionClient.deleteAttachmentsByUuid([screenRecordingInfo.uuid]);
|
|
222
|
+
} else {
|
|
223
|
+
this.log.debug(
|
|
224
|
+
'Skipping XCTest attachment deletion on device (RemoteXPC deletion not available for this session)',
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
} catch (deleteErr: any) {
|
|
228
|
+
if (encodeOrUploadError === undefined) {
|
|
229
|
+
attachmentDeleteError = deleteErr;
|
|
230
|
+
} else {
|
|
231
|
+
this.log.warn(
|
|
232
|
+
`Could not delete XCTest attachment on device after stop (encode/upload had already failed): ${
|
|
233
|
+
deleteErr?.message ?? deleteErr
|
|
234
|
+
}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (encodeOrUploadError !== undefined) {
|
|
242
|
+
throw encodeOrUploadError;
|
|
193
243
|
}
|
|
244
|
+
if (attachmentDeleteError !== undefined) {
|
|
245
|
+
throw attachmentDeleteError;
|
|
246
|
+
}
|
|
247
|
+
|
|
194
248
|
return result;
|
|
195
249
|
}
|
|
@@ -3,11 +3,34 @@ import path from 'node:path';
|
|
|
3
3
|
import {readFileSync} from 'node:fs';
|
|
4
4
|
import type {Services, XCTestRunner} from 'appium-ios-remotexpc';
|
|
5
5
|
|
|
6
|
+
export type RemoteXPCEsmModule = typeof import('appium-ios-remotexpc');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Full ESM namespace after a successful `import('appium-ios-remotexpc')` (e.g. **XCTestAttachment**).
|
|
10
|
+
* Set together with {@link cachedRemoteXPCServices}.
|
|
11
|
+
*/
|
|
12
|
+
let cachedRemoteXPCFullModule: RemoteXPCEsmModule | null = null;
|
|
13
|
+
|
|
6
14
|
/**
|
|
7
15
|
* Cached RemoteXPC Services module
|
|
8
16
|
*/
|
|
9
17
|
let cachedRemoteXPCServices: typeof Services | null = null;
|
|
10
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Set when **appium-ios-remotexpc** resolution failed in a way that is unlikely to succeed on
|
|
21
|
+
* retry in the same process (package not installed). Transient import errors do not set this.
|
|
22
|
+
*/
|
|
23
|
+
let remoteXpcModuleUnavailable = false;
|
|
24
|
+
|
|
25
|
+
/** Stored only when {@link remoteXpcModuleUnavailable} is set (missing-package case). */
|
|
26
|
+
let lastRemoteXpcImportError: Error | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Most recent failed optional `import('appium-ios-remotexpc')` from {@link tryGetRemoteXPCServices}
|
|
30
|
+
* (including full-module backfill). Cleared when an optional load succeeds.
|
|
31
|
+
*/
|
|
32
|
+
let lastTryGetRemoteXPCImportError: Error | null = null;
|
|
33
|
+
|
|
11
34
|
/**
|
|
12
35
|
* Cached XCTestRunner class
|
|
13
36
|
*/
|
|
@@ -18,6 +41,41 @@ let cachedXCTestRunnerClass: typeof XCTestRunner | null = null;
|
|
|
18
41
|
*/
|
|
19
42
|
const {moduleRoot, remoteXpcVersion} = fetchInstallInfo();
|
|
20
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Whether `err` indicates the **appium-ios-remotexpc** package is not installed / not resolvable,
|
|
46
|
+
* as opposed to a transient or corrupt-install failure that may succeed on a later attempt.
|
|
47
|
+
*/
|
|
48
|
+
function isAppiumIosRemotexpcPackageMissingError(err: Error): boolean {
|
|
49
|
+
const msg = err.message;
|
|
50
|
+
return (
|
|
51
|
+
(msg.includes('Cannot find module') && msg.includes('appium-ios-remotexpc')) ||
|
|
52
|
+
(msg.includes('Cannot find package') && msg.includes('appium-ios-remotexpc'))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function throwRemoteXPCImportError(err: Error): never {
|
|
57
|
+
if (err.message.includes('Cannot find module')) {
|
|
58
|
+
let errorMessage =
|
|
59
|
+
'Failed to import appium-ios-remotexpc module. ' +
|
|
60
|
+
'This module is required for iOS 18 and above device operations.';
|
|
61
|
+
|
|
62
|
+
if (moduleRoot && remoteXpcVersion) {
|
|
63
|
+
errorMessage +=
|
|
64
|
+
' Please install it by running: ' +
|
|
65
|
+
`cd "${moduleRoot}" && npm install "appium-ios-remotexpc@${remoteXpcVersion}".`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
errorMessage += ` Original error: ${err.message}`;
|
|
69
|
+
throw new Error(errorMessage);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
throw new Error(
|
|
73
|
+
'Failed to import appium-ios-remotexpc module. ' +
|
|
74
|
+
'This module is required for iOS 18 and above device operations. ' +
|
|
75
|
+
`Original error: ${err.message}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
21
79
|
/**
|
|
22
80
|
* Get the RemoteXPC Services module dynamically
|
|
23
81
|
*
|
|
@@ -30,39 +88,101 @@ const {moduleRoot, remoteXpcVersion} = fetchInstallInfo();
|
|
|
30
88
|
*/
|
|
31
89
|
export async function getRemoteXPCServices(): Promise<typeof Services> {
|
|
32
90
|
if (cachedRemoteXPCServices) {
|
|
91
|
+
if (!cachedRemoteXPCFullModule) {
|
|
92
|
+
try {
|
|
93
|
+
cachedRemoteXPCFullModule = (await import('appium-ios-remotexpc')) as RemoteXPCEsmModule;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throwRemoteXPCImportError(err as Error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
lastTryGetRemoteXPCImportError = null;
|
|
33
99
|
return cachedRemoteXPCServices;
|
|
34
100
|
}
|
|
101
|
+
if (remoteXpcModuleUnavailable && lastRemoteXpcImportError) {
|
|
102
|
+
throwRemoteXPCImportError(lastRemoteXpcImportError);
|
|
103
|
+
}
|
|
35
104
|
|
|
36
105
|
try {
|
|
37
|
-
const remotexpcModule = await import('appium-ios-remotexpc');
|
|
106
|
+
const remotexpcModule = (await import('appium-ios-remotexpc')) as RemoteXPCEsmModule;
|
|
107
|
+
cachedRemoteXPCFullModule = remotexpcModule;
|
|
38
108
|
cachedRemoteXPCServices = remotexpcModule.Services;
|
|
109
|
+
lastTryGetRemoteXPCImportError = null;
|
|
39
110
|
return cachedRemoteXPCServices;
|
|
40
111
|
} catch (err) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
let errorMessage =
|
|
45
|
-
'Failed to import appium-ios-remotexpc module. ' +
|
|
46
|
-
'This module is required for iOS 18 and above device operations.';
|
|
112
|
+
throwRemoteXPCImportError(err as Error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
47
115
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Try to load appium-ios-remotexpc without throwing (e.g. for optional features).
|
|
118
|
+
* Successful loads share the same cache as {@link getRemoteXPCServices}.
|
|
119
|
+
*
|
|
120
|
+
* If the package is **not installed** (resolution error for **appium-ios-remotexpc**), subsequent
|
|
121
|
+
* calls return `null` without re-importing. Other import failures are recorded via
|
|
122
|
+
* {@link getLastRemoteXPCOptionalImportError} and **do not** permanently disable retries.
|
|
123
|
+
*/
|
|
124
|
+
export async function tryGetRemoteXPCServices(): Promise<typeof Services | null> {
|
|
125
|
+
if (cachedRemoteXPCServices) {
|
|
126
|
+
if (!cachedRemoteXPCFullModule) {
|
|
127
|
+
try {
|
|
128
|
+
cachedRemoteXPCFullModule = (await import('appium-ios-remotexpc')) as RemoteXPCEsmModule;
|
|
129
|
+
lastTryGetRemoteXPCImportError = null;
|
|
130
|
+
} catch (err) {
|
|
131
|
+
lastTryGetRemoteXPCImportError = err as Error;
|
|
132
|
+
/* ignore: tryGetRemoteXPCModule may still return null for XCTestAttachment callers */
|
|
52
133
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
throw new Error(errorMessage);
|
|
134
|
+
} else {
|
|
135
|
+
lastTryGetRemoteXPCImportError = null;
|
|
56
136
|
}
|
|
137
|
+
return cachedRemoteXPCServices;
|
|
138
|
+
}
|
|
139
|
+
if (remoteXpcModuleUnavailable) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
57
142
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
143
|
+
try {
|
|
144
|
+
const remotexpcModule = (await import('appium-ios-remotexpc')) as RemoteXPCEsmModule;
|
|
145
|
+
cachedRemoteXPCFullModule = remotexpcModule;
|
|
146
|
+
cachedRemoteXPCServices = remotexpcModule.Services;
|
|
147
|
+
lastTryGetRemoteXPCImportError = null;
|
|
148
|
+
return cachedRemoteXPCServices;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
const error = err as Error;
|
|
151
|
+
lastTryGetRemoteXPCImportError = error;
|
|
152
|
+
if (isAppiumIosRemotexpcPackageMissingError(error)) {
|
|
153
|
+
lastRemoteXpcImportError = error;
|
|
154
|
+
remoteXpcModuleUnavailable = true;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
63
157
|
}
|
|
64
158
|
}
|
|
65
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Whether {@link tryGetRemoteXPCServices} has determined that **appium-ios-remotexpc** is not
|
|
162
|
+
* installed (same process will not retry optional import).
|
|
163
|
+
*/
|
|
164
|
+
export function isRemoteXPCOptionalDependencyMissing(): boolean {
|
|
165
|
+
return remoteXpcModuleUnavailable;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Last error from an optional RemoteXPC `import()`, including transient failures. Cleared when a
|
|
170
|
+
* load succeeds. When {@link isRemoteXPCOptionalDependencyMissing} is `true`, this matches the
|
|
171
|
+
* stored missing-package error.
|
|
172
|
+
*/
|
|
173
|
+
export function getLastRemoteXPCOptionalImportError(): Error | null {
|
|
174
|
+
return lastTryGetRemoteXPCImportError;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Full **appium-ios-remotexpc** module after a successful optional load (same `import()` as
|
|
179
|
+
* {@link tryGetRemoteXPCServices}). Returns `null` if the package is missing or failed to load.
|
|
180
|
+
*/
|
|
181
|
+
export async function tryGetRemoteXPCModule(): Promise<RemoteXPCEsmModule | null> {
|
|
182
|
+
await tryGetRemoteXPCServices();
|
|
183
|
+
return cachedRemoteXPCFullModule;
|
|
184
|
+
}
|
|
185
|
+
|
|
66
186
|
/**
|
|
67
187
|
* Get the XCTestRunner class dynamically from appium-ios-remotexpc
|
|
68
188
|
*
|
|
@@ -74,8 +194,14 @@ export async function getXCTestRunnerClass(): Promise<typeof XCTestRunner> {
|
|
|
74
194
|
return cachedXCTestRunnerClass;
|
|
75
195
|
}
|
|
76
196
|
|
|
197
|
+
await getRemoteXPCServices();
|
|
198
|
+
const remotexpcModule = cachedRemoteXPCFullModule;
|
|
199
|
+
if (!remotexpcModule) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
'appium-ios-remotexpc loaded Services but full module cache is missing; cannot load XCTestRunner.',
|
|
202
|
+
);
|
|
203
|
+
}
|
|
77
204
|
try {
|
|
78
|
-
const remotexpcModule = await import('appium-ios-remotexpc');
|
|
79
205
|
const XCTestRunnerClass = remotexpcModule.XCTestRunner;
|
|
80
206
|
if (typeof XCTestRunnerClass !== 'function') {
|
|
81
207
|
throw new Error(
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type {AppiumLogger} from '@appium/types';
|
|
2
|
+
import {tryGetRemoteXPCModule} from './remotexpc-utils';
|
|
3
|
+
import {isIos18OrNewerPlatform} from '../utils';
|
|
4
|
+
|
|
5
|
+
const REMOTEXPC_UPGRADE_HINT =
|
|
6
|
+
'Upgrade appium-ios-remotexpc to a version that exports XCTestAttachment ' +
|
|
7
|
+
'(XCTest screen recording attachment deletion on real devices).';
|
|
8
|
+
|
|
9
|
+
type XCTestAttachmentCtor = new (udid: string) => {delete(uuids: string[]): Promise<unknown>};
|
|
10
|
+
|
|
11
|
+
/** Subset of appium-ios-remotexpc exports used here (types may lag the runtime package). */
|
|
12
|
+
export type RemotexpcAttachmentModule = {
|
|
13
|
+
XCTestAttachment?: XCTestAttachmentCtor;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface IXctestAttachmentDeletionClient {
|
|
17
|
+
deleteAttachmentsByUuid(uuids: string[]): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Deletes XCTest screen-recording attachments on a real device via appium-ios-remotexpc
|
|
22
|
+
* (testmanagerd). Use {@link isDeletionAvailable} before start; {@link create} returns a client
|
|
23
|
+
* ready to delete (or throws if deletion cannot run).
|
|
24
|
+
*/
|
|
25
|
+
export class XctestAttachmentDeletionClient implements IXctestAttachmentDeletionClient {
|
|
26
|
+
private constructor(
|
|
27
|
+
private readonly udid: string,
|
|
28
|
+
private readonly XCTestAttachment: XCTestAttachmentCtor,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether attachment deletion can run: **iOS 18+**, **appium-ios-remotexpc** loadable, and
|
|
33
|
+
* **XCTestAttachment** exported (new enough package).
|
|
34
|
+
*
|
|
35
|
+
* @param remotexpcModule - optional pre-loaded module (for unit tests)
|
|
36
|
+
* @param log - if set, warns when the package is present but too old (no **XCTestAttachment** export)
|
|
37
|
+
*/
|
|
38
|
+
static async isDeletionAvailable(
|
|
39
|
+
udid: string,
|
|
40
|
+
platformVersion: string,
|
|
41
|
+
remotexpcModule?: RemotexpcAttachmentModule | null,
|
|
42
|
+
log?: AppiumLogger,
|
|
43
|
+
): Promise<boolean> {
|
|
44
|
+
if (!udid?.trim() || !isIos18OrNewerPlatform(platformVersion)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
let mod: RemotexpcAttachmentModule | null | undefined = remotexpcModule;
|
|
48
|
+
if (mod === undefined) {
|
|
49
|
+
mod = (await tryGetRemoteXPCModule()) as RemotexpcAttachmentModule | null;
|
|
50
|
+
if (!mod) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (typeof mod?.XCTestAttachment === 'function') {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (mod && log) {
|
|
58
|
+
log.warn(REMOTEXPC_UPGRADE_HINT);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param remotexpcModule - optional pre-loaded module (for unit tests)
|
|
65
|
+
* @throws {Error} If real-device XCTest attachment deletion is not supported
|
|
66
|
+
*/
|
|
67
|
+
static async create(
|
|
68
|
+
udid: string,
|
|
69
|
+
platformVersion: string,
|
|
70
|
+
remotexpcModule?: RemotexpcAttachmentModule | null,
|
|
71
|
+
): Promise<XctestAttachmentDeletionClient> {
|
|
72
|
+
if (!udid?.trim()) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
'A device UDID is required for XCTest screen recording on a real device so attachments can be removed after stop.',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (!isIos18OrNewerPlatform(platformVersion)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
'XCTest screen recording on a real device requires iOS 18 or newer. ' +
|
|
80
|
+
'The driver removes recordings from the device after stop using appium-ios-remotexpc, which is only supported on iOS 18+.',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let mod: RemotexpcAttachmentModule | null | undefined = remotexpcModule;
|
|
85
|
+
if (mod === undefined) {
|
|
86
|
+
mod = (await tryGetRemoteXPCModule()) as RemotexpcAttachmentModule | null;
|
|
87
|
+
if (!mod) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
'appium-ios-remotexpc must be installed to use XCTest screen recording on a real device. ' +
|
|
90
|
+
'It is used to delete screen-recording attachments after stop.',
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const XCTestAttachment = mod?.XCTestAttachment;
|
|
96
|
+
if (!mod || typeof XCTestAttachment !== 'function') {
|
|
97
|
+
throw new Error(
|
|
98
|
+
'The installed appium-ios-remotexpc package must export XCTestAttachment for real-device XCTest screen recording. ' +
|
|
99
|
+
REMOTEXPC_UPGRADE_HINT,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return new XctestAttachmentDeletionClient(
|
|
104
|
+
udid.trim(),
|
|
105
|
+
XCTestAttachment as XCTestAttachmentCtor,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async deleteAttachmentsByUuid(uuids: string[]): Promise<void> {
|
|
110
|
+
if (!uuids.length) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const attachment = new this.XCTestAttachment(this.udid);
|
|
114
|
+
await attachment.delete(uuids);
|
|
115
|
+
}
|
|
116
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-xcuitest-driver",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.42.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "appium-xcuitest-driver",
|
|
9
|
-
"version": "10.
|
|
9
|
+
"version": "10.42.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@appium/strongbox": "^1.0.0-rc.1",
|
|
@@ -583,9 +583,9 @@
|
|
|
583
583
|
"license": "MIT"
|
|
584
584
|
},
|
|
585
585
|
"node_modules/@types/node": {
|
|
586
|
-
"version": "25.5.
|
|
587
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.
|
|
588
|
-
"integrity": "sha512-
|
|
586
|
+
"version": "25.5.2",
|
|
587
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz",
|
|
588
|
+
"integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==",
|
|
589
589
|
"devOptional": true,
|
|
590
590
|
"license": "MIT",
|
|
591
591
|
"dependencies": {
|
|
@@ -703,9 +703,9 @@
|
|
|
703
703
|
}
|
|
704
704
|
},
|
|
705
705
|
"node_modules/appium-ios-remotexpc": {
|
|
706
|
-
"version": "0.
|
|
707
|
-
"resolved": "https://registry.npmjs.org/appium-ios-remotexpc/-/appium-ios-remotexpc-0.
|
|
708
|
-
"integrity": "sha512-
|
|
706
|
+
"version": "0.44.0",
|
|
707
|
+
"resolved": "https://registry.npmjs.org/appium-ios-remotexpc/-/appium-ios-remotexpc-0.44.0.tgz",
|
|
708
|
+
"integrity": "sha512-e7DbfRJuobWpsOHTpdtzWPlb2R3HvLEjzist9D5Lzk5bzpEDasYiV+hlr+mvq6VM7gjB7ZHN8lZ3CC5kF3HQAw==",
|
|
709
709
|
"license": "Apache-2.0",
|
|
710
710
|
"optional": true,
|
|
711
711
|
"dependencies": {
|
|
@@ -2661,9 +2661,9 @@
|
|
|
2661
2661
|
}
|
|
2662
2662
|
},
|
|
2663
2663
|
"node_modules/lru-cache": {
|
|
2664
|
-
"version": "11.
|
|
2665
|
-
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.
|
|
2666
|
-
"integrity": "sha512-
|
|
2664
|
+
"version": "11.3.0",
|
|
2665
|
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.0.tgz",
|
|
2666
|
+
"integrity": "sha512-sr8xPKE25m6vJVcrdn6NxtC0fVfuPowbscLypegRgOm0yXSqr5JNHCAY3hnusdJ7HRBW04j6Ip4khvHU778DuQ==",
|
|
2667
2667
|
"license": "BlueOak-1.0.0",
|
|
2668
2668
|
"engines": {
|
|
2669
2669
|
"node": "20 || >=22"
|