appium-android-driver 5.14.7 → 6.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 (192) hide show
  1. package/build/index.d.ts +282 -0
  2. package/build/index.d.ts.map +1 -0
  3. package/build/index.js.map +1 -0
  4. package/build/lib/commands/actions.d.ts +6 -224
  5. package/build/lib/commands/actions.d.ts.map +1 -1
  6. package/build/lib/commands/actions.js +306 -405
  7. package/build/lib/commands/actions.js.map +1 -1
  8. package/build/lib/commands/alert.d.ts +7 -9
  9. package/build/lib/commands/alert.d.ts.map +1 -1
  10. package/build/lib/commands/alert.js +24 -18
  11. package/build/lib/commands/alert.js.map +1 -1
  12. package/build/lib/commands/app-management.d.ts +7 -313
  13. package/build/lib/commands/app-management.d.ts.map +1 -1
  14. package/build/lib/commands/app-management.js +135 -293
  15. package/build/lib/commands/app-management.js.map +1 -1
  16. package/build/lib/commands/context.d.ts +8 -92
  17. package/build/lib/commands/context.d.ts.map +1 -1
  18. package/build/lib/commands/context.js +381 -439
  19. package/build/lib/commands/context.js.map +1 -1
  20. package/build/lib/commands/element.d.ts +8 -35
  21. package/build/lib/commands/element.d.ts.map +1 -1
  22. package/build/lib/commands/element.js +153 -136
  23. package/build/lib/commands/element.js.map +1 -1
  24. package/build/lib/commands/emu-console.d.ts +6 -48
  25. package/build/lib/commands/emu-console.d.ts.map +1 -1
  26. package/build/lib/commands/emu-console.js +19 -34
  27. package/build/lib/commands/emu-console.js.map +1 -1
  28. package/build/lib/commands/execute.d.ts +6 -5
  29. package/build/lib/commands/execute.d.ts.map +1 -1
  30. package/build/lib/commands/execute.js +77 -66
  31. package/build/lib/commands/execute.js.map +1 -1
  32. package/build/lib/commands/file-actions.d.ts +7 -128
  33. package/build/lib/commands/file-actions.d.ts.map +1 -1
  34. package/build/lib/commands/file-actions.js +183 -219
  35. package/build/lib/commands/file-actions.js.map +1 -1
  36. package/build/lib/commands/find.d.ts +8 -12
  37. package/build/lib/commands/find.d.ts.map +1 -1
  38. package/build/lib/commands/find.js +19 -23
  39. package/build/lib/commands/find.js.map +1 -1
  40. package/build/lib/commands/general.d.ts +9 -132
  41. package/build/lib/commands/general.d.ts.map +1 -1
  42. package/build/lib/commands/general.js +281 -312
  43. package/build/lib/commands/general.js.map +1 -1
  44. package/build/lib/commands/ime.d.ts +7 -10
  45. package/build/lib/commands/ime.d.ts.map +1 -1
  46. package/build/lib/commands/ime.js +47 -35
  47. package/build/lib/commands/ime.js.map +1 -1
  48. package/build/lib/commands/index.d.ts +27 -2
  49. package/build/lib/commands/index.d.ts.map +1 -1
  50. package/build/lib/commands/index.js +41 -19
  51. package/build/lib/commands/index.js.map +1 -1
  52. package/build/lib/commands/intent.d.ts +7 -417
  53. package/build/lib/commands/intent.d.ts.map +1 -1
  54. package/build/lib/commands/intent.js +104 -216
  55. package/build/lib/commands/intent.js.map +1 -1
  56. package/build/lib/commands/keyboard.d.ts +6 -5
  57. package/build/lib/commands/keyboard.d.ts.map +1 -1
  58. package/build/lib/commands/keyboard.js +16 -8
  59. package/build/lib/commands/keyboard.js.map +1 -1
  60. package/build/lib/commands/log.d.ts +7 -44
  61. package/build/lib/commands/log.d.ts.map +1 -1
  62. package/build/lib/commands/log.js +146 -108
  63. package/build/lib/commands/log.js.map +1 -1
  64. package/build/lib/commands/media-projection.d.ts +7 -143
  65. package/build/lib/commands/media-projection.d.ts.map +1 -1
  66. package/build/lib/commands/media-projection.js +113 -140
  67. package/build/lib/commands/media-projection.js.map +1 -1
  68. package/build/lib/commands/mixins.d.ts +740 -0
  69. package/build/lib/commands/mixins.d.ts.map +1 -0
  70. package/build/lib/commands/mixins.js +19 -0
  71. package/build/lib/commands/mixins.js.map +1 -0
  72. package/build/lib/commands/network.d.ts +7 -138
  73. package/build/lib/commands/network.d.ts.map +1 -1
  74. package/build/lib/commands/network.js +212 -254
  75. package/build/lib/commands/network.js.map +1 -1
  76. package/build/lib/commands/performance.d.ts +24 -70
  77. package/build/lib/commands/performance.d.ts.map +1 -1
  78. package/build/lib/commands/performance.js +144 -100
  79. package/build/lib/commands/performance.js.map +1 -1
  80. package/build/lib/commands/permissions.d.ts +8 -92
  81. package/build/lib/commands/permissions.d.ts.map +1 -1
  82. package/build/lib/commands/permissions.js +75 -87
  83. package/build/lib/commands/permissions.js.map +1 -1
  84. package/build/lib/commands/recordscreen.d.ts +7 -193
  85. package/build/lib/commands/recordscreen.d.ts.map +1 -1
  86. package/build/lib/commands/recordscreen.js +151 -182
  87. package/build/lib/commands/recordscreen.js.map +1 -1
  88. package/build/lib/commands/shell.d.ts +7 -7
  89. package/build/lib/commands/shell.d.ts.map +1 -1
  90. package/build/lib/commands/shell.js +40 -33
  91. package/build/lib/commands/shell.js.map +1 -1
  92. package/build/lib/commands/streamscreen.d.ts +9 -103
  93. package/build/lib/commands/streamscreen.d.ts.map +1 -1
  94. package/build/lib/commands/streamscreen.js +261 -218
  95. package/build/lib/commands/streamscreen.js.map +1 -1
  96. package/build/lib/commands/system-bars.d.ts +22 -90
  97. package/build/lib/commands/system-bars.d.ts.map +1 -1
  98. package/build/lib/commands/system-bars.js +76 -74
  99. package/build/lib/commands/system-bars.js.map +1 -1
  100. package/build/lib/commands/touch.d.ts +10 -29
  101. package/build/lib/commands/touch.d.ts.map +1 -1
  102. package/build/lib/commands/touch.js +301 -285
  103. package/build/lib/commands/touch.js.map +1 -1
  104. package/build/lib/commands/types.d.ts +978 -0
  105. package/build/lib/commands/types.d.ts.map +1 -0
  106. package/build/lib/commands/types.js +3 -0
  107. package/build/lib/commands/types.js.map +1 -0
  108. package/build/lib/constraints.d.ts +291 -0
  109. package/build/lib/constraints.d.ts.map +1 -0
  110. package/build/lib/constraints.js +300 -0
  111. package/build/lib/constraints.js.map +1 -0
  112. package/build/lib/driver.d.ts +68 -37
  113. package/build/lib/driver.d.ts.map +1 -1
  114. package/build/lib/driver.js +123 -80
  115. package/build/lib/driver.js.map +1 -1
  116. package/build/lib/helpers/android.d.ts +164 -0
  117. package/build/lib/helpers/android.d.ts.map +1 -0
  118. package/build/lib/helpers/android.js +819 -0
  119. package/build/lib/helpers/android.js.map +1 -0
  120. package/build/lib/helpers/index.d.ts +7 -0
  121. package/build/lib/helpers/index.d.ts.map +1 -0
  122. package/build/lib/helpers/index.js +29 -0
  123. package/build/lib/helpers/index.js.map +1 -0
  124. package/build/lib/helpers/types.d.ts +121 -0
  125. package/build/lib/helpers/types.d.ts.map +1 -0
  126. package/build/lib/helpers/types.js +3 -0
  127. package/build/lib/helpers/types.js.map +1 -0
  128. package/build/lib/helpers/unlock.d.ts +32 -0
  129. package/build/lib/helpers/unlock.d.ts.map +1 -0
  130. package/build/lib/helpers/unlock.js +273 -0
  131. package/build/lib/helpers/unlock.js.map +1 -0
  132. package/build/lib/helpers/webview.d.ts +74 -0
  133. package/build/lib/helpers/webview.d.ts.map +1 -0
  134. package/build/lib/helpers/webview.js +421 -0
  135. package/build/lib/helpers/webview.js.map +1 -0
  136. package/build/lib/index.d.ts +9 -0
  137. package/build/lib/index.d.ts.map +1 -0
  138. package/build/lib/index.js +37 -0
  139. package/build/lib/index.js.map +1 -0
  140. package/build/lib/method-map.d.ts +0 -8
  141. package/build/lib/method-map.d.ts.map +1 -1
  142. package/build/lib/method-map.js +63 -74
  143. package/build/lib/method-map.js.map +1 -1
  144. package/build/lib/stubs.d.ts +0 -1
  145. package/build/lib/stubs.d.ts.map +1 -1
  146. package/build/lib/stubs.js +1 -0
  147. package/build/lib/stubs.js.map +1 -1
  148. package/build/lib/utils.d.ts +1 -1
  149. package/build/lib/utils.d.ts.map +1 -1
  150. package/lib/commands/actions.js +351 -464
  151. package/lib/commands/alert.js +27 -17
  152. package/lib/commands/app-management.js +156 -314
  153. package/lib/commands/context.js +457 -441
  154. package/lib/commands/element.js +201 -157
  155. package/lib/commands/emu-console.js +25 -45
  156. package/lib/commands/execute.js +106 -90
  157. package/lib/commands/file-actions.js +222 -240
  158. package/lib/commands/find.ts +103 -0
  159. package/lib/commands/general.js +327 -339
  160. package/lib/commands/ime.js +50 -34
  161. package/lib/commands/{index.js → index.ts} +20 -24
  162. package/lib/commands/intent.js +108 -249
  163. package/lib/commands/keyboard.js +20 -8
  164. package/lib/commands/log.js +172 -116
  165. package/lib/commands/media-projection.js +134 -161
  166. package/lib/commands/mixins.ts +966 -0
  167. package/lib/commands/network.js +252 -281
  168. package/lib/commands/performance.js +203 -132
  169. package/lib/commands/permissions.js +108 -109
  170. package/lib/commands/recordscreen.js +212 -209
  171. package/lib/commands/shell.js +51 -40
  172. package/lib/commands/streamscreen.js +355 -289
  173. package/lib/commands/system-bars.js +92 -83
  174. package/lib/commands/touch.js +357 -294
  175. package/lib/commands/types.ts +1097 -0
  176. package/lib/{desired-caps.js → constraints.ts} +106 -103
  177. package/lib/{driver.js → driver.ts} +278 -132
  178. package/lib/helpers/android.ts +1143 -0
  179. package/lib/helpers/index.ts +6 -0
  180. package/lib/helpers/types.ts +134 -0
  181. package/lib/helpers/unlock.ts +329 -0
  182. package/lib/helpers/webview.ts +582 -0
  183. package/lib/index.ts +18 -0
  184. package/lib/method-map.js +87 -98
  185. package/lib/stubs.ts +0 -1
  186. package/package.json +27 -20
  187. package/index.js +0 -24
  188. package/lib/android-helpers.js +0 -983
  189. package/lib/commands/coverage.js +0 -18
  190. package/lib/commands/find.js +0 -82
  191. package/lib/unlock-helpers.js +0 -278
  192. package/lib/webview-helpers.js +0 -602
@@ -1,18 +1,17 @@
1
+ // @ts-check
2
+
1
3
  import _ from 'lodash';
2
- import { fs, util, zip, tempDir } from '@appium/support';
4
+ import {fs, util, zip, tempDir} from '@appium/support';
3
5
  import path from 'path';
4
- import { errors } from 'appium/driver';
5
- import { requireArgs } from '../utils';
6
-
6
+ import {errors} from 'appium/driver';
7
+ import {requireArgs} from '../utils';
8
+ import {mixin} from './mixins';
7
9
 
8
10
  const CONTAINER_PATH_MARKER = '@';
9
11
  // https://regex101.com/r/PLdB0G/2
10
12
  const CONTAINER_PATH_PATTERN = new RegExp(`^${CONTAINER_PATH_MARKER}([^/]+)/(.+)`);
11
13
  const ANDROID_MEDIA_RESCAN_INTENT = 'android.intent.action.MEDIA_SCANNER_SCAN_FILE';
12
14
 
13
-
14
- const commands = {};
15
-
16
15
  /**
17
16
  * Parses the actual destination path from the given value
18
17
  *
@@ -22,11 +21,13 @@ const commands = {};
22
21
  * identifier and the second one is the actual destination path inside the package.
23
22
  * @throws {Error} If the given string cannot be parsed
24
23
  */
25
- function parseContainerPath (remotePath) {
24
+ function parseContainerPath(remotePath) {
26
25
  const match = CONTAINER_PATH_PATTERN.exec(remotePath);
27
26
  if (!match) {
28
- throw new Error(`It is expected that package identifier is separated from the relative path with a single slash. ` +
29
- `'${remotePath}' is given instead`);
27
+ throw new Error(
28
+ `It is expected that package identifier is separated from the relative path with a single slash. ` +
29
+ `'${remotePath}' is given instead`
30
+ );
30
31
  }
31
32
  return [match[1], path.posix.resolve(`/data/data/${match[1]}`, match[2])];
32
33
  }
@@ -37,24 +38,33 @@ function parseContainerPath (remotePath) {
37
38
  * Exceptions are ignored and written into the log.
38
39
  *
39
40
  * @param {ADB} adb ADB instance
40
- * @param {Object?} log Logger instance
41
41
  * @param {string} remotePath The file/folder path on the remote device
42
+ * @param {import('@appium/types').AppiumLogger} [log] Logger instance
42
43
  */
43
- async function scanMedia (adb, remotePath, log = null) {
44
+ async function scanMedia(adb, remotePath, log) {
44
45
  log?.debug(`Performing media scan of '${remotePath}'`);
45
46
  try {
46
47
  // https://github.com/appium/appium/issues/16184
47
- if (await adb.getApiLevel() >= 29) {
48
+ if ((await adb.getApiLevel()) >= 29) {
48
49
  await adb.scanMedia(remotePath);
49
50
  } else {
50
51
  await adb.shell([
51
- 'am', 'broadcast',
52
- '-a', ANDROID_MEDIA_RESCAN_INTENT,
53
- '-d', `file://${remotePath}`
52
+ 'am',
53
+ 'broadcast',
54
+ '-a',
55
+ ANDROID_MEDIA_RESCAN_INTENT,
56
+ '-d',
57
+ `file://${remotePath}`,
54
58
  ]);
55
59
  }
56
60
  } catch (e) {
57
- log?.warn(`Ignoring an unexpected error upon media scanning of '${remotePath}': ${e.stderr || e.message}`);
61
+ const err = /** @type {any} */ (e);
62
+ // FIXME: what has a `stderr` prop?
63
+ log?.warn(
64
+ `Ignoring an unexpected error upon media scanning of '${remotePath}': ${
65
+ err.stderr ?? err.message
66
+ }`
67
+ );
58
68
  }
59
69
  }
60
70
 
@@ -65,199 +75,205 @@ async function scanMedia (adb, remotePath, log = null) {
65
75
  * @param {string} p The initial remote path
66
76
  * @returns {string} The escaped path value
67
77
  */
68
- function escapePath (p) {
78
+ function escapePath(p) {
69
79
  return p.replace(/'/g, `\\'`);
70
80
  }
71
81
 
72
82
  /**
73
- * Pulls a remote file from the device.
74
- * It is required, that a package has debugging flag enabled
75
- * in order to access its files.
76
- *
77
- * @param {string} remotePath The full path to the remote file
78
- * or a specially formatted path, which points to an item inside app bundle
79
- * @returns {string} Base64 encoded content of the pulled file
80
- * @throws {Error} If the pull operation failed
83
+ * @type {import('./mixins').FileActionsMixin & ThisType<import('../driver').AndroidDriver>}
84
+ * @satisfies {import('@appium/types').ExternalDriver}
81
85
  */
82
- commands.pullFile = async function pullFile (remotePath) {
83
- if (remotePath.endsWith('/')) {
84
- throw new errors.InvalidArgumentError(`It is expected that remote path points to a file and not to a folder. ` +
85
- `'${remotePath}' is given instead`);
86
- }
87
- let tmpDestination = null;
88
- if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
89
- const [packageId, pathInContainer] = parseContainerPath(remotePath);
90
- this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`);
91
- tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
92
- try {
93
- await this.adb.shell(['run-as', packageId, `chmod 777 '${escapePath(pathInContainer)}'`]);
94
- await this.adb.shell([
95
- 'run-as', packageId,
96
- `cp -f '${escapePath(pathInContainer)}' '${escapePath(tmpDestination)}'`
97
- ]);
98
- } catch (e) {
99
- this.log.errorAndThrow(`Cannot access the container of '${packageId}' application. ` +
100
- `Is the application installed and has 'debuggable' build option set to true? ` +
101
- `Original error: ${e.message}`);
86
+ const FileActionsMixin = {
87
+ async pullFile(remotePath) {
88
+ const adb = /** @type {ADB} */ (this.adb);
89
+ if (remotePath.endsWith('/')) {
90
+ throw new errors.InvalidArgumentError(
91
+ `It is expected that remote path points to a file and not to a folder. ` +
92
+ `'${remotePath}' is given instead`
93
+ );
102
94
  }
103
- }
104
- const localFile = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
105
- try {
106
- await this.adb.pull(tmpDestination || remotePath, localFile);
107
- return (await util.toInMemoryBase64(localFile)).toString();
108
- } finally {
109
- if (await fs.exists(localFile)) {
110
- await fs.unlink(localFile);
111
- }
112
- if (tmpDestination) {
113
- await this.adb.shell(['rm', '-f', tmpDestination]);
114
- }
115
- }
116
- };
117
-
118
- /**
119
- * @typedef {Object} PullFileOptions
120
- * @property {string} remotePath The full path to the remote file
121
- * or a specially formatted path, which points to an item inside an app bundle,
122
- * for example `@my.app.id/my/path`. It is mandatory for the app bundle to have
123
- * debugging enabled in order to use the latter remotePath format.
124
- */
125
-
126
- /**
127
- * Pulls a remote file from the device.
128
- *
129
- * @param {PullFileOptions} opts
130
- * @returns {string} The same as `pullFile`
131
- */
132
- commands.mobilePullFile = async function mobilePullFile (opts = {}) {
133
- const { remotePath } = requireArgs('remotePath', opts);
134
- return await this.pullFile(remotePath);
135
- };
136
-
137
- /**
138
- * Pushes the given data to a file on the remote device
139
- * It is required, that a package has debugging flag enabled
140
- * in order to access its files.
141
- * After a file is pushed it gets automatically scanned for possible
142
- * media occurrences. If the scan succeeds then the file is added to the
143
- * media library.
144
- *
145
- * @param {string} remotePath The full path to the remote file or
146
- * a file inside a package bundle
147
- * @param {string} base64Data Base64 encoded data to be written to the
148
- * remote file. The remote file will be silently overridden if it already exists.
149
- * @throws {Error} If there was an error while pushing the data
150
- */
151
- commands.pushFile = async function pushFile (remotePath, base64Data) {
152
- if (remotePath.endsWith('/')) {
153
- throw new errors.InvalidArgumentError(
154
- `It is expected that remote path points to a file and not to a folder. ` +
155
- `'${remotePath}' is given instead`
156
- );
157
- }
158
- const localFile = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
159
- if (_.isArray(base64Data)) {
160
- // some clients (ahem) java, send a byte array encoding utf8 characters
161
- // instead of a string, which would be infinitely better!
162
- base64Data = Buffer.from(base64Data).toString('utf8');
163
- }
164
- const content = Buffer.from(base64Data, 'base64');
165
- let tmpDestination = null;
166
- try {
167
- await fs.writeFile(localFile, content.toString('binary'), 'binary');
95
+ let tmpDestination = null;
168
96
  if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
169
97
  const [packageId, pathInContainer] = parseContainerPath(remotePath);
170
- this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. ` +
171
- `Will put the data into '${pathInContainer}'`);
98
+ this.log.debug(
99
+ `Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`
100
+ );
172
101
  tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
173
102
  try {
174
- await this.adb.shell(
175
- ['run-as', packageId, `mkdir -p '${escapePath(path.posix.dirname(pathInContainer))}'`]
176
- );
177
- await this.adb.shell(['run-as', packageId, `touch '${escapePath(pathInContainer)}'`]);
178
- await this.adb.shell(['run-as', packageId, `chmod 777 '${escapePath(pathInContainer)}'`]);
179
- await this.adb.push(localFile, tmpDestination);
180
- await this.adb.shell([
181
- 'run-as', packageId,
182
- `cp -f '${escapePath(tmpDestination)}' '${escapePath(pathInContainer)}'`
103
+ await adb.shell(['run-as', packageId, `chmod 777 '${escapePath(pathInContainer)}'`]);
104
+ await adb.shell([
105
+ 'run-as',
106
+ packageId,
107
+ `cp -f '${escapePath(pathInContainer)}' '${escapePath(tmpDestination)}'`,
183
108
  ]);
184
109
  } catch (e) {
185
- this.log.errorAndThrow(`Cannot access the container of '${packageId}' application. ` +
186
- `Is the application installed and has 'debuggable' build option set to true? ` +
187
- `Original error: ${e.message}`);
110
+ this.log.errorAndThrow(
111
+ `Cannot access the container of '${packageId}' application. ` +
112
+ `Is the application installed and has 'debuggable' build option set to true? ` +
113
+ `Original error: ${/** @type {Error} */ (e).message}`
114
+ );
188
115
  }
189
- } else {
190
- // adb push creates folders and overwrites existing files.
191
- await this.adb.push(localFile, remotePath);
116
+ }
117
+ const localFile = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
118
+ try {
119
+ await adb.pull(tmpDestination || remotePath, localFile);
120
+ return (await util.toInMemoryBase64(localFile)).toString();
121
+ } finally {
122
+ if (await fs.exists(localFile)) {
123
+ await fs.unlink(localFile);
124
+ }
125
+ if (tmpDestination) {
126
+ await adb.shell(['rm', '-f', tmpDestination]);
127
+ }
128
+ }
129
+ },
130
+
131
+ async mobilePullFile(opts) {
132
+ const {remotePath} = requireArgs('remotePath', opts);
133
+ return await this.pullFile(remotePath);
134
+ },
192
135
 
193
- // if we have pushed a file, it might be a media file, so ensure that
194
- // apps know about it
195
- await scanMedia(this.adb, remotePath, this.log);
136
+ async pushFile(remotePath, base64Data) {
137
+ if (remotePath.endsWith('/')) {
138
+ throw new errors.InvalidArgumentError(
139
+ `It is expected that remote path points to a file and not to a folder. ` +
140
+ `'${remotePath}' is given instead`
141
+ );
196
142
  }
197
- } finally {
198
- if (await fs.exists(localFile)) {
199
- await fs.unlink(localFile);
143
+ const localFile = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
144
+ if (_.isArray(base64Data)) {
145
+ // some clients (ahem) java, send a byte array encoding utf8 characters
146
+ // instead of a string, which would be infinitely better!
147
+ base64Data = Buffer.from(base64Data).toString('utf8');
200
148
  }
201
- if (tmpDestination) {
202
- await this.adb.shell(['rm', '-f', tmpDestination]);
149
+ const content = Buffer.from(base64Data, 'base64');
150
+ let tmpDestination = null;
151
+ const adb = /** @type {ADB} */ (this.adb);
152
+ try {
153
+ await fs.writeFile(localFile, content.toString('binary'), 'binary');
154
+ if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
155
+ const [packageId, pathInContainer] = parseContainerPath(remotePath);
156
+ this.log.debug(
157
+ `Parsed package identifier '${packageId}' from '${remotePath}'. ` +
158
+ `Will put the data into '${pathInContainer}'`
159
+ );
160
+ tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
161
+ try {
162
+ await adb.shell([
163
+ 'run-as',
164
+ packageId,
165
+ `mkdir -p '${escapePath(path.posix.dirname(pathInContainer))}'`,
166
+ ]);
167
+ await adb.shell(['run-as', packageId, `touch '${escapePath(pathInContainer)}'`]);
168
+ await adb.shell(['run-as', packageId, `chmod 777 '${escapePath(pathInContainer)}'`]);
169
+ await adb.push(localFile, tmpDestination);
170
+ await adb.shell([
171
+ 'run-as',
172
+ packageId,
173
+ `cp -f '${escapePath(tmpDestination)}' '${escapePath(pathInContainer)}'`,
174
+ ]);
175
+ } catch (e) {
176
+ this.log.errorAndThrow(
177
+ `Cannot access the container of '${packageId}' application. ` +
178
+ `Is the application installed and has 'debuggable' build option set to true? ` +
179
+ `Original error: ${/** @type {Error} */ (e).message}`
180
+ );
181
+ }
182
+ } else {
183
+ // adb push creates folders and overwrites existing files.
184
+ await adb.push(localFile, remotePath);
185
+
186
+ // if we have pushed a file, it might be a media file, so ensure that
187
+ // apps know about it
188
+ await scanMedia(adb, remotePath, this.log);
189
+ }
190
+ } finally {
191
+ if (await fs.exists(localFile)) {
192
+ await fs.unlink(localFile);
193
+ }
194
+ if (tmpDestination) {
195
+ await adb.shell(['rm', '-f', tmpDestination]);
196
+ }
203
197
  }
204
- }
205
- };
198
+ },
206
199
 
207
- /**
208
- * @typedef {Object} PushFileOptions
209
- * @property {string} remotePath The full path to the remote file
210
- * or a specially formatted path, which points to an item inside an app bundle,
211
- * for example `@my.app.id/my/path`. It is mandatory for the app bundle to have
212
- * debugging enabled in order to use the latter remotePath format.
213
- * @property {string} payload Base64-encoded content of the file to be pushed.
214
- */
200
+ async mobilePushFile(opts) {
201
+ const {remotePath, payload} = requireArgs(['remotePath', 'payload'], opts);
202
+ return await this.pushFile(remotePath, payload);
203
+ },
215
204
 
216
- /**
217
- * Pushes the given data to a file on the remote device.
218
- *
219
- * @param {PushFileOptions} opts
220
- */
221
- commands.mobilePushFile = async function mobilePushFile (opts = {}) {
222
- const { remotePath, payload } = requireArgs(['remotePath', 'payload'], opts);
223
- return await this.pushFile(remotePath, payload);
224
- };
205
+ async pullFolder(remotePath) {
206
+ const tmpRoot = await tempDir.openDir();
207
+ try {
208
+ await /** @type {ADB} */ (this.adb).pull(remotePath, tmpRoot);
209
+ return (
210
+ await zip.toInMemoryZip(tmpRoot, {
211
+ encodeToBase64: true,
212
+ })
213
+ ).toString();
214
+ } finally {
215
+ await fs.rimraf(tmpRoot);
216
+ }
217
+ },
225
218
 
226
- /**
227
- * Pulls the whole folder from the remote device
228
- *
229
- * @param {string} remotePath The full path to a folder on the
230
- * remote device or a folder inside an application bundle
231
- * @returns {string} Base64-encoded and zipped content of the folder
232
- * @throws {Error} If there was a failure while getting the folder content
233
- */
234
- commands.pullFolder = async function pullFolder (remotePath) {
235
- const tmpRoot = await tempDir.openDir();
236
- try {
237
- await this.adb.pull(remotePath, tmpRoot);
238
- return (await zip.toInMemoryZip(tmpRoot, {
239
- encodeToBase64: true,
240
- })).toString();
241
- } finally {
242
- await fs.rimraf(tmpRoot);
243
- }
219
+ async mobilePullFolder(opts) {
220
+ const {remotePath} = requireArgs('remotePath', opts);
221
+ return await this.pullFolder(remotePath);
222
+ },
223
+
224
+ async mobileDeleteFile(opts) {
225
+ const {remotePath} = requireArgs('remotePath', opts);
226
+ if (remotePath.endsWith('/')) {
227
+ throw new errors.InvalidArgumentError(
228
+ `It is expected that remote path points to a folder and not to a file. ` +
229
+ `'${remotePath}' is given instead`
230
+ );
231
+ }
232
+ return await deleteFileOrFolder.call(this, /** @type {ADB} */ (this.adb), remotePath);
233
+ },
244
234
  };
245
235
 
246
236
  /**
247
- * @typedef {Object} PullFolderOptions
248
- * @property {string} remotePath The full path to the remote folder.
237
+ * Factory providing filesystem test functions using ADB
238
+ * @param {ADB} adb
249
239
  */
240
+ function createFSTests(adb) {
241
+ /**
242
+ *
243
+ * @param {string} p
244
+ * @param {'d'|'f'|'e'} op
245
+ * @param {string} [runAs]
246
+ * @returns
247
+ */
248
+ const performRemoteFsCheck = async (p, op, runAs) => {
249
+ const passFlag = '__PASS__';
250
+ const checkCmd = `[ -${op} '${escapePath(p)}' ] && echo ${passFlag}`;
251
+ const fullCmd = runAs ? `run-as ${runAs} ${checkCmd}` : checkCmd;
252
+ try {
253
+ return _.includes(await adb.shell([fullCmd]), passFlag);
254
+ } catch (ign) {
255
+ return false;
256
+ }
257
+ };
250
258
 
251
- /**
252
- * Pulls the whole folder from the device under test.
253
- *
254
- * @param {PullFolderOptions} opts
255
- * @returns {string} The same as `pullFolder`
256
- */
257
- commands.mobilePullFolder = async function mobilePullFolder (opts = {}) {
258
- const { remotePath } = requireArgs('remotePath', opts);
259
- return await this.pullFolder(remotePath);
260
- };
259
+ /**
260
+ * @param {string} p
261
+ * @param {string} [runAs]
262
+ */
263
+ const isFile = async (p, runAs) => await performRemoteFsCheck(p, 'f', runAs);
264
+ /**
265
+ * @param {string} p
266
+ * @param {string} [runAs]
267
+ */
268
+ const isDir = async (p, runAs) => await performRemoteFsCheck(p, 'd', runAs);
269
+ /**
270
+ * @param {string} p
271
+ * @param {string} [runAs]
272
+ */
273
+ const isPresent = async (p, runAs) => await performRemoteFsCheck(p, 'e', runAs);
274
+
275
+ return {isFile, isDir, isPresent};
276
+ }
261
277
 
262
278
  /**
263
279
  * Deletes the given folder or file from the remote device
@@ -267,27 +283,16 @@ commands.mobilePullFolder = async function mobilePullFolder (opts = {}) {
267
283
  * or file (folder names must end with a single slash)
268
284
  * @throws {Error} If the provided remote path is invalid or
269
285
  * the package content cannot be accessed
270
- * @returns {boolean} `true` if the remote item has been successfully deleted.
286
+ * @returns {Promise<boolean>} `true` if the remote item has been successfully deleted.
271
287
  * If the remote path is valid, but the remote path does not exist
272
288
  * this function return `false`.
289
+ * @this {import('../driver').AndroidDriver}
273
290
  */
274
- async function deleteFileOrFolder (adb, remotePath) {
275
- const performRemoteFsCheck = async (p, op, runAs = null) => {
276
- const passFlag = '__PASS__';
277
- const checkCmd = `[ -${op} '${escapePath(p)}' ] && echo ${passFlag}`;
278
- const fullCmd = runAs ? `run-as ${runAs} ${checkCmd}` : checkCmd;
279
- try {
280
- return _.includes(await adb.shell([fullCmd]), passFlag);
281
- } catch (ign) {
282
- return false;
283
- }
284
- };
285
- const isFile = async (p, runAs = null) => await performRemoteFsCheck(p, 'f', runAs);
286
- const isDir = async (p, runAs = null) => await performRemoteFsCheck(p, 'd', runAs);
287
- const isPresent = async (p, runAs = null) => await performRemoteFsCheck(p, 'e', runAs);
288
-
291
+ async function deleteFileOrFolder(adb, remotePath) {
292
+ const {isDir, isPresent, isFile} = createFSTests(adb);
289
293
  let dstPath = remotePath;
290
- let pkgId = null;
294
+ /** @type {string|undefined} */
295
+ let pkgId;
291
296
  if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
292
297
  const [packageId, pathInContainer] = parseContainerPath(remotePath);
293
298
  this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'`);
@@ -299,66 +304,43 @@ async function deleteFileOrFolder (adb, remotePath) {
299
304
  try {
300
305
  await adb.shell(['run-as', pkgId, 'ls']);
301
306
  } catch (e) {
302
- this.log.errorAndThrow(`Cannot access the container of '${pkgId}' application. ` +
303
- `Is the application installed and has 'debuggable' build option set to true? ` +
304
- `Original error: ${e.message}`);
307
+ this.log.errorAndThrow(
308
+ `Cannot access the container of '${pkgId}' application. ` +
309
+ `Is the application installed and has 'debuggable' build option set to true? ` +
310
+ `Original error: ${/** @type {Error} */ (e).message}`
311
+ );
305
312
  }
306
313
  }
307
314
 
308
- if (!await isPresent(dstPath, pkgId)) {
315
+ if (!(await isPresent(dstPath, pkgId))) {
309
316
  this.log.info(`The item at '${dstPath}' does not exist. Perhaps, already deleted?`);
310
317
  return false;
311
318
  }
312
319
 
313
320
  const expectsFile = !remotePath.endsWith('/');
314
- if (expectsFile && !await isFile(dstPath, pkgId)) {
321
+ if (expectsFile && !(await isFile(dstPath, pkgId))) {
315
322
  this.log.errorAndThrow(`The item at '${dstPath}' is not a file`);
316
- } else if (!expectsFile && !await isDir(dstPath, pkgId)) {
323
+ } else if (!expectsFile && !(await isDir(dstPath, pkgId))) {
317
324
  this.log.errorAndThrow(`The item at '${dstPath}' is not a folder`);
318
325
  }
319
326
 
320
327
  if (pkgId) {
321
- await adb.shell(
322
- ['run-as', pkgId, `rm -f${expectsFile ? '' : 'r'} '${escapePath(dstPath)}'`]);
328
+ await adb.shell(['run-as', pkgId, `rm -f${expectsFile ? '' : 'r'} '${escapePath(dstPath)}'`]);
323
329
  } else {
324
330
  await adb.shell(['rm', `-f${expectsFile ? '' : 'r'}`, dstPath]);
325
331
  }
326
332
  if (await isPresent(dstPath, pkgId)) {
327
- this.log.errorAndThrow(`The item at '${dstPath}' still exists after being deleted. ` +
328
- `Is it writable?`);
333
+ this.log.errorAndThrow(
334
+ `The item at '${dstPath}' still exists after being deleted. ` + `Is it writable?`
335
+ );
329
336
  }
330
337
  return true;
331
338
  }
332
339
 
333
- /**
334
- * @typedef {Object} DeleteFileOpts
335
- * @property {!string} remotePath The full path to the remote file
336
- * or a file inside an application bundle (for example `@my.app.id/path/in/bundle`)
337
- */
340
+ mixin(FileActionsMixin);
338
341
 
339
342
  /**
340
- * Deletes a file on the remote device
341
- *
342
- * @param {DeleteFileOpts} opts
343
- * @returns {boolean} `true` if the remote file has been successfully deleted.
344
- * If the path to a remote file is valid, but the file itself does not exist
345
- * then `false` is returned.
346
- * @throws {Error} If the argument is invalid or there was an error while
347
- * deleting the file
343
+ * @typedef {import('appium-adb').ADB} ADB
348
344
  */
349
- commands.mobileDeleteFile = async function mobileDeleteFile (opts = {}) {
350
- const {remotePath} = opts;
351
- if (!remotePath) {
352
- throw new errors.InvalidArgumentError(`The 'remotePath' argument is mandatory`);
353
- }
354
- if (remotePath.endsWith('/')) {
355
- throw new errors.InvalidArgumentError(
356
- `It is expected that remote path points to a folder and not to a file. ` +
357
- `'${remotePath}' is given instead`
358
- );
359
- }
360
- return await deleteFileOrFolder(this.adb, remotePath);
361
- };
362
345
 
363
- export { commands };
364
- export default commands;
346
+ export default FileActionsMixin;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @privateRemarks This file needed to be converted to TS because the overload of `findElOrEls` is seemingly impossible to express in JS since the value of `this` cannot be bound via a type assertion.
3
+ * @module
4
+ */
5
+
6
+ import _ from 'lodash';
7
+ import {mixin, type FindMixin} from './mixins';
8
+ import {errors, isErrorType} from 'appium/driver';
9
+ import type {AndroidDriver} from '../driver';
10
+ import type {Element} from '@appium/types';
11
+ import type {FindElementOpts} from './types';
12
+ import type AndroidBootstrap from '../bootstrap';
13
+
14
+ async function findElOrEls(
15
+ this: AndroidDriver,
16
+ strategy: string,
17
+ selector: string,
18
+ mult: true,
19
+ context?: string
20
+ ): Promise<Element[]>;
21
+ async function findElOrEls(
22
+ this: AndroidDriver,
23
+ strategy: string,
24
+ selector: string,
25
+ mult: false,
26
+ context?: string
27
+ ): Promise<Element>;
28
+ async function findElOrEls(
29
+ this: AndroidDriver,
30
+ strategy: string,
31
+ selector: string,
32
+ mult: boolean,
33
+ context = ''
34
+ ) {
35
+ if (!selector) {
36
+ throw new Error('Must provide a selector when finding elements');
37
+ }
38
+
39
+ const params: FindElementOpts = {
40
+ strategy,
41
+ selector,
42
+ context,
43
+ multiple: mult,
44
+ };
45
+
46
+ let element: Element | Element[] | undefined;
47
+ const doFind = async () => {
48
+ try {
49
+ element = await this.doFindElementOrEls(params);
50
+ } catch (err) {
51
+ // if the error that comes back is from a proxied request, we need to
52
+ // unwrap it to its actual protocol error first
53
+ if (isErrorType(err, errors.ProxyRequestError)) {
54
+ err = err.getActualError(); // eslint-disable-line no-ex-assign
55
+ }
56
+
57
+ // now we have to inspect the error to determine if it is a no such
58
+ // element error, based on the shape of the error object from
59
+ // appium/driver
60
+ if (isErrorType(err, errors.NoSuchElementError)) {
61
+ // we are fine with this, just indicate a retry
62
+ return false;
63
+ }
64
+ throw err;
65
+ }
66
+
67
+ // we want to return false if we want to potentially try again
68
+ return !_.isEmpty(element);
69
+ };
70
+
71
+ try {
72
+ await this.implicitWaitForCondition(doFind);
73
+ } catch (e) {
74
+ const err = e as Error;
75
+ if (err.message && err.message.match(/Condition unmet/)) {
76
+ // only get here if we are looking for multiple elements
77
+ // condition was not met setting res to empty array
78
+ element = [];
79
+ } else {
80
+ throw err;
81
+ }
82
+ }
83
+
84
+ if (mult) {
85
+ return element as Element[];
86
+ }
87
+ if (_.isEmpty(element)) {
88
+ throw new errors.NoSuchElementError();
89
+ }
90
+ return element as Element;
91
+ }
92
+
93
+ const FindMixin: FindMixin & ThisType<AndroidDriver> = {
94
+ async doFindElementOrEls(params) {
95
+ return await (this.bootstrap as AndroidBootstrap).sendAction('find', params);
96
+ },
97
+
98
+ findElOrEls,
99
+ };
100
+
101
+ mixin(FindMixin);
102
+
103
+ export default FindMixin;