appium-adb 14.1.2 → 14.1.3

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/lib/tools/aab-utils.d.ts +5 -4
  3. package/build/lib/tools/aab-utils.d.ts.map +1 -1
  4. package/build/lib/tools/aab-utils.js +13 -11
  5. package/build/lib/tools/aab-utils.js.map +1 -1
  6. package/build/lib/tools/android-manifest.d.ts +26 -30
  7. package/build/lib/tools/android-manifest.d.ts.map +1 -1
  8. package/build/lib/tools/android-manifest.js +33 -47
  9. package/build/lib/tools/android-manifest.js.map +1 -1
  10. package/build/lib/tools/apks-utils.d.ts +25 -29
  11. package/build/lib/tools/apks-utils.d.ts.map +1 -1
  12. package/build/lib/tools/apks-utils.js +41 -52
  13. package/build/lib/tools/apks-utils.js.map +1 -1
  14. package/build/lib/tools/fs-commands.d.ts +25 -30
  15. package/build/lib/tools/fs-commands.d.ts.map +1 -1
  16. package/build/lib/tools/fs-commands.js +19 -26
  17. package/build/lib/tools/fs-commands.js.map +1 -1
  18. package/build/lib/tools/general-commands.d.ts +36 -58
  19. package/build/lib/tools/general-commands.d.ts.map +1 -1
  20. package/build/lib/tools/general-commands.js +41 -47
  21. package/build/lib/tools/general-commands.js.map +1 -1
  22. package/build/lib/tools/keyboard-commands.d.ts +27 -36
  23. package/build/lib/tools/keyboard-commands.d.ts.map +1 -1
  24. package/build/lib/tools/keyboard-commands.js +24 -34
  25. package/build/lib/tools/keyboard-commands.js.map +1 -1
  26. package/build/lib/tools/lockmgmt.d.ts +25 -36
  27. package/build/lib/tools/lockmgmt.d.ts.map +1 -1
  28. package/build/lib/tools/lockmgmt.js +34 -38
  29. package/build/lib/tools/lockmgmt.js.map +1 -1
  30. package/build/lib/tools/logcat-commands.d.ts +11 -30
  31. package/build/lib/tools/logcat-commands.d.ts.map +1 -1
  32. package/build/lib/tools/logcat-commands.js +16 -17
  33. package/build/lib/tools/logcat-commands.js.map +1 -1
  34. package/build/lib/tools/network-commands.d.ts +24 -31
  35. package/build/lib/tools/network-commands.d.ts.map +1 -1
  36. package/build/lib/tools/network-commands.js +14 -24
  37. package/build/lib/tools/network-commands.js.map +1 -1
  38. package/lib/tools/{aab-utils.js → aab-utils.ts} +24 -15
  39. package/lib/tools/{android-manifest.js → android-manifest.ts} +66 -59
  40. package/lib/tools/{apks-utils.js → apks-utils.ts} +76 -68
  41. package/lib/tools/{fs-commands.js → fs-commands.ts} +41 -35
  42. package/lib/tools/{general-commands.js → general-commands.ts} +71 -68
  43. package/lib/tools/{keyboard-commands.js → keyboard-commands.ts} +42 -49
  44. package/lib/tools/{lockmgmt.js → lockmgmt.ts} +71 -56
  45. package/lib/tools/{logcat-commands.js → logcat-commands.ts} +24 -22
  46. package/lib/tools/{network-commands.js → network-commands.ts} +42 -34
  47. package/package.json +1 -1
@@ -1,14 +1,15 @@
1
1
  import _ from 'lodash';
2
2
  import path from 'path';
3
+ import type {ADB} from '../adb.js';
4
+ import type {TeenProcessExecOptions} from 'teen_process';
3
5
 
4
6
  /**
5
7
  * Verify whether a remote path exists on the device under test.
6
8
  *
7
- * @this {import('../adb.js').ADB}
8
- * @param {string} remotePath - The remote path to verify.
9
- * @return {Promise<boolean>} True if the given path exists on the device.
9
+ * @param remotePath - The remote path to verify.
10
+ * @return True if the given path exists on the device.
10
11
  */
11
- export async function fileExists(remotePath) {
12
+ export async function fileExists(this: ADB, remotePath: string): Promise<boolean> {
12
13
  const passFlag = '__PASS__';
13
14
  const checkCmd = `[ -e '${remotePath.replace(/'/g, `\\'`)}' ] && echo ${passFlag}`;
14
15
  try {
@@ -21,24 +22,23 @@ export async function fileExists(remotePath) {
21
22
  /**
22
23
  * Get the output of _ls_ command on the device under test.
23
24
  *
24
- * @this {import('../adb.js').ADB}
25
- * @param {string} remotePath - The remote path (the first argument to the _ls_ command).
26
- * @param {string[]} [opts] - Additional _ls_ options.
27
- * @return {Promise<string[]>} The _ls_ output as an array of split lines.
25
+ * @param remotePath - The remote path (the first argument to the _ls_ command).
26
+ * @param opts - Additional _ls_ options.
27
+ * @return The _ls_ output as an array of split lines.
28
28
  * An empty array is returned of the given _remotePath_
29
29
  * does not exist.
30
30
  */
31
- export async function ls(remotePath, opts = []) {
31
+ export async function ls(this: ADB, remotePath: string, opts: string[] = []): Promise<string[]> {
32
32
  try {
33
- let args = ['ls', ...opts, remotePath];
34
- let stdout = await this.shell(args);
35
- let lines = stdout.split('\n');
33
+ const args = ['ls', ...opts, remotePath];
34
+ const stdout = await this.shell(args);
35
+ const lines = stdout.split('\n');
36
36
  return lines
37
37
  .map((l) => l.trim())
38
38
  .filter(Boolean)
39
39
  .filter((l) => l.indexOf('No such file') === -1);
40
40
  } catch (err) {
41
- if (err.message.indexOf('No such file or directory') === -1) {
41
+ if ((err as Error).message.indexOf('No such file or directory') === -1) {
42
42
  throw err;
43
43
  }
44
44
  return [];
@@ -48,12 +48,11 @@ export async function ls(remotePath, opts = []) {
48
48
  /**
49
49
  * Get the size of the particular file located on the device under test.
50
50
  *
51
- * @this {import('../adb.js').ADB}
52
- * @param {string} remotePath - The remote path to the file.
53
- * @return {Promise<number>} File size in bytes.
51
+ * @param remotePath - The remote path to the file.
52
+ * @return File size in bytes.
54
53
  * @throws {Error} If there was an error while getting the size of the given file.
55
54
  */
56
- export async function fileSize(remotePath) {
55
+ export async function fileSize(this: ADB, remotePath: string): Promise<number> {
57
56
  try {
58
57
  const files = await this.ls(remotePath, ['-la']);
59
58
  if (files.length !== 1) {
@@ -66,7 +65,7 @@ export async function fileSize(remotePath) {
66
65
  }
67
66
  return parseInt(match[1], 10);
68
67
  } catch (err) {
69
- throw new Error(`Unable to get file size for '${remotePath}': ${err.message}`);
68
+ throw new Error(`Unable to get file size for '${remotePath}': ${(err as Error).message}`);
70
69
  }
71
70
  }
72
71
 
@@ -74,25 +73,28 @@ export async function fileSize(remotePath) {
74
73
  * Forcefully recursively remove a path on the device under test.
75
74
  * Be careful while calling this method.
76
75
  *
77
- * @this {import('../adb.js').ADB}
78
- * @param {string} path - The path to be removed recursively.
76
+ * @param path - The path to be removed recursively.
79
77
  */
80
- export async function rimraf(path) {
78
+ export async function rimraf(this: ADB, path: string): Promise<void> {
81
79
  await this.shell(['rm', '-rf', path]);
82
80
  }
83
81
 
84
82
  /**
85
83
  * Send a file to the device under test.
86
84
  *
87
- * @this {import('../adb.js').ADB}
88
- * @param {string} localPath - The path to the file on the local file system.
89
- * @param {string} remotePath - The destination path on the remote device.
90
- * @param {object} [opts] - Additional options mapping. See
85
+ * @param localPath - The path to the file on the local file system.
86
+ * @param remotePath - The destination path on the remote device.
87
+ * @param opts - Additional options mapping. See
91
88
  * https://github.com/appium/node-teen_process,
92
89
  * _exec_ method options, for more information about available
93
90
  * options.
94
91
  */
95
- export async function push(localPath, remotePath, opts) {
92
+ export async function push(
93
+ this: ADB,
94
+ localPath: string,
95
+ remotePath: string,
96
+ opts?: TeenProcessExecOptions,
97
+ ): Promise<void> {
96
98
  await this.mkdir(path.posix.dirname(remotePath));
97
99
  await this.adbExec(['push', localPath, remotePath], opts);
98
100
  }
@@ -100,15 +102,19 @@ export async function push(localPath, remotePath, opts) {
100
102
  /**
101
103
  * Receive a file from the device under test.
102
104
  *
103
- * @this {import('../adb.js').ADB}
104
- * @param {string} remotePath - The source path on the remote device.
105
- * @param {string} localPath - The destination path to the file on the local file system.
106
- * @param {import('teen_process').TeenProcessExecOptions} [opts={}] - Additional options mapping. See
105
+ * @param remotePath - The source path on the remote device.
106
+ * @param localPath - The destination path to the file on the local file system.
107
+ * @param opts - Additional options mapping. See
107
108
  * https://github.com/appium/node-teen_process,
108
109
  * _exec_ method options, for more information about available
109
110
  * options.
110
111
  */
111
- export async function pull(remotePath, localPath, opts = {}) {
112
+ export async function pull(
113
+ this: ADB,
114
+ remotePath: string,
115
+ localPath: string,
116
+ opts: TeenProcessExecOptions = {},
117
+ ): Promise<void> {
112
118
  // pull folder can take more time, increasing time out to 60 secs
113
119
  await this.adbExec(['pull', remotePath, localPath], {...opts, timeout: opts.timeout ?? 60000});
114
120
  }
@@ -116,12 +122,12 @@ export async function pull(remotePath, localPath, opts = {}) {
116
122
  /**
117
123
  * Recursively create a new folder on the device under test.
118
124
  *
119
- * @this {import('../adb.js').ADB}
120
- * @param {string} remotePath - The new path to be created.
121
- * @return {Promise<string>} mkdir command output.
125
+ * @param remotePath - The new path to be created.
126
+ * @return mkdir command output.
122
127
  */
123
- export async function mkdir(remotePath) {
128
+ export async function mkdir(this: ADB, remotePath: string): Promise<string> {
124
129
  return /\s+/.test(remotePath)
125
130
  ? await this.shell([`mkdir -p '${remotePath.replace(/'/g, `\\'`)}'`])
126
131
  : await this.shell(['mkdir', '-p', remotePath]);
127
132
  }
133
+
@@ -1,16 +1,17 @@
1
1
  import {log} from '../logger.js';
2
2
  import _ from 'lodash';
3
3
  import {fs, util} from '@appium/support';
4
- import {SubProcess, exec} from 'teen_process';
4
+ import {SubProcess, exec, type ExecError} from 'teen_process';
5
+ import type {ADB} from '../adb.js';
6
+ import type {ScreenrecordOptions, StringRecord} from './types.js';
5
7
 
6
8
  /**
7
9
  * Get the path to adb executable amd assign it
8
10
  * to this.executable.path and this.binaries.adb properties.
9
11
  *
10
- * @this {import('../adb.js').ADB}
11
- * @return {Promise<import('../adb.js').ADB>} ADB instance.
12
+ * @return ADB instance.
12
13
  */
13
- export async function getAdbWithCorrectAdbPath() {
14
+ export async function getAdbWithCorrectAdbPath(this: ADB): Promise<ADB> {
14
15
  this.executable.path = await this.getSdkBinaryPath('adb');
15
16
  return this;
16
17
  }
@@ -18,39 +19,35 @@ export async function getAdbWithCorrectAdbPath() {
18
19
  /**
19
20
  * Get the full path to aapt tool and assign it to
20
21
  * this.binaries.aapt property
21
- * @this {import('../adb.js').ADB}
22
22
  */
23
- export async function initAapt() {
23
+ export async function initAapt(this: ADB): Promise<void> {
24
24
  await this.getSdkBinaryPath('aapt');
25
25
  }
26
26
 
27
27
  /**
28
28
  * Get the full path to aapt2 tool and assign it to
29
29
  * this.binaries.aapt2 property
30
- * @this {import('../adb.js').ADB}
31
30
  */
32
- export async function initAapt2() {
31
+ export async function initAapt2(this: ADB): Promise<void> {
33
32
  await this.getSdkBinaryPath('aapt2');
34
33
  }
35
34
 
36
35
  /**
37
36
  * Get the full path to zipalign tool and assign it to
38
37
  * this.binaries.zipalign property
39
- * @this {import('../adb.js').ADB}
40
38
  */
41
- export async function initZipAlign() {
39
+ export async function initZipAlign(this: ADB): Promise<void> {
42
40
  await this.getSdkBinaryPath('zipalign');
43
41
  }
44
42
 
45
43
  /**
46
44
  * Get the full path to bundletool binary and assign it to
47
45
  * this.binaries.bundletool property
48
- * @this {import('../adb.js').ADB}
49
46
  */
50
- export async function initBundletool() {
47
+ export async function initBundletool(this: ADB): Promise<void> {
51
48
  try {
52
- /** @type {import('./types').StringRecord} */ (this.binaries).bundletool =
53
- await fs.which('bundletool.jar');
49
+ const binaries = this.binaries as StringRecord;
50
+ binaries.bundletool = await fs.which('bundletool.jar');
54
51
  } catch {
55
52
  throw new Error(
56
53
  'bundletool.jar binary is expected to be present in PATH. ' +
@@ -62,12 +59,11 @@ export async function initBundletool() {
62
59
  /**
63
60
  * Retrieve the API level of the device under test.
64
61
  *
65
- * @this {import('../adb.js').ADB}
66
- * @return {Promise<number>} The API level as integer number, for example 21 for
62
+ * @return The API level as integer number, for example 21 for
67
63
  * Android Lollipop. The result of this method is cached, so all the further
68
64
  * calls return the same value as the first one.
69
65
  */
70
- export async function getApiLevel() {
66
+ export async function getApiLevel(this: ADB): Promise<number> {
71
67
  if (!_.isInteger(this._apiLevel)) {
72
68
  try {
73
69
  const strOutput = await this.getDeviceProperty('ro.build.version.sdk');
@@ -92,21 +88,20 @@ export async function getApiLevel() {
92
88
  }
93
89
  } catch (e) {
94
90
  throw new Error(
95
- `Error getting device API level. Original error: ${/** @type {Error} */ (e).message}`,
91
+ `Error getting device API level. Original error: ${(e as Error).message}`,
96
92
  );
97
93
  }
98
94
  }
99
- return /** @type {number} */ (this._apiLevel);
95
+ return this._apiLevel as number;
100
96
  }
101
97
 
102
98
  /**
103
99
  * Verify whether a device is connected.
104
100
  *
105
- * @this {import('../adb.js').ADB}
106
- * @return {Promise<boolean>} True if at least one device is visible to adb.
101
+ * @return True if at least one device is visible to adb.
107
102
  */
108
- export async function isDeviceConnected() {
109
- let devices = await this.getConnectedDevices();
103
+ export async function isDeviceConnected(this: ADB): Promise<boolean> {
104
+ const devices = await this.getConnectedDevices();
110
105
  return devices.length > 0;
111
106
  }
112
107
 
@@ -114,16 +109,15 @@ export async function isDeviceConnected() {
114
109
  * Clear the active text field on the device under test by sending
115
110
  * special keyevents to it.
116
111
  *
117
- * @this {import('../adb.js').ADB}
118
- * @param {number} [length=100] - The maximum length of the text in the field to be cleared.
112
+ * @param length - The maximum length of the text in the field to be cleared.
119
113
  */
120
- export async function clearTextField(length = 100) {
114
+ export async function clearTextField(this: ADB, length: number = 100): Promise<void> {
121
115
  // assumes that the EditText field already has focus
122
116
  log.debug(`Clearing up to ${length} characters`);
123
117
  if (length === 0) {
124
118
  return;
125
119
  }
126
- let args = ['input', 'keyevent'];
120
+ const args = ['input', 'keyevent'];
127
121
  for (let i = 0; i < length; i++) {
128
122
  // we cannot know where the cursor is in the text field, so delete both before
129
123
  // and after so that we get rid of everything
@@ -137,9 +131,8 @@ export async function clearTextField(length = 100) {
137
131
  /**
138
132
  * Send the special keycode to the device under test in order to emulate
139
133
  * Back button tap.
140
- * @this {import('../adb.js').ADB}
141
134
  */
142
- export async function back() {
135
+ export async function back(this: ADB): Promise<void> {
143
136
  log.debug('Pressing the BACK button');
144
137
  await this.keyevent(4);
145
138
  }
@@ -147,35 +140,32 @@ export async function back() {
147
140
  /**
148
141
  * Send the special keycode to the device under test in order to emulate
149
142
  * Home button tap.
150
- * @this {import('../adb.js').ADB}
151
143
  */
152
- export async function goToHome() {
144
+ export async function goToHome(this: ADB): Promise<void> {
153
145
  log.debug('Pressing the HOME button');
154
146
  await this.keyevent(3);
155
147
  }
156
148
 
157
149
  /**
158
- * @this {import('../adb.js').ADB}
159
- * @return {string} the actual path to adb executable.
150
+ * @return the actual path to adb executable.
160
151
  */
161
- export function getAdbPath() {
152
+ export function getAdbPath(this: ADB): string {
162
153
  return this.executable.path;
163
154
  }
164
155
 
165
156
  /**
166
157
  * Restart the device under test using adb commands.
167
158
  *
168
- * @this {import('../adb.js').ADB}
169
159
  * @throws {Error} If start fails.
170
160
  */
171
- export async function restart() {
161
+ export async function restart(this: ADB): Promise<void> {
172
162
  try {
173
163
  await this.stopLogcat();
174
164
  await this.restartAdb();
175
165
  await this.waitForDevice(60);
176
166
  await this.startLogcat(this._logcatStartupParams);
177
167
  } catch (e) {
178
- const err = /** @type {Error} */ (e);
168
+ const err = e as Error;
179
169
  throw new Error(`Restart failed. Original error: ${err.message}`);
180
170
  }
181
171
  }
@@ -184,25 +174,27 @@ export async function restart() {
184
174
  * Retrieve the `adb bugreport` command output. This
185
175
  * operation may take up to several minutes.
186
176
  *
187
- * @this {import('../adb.js').ADB}
188
- * @param {number} [timeout=120000] - Command timeout in milliseconds
189
- * @returns {Promise<string>} Command stdout
177
+ * @param timeout - Command timeout in milliseconds
178
+ * @returns Command stdout
190
179
  */
191
- export async function bugreport(timeout = 120000) {
180
+ export async function bugreport(this: ADB, timeout: number = 120000): Promise<string> {
192
181
  return await this.adbExec(['bugreport'], {timeout});
193
182
  }
194
183
 
195
184
  /**
196
185
  * Initiate screenrecord utility on the device
197
186
  *
198
- * @this {import('../adb.js').ADB}
199
- * @param {string} destination - Full path to the writable media file destination
187
+ * @param destination - Full path to the writable media file destination
200
188
  * on the device file system.
201
- * @param {import('./types').ScreenrecordOptions} [options={}]
202
- * @returns {SubProcess} screenrecord process, which can be then controlled by the client code
189
+ * @param options - Screenrecord options
190
+ * @returns screenrecord process, which can be then controlled by the client code
203
191
  */
204
- export function screenrecord(destination, options = {}) {
205
- const cmd = ['screenrecord'];
192
+ export function screenrecord(
193
+ this: ADB,
194
+ destination: string,
195
+ options: ScreenrecordOptions = {},
196
+ ): SubProcess {
197
+ const cmd: string[] = ['screenrecord'];
206
198
  const {videoSize, bitRate, timeLimit, bugReport} = options;
207
199
  if (util.hasValue(videoSize)) {
208
200
  cmd.push('--size', videoSize);
@@ -226,8 +218,7 @@ export function screenrecord(destination, options = {}) {
226
218
  /**
227
219
  * Retrieves the list of features supported by the device under test
228
220
  *
229
- * @this {import('../adb.js').ADB}
230
- * @returns {Promise<string[]>} the list of supported feature names or an empty list.
221
+ * @returns the list of supported feature names or an empty list.
231
222
  * An example adb command output:
232
223
  * ```
233
224
  * cmd
@@ -243,7 +234,7 @@ export function screenrecord(destination, options = {}) {
243
234
  * ```
244
235
  * @throws {Error} if there was an error while retrieving the list
245
236
  */
246
- export async function listFeatures() {
237
+ export async function listFeatures(this: ADB): Promise<string[]> {
247
238
  this._memoizedFeatures =
248
239
  this._memoizedFeatures ||
249
240
  _.memoize(
@@ -251,12 +242,16 @@ export async function listFeatures() {
251
242
  () => this.curDeviceId,
252
243
  );
253
244
  try {
254
- return (await this._memoizedFeatures())
245
+ const memoizedFeatures = this._memoizedFeatures;
246
+ if (!memoizedFeatures) {
247
+ throw new Error('Memoized features function is not initialized');
248
+ }
249
+ return (await memoizedFeatures())
255
250
  .split(/\s+/)
256
251
  .map((x) => x.trim())
257
252
  .filter(Boolean);
258
253
  } catch (e) {
259
- const err = /** @type {import('teen_process').ExecError} */ (e);
254
+ const err = e as ExecError;
260
255
  if (_.includes(err.stderr, 'unknown command')) {
261
256
  return [];
262
257
  }
@@ -274,11 +269,10 @@ export async function listFeatures() {
274
269
  * See https://github.com/aosp-mirror/platform_system_core/blob/master/adb/client/adb_install.cpp
275
270
  * for more details
276
271
  *
277
- * @this {import('../adb.js').ADB}
278
- * @returns {Promise<boolean>} `true` if the feature is supported by both adb and the
272
+ * @returns `true` if the feature is supported by both adb and the
279
273
  * device under test
280
274
  */
281
- export async function isStreamedInstallSupported() {
275
+ export async function isStreamedInstallSupported(this: ADB): Promise<boolean> {
282
276
  const proto = Object.getPrototypeOf(this);
283
277
  proto._helpOutput = proto._helpOutput || (await this.adbExec(['help']));
284
278
  return proto._helpOutput.includes('--streaming') && (await this.listFeatures()).includes('cmd');
@@ -289,11 +283,10 @@ export async function isStreamedInstallSupported() {
289
283
  * Read https://developer.android.com/preview/features#incremental
290
284
  * for more details on it.
291
285
  *
292
- * @this {import('../adb.js').ADB}
293
- * @returns {Promise<boolean>} `true` if the feature is supported by both adb and the
286
+ * @returns `true` if the feature is supported by both adb and the
294
287
  * device under test
295
288
  */
296
- export async function isIncrementalInstallSupported() {
289
+ export async function isIncrementalInstallSupported(this: ADB): Promise<boolean> {
297
290
  const {binary} = await this.getVersion();
298
291
  if (!binary) {
299
292
  return false;
@@ -307,29 +300,38 @@ export async function isIncrementalInstallSupported() {
307
300
  /**
308
301
  * Takes a screenshot of the given display or the default display.
309
302
  *
310
- * @this {import('../adb.js').ADB}
311
- * @param {number|string?} displayId A valid display identifier. If
303
+ * @param displayId A valid display identifier. If
312
304
  * no identifier is provided then the screenshot of the default display is returned.
313
305
  * Note that only recent Android APIs provide multi-screen support.
314
- * @returns {Promise<Buffer>} PNG screenshot payload
306
+ * @returns PNG screenshot payload
315
307
  */
316
- export async function takeScreenshot(displayId) {
308
+ export async function takeScreenshot(
309
+ this: ADB,
310
+ displayId?: number | string,
311
+ ): Promise<Buffer> {
317
312
  const args = [...this.executable.defaultArgs, 'exec-out', 'screencap', '-p'];
318
313
  // @ts-ignore This validation works as expected
319
- const displayIdStr = isNaN(displayId) ? null : `${displayId}`;
314
+ const displayIdStr = isNaN(Number(displayId)) ? null : `${displayId}`;
320
315
  if (displayIdStr) {
321
316
  args.push('-d', displayIdStr);
322
317
  }
323
- const displayDescr = displayIdStr ? 'default display' : `display #${displayIdStr}`;
324
- let stdout;
318
+ const displayDescr = displayIdStr ? `display #${displayIdStr}` : 'default display';
319
+ let stdout: Buffer;
325
320
  try {
326
321
  ({stdout} = await exec(this.executable.path, args, {encoding: 'binary', isBuffer: true}));
327
322
  } catch (e) {
328
- const err = /** @type {import('teen_process').ExecError} */ (e);
323
+ const err = e as ExecError;
324
+ let outputStr = '';
325
+ if (err.stderr) {
326
+ const stderr = err.stderr as string | Buffer;
327
+ outputStr = typeof stderr === 'string' ? stderr : stderr.toString('utf-8');
328
+ } else if (err.stdout) {
329
+ const stdout = err.stdout as string | Buffer;
330
+ outputStr = typeof stdout === 'string' ? stdout : stdout.toString('utf-8');
331
+ }
329
332
  throw new Error(
330
333
  `Screenshot of the ${displayDescr} failed. ` +
331
- // @ts-ignore The output is a buffer
332
- `Code: '${err.code}', output: '${(err.stderr.length ? err.stderr : err.stdout).toString('utf-8')}'`,
334
+ `Code: '${err.code ?? 'unknown'}', output: '${outputStr}'`,
333
335
  );
334
336
  }
335
337
  if (stdout.length === 0) {
@@ -337,3 +339,4 @@ export async function takeScreenshot(displayId) {
337
339
  }
338
340
  return stdout;
339
341
  }
342
+