Animated Button block the Detox - 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

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...

Detox using jest timeout error: "is assigned to undefined"

I'm trying to add detox using jest and jest-circus to my ReactNative app but I'm currently struggling making it work. Detox, jest and jest-circus have been added from scratch at the latest version.
When launching my test, after a successful build, it launches the simulator but hangs at the first test and stops with a timeout and a is assigned to undefined error. It seems like it doesn't find the simulator but it's correctly running and the uninstall / install app process correctly worked too.
Here's the code.
environment.js
const {
DetoxCircusEnvironment,
SpecReporter,
WorkerAssignReporter,
} = require('detox/runners/jest-circus')
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
constructor(config) {
super(config)
// Can be safely removed, if you are content with the default value (=300000ms)
this.initTimeout = 30000
// This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level.
// This is strictly optional.
this.registerListeners({
SpecReporter,
WorkerAssignReporter,
})
}
}
module.exports = CustomDetoxEnvironment
config.json for jest
{
"testEnvironment": "./environment",
"testRunner": "jest-circus/runner",
"testTimeout": 120000,
"testRegex": "\\.spec\\.js$",
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true
}
.detoxrc.json
{
"testRunner": "jest",
"runnerConfig": "test/tdd/config.json",
"specs": "test/tdd",
"configurations": {
"ios.sim.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/BetaSeriesNative.app",
"build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace ios/BetaSeriesNative.xcworkspace -UseNewBuildSystem=NO -scheme BetaSeriesNative -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -quiet",
"type": "ios.simulator",
"device": {
"type": "iPhone 8"
},
"artifacts": {
"pathBuilder": "./test/tdd/detox.pathbuilder.ios.js"
}
}
}
}
login.spec.js test
describe('When on the incentive page', () => {
beforeEach(async () => {
await device.reloadReactNative()
})
it('it should open the login view', async () => {
await expect(element(by.id('LoginView'))).toBeVisible()
await expect(element(by.id('LoginView_button'))).toBeVisible()
await element(by.id('LoginView_button')).tap()
await expect(element(by.id('LoginView_form'))).toBeVisible()
})
})
Here's the error.
Thanks!
Ok, it seems like I found where the problem was coming from.
I added await device.disableSynchronization() and removed await device.reloadReactNative() in the beforeEach hook.
I also commented a lot of code in my app and I ended returning null in my first homepage view render.
I tried of course to just return null in my render without adding/removing those lines in my test, but without it, it still doesn't work.
Weird thing though. Sometimes it still hangs when launching the test and I still got the same error as before: is assigned to undefined. And when I relaunch it, sometimes it works like the screenshot below. I'd say it's working now maybe 2 out of 3. Maybe there is still some code in my app that is hanging the test and then times out, so I'll keep looking.
Anyway, I think a better error or maybe a warning that it might come from elsewhere, or the app itself, would be better to understand that kind of error. It's pretty unclear for now to know where the source of the error is coming from, even with debug and verbose enabled.
Hopefully it will help some of you guys.
Cheers.
P.S in the screenshot my test still fails because I didn't edit the test, but at least it's running the test alright :)

Unable to do Parallel testing with Detox and Jest

I have seen similar questions on Stack Overflow but I don't feel like we're having the same issue and its been one year for the last question with no answers.
I have followed the documentation and all my tests are working fine, but when I open 4 simulators to try parallel testing only one of them reacts.
package.json
{
...
"detox": {
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/AppName/Build/Products/Debug-iphonesimulator/AppName.app",
"build": "xcodebuild -project ios/AppName.xcodeproj -scheme AppName -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 11"
}
}
},
"test-runner": "jest --detectOpenHandles --verbose",
"runner-config": "tests/detox/jest.config.js"
}
}
tests/detox/jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ['./init.ts']
};
init.ts
import { cleanup, init } from 'detox';
const adapter = require('detox/runners/jest/adapter');
const config = require('../../package.json').detox;
jest.setTimeout(90000);
jasmine.getEnv().addReporter(adapter);
beforeAll(async () => {
await init(config, { initGlobals: false });
}, 90000);
afterAll(async () => {
await adapter.afterAll();
await cleanup();
});
And here is the command I use to start tests, after having 4 IOS simulators running and ready
detox test -l warn -w 4 ./path-to-all-tests
Dependencies
MacOS catalina
xed version 11.4
detox: ^16.0.2
jest: ^24.9.0
ts-jest: ^24.1.0
--detectOpenHandles makes the test run synchronously.
from jest docs:
--detectOpenHandles
Attempt to collect and print open handles preventing Jest from exiting
cleanly. Use this in cases where you need to use --forceExit in order
for Jest to exit to potentially track down the reason. This implies
--runInBand, making tests run serially. Implemented using async_hooks. This option has a significant performance penalty and should only be
used for debugging.
https://jestjs.io/docs/cli
You must remove it to run tests in parallel

detox tests not recognising View even after adding id

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>

Jest Error "Cannot read property 'validAttributes' of undefined" on ReactNative 0.48

We have an app we recently updated from ReactNative 0.42 to 0.48. In that update we migrated to Jest for testing (from mocha/chai/enzyme). We are currently using Jest v21.1.0. When I run each test manually, they all pass without error. When I run just yarn jest I get this error:
/scratch/react_native_app/client/node_modules/react-native/Libraries/Renderer/ReactNativeStack-dev.js:2582
warnForStyleProps$1(nativeProps,viewConfig.validAttributes);
^
TypeError: Cannot read property 'validAttributes' of undefined
at setNativePropsStack$1 (/scratch/react_native_app/client/node_modules/react-native/Libraries/Renderer/ReactNativeStack-dev.js:2582:43)
at Component.setNativeProps (/scratch/react_native_app/client/node_modules/react-native/Libraries/Renderer/ReactNativeStack-dev.js:2550:1)
at AnimatedProps.callback [as _callback] (/scratch/react_native_app/client/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js:1819:20)
at AnimatedProps.update (/scratch/react_native_app/client/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js:1698:6)
at /scratch/react_native_app/client/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js:230:69
at Set.forEach (native)
at _flush (/scratch/react_native_app/client/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js:230:16)
at AnimatedValue._updateValue (/scratch/react_native_app/client/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js:939:1)
at TimingAnimation.animation.start._this9._animation [as _onUpdate] (/scratch/react_native_app/client/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js:906:8)
at TimingAnimation.onUpdate (/scratch/react_native_app/client/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js:345:6)
I cannot be sure, but we only have two components that deal with Animations and timing. We are using jest.useFakeTimers(); If I add a jest.runAllTimers(); I can get the error on individual component. The timing portions of the component look like this:
componentDidMount() {
Animated.timing(
this.state.fadeAnim, {
toValue: 1,
delay: 2000
}
).start();
}
And
<Animated.View style={{ opacity: this.state.fadeAnim }}>
...
</Animated.View>
It took quite some time, but I finally figured out what was going on. Basically, more of my tests were rendering the Animated view then I thought. I had originally included jest.useFakeTimers(); in my specific test for the component, but then I moved that into a setup file named test/jest_setup.js and then added this to my package.json file:
"jest": {
"setupFiles": [
"./node_modules/react-native/jest/setup.js",
"./test/jest_setup.js"
],
// rest of jest config
}
Now my errors are gone!