排毒:检测显示该元素

Detox: detect that element was displayed

我们的应用程序中有一个 toast 组件,它给我们的测试增加了相当多的不稳定因素。 toast 组件显示一个动画 View 4s,然后消失。在很多测试中,我需要检查消息内容是什么才能继续测试。

toast组件的实现代码如下:

// @flow
import * as React from "react"
import { StyleSheet, View, Animated, Dimensions, Text } from "react-native"

import type {
  TextStyle,
  ViewStyle,
} from "react-native/Libraries/StyleSheet/StyleSheet"

import type AnimatedValue from "react-native/Libraries/Animated/src/nodes/AnimatedValue"
import type { CompositeAnimation } from "react-native/Libraries/Animated/src/AnimatedImplementation"
import { AnimationConstants } from "constants/animations"

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    left: 0,
    right: 0,
    elevation: 999,
    alignItems: "center",
    zIndex: 10000,
  },
  content: {
    backgroundColor: "black",
    borderRadius: 5,
    padding: 10,
  },
  text: {
    color: "white",
  },
})

type Props = {
  style: ViewStyle,
  position: "top" | "center" | "bottom",
  textStyle: TextStyle,
  positionValue: number,
  fadeInDuration: number,
  fadeOutDuration: number,
  opacity: number,
}

type State = {
  isShown: boolean,
  text: string | React.Node,
  opacityValue: AnimatedValue,
}

export const DURATION = AnimationConstants.durationShort

const { height } = Dimensions.get("window")

export default class Toast extends React.PureComponent<Props, State> {
  static defaultProps = {
    position: "bottom",
    textStyle: styles.text,
    positionValue: 120,
    fadeInDuration: AnimationConstants.fadeInDuration,
    fadeOutDuration: AnimationConstants.fadeOutDuration,
    opacity: 1,
  }

  isShown: boolean
  duration: number
  callback: Function
  animation: CompositeAnimation
  timer: TimeoutID

  constructor(props: Props) {
    super(props)
    this.state = {
      isShown: false,
      text: "",
      opacityValue: new Animated.Value(this.props.opacity),
    }
  }

  show(text: string | React.Node, duration: number, callback: Function) {
    this.duration = typeof duration === "number" ? duration : DURATION
    this.callback = callback
    this.setState({
      isShown: true,
      text: text,
    })

    this.animation = Animated.timing(this.state.opacityValue, {
      toValue: this.props.opacity,
      duration: this.props.fadeInDuration,
      useNativeDriver: true,
    })
    this.animation.start(() => {
      this.isShown = true
      this.close()
    })
  }

  close(duration?: number) {
    const delay = typeof duration === "undefined" ? this.duration : duration

    if (!this.isShown && !this.state.isShown) return
    this.timer && clearTimeout(this.timer)

    this.timer = setTimeout(() => {
      this.animation = Animated.timing(this.state.opacityValue, {
        toValue: 0.0,
        duration: this.props.fadeOutDuration,
        useNativeDriver: true,
      })
      this.animation.start(() => {
        this.setState({
          isShown: false,
        })
        this.isShown = false
        if (typeof this.callback === "function") {
          this.callback()
        }
      })
    }, delay)
  }

  componentWillUnmount() {
    this.animation && this.animation.stop()
    this.timer && clearTimeout(this.timer)
  }

  render() {
    const { isShown, text, opacityValue } = this.state
    const { position, positionValue } = this.props
    const pos = {
      top: positionValue,
      center: height / 2,
      bottom: height - positionValue,
    }[position]

    if (isShown) {
      return (
        <View style={[styles.container, { top: pos }]}>
          <Animated.View
            style={[
              styles.content,
              { opacity: opacityValue },
              this.props.style,
            ]}
          >
            {React.isValidElement(text) ? (
              text
            ) : (
              <Text style={this.props.textStyle}>{text}</Text>
            )}
          </Animated.View>
        </View>
      )
    }

    return null
  }
}

通常我们会显示 toast 消息 4 秒,但我决定在 e2e 测试中显示它 1.5 秒,以便更快一些。

我正在像这样测试 toast 是否存在:

await expect(element(by.text(text))).toBeVisible()
await waitFor(element(by.text(text))).toBeNotVisible().withTimeout(2000)

然而,在 "toBeVisible" 排毒失败的情况经常发生。我可以在屏幕上看到消息,但出于某种原因,detox 没有看到它。

我应该将消息在屏幕上保留的最短时间是多少,以便排毒检测到它?

在 .circleCI 上,我们录制失败测试的视频。当测试失败并显示 "cannot find element" 并且我观看了视频时,我清楚地看到屏幕上出现了 toast,但 detox 找不到它。

我仍然不确定是否有更好的方法,但我找到了一种目前适合我们的方法。

我们没有在端到端测试中自动隐藏 toast,而是模拟模态组件以显示并保持可见,直到被点击。

一旦 detox 看到我们点击它的元素,关闭它并继续我们的测试。