Python + Selenium(二十)等待

为什么需要等待?

自动化测试脚本在运行时,由于网络原因、机器卡顿、页面元素呈现等原因,导致定位失败。定位失败导致元素无法操作,获取不到用于断言的内容。

最终在检查测试结果时就会出现很多因为这些原因而导致的测试失败,需要花大量精力来排查才能找到真正意义上的问题。

所以必须要使用等待。其实 Selenium 是有默认等待的,当你打开页面时默认会等待页面元素加载完毕才进行元素定位。但是页面加载完毕后产生变化的元素则无法产生等待。

导致页面产生变化的原因:

  • ajax 动态加载内容
  • JavaScript 某些动作改变 HTML 页面元素:比如增加、删除元素,隐藏与可见元素等
  • 通常来说,我们经常会使用三种等待方式:

  • 显式等待(智能等待)
  • 这不是 Selenium 中提供的等待,而是使用 Python 标准库 time 中的 sleep() 函数。

    sleep(second) 函数,会将线程挂起。参数为挂起的秒数。

    程序运行时遇到 sleep() 函数就会停止设置的秒数,然后再运行下一句代码。

    相当于给后面的元素定位提供一定的缓冲时间。

    import time
    time.sleep(0.5)  # => 等待0.5秒
    driver.find_element_by_id('test').click()
    

    这种方式不适合大面积使用,这属于硬性等待,不管元素是否能够定位到, 都需要等待固定的时间。如果加得太多,会造成代码中挂起时间过长,脚本运行效率降低。

    只有当隐式等待和显式等待同时失效时才能使用此方式。

    隐式等待,之所以这样叫,是因为不像 sleep() 函数,要等哪里就要在哪里写上一句;它只需要在初始生成浏览器驱动的时候加上就行,后续每个 find_element 类型的语句都会等待。

    Sets a sticky timeout to implicitly wait for an element to be found,or a command to complete. This method only needs to be called one time per session.
    设置隐含的时间用于等待元素查找和命令执行。每个 session 只需要调用此方法一次。
    -- Selenium 源码对该方法的注释

    implicitly_wait(second) 方法用于实现这种隐式等待,参数表示等待超时的秒数。比如设置为 30,表示每个 find_element 语句都会等待,最长等待 30s。30s 后如果 find_element 语句都没有返回找到的元素则会抛出超时异常。

    driver = webdriver.Chrome()
    driver.implicitly_wait(30)  # => 超时30s
    

    其实隐式等待已经作用于所有的 find_element 语句,为何还需要显式等待呢?

    常见原因是某些情况下元素处于动态变化的过程,会导致查找失败。

    比如点击菜单后菜单展开,鼠标悬停某元素后元素呈现:

    显式等待的用法如下:

    from selenium.webdriver.common.by import By
    #引入WebDriverWait
    from selenium.webdriver.support.ui import WebDriverWait
    #引入expected_conditions类,并重命名为EC
    from selenium.webdriver.support import expected_conditions as EC
    #设置等待
    wait = WebDriverWait(driver, 10, 0.5)
    wait.until(EC.presence_of_element_located((By.ID, "kw")))  # => 这里的(By.ID, "kw")组成一个元组
    

    定位器 locator,在 Selenium 中把定位方式(如 By.ID, By.NAME 等)和 定位语句构成的元组称为定位器。

    上面的例子的含义为:根据设定的时间间隔检查当前页面 id 为 “kw” 的元素是否存在,元素存在才进行下一步,如果超过设置时间检测不到则抛异常。

    显式等待需要用到两个类:
    WebDriverWait 和 expected_conditions 中的条件类。WebDriverWait 用来执行等待过程,expected_conditions 中的类用来指定结束等待的条件。

    先说 WebDriverWait:

    WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
    

    参数说明:

    dirver:浏览器驱动 Timeout:最长超时时间,默认以秒为单位 poll_frequency:检测的间隔步长,默认为0.5s ignored_exceptions:超时后的抛出的异常类型,默认抛出 NoSuchElementException 异常。

    对 WebDriverWait 进行实例化后,需要调用 until 方法进行实际的等待:

    until(method, message='')
    

    参数说明:

    method:接收一个函数或方法作为参数,用来作为等待的条件 message:等待失败后在抛出的异常中显示的异常信息

    作为条件的 expected_conditions 类:

    用于设置一些预期条件,比如元素是否加载出来、元素是否可见等等。

    expected_conditions 中提供的各种条件:

    presence_of_element_located 判断某个元素是否被加到了 DOM 树里,并不代表该元素一定可见;
    常用的条件,用于判断一个元素是否能被 find_element 找到 visibility_of_element_located 判断某个元素是否可见;
    可见代表元素非隐藏,并且元素的宽和高都不等于0;
    常用的条件,用于判断元素是否可见 visibility_of 与 visibility_of_element_located 作用一致;
    接收的参数是元素对象而非定位器 presence_of_all_elements_located 判断是否至少有1个元素存在于DOM树中;
    举个例子,如果页面上有n个元素的class都是'column-md-3',那么只要有1个元素存在,这个方法就返回True text_to_be_present_in_element 判断某个元素中的text是否包含了预期的字符串 text_to_be_present_in_element_value 判断某个元素中的value属性是否包含了预期的字符串 frame_to_be_available_and_switch_to_it 判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去,否则返回False invisibility_of_element_located 判断某个元素中是否不存在于DOM树或不可见 element_to_be_clickable 判断某个元素中是否可见并且是enable的,这样的话才叫clickable staleness_of 等某个元素从DOM树中移除,注意,这个方法也是返回True或False element_to_be_selected 判断某个元素是否被选中了,一般用在下拉列表 element_selection_state_to_be 判断某个元素的选中状态是否符合预期 element_located_selection_state_to_be 跟上面的方法作用一样,只是上面的方法传入定位到的element,而这个方法传入定位器 alert_is_present