Детокс не может найти пользовательский компонент, которому был присвоен testId - PullRequest
0 голосов
/ 10 апреля 2020

Я пытаюсь внедрить тестирование e2e в моем приложении React Native.

Сборки Detox успешно выполняются как на Android, так и на iOS, и мне удалось заставить mocks работать, как и ожидалось, но detox не может найти какие-либо компоненты на странице, которую я хочу протестировать. Все компоненты, которые я пытаюсь найти, имеют testId, и когда я пытаюсь протестировать как Android, так и iOS, идентификаторы присутствуют в иерархии представления.

Это ошибка, которую я получаю на android:

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)

Вот тест:

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

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

и вот компонент, который я пытаюсь 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',
  },
});

Как видите, я ищу пользовательский компонент, но он просто оборачивается вокруг TouchableOpacity, у которого есть prop testId = {props.testId}. Я также попытался дать основному представлению в компоненте Dashboard идентификатор и выполнить его поиск, но все равно не повезло.

Я попытался выполнить поиск по метке и по идентификатору.

Я также читал, что это может быть естественная проблема, поскольку они вкладывают компоненты, поэтому детокс не сможет найти компонент. Это тот случай? Вот мой пакет. json файл, чтобы показать вам версии, которые я использую:

{
  "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"
  }
}

Если вы знаете способ обойти это или альтернативный подход к тестированию e2e, пожалуйста, дайте мне знать. Спасибо заранее за вашу помощь.

1 Ответ

0 голосов
/ 10 апреля 2020

Я тупой. Опечатка.

Идентификатор теста должен быть записан в виде: testID = "Dashboard"

...