appium-adb 16.0.0 → 16.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/build/lib/adb.d.ts +11 -11
- package/build/lib/adb.d.ts.map +1 -1
- package/build/lib/adb.js +9 -9
- package/build/lib/adb.js.map +1 -1
- package/build/lib/logcat.d.ts +1 -1
- package/build/lib/logcat.d.ts.map +1 -1
- package/build/lib/logcat.js +4 -14
- package/build/lib/logcat.js.map +1 -1
- package/build/lib/tools/aab-utils.d.ts.map +1 -1
- package/build/lib/tools/aab-utils.js +4 -4
- package/build/lib/tools/aab-utils.js.map +1 -1
- package/build/lib/tools/android-manifest.d.ts.map +1 -1
- package/build/lib/tools/android-manifest.js +4 -7
- package/build/lib/tools/android-manifest.js.map +1 -1
- package/build/lib/tools/apk-signing.d.ts.map +1 -1
- package/build/lib/tools/apk-signing.js +9 -11
- package/build/lib/tools/apk-signing.js.map +1 -1
- package/build/lib/tools/apk-utils.d.ts.map +1 -1
- package/build/lib/tools/apk-utils.js +19 -38
- package/build/lib/tools/apk-utils.js.map +1 -1
- package/build/lib/tools/apks-utils.d.ts.map +1 -1
- package/build/lib/tools/apks-utils.js +3 -3
- package/build/lib/tools/apks-utils.js.map +1 -1
- package/build/lib/tools/app-commands.d.ts.map +1 -1
- package/build/lib/tools/app-commands.js +11 -15
- package/build/lib/tools/app-commands.js.map +1 -1
- package/build/lib/tools/device-settings.d.ts.map +1 -1
- package/build/lib/tools/device-settings.js +6 -17
- package/build/lib/tools/device-settings.js.map +1 -1
- package/build/lib/tools/emulator-commands.d.ts.map +1 -1
- package/build/lib/tools/emulator-commands.js +6 -8
- package/build/lib/tools/emulator-commands.js.map +1 -1
- package/build/lib/tools/fs-commands.d.ts +1 -1
- package/build/lib/tools/fs-commands.d.ts.map +1 -1
- package/build/lib/tools/fs-commands.js.map +1 -1
- package/build/lib/tools/general-commands.d.ts.map +1 -1
- package/build/lib/tools/general-commands.js +3 -5
- package/build/lib/tools/general-commands.js.map +1 -1
- package/build/lib/tools/keyboard-commands.d.ts.map +1 -1
- package/build/lib/tools/keyboard-commands.js +1 -1
- package/build/lib/tools/keyboard-commands.js.map +1 -1
- package/build/lib/tools/lockmgmt.d.ts.map +1 -1
- package/build/lib/tools/lockmgmt.js +4 -10
- package/build/lib/tools/lockmgmt.js.map +1 -1
- package/build/lib/tools/logcat-commands.d.ts.map +1 -1
- package/build/lib/tools/logcat-commands.js.map +1 -1
- package/build/lib/tools/network-commands.d.ts.map +1 -1
- package/build/lib/tools/network-commands.js.map +1 -1
- package/build/lib/tools/process-commands.d.ts.map +1 -1
- package/build/lib/tools/process-commands.js +1 -1
- package/build/lib/tools/process-commands.js.map +1 -1
- package/build/lib/tools/system-calls.d.ts.map +1 -1
- package/build/lib/tools/system-calls.js +15 -11
- package/build/lib/tools/system-calls.js.map +1 -1
- package/build/lib/tools/types.d.ts.map +1 -1
- package/build/lib/utils/helpers/index.d.ts +1 -1
- package/build/lib/utils/helpers/index.d.ts.map +1 -1
- package/build/lib/utils/helpers/index.js +1 -1
- package/build/lib/utils/helpers/index.js.map +1 -1
- package/build/lib/utils/helpers/install.d.ts.map +1 -1
- package/build/lib/utils/helpers/install.js.map +1 -1
- package/build/lib/utils/helpers/manifest.d.ts.map +1 -1
- package/build/lib/utils/helpers/manifest.js.map +1 -1
- package/build/lib/utils/helpers/resource.d.ts.map +1 -1
- package/build/lib/utils/helpers/resource.js +1 -2
- package/build/lib/utils/helpers/resource.js.map +1 -1
- package/build/lib/utils/helpers/sdk.d.ts.map +1 -1
- package/build/lib/utils/helpers/sdk.js +1 -1
- package/build/lib/utils/helpers/sdk.js.map +1 -1
- package/build/lib/utils/index.d.ts +1 -1
- package/build/lib/utils/index.d.ts.map +1 -1
- package/build/lib/utils/index.js +1 -1
- package/build/lib/utils/index.js.map +1 -1
- package/build/lib/utils/lodash/core.d.ts.map +1 -1
- package/build/lib/utils/lodash/core.js.map +1 -1
- package/lib/adb.ts +14 -13
- package/lib/logcat.ts +9 -23
- package/lib/tools/aab-utils.ts +9 -15
- package/lib/tools/android-manifest.ts +12 -28
- package/lib/tools/apk-signing.ts +16 -41
- package/lib/tools/apk-utils.ts +27 -82
- package/lib/tools/apks-utils.ts +10 -21
- package/lib/tools/app-commands.ts +31 -95
- package/lib/tools/device-settings.ts +15 -48
- package/lib/tools/emulator-commands.ts +13 -38
- package/lib/tools/fs-commands.ts +3 -1
- package/lib/tools/general-commands.ts +6 -13
- package/lib/tools/keyboard-commands.ts +2 -1
- package/lib/tools/lockmgmt.ts +12 -42
- package/lib/tools/logcat-commands.ts +1 -1
- package/lib/tools/network-commands.ts +4 -11
- package/lib/tools/process-commands.ts +4 -11
- package/lib/tools/system-calls.ts +59 -137
- package/lib/tools/types.ts +4 -8
- package/lib/utils/helpers/index.ts +1 -6
- package/lib/utils/helpers/install.ts +2 -4
- package/lib/utils/helpers/manifest.ts +4 -11
- package/lib/utils/helpers/resource.ts +5 -9
- package/lib/utils/helpers/sdk.ts +5 -9
- package/lib/utils/index.ts +2 -1
- package/lib/utils/lodash/core.ts +3 -9
- package/package.json +36 -50
package/lib/tools/aab-utils.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
|
|
3
4
|
import {fs, tempDir, util} from '@appium/support';
|
|
4
|
-
import {LRUCache} from 'lru-cache';
|
|
5
5
|
import AsyncLock from 'async-lock';
|
|
6
|
-
import
|
|
6
|
+
import {LRUCache} from 'lru-cache';
|
|
7
|
+
|
|
7
8
|
import type {ADB} from '../adb.js';
|
|
8
|
-
import
|
|
9
|
+
import {log} from '../logger.js';
|
|
9
10
|
import {unzipFile} from '../utils/index.js';
|
|
11
|
+
import type {ApkCreationOptions, StringRecord} from './types.js';
|
|
10
12
|
|
|
11
13
|
const AAB_CACHE = new LRUCache<string, string>({
|
|
12
14
|
max: 10,
|
|
@@ -21,9 +23,7 @@ process.on('exit', () => {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const paths = [...AAB_CACHE.values()];
|
|
24
|
-
log.debug(
|
|
25
|
-
`Performing cleanup of ${paths.length} cached .aab ` + util.pluralize('package', paths.length),
|
|
26
|
-
);
|
|
26
|
+
log.debug(`Performing cleanup of ${paths.length} cached .aab ` + util.pluralize('package', paths.length));
|
|
27
27
|
for (const appPath of paths) {
|
|
28
28
|
try {
|
|
29
29
|
// Asynchronous calls are not supported in onExit handler
|
|
@@ -45,11 +45,7 @@ process.on('exit', () => {
|
|
|
45
45
|
* by default.
|
|
46
46
|
* @throws {Error} If there was an error while creating the universal .apk
|
|
47
47
|
*/
|
|
48
|
-
export async function extractUniversalApk(
|
|
49
|
-
this: ADB,
|
|
50
|
-
aabPath: string,
|
|
51
|
-
opts: ApkCreationOptions = {},
|
|
52
|
-
): Promise<string> {
|
|
48
|
+
export async function extractUniversalApk(this: ADB, aabPath: string, opts: ApkCreationOptions = {}): Promise<string> {
|
|
53
49
|
if (!(await fs.exists(aabPath))) {
|
|
54
50
|
throw new Error(`The file at '${aabPath}' either does not exist or is not accessible`);
|
|
55
51
|
}
|
|
@@ -65,9 +61,7 @@ export async function extractUniversalApk(
|
|
|
65
61
|
let cacheHash = aabHash;
|
|
66
62
|
if (keystore) {
|
|
67
63
|
if (!(await fs.exists(keystore))) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`The keystore file at '${keystore}' either does not exist ` + `or is not accessible`,
|
|
70
|
-
);
|
|
64
|
+
throw new Error(`The keystore file at '${keystore}' either does not exist or is not accessible`);
|
|
71
65
|
}
|
|
72
66
|
if (!keystorePassword || !keyAlias || !keyPassword) {
|
|
73
67
|
throw new Error(
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {exec} from 'teen_process';
|
|
2
|
-
import {log} from '../logger.js';
|
|
3
|
-
import {fs, zip, tempDir, util} from '@appium/support';
|
|
4
1
|
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import {fs, zip, tempDir, util} from '@appium/support';
|
|
4
|
+
import {exec} from 'teen_process';
|
|
5
|
+
|
|
5
6
|
import type {ADB} from '../adb.js';
|
|
6
|
-
import
|
|
7
|
+
import {log} from '../logger.js';
|
|
7
8
|
import {APKS_EXTENSION, readPackageManifest, unzipFile} from '../utils/index.js';
|
|
9
|
+
import type {APKInfo, PlatformInfo, StringRecord} from './types.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Extract package and main activity name from application manifest.
|
|
@@ -14,10 +16,7 @@ import {APKS_EXTENSION, readPackageManifest, unzipFile} from '../utils/index.js'
|
|
|
14
16
|
* @throws {error} If there was an error while getting the data from the given
|
|
15
17
|
* application package.
|
|
16
18
|
*/
|
|
17
|
-
export async function packageAndLaunchActivityFromManifest(
|
|
18
|
-
this: ADB,
|
|
19
|
-
appPath: string,
|
|
20
|
-
): Promise<APKInfo> {
|
|
19
|
+
export async function packageAndLaunchActivityFromManifest(this: ADB, appPath: string): Promise<APKInfo> {
|
|
21
20
|
if (appPath.endsWith(APKS_EXTENSION)) {
|
|
22
21
|
appPath = await this.extractBaseApk(appPath);
|
|
23
22
|
}
|
|
@@ -48,10 +47,7 @@ export async function targetSdkVersionFromManifest(this: ADB, appPath: string):
|
|
|
48
47
|
|
|
49
48
|
const {targetSdkVersion} = await readPackageManifest.bind(this)(appPath);
|
|
50
49
|
if (!targetSdkVersion) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`Cannot extract targetSdkVersion of '${originalAppPath}'. Does ` +
|
|
53
|
-
`the package manifest define it?`,
|
|
54
|
-
);
|
|
50
|
+
throw new Error(`Cannot extract targetSdkVersion of '${originalAppPath}'. Does the package manifest define it?`);
|
|
55
51
|
}
|
|
56
52
|
return targetSdkVersion;
|
|
57
53
|
}
|
|
@@ -71,9 +67,7 @@ export async function targetSdkVersionUsingPKG(
|
|
|
71
67
|
): Promise<number> {
|
|
72
68
|
const stdout = cmdOutput || (await this.shell(['dumpsys', 'package', pkg]));
|
|
73
69
|
const targetSdkVersionMatch = new RegExp(/targetSdk=([^\s\s]+)/g).exec(stdout);
|
|
74
|
-
return targetSdkVersionMatch && targetSdkVersionMatch.length >= 2
|
|
75
|
-
? parseInt(targetSdkVersionMatch[1], 10)
|
|
76
|
-
: 0;
|
|
70
|
+
return targetSdkVersionMatch && targetSdkVersionMatch.length >= 2 ? parseInt(targetSdkVersionMatch[1], 10) : 0;
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
/**
|
|
@@ -93,9 +87,7 @@ export async function compileManifest(
|
|
|
93
87
|
): Promise<void> {
|
|
94
88
|
const {platform, platformPath} = await getAndroidPlatformAndPath(this.sdkRoot as string);
|
|
95
89
|
if (!platform || !platformPath) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
'Cannot compile the manifest. The required platform does not exist (API level >= 17)',
|
|
98
|
-
);
|
|
90
|
+
throw new Error('Cannot compile the manifest. The required platform does not exist (API level >= 17)');
|
|
99
91
|
}
|
|
100
92
|
const resultPath = `${manifest}.apk`;
|
|
101
93
|
const androidJarPath = path.resolve(platformPath, 'android.jar');
|
|
@@ -169,12 +161,7 @@ export async function compileManifest(
|
|
|
169
161
|
* @param dstApk - Full path to the resulting package.
|
|
170
162
|
* The file will be overridden if it already exists.
|
|
171
163
|
*/
|
|
172
|
-
export async function insertManifest(
|
|
173
|
-
this: ADB,
|
|
174
|
-
manifest: string,
|
|
175
|
-
srcApk: string,
|
|
176
|
-
dstApk: string,
|
|
177
|
-
): Promise<void> {
|
|
164
|
+
export async function insertManifest(this: ADB, manifest: string, srcApk: string, dstApk: string): Promise<void> {
|
|
178
165
|
log.debug(`Inserting manifest '${manifest}', src: '${srcApk}', dst: '${dstApk}'`);
|
|
179
166
|
await zip.assertValidZip(srcApk);
|
|
180
167
|
await unzipFile(`${manifest}.apk`);
|
|
@@ -221,10 +208,7 @@ export async function insertManifest(
|
|
|
221
208
|
* @param appPath - The full path to .apk(s) package.
|
|
222
209
|
* @return True if the manifest requires Internet access permission.
|
|
223
210
|
*/
|
|
224
|
-
export async function hasInternetPermissionFromManifest(
|
|
225
|
-
this: ADB,
|
|
226
|
-
appPath: string,
|
|
227
|
-
): Promise<boolean> {
|
|
211
|
+
export async function hasInternetPermissionFromManifest(this: ADB, appPath: string): Promise<boolean> {
|
|
228
212
|
log.debug(`Checking if '${appPath}' requires internet access permission in the manifest`);
|
|
229
213
|
if (appPath.endsWith(APKS_EXTENSION)) {
|
|
230
214
|
appPath = await this.extractBaseApk(appPath);
|
package/lib/tools/apk-signing.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import _fs from 'node:fs';
|
|
2
|
-
import {exec, type ExecError} from 'teen_process';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
|
|
3
|
+
|
|
5
4
|
import {tempDir, system, mkdirp, fs, util, zip} from '@appium/support';
|
|
6
5
|
import {LRUCache} from 'lru-cache';
|
|
6
|
+
import {exec, type ExecError} from 'teen_process';
|
|
7
|
+
|
|
7
8
|
import type {ADB} from '../adb.js';
|
|
8
|
-
import
|
|
9
|
+
import {log} from '../logger.js';
|
|
9
10
|
import {APKS_EXTENSION, getJavaForOs, getJavaHome, getResourcePath} from '../utils/index.js';
|
|
11
|
+
import type {StringRecord, SignedAppCacheValue, CertCheckOptions, KeystoreHash} from './types.js';
|
|
10
12
|
|
|
11
13
|
const DEFAULT_PRIVATE_KEY = path.join('keys', 'testkey.pk8');
|
|
12
14
|
const DEFAULT_CERTIFICATE = path.join('keys', 'testkey.x509.pem');
|
|
@@ -134,11 +136,7 @@ export async function signWithCustomCert(this: ADB, apk: string): Promise<void>
|
|
|
134
136
|
} else {
|
|
135
137
|
log.debug(`'${apk}' does not need to be unsigned`);
|
|
136
138
|
}
|
|
137
|
-
const jarsigner = path.resolve(
|
|
138
|
-
await getJavaHome(),
|
|
139
|
-
'bin',
|
|
140
|
-
`jarsigner${system.isWindows() ? '.exe' : ''}`,
|
|
141
|
-
);
|
|
139
|
+
const jarsigner = path.resolve(await getJavaHome(), 'bin', `jarsigner${system.isWindows() ? '.exe' : ''}`);
|
|
142
140
|
const fullCmd: string[] = [
|
|
143
141
|
jarsigner,
|
|
144
142
|
'-sigalg',
|
|
@@ -158,11 +156,9 @@ export async function signWithCustomCert(this: ADB, apk: string): Promise<void>
|
|
|
158
156
|
await exec(fullCmd[0], fullCmd.slice(1));
|
|
159
157
|
} catch (e) {
|
|
160
158
|
const execErr = e as ExecError;
|
|
161
|
-
throw new Error(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{cause: e},
|
|
165
|
-
);
|
|
159
|
+
throw new Error(`Could not sign with custom certificate. Original error: ${execErr.stderr || execErr.message}`, {
|
|
160
|
+
cause: e,
|
|
161
|
+
});
|
|
166
162
|
}
|
|
167
163
|
}
|
|
168
164
|
}
|
|
@@ -239,10 +235,7 @@ export async function zipAlignApk(this: ADB, apk: string): Promise<boolean> {
|
|
|
239
235
|
if (await fs.exists(alignedApk)) {
|
|
240
236
|
await fs.unlink(alignedApk);
|
|
241
237
|
}
|
|
242
|
-
throw new Error(
|
|
243
|
-
`zipAlignApk failed. Original error: ${err.message || (err as ExecError).stderr}`,
|
|
244
|
-
{cause: e},
|
|
245
|
-
);
|
|
238
|
+
throw new Error(`zipAlignApk failed. Original error: ${err.message || (err as ExecError).stderr}`, {cause: e});
|
|
246
239
|
}
|
|
247
240
|
}
|
|
248
241
|
|
|
@@ -253,11 +246,7 @@ export async function zipAlignApk(this: ADB, apk: string): Promise<boolean> {
|
|
|
253
246
|
* @param opts - Certificate checking options
|
|
254
247
|
* @returns True if given application is already signed.
|
|
255
248
|
*/
|
|
256
|
-
export async function checkApkCert(
|
|
257
|
-
this: ADB,
|
|
258
|
-
appPath: string,
|
|
259
|
-
opts: CertCheckOptions = {},
|
|
260
|
-
): Promise<boolean> {
|
|
249
|
+
export async function checkApkCert(this: ADB, appPath: string, opts: CertCheckOptions = {}): Promise<boolean> {
|
|
261
250
|
log.debug(`Checking app cert for ${appPath}`);
|
|
262
251
|
if (!(await fs.exists(appPath))) {
|
|
263
252
|
log.debug(`'${appPath}' does not exist`);
|
|
@@ -299,15 +288,9 @@ export async function checkApkCert(
|
|
|
299
288
|
const output = await this.executeApksigner(['verify', '--print-certs', actualAppPath]);
|
|
300
289
|
const hasMatch = hashMatches(output, expected);
|
|
301
290
|
if (hasMatch) {
|
|
302
|
-
log.info(
|
|
303
|
-
`'${actualAppPath}' is signed with the ` +
|
|
304
|
-
`${this.useKeystore ? 'keystore' : 'default'} certificate`,
|
|
305
|
-
);
|
|
291
|
+
log.info(`'${actualAppPath}' is signed with the ${this.useKeystore ? 'keystore' : 'default'} certificate`);
|
|
306
292
|
} else {
|
|
307
|
-
log.info(
|
|
308
|
-
`'${actualAppPath}' is signed with a ` +
|
|
309
|
-
`non-${this.useKeystore ? 'keystore' : 'default'} certificate`,
|
|
310
|
-
);
|
|
293
|
+
log.info(`'${actualAppPath}' is signed with a non-${this.useKeystore ? 'keystore' : 'default'} certificate`);
|
|
311
294
|
}
|
|
312
295
|
const isSigned = (!this.useKeystore && !requireDefaultCert) || hasMatch;
|
|
313
296
|
if (isSigned) {
|
|
@@ -339,10 +322,7 @@ export async function checkApkCert(
|
|
|
339
322
|
log.warn(`Assuming '${actualAppPath}' is already signed and continuing anyway`);
|
|
340
323
|
return true;
|
|
341
324
|
}
|
|
342
|
-
throw new Error(
|
|
343
|
-
`Cannot verify the signature of '${actualAppPath}'. ` + `Original error: ${errMsg}`,
|
|
344
|
-
{cause: err},
|
|
345
|
-
);
|
|
325
|
+
throw new Error(`Cannot verify the signature of '${actualAppPath}'. Original error: ${errMsg}`, {cause: err});
|
|
346
326
|
}
|
|
347
327
|
}
|
|
348
328
|
|
|
@@ -354,11 +334,7 @@ export async function checkApkCert(
|
|
|
354
334
|
*/
|
|
355
335
|
export async function getKeystoreHash(this: ADB): Promise<KeystoreHash> {
|
|
356
336
|
log.debug(`Getting hash of the '${this.keystorePath}' keystore`);
|
|
357
|
-
const keytool = path.resolve(
|
|
358
|
-
await getJavaHome(),
|
|
359
|
-
'bin',
|
|
360
|
-
`keytool${system.isWindows() ? '.exe' : ''}`,
|
|
361
|
-
);
|
|
337
|
+
const keytool = path.resolve(await getJavaHome(), 'bin', `keytool${system.isWindows() ? '.exe' : ''}`);
|
|
362
338
|
if (!(await fs.exists(keytool))) {
|
|
363
339
|
throw new Error(`The keytool utility cannot be found at '${keytool}'`);
|
|
364
340
|
}
|
|
@@ -394,8 +370,7 @@ export async function getKeystoreHash(this: ADB): Promise<KeystoreHash> {
|
|
|
394
370
|
} catch (e) {
|
|
395
371
|
const err = e as ExecError;
|
|
396
372
|
throw new Error(
|
|
397
|
-
`Cannot get the hash of '${this.keystorePath}' keystore.
|
|
398
|
-
`Original error: ${err.stderr || err.message}`,
|
|
373
|
+
`Cannot get the hash of '${this.keystorePath}' keystore. Original error: ${err.stderr || err.message}`,
|
|
399
374
|
{cause: e},
|
|
400
375
|
);
|
|
401
376
|
}
|
package/lib/tools/apk-utils.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {log} from '../logger.js';
|
|
1
|
+
import os from 'node:os';
|
|
3
2
|
import path from 'node:path';
|
|
3
|
+
|
|
4
4
|
import {fs, util, mkdirp, timing} from '@appium/support';
|
|
5
|
-
import * as semver from 'semver';
|
|
6
|
-
import os from 'node:os';
|
|
7
5
|
import {LRUCache} from 'lru-cache';
|
|
6
|
+
import * as semver from 'semver';
|
|
7
|
+
import {exec, type ExecError} from 'teen_process';
|
|
8
|
+
|
|
8
9
|
import type {ADB} from '../adb.js';
|
|
10
|
+
import {log} from '../logger.js';
|
|
9
11
|
import {
|
|
10
12
|
APKS_EXTENSION,
|
|
11
13
|
APK_INSTALL_TIMEOUT,
|
|
@@ -38,11 +40,7 @@ export const REMOTE_CACHE_ROOT = '/data/local/tmp/appium_cache';
|
|
|
38
40
|
* @returns True if the package was found on the device and
|
|
39
41
|
* successfully uninstalled.
|
|
40
42
|
*/
|
|
41
|
-
export async function uninstallApk(
|
|
42
|
-
this: ADB,
|
|
43
|
-
pkg: string,
|
|
44
|
-
options: UninstallOptions = {},
|
|
45
|
-
): Promise<boolean> {
|
|
43
|
+
export async function uninstallApk(this: ADB, pkg: string, options: UninstallOptions = {}): Promise<boolean> {
|
|
46
44
|
log.debug(`Uninstalling ${pkg}`);
|
|
47
45
|
if (!options.skipInstallCheck && !(await this.isAppInstalled(pkg))) {
|
|
48
46
|
log.info(`${pkg} was not uninstalled, because it was not present on the device`);
|
|
@@ -98,11 +96,7 @@ export async function installFromDevicePath(
|
|
|
98
96
|
* @returns Full path to the cached apk on the remote file system
|
|
99
97
|
* @throws if there was a failure while caching the app
|
|
100
98
|
*/
|
|
101
|
-
export async function cacheApk(
|
|
102
|
-
this: ADB,
|
|
103
|
-
apkPath: string,
|
|
104
|
-
options: CachingOptions = {},
|
|
105
|
-
): Promise<string> {
|
|
99
|
+
export async function cacheApk(this: ADB, apkPath: string, options: CachingOptions = {}): Promise<string> {
|
|
106
100
|
const appHash = await fs.hash(apkPath);
|
|
107
101
|
const remotePath = path.posix.join(REMOTE_CACHE_ROOT, `${appHash}.apk`);
|
|
108
102
|
const remoteCachedFiles: string[] = [];
|
|
@@ -110,21 +104,12 @@ export async function cacheApk(
|
|
|
110
104
|
try {
|
|
111
105
|
const errorMarker = '_ERROR_';
|
|
112
106
|
let lsOutput: string | null = null;
|
|
113
|
-
if (
|
|
114
|
-
this._areExtendedLsOptionsSupported === true ||
|
|
115
|
-
typeof this._areExtendedLsOptionsSupported !== 'boolean'
|
|
116
|
-
) {
|
|
107
|
+
if (this._areExtendedLsOptionsSupported === true || typeof this._areExtendedLsOptionsSupported !== 'boolean') {
|
|
117
108
|
lsOutput = await this.shell([`ls -t -1 ${REMOTE_CACHE_ROOT} 2>&1 || echo ${errorMarker}`]);
|
|
118
109
|
}
|
|
119
|
-
if (
|
|
120
|
-
typeof lsOutput !== 'string' ||
|
|
121
|
-
(lsOutput.includes(errorMarker) && !lsOutput.includes(REMOTE_CACHE_ROOT))
|
|
122
|
-
) {
|
|
110
|
+
if (typeof lsOutput !== 'string' || (lsOutput.includes(errorMarker) && !lsOutput.includes(REMOTE_CACHE_ROOT))) {
|
|
123
111
|
if (typeof this._areExtendedLsOptionsSupported !== 'boolean') {
|
|
124
|
-
log.debug(
|
|
125
|
-
'The current Android API does not support extended ls options. ' +
|
|
126
|
-
'Defaulting to no-options call',
|
|
127
|
-
);
|
|
112
|
+
log.debug('The current Android API does not support extended ls options. Defaulting to no-options call');
|
|
128
113
|
}
|
|
129
114
|
lsOutput = await this.shell([`ls ${REMOTE_CACHE_ROOT} 2>&1 || echo ${errorMarker}`]);
|
|
130
115
|
this._areExtendedLsOptionsSupported = false;
|
|
@@ -206,11 +191,7 @@ export async function cacheApk(
|
|
|
206
191
|
* @param options - The set of installation options.
|
|
207
192
|
* @throws If an unexpected error happens during install.
|
|
208
193
|
*/
|
|
209
|
-
export async function install(
|
|
210
|
-
this: ADB,
|
|
211
|
-
appPath: string,
|
|
212
|
-
options: InstallOptions = {},
|
|
213
|
-
): Promise<void> {
|
|
194
|
+
export async function install(this: ADB, appPath: string, options: InstallOptions = {}): Promise<void> {
|
|
214
195
|
if (appPath.endsWith(APKS_EXTENSION)) {
|
|
215
196
|
return await this.installApks(appPath, options);
|
|
216
197
|
}
|
|
@@ -218,8 +199,7 @@ export async function install(
|
|
|
218
199
|
options = cloneDeep(options);
|
|
219
200
|
defaults(options, {
|
|
220
201
|
replace: true,
|
|
221
|
-
timeout:
|
|
222
|
-
this.adbExecTimeout === DEFAULT_ADB_EXEC_TIMEOUT ? APK_INSTALL_TIMEOUT : this.adbExecTimeout,
|
|
202
|
+
timeout: this.adbExecTimeout === DEFAULT_ADB_EXEC_TIMEOUT ? APK_INSTALL_TIMEOUT : this.adbExecTimeout,
|
|
223
203
|
timeoutCapName: 'androidInstallTimeout',
|
|
224
204
|
});
|
|
225
205
|
|
|
@@ -237,9 +217,7 @@ export async function install(
|
|
|
237
217
|
try {
|
|
238
218
|
const timer = new timing.Timer().start();
|
|
239
219
|
const output = await this.adbExec(installCmd, installOpts);
|
|
240
|
-
log.info(
|
|
241
|
-
`The installation of '${path.basename(appPath)}' took ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
|
|
242
|
-
);
|
|
220
|
+
log.info(`The installation of '${path.basename(appPath)}' took ${timer.getDuration().asMilliSeconds.toFixed(0)}ms`);
|
|
243
221
|
log.debug(`Install command stdout: ${util.truncateString(output, {length: 300})}`);
|
|
244
222
|
if (/\[INSTALL[A-Z_]+FAILED[A-Z_]+\]/.test(output)) {
|
|
245
223
|
if (this.isTestPackageOnlyError(output)) {
|
|
@@ -283,11 +261,7 @@ export async function getApplicationInstallState(
|
|
|
283
261
|
return this.APP_INSTALL_STATE.UNKNOWN;
|
|
284
262
|
}
|
|
285
263
|
|
|
286
|
-
const {
|
|
287
|
-
versionCode: pkgVersionCode,
|
|
288
|
-
versionName: pkgVersionNameStr,
|
|
289
|
-
isInstalled,
|
|
290
|
-
} = await this.getPackageInfo(pkg);
|
|
264
|
+
const {versionCode: pkgVersionCode, versionName: pkgVersionNameStr, isInstalled} = await this.getPackageInfo(pkg);
|
|
291
265
|
if (!isInstalled) {
|
|
292
266
|
log.debug(`App '${appPath}' is not installed`);
|
|
293
267
|
return this.APP_INSTALL_STATE.NOT_INSTALLED;
|
|
@@ -375,10 +349,7 @@ export async function installOrUpgrade(
|
|
|
375
349
|
if ('name' in apkInfo) {
|
|
376
350
|
pkg = apkInfo.name;
|
|
377
351
|
} else {
|
|
378
|
-
log.warn(
|
|
379
|
-
`Cannot determine the package name of '${appPath}'. ` +
|
|
380
|
-
`Continuing with the install anyway`,
|
|
381
|
-
);
|
|
352
|
+
log.warn(`Cannot determine the package name of '${appPath}'. Continuing with the install anyway`);
|
|
382
353
|
}
|
|
383
354
|
}
|
|
384
355
|
|
|
@@ -431,9 +402,7 @@ export async function installOrUpgrade(
|
|
|
431
402
|
await this.install(appPath, {...options, replace: true});
|
|
432
403
|
} catch (err) {
|
|
433
404
|
const error = err as Error;
|
|
434
|
-
log.warn(
|
|
435
|
-
`Cannot install/upgrade '${pkg}' because of '${error.message}'. Trying full reinstall`,
|
|
436
|
-
);
|
|
405
|
+
log.warn(`Cannot install/upgrade '${pkg}' because of '${error.message}'. Trying full reinstall`);
|
|
437
406
|
await uninstallPackage();
|
|
438
407
|
await this.install(appPath, {...options, replace: false});
|
|
439
408
|
}
|
|
@@ -472,11 +441,7 @@ export async function extractStringsFromApk(
|
|
|
472
441
|
|
|
473
442
|
configMarker = await formatConfigMarker(
|
|
474
443
|
async () => {
|
|
475
|
-
const {stdout} = await exec((this.binaries as StringRecord).aapt as string, [
|
|
476
|
-
'd',
|
|
477
|
-
'configurations',
|
|
478
|
-
appPath,
|
|
479
|
-
]);
|
|
444
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt as string, ['d', 'configurations', appPath]);
|
|
480
445
|
return util.uniq(stdout.split(os.EOL));
|
|
481
446
|
},
|
|
482
447
|
language,
|
|
@@ -492,20 +457,13 @@ export async function extractStringsFromApk(
|
|
|
492
457
|
apkStrings = parseAaptStrings(stdout, configMarker);
|
|
493
458
|
} catch (e) {
|
|
494
459
|
const err = e as ExecError;
|
|
495
|
-
log.debug(
|
|
496
|
-
'Cannot extract resources using aapt. Trying aapt2. ' +
|
|
497
|
-
`Original error: ${err.stderr || err.message}`,
|
|
498
|
-
);
|
|
460
|
+
log.debug(`Cannot extract resources using aapt. Trying aapt2. Original error: ${err.stderr || err.message}`);
|
|
499
461
|
|
|
500
462
|
await this.initAapt2();
|
|
501
463
|
|
|
502
464
|
configMarker = await formatConfigMarker(
|
|
503
465
|
async () => {
|
|
504
|
-
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, [
|
|
505
|
-
'd',
|
|
506
|
-
'configurations',
|
|
507
|
-
appPath,
|
|
508
|
-
]);
|
|
466
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, ['d', 'configurations', appPath]);
|
|
509
467
|
return util.uniq(stdout.split(os.EOL));
|
|
510
468
|
},
|
|
511
469
|
language,
|
|
@@ -513,18 +471,13 @@ export async function extractStringsFromApk(
|
|
|
513
471
|
);
|
|
514
472
|
|
|
515
473
|
try {
|
|
516
|
-
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, [
|
|
517
|
-
'd',
|
|
518
|
-
'resources',
|
|
519
|
-
appPath,
|
|
520
|
-
]);
|
|
474
|
+
const {stdout} = await exec((this.binaries as StringRecord).aapt2 as string, ['d', 'resources', appPath]);
|
|
521
475
|
apkStrings = parseAapt2Strings(stdout, configMarker);
|
|
522
476
|
} catch (e) {
|
|
523
477
|
const error = e as Error;
|
|
524
|
-
throw new Error(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
);
|
|
478
|
+
throw new Error(`Cannot extract resources from '${originalAppPath}'. Original error: ${error.message}`, {
|
|
479
|
+
cause: e,
|
|
480
|
+
});
|
|
528
481
|
}
|
|
529
482
|
}
|
|
530
483
|
|
|
@@ -673,9 +626,7 @@ export function parseAapt2Strings(rawOutput: string, configMarker: string): Stri
|
|
|
673
626
|
lineIndex = idx;
|
|
674
627
|
if (typeof content === 'string') {
|
|
675
628
|
apkStrings[currentResourceId] = [
|
|
676
|
-
...(Array.isArray(apkStrings[currentResourceId])
|
|
677
|
-
? apkStrings[currentResourceId]
|
|
678
|
-
: []),
|
|
629
|
+
...(Array.isArray(apkStrings[currentResourceId]) ? apkStrings[currentResourceId] : []),
|
|
679
630
|
content,
|
|
680
631
|
];
|
|
681
632
|
}
|
|
@@ -796,14 +747,8 @@ async function formatConfigMarker(
|
|
|
796
747
|
const configs = await configsGetter();
|
|
797
748
|
log.debug(`Resource configurations: ${JSON.stringify(configs)}`);
|
|
798
749
|
// Assume the 'en' configuration is the default one
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
!configs.some((x) => x.trim() === configMarker)
|
|
802
|
-
) {
|
|
803
|
-
log.debug(
|
|
804
|
-
`Resource configuration name '${configMarker}' is unknown. ` +
|
|
805
|
-
`Replacing it with '${defaultMarker}'`,
|
|
806
|
-
);
|
|
750
|
+
if (configMarker.toLowerCase().startsWith('en') && !configs.some((x) => x.trim() === configMarker)) {
|
|
751
|
+
log.debug(`Resource configuration name '${configMarker}' is unknown. Replacing it with '${defaultMarker}'`);
|
|
807
752
|
configMarker = defaultMarker;
|
|
808
753
|
} else {
|
|
809
754
|
log.debug(`Selected configuration: '${configMarker}'`);
|
package/lib/tools/apks-utils.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {exec} from 'teen_process';
|
|
2
|
-
import {log} from '../logger.js';
|
|
3
1
|
import path from 'node:path';
|
|
2
|
+
|
|
4
3
|
import {fs, tempDir, util} from '@appium/support';
|
|
5
|
-
import {LRUCache} from 'lru-cache';
|
|
6
4
|
import AsyncLock from 'async-lock';
|
|
5
|
+
import {LRUCache} from 'lru-cache';
|
|
6
|
+
import {exec} from 'teen_process';
|
|
7
|
+
|
|
7
8
|
import type {ADB} from '../adb.js';
|
|
8
|
-
import
|
|
9
|
+
import {log} from '../logger.js';
|
|
9
10
|
import {APK_INSTALL_TIMEOUT, buildInstallArgs, getJavaForOs, unzipFile} from '../utils/index.js';
|
|
11
|
+
import type {InstallMultipleApksOptions, InstallApksOptions, StringRecord} from './types.js';
|
|
10
12
|
|
|
11
13
|
const BASE_APK = 'base-master.apk';
|
|
12
14
|
const LANGUAGE_APK = (lang: string) => `base-${lang}.apk`;
|
|
@@ -24,9 +26,7 @@ process.on('exit', () => {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const paths = [...APKS_CACHE.values()];
|
|
27
|
-
log.debug(
|
|
28
|
-
`Performing cleanup of ${paths.length} cached .apks ` + util.pluralize('package', paths.length),
|
|
29
|
-
);
|
|
29
|
+
log.debug(`Performing cleanup of ${paths.length} cached .apks ` + util.pluralize('package', paths.length));
|
|
30
30
|
for (const appPath of paths) {
|
|
31
31
|
try {
|
|
32
32
|
// Asynchronous calls are not supported in onExit handler
|
|
@@ -125,11 +125,7 @@ export async function installMultipleApks(
|
|
|
125
125
|
* @param options - Installation options
|
|
126
126
|
* @throws {Error} If the .apks bundle cannot be installed
|
|
127
127
|
*/
|
|
128
|
-
export async function installApks(
|
|
129
|
-
this: ADB,
|
|
130
|
-
apks: string,
|
|
131
|
-
options: InstallApksOptions = {},
|
|
132
|
-
): Promise<void> {
|
|
128
|
+
export async function installApks(this: ADB, apks: string, options: InstallApksOptions = {}): Promise<void> {
|
|
133
129
|
const {grantPermissions, allowTestPackages, timeout} = options;
|
|
134
130
|
|
|
135
131
|
const args: string[] = [
|
|
@@ -147,10 +143,7 @@ export async function installApks(
|
|
|
147
143
|
args.push('--allow-test-only');
|
|
148
144
|
}
|
|
149
145
|
const tasks: Promise<any>[] = [
|
|
150
|
-
this.execBundletool(
|
|
151
|
-
args,
|
|
152
|
-
`Cannot install '${path.basename(apks)}' to the device ${this.curDeviceId}`,
|
|
153
|
-
),
|
|
146
|
+
this.execBundletool(args, `Cannot install '${path.basename(apks)}' to the device ${this.curDeviceId}`),
|
|
154
147
|
];
|
|
155
148
|
if (grantPermissions) {
|
|
156
149
|
tasks.push(this.getApkInfo(apks));
|
|
@@ -184,11 +177,7 @@ export async function extractBaseApk(this: ADB, apks: string): Promise<string> {
|
|
|
184
177
|
* if language split is not enabled for the bundle.
|
|
185
178
|
* @throws {Error} If there was an error while extracting/finding the file
|
|
186
179
|
*/
|
|
187
|
-
export async function extractLanguageApk(
|
|
188
|
-
this: ADB,
|
|
189
|
-
apks: string,
|
|
190
|
-
language: string | null = null,
|
|
191
|
-
): Promise<string> {
|
|
180
|
+
export async function extractLanguageApk(this: ADB, apks: string, language: string | null = null): Promise<string> {
|
|
192
181
|
if (language) {
|
|
193
182
|
try {
|
|
194
183
|
return await extractFromApks(apks, ['splits', LANGUAGE_APK(language)]);
|