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.
- package/build/index.d.ts +282 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js.map +1 -0
- package/build/lib/commands/actions.d.ts +6 -224
- package/build/lib/commands/actions.d.ts.map +1 -1
- package/build/lib/commands/actions.js +306 -405
- package/build/lib/commands/actions.js.map +1 -1
- package/build/lib/commands/alert.d.ts +7 -9
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js +24 -18
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-management.d.ts +7 -313
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +135 -293
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/context.d.ts +8 -92
- package/build/lib/commands/context.d.ts.map +1 -1
- package/build/lib/commands/context.js +381 -439
- package/build/lib/commands/context.js.map +1 -1
- package/build/lib/commands/element.d.ts +8 -35
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +153 -136
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/emu-console.d.ts +6 -48
- package/build/lib/commands/emu-console.d.ts.map +1 -1
- package/build/lib/commands/emu-console.js +19 -34
- package/build/lib/commands/emu-console.js.map +1 -1
- package/build/lib/commands/execute.d.ts +6 -5
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +77 -66
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-actions.d.ts +7 -128
- package/build/lib/commands/file-actions.d.ts.map +1 -1
- package/build/lib/commands/file-actions.js +183 -219
- package/build/lib/commands/file-actions.js.map +1 -1
- package/build/lib/commands/find.d.ts +8 -12
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +19 -23
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/general.d.ts +9 -132
- package/build/lib/commands/general.d.ts.map +1 -1
- package/build/lib/commands/general.js +281 -312
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/ime.d.ts +7 -10
- package/build/lib/commands/ime.d.ts.map +1 -1
- package/build/lib/commands/ime.js +47 -35
- package/build/lib/commands/ime.js.map +1 -1
- package/build/lib/commands/index.d.ts +27 -2
- package/build/lib/commands/index.d.ts.map +1 -1
- package/build/lib/commands/index.js +41 -19
- package/build/lib/commands/index.js.map +1 -1
- package/build/lib/commands/intent.d.ts +7 -417
- package/build/lib/commands/intent.d.ts.map +1 -1
- package/build/lib/commands/intent.js +104 -216
- package/build/lib/commands/intent.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +6 -5
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +16 -8
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/log.d.ts +7 -44
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +146 -108
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/media-projection.d.ts +7 -143
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js +113 -140
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/mixins.d.ts +740 -0
- package/build/lib/commands/mixins.d.ts.map +1 -0
- package/build/lib/commands/mixins.js +19 -0
- package/build/lib/commands/mixins.js.map +1 -0
- package/build/lib/commands/network.d.ts +7 -138
- package/build/lib/commands/network.d.ts.map +1 -1
- package/build/lib/commands/network.js +212 -254
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/performance.d.ts +24 -70
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +144 -100
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +8 -92
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +75 -87
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +7 -193
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +151 -182
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/shell.d.ts +7 -7
- package/build/lib/commands/shell.d.ts.map +1 -1
- package/build/lib/commands/shell.js +40 -33
- package/build/lib/commands/shell.js.map +1 -1
- package/build/lib/commands/streamscreen.d.ts +9 -103
- package/build/lib/commands/streamscreen.d.ts.map +1 -1
- package/build/lib/commands/streamscreen.js +261 -218
- package/build/lib/commands/streamscreen.js.map +1 -1
- package/build/lib/commands/system-bars.d.ts +22 -90
- package/build/lib/commands/system-bars.d.ts.map +1 -1
- package/build/lib/commands/system-bars.js +76 -74
- package/build/lib/commands/system-bars.js.map +1 -1
- package/build/lib/commands/touch.d.ts +10 -29
- package/build/lib/commands/touch.d.ts.map +1 -1
- package/build/lib/commands/touch.js +301 -285
- package/build/lib/commands/touch.js.map +1 -1
- package/build/lib/commands/types.d.ts +978 -0
- package/build/lib/commands/types.d.ts.map +1 -0
- package/build/lib/commands/types.js +3 -0
- package/build/lib/commands/types.js.map +1 -0
- package/build/lib/constraints.d.ts +291 -0
- package/build/lib/constraints.d.ts.map +1 -0
- package/build/lib/constraints.js +300 -0
- package/build/lib/constraints.js.map +1 -0
- package/build/lib/driver.d.ts +68 -37
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +123 -80
- package/build/lib/driver.js.map +1 -1
- package/build/lib/helpers/android.d.ts +164 -0
- package/build/lib/helpers/android.d.ts.map +1 -0
- package/build/lib/helpers/android.js +819 -0
- package/build/lib/helpers/android.js.map +1 -0
- package/build/lib/helpers/index.d.ts +7 -0
- package/build/lib/helpers/index.d.ts.map +1 -0
- package/build/lib/helpers/index.js +29 -0
- package/build/lib/helpers/index.js.map +1 -0
- package/build/lib/helpers/types.d.ts +121 -0
- package/build/lib/helpers/types.d.ts.map +1 -0
- package/build/lib/helpers/types.js +3 -0
- package/build/lib/helpers/types.js.map +1 -0
- package/build/lib/helpers/unlock.d.ts +32 -0
- package/build/lib/helpers/unlock.d.ts.map +1 -0
- package/build/lib/helpers/unlock.js +273 -0
- package/build/lib/helpers/unlock.js.map +1 -0
- package/build/lib/helpers/webview.d.ts +74 -0
- package/build/lib/helpers/webview.d.ts.map +1 -0
- package/build/lib/helpers/webview.js +421 -0
- package/build/lib/helpers/webview.js.map +1 -0
- package/build/lib/index.d.ts +9 -0
- package/build/lib/index.d.ts.map +1 -0
- package/build/lib/index.js +37 -0
- package/build/lib/index.js.map +1 -0
- package/build/lib/method-map.d.ts +0 -8
- package/build/lib/method-map.d.ts.map +1 -1
- package/build/lib/method-map.js +63 -74
- package/build/lib/method-map.js.map +1 -1
- package/build/lib/stubs.d.ts +0 -1
- package/build/lib/stubs.d.ts.map +1 -1
- package/build/lib/stubs.js +1 -0
- package/build/lib/stubs.js.map +1 -1
- package/build/lib/utils.d.ts +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/lib/commands/actions.js +351 -464
- package/lib/commands/alert.js +27 -17
- package/lib/commands/app-management.js +156 -314
- package/lib/commands/context.js +457 -441
- package/lib/commands/element.js +201 -157
- package/lib/commands/emu-console.js +25 -45
- package/lib/commands/execute.js +106 -90
- package/lib/commands/file-actions.js +222 -240
- package/lib/commands/find.ts +103 -0
- package/lib/commands/general.js +327 -339
- package/lib/commands/ime.js +50 -34
- package/lib/commands/{index.js → index.ts} +20 -24
- package/lib/commands/intent.js +108 -249
- package/lib/commands/keyboard.js +20 -8
- package/lib/commands/log.js +172 -116
- package/lib/commands/media-projection.js +134 -161
- package/lib/commands/mixins.ts +966 -0
- package/lib/commands/network.js +252 -281
- package/lib/commands/performance.js +203 -132
- package/lib/commands/permissions.js +108 -109
- package/lib/commands/recordscreen.js +212 -209
- package/lib/commands/shell.js +51 -40
- package/lib/commands/streamscreen.js +355 -289
- package/lib/commands/system-bars.js +92 -83
- package/lib/commands/touch.js +357 -294
- package/lib/commands/types.ts +1097 -0
- package/lib/{desired-caps.js → constraints.ts} +106 -103
- package/lib/{driver.js → driver.ts} +278 -132
- package/lib/helpers/android.ts +1143 -0
- package/lib/helpers/index.ts +6 -0
- package/lib/helpers/types.ts +134 -0
- package/lib/helpers/unlock.ts +329 -0
- package/lib/helpers/webview.ts +582 -0
- package/lib/index.ts +18 -0
- package/lib/method-map.js +87 -98
- package/lib/stubs.ts +0 -1
- package/package.json +27 -20
- package/index.js +0 -24
- package/lib/android-helpers.js +0 -983
- package/lib/commands/coverage.js +0 -18
- package/lib/commands/find.js +0 -82
- package/lib/unlock-helpers.js +0 -278
- package/lib/webview-helpers.js +0 -602
|
@@ -1,18 +1,17 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
import _ from 'lodash';
|
|
2
|
-
import {
|
|
4
|
+
import {fs, util, zip, tempDir} from '@appium/support';
|
|
3
5
|
import path from 'path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
|
24
|
+
function parseContainerPath(remotePath) {
|
|
26
25
|
const match = CONTAINER_PATH_PATTERN.exec(remotePath);
|
|
27
26
|
if (!match) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`
|
|
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
|
|
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',
|
|
52
|
-
'
|
|
53
|
-
'-
|
|
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
|
-
|
|
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
|
|
78
|
+
function escapePath(p) {
|
|
69
79
|
return p.replace(/'/g, `\\'`);
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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(
|
|
171
|
-
`Will
|
|
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
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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(
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
*
|
|
248
|
-
* @
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
275
|
-
const
|
|
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
|
-
|
|
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(
|
|
303
|
-
`
|
|
304
|
-
|
|
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(
|
|
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
|
-
*
|
|
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
|
|
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;
|