diff --git a/detox/src/devices/Device.js b/detox/src/devices/Device.js
index 825afff155..fb44341103 100644
--- a/detox/src/devices/Device.js
+++ b/detox/src/devices/Device.js
@@ -83,7 +83,7 @@ class Device {
}
}
- const processId = await this.deviceDriver.launchApp(this._deviceId, _bundleId, this._prepareLaunchArgs(baseLaunchArgs));
+ const processId = await this.deviceDriver.launchApp(this._deviceId, _bundleId, this._prepareLaunchArgs(baseLaunchArgs), params.languageAndLocale);
this._processes[_bundleId] = processId;
await this.deviceDriver.waitUntilReady();
diff --git a/detox/src/devices/Device.test.js b/detox/src/devices/Device.test.js
index f5b754cedd..aeb116a4e1 100644
--- a/detox/src/devices/Device.test.js
+++ b/detox/src/devices/Device.test.js
@@ -96,7 +96,22 @@ describe('Device', () => {
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"}, undefined);
+ });
+
+ it('launchApp({languageAndLocale}) should launch app with a specific language/locale', async () => {
+ device = validDevice();
+
+ const languageAndLocale = {
+ language: 'es-MX',
+ locale: 'es-MX'
+ };
+
+ await device.launchApp({languageAndLocale});
+
+ expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
+ device._bundleId,
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"}, languageAndLocale);
});
it(`relaunchApp()`, async () => {
@@ -107,7 +122,7 @@ describe('Device', () => {
expect(device.deviceDriver.terminate).toHaveBeenCalled();
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"}, undefined);
});
it(`relaunchApp({newInstance: false}) should not terminate the app before launch`, async () => {
@@ -144,7 +159,7 @@ describe('Device', () => {
expect(device.deviceDriver.installApp).toHaveBeenCalled();
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"}, undefined);
});
it(`relaunchApp() without delete when reuse is enabled should not uninstall and install`, async () => {
@@ -158,7 +173,7 @@ describe('Device', () => {
expect(device.deviceDriver.installApp).not.toHaveBeenCalled();
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test"}, undefined);
});
it(`relaunchApp() with url should send the url as a param in launchParams`, async () => {
@@ -167,7 +182,7 @@ describe('Device', () => {
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-detoxURLOverride": "scheme://some.url"});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-detoxURLOverride": "scheme://some.url"}, undefined);
});
it(`relaunchApp() with url should send the url as a param in launchParams`, async () => {
@@ -179,7 +194,7 @@ describe('Device', () => {
{
"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-detoxURLOverride": "scheme://some.url", "-detoxSourceAppOverride":
"sourceAppBundleId"
- });
+ }, undefined);
});
it(`launchApp() with disableTouchIndicators should send a boolean switch as a param in launchParams`, async () => {
@@ -188,7 +203,7 @@ describe('Device', () => {
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-detoxDisableTouchIndicators": true});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-detoxDisableTouchIndicators": true}, undefined);
});
it(`relaunchApp() with userNofitication should send the userNotification as a param in launchParams`, async () => {
@@ -200,7 +215,7 @@ describe('Device', () => {
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-detoxUserNotificationDataURL": "url"});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-detoxUserNotificationDataURL": "url"}, undefined);
});
it(`relaunchApp() with url and userNofitication should throw`, async () => {
@@ -228,7 +243,7 @@ describe('Device', () => {
expect(device.deviceDriver.launchApp).toHaveBeenCalledWith(device._deviceId,
device._bundleId,
- {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-arg1": "1", "-arg2": 2});
+ {"-detoxServer": "ws://localhost:8099", "-detoxSessionId": "test", "-arg1": "1", "-arg2": 2}, undefined);
});
it(`sendToHome() should pass to device driver`, async () => {
diff --git a/detox/src/devices/drivers/AndroidDriver.js b/detox/src/devices/drivers/AndroidDriver.js
index 32af22b7d7..29fae59b7e 100644
--- a/detox/src/devices/drivers/AndroidDriver.js
+++ b/detox/src/devices/drivers/AndroidDriver.js
@@ -83,7 +83,7 @@ class AndroidDriver extends DeviceDriverBase {
}
}
- async launchApp(deviceId, bundleId, launchArgs) {
+ async launchApp(deviceId, bundleId, launchArgs, languageAndLocale) {
await this.emitter.emit('beforeLaunchApp', { deviceId, bundleId, launchArgs });
if (!this.instrumentationProcess) {
diff --git a/detox/src/devices/drivers/SimulatorDriver.js b/detox/src/devices/drivers/SimulatorDriver.js
index 7960655222..dbb7d7862c 100644
--- a/detox/src/devices/drivers/SimulatorDriver.js
+++ b/detox/src/devices/drivers/SimulatorDriver.js
@@ -84,9 +84,9 @@ class SimulatorDriver extends IosDriver {
await this._applesimutils.uninstall(deviceId, bundleId);
}
- async launchApp(deviceId, bundleId, launchArgs) {
+ async launchApp(deviceId, bundleId, launchArgs, languageAndLocale) {
await this.emitter.emit('beforeLaunchApp', {bundleId, deviceId, launchArgs});
- const pid = await this._applesimutils.launch(deviceId, bundleId, launchArgs);
+ const pid = await this._applesimutils.launch(deviceId, bundleId, launchArgs, languageAndLocale);
await this.emitter.emit('launchApp', {bundleId, deviceId, launchArgs, pid});
return pid;
diff --git a/detox/src/devices/ios/AppleSimUtils.js b/detox/src/devices/ios/AppleSimUtils.js
index db62c95915..810c6f91f6 100644
--- a/detox/src/devices/ios/AppleSimUtils.js
+++ b/detox/src/devices/ios/AppleSimUtils.js
@@ -121,12 +121,12 @@ class AppleSimUtils {
}
}
- async launch(udid, bundleId, launchArgs) {
+ async launch(udid, bundleId, launchArgs, languageAndLocale) {
const frameworkPath = await environment.getFrameworkPath();
const logsInfo = new LogsInfo(udid);
const args = this._joinLaunchArgs(launchArgs);
- const result = await this._launchMagically(frameworkPath, logsInfo, udid, bundleId, args);
+ const result = await this._launchMagically(frameworkPath, logsInfo, udid, bundleId, args, languageAndLocale);
return this._parseLaunchId(result);
}
@@ -250,17 +250,25 @@ class AppleSimUtils {
return _.map(launchArgs, (v, k) => `${k} ${v}`).join(' ').trim();
}
- async _launchMagically(frameworkPath, logsInfo, udid, bundleId, args) {
+ async _launchMagically(frameworkPath, logsInfo, udid, bundleId, args, languageAndLocale) {
const statusLogs = {
trying: `Launching ${bundleId}...`,
successful: `${bundleId} launched. The stdout and stderr logs were recreated, you can watch them with:\n` +
` tail -F ${logsInfo.absJoined}`
};
- const launchBin = `/bin/cat /dev/null >${logsInfo.absStdout} 2>${logsInfo.absStderr} && ` +
+ let launchBin = `/bin/cat /dev/null >${logsInfo.absStdout} 2>${logsInfo.absStderr} && ` +
`SIMCTL_CHILD_DYLD_INSERT_LIBRARIES="${frameworkPath}/Detox" ` +
`/usr/bin/xcrun simctl launch --stdout=${logsInfo.simStdout} --stderr=${logsInfo.simStderr} ` +
- `${udid} ${bundleId} --args ${args}`;
+ `${udid} ${bundleId} --args ${args}`;;
+
+ if (!!languageAndLocale && !!languageAndLocale.language) {
+ launchBin += ` -AppleLanguages "(${languageAndLocale.language})"`;
+ }
+
+ if (!!languageAndLocale && !!languageAndLocale.locale) {
+ launchBin += ` -AppleLocale ${languageAndLocale.locale}`;
+ }
return await exec.execWithRetriesAndLogs(launchBin, undefined, statusLogs, 1);
}
diff --git a/detox/src/devices/ios/AppleSimUtils.test.js b/detox/src/devices/ios/AppleSimUtils.test.js
index d5cb389782..6f436125a3 100644
--- a/detox/src/devices/ios/AppleSimUtils.test.js
+++ b/detox/src/devices/ios/AppleSimUtils.test.js
@@ -317,6 +317,15 @@ describe('AppleSimUtils', () => {
expect(exec.execWithRetriesAndLogs.mock.calls).toMatchSnapshot();
});
+ it('appends language and locale flags', async () => {
+ const languageAndLocale = {
+ language: "es-MS",
+ locale: "en-US"
+ };
+ await uut.launch('udid', 'theBundleId', undefined, languageAndLocale);
+ expect(exec.execWithRetriesAndLogs.mock.calls).toMatchSnapshot();
+ });
+
it('concats args', async () => {
await uut.launch('udid', 'theBundleId', { 'foo': 'bar', 'bob': 'yourUncle' });
expect(exec.execWithRetriesAndLogs.mock.calls).toMatchSnapshot();
@@ -344,6 +353,7 @@ describe('AppleSimUtils', () => {
const result = await uut.launch('udid', 'theBundleId');
expect(result).toEqual(12345);
});
+
});
describe('sendToHome', () => {
diff --git a/detox/src/devices/ios/__snapshots__/AppleSimUtils.test.js.snap b/detox/src/devices/ios/__snapshots__/AppleSimUtils.test.js.snap
index e38bf52977..3291f4da7e 100644
--- a/detox/src/devices/ios/__snapshots__/AppleSimUtils.test.js.snap
+++ b/detox/src/devices/ios/__snapshots__/AppleSimUtils.test.js.snap
@@ -132,6 +132,21 @@ Array [
]
`;
+exports[`AppleSimUtils launch appends language and locale flags 1`] = `
+Array [
+ Array [
+ "/bin/cat /dev/null >/Users/detox/Library/Developer/CoreSimulator/Devices/udid/data/tmp/detox.last_launch_app_log.out 2>/Users/detox/Library/Developer/CoreSimulator/Devices/udid/data/tmp/detox.last_launch_app_log.err && SIMCTL_CHILD_DYLD_INSERT_LIBRARIES=\\"undefined/Detox\\" /usr/bin/xcrun simctl launch --stdout=/tmp/detox.last_launch_app_log.out --stderr=/tmp/detox.last_launch_app_log.err udid theBundleId --args -AppleLanguages \\"(es-MS)\\" -AppleLocale en-US",
+ undefined,
+ Object {
+ "successful": "theBundleId launched. The stdout and stderr logs were recreated, you can watch them with:
+ tail -F /Users/detox/Library/Developer/CoreSimulator/Devices/udid/data/tmp/detox.last_launch_app_log.{out,err}",
+ "trying": "Launching theBundleId...",
+ },
+ 1,
+ ],
+]
+`;
+
exports[`AppleSimUtils launch asks environment for frameworkPath 1`] = `
Array [
Array [
diff --git a/detox/test/e2e/06.device.test.js b/detox/test/e2e/06.device.test.js
index ed460c7f87..4d189049ee 100644
--- a/detox/test/e2e/06.device.test.js
+++ b/detox/test/e2e/06.device.test.js
@@ -39,6 +39,29 @@ describe('Device', () => {
await expect(element(by.text('Hello!!!'))).toBeVisible();
});
+ // // Passing on iOS, not implemented on Android
+ // it('launchApp in a different language', async () => {
+ // let languageAndLocale = {
+ // language: "es-MX",
+ // locale: "es-MX"
+ // };
+
+ // await device.launchApp({newInstance: true, languageAndLocale});
+ // await element(by.text('Language')).tap();
+ // await expect(element(by.text(`Current locale: ${languageAndLocale.locale}`))).toBeVisible();
+ // await expect(element(by.text(`Current language: ${languageAndLocale.language}`))).toBeVisible();
+
+ // languageAndLocale = {
+ // language: "en-US",
+ // locale: "en-US"
+ // };
+
+ // await device.launchApp({newInstance: true, languageAndLocale});
+ // await element(by.text('Language')).tap();
+ // await expect(element(by.text(`Current locale: ${languageAndLocale.locale}`))).toBeVisible();
+ // await expect(element(by.text(`Current language: ${languageAndLocale.language}`))).toBeVisible();
+ // });
+
it('resetContentAndSettings() + install() + relaunch() - should tap successfully', async () => {
await device.resetContentAndSettings();
await device.installApp();
diff --git a/detox/test/src/Screens/LanguageScreen.js b/detox/test/src/Screens/LanguageScreen.js
new file mode 100644
index 0000000000..88e6256d2c
--- /dev/null
+++ b/detox/test/src/Screens/LanguageScreen.js
@@ -0,0 +1,27 @@
+import React, { Component } from 'react';
+import { Text, View, NativeModules, Platform } from 'react-native';
+import _ from 'lodash';
+
+export default class LanguageScreen extends Component {
+ render() {
+
+ const locale = Platform.select({
+ ios: () => NativeModules.SettingsManager.settings.AppleLocale,
+ android: () => NativeModules.I18nManager.localeIdentifier
+ })();
+
+ const language = Platform.select({
+ ios: () => _.take(NativeModules.SettingsManager.settings.AppleLanguages, 1),
+ android: () => 'Unavailable'
+ })();
+
+ return (
+
+ Current locale: {locale}
+
+ Current language: {language}
+
+
+ );
+ }
+}
diff --git a/detox/test/src/Screens/index.js b/detox/test/src/Screens/index.js
index 8548420b1c..01906d2300 100644
--- a/detox/test/src/Screens/index.js
+++ b/detox/test/src/Screens/index.js
@@ -12,7 +12,8 @@ import NetworkScreen from './NetworkScreen';
import AnimationsScreen from './AnimationsScreen';
import LocationScreen from './LocationScreen';
import ShakeScreen from './ShakeScreen';
-import DatePickerScreen from './DatePickerScreen'
+import DatePickerScreen from './DatePickerScreen';
+import LanguageScreen from './LanguageScreen';
export {
SanityScreen,
@@ -29,5 +30,6 @@ export {
AnimationsScreen,
LocationScreen,
ShakeScreen,
- DatePickerScreen
+ DatePickerScreen,
+ LanguageScreen
};
diff --git a/detox/test/src/app.js b/detox/test/src/app.js
index cdb9201845..2e43897d19 100644
--- a/detox/test/src/app.js
+++ b/detox/test/src/app.js
@@ -78,6 +78,7 @@ class example extends Component {
Choose a test
+ {this.renderScreenButton('Language', Screens.LanguageScreen)}
{this.renderScreenButton('Sanity', Screens.SanityScreen)}
{this.renderScreenButton('Matchers', Screens.MatchersScreen)}
{this.renderScreenButton('Actions', Screens.ActionsScreen)}
diff --git a/docs/APIRef.DeviceObjectAPI.md b/docs/APIRef.DeviceObjectAPI.md
index 6396bae06f..509393bdfe 100644
--- a/docs/APIRef.DeviceObjectAPI.md
+++ b/docs/APIRef.DeviceObjectAPI.md
@@ -135,6 +135,48 @@ Disable touch indicators on iOS.
await device.launchApp({disableTouchIndicators: true});
```
+##### 9. Launch with a specific language (iOS only)
+Launch the app with a specific system language
+
+Information about accepted values can be found [here](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html).
+
+```js
+await device.launchApp({
+ languageAndLocale: {
+ language: "es-MX",
+ locale: "es-MX"
+ }
+});
+```
+
+With this API, you can run sets of e2e tests per language. For example:
+```js
+['es-MX', 'fr-FR', 'pt-BR'].forEach(locale => {
+ describe(`Test suite in ${locale}`, () => {
+
+ beforeAll(async () => {
+ await device.launchApp({
+ newInstance: true,
+ languageAndLocale: {
+ language: locale,
+ locale
+ }
+ });
+ });
+
+
+ it('Test A', () => {
+
+ })
+
+ it('Test B', () => {
+
+ })
+
+ });
+});
+```
+
### `device.relaunchApp(params)`
**Deprecated** Use `device.launchApp(params)` instead. This method is now calling `launchApp({newInstance: true})` for backwards compatibility, it will be removed in Detox 6.X.X.
Kill and relaunch the app defined in the current [`configuration`](APIRef.Configuration.md).