Skip to content

Commit 0705269

Browse files
feat: Add 'mobile: getAppStrings' extension (#593)
1 parent 4f80ef7 commit 0705269

File tree

4 files changed

+104
-63
lines changed

4 files changed

+104
-63
lines changed

README.md

+24-10
Original file line numberDiff line numberDiff line change
@@ -1044,20 +1044,34 @@ wifi | booolean | no | Either to enable or disable Wi-Fi. | false
10441044
data | booolean | no | Either to enable or disable mobile data. | false
10451045
airplaneMode | booolean | no | Either to enable or disable Airplane Mode. | false
10461046

1047+
### mobile: getAppStrings
1048+
1049+
Retrieves string resources for the given app language. An error is thrown if strings cannot be fetched or no strings exist
1050+
for the given language abbreviation. Available since driver version 2.15.0
1051+
1052+
#### Arguments
1053+
1054+
Name | Type | Required | Description | Example
1055+
--- | --- | --- | --- | ---
1056+
language | string | no | The language abbreviation to fetch app strings mapping for. If no language is provided then strings for the default language on the device under test would be returned | fr
1057+
1058+
#### Returned Result
1059+
1060+
App strings map, where keys are resource identifiers.
10471061

10481062
## Applications Management
10491063

10501064
UiAutomator2 driver supports Appium endpoints for applications management:
1051-
- Check if app is installed (`POST /appium/device/app_installed`)
1052-
- Install/upgrade app (`POST /appium/device/install_app`)
1053-
- Activate app (`POST /appium/device/activate_app`)
1065+
- Check if app is installed ([mobile: isAppInstalled](#mobile-isappinstalled))
1066+
- Install/upgrade app ([mobile: installApp](#mobile-installapp))
1067+
- Activate app ([mobile: activateApp](#mobile-activateapp))
10541068
- Since UIAutomator2 driver v2.2.0, the server calls `am start`/`am start-activity` to start the application on devices with API level 24+. [monkey](https://developer.android.com/studio/test/other-testing-tools/monkey) tool is called on devices below API level 24.
10551069
- UIAutomator2 driver v2.1.2 and lower versions call the `monkey` tool on all devices.
10561070
- The `monkey` tool [turns on auto rotation](https://stackoverflow.com/questions/56684778/adb-shell-monkey-command-changing-device-orientation-lock), so please consider using [mobile: startActivity](#mobile-startactivity) if you would like to keep your current rotation preferences.
1057-
- Uninstall app (`POST /appium/device/remove_app`)
1058-
- Terminate app (`POST /appium/device/terminate_app`)
1059-
- Start app activity (`POST /appium/device/start_activity`)
1060-
- Query the current app state (`POST /appium/device/app_state`)
1071+
- Uninstall app ([mobile: removeApp](#mobile-removeapp))
1072+
- Terminate app ([mobile: terminateApp](#mobile-terminateapp))
1073+
- Start app activity ([mobile: startActivity](#mobile-startactivity))
1074+
- Query the current app state ([mobile: queryAppState](#mobile-queryappstate))
10611075

10621076
Refer to the corresponding Appium client tutorial to find out the names of the corresponding wrappers for these APIs.
10631077

@@ -1070,9 +1084,9 @@ Useful links:
10701084
## Files Management
10711085

10721086
UiAutomator2 driver supports Appium endpoints for files management:
1073-
- Push file (`POST /appium/device/push_file`)
1074-
- Pull file (`POST /appium/device/pull_file`)
1075-
- Pull folder (`POST /appium/device/pull_folder`)
1087+
- Push file ([mobile: pushFile](#mobile-pushfile))
1088+
- Pull file ([mobile: pullFile](#mobile-pullfile))
1089+
- Pull folder ([mobile: pullFolder](#mobile-pullfolder))
10761090

10771091
Refer to the corresponding Appium client tutorial to find out the names of the corresponding wrappers for these APIs.
10781092

lib/commands/app-strings.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import _ from 'lodash';
2+
import { fs, tempDir } from 'appium/support';
3+
4+
const commands = {};
5+
6+
commands.getStrings = async function (language) {
7+
if (!language) {
8+
language = await this.adb.getDeviceLanguage();
9+
this.log.info(`No language specified, returning strings for: ${language}`);
10+
}
11+
12+
// Clients require the resulting mapping to have both keys
13+
// and values of type string
14+
const preprocessStringsMap = function (mapping) {
15+
const result = {};
16+
for (const [key, value] of _.toPairs(mapping)) {
17+
result[key] = _.isString(value) ? value : JSON.stringify(value);
18+
}
19+
return result;
20+
};
21+
22+
if (this.apkStrings[language]) {
23+
// Return cached strings
24+
return preprocessStringsMap(this.apkStrings[language]);
25+
}
26+
27+
if (!this.opts.app && !this.opts.appPackage) {
28+
this.log.errorAndThrow("One of 'app' or 'appPackage' capabilities should must be specified");
29+
}
30+
31+
let app = this.opts.app;
32+
const tmpRoot = await tempDir.openDir();
33+
try {
34+
if (!app) {
35+
try {
36+
app = await this.adb.pullApk(this.opts.appPackage, tmpRoot);
37+
} catch (err) {
38+
this.log.errorAndThrow(`Failed to pull an apk from '${this.opts.appPackage}'. Original error: ${err.message}`);
39+
}
40+
}
41+
42+
if (!await fs.exists(app)) {
43+
this.log.errorAndThrow(`The app at '${app}' does not exist`);
44+
}
45+
46+
try {
47+
const {apkStrings} = await this.adb.extractStringsFromApk(app, language, tmpRoot);
48+
this.apkStrings[language] = apkStrings;
49+
return preprocessStringsMap(apkStrings);
50+
} catch (err) {
51+
this.log.errorAndThrow(`Cannot extract strings from '${app}'. Original error: ${err.message}`);
52+
}
53+
} finally {
54+
await fs.rimraf(tmpRoot);
55+
}
56+
};
57+
58+
/**
59+
* @typedef {Object} GetAppStringsOptions
60+
* @property {string?} language The language abbreviation to fetch app strings mapping for. If no
61+
* language is provided then strings for the default language on the device under test
62+
* would be returned. Examples: en, fr
63+
*/
64+
65+
/**
66+
* Retrives app strings from its resources for the given language
67+
* or the default device language.
68+
*
69+
* @param {GetAppStringsOptions} opts
70+
* @returns {Promise<object>} App strings map
71+
*/
72+
commands.mobileGetAppStrings = async function mobileGetAppStrings (opts = {}) {
73+
return await this.getStrings(opts.language);
74+
};
75+
76+
export default commands;

lib/commands/general.js

+2-53
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import _ from 'lodash';
22
import B from 'bluebird';
33
import { errors } from 'appium/driver';
4-
import { fs, tempDir } from 'appium/support';
54
import { APK_EXTENSION } from '../extensions';
65

76
let extensions = {},
@@ -34,58 +33,6 @@ commands.back = async function () {
3433
await this.adb.keyevent(4);
3534
};
3635

37-
commands.getStrings = async function (language) {
38-
if (!language) {
39-
language = await this.adb.getDeviceLanguage();
40-
this.log.info(`No language specified, returning strings for: ${language}`);
41-
}
42-
43-
// Clients require the resulting mapping to have both keys
44-
// and values of type string
45-
const preprocessStringsMap = function (mapping) {
46-
const result = {};
47-
for (const [key, value] of _.toPairs(mapping)) {
48-
result[key] = _.isString(value) ? value : JSON.stringify(value);
49-
}
50-
return result;
51-
};
52-
53-
if (this.apkStrings[language]) {
54-
// Return cached strings
55-
return preprocessStringsMap(this.apkStrings[language]);
56-
}
57-
58-
if (!this.opts.app && !this.opts.appPackage) {
59-
this.log.errorAndThrow("One of 'app' or 'appPackage' capabilities should must be specified");
60-
}
61-
62-
let app = this.opts.app;
63-
const tmpRoot = await tempDir.openDir();
64-
try {
65-
if (!app) {
66-
try {
67-
app = await this.adb.pullApk(this.opts.appPackage, tmpRoot);
68-
} catch (err) {
69-
this.log.errorAndThrow(`Failed to pull an apk from '${this.opts.appPackage}'. Original error: ${err.message}`);
70-
}
71-
}
72-
73-
if (!await fs.exists(app)) {
74-
this.log.errorAndThrow(`The app at '${app}' does not exist`);
75-
}
76-
77-
try {
78-
const {apkStrings} = await this.adb.extractStringsFromApk(app, language, tmpRoot);
79-
this.apkStrings[language] = apkStrings;
80-
return preprocessStringsMap(apkStrings);
81-
} catch (err) {
82-
this.log.errorAndThrow(`Cannot extract strings from '${app}'. Original error: ${err.message}`);
83-
}
84-
} finally {
85-
await fs.rimraf(tmpRoot);
86-
}
87-
};
88-
8936
commands.getDisplayDensity = async function getDisplayDensity () {
9037
return await this.uiautomator2.jwproxy.command('/appium/device/display_density', 'GET', {});
9138
};
@@ -175,6 +122,8 @@ extensions.executeMobile = async function (mobileCommand, opts = {}) {
175122

176123
getContexts: 'mobileGetContexts',
177124

125+
getAppStrings: 'mobileGetAppStrings',
126+
178127
installMultipleApks: 'mobileInstallMultipleApks',
179128

180129
unlock: 'mobileUnlock',

lib/commands/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import viewportCmds from './viewport';
88
import screenshotCmds from './screenshot';
99
import batteryCmds from './battery';
1010
import gesturesCmds from './gestures';
11+
import appStringsCmds from './app-strings';
1112

1213
let commands = {};
1314
Object.assign(
@@ -22,6 +23,7 @@ Object.assign(
2223
screenshotCmds,
2324
batteryCmds,
2425
gesturesCmds,
26+
appStringsCmds,
2527
// add other command types here
2628
);
2729

0 commit comments

Comments
 (0)