9

Description

I need to write an e2e test that in some point it has to select an image in UIImagePickerController, I tried to use element(by.type('UIImagePickerController')). tapAtPoint() with no use. I need a way to select an image. I have found a way to do it with native tests.

Also mocking isn't an option for me since I use a higher version that the one react-native-repackeger needs.

Steps to Reproduce
  • Use with any application that uses image picker

  • Try to use element(by.type('UIImagePickerController')).tapAtPoint({ x: 50, y: 200 })

Detox, Node, Device, Xcode and macOS Versions
  • Detox: 6.0.2
  • Node: 8.9.0
  • Device: iOS Simulator 6s
  • Xcode: 9.2
  • macOS: 10.13.1
  • React-Native: 0.46.4
Device and verbose Detox logs

There's no logs, the device taps on the right location but the tap doesn't make an effect.

Tareq El-Masri
  • 2,413
  • 15
  • 23

2 Answers2

9

Noticed the original question stated that mocks were not an option in the case presented, but I came across this Stack Overflow question a few times in my searches for a solution and thought to share what I ultimately came up with for my situation.

I was able to get around the limitations for the e2e test by wrapping react-native-image-picker in my own export:

ImagePicker.js

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

export default ImagePicker;

And then creating a mock with a custom extension (i.e. 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,
    });
  },
};

Finally, configure the metro bundler to prioritize your custom extension:

[project root]/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,
  },
};

Then run with the RN_SRC_EXT environment variable set to the custom extension:

RN_SRC_EXT=e2e.js react-native start

See the Detox Mocking Guide for more information.

Kokaubeam
  • 646
  • 6
  • 8
5

Not sure if this is related, but for iOS 11 I can't even see those native view types in the Debug View Hierarchy.

Photos Camera Roll

For iOS 9 and 10 however, I would solve the problem like this:

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();
});

There are probably many other possibilities to solve this problem with different native view types and accessibility traits.

I just used the example provided from react-native-image-picker to test with above code:

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);

preview

Antoni4
  • 2,535
  • 24
  • 37