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 构建在 android 和 ios 上都成功了,我已经设法让模拟按预期工作,但 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
找不到 detox 应用程序,您是不是运行了“./gradlew assembleAndroidTest”?