mirror of
https://github.com/hanzoai/Detox.git
synced 2026-09-24 16:55:12 +00:00
Add device.setLocation support to Android (#2783)
This commit is contained in:
committed by
GitHub
parent
2b8c6011a6
commit
576879a674
@@ -120,6 +120,8 @@ dependencies {
|
||||
}
|
||||
implementation 'org.apache.commons:commons-lang3:3.7' // Needed by invoke. Warning: Upgrading to newer versions is not seamless.
|
||||
implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
|
||||
|
||||
implementation fileTree(dir: '../libs', includes: ['*.jar']) // Includes: Genymotion SDK
|
||||
}
|
||||
|
||||
// Unit-testing deps.
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.wix.detox.genymotion;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.genymotion.api.GenymotionManager;
|
||||
|
||||
public class DetoxGenymotionManager {
|
||||
public static void setLocation(double lat, double lon) {
|
||||
GenymotionManager genymotion = getGenymotionManager();
|
||||
genymotion.getGps().setLatitude(lat).setLongitude(lon);
|
||||
}
|
||||
|
||||
private static GenymotionManager getGenymotionManager() {
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
|
||||
return GenymotionManager.getGenymotionManager(context);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Vendored
+8
-1
@@ -528,7 +528,14 @@ declare global {
|
||||
setOrientation(orientation: Orientation): Promise<void>;
|
||||
|
||||
/**
|
||||
* Note: setLocation is dependent on fbsimctl. if fbsimctl is not installed, the command will fail, it must be installed. Sets the simulator location to the given latitude and longitude.
|
||||
* Sets the simulator/emulator location to the given latitude and longitude.
|
||||
*
|
||||
* <p/>On iOS `setLocation` is dependent on [fbsimctl](https://github.com/facebook/idb/tree/4b7929480c3c0f158f33f78a5b802c1d0e7030d2/fbsimctl)
|
||||
* which [is now deprecated](https://github.com/wix/Detox/issues/1371).
|
||||
* If `fbsimctl` is not installed, the command will fail, asking for it to be installed.
|
||||
*
|
||||
* <p/>On Android `setLocation` will work with both Android Emulator (bundled with Android development tools) and Genymotion.
|
||||
* The correct permissions must be set in your app manifest.
|
||||
*
|
||||
* @example await device.setLocation(32.0853, 34.7818);
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
|
||||
This code is generated.
|
||||
For more information see generation/README.md.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
class DetoxGenymotionManager {
|
||||
static setLocation(lat, lon) {
|
||||
if (typeof lat !== "number") throw new Error("lat should be a number, but got " + (lat + (" (" + (typeof lat + ")"))));
|
||||
if (typeof lon !== "number") throw new Error("lon should be a number, but got " + (lon + (" (" + (typeof lon + ")"))));
|
||||
return {
|
||||
target: {
|
||||
type: "Class",
|
||||
value: "com.wix.detox.genymotion.DetoxGenymotionManager"
|
||||
},
|
||||
method: "setLocation",
|
||||
args: [{
|
||||
type: "Double",
|
||||
value: lat
|
||||
}, {
|
||||
type: "Double",
|
||||
value: lon
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
static getGenymotionManager() {
|
||||
return {
|
||||
target: {
|
||||
type: "Class",
|
||||
value: "com.wix.detox.genymotion.DetoxGenymotionManager"
|
||||
},
|
||||
method: "getGenymotionManager",
|
||||
args: []
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DetoxGenymotionManager;
|
||||
@@ -85,6 +85,10 @@ class EmulatorDriver extends AndroidDriver {
|
||||
await this._emulatorLauncher.shutdown(deviceId);
|
||||
}
|
||||
|
||||
async setLocation(deviceId, lat, lon) {
|
||||
await this.adb.setLocation(deviceId, lat, lon);
|
||||
}
|
||||
|
||||
async _fixAvdConfigIniSkinNameIfNeeded(avdName) {
|
||||
const binaryVersion = _.get(await this.binaryVersion(), 'major');
|
||||
if (!binaryVersion) {
|
||||
|
||||
@@ -115,6 +115,28 @@ class ADB {
|
||||
await this.shell(deviceId, `am force-stop ${appId}`);
|
||||
}
|
||||
|
||||
async setLocation(deviceId, lat, lon) {
|
||||
// NOTE: QEMU for Android for the telnet part relies on C stdlib
|
||||
// function `strtod` which is locale-sensitive, meaning that depending
|
||||
// on user environment you'll have to send either comma-separated
|
||||
// numbers or dot-separated ones.
|
||||
//
|
||||
// See: https://android.googlesource.com/platform/external/qemu/+/ae0eaf51751391abea2639a65200e724131dc3d6/android/console.c#2273
|
||||
//
|
||||
// As by default Node.js is distributed without ICU, the locale issue
|
||||
// becomes tricky to solve across different platforms, that's why
|
||||
// it's easier for us just to send 2 commands in a row, ignoring one
|
||||
// which will obviosuly fail.
|
||||
//
|
||||
// Since `adb emu` commands fail silently, .catch() is not necessary.
|
||||
|
||||
const dot = `${lon} ${lat}`;
|
||||
const comma = dot.replace(/\./g, ',');
|
||||
|
||||
await this.emu(deviceId, `geo fix ${dot}`);
|
||||
await this.emu(deviceId, `geo fix ${comma}`);
|
||||
}
|
||||
|
||||
async pidof(deviceId, bundleId) {
|
||||
const bundleIdRegex = escape.inQuotedRegexp(bundleId) + '$';
|
||||
|
||||
@@ -268,6 +290,10 @@ class ADB {
|
||||
return (await this.adbCmd(deviceId, `shell "${escape.inQuotedString(cmd)}"`, options)).stdout.trim();
|
||||
}
|
||||
|
||||
async emu(deviceId, cmd, options) {
|
||||
return (await this.adbCmd(deviceId, `emu "${escape.inQuotedString(cmd)}"`, options)).stdout.trim();
|
||||
}
|
||||
|
||||
async reverse(deviceId, port) {
|
||||
return this.adbCmd(deviceId, `reverse tcp:${port} tcp:${port}`);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,21 @@ describe('ADB', () => {
|
||||
expect(exec).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it(`sets location both with commas and dots due to the locale issue`, async () => {
|
||||
const lat = 30.5;
|
||||
const lon = -70.5;
|
||||
|
||||
await adb.setLocation(deviceId, lat, lon);
|
||||
|
||||
expect(exec).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`-s mockEmulator emu "geo fix -70.5 30.5"`),
|
||||
expect.anything());
|
||||
|
||||
expect(exec).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`-s mockEmulator emu "geo fix -70,5 30,5"`),
|
||||
expect.anything());
|
||||
});
|
||||
|
||||
it(`pidof (success)`, async () => {
|
||||
jest.spyOn(adb, 'shell').mockImplementation(async () =>
|
||||
`u0_a19 2199 1701 3554600 70264 0 0 s com.google.android.ext.services `);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const semver = require('semver');
|
||||
const onSignalExit = require('signal-exit');
|
||||
|
||||
const DetoxGenymotionManager = require('../../../../android/espressoapi/DetoxGenymotionManager');
|
||||
const DetoxRuntimeError = require('../../../../errors/DetoxRuntimeError');
|
||||
const environment = require('../../../../utils/environment');
|
||||
const logger = require('../../../../utils/logger').child({ __filename });
|
||||
@@ -86,6 +87,10 @@ class GenyCloudDriver extends AndroidDriver {
|
||||
await this._instanceLauncher.shutdown(instance);
|
||||
}
|
||||
|
||||
async setLocation(instance, lat, lon) {
|
||||
await this.invocationManager.execute(DetoxGenymotionManager.setLocation(parseFloat(lat), parseFloat(lon)));
|
||||
}
|
||||
|
||||
_assertRecipe(deviceQuery, recipe) {
|
||||
if (!recipe) {
|
||||
throw new DetoxRuntimeError({
|
||||
|
||||
@@ -46,6 +46,8 @@ describe('Genymotion-cloud driver', () => {
|
||||
let instanceLauncher;
|
||||
let instanceAllocation;
|
||||
let authServiceObj;
|
||||
let detoxGenymotionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
signalExit = require('signal-exit');
|
||||
|
||||
@@ -89,6 +91,9 @@ describe('Genymotion-cloud driver', () => {
|
||||
|
||||
const AuthService = require('./services/GenyAuthService');
|
||||
authServiceObj = () => latestInstanceOf(AuthService);
|
||||
|
||||
jest.mock('../../../../android/espressoapi/DetoxGenymotionManager');
|
||||
detoxGenymotionManager = require('../../../../android/espressoapi/DetoxGenymotionManager');
|
||||
});
|
||||
|
||||
let instanceLifecycleService;
|
||||
@@ -310,6 +315,20 @@ describe('Genymotion-cloud driver', () => {
|
||||
expect(instanceLauncher().shutdown).toHaveBeenCalledWith(instance);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setLocation', () => {
|
||||
it('should invoke `DetoxGenymotionManager.setLocation` with specified coordinates', async () => {
|
||||
const invocation = {
|
||||
method: 'setLocation'
|
||||
};
|
||||
detoxGenymotionManager.setLocation.mockReturnValue(invocation);
|
||||
|
||||
const instance = anInstance();
|
||||
await uut.setLocation(instance, '40.5', '55.5');
|
||||
expect(invocationManager.execute).toHaveBeenCalledWith(invocation);
|
||||
expect(detoxGenymotionManager.setLocation).toHaveBeenCalledWith(40.5, 55.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('class scope', () => {
|
||||
|
||||
@@ -102,10 +102,10 @@ dependencies {
|
||||
BINTRAY_SUNSET_HACK end
|
||||
*/
|
||||
|
||||
// noinspection GradleDynamicVersion
|
||||
implementation project(path: ':AsyncStorage')
|
||||
implementation project(':AsyncStorage')
|
||||
implementation project(':react-native-webview')
|
||||
implementation project(':react-native-community-checkbox')
|
||||
implementation project(':react-native-community-geolocation')
|
||||
|
||||
androidTestImplementation(project(path: ':detox'))
|
||||
androidTestImplementation 'com.linkedin.testbutler:test-butler-library:2.2.1'
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.reactnativecommunity.checkbox.ReactCheckBoxPackage;
|
||||
import com.reactnativecommunity.geolocation.GeolocationPackage;
|
||||
import com.reactnativecommunity.webview.RNCWebViewPackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
@@ -26,6 +27,7 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.asList(
|
||||
new MainReactPackage(),
|
||||
new GeolocationPackage(),
|
||||
new RNCWebViewPackage(),
|
||||
new NativeModulePackage(),
|
||||
new AsyncStoragePackage(),
|
||||
|
||||
@@ -15,3 +15,6 @@ project(':react-native-webview').projectDir = new File(rootProject.projectDir, '
|
||||
|
||||
include ':react-native-community-checkbox'
|
||||
project(':react-native-community-checkbox').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/checkbox/android')
|
||||
|
||||
include ':react-native-community-geolocation'
|
||||
project(':react-native-community-geolocation').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/geolocation/android')
|
||||
|
||||
@@ -11,8 +11,12 @@ async function isFbsimctlInstalled() {
|
||||
}
|
||||
}
|
||||
|
||||
describe(':ios: location', () => {
|
||||
it('Location should be unavailable', async () => {
|
||||
describe('location', () => {
|
||||
const lat = -80.125;
|
||||
const lon = 66.5;
|
||||
|
||||
// Skipped on Android because there is no Android permissions support yet
|
||||
it(':ios: Location should be unavailable', async () => {
|
||||
if (!await isFbsimctlInstalled()) {
|
||||
return;
|
||||
}
|
||||
@@ -22,17 +26,20 @@ describe(':ios: location', () => {
|
||||
await expect(element(by.id('error'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('Should receive location (20,20)', async () => {
|
||||
if (!await isFbsimctlInstalled()) {
|
||||
it('Should accept a location', async () => {
|
||||
const isIOS = device.getPlatform() === 'ios';
|
||||
|
||||
if (isIOS && !await isFbsimctlInstalled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await device.relaunchApp({ permissions: { location: 'always' } });
|
||||
await device.setLocation(20.1, 20.2);
|
||||
await device.setLocation(lat, lon);
|
||||
await element(by.text('Location')).tap();
|
||||
await element(by.id('getLocationButton')).tap();
|
||||
await waitFor(element(by.text('Latitude: 20.1'))).toBeVisible().withTimeout(3000);
|
||||
await waitFor(element(by.text(`Latitude: ${lat}`))).toBeVisible().withTimeout(5000);
|
||||
|
||||
await expect(element(by.text('Latitude: 20.1'))).toBeVisible();
|
||||
await expect(element(by.text('Longitude: 20.2'))).toBeVisible();
|
||||
await expect(element(by.text(`Latitude: ${lat}`))).toBeVisible();
|
||||
await expect(element(by.text(`Longitude: ${lon}`))).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"dependencies": {
|
||||
"@react-native-community/async-storage": "^1.12.0",
|
||||
"@react-native-community/checkbox": "^0.5.7",
|
||||
"@react-native-community/geolocation": "^2.0.2",
|
||||
"moment": "^2.24.0",
|
||||
"react": "16.11.x",
|
||||
"react-native": "0.62.x",
|
||||
|
||||
@@ -4,64 +4,89 @@ import {
|
||||
View,
|
||||
Button
|
||||
} from 'react-native';
|
||||
import Geolocation from '@react-native-community/geolocation';
|
||||
|
||||
function Frame({ children }) {
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 20, justifyContent: 'center', alignItems: 'center' }}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function Label({ testID, children }) {
|
||||
return (
|
||||
<Text testID={testID} style={{ marginBottom: 20 }}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
export default class LocationScreen extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
locationRequested: false,
|
||||
coordinates: null
|
||||
coordinates: null,
|
||||
error: '',
|
||||
}
|
||||
|
||||
this.getLocation();
|
||||
}
|
||||
|
||||
async getLocation() {
|
||||
function success(pos) {
|
||||
this.setState({
|
||||
coordinates: pos.coords
|
||||
});
|
||||
};
|
||||
onLocationReceived = (pos) => {
|
||||
this.setState({
|
||||
coordinates: pos.coords,
|
||||
error: '',
|
||||
});
|
||||
};
|
||||
|
||||
function error(err) {
|
||||
this.setState({
|
||||
coordinates: null
|
||||
});
|
||||
};
|
||||
onLocationError = (error) => {
|
||||
this.setState({
|
||||
coordinates: null,
|
||||
error: error.message,
|
||||
});
|
||||
};
|
||||
|
||||
await navigator.geolocation.getCurrentPosition(success.bind(this), error.bind(this));
|
||||
requestLocation = async () => {
|
||||
this.setState({ locationRequested: true });
|
||||
|
||||
await Geolocation.getCurrentPosition(this.onLocationReceived, this.onLocationError, {
|
||||
enableHighAccuracy: true,
|
||||
timeout: 4500,
|
||||
maximumAge: 0,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if(!this.state.locationRequested) {
|
||||
if (!this.state.locationRequested) {
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 20, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Button
|
||||
testID="getLocationButton"
|
||||
title="get location"
|
||||
onPress={async () => {
|
||||
await this.getLocation();
|
||||
this.setState({locationRequested: true});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Frame>
|
||||
<Button testID="getLocationButton" title="Get location" onPress={this.requestLocation} />
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.coordinates) {
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 20, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text testID="latitude" style={{ marginBottom: 20 }}>Latitude: {this.state.coordinates.latitude}</Text>
|
||||
<Text testID="longitude" style={{ marginBottom: 20 }}>Longitude: {this.state.coordinates.longitude}</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 20, justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text testID="error" style={{ marginBottom: 20 }}>Location unavailable</Text>
|
||||
</View>
|
||||
<Frame>
|
||||
<Label testID="latitude">Latitude: {this.state.coordinates.latitude}</Label>
|
||||
<Label testID="longitude">Longitude: {this.state.coordinates.longitude}</Label>
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<Frame>
|
||||
<Label testID="error">{this.state.error}</Label>
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Frame>
|
||||
<Label testID="loading">Locating...</Label>
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,19 +38,18 @@ device.appLaunchArgs.reset();
|
||||
This is the most flexible way of editing the launch arguments. Refer to the [launch arguments guide](APIRef.LaunchArgs.md) for complete details.
|
||||
|
||||
## Methods
|
||||
|
||||
- [`device.selectApp(name)`](#deviceselectappname)
|
||||
- [`device.launchApp()`](#devicelaunchappparams)
|
||||
- [`device.launchApp(params)`](#devicelaunchappparams)
|
||||
- [`device.terminateApp()`](#deviceterminateapp)
|
||||
- [`device.sendToHome()`](#devicesendtohome)
|
||||
- [`device.reloadReactNative()`](#devicereloadreactnative)
|
||||
- [`device.installApp()`](#deviceinstallapp)
|
||||
- [`device.uninstallApp()`](#deviceuninstallapp)
|
||||
- [`device.openURL(url)`](#deviceopenurlurl-sourceappoptional)
|
||||
- [`device.openURL({url, sourceApp[optional]})`](#deviceopenurlurl-sourceappoptional)
|
||||
- [`device.sendUserNotification(params)`](#devicesendusernotificationparams)
|
||||
- [`device.sendUserActivity(params)` **iOS Only**](#devicesenduseractivityparams-ios-only)
|
||||
- [`device.setOrientation(orientation)`](#devicesetorientationorientation)
|
||||
- [`device.setLocation(lat, lon)` **iOS Only**](#devicesetlocationlat-lon-ios-only)
|
||||
- [`device.setLocation(lat, lon)`](#devicesetlocationlat-lon)
|
||||
- [`device.enableSynchronization()`](#deviceenablesynchronization)
|
||||
- [`device.disableSynchronization()`](#devicedisablesynchronization)
|
||||
- [`device.setURLBlacklist([urls])`](#deviceseturlblacklisturls)
|
||||
@@ -69,7 +68,7 @@ This is the most flexible way of editing the launch arguments. Refer to the [lau
|
||||
- [`device.reverseTcpPort()` **Android Only**](#devicereversetcpport-android-only)
|
||||
- [`device.unreverseTcpPort()` **Android Only**](#deviceunreversetcpport-android-only)
|
||||
- [`device.pressBack()` **Android Only**](#devicepressback-android-only)
|
||||
- [`device.getUIDevice()` **Android Only**](#devicegetuidevice-android-only)
|
||||
- [`device.getUiDevice()` **Android Only**](#devicegetuidevice-android-only)
|
||||
|
||||
### `device.selectApp(name)`
|
||||
|
||||
@@ -317,11 +316,13 @@ Takes `"portrait"` or `"landscape"` and rotates the device to the given orientat
|
||||
|
||||
Check out Detox's [own test suite.](../detox/test/e2e/06.device-orientation.test.js)
|
||||
|
||||
### `device.setLocation(lat, lon)` **iOS Only**
|
||||
### `device.setLocation(lat, lon)`
|
||||
|
||||
> Note: `setLocation` is dependent on [fbsimctl](https://github.com/facebook/idb/tree/4b7929480c3c0f158f33f78a5b802c1d0e7030d2/fbsimctl) which [is now deprecated](https://github.com/wix/Detox/issues/1371). If `fbsimctl` is not installed, the command will fail, asking for it to be installed.
|
||||
Sets the simulator/emulator location to the given latitude and longitude.
|
||||
|
||||
Sets the simulator location to the given latitude and longitude.
|
||||
> On iOS `setLocation` is dependent on [fbsimctl](https://github.com/facebook/idb/tree/4b7929480c3c0f158f33f78a5b802c1d0e7030d2/fbsimctl) which [is now deprecated](https://github.com/wix/Detox/issues/1371). If `fbsimctl` is not installed, the command will fail, asking for it to be installed.
|
||||
>
|
||||
> On Android `setLocation` will work with both Android Emulator (bundled with Android development tools) and Genymotion. The correct permissions must be set in your app manifest.
|
||||
|
||||
```js
|
||||
await device.setLocation(32.0853, 34.7818);
|
||||
|
||||
@@ -83,11 +83,10 @@ dependencies {
|
||||
BINTRAY_SUNSET_HACK end
|
||||
*/
|
||||
|
||||
// noinspection GradleDynamicVersion
|
||||
implementation(project(path: ':AsyncStorage')) // Note: not required unless effectively used by your app
|
||||
|
||||
// noinspection GradleDynamicVersion
|
||||
androidTestImplementation('com.wix:detox:+')
|
||||
androidTestImplementation 'com.wix:detox:+'
|
||||
androidTestImplementation 'com.linkedin.testbutler:test-butler-library:2.2.1'
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -49,7 +49,8 @@ function createAndroidFiles() {
|
||||
'../detox/android/detox/src/full/java/com/wix/detox/espresso/web/DetoxWebAtomMatcher.java': '../detox/src/android/espressoapi/web/DetoxWebAtomMatcher.js',
|
||||
'../detox/android/detox/src/full/java/com/wix/detox/espresso/web/WebViewElement.java': '../detox/src/android/espressoapi/web/WebViewElement.js',
|
||||
'../detox/android/detox/src/full/java/com/wix/detox/espresso/web/WebElement.java': '../detox/src/android/espressoapi/web/WebElement.js',
|
||||
'../detox/android/detox/src/full/java/com/wix/detox/espresso/web/WebExpect.java': '../detox/src/android/espressoapi/web/WebExpect.js'
|
||||
'../detox/android/detox/src/full/java/com/wix/detox/espresso/web/WebExpect.java': '../detox/src/android/espressoapi/web/WebExpect.js',
|
||||
'../detox/android/detox/src/full/java/com/wix/detox/genymotion/DetoxGenymotionManager.java': '../detox/src/android/espressoapi/DetoxGenymotionManager.js'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user