detox tests not recognising View even after adding id - react-native

Apologies in advance if this has been asked before. I came across the detox e2e framework for react native apps, and I thought of giving it a try.
I am trying to automate this demo mobile application given here - link
Since the tests in detox uses testID as one of the locators so I added one in the LoginScreenMaterial.js file inside app/screen/LoginScreenMaterial.js like this
<View testID="login_screen" style={{width: this._width, justifyContent: 'center'}}>
<RkCard style={styles.container}>
<View rkCardHeader style={styles.header}>
<RkText/>
<RkText style={styles.label}>Sign in into your account</RkText>
</View>
However, even after sucessfully building the app, I ran the app with this simple test
it('should have welcome screen', async () => {
await expect(element(by.id('login_screen'))).toBeVisible();
});
However, the tests still fail with the element being failed to be recognised. What am I missing here in this tests? Can we not add testID like this explicitly in the .js file.
Edit 1 : Adding the error message
1) Example
should have welcome screen:
Error: Error: Cannot find UI Element.
Exception with Assertion: {
"Assertion Criteria" : "assertWithMatcher:matcherForSufficientlyVisible(>=0.750000)",
"Element Matcher" : "(((respondsToSelector(accessibilityIdentifier) && accessibilityID('login_screen')) && !(kindOfClass('RCTScrollView'))) || (kindOfClass('UIScrollView') && ((kindOfClass('UIView') || respondsToSelector(accessibilityContainer)) && ancestorThatMatches(((respondsToSelector(accessibilityIdentifier) && accessibilityID('login_screen')) && kindOfClass('RCTScrollView'))))))",
"Recovery Suggestion" : "Check if the element exists in the UI hierarchy printed below. If it exists, adjust the matcher so that it accurately matches element."
}
Error Trace: [
{
"Description" : "Interaction cannot continue because the desired element was not found.",
"Error Domain" : "com.google.earlgrey.ElementInteractionErrorDomain",
"Error Code" : "0",
"File Name" : "GREYElementInteraction.m",
"Function Name" : "-[GREYElementInteraction matchedElementsWithTimeout:error:]",
"Line" : "124"
}
]
at Client.execute (node_modules/detox/src/client/Client.js:74:13)

I took a look at the application and was able to get it to work. I set the following in my devDependencies.
"devDependencies": {
...
"jest": "23.2.0",
"detox": "8.0.0"
...
},
To the package.json I also added
"detox": {
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/BoomApp.app",
"build": "xcodebuild -project ios/BoomApp.xcodeproj -scheme BoomApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 7"
}
},
I ran detox init -r jest
I was then able to get it to recognise when a specific screen had been rendered I did this by adding the testID to the ScrollView LoginScreenBlur.js (line 23)
<AppWrapper>
<ScrollView contentContainerStyle={{flex: 1}} testID={'login_screen'}>
....
</ScrollView>
</AppWrapper>
A then in e2e/firstTest.spec.js I replaced the tests with
it('should have loginScreen', async () => {
await expect(element(by.id('login_screen'))).toBeVisible();
});
This was my console response after running detox build && detox test
node_modules/.bin/jest e2e --config=e2e/config.json --maxWorkers=1 --testNamePattern='^((?!:android:).)*$'
server listening on localhost:64579...
: Searching for device matching iPhone 7...
: Uninstalling org.reactjs.native.example.BoomApp...
: org.reactjs.native.example.BoomApp uninstalled
: Installing /Users/work/Downloads/react-native-ui-kitten-demo-app-master/ios/build/Build/Products/Debug-iphonesimulator/BoomApp.app...
: /Users/work/Downloads/react-native-ui-kitten-demo-app-master/ios/build/Build/Products/Debug-iphonesimulator/BoomApp.app installed
: Terminating org.reactjs.native.example.BoomApp...
: org.reactjs.native.example.BoomApp terminated
: Launching org.reactjs.native.example.BoomApp...
7: org.reactjs.native.example.BoomApp launched. The stdout and stderr logs were recreated, you can watch them with:
tail -F /Users/work/Library/Developer/CoreSimulator/Devices/AF406169-5CF3-4480-9D00-8F934C420043/data/tmp/detox.last_launch_app_log.{out,err}
PASS e2e/firstTest.spec.js (7.935s)
Example
✓ should have loginScreen (1499ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.87s, estimated 9s
Ran all test suites matching /e2e/i with tests matching "^((?!:android:).)*$".
It would seem that the app defaults to launching LoginScreenBlur, so it would make sense to test it first, rather than LoginScreenMaterial.
One thing I have noticed is that the application uses RKTextInput and RkButton, these are not native components but wrappers around native components. This means that you will need to pass the testID down to the native component that you want to have the testID. I am not sure if react-native-ui-kitten supports accessibility labels, so there may be some more work ahead if you wish to automate input of text and button taps.
Adding testID to custom components
See Step 3 https://github.com/wix/detox/blob/master/docs/Introduction.WritingFirstTest.md
Note that not all React components support this prop. Most of the built-in native components in React Native like View, Text, TextInput, Switch, ScrollView have support though. If you create your own composite components, you will have to propagate this prop manually to the correct native component.
A more detailed explanation of adding testID to custom components is given here https://github.com/wix/detox/blob/master/docs/Troubleshooting.RunningTests.md#cant-find-my-component-even-though-i-added-testid-to-its-props
Briefly you should implement your custom component as follows.
Custom Component
export class MyCompositeComponent extends Component {
render() {
return (
<TouchableOpacity testID={this.props.testID}>
<View>
<Text>Something something</Text>
</View>
</TouchableOpacity>
);
}
}
Using Custom Component
Then you should use it like this.
render() {
return <MyCompositeComponent testID='MyUniqueId123' />;
}
Searching the hierarchy
If you have done the above and you are sure your item has the correct testID and the tests are still failing, then you can search for it in the view hierarchy https://github.com/wix/detox/blob/master/docs/Troubleshooting.RunningTests.md#debug-view-hierarchy
I won't repeat the above post in full but the steps are
Start a debuggable app (not a release build) in your simulator
Open Xcode
Attach Xcode to your app's process
Press the Debug View Hierarchy button
This will open the hierarchy viewer, and will show a breakdown of your app's native view hierarchy. Here you can browse through the views
React Native testIDs are manifested as accessibility identifiers in the native view hierarchy

Use AccessiblityLabel to add ID to the View Element:
https://facebook.github.io/react-native/docs/accessibility.html

Indeed, this accessibilityLabel is gonna be your testID which will be recognized in your testing tools:
<TouchableOpacity
accessible={true}
accessibilityLabel={'Tap me!'}
onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>

Related

Expo EAS build ignores background color

In my App's package.json I have the following commands:
"build-android-eas": "set EAS_NO_VCS=1 && eas build --profile preview --platform android",
"build-android-old": "set EAS_NO_VCS=1 && expo build:android -t apk",
in the app.json I set the color:
"androidNavigationBar": {
"backgroundColor":"#363636"
}
Both build commands are executing successfully, but results are different:
i.e. in the EAS-APK the nav-bar keeps the default white bg-color, whereas the older expo-APK produces the expected result.
What am I missing?
TIA
I quote my workaround here from https://github.com/expo/expo/issues/13062
I added "expo-navigation-bar": "^1.3.0" to my dependencies, and call it in the main "App" component:
export default () => {
useEffect( _ => {
const prepare = async _ => {
NavigationBar.setBackgroundColorAsync( '#363636' )
await SplashScreen.preventAutoHideAsync()
}
prepare()
}, [] )
// some code
}
Optically it shows the default (white) color of the NavBar on SplashScreen, then in approx 500 ms repaints it to the color I need and remains the same for the rest of the app.
BTW, If if call NavigationBar.getBackgroundColorAsync() just before the setter, it returns the correct color which is defined in app.json. That color has no effect though.
Maybe it will help somebody for now...

React Native - on button press "NativeAnimatedModule.startOperationBatch is not a function"

I'm attempting to create my first React Native application with Expo, and I'm trying to console.log a random text upon the click of a button.
When the button is pressed, I get two errors (this is via Android Studio):
1 - NativeAnimatedModule.startOperationBatch is not a function
2 - There was a problem sending log messages to your development environment TypeError: stackString.split is not a function.
Occasionally I sometimes get this error as well
Animated node tag 3 does not exist
Below is the simple code I'm trying to execute
<View>
<TouchableOpacity
onPress={() => console.log('test')}
>
<Text>Log In</Text>
</TouchableOpacity>
</View>
I looked online and I don't really see anything on these 2 errors. Is it the way I have the code setup? Could I be missing packages?
I have these react-native packages installed for the time being
"react-native": "^0.64.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-reanimated": "^2.0.0",
Thanks in advance
I could solve this by following the solution provided in https://github.com/facebook/react-native/issues/29999
This seems to be a bug in the new versions of react native, to solve it, go to react-native/Libraries/Animated/NativeAnimatedHelper.js, then change lines 71 with:
if (Platform.OS === 'android') {
NativeAnimatedModule.startOperationBatch();
}
to
if (Platform.OS === 'android' && NativeAnimatedModule.startOperationBatch) {
NativeAnimatedModule.startOperationBatch();
}
and in line 78:
if (Platform.OS === 'android') {
NativeAnimatedModule.finishOperationBatch();
}
to
if (Platform.OS === 'android' && NativeAnimatedModule.finishOperationBatch) {
NativeAnimatedModule.finishOperationBatch();
}
Hopefully it will work.

Check if device is jailbroken/rooted using Jail Monkey in React Native fails module is 'undefined'

In React Native I found two plugins to check if a device (iOS/Android) is jailbroken/rooted:
Jail Monkey
react-native-is-device-rooted
I have firstly tried the npm package react-native-is-device-rooted but it doesn't work and it seems to be outdated. So I tried Jail Monkey, but I get this following error:
The code is:
import JailMonkey from 'jail-monkey'
export default class Welcome extends Component {
render() {
return (
...
<View style={styles.lowerView}>
<CustomButton text={"Jail Monkey"} onPress={() => this.printJailMonkey()}/>
</View>
...
);
}
printJailMonkey = () => {
console.log("Jail Monkey library content: " + JailMonkey.isJailBroken())
}
}
I have checked carefully the manual link of the package (using Xcode, pod install, and so on...). Nothing worked, does someone can help me?
JailMonkey uses Native Modules and thus cannot run in an Expo managed app. You need to eject it to ExpoKit for JailMonkey to work.
Solved but doing manually the linking.
Try following steps for manual linking.
Navigate to root folder in terminal enter commands:
react-native link
cd ios
pod update

Animated Button block the Detox

This is what I meant about the animating button. I let it have an ID, but it can't be located by the Detox somehow.
Detox eliminates flakiness by automatically synchronizing your tests with the app. A test cannot continue to the next line if the app is busy. The test will only resume when the app becomes idle. Detox monitors your app very closely in order to know when it's idle. It tracks several asynchronous operations and waits until they complete. This includes:
Keeping track of all network requests that are currently in-flight and waiting until they complete
Keeping track of pending animations and waiting until they complete
Keeping track of timers (like setTimeout) and waiting until they expire
Keeping track of the React Native bridge which carries asynchronous messages
Keeping track of asynchronous React Native layout and the shadow queue
Keeping track of the JavaScript event loop which may contain pending asynchronous actions
So apparently there is a line saying that keeping track of pending animation, so if the button keeps animating like this. Then It will keep waiting? Thus, usually how to deal with this issue properly?
Thanks
From Detox documentation:
Endless looping animations
By default, Detox will wait until animations complete. If you have an
endless looping animation, this may cause Detox to hang. In this case,
consider turning off the animation synchronization or remove the
endless loop in your E2E build with react-native-repackager.
https://github.com/wix/detox/blob/master/docs/Troubleshooting.Synchronization.md#endless-looping-animations
General remarks
Infinite animations (looped animations) can make detox wait forever.
Please consider turning looped animations off for testing. It's also a
good practice to speed up all animations for testing.
https://github.com/wix/detox/blob/master/docs/More.AndroidSupportStatus.md#general-remarks
Detox provides disableSynchronization() - so you can temporarily disable synchronization to work around an animation
and then turn it back on after the animation is gone. This however will not work for all cases. For example if you are using react-navigation and the on press button pushes new screen to the navigation stack the button is still going to continue animating in the background, blocking any further tests you are planning to run on the new screen.
So ideally you want to go with the other suggestion and disable these types of animations for your E2E tests. Here are 3 possible options to achieve this.
A:
Detox authors suggest using react-native-repackager for this. At the moment it only supports RN 0.51, so this might not be ideal for everyone. Please check supported version before using.
Currently supports only RN 0.51
B:
Set up React Native build environments. Based on an environment configuration variables you can then disable continues animations when building for E2E tests.
https://blog.carbonfive.com/2016/09/29/setting-up-react-native-build-environments-using-nativemodules/
C:
The easiest way I found is to use react-native-config. Here is also a good article on Managing Configuration in React Native with react-native-config, and another related question how-to-tell-detox-is-running-tests.
Install the package:
$ yarn add react-native-config
Link the library:
$ react-native link react-native-config
To test this solution I created 2 files, .env.production and .env.testing in the root React Native app directory. I am then using IS_ANIMATE config variable to toggle animation depending on the build environment. You need to add ENVFILE=.env.testing and ENVFILE=.env.production to your detox build config.
.env.production
ENV_TYPE=Production
IS_ANIMATE=1
.env.testing
ENV_TYPE=Testing
IS_ANIMATE=0
app.js
import Config from 'react-native-config'
import React, { Component } from 'react'
import {
AppRegistry,
StyleSheet,
Alert,
Animated,
View,
TouchableOpacity,
Text
} from 'react-native'
class example extends Component {
constructor(props) {
super(props)
this.state = {
radius: new Animated.Value(1)
}
}
componentDidMount() {
// only enable animation for production
if (Config.IS_ANIMATE == true) {
this.cycleAnimation()
}
}
cycleAnimation() {
Animated.loop(
Animated.sequence([
Animated.timing(this.state.radius, {
toValue: 2,
duration: 500,
delay: 1000
}),
Animated.timing(this.state.radius, {
toValue: 1,
duration: 500
})
])
).start()
}
render() {
return (
<View testID='container' style={styles.container}>
<Text>{Config.ENV_TYPE}</Text>
<TouchableOpacity
testID='button'
onPress={() => Alert.alert("I was pressed")}
>
<Animated.View
style={[
styles.button,
{transform: [
{scale: this.state.radius},
]}
]}
>
<Text>START DIARY</Text>
</Animated.View>
</TouchableOpacity>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
button: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 60,
width: 120,
height: 120,
backgroundColor: 'green'
},
text: {
padding: 5,
fontSize: 14
}
})
AppRegistry.registerComponent('example', () => example)
example.spec.js
it('Animated Button', async () => {
const buttonElement = element(by.id('button'));
await expect(buttonElement).toBeVisible();
await buttonElement.tap();
});
package.json
"detox": {
"specs": "e2e",
"configurations": {
"ios.sim.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/example.app",
"build": "ENVFILE=.env.production export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -project ios/example.xcodeproj -scheme example -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 5s, iOS 10.3"
},
"ios.sim.test": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app",
"build": "ENVFILE=.env.testing xcodebuild -project ios/example.xcodeproj -scheme example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build -arch x86_64",
"type": "ios.simulator",
"name": "iPhone 5s, iOS 10.3"
}
}
}
The release build will hang: detox build --configuration ios.sim.release && detox test --configuration ios.sim.release
The test build will pass: detox build --configuration ios.sim.test && detox test --configuration ios.sim.test
You can solve this infinite animation loop by using
await device.disableSynchronization();
Just put this line before interacting with the animated element
and then you can enable the synchorization again
await device.enableSynchronization();
This feature had a bug before and they just fixed it in this release: 18.18.0

Expo / React-native change fonts between Android and IOS

I want to know how to have different fonts for IOS and Android,
I have only 1 index file.(App.js)
My code that works on IOS:
<Text style={{fontFamily: 'AppleSDGothicNeo-Thin'}}>Hello!</Text>
But when I open the App on Android I see the standard font (Arial?)
I tried something like this:
<Text style={{fontFamily:'Roboto', fontFamily:'AppleSDGothicNeo-Thin'>Hello!</Text>
But this just gives me an Error that the font wasn't found.
You can use condition in your style using Platform component from React Native
import { Platform } from 'react-native';
<Text style={{fontFamily: (Platform.OS === 'ios') ? 'AppleSDGothicNeo-Thin' : 'Roboto'}}>Hello!</Text>
Also be sure fonts are well imported.
Otherwise import them with the following steps.
1 - Place the fonts you want to use in a directory inside your project. For example in ./assets/fonts/
2 - Add the following line in your package.json:
“rnpm”: {
“assets”: [“./assets/fonts”]
}
3 - run in terminal:
$ react-native link