diff --git a/gulpfile.js b/gulpfile.js index 473337ca..e1e264a2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,10 +1,10 @@ "use strict"; -let gulp = require('gulp'), - boilerplate = require('appium-gulp-plugins').boilerplate.use(gulp); +const gulp = require('gulp'); +const boilerplate = require('appium-gulp-plugins').boilerplate.use(gulp); boilerplate({ build: 'appium-adb', - jscs: false, - e2eTest: { android: true } + e2eTest: { android: true }, + eslint: true, }); diff --git a/lib/tools/adb-commands.js b/lib/tools/adb-commands.js index 7e3c547d..7901a147 100644 --- a/lib/tools/adb-commands.js +++ b/lib/tools/adb-commands.js @@ -836,12 +836,22 @@ methods.isAnimationOn = async function () { * * @param {string} language - Language. e.g. en, ja * @param {string} country - Country. e.g. US, JP + * @param {?string} script - Script. e.g. Hans in `zh-Hans-CN` */ -methods.setDeviceSysLocaleViaSettingApp = async function (language, country) { - await this.shell(['am', 'broadcast', '-a', LOCALE_SETTING_ACTION, +methods.setDeviceSysLocaleViaSettingApp = async function (language, country, script = null) { + const params = [ + 'am', 'broadcast', + '-a', LOCALE_SETTING_ACTION, '-n', LOCALE_SETTING_RECEIVER, '--es', 'lang', language.toLowerCase(), - '--es', 'country', country.toUpperCase()]); + '--es', 'country', country.toUpperCase() + ]; + + if (script) { + params.push('--es', 'script', script); + } + + await this.shell(params); }; /** diff --git a/lib/tools/apk-utils.js b/lib/tools/apk-utils.js index 3654c0b3..e22a9021 100644 --- a/lib/tools/apk-utils.js +++ b/lib/tools/apk-utils.js @@ -711,10 +711,11 @@ apkUtilsMethods.setDeviceLocale = async function (locale) { * * @param {string} language - Language. The language field is case insensitive, but Locale always canonicalizes to lower case. * @param {string} country - Country. The language field is case insensitive, but Locale always canonicalizes to lower case. + * @param {?string} script - Script. The script field is case insensitive but Locale always canonicalizes to title case. * * @return {boolean} If current locale is language and country as arguments, return true. */ -apkUtilsMethods.ensureCurrentLocale = async function (language, country) { +apkUtilsMethods.ensureCurrentLocale = async function (language, country, script = null) { const hasLanguage = _.isString(language); const hasCountry = _.isString(country); @@ -750,7 +751,11 @@ apkUtilsMethods.ensureCurrentLocale = async function (language, country) { } } else { const curLocale = (await this.getDeviceLocale()).toLowerCase(); - if (`${language}-${country}` === curLocale) { + // zh-hans-cn : zh-cn + const localeCode = script ? `${language}-${script.toLowerCase()}-${country}` : `${language}-${country}`; + + if (localeCode === curLocale) { + log.debug(`Requested locale is equal to current locale: '${curLocale}'`); return true; } } @@ -772,8 +777,10 @@ apkUtilsMethods.ensureCurrentLocale = async function (language, country) { * format: [a-zA-Z]{2,8}. e.g. en, ja : https://developer.android.com/reference/java/util/Locale.html * @param {string} country - Country. The country (region) field is case insensitive, but Locale always canonicalizes to upper case. * format: [a-zA-Z]{2} | [0-9]{3}. e.g. US, JP : https://developer.android.com/reference/java/util/Locale.html + * @param {?string} script - Script. The script field is case insensitive but Locale always canonicalizes to title case. + * format: [a-zA-Z]{4}. e.g. Hans in zh-Hans-CN : https://developer.android.com/reference/java/util/Locale.html */ -apkUtilsMethods.setDeviceLanguageCountry = async function (language, country) { +apkUtilsMethods.setDeviceLanguageCountry = async function (language, country, script = null) { let hasLanguage = language && _.isString(language); let hasCountry = country && _.isString(country); if (!hasLanguage && !hasCountry) { @@ -823,9 +830,11 @@ apkUtilsMethods.setDeviceLanguageCountry = async function (language, country) { return; } - log.debug(`Current locale: '${curLocale}'; requested locale: '${language}-${country}'`); - if (`${language}-${country}`.toLowerCase() !== curLocale.toLowerCase()) { - await this.setDeviceSysLocaleViaSettingApp(language, country); + // zh-Hans-CN : zh-CN + const localeCode = script ? `${language}-${script}-${country}` : `${language}-${country}`; + log.debug(`Current locale: '${curLocale}'; requested locale: '${localeCode}'`); + if (localeCode.toLowerCase() !== curLocale.toLowerCase()) { + await this.setDeviceSysLocaleViaSettingApp(language, country, script); } } } diff --git a/package.json b/package.json index 34b281d3..bb4af899 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "watch": "gulp watch", "build": "gulp transpile", "mocha": "mocha", - "e2e-test": "gulp e2e-test", + "e2e-test": "gulp transpile && npm run mocha -- -t 600000 --recursive build/test/functional/", "coverage": "gulp coveralls", "precommit-msg": "echo 'Pre-commit checks...' && exit 0", "precommit-test": "REPORTER=dot gulp once", diff --git a/test/fixtures/selendroid-test-app.apk b/test/fixtures/selendroid-test-app.apk index 68a9e055..d31c4d21 100644 Binary files a/test/fixtures/selendroid-test-app.apk and b/test/fixtures/selendroid-test-app.apk differ diff --git a/test/functional/adb-commands-e2e-specs.js b/test/functional/adb-commands-e2e-specs.js index 23d09239..c4a618a6 100644 --- a/test/functional/adb-commands-e2e-specs.js +++ b/test/functional/adb-commands-e2e-specs.js @@ -16,6 +16,7 @@ let expect = chai.expect; const IME = 'com.example.android.softkeyboard/.SoftKeyboard', defaultIMEs = [ 'com.android.inputmethod.latin/.LatinIME', + 'com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME', 'io.appium.android.ime/.UnicodeIME', ], contactManagerPath = path.resolve(rootDir, 'test', @@ -105,6 +106,7 @@ describe('adb commands', function () { it('should get device locale', async function () { if (parseInt(apiLevel, 10) < 23) return this.skip(); // eslint-disable-line curly + await adb.setDeviceSysLocaleViaSettingApp('en', 'US'); ['us', 'en', 'ca_en', 'en-US'].should.contain(await adb.getDeviceLocale()); }); it('should forward the port', async function () { @@ -156,10 +158,14 @@ describe('adb commands', function () { (await adb.isWifiOn()).should.be.false; }); it('should be able to turn off animation @skip-ci', async function () { + await adb.grantPermission('io.appium.settings', 'android.permission.SET_ANIMATION_SCALE'); + await adb.setAnimationState(false); (await adb.isAnimationOn()).should.be.false; }); it('should be able to turn on animation @skip-ci', async function () { + await adb.grantPermission('io.appium.settings', 'android.permission.SET_ANIMATION_SCALE'); + await adb.setAnimationState(true); (await adb.isAnimationOn()).should.be.true; }); @@ -171,6 +177,9 @@ describe('adb commands', function () { await adb.setDeviceSysLocaleViaSettingApp('fr', 'fr'); (await adb.getDeviceSysLocale()).should.equal('fr-FR'); + await adb.setDeviceSysLocaleViaSettingApp('zh', 'CN', 'Hans'); + (await adb.getDeviceSysLocale()).should.equal('zh-Hans-CN'); + await adb.setDeviceSysLocaleViaSettingApp('en', 'us'); (await adb.getDeviceSysLocale()).should.equal('en-US'); }); diff --git a/test/functional/setup.js b/test/functional/setup.js index c6545bdd..d4536ebc 100644 --- a/test/functional/setup.js +++ b/test/functional/setup.js @@ -9,6 +9,9 @@ const API_LEVEL_MAP = { '6': '23', '7': '24', '7.1': '25', + '8.0': '26', + '8.1': '27', + '9.0': '28', }; const avdName = process.env.ANDROID_AVD || 'NEXUS_S_18_X86'; diff --git a/test/unit/adb-commands-specs.js b/test/unit/adb-commands-specs.js index 7ece7a22..b08d7373 100644 --- a/test/unit/adb-commands-specs.js +++ b/test/unit/adb-commands-specs.js @@ -516,13 +516,22 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m }); }); describe('setDeviceSysLocaleViaSettingApp', function () { - const adbArgs = ['am', 'broadcast', '-a', 'io.appium.settings.locale', - '-n', 'io.appium.settings/.receivers.LocaleSettingReceiver', - '--es', 'lang', 'en', '--es', 'country', 'US']; - it('should call shell with locale settings', async function () { + it('should call shell with locale settings without script', async function () { + const adbArgs = ['am', 'broadcast', '-a', 'io.appium.settings.locale', + '-n', 'io.appium.settings/.receivers.LocaleSettingReceiver', + '--es', 'lang', 'en', '--es', 'country', 'US']; + mocks.adb.expects("shell").once().withExactArgs(adbArgs); await adb.setDeviceSysLocaleViaSettingApp('en', 'US'); }); + + it('should call shell with locale settings with script', async function () { + const adbArgs = ['am', 'broadcast', '-a', 'io.appium.settings.locale', + '-n', 'io.appium.settings/.receivers.LocaleSettingReceiver', + '--es', 'lang', 'zh', '--es', 'country', 'CN', '--es', 'script', 'Hans']; + mocks.adb.expects("shell").once().withExactArgs(adbArgs); + await adb.setDeviceSysLocaleViaSettingApp('zh', 'CN', 'Hans'); + }); }); describe('setGeoLocation', function () { const location = { diff --git a/test/unit/apk-utils-specs.js b/test/unit/apk-utils-specs.js index 3f8e00e8..76d39c8e 100644 --- a/test/unit/apk-utils-specs.js +++ b/test/unit/apk-utils-specs.js @@ -670,6 +670,16 @@ describe('Apk-utils', withMocks({adb, fs, teen_process}, function (mocks) { mocks.adb.expects("getDeviceLocale").withExactArgs().once().returns(""); (await adb.ensureCurrentLocale('en', 'us')).should.be.false; }); + it('should return true when API 23 with script', async function () { + mocks.adb.expects("getApiLevel").withExactArgs().once().returns(23); + mocks.adb.expects("getDeviceLocale").withExactArgs().once().returns("zh-Hans-CN"); + (await adb.ensureCurrentLocale('zh', 'CN', 'Hans')).should.be.true; + }); + it('should return false when API 23 with script', async function () { + mocks.adb.expects("getApiLevel").withExactArgs().once().returns(23); + mocks.adb.expects("getDeviceLocale").withExactArgs().once().returns(""); + (await adb.ensureCurrentLocale('zh', 'CN', 'Hans')).should.be.false; + }); }); describe('setDeviceLocale', function () { it('should not call setDeviceLanguageCountry because of empty', async function () { @@ -758,11 +768,21 @@ describe('Apk-utils', withMocks({adb, fs, teen_process}, function (mocks) { .once().returns(24); mocks.adb.expects("getDeviceLocale").withExactArgs() .once().returns('fr-FR'); - mocks.adb.expects("setDeviceSysLocaleViaSettingApp").withExactArgs(language, country) + mocks.adb.expects("setDeviceSysLocaleViaSettingApp").withExactArgs(language, country, null) .once().returns(""); mocks.adb.expects('reboot').never(); await adb.setDeviceLanguageCountry(language, country); }); + it('should call set locale with script via setting app when API 24+', async function () { + mocks.adb.expects("getApiLevel").withExactArgs() + .once().returns(24); + mocks.adb.expects("getDeviceLocale").withExactArgs() + .once().returns('fr-FR'); + mocks.adb.expects("setDeviceSysLocaleViaSettingApp").withExactArgs('zh', 'CN', 'Hans') + .once().returns(""); + mocks.adb.expects('reboot').never(); + await adb.setDeviceLanguageCountry('zh', 'CN', 'Hans'); + }); it('should not set language and country if it does not change when API 24+', async function () { mocks.adb.expects("getApiLevel").withExactArgs() .once().returns(24);