wix / Detox

Gray box end-to-end testing and automation framework for mobile apps
https://wix.github.io/Detox/
MIT License
11.15k stars 1.92k forks source link

Detox can't find custom component that has been given testId #2000

Closed jadeirving9 closed 4 years ago

jadeirving9 commented 4 years ago

Hi, I am trying to implement e2e testing on my React Native app.

Detox builds succeed on both Android and iOS and I have managed to get mocks working as expected, but detox can't seem to locate any of the components on the page I want to test. All of the components I am trying to locate have a testId, and when I try to test on both Android and iOS the ids are present in the view hierarchy.

This is the error I am receiving:

`androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'at least 75 percent of the view's area is displayed to the user.' doesn't match the selected view. Expected: at least 75 percent of the view's area is displayed to the user. Got: null

    at dalvik.system.VMStack.getThreadStackTrace(Native Method)
    at java.lang.Thread.getStackTrace(Thread.java:1538)
    at androidx.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:96)
    at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:59)
    at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:324)
    at androidx.test.espresso.ViewInteraction.check(ViewInteraction.java:306)
    at com.wix.detox.espresso.DetoxAssertion.assertMatcher(DetoxAssertion.java:32)
    at java.lang.reflect.Method.invoke(Native Method)
    at org.apache.commons.lang3.reflect.MethodUtils.invokeStaticMethod(MethodUtils.java:443)
    at org.apache.commons.lang3.reflect.MethodUtils.invokeStaticMethod(MethodUtils.java:405)
    at com.wix.invoke.types.ClassTarget.execute(ClassTarget.java:23)
    at com.wix.invoke.types.Target.invoke(Target.java:59)
    at com.wix.invoke.MethodInvocation.invoke(MethodInvocation.java:35)
    at com.wix.invoke.MethodInvocation.invoke(MethodInvocation.java:26)
    at com.wix.invoke.MethodInvocation.invoke(MethodInvocation.java:20)
    at com.wix.detox.InvokeActionHandler.handle(DetoxActionHandlers.kt:52)
    at com.wix.detox.DetoxManager$4.run(DetoxManager.java:121)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:164)
    at com.wix.detox.Detox$1.run(Detox.java:135)
    at java.lang.Thread.run(Thread.java:764)
Caused by: junit.framework.AssertionFailedError: 'at least 75 percent of the view's area is displayed to the user.' doesn't match the selected view.
Expected: at least 75 percent of the view's area is displayed to the user.
     Got: null

    at androidx.test.espresso.matcher.ViewMatchers.assertThat(ViewMatchers.java:540)
    at com.wix.detox.espresso.assertion.ViewAssertions$MatchesViewAssertion.check(ViewAssertions.java:52)
    at androidx.test.espresso.ViewInteraction$SingleExecutionViewAssertion.check(ViewInteraction.java:425)
    at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:288)
    at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:272)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6494)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Check device logs for full details!

   9 | 
  10 |   it('should have physiotherapy button', async () => {
> 11 |     await expect(element(by.id('Physiotherapy button'))).toBeVisible();
     |                                                             ^
  12 |   });
  13 | 
  14 |   it('should have reminders button', async () => {

  at Client.execute (../node_modules/detox/src/client/Client.js:92:28)
  at InvocationManager.execute (../node_modules/detox/src/invoke.js:11:39)
  at MatcherAssertionInteraction.execute (../node_modules/detox/src/android/expect.js:128:35)
  at ExpectElement.toBeVisible (../node_modules/detox/src/android/expect.js:275:112)
  at _callee3$ (dashboard.spec.js:11:61)
  at tryCatch (../node_modules/regenerator-runtime/runtime.js:45:40)
  at Generator.invoke [as _invoke] (../node_modules/regenerator-runtime/runtime.js:271:22)
  at Generator.prototype.<computed> [as next] (../node_modules/regenerator-runtime/runtime.js:97:21)
  at tryCatch (../node_modules/regenerator-runtime/runtime.js:45:40)
  at invoke (../node_modules/regenerator-runtime/runtime.js:135:20)
  at ../node_modules/regenerator-runtime/runtime.js:170:11
  at callInvokeWithMethodAndArg (../node_modules/regenerator-runtime/runtime.js:169:16)
  at AsyncIterator.enqueue (../node_modules/regenerator-runtime/runtime.js:192:13)
  at AsyncIterator.prototype.<computed> [as next] (../node_modules/regenerator-runtime/runtime.js:97:21)
  at Object.exports.async (../node_modules/regenerator-runtime/runtime.js:216:14) `

Here is the test:

`describe('Dashboard', () => { beforeEach(async () => { await device.reloadReactNative(); });

it('should have physiotherapy button', async () => { await expect(element(by.id('Physiotherapy button'))).toBeVisible(); });`

and here is the component I am trying to test:

`/ Dashboard with custom buttons to navigate between pages / import React, {Component} from 'react'; import {View, StyleSheet} from 'react-native'; import SplashScreen from 'react-native-splash-screen'; import DashboardButton from '../layout/DashboardButton'; import {connect} from 'react-redux'; import * as actions from '../../actions/index'; import {NavigationEvents} from 'react-navigation';

export class Dashboard extends Component { constructor(props) { super(props);

// present log in page if user is not logged in
if (props.loggedIn !== true) {
  this.login();
}

// show dashboard
SplashScreen.hide();

}

login() { this.props.addDevice('testId', 'testToken'); this.props.loginUser('testId', 'testToken'); this.props.loadInitialReminders(); this.props.loadInitialDiaryEntries(); }

render() { return ( <View testId={'Dashboard'} accessible={true} style={styles.mainContainer}> <NavigationEvents onDidFocus={() => { if (this.props.loggedIn !== true) { this.login(); } }} />

    <DashboardButton
      testId={'Physiotherapy button'}
      accessibilityLabel={'Physiotherapy button'}
      accessibilityHint={
        'Navigates to the Physiotherapy exercise categories screen'
      }
      disabled={!this.props.loggedIn}
      title="PHYSIOTHERAPY"
      customClick={() =>
        this.props.navigation.navigate('PhysiotherapyExerciseCategories')
      }
    />
    <DashboardButton
      testId={'Reminders button'}
      accessibilityLabel={'Reminders button'}
      accessibilityHint={'Navigates to the Reminders screen'}
      disabled={!this.props.loggedIn}
      title="REMINDERS"
      customClick={() => this.props.navigation.navigate('Reminders')}
    />
    <DashboardButton
      testId={'Diary button'}
      accessibilityLabel={'Diary button'}
      accessibilityHint={'Navigates to the Diary screen'}
      disabled={!this.props.loggedIn}
      title="DIARY"
      customClick={() => this.props.navigation.navigate('Diary')}
    />
  </View>
);

} }

const mapStateToProps = state => { return { loggedIn: state.authorisationReducer.loggedIn, reminders: state.remindersReducer.reminders, notificationsSet: state.remindersReducer.notificationsSet, }; };

export default connect( mapStateToProps, actions, )(Dashboard);

const styles = StyleSheet.create({ mainContainer: { flex: 1, backgroundColor: 'white', flexDirection: 'column', }, });`

As you can see I am looking for a custom component but that is simply wrapped around a TouchableOpacity which has the prop testId={props.testId}. I have also tried giving the main view in the Dashboard component an ID and searching for that, but still no luck.

I have tried searching by label and by id.

I also read that it could be a react native issue as they nest components so detox won't be able to find the component. Is this the case? Here's my package.json file to show you the versions I am using:

{ "name": "XXX", "version": "1.0.0", "private": true, "description": "XXX", "author": "XXX", "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", "test": "jest", "lint": "eslint ." }, "dependencies": { "@bam.tech/react-native-make": "^1.0.3", "@fortawesome/fontawesome-svg-core": "^1.2.25", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/react-native-fontawesome": "^0.1.0", "@react-native-community/async-storage": "^1.8.1", "@react-native-community/cli": "^4.0.0", "@react-native-community/cli-platform-android": "^4.6.3", "@react-native-community/datetimepicker": "^2.2.1", "@react-native-community/netinfo": "^5.5.1", "axios": "^0.18.1", "bcryptjs": "^2.4.3", "body-parser": "^1.19.0", "classnames": "^2.2.6", "concurrently": "^4.1.2", "enzyme-adapter-react-16": "^1.15.2", "express": "^4.17.1", "is-empty": "^1.2.0", "jetifier": "^1.6.5", "jsonwebtoken": "^8.5.1", "jwt-decode": "^2.2.0", "lodash.uniqueid": "^4.0.1", "moment": "^2.24.0", "mongoose": "^5.7.12", "node-schedule": "^1.3.2", "passport": "^0.4.0", "passport-jwt": "^4.0.0", "react": "16.9.0", "react-dom": "^16.12.0", "react-native": "0.61.5", "react-native-auth0": "^2.3.0", "react-native-base64": "0.0.2", "react-native-elements": "^1.2.7", "react-native-firebase": "^5.6.0", "react-native-gesture-handler": "^1.5.0", "react-native-modal": "^11.5.4", "react-native-modal-selector": "^1.1.5", "react-native-paper": "^3.2.1", "react-native-render-html": "^4.2.0", "react-native-screens": "^2.0.0-beta.2", "react-native-splash-screen": "^3.0.6", "react-native-svg": "^11.0.1", "react-native-uuid-generator": "^6.1.1", "react-native-vector-icons": "^6.6.0", "react-native-webview": "^8.1.2", "react-navigation": "^4.0.10", "react-navigation-stack": "^1.10.3", "react-redux": "^5.1.2", "react-router-dom": "^4.3.1", "react-scripts": "^3.3.0", "redux": "^4.0.4", "redux-thunk": "^2.3.0", "serialize-javascript": "^2.1.2", "typescript": "^3.7.2", "validator": "^10.11.0", "xss": "^1.0.6" }, "devDependencies": { "@babel/core": "^7.6.2", "@babel/preset-env": "^7.1.0", "@babel/preset-flow": "^7.8.3", "@babel/preset-react": "^7.0.0", "@babel/runtime": "^7.6.2", "@bam.tech/react-native-make": "^1.0.3", "@react-native-community/eslint-config": "^0.0.5", "babel-jest": "^24.9.0", "detox": "^16.1.1", "dotenv-webpack": "^1.7.0", "enzyme": "^3.11.0", "eslint": "^6.5.1", "jest": "^25.3.0", "metro-react-native-babel-preset": "^0.56.0", "nodemon": "^2.0.0", "react-test-renderer": "16.9.0", "redux-mock-store": "^1.5.4" }, "jest": { "preset": "react-native", "setupFiles": [ "./node_modules/react-native-gesture-handler/jestSetup.js", "./node_modules/react-test-renderer/cjs/react-test-renderer.development.js" ], "setupFilesAfterEnv": [ "./__mocks__/async-storage.js", "./__mocks__/auth0.js", "./__mocks__/firebase.js", "./__mocks__/net-info.js", "./__mocks__/react-native-navigation.js", "./__mocks__/react-native-splash-screen.js" ], "transformIgnorePatterns": [ "./node_modules/?!(react-navigation|react-native-gesture-handler)" ] }, "detox": { "configurations": { "ios.sim.debug": { "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/Tulip.app", "build": "RN_SRC_EXT=e2e.js xcodebuild -workspace ios/clean.xcworkspace -scheme Tulip -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", "type": "ios.simulator", "device": { "type": "iPhone 11 Pro" } }, "android.emu.debug": { "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk", "build": "RN_SRC_EXT=e2e.js cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..", "type": "android.emulator", "device": { "avdName": "Pixel_2_XL_API_27" } }, "android.emu.release": { "binaryPath": "android/app/build/outputs/apk/release/app-release.apk", "build": "RN_SRC_EXT=e2e.js cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..", "type": "android.emulator", "device": { "avdName": "Pixel_2_XL_API_27" } } }, "test-runner": "jest" } }

support[bot] commented 4 years ago

We use the issue tracker exclusively for bug reports and feature requests. This issue appears to be a general usage or support question. Instead, please ask a question on Stack Overflow with the detox tag.

Feel free to post your Stack Overflow question here for more visibility. We'll take a look at it.

For issues with Expo apps, it is most likely not an issue with Detox itself, but with the Expo runtime or with incorrect Detox setup. For support on how to use Detox with Expo, you should contact the Expo team or the Expo community.

For more information on bots in this repository, read this discussion.