appium-xcuitest-driver 11.8.0 → 11.8.1
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.map +1 -1
- package/build/lib/commands/xctest-record-screen.js +93 -47
- package/build/lib/commands/xctest-record-screen.js.map +1 -1
- package/lib/commands/xctest-record-screen.ts +114 -54
- package/npm-shrinkwrap.json +35 -11
- package/package.json +1 -1
- package/scripts/cleanup-videos.mjs +26 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [11.8.1](https://github.com/appium/appium-xcuitest-driver/compare/v11.8.0...v11.8.1) (2026-06-03)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* Video attachments path for Xcode 26.5+ ([#2862](https://github.com/appium/appium-xcuitest-driver/issues/2862)) ([174ff70](https://github.com/appium/appium-xcuitest-driver/commit/174ff707cba80217197d81f9ee917e9abf66f973))
|
|
6
|
+
|
|
1
7
|
## [11.8.0](https://github.com/appium/appium-xcuitest-driver/compare/v11.7.7...v11.8.0) (2026-06-02)
|
|
2
8
|
|
|
3
9
|
### Features
|
|
@@ -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,
|
|
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,EAAe,WAAW,EAAC,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EAAC,yBAAyB,EAAE,qBAAqB,EAAC,MAAM,SAAS,CAAC;AAoH9E;;;;;;;;;;;;;;;;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,CAgFhC"}
|
|
@@ -8,17 +8,102 @@ exports.mobileGetXctestScreenRecordingInfo = mobileGetXctestScreenRecordingInfo;
|
|
|
8
8
|
exports.mobileStopXctestScreenRecording = mobileStopXctestScreenRecording;
|
|
9
9
|
const support_1 = require("appium/support");
|
|
10
10
|
const helpers_1 = require("./helpers");
|
|
11
|
-
const
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
12
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
13
|
const xctest_attachment_deletion_client_1 = require("../device/xctest-attachment-deletion-client");
|
|
14
14
|
const remotexpc_utils_1 = require("../device/remotexpc-utils");
|
|
15
15
|
const MOV_EXT = '.mov';
|
|
16
|
+
/**
|
|
17
|
+
* On simulators XCTest stores screen recording attachments under
|
|
18
|
+
* `InternalDaemon/<id>/Attachments/<uuid>` (legacy) or
|
|
19
|
+
* `InternalDaemon/<id>/tmp/Attachments/<uuid>` (Xcode 26.5+).
|
|
20
|
+
* Brace `{,tmp/}` matches both in a single glob.
|
|
21
|
+
*/
|
|
22
|
+
const SIMULATOR_XCTEST_RECORDING_ATTACHMENT_GLOB = '*/{,tmp/}Attachments/*';
|
|
16
23
|
/** Insecure feature when real-device XCTest recording is used without RemoteXPC attachment deletion. */
|
|
17
24
|
const XCTEST_SCREEN_RECORD_FEATURE = 'xctest_screen_record';
|
|
18
25
|
const DOMAIN_IDENTIFIER = 'com.apple.testmanagerd';
|
|
19
26
|
const DOMAIN_TYPE = 'appDataContainer';
|
|
20
27
|
const USERNAME = 'mobile';
|
|
21
|
-
|
|
28
|
+
/** Legacy layout and Xcode 26.5+ `tmp/Attachments` within testmanagerd's app data container. */
|
|
29
|
+
const REAL_DEVICE_XCTEST_ATTACHMENT_SUBDIRECTORIES = ['Attachments', 'tmp/Attachments'];
|
|
30
|
+
class XcTestScreenRecordingRetriever {
|
|
31
|
+
log;
|
|
32
|
+
constructor(log) {
|
|
33
|
+
this.log = log;
|
|
34
|
+
}
|
|
35
|
+
static nameMatchesUuid(name, uuid) {
|
|
36
|
+
return name.toUpperCase() === uuid.toUpperCase();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class SimulatorXcTestScreenRecordingRetriever extends XcTestScreenRecordingRetriever {
|
|
40
|
+
device;
|
|
41
|
+
constructor(device, log) {
|
|
42
|
+
super(log);
|
|
43
|
+
this.device = device;
|
|
44
|
+
}
|
|
45
|
+
async retrieve(uuid) {
|
|
46
|
+
const dataRoot = this.device.getDir();
|
|
47
|
+
// e.g. .../CoreSimulator/Devices/<udid>/data/Containers/Data/InternalDaemon/<daemon-id>/Attachments/<uuid>
|
|
48
|
+
// or .../InternalDaemon/<daemon-id>/tmp/Attachments/<uuid> (Xcode 26.5+)
|
|
49
|
+
const internalDaemonRoot = node_path_1.default.resolve(dataRoot, 'Containers', 'Data', 'InternalDaemon');
|
|
50
|
+
const attachmentPaths = await support_1.fs.glob(SIMULATOR_XCTEST_RECORDING_ATTACHMENT_GLOB, {
|
|
51
|
+
cwd: internalDaemonRoot,
|
|
52
|
+
absolute: true,
|
|
53
|
+
});
|
|
54
|
+
const videoPath = attachmentPaths.find((fp) => XcTestScreenRecordingRetriever.nameMatchesUuid(node_path_1.default.basename(fp), uuid));
|
|
55
|
+
if (!videoPath) {
|
|
56
|
+
throw new Error(`Unable to locate XCTest screen recording identified by '${uuid}' for the Simulator ${this.device.udid}`);
|
|
57
|
+
}
|
|
58
|
+
const { size } = await support_1.fs.stat(videoPath);
|
|
59
|
+
this.log.debug(`Located the video at '${videoPath}' (${support_1.util.toReadableSizeString(size)})`);
|
|
60
|
+
return videoPath;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
class RealDeviceXcTestScreenRecordingRetriever extends XcTestScreenRecordingRetriever {
|
|
64
|
+
device;
|
|
65
|
+
tmpDir;
|
|
66
|
+
constructor(device, tmpDir, log) {
|
|
67
|
+
super(log);
|
|
68
|
+
this.device = device;
|
|
69
|
+
this.tmpDir = tmpDir;
|
|
70
|
+
}
|
|
71
|
+
async retrieve(uuid) {
|
|
72
|
+
const attachment = await this.findAttachment(uuid);
|
|
73
|
+
if (!attachment) {
|
|
74
|
+
throw new Error(`Unable to locate XCTest screen recording identified by '${uuid}' for the device ${this.device.udid}`);
|
|
75
|
+
}
|
|
76
|
+
const videoPath = node_path_1.default.join(this.tmpDir, `${uuid}${MOV_EXT}`);
|
|
77
|
+
const { subdirectory, fileName } = attachment;
|
|
78
|
+
await this.device.devicectl.pullFile(`${subdirectory}/${fileName}`, videoPath, {
|
|
79
|
+
username: USERNAME,
|
|
80
|
+
domainIdentifier: DOMAIN_IDENTIFIER,
|
|
81
|
+
domainType: DOMAIN_TYPE,
|
|
82
|
+
});
|
|
83
|
+
const { size } = await support_1.fs.stat(videoPath);
|
|
84
|
+
this.log.debug(`Pulled the video to '${videoPath}' (${support_1.util.toReadableSizeString(size)})`);
|
|
85
|
+
return videoPath;
|
|
86
|
+
}
|
|
87
|
+
async findAttachment(uuid) {
|
|
88
|
+
for (const subdirectory of REAL_DEVICE_XCTEST_ATTACHMENT_SUBDIRECTORIES) {
|
|
89
|
+
let fileNames;
|
|
90
|
+
try {
|
|
91
|
+
fileNames = await this.device.devicectl.listFiles(DOMAIN_TYPE, DOMAIN_IDENTIFIER, {
|
|
92
|
+
username: USERNAME,
|
|
93
|
+
subdirectory,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const fileName = fileNames.find((name) => XcTestScreenRecordingRetriever.nameMatchesUuid(name, uuid));
|
|
100
|
+
if (fileName) {
|
|
101
|
+
return { subdirectory, fileName };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
22
107
|
/**
|
|
23
108
|
* Start a new screen recording via XCTest.
|
|
24
109
|
*
|
|
@@ -106,7 +191,7 @@ async function mobileStopXctestScreenRecording(remotePath, user, pass, headers,
|
|
|
106
191
|
}
|
|
107
192
|
this.log.debug(`Stopping the active screen recording: ${JSON.stringify(screenRecordingInfo)}`);
|
|
108
193
|
await this.proxyCommand('/wda/video/stop', 'POST', {});
|
|
109
|
-
const videoPath = await
|
|
194
|
+
const videoPath = await createXcTestScreenRecordingRetriever(this).retrieve(screenRecordingInfo.uuid);
|
|
110
195
|
const result = {
|
|
111
196
|
...screenRecordingInfo,
|
|
112
197
|
payload: '', // Will be set below
|
|
@@ -163,49 +248,10 @@ async function mobileStopXctestScreenRecording(remotePath, user, pass, headers,
|
|
|
163
248
|
}
|
|
164
249
|
return result;
|
|
165
250
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const internalDaemonRoot = node_path_1.default.resolve(dataRoot, 'Containers', 'Data', 'InternalDaemon');
|
|
172
|
-
const videoPaths = await support_1.fs.glob(`*/Attachments/${uuid}`, {
|
|
173
|
-
cwd: internalDaemonRoot,
|
|
174
|
-
absolute: true,
|
|
175
|
-
});
|
|
176
|
-
if ((0, utils_1.isEmpty)(videoPaths)) {
|
|
177
|
-
throw new Error(`Unable to locate XCTest screen recording identified by '${uuid}' for the Simulator ${device.udid}`);
|
|
178
|
-
}
|
|
179
|
-
const videoPath = videoPaths[0];
|
|
180
|
-
const { size } = await support_1.fs.stat(videoPath);
|
|
181
|
-
this.log.debug(`Located the video at '${videoPath}' (${support_1.util.toReadableSizeString(size)})`);
|
|
182
|
-
return videoPath;
|
|
183
|
-
}
|
|
184
|
-
async function retrieveRecodingFromRealDevice(uuid) {
|
|
185
|
-
const device = this.device;
|
|
186
|
-
const fileNames = await device.devicectl.listFiles(DOMAIN_TYPE, DOMAIN_IDENTIFIER, {
|
|
187
|
-
username: USERNAME,
|
|
188
|
-
subdirectory: SUBDIRECTORY,
|
|
189
|
-
});
|
|
190
|
-
if (!fileNames.includes(uuid)) {
|
|
191
|
-
throw new Error(`Unable to locate XCTest screen recording identified by '${uuid}' for the device ${this.opts.udid}`);
|
|
192
|
-
}
|
|
193
|
-
if (!this.opts.tmpDir) {
|
|
194
|
-
throw new Error('tmpDir is not set in driver options');
|
|
195
|
-
}
|
|
196
|
-
const videoPath = node_path_1.default.join(this.opts.tmpDir, `${uuid}${MOV_EXT}`);
|
|
197
|
-
await device.devicectl.pullFile(`${SUBDIRECTORY}/${uuid}`, videoPath, {
|
|
198
|
-
username: USERNAME,
|
|
199
|
-
domainIdentifier: DOMAIN_IDENTIFIER,
|
|
200
|
-
domainType: DOMAIN_TYPE,
|
|
201
|
-
});
|
|
202
|
-
const { size } = await support_1.fs.stat(videoPath);
|
|
203
|
-
this.log.debug(`Pulled the video to '${videoPath}' (${support_1.util.toReadableSizeString(size)})`);
|
|
204
|
-
return videoPath;
|
|
205
|
-
}
|
|
206
|
-
async function retrieveXcTestScreenRecording(uuid) {
|
|
207
|
-
return this.isRealDevice()
|
|
208
|
-
? await retrieveRecodingFromRealDevice.call(this, uuid)
|
|
209
|
-
: await retrieveRecodingFromSimulator.call(this, uuid);
|
|
251
|
+
function createXcTestScreenRecordingRetriever(driver) {
|
|
252
|
+
if (driver.isRealDevice()) {
|
|
253
|
+
return new RealDeviceXcTestScreenRecordingRetriever(driver.device, driver.opts.tmpDir || node_os_1.default.tmpdir(), driver.log);
|
|
254
|
+
}
|
|
255
|
+
return new SimulatorXcTestScreenRecordingRetriever(driver.device, driver.log);
|
|
210
256
|
}
|
|
211
257
|
//# sourceMappingURL=xctest-record-screen.js.map
|
|
@@ -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,11 +1,11 @@
|
|
|
1
1
|
import {fs, util} from 'appium/support';
|
|
2
2
|
import {encodeBase64OrUpload} from './helpers';
|
|
3
|
-
import
|
|
3
|
+
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import type {XCUITestDriver} from '../driver';
|
|
6
6
|
import type {Simulator} from 'appium-ios-simulator';
|
|
7
7
|
import type {RealDevice} from '../device/real-device-management';
|
|
8
|
-
import type {HTTPHeaders} from '@appium/types';
|
|
8
|
+
import type {AppiumLogger, HTTPHeaders} from '@appium/types';
|
|
9
9
|
import type {XcTestScreenRecordingInfo, XcTestScreenRecording} from './types';
|
|
10
10
|
import {XctestAttachmentDeletionClient} from '../device/xctest-attachment-deletion-client';
|
|
11
11
|
import {
|
|
@@ -14,12 +14,113 @@ import {
|
|
|
14
14
|
} from '../device/remotexpc-utils';
|
|
15
15
|
|
|
16
16
|
const MOV_EXT = '.mov';
|
|
17
|
+
/**
|
|
18
|
+
* On simulators XCTest stores screen recording attachments under
|
|
19
|
+
* `InternalDaemon/<id>/Attachments/<uuid>` (legacy) or
|
|
20
|
+
* `InternalDaemon/<id>/tmp/Attachments/<uuid>` (Xcode 26.5+).
|
|
21
|
+
* Brace `{,tmp/}` matches both in a single glob.
|
|
22
|
+
*/
|
|
23
|
+
const SIMULATOR_XCTEST_RECORDING_ATTACHMENT_GLOB = '*/{,tmp/}Attachments/*';
|
|
17
24
|
/** Insecure feature when real-device XCTest recording is used without RemoteXPC attachment deletion. */
|
|
18
25
|
const XCTEST_SCREEN_RECORD_FEATURE = 'xctest_screen_record';
|
|
19
26
|
const DOMAIN_IDENTIFIER = 'com.apple.testmanagerd';
|
|
20
27
|
const DOMAIN_TYPE = 'appDataContainer';
|
|
21
28
|
const USERNAME = 'mobile';
|
|
22
|
-
|
|
29
|
+
/** Legacy layout and Xcode 26.5+ `tmp/Attachments` within testmanagerd's app data container. */
|
|
30
|
+
const REAL_DEVICE_XCTEST_ATTACHMENT_SUBDIRECTORIES = ['Attachments', 'tmp/Attachments'] as const;
|
|
31
|
+
|
|
32
|
+
abstract class XcTestScreenRecordingRetriever {
|
|
33
|
+
constructor(protected readonly log: AppiumLogger) {}
|
|
34
|
+
|
|
35
|
+
protected static nameMatchesUuid(name: string, uuid: string): boolean {
|
|
36
|
+
return name.toUpperCase() === uuid.toUpperCase();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
abstract retrieve(uuid: string): Promise<string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class SimulatorXcTestScreenRecordingRetriever extends XcTestScreenRecordingRetriever {
|
|
43
|
+
constructor(
|
|
44
|
+
private readonly device: Simulator,
|
|
45
|
+
log: AppiumLogger,
|
|
46
|
+
) {
|
|
47
|
+
super(log);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override async retrieve(uuid: string): Promise<string> {
|
|
51
|
+
const dataRoot = this.device.getDir();
|
|
52
|
+
// e.g. .../CoreSimulator/Devices/<udid>/data/Containers/Data/InternalDaemon/<daemon-id>/Attachments/<uuid>
|
|
53
|
+
// or .../InternalDaemon/<daemon-id>/tmp/Attachments/<uuid> (Xcode 26.5+)
|
|
54
|
+
const internalDaemonRoot = path.resolve(dataRoot, 'Containers', 'Data', 'InternalDaemon');
|
|
55
|
+
const attachmentPaths = await fs.glob(SIMULATOR_XCTEST_RECORDING_ATTACHMENT_GLOB, {
|
|
56
|
+
cwd: internalDaemonRoot,
|
|
57
|
+
absolute: true,
|
|
58
|
+
});
|
|
59
|
+
const videoPath = attachmentPaths.find((fp) =>
|
|
60
|
+
XcTestScreenRecordingRetriever.nameMatchesUuid(path.basename(fp), uuid),
|
|
61
|
+
);
|
|
62
|
+
if (!videoPath) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Unable to locate XCTest screen recording identified by '${uuid}' for the Simulator ${this.device.udid}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const {size} = await fs.stat(videoPath);
|
|
68
|
+
this.log.debug(`Located the video at '${videoPath}' (${util.toReadableSizeString(size)})`);
|
|
69
|
+
return videoPath;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class RealDeviceXcTestScreenRecordingRetriever extends XcTestScreenRecordingRetriever {
|
|
74
|
+
constructor(
|
|
75
|
+
private readonly device: RealDevice,
|
|
76
|
+
private readonly tmpDir: string,
|
|
77
|
+
log: AppiumLogger,
|
|
78
|
+
) {
|
|
79
|
+
super(log);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
override async retrieve(uuid: string): Promise<string> {
|
|
83
|
+
const attachment = await this.findAttachment(uuid);
|
|
84
|
+
if (!attachment) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Unable to locate XCTest screen recording identified by '${uuid}' for the device ${this.device.udid}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const videoPath = path.join(this.tmpDir, `${uuid}${MOV_EXT}`);
|
|
90
|
+
const {subdirectory, fileName} = attachment;
|
|
91
|
+
await this.device.devicectl.pullFile(`${subdirectory}/${fileName}`, videoPath, {
|
|
92
|
+
username: USERNAME,
|
|
93
|
+
domainIdentifier: DOMAIN_IDENTIFIER,
|
|
94
|
+
domainType: DOMAIN_TYPE,
|
|
95
|
+
});
|
|
96
|
+
const {size} = await fs.stat(videoPath);
|
|
97
|
+
this.log.debug(`Pulled the video to '${videoPath}' (${util.toReadableSizeString(size)})`);
|
|
98
|
+
return videoPath;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async findAttachment(
|
|
102
|
+
uuid: string,
|
|
103
|
+
): Promise<{subdirectory: string; fileName: string} | null> {
|
|
104
|
+
for (const subdirectory of REAL_DEVICE_XCTEST_ATTACHMENT_SUBDIRECTORIES) {
|
|
105
|
+
let fileNames: string[];
|
|
106
|
+
try {
|
|
107
|
+
fileNames = await this.device.devicectl.listFiles(DOMAIN_TYPE, DOMAIN_IDENTIFIER, {
|
|
108
|
+
username: USERNAME,
|
|
109
|
+
subdirectory,
|
|
110
|
+
});
|
|
111
|
+
} catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const fileName = fileNames.find((name) =>
|
|
115
|
+
XcTestScreenRecordingRetriever.nameMatchesUuid(name, uuid),
|
|
116
|
+
);
|
|
117
|
+
if (fileName) {
|
|
118
|
+
return {subdirectory, fileName};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
23
124
|
|
|
24
125
|
/**
|
|
25
126
|
* Start a new screen recording via XCTest.
|
|
@@ -136,8 +237,7 @@ export async function mobileStopXctestScreenRecording(
|
|
|
136
237
|
|
|
137
238
|
this.log.debug(`Stopping the active screen recording: ${JSON.stringify(screenRecordingInfo)}`);
|
|
138
239
|
await this.proxyCommand('/wda/video/stop', 'POST', {});
|
|
139
|
-
const videoPath
|
|
140
|
-
this,
|
|
240
|
+
const videoPath = await createXcTestScreenRecordingRetriever(this).retrieve(
|
|
141
241
|
screenRecordingInfo.uuid,
|
|
142
242
|
);
|
|
143
243
|
const result: XcTestScreenRecording = {
|
|
@@ -211,55 +311,15 @@ export async function mobileStopXctestScreenRecording(
|
|
|
211
311
|
return result;
|
|
212
312
|
}
|
|
213
313
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
absolute: true,
|
|
223
|
-
});
|
|
224
|
-
if (isEmpty(videoPaths)) {
|
|
225
|
-
throw new Error(
|
|
226
|
-
`Unable to locate XCTest screen recording identified by '${uuid}' for the Simulator ${device.udid}`,
|
|
314
|
+
function createXcTestScreenRecordingRetriever(
|
|
315
|
+
driver: XCUITestDriver,
|
|
316
|
+
): XcTestScreenRecordingRetriever {
|
|
317
|
+
if (driver.isRealDevice()) {
|
|
318
|
+
return new RealDeviceXcTestScreenRecordingRetriever(
|
|
319
|
+
driver.device as RealDevice,
|
|
320
|
+
driver.opts.tmpDir || os.tmpdir(),
|
|
321
|
+
driver.log,
|
|
227
322
|
);
|
|
228
323
|
}
|
|
229
|
-
|
|
230
|
-
const {size} = await fs.stat(videoPath);
|
|
231
|
-
this.log.debug(`Located the video at '${videoPath}' (${util.toReadableSizeString(size)})`);
|
|
232
|
-
return videoPath;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async function retrieveRecodingFromRealDevice(this: XCUITestDriver, uuid: string): Promise<string> {
|
|
236
|
-
const device = this.device as RealDevice;
|
|
237
|
-
|
|
238
|
-
const fileNames = await device.devicectl.listFiles(DOMAIN_TYPE, DOMAIN_IDENTIFIER, {
|
|
239
|
-
username: USERNAME,
|
|
240
|
-
subdirectory: SUBDIRECTORY,
|
|
241
|
-
});
|
|
242
|
-
if (!fileNames.includes(uuid)) {
|
|
243
|
-
throw new Error(
|
|
244
|
-
`Unable to locate XCTest screen recording identified by '${uuid}' for the device ${this.opts.udid}`,
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
if (!this.opts.tmpDir) {
|
|
248
|
-
throw new Error('tmpDir is not set in driver options');
|
|
249
|
-
}
|
|
250
|
-
const videoPath = path.join(this.opts.tmpDir, `${uuid}${MOV_EXT}`);
|
|
251
|
-
await device.devicectl.pullFile(`${SUBDIRECTORY}/${uuid}`, videoPath, {
|
|
252
|
-
username: USERNAME,
|
|
253
|
-
domainIdentifier: DOMAIN_IDENTIFIER,
|
|
254
|
-
domainType: DOMAIN_TYPE,
|
|
255
|
-
});
|
|
256
|
-
const {size} = await fs.stat(videoPath);
|
|
257
|
-
this.log.debug(`Pulled the video to '${videoPath}' (${util.toReadableSizeString(size)})`);
|
|
258
|
-
return videoPath;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async function retrieveXcTestScreenRecording(this: XCUITestDriver, uuid: string): Promise<string> {
|
|
262
|
-
return this.isRealDevice()
|
|
263
|
-
? await retrieveRecodingFromRealDevice.call(this, uuid)
|
|
264
|
-
: await retrieveRecodingFromSimulator.call(this, uuid);
|
|
324
|
+
return new SimulatorXcTestScreenRecordingRetriever(driver.device as Simulator, driver.log);
|
|
265
325
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-xcuitest-driver",
|
|
3
|
-
"version": "11.8.
|
|
3
|
+
"version": "11.8.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "appium-xcuitest-driver",
|
|
9
|
-
"version": "11.8.
|
|
9
|
+
"version": "11.8.1",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@appium/strongbox": "^1.0.0-rc.1",
|
|
@@ -100,6 +100,18 @@
|
|
|
100
100
|
"spdy": "4.0.2"
|
|
101
101
|
}
|
|
102
102
|
},
|
|
103
|
+
"node_modules/@appium/base-driver/node_modules/axios": {
|
|
104
|
+
"version": "1.16.1",
|
|
105
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
|
|
106
|
+
"integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
|
|
107
|
+
"license": "MIT",
|
|
108
|
+
"dependencies": {
|
|
109
|
+
"follow-redirects": "^1.16.0",
|
|
110
|
+
"form-data": "^4.0.5",
|
|
111
|
+
"https-proxy-agent": "^5.0.1",
|
|
112
|
+
"proxy-from-env": "^2.1.0"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
103
115
|
"node_modules/@appium/base-driver/node_modules/lru-cache": {
|
|
104
116
|
"version": "11.5.0",
|
|
105
117
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz",
|
|
@@ -234,6 +246,18 @@
|
|
|
234
246
|
"sharp": "0.34.5"
|
|
235
247
|
}
|
|
236
248
|
},
|
|
249
|
+
"node_modules/@appium/support/node_modules/axios": {
|
|
250
|
+
"version": "1.16.1",
|
|
251
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
|
|
252
|
+
"integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
|
|
253
|
+
"license": "MIT",
|
|
254
|
+
"dependencies": {
|
|
255
|
+
"follow-redirects": "^1.16.0",
|
|
256
|
+
"form-data": "^4.0.5",
|
|
257
|
+
"https-proxy-agent": "^5.0.1",
|
|
258
|
+
"proxy-from-env": "^2.1.0"
|
|
259
|
+
}
|
|
260
|
+
},
|
|
237
261
|
"node_modules/@appium/tsconfig": {
|
|
238
262
|
"version": "1.1.2",
|
|
239
263
|
"resolved": "https://registry.npmjs.org/@appium/tsconfig/-/tsconfig-1.1.2.tgz",
|
|
@@ -672,9 +696,9 @@
|
|
|
672
696
|
"license": "MIT"
|
|
673
697
|
},
|
|
674
698
|
"node_modules/axios": {
|
|
675
|
-
"version": "1.
|
|
676
|
-
"resolved": "https://registry.npmjs.org/axios/-/axios-1.
|
|
677
|
-
"integrity": "sha512-
|
|
699
|
+
"version": "1.17.0",
|
|
700
|
+
"resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz",
|
|
701
|
+
"integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==",
|
|
678
702
|
"license": "MIT",
|
|
679
703
|
"dependencies": {
|
|
680
704
|
"follow-redirects": "^1.16.0",
|
|
@@ -707,9 +731,9 @@
|
|
|
707
731
|
}
|
|
708
732
|
},
|
|
709
733
|
"node_modules/bare-events": {
|
|
710
|
-
"version": "2.
|
|
711
|
-
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.
|
|
712
|
-
"integrity": "sha512-
|
|
734
|
+
"version": "2.9.1",
|
|
735
|
+
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
|
|
736
|
+
"integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
|
|
713
737
|
"license": "Apache-2.0",
|
|
714
738
|
"peerDependencies": {
|
|
715
739
|
"bare-abort-controller": "*"
|
|
@@ -721,9 +745,9 @@
|
|
|
721
745
|
}
|
|
722
746
|
},
|
|
723
747
|
"node_modules/bare-fs": {
|
|
724
|
-
"version": "4.7.
|
|
725
|
-
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.
|
|
726
|
-
"integrity": "sha512-
|
|
748
|
+
"version": "4.7.2",
|
|
749
|
+
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz",
|
|
750
|
+
"integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==",
|
|
727
751
|
"license": "Apache-2.0",
|
|
728
752
|
"dependencies": {
|
|
729
753
|
"bare-events": "^2.5.4",
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Lists XCTest attachment file names (UUIDs) under **testmanagerd**'s app-data `Attachments`
|
|
3
|
+
* Lists XCTest attachment file names (UUIDs) under **testmanagerd**'s app-data `Attachments` and
|
|
4
|
+
* `tmp/Attachments` folders
|
|
4
5
|
* and deletes them via **appium-ios-remotexpc** (`XCTestAttachment.delete`), the same mechanism the
|
|
5
6
|
* driver uses for real-device XCTest screen recording cleanup.
|
|
6
7
|
*
|
|
@@ -29,7 +30,8 @@ const log = logger.getLogger('cleanup-videos');
|
|
|
29
30
|
const DOMAIN_IDENTIFIER = 'com.apple.testmanagerd';
|
|
30
31
|
const DOMAIN_TYPE = 'appDataContainer';
|
|
31
32
|
const USERNAME = 'mobile';
|
|
32
|
-
|
|
33
|
+
/** Legacy layout and Xcode 26.5+ `tmp/Attachments` (same as xctest-record-screen.ts). */
|
|
34
|
+
const ATTACHMENT_SUBDIRECTORIES = ['Attachments', 'tmp/Attachments'];
|
|
33
35
|
|
|
34
36
|
/** Attachment entries are UUID-shaped file names (no extension in this listing). */
|
|
35
37
|
const UUID_NAME_RE = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
|
|
@@ -47,24 +49,32 @@ class CleanupVideos {
|
|
|
47
49
|
await requirePlatformVersion(udid);
|
|
48
50
|
|
|
49
51
|
const devicectl = new Devicectl(udid);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
52
|
+
const fileNames = [];
|
|
53
|
+
const listErrors = [];
|
|
54
|
+
let anyListSucceeded = false;
|
|
55
|
+
for (const subdirectory of ATTACHMENT_SUBDIRECTORIES) {
|
|
56
|
+
try {
|
|
57
|
+
const names = await devicectl.listFiles(DOMAIN_TYPE, DOMAIN_IDENTIFIER, {
|
|
58
|
+
username: USERNAME,
|
|
59
|
+
subdirectory,
|
|
60
|
+
});
|
|
61
|
+
anyListSucceeded = true;
|
|
62
|
+
fileNames.push(...names);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
listErrors.push(err);
|
|
65
|
+
log.debug(`Could not list ${subdirectory}: ${err.message ?? err}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!anyListSucceeded) {
|
|
69
|
+
const errorMessage = `Failed to list files in ${DOMAIN_TYPE}/${DOMAIN_IDENTIFIER}/` +
|
|
70
|
+
`{${ATTACHMENT_SUBDIRECTORIES.join(',')}}`;
|
|
71
|
+
throw new AggregateError(listErrors, errorMessage);
|
|
62
72
|
}
|
|
63
73
|
|
|
64
|
-
const uuids = filterAttachmentUuids(fileNames);
|
|
74
|
+
const uuids = filterAttachmentUuids([...new Set(fileNames)]);
|
|
65
75
|
log.info(
|
|
66
76
|
`Found ${util.pluralize('UUID-shaped attachment', uuids.length, true)} ` +
|
|
67
|
-
`in testmanagerd Attachments (out of ${fileNames.length} listed names).`,
|
|
77
|
+
`in testmanagerd Attachments and tmp/Attachments (out of ${fileNames.length} listed names).`,
|
|
68
78
|
);
|
|
69
79
|
if (uuids.length > 0) {
|
|
70
80
|
log.info(uuids.join('\n'));
|