【问题标题】:E2E: Select an image from a UIImagePickerController with Wix DetoxE2E:使用 Wix Detox 从 UIImagePickerController 中选择图像
【发布时间】:2018-06-20 20:45:33
【问题描述】:
描述

我需要编写一个 e2e 测试,在某些时候它必须在 UIImagePickerController 中选择一个图像,我尝试使用 element(by.type('UIImagePickerController')). tapAtPoint() 而没有用。我需要一种选择图像的方法。我找到了一个 way 来做原生测试。

对于我来说,模拟也不是一种选择,因为我使用的是 react-native-repackeger 需要的更高版本。

重现步骤
  • 与任何使用图像选择器的应用程序一起使用

  • 尝试使用element(by.type('UIImagePickerController')).tapAtPoint({ x: 50, y: 200 })

Detox、Node、Device、Xcode 和 macOS 版本
  • 排毒:6.0.2
  • 节点:8.9.0
  • 设备:iOS 模拟器 6s
  • Xcode:9.2
  • macOS:10.13.1
  • React-Native:0.46.4
设备和详细排毒日志

没有日志,设备点击了正确的位置,但点击没有效果。

【问题讨论】:

    标签: ios react-native react-native-ios e2e-testing detox


    【解决方案1】:

    注意到最初的问题指出,在所提供的案例中模拟不是一个选项,但我在搜索解决方案时遇到了几次 Stack Overflow 问题,并想分享我最终针对我的情况提出的问题。

    我能够通过在我自己的导出中包装 react-native-image-picker 来绕过 e2e 测试的限制:

    ImagePicker.js

    import ImagePicker from 'react-native-image-picker';
    
    export default ImagePicker;
    

    然后使用自定义扩展创建一个模拟(即e2e.js):

    ImagePicker.e2e.js

    const mockImageData = '/9j/4AAQSkZ...MORE BASE64 DATA OF CUTE KITTENS HERE.../9k=';
    
    export default {
      showImagePicker: function showImagePicker(options, callback) {
        if (typeof options === 'function') {
          callback = options;
        }
    
        callback({
          data: mockImageData,
        });
      },
    };
    

    最后,配置 Metro 捆绑器以优先考虑您的自定义扩展:

    [项目根目录]/rn-cli.config.js

    const defaultSourceExts = require('metro-config/src/defaults/defaults')
      .sourceExts;
    
    module.exports = {
      resolver: {
        sourceExts: process.env.RN_SRC_EXT
          ? process.env.RN_SRC_EXT.split(',').concat(defaultSourceExts)
          : defaultSourceExts,
      },
    };
    

    然后使用设置为自定义扩展的RN_SRC_EXT 环境变量运行:

    RN_SRC_EXT=e2e.js react-native start
    

    请参阅Detox Mocking Guide 了解更多信息。

    【讨论】:

      【解决方案2】:

      不确定这是否相关,但对于 iOS 11,我什至无法在 Debug View Hierarchy 中看到那些原生视图类型。

      但是,对于 iOS 910,我会这样解决问题:

      it('select first image from camera roll', async () => {
        // select a photo
        await element(by.id('select_photo')).tap();
        // Choose from Library...
        await element(by.traits(['button']).and(by.type('_UIAlertControllerActionView'))).atIndex(1).tap();
        // select Cemara Roll, use index 0 for Moments
        await element(by.type('UITableViewCellContentView')).atIndex(1).tap();
        // select first image
        await element(by.type('PUPhotoView')).atIndex(0).tap();
      });
      

      使用不同的原生视图类型和可访问性特征可能还有许多其他可能性来解决这个问题。

      我只是使用react-native-image-picker 提供的示例来测试上面的代码:

      import React from 'react';
      import {
        AppRegistry,
        StyleSheet,
        Text,
        View,
        PixelRatio,
        TouchableOpacity,
        Image,
      } from 'react-native';
      
      import ImagePicker from 'react-native-image-picker';
      
      export default class App extends React.Component {
      
        state = {
          avatarSource: null,
          videoSource: null
        };
      
        selectPhotoTapped() {
          const options = {
            quality: 1.0,
            maxWidth: 500,
            maxHeight: 500,
            storageOptions: {
              skipBackup: true
            }
          };
      
          ImagePicker.showImagePicker(options, (response) => {
            console.log('Response = ', response);
      
            if (response.didCancel) {
              console.log('User cancelled photo picker');
            }
            else if (response.error) {
              console.log('ImagePicker Error: ', response.error);
            }
            else if (response.customButton) {
              console.log('User tapped custom button: ', response.customButton);
            }
            else {
              let source = { uri: response.uri };
      
              // You can also display the image using data:
              // let source = { uri: 'data:image/jpeg;base64,' + response.data };
      
              this.setState({
                avatarSource: source
              });
            }
          });
        }
      
        selectVideoTapped() {
          const options = {
            title: 'Video Picker',
            takePhotoButtonTitle: 'Take Video...',
            mediaType: 'video',
            videoQuality: 'medium'
          };
      
          ImagePicker.showImagePicker(options, (response) => {
            console.log('Response = ', response);
      
            if (response.didCancel) {
              console.log('User cancelled video picker');
            }
            else if (response.error) {
              console.log('ImagePicker Error: ', response.error);
            }
            else if (response.customButton) {
              console.log('User tapped custom button: ', response.customButton);
            }
            else {
              this.setState({
                videoSource: response.uri
              });
            }
          });
        }
      
        render() {
          return (
            <View style={styles.container}>
              <TouchableOpacity testID="select_photo" onPress={this.selectPhotoTapped.bind(this)}>
                <View style={[styles.avatar, styles.avatarContainer, {marginBottom: 20}]}>
                { this.state.avatarSource === null ? <Text>Select a Photo</Text> :
                  <Image style={styles.avatar} source={this.state.avatarSource} />
                }
                </View>
              </TouchableOpacity>
      
              <TouchableOpacity onPress={this.selectVideoTapped.bind(this)}>
                <View style={[styles.avatar, styles.avatarContainer]}>
                  <Text>Select a Video</Text>
                </View>
              </TouchableOpacity>
      
              { this.state.videoSource &&
                <Text style={{margin: 8, textAlign: 'center'}}>{this.state.videoSource}</Text>
              }
            </View>
          );
        }
      
      }
      
      const styles = StyleSheet.create({
        container: {
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center',
          backgroundColor: '#F5FCFF'
        },
        avatarContainer: {
          borderColor: '#9B9B9B',
          borderWidth: 1 / PixelRatio.get(),
          justifyContent: 'center',
          alignItems: 'center'
        },
        avatar: {
          borderRadius: 75,
          width: 150,
          height: 150
        }
      });
      
      AppRegistry.registerComponent('example', () => App);
      

      【讨论】:

      • 对于 iOS 11,Apple 在进程外渲染视图层次结构。
      • 这正是@LeoNatan 的问题,我最终模拟了组件以获得工作测试!
      • 非常感谢@Antoni4 的努力和帮助!
      • 有没有办法在 react-native 中控制台记录本机屏幕的完整视图层次结构。
      • 如何检查原生屏幕的视图层次结构?
      猜你喜欢
      • 2015-06-11
      • 1970-01-01
      • 2012-10-17
      • 1970-01-01
      • 2012-07-24
      • 2018-11-17
      • 1970-01-01
      • 1970-01-01
      • 2017-10-30
      相关资源
      最近更新 更多