在本机组件上设置Testid

时间:2020-02-23 01:38:27

标签: react-native detox

我正在使用Detox,并使用以下UI对tipsi-stripe软件包进行集成测试:

Credit Card Sample

我的测试如下:

    ...
    await expect(element(by.text('10'))).toBeVisible()
    await expect(element(by.text('Give $10'))).toBeNotVisible()
    await element(by.text('10')).tap()
    await expect(element(by.text('Give $10'))).toBeVisible()
    await expect(element(by.id('cc-card-input'))).toBeVisible()
    await element(by.id('cc-card-input')).typeText("4242424242424242")
    ...

最后一行失败:

  ● Signin › allows the person to make a payment

    Error performing 'Click to focus & type text (4242424242424242)' on view '(with tag value: is "cc-card-input" and view has effective visibility=VISIBLE)'.
    > 28 |     await element(by.id('cc-card input')).typeText("4242424242424242")

失败表明无法输入cc卡输入。

从相关库中获取组件:

import React, { Component } from 'react'
import {
  requireNativeComponent,
  findNodeHandle,
  StyleSheet,
  View,
  TouchableWithoutFeedback,
  ViewPropTypes,
  Platform,
  Text,
} from 'react-native'
import PropTypes from 'prop-types'
import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState'

const FieldStylePropType = PropTypes.shape({
  ...ViewPropTypes.style,
  color: PropTypes.string,
})

/**
 * @typedef {Object} PaymentCardTextFieldNativeEventParams
 * @property {string} number -- card number as a string
 * @property {number} expMonth
 * @property {number} expYear
 * @property {string} cvc
 */

/**
 * @typedef {Object} PaymentCardTextFieldNativeEvent
 * @property {boolean}  valid
 * @property {PaymentCardTextFieldNativeEventParams} params
 */

/**
 * @callback OnChangeCallback
 * @param {PaymentCardTextFieldNativeEvent} params
 */

/**
 * // TODO: Get a more precise type here, not sure how to JSDoc react-native Style Types
 * @typedef {Object} PaymentComponentTextFieldStyleProp
 */

/**
 * A Component that collects the CardNumber, ExpirationDate, and CVC all in one.
 * @typedef {Object} PaymentCardTextFieldProps
 *
 * @property {string} expirationPlaceholder
 * @property {string} numberPlaceholder
 * @property {string} cvcPlaceholder
 * @property {boolean} disabled
 * @property {OnChangeCallback} onChange
 * @property {PaymentComponentTextFieldStyleProp} style
 *
 * @property {string} cursorColor iOS-only!
 * @property {string} textErrorColor iOS-only!
 * @property {string} placeholderColor iOS-only!
 * @property {"default"|"light"|"dark"} keyboardAppearance iOS-only!
 *
 * @property {boolean} setEnabled Android-only!
 * @property {string} backgroundColor Android-only!
 * @property {string} cardNumber Android-only!
 * @property {string} expDate Android-only!
 * @property {string} securityCode Android-only!
 */

const NativePaymentCardTextField = requireNativeComponent('TPSCardField', PaymentCardTextField, {
  nativeOnly: {
    borderColor: true,
    borderWidth: true,
    cornerRadius: true,
    textColor: true,
    fontFamily: true,
    fontWeight: true,
    fontStyle: true,
    fontSize: true,
    enabled: true,
    onChange: true,
    params: true, // Currently iOS only
    keyboardAppearance: true, // iOS only
  },
})

/**
 * @type {import('react').ComponentClass<PaymentCardTextFieldProps>}
 */
export default class PaymentCardTextField extends Component {
  static propTypes = {
    ...ViewPropTypes,
    style: FieldStylePropType,

    // Common
    expirationPlaceholder: PropTypes.string,
    numberPlaceholder: PropTypes.string,
    cvcPlaceholder: PropTypes.string,
    disabled: PropTypes.bool,
    onChange: PropTypes.func,

    ...Platform.select({
      ios: {
        cursorColor: PropTypes.string,
        textErrorColor: PropTypes.string,
        placeholderColor: PropTypes.string,
        keyboardAppearance: PropTypes.oneOf(['default', 'light', 'dark']),
      },
      android: {
        setEnabled: PropTypes.bool,
        backgroundColor: PropTypes.string,
        cardNumber: PropTypes.string,
        expDate: PropTypes.string,
        securityCode: PropTypes.string,
      },
    }),
  }

  static defaultProps = {
    ...View.defaultProps,
  }

  valid = false // eslint-disable-line react/sort-comp
  params = {
    number: '',
    expMonth: 0,
    expYear: 0,
    cvc: '',
  }

  componentWillUnmount() {
    if (this.isFocused()) {
      this.blur()
    }
  }

  isFocused = () => TextInputState.currentlyFocusedField() === findNodeHandle(this.cardTextFieldRef)

  focus = () => {
    TextInputState.focusTextInput(findNodeHandle(this.cardTextFieldRef))
  }

  blur = () => {
    TextInputState.blurTextInput(findNodeHandle(this.cardTextFieldRef))
  }

  handlePress = () => {
    this.focus()
  }

  handleChange = (event) => {
    const { onChange, onParamsChange } = this.props
    const { nativeEvent } = event

    this.valid = nativeEvent.valid
    this.params = nativeEvent.params

    if (onChange) {
      // Send the intended parameters back into JS
      onChange({ ...nativeEvent })
    }

    if (onParamsChange) {
      onParamsChange(nativeEvent.valid, nativeEvent.params)
    }
  }

  setCardTextFieldRef = (node) => {
    this.cardTextFieldRef = node
  }

  // Previously on iOS only
  setParams = (params) => {
    this.cardTextFieldRef.setNativeProps({ params })
  }

  render() {
    const {
      style,
      disabled,
      expDate,
      cardNumber,
      securityCode,
      cursorColor,
      textErrorColor,
      placeholderColor,
      numberPlaceholder,
      expirationPlaceholder,
      cvcPlaceholder,
      keyboardAppearance,
      ...rest
    } = this.props

    const {
      borderColor,
      borderWidth,
      borderRadius,
      fontFamily,
      fontWeight,
      fontStyle,
      fontSize,
      overflow,
      backgroundColor,
      color,
      ...fieldStyles
    } = StyleSheet.flatten(style)

    const viewStyles = {
      overflow,
      width: fieldStyles.width,
    }

    const commonStyles = {
      borderColor,
      borderWidth,
      borderRadius,
      backgroundColor,
    }

    return (
      <View style={[commonStyles, viewStyles]}>
        <TouchableWithoutFeedback
          style={{ borderWidth: 2, borderColor: 'red'}}
          rejectResponderTermination
          testID={rest.testID}
          onPress={this.handlePress}
          accessible={rest.accessible}
          accessibilityLabel={rest.accessibilityLabel}
          accessibilityTraits={rest.accessibilityTraits}
        >
          <NativePaymentCardTextField
            ref={this.setCardTextFieldRef}
            style={[styles.field, fieldStyles]}
            borderColor="transparent"
            borderWidth={0}
            cornerRadius={borderRadius}
            textColor={color}
            fontFamily={fontFamily}
            fontWeight={fontWeight}
            fontStyle={fontStyle}
            fontSize={fontSize}
            enabled={!disabled}
            numberPlaceholder={numberPlaceholder}
            expirationPlaceholder={expirationPlaceholder}
            cvcPlaceholder={cvcPlaceholder}
            onChange={this.handleChange}
            // iOS only
            cursorColor={cursorColor}
            textErrorColor={textErrorColor}
            placeholderColor={placeholderColor}
            keyboardAppearance={keyboardAppearance}
            // Android only
            cardNumber={cardNumber}
            expDate={expDate}
            securityCode={securityCode}
          />
        </TouchableWithoutFeedback>
      </View>
    )
  }
}

您会看到testID被分配给TouchableWithoutFeedBack组件-而不是输入的组件。

我尝试过

  • NativePaymentCardTextField上设置testID
  • 设置accessible={true},并在父组件上设置了accessibilityId

View Hierarchy from Detox

我在两者上都遇到相同的错误。

要在此组件中输入信用卡号(如果可能的话),我需要做些什么

谢谢。

1 个答案:

答案 0 :(得分:0)

从层次结构来看,似乎cc-card-input测试ID被分配给包装器而不是文本字段本身。特别是,本节显示:

    +----------->CreditCardForm{id=243, visibility=VISIBLE, width=787, height=115, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.view.ViewGroup$LayoutParams@ad170a6, tag=cc-card-input, root-is-layout-requested=false, has-input-connection=false, x=3.0, y=3.0, child-count=2} 
    |
    +------------>LinearLayout{id=2131296373, res-name=cc_form_layout, visibility=VISIBLE, width=703, height=69, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.RelativeLayout$LayoutParams@ce0b53d, tag=null, root-is-layout-requested=false, has-input-connection=false, x=42.0, y=21.0, child-count=2} 
    |
    +------------->FrameLayout{id=-1, visibility=VISIBLE, width=94, height=53, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, layout-params=android.widget.LinearLayout$LayoutParams@5c61800, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=8.0, child-count=2} 
    |
    +-------------->ImageView{id=-1, visibility=VISIBLE, width=84, height=53, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.FrameLayout$LayoutParams@a86839, tag=null, root-is-layout-requested=false, has-input-connection=false, x=10.0, y=0.0} 
    |
    +-------------->ImageView{id=-1, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.FrameLayout$LayoutParams@c8bee7e, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0} 
    |
    +------------->CreditCardEntry{id=2131296370, res-name=cc_entry, visibility=VISIBLE, width=609, height=69, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=true, is-selected=false, layout-params=android.widget.LinearLayout$LayoutParams@e74b42c, tag=null, root-is-layout-requested=false, has-input-connection=false, x=94.0, y=0.0, child-count=1} 
    |
    +-------------->LinearLayout{id=2131296371, res-name=cc_entry_internal, visibility=VISIBLE, width=1632, height=63, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.FrameLayout$LayoutParams@1f4748a, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=4} 
    |
    +--------------->CreditCardText{id=2131296368, res-name=cc_card, visibility=VISIBLE, width=1080, height=63, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=true, is-focusable=true, is-layout-requested=true, is-selected=false, layout-params=android.widget.LinearLayout$LayoutParams@d53dafb, tag=null, root-is-layout-requested=false, has-input-connection=true, editor-info=[inputType=0x2 imeOptions=0x10000006 privateImeOptions=null actionLabel=null actionId=0 initialSelStart=0 initialSelEnd=0 initialCapsMode=0x0 hintText=1234 5678 9012 3456 label=null packageName=null fieldId=0 fieldName=null extras=null ], x=0.0, y=0.0, text=, hint=1234 5678 9012 3456, input-type=2, ime-target=true, has-links=false} 
    |
    +--------------->TextView{id=2131296374, res-name=cc_four_digits, visibility=VISIBLE, width=104, height=63, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.LinearLayout$LayoutParams@28ee718, tag=null, root-is-layout-requested=false, has-input-connection=false, x=1080.0, y=0.0, text=4242, input-type=0, ime-target=false, has-links=false} 

(查看与CreditCardForm视图关联的标签)

在确实可见的情况下,您只能有效typeText()覆盖作为文本输入的视图(即,打开软键盘的位置)。

将测试ID重新定位到可以有效接受输入的视图即可解决它(我很难说哪个是根据层次结构确定的确切视图,因为视图似乎是自定义的。)