Selenium自动化常见问题

1,823 阅读11分钟

Selenium是最流行的web端自动化测试框架之一,用于自动执行用户对被测产品的操作。Selenium是开源的,Selenium框架的核心组件是Selenium WebDriverSelenium WebDriver允许使用者在不同的浏览器(例如Chrome,Firefox,Internet Explorer,Microsoft Edge等)上执行测试用例。使用Selenium WebDriver的主要优点是它支持.NETJavaC#Python等。可以参考有关Selenium WebDriver体系结构的官方文档以了解更多信息。

尽管Selenium简化了Web网站或Web应用程序的测试,但测试开发人员在使用框架时面临着许多Selenium自动化挑战。让我们看一下Selenium Automation中面临的一些最常见挑战及其比较不错的解决方案。

误报成功和误报失败

误报成功也是测试结果成功的一种情况,即使实际情况并非如此。反之亦然,误报失败是测试失败一种情况,即使一切都按预期进行,测试结果也会报告脚本执行过程中出现错误。误报对自动化测试一直是最大的挑战,当然Selenium也不例外。

当测试工程师通过Selenium脚本运行成百上千的测试用例时,可能会遇到一些不稳定的测试,这些测试显示误报。如果长时间不处理,可能会导致整个自动化测试项目失去价值,从而使测试人员的自动化测试脚本沦为“废物”。

测试脚本的稳定性无疑是Selenium自动化中最常见的挑战之一。目前通用的解决办法依然缺少,但从过往工作经验来看,测试左移,独立测试环境,统计脚本误报率等等从流程上来解决这个难题是一个不错的思路。

等待网页加载JavaScript

现在很多网站包含需要JS异步加载Web元素,例如基于用户选择的下拉列表。则Selenium脚本在运行时可能会在这些Web元素时突然失效。发生这种情况是因为WebDriver没有处理网页完全加载所花费的时间。为了处理页面加载的Selenium自动化中的异步加载的问题,需要使WebDriver等到该页面的完整JavaScript加载完成之后再进行操作。在任何网页上执行测试之前,您应确保该网页(尤其是带有很多JavaScript代码的网页)的加载已完成。您可以使用readyState属性,该属性描述文档/网页的加载状态。document.readyState状态为complete表示页面/文档的解析已完成。

''' 在此示例中,我们将pytest框架与Selenium一起使用 '''
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from time import sleep
from contextlib import contextmanager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import staleness_of

@pytest.fixture(params=["chrome"],scope="class")
def driver_init(request):
    if request.param == "chrome":
        web_driver = webdriver.Chrome()
    request.cls.driver = web_driver
    yield
    web_driver.close()

@pytest.mark.usefixtures("driver_init")
class BasicTest:
    pass
class Test_URL(BasicTest):
        def test_open_url(self):
            self.driver.get("https://www.*****.com/")
            print(self.driver.title)
            sleep(5)
  def wait_for_page_load(self, timeout=30):
            old_page = self.driver.find_element_by_id('owl-example')  
            yield
            WebDriverWait(self.driver, timeout).until(
                staleness_of(old_page)
            )
        
        def test_click_operation(self):
            # 等待10秒钟的超时时间,然后执行CLICK操作
            with self.wait_for_page_load(timeout=10):
                self.driver.find_element_by_link_text('FREE SIGN UP').click()
                print(self.driver.execute_script("return document.readyState"))

无法扩展的方法

Selenium是一种出色的自动化测试工具,它是开源的,它使世界各地的Web测试人员的生活变得更加轻松。但是,Selenium自动化的主要挑战之一是无法扩展。

执行自动化测试的最终目标是在更短的时间内覆盖更多的测试范围。最初可能会进行简短的测试构建,但是产品势必会随着每个迭代版本发生变化。这意味着您可能需要涵盖更多的测试用例。使用Selenium WebDriver,您只能以顺序的方式执行测试,并且效果不如您希望的自动化过程有效,测试脚本的执行速度也会变得越来越慢。

现在,Selenium Grid可以并行运行测试用例,但这也有一个缺点。无法跨浏览器和操作系统的多种组合全面测试网站或网络应用。因为Selenium Grid仅有助于在本地计算机上安装的特定浏览器上执行跨浏览器测试。您可以利用Selenium中的并行测试功能来代替线性测试,从而降低总体项目成本,并在并行执行自动化测试时加快产品/功能迭代交付。

处理动态内容

越来越多的网站使用了动态的内容。测试具有静态内容的网站对Selenium自动化来说相对轻松。在当今时代,大多数网站所包含的内容可能因一个访客而异。这意味着内容本质上是动态的(基于AJAX的应用程序)。

例如,电子商务网站可以根据用户登录的位置加载不同的产品、内容可能会有所不同,具体取决于用户从特定下拉菜单中选择的内容。由于新内容的加载需要时间,因此仅在加载完成后才触发测试非常重要。由于网页上的元素以不同的时间间隔加载,因此如果DOM中尚不存在某个元素,则可能会出现错误。这就是为什么处理动态内容一直是Selenium自动化中最常见的挑战之一的原因。

解决此问题的一个简单解决方案是使线程休眠几秒钟,这可能会提供足够的时间来加载内容。但是,这不是一个好习惯,因为,无论是否发生必需的事件,线程都会休眠那么长的时间。

# 并不完美的方案
from selenium import webdriver
import time
from time import sleep
 
driver = webdriver.Firefox()
driver.get("https://www.*****.com")

# 睡眠10秒,无论是否存在元素
time.sleep(10)

# 资源释放
driver.close()

使用Selenium WebDriver动态内容处理此挑战的更好方法是使用隐式等待或显式等待,这取决于各自的需求。

显式等待处理动态内容

使用显式等待,您可以使Selenium WebDriver停止执行并等待直到满足特定条件。如果您希望设置条件以等待到确切的时间段,则可以将它与thread.sleep()函数一起使用。有多种方法可以实现显式等待,带有ExpectedConditionWebDriver是最受欢迎的选项。

from selenium import webdriver
from time import sleep
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait

driver = webdriver.Firefox()
driver.get("https://www.*****.com")

try:
    myElem_1 = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'home-btn')))
    print("Element 1 found")
    myElem_2 = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CLASS_NAME, 'login')))
    print("Element 2 found")
    myElem_2.click()
    sleep(10)
#异常处理
except TimeoutException:
    print("No element found")

sleep(10)

driver.close()

在上面的示例中,对受测URL内容进行了两次搜索。第一次搜索是通过CLASS_NAME将元素定位到元素home-btn的最长持续时间为10秒。

第二个搜索是可点击元素登录,最长持续时间为10秒。如果存在clickable元素,则执行click()操作。在两种情况下,都将WebDriverWaitExpectedCondition一起使用。WebDriverWait触发ExpectedCondition的默认限制为500毫秒,直到收到成功的响应。

隐式等待处理动态内容

隐式等待通知WebDriver在特定时间段内轮询DOM,以获取页面上Web元素的存在。默认超时为0秒。隐式等待需要一次性设置,如果配置正确,它将在Selenium WebDriver对象的生存周期内可用。

from selenium import webdriver
import time
from time import sleep
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support import expected_conditions as EC
from builtins import str

driver = webdriver.Firefox()
driver.get("https://www.*****.com")
driver.implicitly_wait(60)
curr_time = time.time()
try: 
    curr_element = driver.find_element_by_class_name("home-btn") 
except: 
    print("元素不存在!")
print("耗时 " + str(int(time.time()-curr_time))+'-secs')

driver.close()

处理弹出窗口

在某些情况下,您需要与弹出窗口进行自动交互,这是Selenium自动化中最常见的挑战之一。有不同类型的弹出窗口:

  • 简单警报:显示一些消息的东西。
  • 确认警报:要求用户确认操作。
  • 提示警报:通知用户输入内容。

尽管Selenium WebDriver无法处理基于Windows的警报,但它确实具有处理基于Web的警报的功能。switch_to方法用于处理弹出窗口。为了演示,我们创建了一个简单的HTML文件,其中包含Alert弹出窗口实现。

<!DOCTYPE html>
<html>
<body>
<h2>
Demo for Alert</h3>


<button onclick="create_alert_dialogue()" name ="submit">Alert Creation</button>
<script>
function create_alert_dialogue() {
 alert("测试弹框,请点击继续!");
}
</script>
</body>
</html>

下面是我们在switch_to方法中的用法 .switch_to.alert.alert,以便切换到警报对话框。进入“警报框”后,可以使用alert.accept()方法接受警报。

from selenium import webdriver
import time
from time import sleep
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support import expected_conditions as EC
from builtins import str

driver = webdriver.Firefox()
driver.get("file://<HTML File location>")

driver.find_element_by_name("submit").click()
sleep(5)

alert = driver.switch_to.alert
text_in_alert = alert.text
sleep(10)

alert.accept()

print("ok,跳过警告框")

driver.close()

如果页面显示输入警报,则可以使用send_keys()方法将文本发送到输入框。

from selenium import webdriver
import time
from time import sleep
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support import expected_conditions as EC
from builtins import str

driver = webdriver.Firefox()
driver.get("file://<HTML File location>")

driver.find_element_by_name("***").send_keys("***")
sleep(5)

driver.find_element_by_name("submit").click()
sleep(5)

try:
    WebDriverWait(driver, 10).until(EC.alert_is_present(),
                '超时')
    alert = driver.switch_to.alert
    sleep(10)
    alert.accept()
    sleep(10)  
except TimeoutException:
    print("没有发现警告框")

driver.close()

切换浏览器窗口

多窗口测试无疑是Selenium自动化中的常见挑战之一。一种理想的情况是单击按钮会打开一个弹出窗口,该弹出窗口成为子窗口。一旦有关子窗口上的活动的活动完成,则应将控件移交给父级或者上一级窗口。这可以通过使用switch_to.window()方法(其中window_handle作为输入参数传递)来实现。

用户单击链接后,将打开一个新的弹出窗口,该窗口将称为子窗口。switch_to.window方法用于切换回父窗口。可以使用driver.switch_to.window(window-handle-id)driver.switch_to.default_content()切换到父窗口。

import unittest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from time import sleep
 
class test_switch_windows(unittest.TestCase):
 
    def setUp(self):
        self.driver = webdriver.Firefox()
    
    def test_open_pop_up_window(self):
        driver = self.driver
        driver.get("http://www.****.com/html/codes/html_popup_window_code.cfm")
        title1 = driver.title
        print(title1)
    

        #获取当前窗口的窗口句柄
        parent_window = driver.window_handles[0]
        print(parent_window)
 
        #弹出式窗口是iframe
        driver.switch_to.frame(driver.find_element_by_name('result1'))
        driver.find_element_by_link_text('show_iframe').click()
        
        #获取子窗口的句柄
        child_window = driver.window_handles[1]
        
        #父窗口将在后台
        #子窗口来到前台
        driver.switch_to.window(child_window)
        title2 = driver.title
        print(title2)
        print(child_window)
        
        #断言主窗口和子窗口标题不匹配
        self.assertNotEqual(parent_window , child_window)
        
        sleep(5)
    
    def tearDown(self):
        self.driver.close()
 
if __name__ == "__main__":
    unittest.main()

无法测试移动设备

尽管Selenium框架已广泛用于跨浏览器和操作系统的不同组合测试网站或Web应用程序,但该测试仍仅限于非移动设备。因此,测试针对移动设备的网站或Web应用程序是Selenium自动化面临的重大挑战之一。

如果对移动应用程序执行测试自动化测试,那么最著名的开源框架将是Appium

无法自动化一切

100%自动化是一个吹牛的命题。众所周知的是,并非所有测试方案都可以自动化,因为有些测试需要手动干预。您需要确定团队在自动化测试上相对于手动测试应花费的精力的优先级。尽管Selenium框架具有一些功能,可以通过这些功能来截取屏幕截图,记录视频(测试的整体执行情况)以及可视化测试的其他方面,但是将这些功能与可扩展的基于云的跨浏览器测试平台一起使用可能会具有很大的价值。

生成测试报告

任何测试活动的主要目的是发现BUG并改善整体产品。在跟踪正在执行的测试,生成的输出和测试结果方面,报告可以发挥主要作用。尽管有可以与pytest和Selenium一起使用的模块,例如pytest_html(对于Python),但是测试报告中的信息可能并不十分详尽。Selenium可以使用不同类型的编程语言(例如JavaC#.Net等)的类似模块/包,但是这些语言仍然存在相同的问题。收集测试报告是Selenium自动化中的关键挑战之一。

很多基于Selenium的第三方云测平台,还有很多公司机遇Selenium开发的自己的报告框架提取,一般来说从以下几个方面丰富报告信息:

  • 检索构建信息,例如构建测试状态,单个测试状态,测试运行时间,错误和测试日志
  • 通过命令获取屏幕截图
  • 浏览器环境的详细信息

有所不能

上面提到的是Selenium自动化中的一些常见挑战,就Selenium而言存在一些限制。只能使用Selenium框架来测试Web应用程序,即不能用于测试基于本地Windows的应用程序。在某些情况下可能需要使用这些场景,出于安全目的,因此自动化测试很难甚至永远无法绕过一些严格身份验证。


  • 公众号FunTester首发,更多原创文章:FunTester440+原创文章,欢迎关注、交流,禁止第三方擅自转载。

热文精选