Pytest Selenium WebDriver退出

        在使用Python Selenium进行web自动化测试过程中,考虑整个测试框架中如何处理WebDriver的退出问题。问了ChatGPT两个问题,返回大段内容。以下整理了ChatGPT的回复,在后面的实践中进行核对修正。

----------------------------------------------分割线-------------------------------------------------------

        在 Selenium 自动化测试 中,如果程序因错误退出,webdriver 不一定会自动关闭,可能导致内存泄漏或未释放的资源。这是一个常见的问题,尤其是当程序意外中断时。

Selenium 遇到错误时 WebDriver 会退出吗?

        不会自动退出,除非你明确关闭它。

  • 如果脚本抛出异常 (如 ElementNotFoundTimeoutException 等),webdriver 不会自动调用 driver.quit()
  • 这会导致浏览器进程 (如 Chrome/Firefox) 继续运行,并消耗系统内存和资源。

如何正确退出 WebDriver?

使用 driver.quit() 关闭 WebDriver

        在正常情况下,使用 driver.quit() 来关闭浏览器并释放内存:

from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://example.com")

# 执行一些操作
driver.quit()  # 关闭所有窗口,释放内存

使用 try...finally 确保退出

        如果程序意外中断,finally 子句总会执行,即使有异常抛出。
        示例:

from selenium import webdriver

driver = None
try:
    driver = webdriver.Chrome()
    driver.get("https://example.com")
    # 可能的操作,存在风险
    element = driver.find_element("id", "non_existent_id")
except Exception as e:
    print(f"发生异常: {e}")
finally:
    if driver:
        driver.quit()

解释:

  • 即使脚本遇到异常,finally 子句仍会执行,调用 driver.quit() 来关闭浏览器。
  • 确保即使发生异常,WebDriver 也会正常退出。

 

使用 pytest 进行自动化测试

  • 在 pytest 中,可以使用 setupteardown 方法确保 WebDriver 被关闭。
  • 这对于组织大规模的自动化测试套件特别重要。

pytest 中的例子

import pytest
from selenium import webdriver

@pytest.fixture(scope="function", autouse=True)
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

def test_example(driver):
    driver.get("https://example.com")
    assert "Example" in driver.title

解释:

  • driver() 是一个 pytest fixture,会在每个测试用例前启动 WebDriver,并在用例结束后自动调用 driver.quit()
  • 这样即使测试用例失败,浏览器也会自动关闭。

使用类级别的 driver (推荐)

通过使用 pytest.mark.usefixtures 和 类级别的 fixture,可以避免每个方法都传入 driver

示例代码

import pytest
from selenium import webdriver

@pytest.fixture(scope="class")
def driver(request):
    driver = webdriver.Chrome()
    request.cls.driver = driver  # 绑定 driver 到测试类
    yield driver
    driver.quit()

@pytest.mark.usefixtures("driver")
class TestExample:
    def test_example_1(self):
        self.driver.get("https://example.com")
        assert "Example" in self.driver.title

    def test_example_2(self):
        self.driver.get("https://example.com/page2")
        assert "Page2" in self.driver.title

解释

  1. @pytest.fixture(scope="class")

    • 使 driver 在类的整个作用范围内只启动一次,共享一个 driver 实例。
    • request.cls.driver = driverdriver 绑定到类的实例 (self.driver) 上。
  2. @pytest.mark.usefixtures("driver")

    • 告诉 pytest 使用 driver fixture 作为类的前置条件,不需要在每个方法中显式传递 driver
  3. self.driver

    • 在测试方法中,你可以直接使用 self.driver,不需要每个方法中传入 driver

🚀 优点

  • 只需在类的前面声明 @pytest.mark.usefixtures("driver"),不需要为每个方法传递 driver
  • 减少样板代码,更整洁的代码结构。
  • 避免为每个测试用例重复创建和关闭 WebDriver(节省测试时间)。

进阶技巧:atexit 模块

如果你想确保即使脚本因未捕获的异常中断,也能关闭 WebDriver,可以使用 atexit
示例

import atexit
from selenium import webdriver

driver = webdriver.Chrome()

def close_browser():
    print("程序退出,关闭浏览器")
    driver.quit()

atexit.register(close_browser)

driver.get("https://example.com")
# 可能的风险操作
raise Exception("模拟的程序崩溃")

解释

  • 当 Python 解释器意外中断(无论是错误、KeyboardInterrupt 还是脚本崩溃),atexit 将调用 close_browser(),确保 driver.quit() 执行。
  • 这种方式类似于 finally,但适用于整个脚本

使用 autouse=True (自动启用)

如果不想为每个类都手动绑定 driver,可以使用 autouse=True自动启用 fixture

示例代码

import pytest
from selenium import webdriver

@pytest.fixture(scope="function", autouse=True)
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

def test_example_1():
    driver.get("https://example.com")
    assert "Example" in driver.title

def test_example_2():
    driver.get("https://example.com/page2")
    assert "Page2" in driver.title

解释

  1. autouse=True

    • 自动启用 fixture,不需要在每个方法中显式传递 driver
    • 在每个测试函数的上下文中,pytest 会自动创建并关闭 driver
  2. driver() fixture

    • 每个测试函数在开始时,都会自动获取一个 driver 实例,在函数结束后自动关闭
  3. 无需传入 driver

    • 你不需要在 test_example_1(driver) 中传入 driver,而是直接在测试函数中使用 driver

优点

  • 最简单的实现方式,没有显式的依赖注入。
  • 适用于每个测试函数的 WebDriver 独立运行的场景。

注意

  • 由于 autouse=True全局启用的所有测试都会加载 driver。如果有不需要 driver 的测试,也会创建 driver,从而浪费资源。
  • 如果希望只针对特定的类特定的模块启用 driver,建议使用 scope="class"@pytest.mark.usefixtures() 方式。

3. pytest hooks (pytest_sessionstart, pytest_runtest_setup)

如果想在整个会话范围内共享一个 WebDriver(例如,一个浏览器用于整个测试会话),可以使用 pytest_sessionstartpytest_sessionfinish

示例代码

import pytest
from selenium import webdriver

def pytest_sessionstart(session):
    """ 在测试会话开始时启动 WebDriver """
    session.driver = webdriver.Chrome()

def pytest_sessionfinish(session, exitstatus):
    """ 在测试会话结束时关闭 WebDriver """
    session.driver.quit()

@pytest.fixture(scope="function", autouse=True)
def driver(request):
    """ 在每个测试中都使用 session.driver """
    driver = request.session.driver
    yield driver

def test_example_1(driver):
    driver.get("https://example.com")
    assert "Example" in driver.title

def test_example_2(driver):
    driver.get("https://example.com/page2")
    assert "Page2" in driver.title

解释

  1. pytest_sessionstartpytest_sessionfinish

    • pytest_sessionstart 在 pytest 启动前创建一个全局的 WebDriver
    • pytest_sessionfinish 在 pytest 执行完成后,关闭 WebDriver
  2. driver fixture

    • 在每个测试中,driverrequest.session.driver 的引用,因此只会有一个全局的 WebDriver
  3. 持久的 WebDriver

    • 在整个测试会话中,只启动一次浏览器,减少浏览器的反复创建和关闭,节省时间。

优点

  • 适用于长时间运行的测试会话,只需启动一次浏览器,节省资源。
  • 速度更快,因为每个测试都使用相同的 WebDriver,减少了重复启动的时间。

对比总结:三种方式对比

方法优势缺点
@pytest.mark.usefixtures简单、灵活,可控制作用范围每个类都要标注 @pytest.mark.usefixtures
autouse=True最简单、全自动,不需要传递 driver每个测试用例都自动创建和关闭 WebDriver,可能浪费资源
pytest hooks高效、单个 WebDriver,更节省时间WebDriver 在整个会话中保持打开,无法在中间重启

推荐的最佳实践

  1. 小型项目/临时脚本:使用 autouse=True,让 WebDriver 在所有测试中自动可用。
  2. 中型项目/团队协作:使用 @pytest.mark.usefixtures("driver")类级的 driver,保持测试的可读性和灵活性。
  3. 大型项目/运行时间较长的脚本:使用全局 driver (pytest hooks),仅在整个会话中运行一个 WebDriver,节省运行时间。

示例:大型项目的最佳实践

import pytest
from selenium import webdriver

@pytest.fixture(scope="class")
def driver(request):
    driver = webdriver.Chrome()
    request.cls.driver = driver
    yield driver
    driver.quit()

@pytest.mark.usefixtures("driver")
class TestLogin:
    def test_valid_login(self):
        self.driver.get("https://example.com/login")
        assert "Login" in self.driver.title

    def test_invalid_login(self):
        self.driver.get("https://example.com/login")
        assert "Invalid" not in self.driver.title

@pytest.mark.usefixtures("driver")
class TestDashboard:
    def test_dashboard(self):
        self.driver.get("https://example.com/dashboard")
        assert "Dashboard" in self.driver.title

综合考虑,个人选择类级别使用@pytest.mark.usefixtures

可能的内存泄漏场景和解决方案

场景原因解决方法
程序崩溃未关闭浏览器异常中断,driver.quit() 未被调用使用 try...finally 确保退出
并行运行多个测试多个 WebDriver 竞争资源使用 pytest-xdist,并确保 driver.quit()teardown 中执行
未关闭浏览器会话driver.close() 只关闭窗口,未退出使用 driver.quit() 代替
后台残留的进程系统中残留的 chromedriver 进程运行 pkill -f chromedriver 关闭后台进程
内存使用增加未释放的页面资源在长时间运行的脚本中,定期调用 driver.quit() 并重新启动 WebDriver

driver.close()driver.quit() 的区别

方法操作场景
driver.close()关闭当前窗口 (不退出进程)当有多个窗口/Tab时,关闭当前窗口,但不会关闭 WebDriver 进程
driver.quit()关闭所有窗口并退出进程退出整个会话,释放所有资源,推荐在测试结束时使用

注意:如果仅调用 driver.close()WebDriver 进程仍在运行,可能导致内存泄漏
推荐总是使用 driver.quit() 以确保完全退出

关键要点总结

  1. 确保 WebDriver 正确关闭,使用 driver.quit() 而不是 driver.close()
  2. 使用 try...finallypytest fixtureunittest teardown 等技术,确保即使发生错误,浏览器也能关闭
  3. 使用 atexit.register(),即使脚本中断,也能执行退出逻辑
  4. 长时间运行的脚本中,定期调用 driver.quit() 以释放内存。
  5. 如果发现内存泄漏,请检查是否有后台的 chromedriver 进程,可用 pkill -f chromedriver 终止它们。

如果是类级别使用@pytest.mark.usefixtures,能确保用例异常退出时退出webdriver吗

是的,类级别使用 @pytest.mark.usefixtures 结合 scope="class"yield 语法,可以确保即使用例异常退出WebDriver 也会被关闭

🔥 如何确保异常退出时退出 WebDriver?

  1. @pytest.mark.usefixtures:作用于整个类,在类开始前执行 fixture 的 setup,在类结束后执行 teardown。
  2. scope="class":确保整个类中的用例共享一个 WebDriver 实例
  3. yield 语法:即使测试中出现异常,yield 之后的代码也会在 pytest 的teardown 阶段执行,确保 WebDriver 被关闭。
  4. pytest 的退出机制:pytest 保证了即使用例中断、异常退出、断言失败等情况,teardown 逻辑(yield 之后的部分)也会被执行。

🔥 完整实现示例

1️⃣ 推荐的实现方式

文件:conftest.py

import pytest
from selenium import webdriver

@pytest.fixture(scope="class")
def driver():
    """设置类级别的 WebDriver,只启动一次,类的所有用例共享这个实例"""
    print("\n[Setup] 启动 WebDriver")
    driver = webdriver.Chrome()
    driver.maximize_window()
    yield driver  # 用例执行完成后,会执行 yield 之后的部分
    print("\n[Teardown] 关闭 WebDriver")
    driver.quit()

文件:test_example.py

import pytest

@pytest.mark.usefixtures("driver")
class TestExample:
    """类级别使用 driver,每个用例共享一个 driver 实例"""
    
    def test_google_search(self, driver):
        """测试 Google 搜索"""
        driver.get("https://www.google.com")
        assert "Google" in driver.title  # 假设这里断言失败,driver.quit() 仍会执行

    def test_bing_search(self, driver):
        """测试 Bing 搜索"""
        driver.get("https://www.bing.com")
        assert "Bing" in driver.title  # 这里用例会正常通过

执行效果

运行 pytest -s test_example.py,输出类似以下内容:

[Setup] 启动 WebDriver

test_example.py::TestExample::test_google_search 
  (页面打开,Google 未找到,断言失败)
[Teardown] 关闭 WebDriver

test_example.py::TestExample::test_bing_search 
  (页面打开,Bing 加载成功)

关键点解读

  1. WebDriver 只启动一次

    • TestExample 中,driver 只在 setup_class 中启动一次。
  2. WebDriver 在类结束后关闭

    • 即使 test_google_search 失败,driver.quit() 也会在类的 teardown 阶段被执行。
  3. 作用范围是整个类

    • scope="class" 作用于整个类的范围,同一个 driver 实例TestExample 中的所有用例共享。
  4. @pytest.mark.usefixturesyield 确保稳定性

    • 即使中途用例失败(比如 test_google_search 中的断言失败),driver.quit() 也会在类 teardown 阶段运行

常见问题与解答

1️⃣ 如何确保 WebDriver 在类结束时关闭?

  • 通过 scope="class"yield 机制,确保不管测试中途如何中断,在类级别 teardown 阶段driver.quit() 都会被调用。
  • 关键点在于:
yield driver  # 控制权交给测试用例
driver.quit()  # 用例结束后执行 teardown

如果类中用例过多,如何确保每个用例都可以正常使用 driver?

  • 使用 @pytest.mark.usefixtures("driver"),确保整个类中的每个用例都可以访问共享的 driver 实例
  • 如果不想使用 @pytest.mark.usefixtures,也可以直接将 driver 作为类的class attribute

python

复制代码

class TestExample:
    driver = None  # 将 driver 存储为类属性

    @classmethod
    def setup_class(cls):
        cls.driver = webdriver.Chrome()

    @classmethod
    def teardown_class(cls):
        cls.driver.quit()

    def test_google_search(self):
        self.driver.get("https://www.google.com")
        assert "Google" in self.driver.title

如果用例中抛出异常,driver.quit() 还会执行吗?

  • 是的!
  • pytest 机制总会在用例结束后,回到 yield 语句的 teardown 部分
  • 例如,如果 test_google_search 抛出 AssertionErrordriver.quit() 仍会被执行。
  • '

@pytest.mark.usefixturesautouse=True 的区别?

用法生效范围是否需要手动传 driver自动调用
@pytest.mark.usefixtures类或方法❌ 不需要❌ 不自动生效
autouse=True类或方法❌ 不需要✅ 自动生效
  • @pytest.mark.usefixtures 作用于类或方法,需要显式声明 driver 作为参数
  • autouse=True 作用于所有方法和类无需显式传入 driver

🔥 最佳实践

方法 1:@pytest.mark.usefixtures

  • 适用于类级别的 driver 共享,每个用例都共享一个 driver,用例结束后,driver 会被关闭
  • 使用如下:
@pytest.mark.usefixtures("driver")
class TestExample:
    def test_1(self, driver):
        driver.get("https://example.com")

方法 2:autouse=True

  • 如果不希望每次都写 @pytest.mark.usefixtures,可以直接使用 autouse=True
  • 示例:

@pytest.fixture(scope="class", autouse=True)
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

在这种情况下,不需要在每个测试类上使用 @pytest.mark.usefixtures,driver 会被自动激活。

方法 3:类级别的 setup/teardown

  • 如果不想依赖 pytest 的 fixture,直接用类的 setup_classteardown_class
class TestExample:
    driver = None  # 将 driver 存储为类属性

    @classmethod
    def setup_class(cls):
        cls.driver = webdriver.Chrome()

    @classmethod
    def teardown_class(cls):
        cls.driver.quit()

    def test_1(self):
        self.driver.get("https://example.com")

总结

方法控制范围关闭 driver 机制是否自动关闭 driver
@pytest.mark.usefixtures + scope="class"类级别teardown 在 yield 之后运行✅ 是的 (不管用例失败与否)
autouse=True全局生效pytest 自动控制✅ 是的 (不管用例失败与否)
类的 setup_class/teardown_class类级别teardown_class 始终运行✅ 是的 (不管用例失败与否)

🚀 推荐的实现

如果你要确保不管用例失败、断言失败、异常退出driver.quit() 也会被调用,建议:

  1. 使用 @pytest.mark.usefixturesscope="class" 来控制 driver 的生命周期。
  2. 通过 yield 确保用例结束后调用 driver.quit()
  3. 使用 autouse=True 可进一步简化代码,不必传入 driver 参数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值