如何提升Selenium脚本执行稳定性,selenium定位不到元素,selenium操作不了元素,元素可靠定位,元素可靠操作,JS执行未完成,alert、frame、window可靠切换,新页面加载

  • Post author:
  • Post category:其他


如何提升Selenium用例执行稳定性

使用selenium会发现很多稳定性问题,有时定位不到,有时操作不了。对于不熟悉selenium的人来说是很痛苦的。

有些问题增加等待时间可以减少出问题的几率,但是出了问题就增加硬性等待时间会导致案例执行时间越来越长。所以在增加等待时,应该使用显示等待,或者封装出显示等待的效果,这样一来一旦条件满足就会立刻进行后续操作。通过合理的库的封装,既提高可靠性,又不会不合理的增加案例执行时间。

有些问题需要特殊处理。

这里我列出项目中遇到过的稳定性问题及其解决办法。将健壮的操作方式封装在库里,测试用例脚本书写者就感知不到selenium自动化时需要面临的很多困难,降低了案例书写难度。

定位不到/操作不了元素的常见原因

1、没有切换到正确的frame。

项目中遇到过一个对象配置窗口所在的iframe的id会变化,有时候是frame1,有时候是frame2。但是其实frame1和frame2始终都是存在的。处理方式是,如果frame1中找不到要操作的元素,捕捉异常,切换到frame2进行查找。

2、没有切换到正确的window。这里window不是指弹出窗口,而是指另一个网页窗口,或者是浏览器的另一个标签页。

3、定位信息发生变化,比如id变化、class变化(导致css选择器变化),xpath变化。项目中遇到每次打开页面时,某些元素的id值会变化。因为js的存在,所以页面元素的XPATH可能会发生变化。

4、JS还没有执行完。元素还不可见、还没有使能、还不能点击等导致不能操作。解决方法见JS执行未完成和元素可靠操作。

元素可靠定位

涉及javascript代码生成或者修改的元素时,使用implicity_wait(),find_element_by_xx()不可靠,需要使用显示等待。

例如:

def find_element_by_id_robust(driver,id_):

return WebDriverWait(driver,10).until(lambda driver: driver.find_element_by_id(id_))

find_element_by_id_robust(self.driver,”username”).send_keys(“user1”);

在selenium中直接重写find_element_by_id(),使用更方便。但是建议谨慎修改。

其实这里使用EC提供的presence_of_element_located(locator)更方便。

元素可靠操作

有时候元素虽然找到了,但是操作时却提示元素不可见或者没有使能。

一个元素出现了,并不代表可以操作了。比如find_element找到了元素,但是javascript脚本还没有执行完,这个元素还不能进行点击。只有元素可见并且使能了,才能够进行点击。这个时候可以使用WebDriverWait().until()进行显式等待,until()里面调用模块expected_conditions中提供的函数使脚本变得健壮。这些函数定义了WebDriverWait()可以直接使用的条件。

<element hidden=”hidden”>或者简写为<element hidden>、type=hidden则元素不可见。

CSS如果有属性visiblity:hidden或者display:none,则元素不可见。

visibility_of_element_located()。元素可见则返回True。

Html元素属性disabled用来控制元素是否可用。disabled=”disabled”,用来禁用属性。不过EC没有提供检查元素是否enabled的函数。如果需要,可以使用WebElement.is_enabled(),在expect_conditions.py中封装一个class element_located_to_be_enabled。实现如下:

class element_located_to_be_enabled(object):
    def __init__(self, locator):
        self.locator = locator
    def __call__(self, driver):
        element = presence_of_element_located(self.locator)(driver)
        if element and element.is_enabled():
            return element
        else:
            return False

element_to_be_clickable()。只有元素可见并且使能了,才能返回True。推荐使用。但是如果元素有readonly=’readonly’,则其实还是点击不了。这个时候还需要满足条件element.get_attribute(‘readonly’) is None。

使用举例:

from selenium import webdriver

from selenium.webdriver.common.by import By

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC

#每0.5秒调用一次until()里的函数,直到返回值不是False。until()返回其参数中的函数的返回值。如果60s超时,则抛出TimeoutException异常。

element=WebDriverWait(driver,60).until(EC. visibility_of_element_located ((By.ID, “element_id”)))

#后面就可以直接操作这个元素

element.send_keys(“username”);

until()中也可以使用自定义的函数,这样就可以不用再使用EC。例如:

exist = WebDriverWait(driver,10).until(lambda the_driver: the_driver.find_element_by_name(“username”).is_displayed())

这种方式相比于EC的方式缺点在于,条件成立时,WebDriverWait.until()也不会返回element,而是返回True。随后还是需要element= find_element_by_name(“username”)来获取元素。

对于普通的文本,虽然没有点击效果,但是也可以使用element_to_be_clickable()。

element=WebDriverWait(driver,10).until(

EC.element_to_be_clickable((By.XPATH,”//span[contains(text(),’经营性-2017-0020′)]”)))

print(element.text) #(京)-经营性-2017-0020

JS执行未完成

如果js使用的是基于jQuery的框架。那么问题就变得容易处理了。使用jQuery.active==0判断出js执行完成了。等执行完成后再定位和操作元素。

实现自动化框架时,可以在expected_conditions.py中扩展的一个类,使用selenium的显示等待机制调用。

class completion_of_js(object):

def __call__(self, driver):

return driver.execute_script(“return jQuery.active == 0”)

调用示例:

WebDriverWait(driver,60).until(EC.completion_of_js())

实现自动化框架时,可以将封装的操作添加一个名字参数wait_for_js=False,如果需要等待js执行完成,那就将wait_for_js设置为True。

alert可靠切换

直接切换到alert,如果alert还没有弹出,则会抛出异常。硬等待会浪费时间。

使用显示等待和EC中的alert_is_present()。例如

alert=WebDriverWait(driver,60).until(EC.alert_is_present())

frame可靠切换

直接切换到frame,如果frame还没有加载,则会抛出异常。硬等待会浪费时间。

使用显示等待和EC中的frame_to_be_available_and_switch_to_it()。例如

alert=WebDriverWait(driver,60).until(EC.frame_to_be_available_and_switch_to_it())

window可靠切换

<a>元素的target属性指定在何处打开新文档。target=_bland表示在新窗口中打开文档。

直接切换到driver.window_handles[-1],如果新的window还没有出现,则其实还在老的window中。

因为selenium没有提供window的EC机制。切换到新打开的window时建议封装出显式等待的效果。可以通过driver.title或者driver.url确认新window已经打开。

普通函数实现举例:

def switch_to_new_window(self,title):
    for i in range(120):
        for handle in self.driver.window_handles:
            self.driver.switch_to_window(handle)
            if title in self.driver.title:
                return
        time.sleep(0.5)
    raise TimeoutException("Could not find expected window!")

大致来说,这里的超时时间是60s,但是因为每次循环中的操作都需要时间,所以超时会略长于60秒。如果希望严格60秒超时,可以在循环开始前先使用time.time()获取当前时间,加上60秒,就是超时的时间点。然后每次循环时都检查是否超时了。

EC机制实现举例,在expected_conditions.py中定义下面的类

class window_to_be_available_and_switch_to_it():
    def __init__(self, title):
        self.window_title = title

    def __call__(self, driver):
        for handle in driver.window_handles:
            driver.switch_to_window(handle)
            if self.window_title in driver.title:
                return True

点击链接产生新页面后元素可靠操作

如果点击链接之后在当前标签页显示新页面,这时查找新页面的元素,可能会出现找不到元素的异常。这时driver.current_url还是老的页面的url。如果增加隐式等待时间,会导致确实找不到元素时,脚本等待太久。可以使用显式等待处理这一问题。例如:

self.driver.find_element_by_link_text(“学院”).click();

WebDriverWait(self.driver,30).until(lambda driver: self.driver.current_url==”https://edu.csdn.net/”)

self.assertEqual(self.driver.current_url,”https://edu.csdn.net/”)

没有使用显式等待时,执行20次失败7次,用时156s。使用了显式等待,执行20次全部成功,用时166s,耗时只增加了10s。

也可以直接使用EC提供的title_contains(title)。

还可以使用EC提供的presence_of_element_located(locator)来判断新页面中要操作的元素已经出现了。



版权声明:本文为jxzdsw原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。