appium-android-driver 7.8.2 → 8.0.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.
Files changed (261) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/build/lib/commands/app-management.d.ts +129 -5
  3. package/build/lib/commands/app-management.d.ts.map +1 -1
  4. package/build/lib/commands/app-management.js +433 -128
  5. package/build/lib/commands/app-management.js.map +1 -1
  6. package/build/lib/commands/appearance.d.ts +17 -4
  7. package/build/lib/commands/appearance.d.ts.map +1 -1
  8. package/build/lib/commands/appearance.js +32 -33
  9. package/build/lib/commands/appearance.js.map +1 -1
  10. package/build/lib/commands/context/cache.d.ts +19 -0
  11. package/build/lib/commands/context/cache.d.ts.map +1 -0
  12. package/build/lib/commands/context/cache.js +32 -0
  13. package/build/lib/commands/context/cache.js.map +1 -0
  14. package/build/lib/commands/context/exports.d.ts +141 -0
  15. package/build/lib/commands/context/exports.d.ts.map +1 -0
  16. package/build/lib/commands/context/exports.js +351 -0
  17. package/build/lib/commands/context/exports.js.map +1 -0
  18. package/build/lib/commands/context/helpers.d.ts +98 -0
  19. package/build/lib/commands/context/helpers.d.ts.map +1 -0
  20. package/build/lib/commands/context/helpers.js +715 -0
  21. package/build/lib/commands/context/helpers.js.map +1 -0
  22. package/build/lib/commands/device/common.d.ts +23 -0
  23. package/build/lib/commands/device/common.d.ts.map +1 -0
  24. package/build/lib/commands/device/common.js +230 -0
  25. package/build/lib/commands/device/common.js.map +1 -0
  26. package/build/lib/commands/device/emulator-actions.d.ts +114 -0
  27. package/build/lib/commands/device/emulator-actions.d.ts.map +1 -0
  28. package/build/lib/commands/device/emulator-actions.js +197 -0
  29. package/build/lib/commands/device/emulator-actions.js.map +1 -0
  30. package/build/lib/commands/device/emulator-console.d.ts +7 -0
  31. package/build/lib/commands/device/emulator-console.d.ts.map +1 -0
  32. package/build/lib/commands/device/emulator-console.js +24 -0
  33. package/build/lib/commands/device/emulator-console.js.map +1 -0
  34. package/build/lib/commands/device/utils.d.ts +50 -0
  35. package/build/lib/commands/device/utils.d.ts.map +1 -0
  36. package/build/lib/commands/device/utils.js +238 -0
  37. package/build/lib/commands/device/utils.js.map +1 -0
  38. package/build/lib/commands/deviceidle.d.ts +8 -5
  39. package/build/lib/commands/deviceidle.d.ts.map +1 -1
  40. package/build/lib/commands/deviceidle.js +31 -37
  41. package/build/lib/commands/deviceidle.js.map +1 -1
  42. package/build/lib/commands/element.d.ts +99 -5
  43. package/build/lib/commands/element.d.ts.map +1 -1
  44. package/build/lib/commands/element.js +152 -116
  45. package/build/lib/commands/element.js.map +1 -1
  46. package/build/lib/commands/execute.d.ts +12 -4
  47. package/build/lib/commands/execute.d.ts.map +1 -1
  48. package/build/lib/commands/execute.js +83 -78
  49. package/build/lib/commands/execute.js.map +1 -1
  50. package/build/lib/commands/file-actions.d.ts +42 -5
  51. package/build/lib/commands/file-actions.d.ts.map +1 -1
  52. package/build/lib/commands/file-actions.js +230 -194
  53. package/build/lib/commands/file-actions.js.map +1 -1
  54. package/build/lib/commands/find.d.ts +5 -4
  55. package/build/lib/commands/find.d.ts.map +1 -1
  56. package/build/lib/commands/find.js +7 -10
  57. package/build/lib/commands/find.js.map +1 -1
  58. package/build/lib/commands/geolocation.d.ts +45 -0
  59. package/build/lib/commands/geolocation.d.ts.map +1 -0
  60. package/build/lib/commands/geolocation.js +182 -0
  61. package/build/lib/commands/geolocation.js.map +1 -0
  62. package/build/lib/commands/ime.d.ts +25 -5
  63. package/build/lib/commands/ime.d.ts.map +1 -1
  64. package/build/lib/commands/ime.js +59 -42
  65. package/build/lib/commands/ime.js.map +1 -1
  66. package/build/lib/commands/intent.d.ts +56 -5
  67. package/build/lib/commands/intent.d.ts.map +1 -1
  68. package/build/lib/commands/intent.js +135 -83
  69. package/build/lib/commands/intent.js.map +1 -1
  70. package/build/lib/commands/keyboard.d.ts +58 -4
  71. package/build/lib/commands/keyboard.d.ts.map +1 -1
  72. package/build/lib/commands/keyboard.js +119 -17
  73. package/build/lib/commands/keyboard.js.map +1 -1
  74. package/build/lib/commands/lock/exports.d.ts +301 -0
  75. package/build/lib/commands/lock/exports.d.ts.map +1 -0
  76. package/build/lib/commands/lock/exports.js +121 -0
  77. package/build/lib/commands/lock/exports.js.map +1 -0
  78. package/build/lib/commands/lock/helpers.d.ts +349 -0
  79. package/build/lib/commands/lock/helpers.d.ts.map +1 -0
  80. package/build/lib/commands/lock/helpers.js +375 -0
  81. package/build/lib/commands/lock/helpers.js.map +1 -0
  82. package/build/lib/commands/log.d.ts +59 -5
  83. package/build/lib/commands/log.d.ts.map +1 -1
  84. package/build/lib/commands/log.js +150 -140
  85. package/build/lib/commands/log.js.map +1 -1
  86. package/build/lib/commands/media-projection.d.ts +16 -5
  87. package/build/lib/commands/media-projection.d.ts.map +1 -1
  88. package/build/lib/commands/media-projection.js +69 -58
  89. package/build/lib/commands/media-projection.js.map +1 -1
  90. package/build/lib/commands/memory.d.ts +9 -5
  91. package/build/lib/commands/memory.d.ts.map +1 -1
  92. package/build/lib/commands/memory.js +19 -24
  93. package/build/lib/commands/memory.js.map +1 -1
  94. package/build/lib/commands/misc.d.ts +42 -0
  95. package/build/lib/commands/misc.d.ts.map +1 -0
  96. package/build/lib/commands/misc.js +100 -0
  97. package/build/lib/commands/misc.js.map +1 -0
  98. package/build/lib/commands/network.d.ts +61 -5
  99. package/build/lib/commands/network.d.ts.map +1 -1
  100. package/build/lib/commands/network.js +196 -189
  101. package/build/lib/commands/network.js.map +1 -1
  102. package/build/lib/commands/performance.d.ts +67 -27
  103. package/build/lib/commands/performance.d.ts.map +1 -1
  104. package/build/lib/commands/performance.js +105 -80
  105. package/build/lib/commands/performance.js.map +1 -1
  106. package/build/lib/commands/permissions.d.ts +12 -6
  107. package/build/lib/commands/permissions.d.ts.map +1 -1
  108. package/build/lib/commands/permissions.js +65 -62
  109. package/build/lib/commands/permissions.js.map +1 -1
  110. package/build/lib/commands/recordscreen.d.ts +44 -5
  111. package/build/lib/commands/recordscreen.d.ts.map +1 -1
  112. package/build/lib/commands/recordscreen.js +131 -126
  113. package/build/lib/commands/recordscreen.js.map +1 -1
  114. package/build/lib/commands/resources.d.ts +16 -0
  115. package/build/lib/commands/resources.d.ts.map +1 -0
  116. package/build/lib/commands/resources.js +91 -0
  117. package/build/lib/commands/resources.js.map +1 -0
  118. package/build/lib/commands/shell.d.ts +8 -5
  119. package/build/lib/commands/shell.d.ts.map +1 -1
  120. package/build/lib/commands/shell.js +29 -33
  121. package/build/lib/commands/shell.js.map +1 -1
  122. package/build/lib/commands/streamscreen.d.ts +34 -6
  123. package/build/lib/commands/streamscreen.d.ts.map +1 -1
  124. package/build/lib/commands/streamscreen.js +166 -162
  125. package/build/lib/commands/streamscreen.js.map +1 -1
  126. package/build/lib/commands/system-bars.d.ts +18 -13
  127. package/build/lib/commands/system-bars.d.ts.map +1 -1
  128. package/build/lib/commands/system-bars.js +68 -64
  129. package/build/lib/commands/system-bars.js.map +1 -1
  130. package/build/lib/commands/time.d.ts +14 -0
  131. package/build/lib/commands/time.d.ts.map +1 -0
  132. package/build/lib/commands/time.js +39 -0
  133. package/build/lib/commands/time.js.map +1 -0
  134. package/build/lib/commands/touch.d.ts +99 -6
  135. package/build/lib/commands/touch.d.ts.map +1 -1
  136. package/build/lib/commands/touch.js +399 -280
  137. package/build/lib/commands/touch.js.map +1 -1
  138. package/build/lib/commands/types.d.ts +115 -3
  139. package/build/lib/commands/types.d.ts.map +1 -1
  140. package/build/lib/doctor/checks.d.ts.map +1 -1
  141. package/build/lib/doctor/checks.js +4 -4
  142. package/build/lib/doctor/checks.js.map +1 -1
  143. package/build/lib/driver.d.ts +224 -27
  144. package/build/lib/driver.d.ts.map +1 -1
  145. package/build/lib/driver.js +232 -7
  146. package/build/lib/driver.js.map +1 -1
  147. package/build/lib/index.d.ts +1 -4
  148. package/build/lib/index.d.ts.map +1 -1
  149. package/build/lib/index.js +1 -13
  150. package/build/lib/index.js.map +1 -1
  151. package/build/lib/logger.js.map +1 -1
  152. package/build/lib/method-map.d.ts +0 -23
  153. package/build/lib/method-map.d.ts.map +1 -1
  154. package/build/lib/method-map.js +0 -11
  155. package/build/lib/method-map.js.map +1 -1
  156. package/build/lib/utils.d.ts +12 -0
  157. package/build/lib/utils.d.ts.map +1 -1
  158. package/build/lib/utils.js +38 -2
  159. package/build/lib/utils.js.map +1 -1
  160. package/lib/commands/app-management.js +470 -145
  161. package/lib/commands/appearance.js +29 -36
  162. package/lib/commands/context/cache.js +29 -0
  163. package/lib/commands/context/exports.js +379 -0
  164. package/lib/commands/context/helpers.js +802 -0
  165. package/lib/commands/device/common.js +264 -0
  166. package/lib/commands/device/emulator-actions.js +194 -0
  167. package/lib/commands/device/emulator-console.js +24 -0
  168. package/lib/commands/device/utils.js +285 -0
  169. package/lib/commands/deviceidle.js +31 -44
  170. package/lib/commands/element.js +149 -142
  171. package/lib/commands/execute.js +86 -87
  172. package/lib/commands/file-actions.js +249 -222
  173. package/lib/commands/find.ts +13 -19
  174. package/lib/commands/geolocation.js +179 -0
  175. package/lib/commands/ime.js +53 -45
  176. package/lib/commands/intent.js +149 -91
  177. package/lib/commands/keyboard.js +114 -17
  178. package/lib/commands/lock/exports.js +139 -0
  179. package/lib/commands/lock/helpers.js +379 -0
  180. package/lib/commands/log.js +170 -166
  181. package/lib/commands/media-projection.js +75 -70
  182. package/lib/commands/memory.js +17 -29
  183. package/lib/commands/misc.js +94 -0
  184. package/lib/commands/network.js +209 -223
  185. package/lib/commands/performance.js +88 -73
  186. package/lib/commands/permissions.js +83 -84
  187. package/lib/commands/recordscreen.js +171 -170
  188. package/lib/commands/resources.js +96 -0
  189. package/lib/commands/shell.js +28 -42
  190. package/lib/commands/streamscreen.js +207 -206
  191. package/lib/commands/system-bars.js +76 -77
  192. package/lib/commands/time.js +36 -0
  193. package/lib/commands/touch.js +442 -346
  194. package/lib/commands/types.ts +142 -10
  195. package/lib/doctor/checks.js +24 -16
  196. package/lib/driver.ts +454 -12
  197. package/lib/index.ts +1 -13
  198. package/lib/logger.js +1 -1
  199. package/lib/method-map.js +0 -11
  200. package/lib/utils.js +40 -3
  201. package/package.json +1 -1
  202. package/build/lib/commands/actions.d.ts +0 -8
  203. package/build/lib/commands/actions.d.ts.map +0 -1
  204. package/build/lib/commands/actions.js +0 -207
  205. package/build/lib/commands/actions.js.map +0 -1
  206. package/build/lib/commands/alert.d.ts +0 -8
  207. package/build/lib/commands/alert.d.ts.map +0 -1
  208. package/build/lib/commands/alert.js +0 -29
  209. package/build/lib/commands/alert.js.map +0 -1
  210. package/build/lib/commands/context.d.ts +0 -10
  211. package/build/lib/commands/context.d.ts.map +0 -1
  212. package/build/lib/commands/context.js +0 -431
  213. package/build/lib/commands/context.js.map +0 -1
  214. package/build/lib/commands/emu-console.d.ts +0 -7
  215. package/build/lib/commands/emu-console.d.ts.map +0 -1
  216. package/build/lib/commands/emu-console.js +0 -27
  217. package/build/lib/commands/emu-console.js.map +0 -1
  218. package/build/lib/commands/general.d.ts +0 -9
  219. package/build/lib/commands/general.d.ts.map +0 -1
  220. package/build/lib/commands/general.js +0 -293
  221. package/build/lib/commands/general.js.map +0 -1
  222. package/build/lib/commands/index.d.ts +0 -28
  223. package/build/lib/commands/index.d.ts.map +0 -1
  224. package/build/lib/commands/index.js +0 -57
  225. package/build/lib/commands/index.js.map +0 -1
  226. package/build/lib/commands/mixins.d.ts +0 -747
  227. package/build/lib/commands/mixins.d.ts.map +0 -1
  228. package/build/lib/commands/mixins.js +0 -19
  229. package/build/lib/commands/mixins.js.map +0 -1
  230. package/build/lib/helpers/android.d.ts +0 -163
  231. package/build/lib/helpers/android.d.ts.map +0 -1
  232. package/build/lib/helpers/android.js +0 -818
  233. package/build/lib/helpers/android.js.map +0 -1
  234. package/build/lib/helpers/index.d.ts +0 -7
  235. package/build/lib/helpers/index.d.ts.map +0 -1
  236. package/build/lib/helpers/index.js +0 -29
  237. package/build/lib/helpers/index.js.map +0 -1
  238. package/build/lib/helpers/types.d.ts +0 -122
  239. package/build/lib/helpers/types.d.ts.map +0 -1
  240. package/build/lib/helpers/types.js +0 -3
  241. package/build/lib/helpers/types.js.map +0 -1
  242. package/build/lib/helpers/unlock.d.ts +0 -32
  243. package/build/lib/helpers/unlock.d.ts.map +0 -1
  244. package/build/lib/helpers/unlock.js +0 -273
  245. package/build/lib/helpers/unlock.js.map +0 -1
  246. package/build/lib/helpers/webview.d.ts +0 -74
  247. package/build/lib/helpers/webview.d.ts.map +0 -1
  248. package/build/lib/helpers/webview.js +0 -442
  249. package/build/lib/helpers/webview.js.map +0 -1
  250. package/lib/commands/actions.js +0 -244
  251. package/lib/commands/alert.js +0 -34
  252. package/lib/commands/context.js +0 -507
  253. package/lib/commands/emu-console.js +0 -31
  254. package/lib/commands/general.js +0 -343
  255. package/lib/commands/index.ts +0 -54
  256. package/lib/commands/mixins.ts +0 -976
  257. package/lib/helpers/android.ts +0 -1153
  258. package/lib/helpers/index.ts +0 -6
  259. package/lib/helpers/types.ts +0 -136
  260. package/lib/helpers/unlock.ts +0 -329
  261. package/lib/helpers/webview.ts +0 -604
@@ -1,11 +1,8 @@
1
- // @ts-check
2
-
3
1
  import {fs, net, system, tempDir, timing, util} from '@appium/support';
4
2
  import {waitForCondition} from 'asyncbox';
5
3
  import _ from 'lodash';
6
4
  import path from 'path';
7
5
  import {exec} from 'teen_process';
8
- import {mixin} from './mixins';
9
6
 
10
7
  const RETRY_PAUSE = 300;
11
8
  const RETRY_TIMEOUT = 5000;
@@ -18,6 +15,155 @@ const DEFAULT_EXT = '.mp4';
18
15
  const MIN_EMULATOR_API_LEVEL = 27;
19
16
  const FFMPEG_BINARY = `ffmpeg${system.isWindows() ? '.exe' : ''}`;
20
17
 
18
+ /**
19
+ *
20
+ * @this {import('../driver').AndroidDriver}
21
+ * @param {import('./types').StartScreenRecordingOpts} [options={}]
22
+ * @returns {Promise<string>}
23
+ */
24
+ export async function startRecordingScreen(options = {}) {
25
+ await verifyScreenRecordIsSupported(this.adb, this.isEmulator());
26
+
27
+ let result = '';
28
+ const {
29
+ videoSize,
30
+ timeLimit = DEFAULT_RECORDING_TIME_SEC,
31
+ bugReport,
32
+ bitRate,
33
+ forceRestart,
34
+ } = options;
35
+ if (!forceRestart) {
36
+ result = await this.stopRecordingScreen(options);
37
+ }
38
+
39
+ if (await terminateBackgroundScreenRecording(this.adb, true)) {
40
+ this.log.warn(
41
+ `There were some ${SCREENRECORD_BINARY} process leftovers running ` +
42
+ `in the background. Make sure you stop screen recording each time after it is started, ` +
43
+ `otherwise the recorded media might quickly exceed all the free space on the device under test.`,
44
+ );
45
+ }
46
+
47
+ if (!_.isEmpty(this._screenRecordingProperties)) {
48
+ // XXX: this doesn't need to be done in serial, does it?
49
+ for (const record of this._screenRecordingProperties.records || []) {
50
+ await this.adb.rimraf(record);
51
+ }
52
+ this._screenRecordingProperties = undefined;
53
+ }
54
+
55
+ const timeout = parseFloat(String(timeLimit));
56
+ if (isNaN(timeout) || timeout > MAX_TIME_SEC || timeout <= 0) {
57
+ throw new Error(
58
+ `The timeLimit value must be in range [1, ${MAX_TIME_SEC}] seconds. ` +
59
+ `The value of '${timeLimit}' has been passed instead.`,
60
+ );
61
+ }
62
+
63
+ this._screenRecordingProperties = {
64
+ timer: new timing.Timer().start(),
65
+ videoSize,
66
+ timeLimit,
67
+ currentTimeLimit: timeLimit,
68
+ bitRate,
69
+ bugReport,
70
+ records: [],
71
+ recordingProcess: null,
72
+ stopped: false,
73
+ };
74
+ await scheduleScreenRecord.bind(this)(this._screenRecordingProperties);
75
+ return result;
76
+ }
77
+
78
+ /**
79
+ *
80
+ * @this {import('../driver').AndroidDriver}
81
+ * @param {import('./types').StopScreenRecordingOpts} [options={}]
82
+ * @returns {Promise<string>}
83
+ */
84
+ export async function stopRecordingScreen(options = {}) {
85
+ await verifyScreenRecordIsSupported(this.adb, this.isEmulator());
86
+
87
+ if (!_.isEmpty(this._screenRecordingProperties)) {
88
+ this._screenRecordingProperties.stopped = true;
89
+ }
90
+
91
+ try {
92
+ await terminateBackgroundScreenRecording(this.adb, false);
93
+ } catch (err) {
94
+ this.log.warn(/** @type {Error} */ (err).message);
95
+ if (!_.isEmpty(this._screenRecordingProperties)) {
96
+ this.log.warn('The resulting video might be corrupted');
97
+ }
98
+ }
99
+
100
+ if (_.isEmpty(this._screenRecordingProperties)) {
101
+ this.log.info(
102
+ `Screen recording has not been previously started by Appium. There is nothing to stop`,
103
+ );
104
+ return '';
105
+ }
106
+
107
+ if (
108
+ this._screenRecordingProperties.recordingProcess &&
109
+ this._screenRecordingProperties.recordingProcess.isRunning
110
+ ) {
111
+ try {
112
+ await this._screenRecordingProperties.recordingProcess.stop(
113
+ 'SIGINT',
114
+ PROCESS_SHUTDOWN_TIMEOUT,
115
+ );
116
+ } catch (e) {
117
+ this.log.errorAndThrow(
118
+ `Unable to stop screen recording within ${PROCESS_SHUTDOWN_TIMEOUT}ms`,
119
+ );
120
+ }
121
+ this._screenRecordingProperties.recordingProcess = null;
122
+ }
123
+
124
+ if (_.isEmpty(this._screenRecordingProperties.records)) {
125
+ this.log.errorAndThrow(
126
+ `No screen recordings have been stored on the device so far. ` +
127
+ `Are you sure the ${SCREENRECORD_BINARY} utility works as expected?`,
128
+ );
129
+ }
130
+
131
+ const tmpRoot = await tempDir.openDir();
132
+ try {
133
+ const localRecords = [];
134
+ for (const pathOnDevice of this._screenRecordingProperties.records) {
135
+ const relativePath = path.resolve(tmpRoot, path.posix.basename(pathOnDevice));
136
+ localRecords.push(relativePath);
137
+ await this.adb.pull(pathOnDevice, relativePath);
138
+ await this.adb.rimraf(pathOnDevice);
139
+ }
140
+ let resultFilePath = /** @type {string} */ (_.last(localRecords));
141
+ if (localRecords.length > 1) {
142
+ this.log.info(`Got ${localRecords.length} screen recordings. Trying to merge them`);
143
+ try {
144
+ resultFilePath = await mergeScreenRecords.bind(this)(localRecords);
145
+ } catch (e) {
146
+ this.log.warn(
147
+ `Cannot merge the recorded files. The most recent screen recording is going to be returned as the result. ` +
148
+ `Original error: ${/** @type {Error} */ (e).message}`,
149
+ );
150
+ }
151
+ }
152
+ if (_.isEmpty(options.remotePath)) {
153
+ const {size} = await fs.stat(resultFilePath);
154
+ this.log.debug(
155
+ `The size of the resulting screen recording is ${util.toReadableSizeString(size)}`,
156
+ );
157
+ }
158
+ return await uploadRecordedMedia(resultFilePath, options.remotePath, options);
159
+ } finally {
160
+ await fs.rimraf(tmpRoot);
161
+ this._screenRecordingProperties = undefined;
162
+ }
163
+ }
164
+
165
+ // #region Internal helpers
166
+
21
167
  /**
22
168
  *
23
169
  * @param {string} localFile
@@ -56,24 +202,22 @@ async function verifyScreenRecordIsSupported(adb, isEmulator) {
56
202
  const apiLevel = await adb.getApiLevel();
57
203
  if (isEmulator && apiLevel < MIN_EMULATOR_API_LEVEL) {
58
204
  throw new Error(
59
- `Screen recording does not work on emulators running Android API level less than ${MIN_EMULATOR_API_LEVEL}`
205
+ `Screen recording does not work on emulators running Android API level less than ${MIN_EMULATOR_API_LEVEL}`,
60
206
  );
61
207
  }
62
208
  if (apiLevel < 19) {
63
209
  throw new Error(
64
- `Screen recording not available on API Level ${apiLevel}. Minimum API Level is 19.`
210
+ `Screen recording not available on API Level ${apiLevel}. Minimum API Level is 19.`,
65
211
  );
66
212
  }
67
213
  }
68
214
 
69
215
  /**
70
- *
71
- * @param {ADB} adb
216
+ * @this {import('../driver').AndroidDriver}
72
217
  * @param {import('@appium/types').StringRecord} recordingProperties
73
- * @param {import('@appium/types').AppiumLogger} [log]
74
218
  * @returns {Promise<void>}
75
219
  */
76
- async function scheduleScreenRecord(adb, recordingProperties, log) {
220
+ async function scheduleScreenRecord(recordingProperties) {
77
221
  if (recordingProperties.stopped) {
78
222
  return;
79
223
  }
@@ -88,7 +232,7 @@ async function scheduleScreenRecord(adb, recordingProperties, log) {
88
232
  }
89
233
  }
90
234
  const pathOnDevice = `/sdcard/${util.uuidV4().substring(0, 8)}${DEFAULT_EXT}`;
91
- const recordingProc = adb.screenrecord(pathOnDevice, {
235
+ const recordingProc = this.adb.screenrecord(pathOnDevice, {
92
236
  videoSize,
93
237
  bitRate,
94
238
  timeLimit: currentTimeLimit,
@@ -100,10 +244,10 @@ async function scheduleScreenRecord(adb, recordingProperties, log) {
100
244
  return;
101
245
  }
102
246
  const currentDuration = timer.getDuration().asSeconds.toFixed(0);
103
- log?.debug(`The overall screen recording duration is ${currentDuration}s so far`);
247
+ this.log.debug(`The overall screen recording duration is ${currentDuration}s so far`);
104
248
  const timeLimitInt = parseInt(timeLimit, 10);
105
249
  if (isNaN(timeLimitInt) || currentDuration >= timeLimitInt) {
106
- log?.debug('There is no need to start the next recording chunk');
250
+ this.log.debug('There is no need to start the next recording chunk');
107
251
  return;
108
252
  }
109
253
 
@@ -112,15 +256,15 @@ async function scheduleScreenRecord(adb, recordingProperties, log) {
112
256
  recordingProperties.currentTimeLimit < MAX_RECORDING_TIME_SEC
113
257
  ? recordingProperties.currentTimeLimit
114
258
  : MAX_RECORDING_TIME_SEC;
115
- log?.debug(
259
+ this.log.debug(
116
260
  `Starting the next ${chunkDuration}s-chunk ` +
117
- `of screen recording in order to achieve ${timeLimitInt}s total duration`
261
+ `of screen recording in order to achieve ${timeLimitInt}s total duration`,
118
262
  );
119
263
  (async () => {
120
264
  try {
121
- await scheduleScreenRecord(adb, recordingProperties, log);
265
+ await scheduleScreenRecord.bind(this)(recordingProperties);
122
266
  } catch (e) {
123
- log?.error(/** @type {Error} */ (e).stack);
267
+ this.log.error(/** @type {Error} */ (e).stack);
124
268
  recordingProperties.stopped = true;
125
269
  }
126
270
  })();
@@ -128,14 +272,14 @@ async function scheduleScreenRecord(adb, recordingProperties, log) {
128
272
 
129
273
  await recordingProc.start(0);
130
274
  try {
131
- await waitForCondition(async () => await adb.fileExists(pathOnDevice), {
275
+ await waitForCondition(async () => await this.adb.fileExists(pathOnDevice), {
132
276
  waitMs: RETRY_TIMEOUT,
133
277
  intervalMs: RETRY_PAUSE,
134
278
  });
135
279
  } catch (e) {
136
280
  throw new Error(
137
281
  `The expected screen record file '${pathOnDevice}' does not exist after ${RETRY_TIMEOUT}ms. ` +
138
- `Is ${SCREENRECORD_BINARY} utility available and operational on the device under test?`
282
+ `Is ${SCREENRECORD_BINARY} utility available and operational on the device under test?`,
139
283
  );
140
284
  }
141
285
 
@@ -145,29 +289,29 @@ async function scheduleScreenRecord(adb, recordingProperties, log) {
145
289
 
146
290
  /**
147
291
  *
292
+ * @this {import('../driver').AndroidDriver}
148
293
  * @param {string[]} mediaFiles
149
- * @param {import('@appium/types').AppiumLogger} [log]
150
294
  * @returns {Promise<string>}
151
295
  */
152
- async function mergeScreenRecords(mediaFiles, log) {
296
+ async function mergeScreenRecords(mediaFiles) {
153
297
  try {
154
298
  await fs.which(FFMPEG_BINARY);
155
299
  } catch (e) {
156
300
  throw new Error(
157
- `${FFMPEG_BINARY} utility is not available in PATH. Please install it from https://www.ffmpeg.org/`
301
+ `${FFMPEG_BINARY} utility is not available in PATH. Please install it from https://www.ffmpeg.org/`,
158
302
  );
159
303
  }
160
304
  const configContent = mediaFiles.map((x) => `file '${x}'`).join('\n');
161
305
  const configFile = path.resolve(path.dirname(mediaFiles[0]), 'config.txt');
162
306
  await fs.writeFile(configFile, configContent, 'utf8');
163
- log?.debug(`Generated ffmpeg merging config '${configFile}' with items:\n${configContent}`);
307
+ this.log.debug(`Generated ffmpeg merging config '${configFile}' with items:\n${configContent}`);
164
308
  const result = path.resolve(
165
309
  path.dirname(mediaFiles[0]),
166
- `merge_${Math.floor(+new Date())}${DEFAULT_EXT}`
310
+ `merge_${Math.floor(+new Date())}${DEFAULT_EXT}`,
167
311
  );
168
312
  const args = ['-safe', '0', '-f', 'concat', '-i', configFile, '-c', 'copy', result];
169
- log?.info(
170
- `Initiating screen records merging using the command '${FFMPEG_BINARY} ${args.join(' ')}'`
313
+ this.log.info(
314
+ `Initiating screen records merging using the command '${FFMPEG_BINARY} ${args.join(' ')}'`,
171
315
  );
172
316
  await exec(FFMPEG_BINARY, args);
173
317
  return result;
@@ -194,155 +338,12 @@ async function terminateBackgroundScreenRecording(adb, force = true) {
194
338
  return true;
195
339
  } catch (err) {
196
340
  throw new Error(
197
- `Unable to stop the background screen recording: ${/** @type {Error} */ (err).message}`
341
+ `Unable to stop the background screen recording: ${/** @type {Error} */ (err).message}`,
198
342
  );
199
343
  }
200
344
  }
201
345
 
202
- /**
203
- * @type {import('./mixins').RecordScreenMixin & ThisType<import('../driver').AndroidDriver>}
204
- * @satisfies {import('@appium/types').ExternalDriver}
205
- */
206
- const RecordScreenMixin = {
207
- async startRecordingScreen(options = {}) {
208
- await verifyScreenRecordIsSupported(this.adb, this.isEmulator());
209
-
210
- let result = '';
211
- const {
212
- videoSize,
213
- timeLimit = DEFAULT_RECORDING_TIME_SEC,
214
- bugReport,
215
- bitRate,
216
- forceRestart,
217
- } = options;
218
- if (!forceRestart) {
219
- result = await this.stopRecordingScreen(options);
220
- }
221
-
222
- if (await terminateBackgroundScreenRecording(this.adb, true)) {
223
- this.log.warn(
224
- `There were some ${SCREENRECORD_BINARY} process leftovers running ` +
225
- `in the background. Make sure you stop screen recording each time after it is started, ` +
226
- `otherwise the recorded media might quickly exceed all the free space on the device under test.`
227
- );
228
- }
229
-
230
- if (!_.isEmpty(this._screenRecordingProperties)) {
231
- // XXX: this doesn't need to be done in serial, does it?
232
- for (const record of this._screenRecordingProperties.records || []) {
233
- await this.adb.rimraf(record);
234
- }
235
- this._screenRecordingProperties = undefined;
236
- }
237
-
238
- const timeout = parseFloat(String(timeLimit));
239
- if (isNaN(timeout) || timeout > MAX_TIME_SEC || timeout <= 0) {
240
- throw new Error(
241
- `The timeLimit value must be in range [1, ${MAX_TIME_SEC}] seconds. ` +
242
- `The value of '${timeLimit}' has been passed instead.`
243
- );
244
- }
245
-
246
- this._screenRecordingProperties = {
247
- timer: new timing.Timer().start(),
248
- videoSize,
249
- timeLimit,
250
- currentTimeLimit: timeLimit,
251
- bitRate,
252
- bugReport,
253
- records: [],
254
- recordingProcess: null,
255
- stopped: false,
256
- };
257
- await scheduleScreenRecord(this.adb, this._screenRecordingProperties, this.log);
258
- return result;
259
- },
260
-
261
- async stopRecordingScreen(options = {}) {
262
- await verifyScreenRecordIsSupported(this.adb, this.isEmulator());
263
-
264
- if (!_.isEmpty(this._screenRecordingProperties)) {
265
- this._screenRecordingProperties.stopped = true;
266
- }
267
-
268
- try {
269
- await terminateBackgroundScreenRecording(this.adb, false);
270
- } catch (err) {
271
- this.log.warn(/** @type {Error} */ (err).message);
272
- if (!_.isEmpty(this._screenRecordingProperties)) {
273
- this.log.warn('The resulting video might be corrupted');
274
- }
275
- }
276
-
277
- if (_.isEmpty(this._screenRecordingProperties)) {
278
- this.log.info(
279
- `Screen recording has not been previously started by Appium. There is nothing to stop`
280
- );
281
- return '';
282
- }
283
-
284
- if (
285
- this._screenRecordingProperties.recordingProcess &&
286
- this._screenRecordingProperties.recordingProcess.isRunning
287
- ) {
288
- try {
289
- await this._screenRecordingProperties.recordingProcess.stop(
290
- 'SIGINT',
291
- PROCESS_SHUTDOWN_TIMEOUT
292
- );
293
- } catch (e) {
294
- this.log.errorAndThrow(
295
- `Unable to stop screen recording within ${PROCESS_SHUTDOWN_TIMEOUT}ms`
296
- );
297
- }
298
- this._screenRecordingProperties.recordingProcess = null;
299
- }
300
-
301
- if (_.isEmpty(this._screenRecordingProperties.records)) {
302
- this.log.errorAndThrow(
303
- `No screen recordings have been stored on the device so far. ` +
304
- `Are you sure the ${SCREENRECORD_BINARY} utility works as expected?`
305
- );
306
- }
307
-
308
- const tmpRoot = await tempDir.openDir();
309
- try {
310
- const localRecords = [];
311
- for (const pathOnDevice of this._screenRecordingProperties.records) {
312
- const relativePath = path.resolve(tmpRoot, path.posix.basename(pathOnDevice));
313
- localRecords.push(relativePath);
314
- await this.adb.pull(pathOnDevice, relativePath);
315
- await this.adb.rimraf(pathOnDevice);
316
- }
317
- let resultFilePath = /** @type {string} */ (_.last(localRecords));
318
- if (localRecords.length > 1) {
319
- this.log.info(`Got ${localRecords.length} screen recordings. Trying to merge them`);
320
- try {
321
- resultFilePath = await mergeScreenRecords(localRecords, this.log);
322
- } catch (e) {
323
- this.log.warn(
324
- `Cannot merge the recorded files. The most recent screen recording is going to be returned as the result. ` +
325
- `Original error: ${/** @type {Error} */ (e).message}`
326
- );
327
- }
328
- }
329
- if (_.isEmpty(options.remotePath)) {
330
- const {size} = await fs.stat(resultFilePath);
331
- this.log.debug(
332
- `The size of the resulting screen recording is ${util.toReadableSizeString(size)}`
333
- );
334
- }
335
- return await uploadRecordedMedia(resultFilePath, options.remotePath, options);
336
- } finally {
337
- await fs.rimraf(tmpRoot);
338
- this._screenRecordingProperties = undefined;
339
- }
340
- },
341
- };
342
-
343
- mixin(RecordScreenMixin);
344
-
345
- export default RecordScreenMixin;
346
+ // #endregion
346
347
 
347
348
  /**
348
349
  * @typedef {import('appium-adb').ADB} ADB
@@ -0,0 +1,96 @@
1
+ import path from 'node:path';
2
+ import _ from 'lodash';
3
+ import {fs, util} from '@appium/support';
4
+
5
+ /**
6
+ * @this {import('../driver').AndroidDriver}
7
+ * @param {string?} [language=null]
8
+ * @returns {Promise<import('@appium/types').StringRecord>}}
9
+ */
10
+ export async function getStrings(language = null) {
11
+ if (!language) {
12
+ language = await this.adb.getDeviceLanguage();
13
+ this.log.info(`No language specified, returning strings for: ${language}`);
14
+ }
15
+
16
+ // Clients require the resulting mapping to have both keys
17
+ // and values of type string
18
+ /** @param {import('@appium/types').StringRecord} mapping */
19
+ const preprocessStringsMap = (mapping) => {
20
+ /** @type {import('@appium/types').StringRecord} */
21
+ const result = {};
22
+ for (const [key, value] of _.toPairs(mapping)) {
23
+ result[key] = _.isString(value) ? value : JSON.stringify(value);
24
+ }
25
+ return result;
26
+ };
27
+
28
+ if (!this.apkStrings[language]) {
29
+ this.apkStrings[language] = await extractStringsFromResources.bind(this)(language);
30
+ }
31
+ const mapping = JSON.parse(await fs.readFile(this.apkStrings[language], 'utf-8'));
32
+ return preprocessStringsMap(mapping);
33
+ }
34
+
35
+ /**
36
+ * @this {import('../driver').AndroidDriver}
37
+ * @param {string} language
38
+ * @param {string} country
39
+ * @param {string} [script]
40
+ * @returns {Promise<void>}}
41
+ */
42
+ export async function ensureDeviceLocale(language, country, script) {
43
+ await this.settingsApp.setDeviceLocale(language, country, script);
44
+
45
+ if (!(await this.adb.ensureCurrentLocale(language, country, script))) {
46
+ const message = script
47
+ ? `language: ${language}, country: ${country} and script: ${script}`
48
+ : `language: ${language} and country: ${country}`;
49
+ throw new Error(`Failed to set ${message}`);
50
+ }
51
+ }
52
+
53
+ // #region Internal helpers
54
+
55
+ /**
56
+ * @this {import('../driver').AndroidDriver}
57
+ * @param {string?} [language]
58
+ * @param {import('../driver').AndroidDriverOpts?} [opts=null]
59
+ * @returns {Promise<string>};
60
+ */
61
+ async function extractStringsFromResources(language, opts = null) {
62
+ const caps = opts ?? this.opts;
63
+
64
+ /** @type {string|undefined} */
65
+ let app;
66
+ try {
67
+ app =
68
+ caps.app ||
69
+ (caps.appPackage && caps.tmpDir && (await this.adb.pullApk(caps.appPackage, caps.tmpDir)));
70
+ } catch (err) {
71
+ throw new Error(
72
+ `Failed to pull an apk from '${caps.appPackage}' to '${caps.tmpDir}'. Original error: ${err.message}`,
73
+ );
74
+ }
75
+
76
+ if (!app || !(await fs.exists(app))) {
77
+ throw new Error(`Could not extract app strings, no app or package specified`);
78
+ }
79
+
80
+ const stringsTmpDir = path.resolve(String(caps.tmpDir), util.uuidV4());
81
+ try {
82
+ this.log.debug(
83
+ `Extracting strings from '${app}' for the language '${language || 'default'}' into '${stringsTmpDir}'`,
84
+ );
85
+ const {localPath} = await this.adb.extractStringsFromApk(app, language ?? null, stringsTmpDir);
86
+ return localPath;
87
+ } catch (err) {
88
+ throw new Error(`Could not extract app strings. Original error: ${err.message}`);
89
+ }
90
+ }
91
+
92
+ // #endregion
93
+
94
+ /**
95
+ * @typedef {import('appium-adb').ADB} ADB
96
+ */
@@ -1,56 +1,42 @@
1
- // @ts-check
2
-
3
1
  import {util} from '@appium/support';
4
2
  import {errors} from 'appium/driver';
5
3
  import _ from 'lodash';
6
4
  import {exec} from 'teen_process';
7
5
  import {ADB_SHELL_FEATURE} from '../utils';
8
- import {mixin} from './mixins';
9
6
 
10
7
  /**
11
- * @type {import('./mixins').ShellMixin & ThisType<import('../driver').AndroidDriver>}
12
- * @satisfies {import('@appium/types').ExternalDriver}
8
+ * @this {import('../driver').AndroidDriver}
9
+ * @param {import('./types').ShellOpts} [opts={}]
10
+ * @returns {Promise<string | {stderr: string; stdout: string}>};
13
11
  */
14
- const ShellMixin = {
15
- async mobileShell(opts) {
16
- this.ensureFeatureEnabled(ADB_SHELL_FEATURE);
17
- const {
18
- command,
19
- args = /** @type {string[]} */ ([]),
20
- timeout = 20000,
21
- includeStderr,
22
- } = opts ?? {};
12
+ export async function mobileShell(opts) {
13
+ this.ensureFeatureEnabled(ADB_SHELL_FEATURE);
14
+ const {command, args = /** @type {string[]} */ ([]), timeout = 20000, includeStderr} = opts ?? {};
23
15
 
24
- if (!_.isString(command)) {
25
- throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
26
- }
16
+ if (!_.isString(command)) {
17
+ throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
18
+ }
27
19
 
28
- const adbArgs = [...this.adb.executable.defaultArgs, 'shell', command, ..._.castArray(args)];
29
- this.log.debug(`Running '${this.adb.executable.path} ${util.quote(adbArgs)}'`);
30
- try {
31
- const {stdout, stderr} = await exec(this.adb.executable.path, adbArgs, {timeout});
32
- if (includeStderr) {
33
- return {
34
- stdout,
35
- stderr,
36
- };
37
- }
38
- return stdout;
39
- } catch (e) {
40
- const err = /** @type {import('teen_process').ExecError} */ (e);
41
- this.log.errorAndThrow(
42
- `Cannot execute the '${command}' shell command. ` +
43
- `Original error: ${err.message}. ` +
44
- `StdOut: ${err.stdout}. StdErr: ${err.stderr}`
45
- );
46
- throw new Error(); // unreachable; for TS
20
+ const adbArgs = [...this.adb.executable.defaultArgs, 'shell', command, ..._.castArray(args)];
21
+ this.log.debug(`Running '${this.adb.executable.path} ${util.quote(adbArgs)}'`);
22
+ try {
23
+ const {stdout, stderr} = await exec(this.adb.executable.path, adbArgs, {timeout});
24
+ if (includeStderr) {
25
+ return {
26
+ stdout,
27
+ stderr,
28
+ };
47
29
  }
48
- },
49
- };
50
-
51
- mixin(ShellMixin);
52
-
53
- export default ShellMixin;
30
+ return stdout;
31
+ } catch (e) {
32
+ const err = /** @type {import('teen_process').ExecError} */ (e);
33
+ throw this.log.errorAndThrow(
34
+ `Cannot execute the '${command}' shell command. ` +
35
+ `Original error: ${err.message}. ` +
36
+ `StdOut: ${err.stdout}. StdErr: ${err.stderr}`,
37
+ );
38
+ }
39
+ }
54
40
 
55
41
  /**
56
42
  * @typedef {import('appium-adb').ADB} ADB