rn-testid-babel-plugin
v1.0.1
Published
Babel plugin to auto-inject testID prop into React Native JSX elements for E2E testing (Detox, Appium, Maestro)
Maintainers
Readme
rn-testid-babel-plugin
自动为 React Native JSX 元素注入
testIDprop 的 Babel 插件,用于 E2E 测试(Detox、Appium、Maestro)。
特性
- 零配置:默认自动为所有 React Native 内置组件注入
testID - 自定义 UI 库:支持配置组件名前缀(如
RNE、NB、Paper) - FlatList 感知:自动标记
renderItem内部元素,支持运行时去重 - 确定性的 ID:同一源码文件始终生成相同的 testID
- 运行时兜底(可选):
createElementmonkey-patch 处理动态元素 - TypeScript:完整类型定义
安装
npm install --save-dev rn-testid-babel-plugin
# 或
pnpm add -D rn-testid-babel-plugin快速开始
Expo 项目
在 babel.config.js 中添加插件:
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'], // Expo 默认 preset
plugins: [
'rn-testid-babel-plugin',
],
};
};⚠️ 重要:修改 babel.config.js 后必须清除缓存:
npx expo start -c
裸 React Native 项目 (CLI)
module.exports = {
presets: ['module:metro-react-native-babel-preset'], // RN CLI 默认 preset
plugins: [
'rn-testid-babel-plugin',
],
};⚠️ 重要:修改 babel.config.js 后必须清除 Metro 缓存:
npx react-native start --reset-cache或删除
node_modules/.cache目录后重新启动。
搞定!所有 React Native 内置组件将自动获得 testID prop:
// 编译前
<View style={styles.container}>
<Text>Hello World</Text>
<TouchableOpacity onPress={handlePress}>
<Text>Click me</Text>
</TouchableOpacity>
</View>
// 编译后
<View testID="View-0" style={styles.container}>
<Text testID="Text-1">Hello World</Text>
<TouchableOpacity testID="TouchableOpacity-2" onPress={handlePress}>
<Text testID="Text-3">Click me</Text>
</TouchableOpacity>
</View>配置自定义 UI 库前缀
module.exports = {
plugins: [
['rn-testid-babel-plugin', {
customPrefixes: ['RNE', 'NB', 'Paper'],
}],
],
}运行时兜底(可选)
// App.tsx — 必须在任何 React 组件之前导入!
import { setupRuntimeTestIDs } from 'rn-testid-babel-plugin/runtime'
if (__DEV__) {
setupRuntimeTestIDs({ debug: true })
}配置项
| 选项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| attrName | string | 'testID' | 自定义属性名 |
| customPrefixes | string[] | [] | 额外组件名前缀 |
| includeRNComponents | boolean | true | 是否包含 RN 内置组件 |
| injectFlatListItems | boolean | true | 标记 FlatList renderItem 内部元素 |
| exclude | (string \| RegExp)[] | [] | 跳过文件 |
| include | (string \| RegExp)[] | [] | 仅处理文件 |
| debug | boolean | false | 输出注入日志 |
| formatTestID | function | — | 自定义 testID 格式 |
完整配置示例
module.exports = {
plugins: [
['rn-testid-babel-plugin', {
attrName: 'testID',
includeRNComponents: true,
injectFlatListItems: true,
customPrefixes: ['RNE', 'NB', 'Paper'],
exclude: [/\.test\.(tsx|jsx)$/, /__tests__/],
debug: process.env.NODE_ENV !== 'production',
formatTestID(tag, counter, meta) {
const suffix = meta.isInFlatList ? '-flatlist-' : ''
return `e2e-${tag}-${counter}${suffix}`
},
}],
],
}testID 格式
| 场景 | 格式 | 示例 |
|------|------|------|
| RN 内置组件 | {Name}-{counter} | View-0, Text-5 |
| 自定义前缀组件 | {PrefixName}-{counter} | RNEButton-3 |
| FlatList renderItem | {Name}-{counter}-flatlist- | View-8-flatlist- |
| FlatList 运行时去重 | {Name}-{counter}-flatlist-:{N} | View-8-flatlist-:0 |
| 运行时注入(无编译期) | {Name}-runtime-{counter} | View-runtime-1 |
| 用户手动指定 | 保持不变 | my-custom-id |
E2E 测试集成
Detox
await element(by.id('View-0')).tap()
await element(by.id('TouchableOpacity-2')).tap()Appium
const el = await driver.elementById('View-0')
await el.click()Maestro
- tapOn:
id: "TouchableOpacity-2"工作原理
详见 DESIGN.md。
覆盖的 React Native 内置组件
View, Text, Image, ImageBackground, ScrollView, FlatList, SectionList, VirtualizedList, TextInput, TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, TouchableNativeFeedback, Pressable, Button, Modal, ActivityIndicator, Switch, RefreshControl, SafeAreaView, KeyboardAvoidingView, StatusBar, Slider, Picker, DatePickerIOS, ProgressViewIOS, ProgressBarAndroid, DrawerLayoutAndroid
故障排查
看不到 testID?
1. 清除 Metro 缓存(最常见原因)
# Expo
npx expo start -c
# 裸 React Native CLI
npx react-native start --reset-cache2. 确认插件已加载
在 babel.config.js 中临时开启 debug 模式:
plugins: [
['rn-testid-babel-plugin', { debug: true }],
],重新构建后,终端会输出每个注入的日志:
[rn-testid] View → testID="View-0" (App.tsx:12)
[rn-testid] Text → testID="Text-1" (App.tsx:14)3. 验证 JS Bundle 产物
搜索编译后的 bundle 文件确认 testID 字符串存在:
# Expo
npx expo export --platform ios
grep -o 'testID="View-' dist/_expo/static/js/ios/*.js | head
# 裸 React Native CLI (iOS)
npx react-native bundle --platform ios --dev false --entry-file index.js --bundle-output /tmp/bundle.js
grep -o 'testID="View-' /tmp/bundle.js | head4. 在 React DevTools 中查看
组件树的右侧 Props 面板中应该显示 testID prop。注意 testID 是一个普通的 React prop,不是 HTML attribute,在 RN 的 Native 层中它被映射到 accessibilityIdentifier (iOS) / testID (Android)。
5. 检查文件是否被排除
确保你的组件文件没有匹配 exclude 选项中的模式。
Expo 特殊说明
- Expo Go vs Development Build:两者都支持 Babel 插件,无需特殊处理
- Monorepo:如果使用 pnpm/yarn workspaces,确保
rn-testid-babel-plugin的软链接正确,可使用npx expo start -c重建依赖图
License
MIT
