Detox 找不到已指定 testId 的自定义组件

Posted

技术标签:

【中文标题】Detox 找不到已指定 testId 的自定义组件【英文标题】:Detox can't find custom component that has been given testId 【发布时间】:2020-07-23 06:00:28 【问题描述】:

我正在尝试在我的 React Native 应用程序上实现 e2e 测试。

Detox 构建在 androidios 上都成功了,我已经设法让模拟按预期工作,但 detox 似乎无法在我要测试的页面上找到任何组件。我尝试定位的所有组件都有一个 testId,当我尝试在 Android 和 iOS 上进行测试时,ID 都存在于视图层次结构中。

这是我在 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();
  );

这是我要测试的组件:

/* 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',
  ,
);

如您所见,我正在寻找一个自定义组件,但它只是简单地包裹在具有 prop testId=props.testId 的 TouchableOpacity 周围。我也尝试为仪表板组件中的主视图提供一个 ID 并搜索它,但仍然没有运气。

我已尝试按标签和 ID 进行搜索。

我还读到这可能是一个反应原生问题,因为它们嵌套了组件,因此 detox 将无法找到该组件。是这样吗?这是我的 package.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】:

我很傻。错字。

测试ID应该写成:testID="Dashboard"

【讨论】:

以上是关于Detox 找不到已指定 testId 的自定义组件的主要内容,如果未能解决你的问题,请参考以下文章

wix/Detox 测试找不到 ANDROID_SDK_ROOT

使用 testID 自动化移动应用程序是好是坏?

找不到 com.wix:detox:+ 的任何匹配项

找不到 detox 应用程序,您是不是运行了“./gradlew assembleAndroidTest”?

在 Azure B2C 的自定义策略中找不到声明 - Saml

具有过滤查询集的组权限的自定义表单