9

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.

animating_button

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

zhirzh
  • 3,273
  • 3
  • 25
  • 30
Hien Tran
  • 171
  • 1
  • 12

2 Answers2

15

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

detox_hangs

The test build will pass: detox build --configuration ios.sim.test && detox test --configuration ios.sim.test

detox_passes

Antoni4
  • 2,535
  • 24
  • 37
  • Hi Antoni4, I tried this approach, but apparently the button is endless animation. when I clicked it, it still stayed in the stack for I use react_native_navigation. So the screen is still there just not visible. This is what I assume, that even though the screen with the button is not visible I can still see it using the hierarchy view and thus it still animate in the background? Which cause the synchronisation wait for ever again? – Hien Tran Nov 20 '17 at 23:16
  • Yes I see, that could be the case. Any chance you can provide some code example with detox test to recreate this problem? – Antoni4 Nov 21 '17 at 00:07
  • By the way, just to test your theory. On button press can you disable the button animation just before pushing new screen onto the stack? If your theory is correct then Detox should be able to continue with the next tests. – Antoni4 Nov 21 '17 at 00:18
  • One more thought that comes to mind. Maybe you can define an environment variable for this specific build type and use it to disable the button animation during E2E tests. Good article on this topic: https://blog.carbonfive.com/2016/09/29/setting-up-react-native-build-environments-using-nativemodules/ – Antoni4 Nov 21 '17 at 00:32
  • Or instead use react-native-repackager as suggested in documentation to disable button animation when performing E2E tests with Detox. – Antoni4 Nov 21 '17 at 01:48
  • Oh nice recommendation @Antoni4. I would definitely try that option. I have been able to overcome the animation blocker by doing a hacky way by manual handling the synchronisation. However, It just feel unstatble because I can't be sure if the handling is correctly resolved or just newbie luck. I would try your approach and return to you ASAP. – Hien Tran Nov 22 '17 at 23:58
  • Hey @HienTran did you get a chance to explore this further? – Antoni4 Nov 29 '17 at 18:37
  • Hi @Antoni4, I did try it, but then right now if I am running in the debug mode -> 'DEV', the red screen appears -> blocking my test -> failed. Now I have to do the release mode to make sure the test working properly. So I actually am blocked by this. – Hien Tran Dec 14 '17 at 00:51
  • Hi. Are you using env vars or react-native-repackager to solve this? I think all you supposed to do is disable/remove the animation styles during build type for testing. I'll post an example later. – Antoni4 Dec 14 '17 at 01:15
  • It would be great, I have never built for testing before so I am unsure what you meant so an example might be a great help. – Hien Tran Dec 14 '17 at 02:06
  • I updated the answer based on our comments and provided a working example too – Antoni4 Dec 15 '17 at 02:49
  • thanks heap, I will follow up the answer and return to you as soon as possible. – Hien Tran Dec 21 '17 at 04:49
  • awesome, let me know if you get stack at any stage – Antoni4 Dec 21 '17 at 10:39
1

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

Ahmed Khashaba
  • 788
  • 7
  • 14