**还在手动写 Selenium 测试?SeleniumBase 让你的自动化效率提升 10 倍**

**还在手动写 Selenium 测试?SeleniumBase 让你的自动化效率提升 10 倍**

还在手动写 Selenium 测试?SeleniumBase 让你的自动化效率提升 10 倍


为什么 SeleniumBase 值得关注

Selenium 是自动化测试领域最受欢迎的框架之一,但用过它的人都知道,原生 Selenium 存在诸多痛点:等待时间难以把控、截图和日志收集繁琐、测试报告不直观、并行执行配置复杂。这些问题会消耗开发者大量时间,甚至让人怀疑自动化测试是否真的值得。

SeleniumBase 正是为解决这些问题而生。这是一个基于 Python 的测试框架,在 Selenium WebDriver 基础上封装了大量实用功能,让你可以把注意力集中在测试逻辑本身,而不是被各种配置和兼容性细节所困扰。

SeleniumBase 的核心优势包括:内置智能等待机制省去手动设置 implicitly_wait 的麻烦、自动截图和录屏让问题排查一目了然、集成 pytest生态可以无缝使用各种插件、内置 Dashboard 模式提供美观的测试报告、支持浏览器自动化和 API 模式、一行命令即可实现测试并行化。这些特性使得 SeleniumBase 成为从个人项目到企业级应用的理想选择。

无论你是刚接触 Web 自动化测试的新手,还是希望提升现有测试效率的老兵,SeleniumBase 都值得一试。它不会让你抛弃已有的 Selenium 知识,而是以你熟悉的方式工作,同时大幅降低出错概率和维护成本。


环境搭建:从零开始配置 SeleniumBase

系统要求

在开始之前,确保你的系统满足以下要求:Python 3.7 或更高版本、 pip 包管理器、主流浏览器(Chrome、Firefox、Edge 等)以及对应的 WebDriver。SeleniumBase 支持 Linux、macOS 和 Windows 三大平台。

安装步骤

安装 SeleniumBase 非常简单,打开终端执行以下命令即可:

pip install seleniumbase

如果你需要使用额外的功能,比如 PDF 处理或视觉比较,可以安装完整版本:

pip install seleniumbase[all]

安装完成后,验证环境是否正确配置:

python -c "import seleniumbase; print(seleniumbase.__version__)"

浏览器驱动管理

SeleniumBase 内置了浏览器驱动管理工具,可以自动下载和配置所需的 WebDriver。你只需要运行:

seleniumbase install chromium

这条命令会自动下载 ChromeDriver 并配置好环境变量。对于 Firefox 或 Edge,运行对应的命令即可。支持的浏览器包括 chromium、firefox、edge、safari 和 ie。

创建第一个测试文件

建议创建一个专门的测试目录来存放测试代码:

mkdir selenium_tests
cd selenium_tests

然后创建一个简单的测试文件来验证环境是否正常工作。


核心功能详解

1. 简化的测试语法

SeleniumBase 的核心设计理念是让测试代码更简洁。相比原生 Selenium,它提供了更高层次的抽象。你不再需要记住各种复杂的 WebDriver API,一个简单的例子就能看出区别。

假设你要打开一个网页并点击按钮。用原生 Selenium 写法需要这样:

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

driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
button = wait.until(EC.element_to_be_clickable((By.ID, "my-button")))
button.click()
driver.quit()

用 SeleniumBase 的写法变成了:

from seleniumbase import BaseCase

class MyTests(BaseCase):
    def test_example(self):
        self.open("https://example.com")
        self.click("#my-button")

代码量大幅减少,可读性显著提升。所有常见的 WebDriver 操作都被封装成了简洁的方法,如 open、click、type、get_text 等。

2. 智能等待机制

在 Web 自动化测试中,等待是最容易出问题的环节。元素还没加载完成就尝试操作会导致测试失败,而盲目添加固定延时又会拖慢整个测试套件的执行速度。

SeleniumBase 内置了智能等待机制,会自动检测元素何时可用。你不再需要手动设置 implicitly_wait 或编写复杂的显式等待代码。框架会持续尝试直到元素可交互或超时,这大大提高了测试的稳定性和执行效率。

对于特殊场景,你仍然可以使用自定义等待:

self.wait_for_element_visible("#id", timeout=20)
self.wait_for_text("期望的文本内容", "#id")
self.wait_for_element_not_present(".loading-spinner")

3. 丰富的断言方法

SeleniumBase 提供了大量实用的断言方法,让测试验证更加直观:

self.assert_element_present("#header")
self.assert_text("欢迎登录", "#title")
self.assert_attribute("#input", "placeholder", "请输入用户名")
self.assert_url("https://example.com/dashboard")
self.assert_title("仪表盘")

这些方法会在断言失败时自动截取当前页面,帮助你快速定位问题。

4. 自动截图和录屏

当测试失败时,问题排查往往是最耗时的环节。SeleniumBase 会在测试失败时自动截取屏幕截图,并保存完整的控制台日志。你可以在测试报告中直接查看失败时的页面状态。

要手动截图也非常简单:

self.save_screenshot("step1.png")

如果需要录制整个测试过程,启用视频录制功能:

self.enable_recording()
self.open("https://example.com")
self.do_something()
self.disable_recording()

5. 浏览器配置灵活性

SeleniumBase 允许你灵活配置浏览器启动参数,比如无头模式、禁用图片加载、设置代理等:

self.open("https://example.com", window_size=(1920, 1080))

你也可以在启动时指定浏览器选项:

@classmethod
def setUpClass(cls):
    cls.driver = None
    cls.options = webdriver.ChromeOptions()
    cls.options.add_argument("--disable-extensions")

6. Dashboard 模式

SeleniumBase 的一大亮点是内置的 Dashboard 功能。运行测试后,它会生成一个交互式的 HTML 报告,展示测试结果、失败原因、截图等信息:

pytest test_file.py --dashboard

生成的报告保存在 htmlcov 目录下,可以用浏览器直接打开查看。报告中包含了测试通过率、耗时统计、失败详情等丰富信息。

7. API 模式和可视化测试

除了传统的浏览器自动化,SeleniumBase 还支持 API 测试和视觉比较。你可以轻松验证 REST 接口的响应,也可以对页面截图进行像素级对比,确保 UI 没有意外变化。


实战教程:一步步构建完整测试

第一步:基础测试结构

创建一个 Python 文件,导入 SeleniumBase 的基类,然后编写你的测试类:

from seleniumbase import BaseCase

class LoginTests(BaseCase):
    """登录功能测试套件"""

    def setUp(self):
        """每个测试方法执行前的准备工作"""
        super().setUp()
        # 访问登录页面
        self.open("https://practice.automationtesting.in/login/")

    def tearDown(self):
        """每个测试方法执行后的清理工作"""
        # 可以在此处添加自定义清理逻辑
        super().tearDown()

    def test_successful_login(self):
        """测试正确的用户名和密码可以成功登录"""
        # 输入用户名
        self.type("#username", "testuser@example.com")
        # 输入密码
        self.type("#password", "TestPassword123!")
        # 点击登录按钮
        self.click("[name='login']")
        # 验证登录成功,跳转到账户页面
        self.wait_for_url_contains("account")
        self.assert_element_present(".woocommerce-MyAccount-content")

这个例子展示了 SeleniumBase 测试的基本结构。每个测试类继承自 BaseCase,就自动获得了大量实用方法。setUp 和 tearDown 方法分别在每个测试前后执行,用于初始化和清理工作。

第二步:处理复杂交互

实际项目中,表单交互往往比简单的输入和点击更复杂。来看几个常见场景的处理方法。

拖拽操作:

def test_drag_and_drop(self):
    """测试拖拽功能"""
    self.open("https://jqueryui.com/droppable/")
    # 切换到 iframe
    self.switch_to_frame(".demo-frame")
    # 执行拖拽操作
    self.drag_and_drop("#draggable", "#droppable")
    # 验证放置成功
    self.assert_text("Dropped!", "#droppable")
    # 切换回主文档
    self.switch_to_default_content()

文件上传:

def test_file_upload(self):
    """测试文件上传功能"""
    self.open("https://practice.automationtesting.in/upload/")
    # 使用绝对路径上传文件
    self.choose_file("#upload_file", "/path/to/test-image.png")
    # 点击上传按钮
    self.click("#submit_button")
    # 验证上传成功
    self.wait_for_text("uploaded successfully", timeout=10)

处理下拉菜单:

def test_dropdown_selection(self):
    """测试下拉菜单选择"""
    self.open("https://practice.automationtesting.in/dropdown/")
    # 选择下拉选项
    self.select_option_by_text("#抽筋dropdown", "Option 3")
    # 验证选择成功
    selected_value = self.get_selected_option("#抽筋dropdown")
    self.assert_true("Option 3" in selected_value)

处理弹窗和确认对话框:

def test_javascript_dialogs(self):
    """测试 JavaScript 弹窗处理"""
    self.open("https://practice.automationtesting.in/javascript-alert/")
    # 点击触发确认弹窗的按钮
    self.click("button.confirm")
    # 接受弹窗
    self.accept_alert()
    # 或者拒绝弹窗
    self.click("button.confirm")
    self.dismiss_alert()

第三步:数据驱动测试

SeleniumBase 支持数据驱动测试,可以用不同的数据多次运行同一个测试用例。这对于测试登录功能、搜索功能等场景特别有用。

from seleniumbase import BaseCase
from parameterized import parameterized

class SearchTests(BaseCase):
    """搜索功能测试"""

    @parameterized.expand([
        ("python", "Python 编程"),
        ("javascript", "JavaScript 教程"),
        ("selenium", "Selenium 自动化"),
    ])
    def test_search(self, keyword, expected_result):
        """测试不同关键词的搜索结果"""
        self.open("https://www.google.com")
        # 在搜索框输入关键词
        self.type("input[name='q']", keyword)
        # 按回车搜索
        self.enter("input[name='q']")
        # 验证搜索结果包含期望内容
        self.wait_for_text(expected_result, timeout=10)

如果没有安装 parameterized 库,需要先安装:

pip install parameterized

第四步:跨浏览器测试

确保你的网站在不同浏览器中都能正常工作是很重要的。SeleniumBase 可以轻松实现这一点:

import pytest

class CrossBrowserTests(BaseCase):
    """跨浏览器兼容性测试"""

    @pytest.mark.parametrize("browser", ["chrome", "firefox", "edge"])
    def test_homepage_loads(self, browser):
        """测试网站在不同浏览器中的加载情况"""
        self.open("https://example.com")
        # 验证页面基本元素存在
        self.assert_element_present("header")
        self.assert_element_present("footer")
        self.assert_element_present("main")
        # 验证页面标题
        self.assert_true(len(self.get_page_title()) > 0)

运行特定浏览器的测试:

pytest test_file.py --browser=firefox

第五步:Page Object 模式

对于复杂的页面,SeleniumBase 推荐使用 Page Object 模式来组织代码。这种模式将页面元素和操作封装在单独的类中,使测试代码更加清晰和可维护。

首先,创建一个页面对象文件:

from seleniumbase import BaseCase

class LoginPage:
    """登录页面对象"""

    def __init__(self, test_case):
        """初始化页面对象,传入测试实例"""
        self.test = test_case

    def open(self):
        """打开登录页面"""
        self.test.open("https://practice.automationtesting.in/login/")

    def enter_username(self, username):
        """输入用户名"""
        self.test.type("#username", username)

    def enter_password(self, password):
        """输入密码"""
        self.test.type("#password", password)

    def click_login(self):
        """点击登录按钮"""
        self.test.click("[name='login']")

    def login_as(self, username, password):
        """执行完整的登录流程"""
        self.open()
        self.enter_username(username)
        self.enter_password(password)
        self.click_login()

    def get_error_message(self):
        """获取错误提示信息"""
        return self.test.get_text(".woocommerce-error")


class DashboardPage:
    """仪表盘页面对象"""

    def __init__(self, test_case):
        self.test = test_case

    def get_welcome_text(self):
        """获取欢迎文本"""
        return self.test.get_text(".woocommerce-MyAccount-content p")

    def is_logged_in(self):
        """检查是否已登录"""
        return self.test.is_element_present(".woocommerce-MyAccount-content")

然后在测试中使用这些页面对象:

from seleniumbase import BaseCase
from pages.login_page import LoginPage
from pages.dashboard_page import DashboardPage

class LoginTests(BaseCase):
    """使用 Page Object 模式的登录测试"""

    def test_successful_login(self):
        """测试成功登录"""
        # 创建页面对象实例
        login_page = LoginPage(self)
        dashboard_page = DashboardPage(self)

        # 执行登录
        login_page.login_as("testuser@example.com", "TestPassword123!")

        # 验证结果
        self.wait_for_url_contains("account")
        self.assert_true(dashboard_page.is_logged_in())
        self.assert_in("Hello", dashboard_page.get_welcome_text())

    def test_invalid_credentials(self):
        """测试无效凭据登录"""
        login_page = LoginPage(self)

        # 尝试使用错误密码登录
        login_page.login_as("testuser@example.com", "wrongpassword")

        # 验证错误提示
        self.assert_text("ERROR", login_page.get_error_message())

这种模式的好处是:当页面结构改变时,你只需要修改页面对象类中的定位符,测试代码无需改动。同时,测试用例变得更加易读,更接近业务语言。

第六步:使用 Test Fixtures

pytest 的 fixtures 功能在 SeleniumBase 中同样适用,可以用来管理测试数据、共享配置等:

import pytest
from seleniumbase import BaseCase

@pytest.fixture
def test_data():
    """提供测试数据"""
    return {
        "valid_user": {
            "email": "testuser@example.com",
            "password": "TestPassword123!"
        },
        "invalid_user": {
            "email": "invalid@example.com",
            "password": "wrongpassword"
        }
    }


class DataDrivenTests(BaseCase):
    """使用 fixtures 的数据驱动测试"""

    def test_login_with_valid_credentials(self, test_data):
        """使用有效凭据登录"""
        user = test_data["valid_user"]
        self.open("https://practice.automationtesting.in/login/")
        self.type("#username", user["email"])
        self.type("#password", user["password"])
        self.click("[name='login']")
        self.wait_for_url_contains("account")

    def test_login_with_invalid_credentials(self, test_data):
        """使用无效凭据登录"""
        user = test_data["invalid_user"]
        self.open("https://practice.automationtesting.in/login/")
        self.type("#username", user["email"])
        self.type("#password", user["password"])
        self.click("[name='login']")
        self.wait_for_text("ERROR", timeout=5)

常见使用场景

场景一:电商网站功能测试

电商网站通常包含商品浏览、购物车、结算等多个流程,使用 SeleniumBase 可以轻松实现端到端测试:

from seleniumbase import BaseCase

class EcommerceTests(BaseCase):
    """电商网站端到端测试"""

    def test_complete_purchase_flow(self):
        """测试完整的购买流程"""
        # 访问网站首页
        self.open("https://shop.example.com")

        # 浏览商品
        self.click(".category-electronics")
        self.wait_for_text("电子产品", timeout=10)

        # 添加商品到购物车
        self.hover_and_click(".product-item:first-child", ".add-to-cart")
        self.wait_for_text("已添加到购物车", timeout=5)

        # 查看购物车
        self.click(".cart-icon")
        self.assert_element_present(".cart-item")

        # 点击结算
        self.click(".checkout-button")

        # 填写收货信息
        self.type("#shipping-name", "张三")
        self.type("#shipping-address", "北京市朝阳区某街道123号")
        self.type("#shipping-phone", "13800138000")

        # 选择支付方式并下单
        self.click("#payment-cod")
        self.click(".place-order")

        # 验证订单成功
        self.wait_for_text("订单提交成功", timeout=10)
        order_number = self.get_text(".order-number")
        self.assert_true(order_number.startswith("ORD"))

场景二:表单验证测试

表单是 Web 应用中最常见的交互元素,完整的表单验证测试至关重要:

from seleniumbase import BaseCase

class FormValidationTests(BaseCase):
    """表单验证测试"""

    def test_registration_form(self):
        """测试注册表单验证"""
        self.open("https://example.com/register")

        # 测试空表单提交
        self.click("#submit")
        self.assert_text("请填写用户名", "#username-error")

        # 测试用户名格式验证
        self.type("#username", "ab")
        self.click("#submit")
        self.assert_text("用户名至少需要3个字符", "#username-error")

        # 测试邮箱格式验证
        self.type("#username", "validuser")
        self.type("#email", "notanemail")
        self.click("#submit")
        self.assert_text("请输入有效的邮箱地址", "#email-error")

        # 测试密码强度验证
        self.type("#email", "valid@email.com")
        self.type("#password", "123")
        self.click("#submit")
        self.assert_text("密码至少需要8个字符", "#password-error")

        # 测试密码确认不匹配
        self.type("#password", "StrongPass123")
        self.type("#confirm-password", "DifferentPass123")
        self.click("#submit")
        self.assert_text("两次输入的密码不一致", "#confirm-password-error")

        # 测试有效数据提交
        self.type("#confirm-password", "StrongPass123")
        self.click("#submit")
        self.wait_for_text("注册成功", timeout=10)

场景三:异步内容加载测试

现代 Web 应用大量使用 AJAX 和 JavaScript 动态加载内容,测试这类应用需要特殊处理:

from seleniumbase import BaseCase

class AsyncContentTests(BaseCase):
    """异步内容加载测试"""

    def test_infinite_scroll(self):
        """测试无限滚动加载"""
        self.open("https://example.com/news-feed")

        # 初始加载的内容数量
        initial_items = self.find_elements(".news-item")
        initial_count = len(initial_items)

        # 滚动到页面底部触发加载
        self.scroll_to_bottom()
        self.sleep(2)  # 等待新内容加载

        # 验证内容增加
        new_items = self.find_elements(".news-item")
        new_count = len(new_items)
        self.assert_true(new_count > initial_count, "滚动后内容数量应该增加")

    def test_lazy_loaded_images(self):
        """测试懒加载图片"""
        self.open("https://example.com/gallery")

        # 初始状态下,部分图片应该还在加载中
        first_image_src = self.get_attribute(".gallery-image:first-child img", "src")

        # 滚动到图片位置
        self.scroll_to(".gallery-image:last-child")
        self.sleep(1)  # 等待图片加载

        # 验证图片已完全加载(src 不再是占位符)
        last_image_src = self.get_attribute(".gallery-image:last-child img", "src")
        self.assert_false("placeholder" in last_image_src)

    def test_dynamic_search_results(self):
        """测试动态搜索结果"""
        self.open("https://example.com/search")

        # 输入搜索关键词
        self.type("#search-input", "python")
        self.sleep(0.5)  # 等待防抖

        # 等待搜索结果显示
        self.wait_for_element_visible(".search-results", timeout=10)
        self.wait_for_ajax_calls_finished()

        # 验证搜索结果
        result_count = len(self.find_elements(".search-result"))
        self.assert_true(result_count > 0, "应该显示搜索结果")
        self.assert_in("python", self.get_text(".search-result:first-child").lower())

场景四:浏览器兼容性测试

确保应用在主流浏览器中都能正常运行:

import pytest
from seleniumbase import BaseCase

class BrowserCompatibilityTests(BaseCase):
    """浏览器兼容性测试"""

    def test_form_rendering(self):
        """测试表单在各浏览器中的渲染"""
        self.open("https://example.com/form")

        # 验证所有表单项都可见
        self.assert_element_visible("#username")
        self.assert_element_visible("#email")
        self.assert_element_visible("#password")
        self.assert_element_visible("#submit-btn")

        # 验证表单元素可以交互
        self.type("#username", "testuser")
        self.type("#email", "test@example.com")
        self.type("#password", "SecurePass123!")

        # 验证输入内容正确显示
        self.assert_input_value("#username", "testuser")
        self.assert_input_value("#email", "test@example.com")
        self.assert_input_value("#password", "SecurePass123!")

    def test_responsive_design(self):
        """测试响应式设计"""
        # 桌面视图
        self.open("https://example.com")
        self.set_window_size(1920, 1080)
        self.assert_element_visible(".desktop-nav")

        # 平板视图
        self.set_window_size(768, 1024)
        self.sleep(0.5)
        self.assert_element_visible(".tablet-nav")

        # 移动端视图
        self.set_window_size(375, 667)
        self.sleep(0.5)
        self.assert_element_present(".mobile-menu")
        # 打开汉堡菜单
        self.click(".menu-toggle")
        self.assert_element_visible(".mobile-nav")

高级技巧与最佳实践

技巧一:自定义命令扩展基类

如果某些操作在项目中经常使用,可以创建自定义命令来减少重复代码:

from seleniumbase import BaseCase
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class ExtendedBaseCase(BaseCase):
    """扩展的 SeleniumBase 基类"""

    def wait_for_loader(self, timeout=30):
        """等待页面加载器消失"""
        try:
            self.wait_for_element_not_visible(
                ".loader, .spinner, .loading-overlay",
                timeout=timeout
            )
        except Exception:
            pass  # 如果没有加载器,忽略异常

    def login(self, username, password):
        """统一的登录方法"""
        self.open("https://example.com/login")
        self.wait_for_loader()
        self.type("#username", username)
        self.type("#password", password)
        self.click("#login-btn")
        self.wait_for_loader()
        self.wait_for_url_contains("/dashboard")

    def logout(self):
        """统一的登出方法"""
        self.click(".user-menu")
        self.click(".logout-btn")
        self.wait_for_url_contains("/login")

    def is_logged_in(self):
        """检查用户是否已登录"""
        return self.is_element_present(".user-menu")

    def take_element_screenshot(self, selector, filename):
        """截取特定元素的屏幕截图"""
        element = self.find_element(selector)
        element.screenshot(filename)

    def get_table_data(self, table_selector):
        """获取表格数据,返回二维列表"""
        rows = self.find_elements(f"{table_selector} tr")
        data = []
        for row in rows:
            cells = row.find_elements(By.TAG_NAME, "td")
            if cells:
                data.append([cell.text for cell in cells])
        return data

然后让你的测试类继承这个扩展基类:

class MyTests(ExtendedBaseCase):

    def test_something(self):
        self.login("user@example.com", "password")
        # 使用自定义的 login 方法
        # ...
        self.logout()

技巧二:测试报告增强

SeleniumBase 默认的报告已经很不错,但你可以通过配置让它更强大:

# 在 pytest.ini 或 setup.cfg 中配置

[pytest]
addopts = 
    --dashboard
    --html=reports/report.html
    --self-contained-html
    --reruns=2
    --tb=short

运行带详细报告的测试:

pytest tests/ --dashboard --html=reports/report.html --self-contained-html

生成的报告会自动包含:测试执行时间、失败截图、浏览器信息、控制台日志等。

技巧三:并行执行加速测试

测试套件变大后,执行时间会成为瓶颈。SeleniumBase 内置并行执行支持:

# 使用 4 个进程并行执行
pytest tests/ -n=4

# 分布式并行执行(需要安装 redis)
pytest tests/ -n=8 --dist=loadscope

并行执行时注意:确保测试之间相互独立,不要依赖执行顺序,使用唯一的数据避免冲突。

技巧四:处理验证码

自动化测试中处理验证码是个常见挑战,以下是几种实用方案:

from seleniumbase import BaseCase

class CaptchaTests(BaseCase):
    """验证码处理策略"""

    def test_with_api_bypass(self):
        """方案一:通过 API 绕过验证"""
        # 在测试环境中,可以直接调用后端 API 完成验证
        import requests
        captcha_token = self.solve_captcha_api()
        requests.post("https://example.com/verify", data={"captcha": captcha_token})

    def test_with_test_account(self):
        """方案二:使用测试账户"""
        # 联系开发团队在测试环境中禁用验证码
        self.open("https://test.example.com/login")
        self.type("#username", "testbot@example.com")
        self.type("#password", "TestBotPass123")
        self.click("#login-btn")
        # 无需处理验证码,直接登录成功

技巧五:处理 iframe 和多窗口

复杂的 Web 页面经常包含 iframe 和弹出窗口:

def test_iframe_interaction(self):
    """处理 iframe 交互"""
    self.open("https://example.com/page-with-iframe")

    # 切换到 iframe
    self.switch_to_frame("#editor-frame")

    # 在 iframe 中操作
    self.type(".editor", "在 iframe 中输入的内容")

    # 切换回主文档
    self.switch_to_default_content()

    # 验证主文档中的元素
    self.assert_element_present(".main-content")


def test_multiple_windows(self):
    """处理多窗口场景"""
    main_window = self.current_window_handle

    # 点击打开新窗口的链接
    self.click(".open-new-window")
    self.wait_for_new_window()

    # 切换到新窗口
    new_window = [w for w in self.window_handles if w != main_window][0]
    self.switch_to_window(new_window)

    # 在新窗口中操作
    self.type("#email-input", "test@example.com")

    # 关闭新窗口,切回主窗口
    self.close()
    self.switch_to_window(main_window)

    # 继续在主窗口操作
    self.assert_text("Thanks for subscribing!", ".subscription-status")

技巧六:优雅的失败处理

当测试可能失败但你不想让整个测试套件中断时,可以使用软断言:

def test_multiple_checks(self):
    """执行多个检查,即使部分失败也继续"""
    checks = [
        ("#header-logo", "Logo 应该可见"),
        ("#main-nav", "导航应该可见"),
        ("#footer-links", "页脚链接应该可见"),
        ("#copyright", "版权信息应该可见"),
    ]

    failed_checks = []

    for selector, message in checks:
        try:
            self.assert_element_visible(selector)
        except AssertionError:
            failed_checks.append(message)
            self.save_screenshot(f"failed_{selector.replace('#', '')}.png")

    # 最后报告所有失败
    if failed_checks:
        self.fail(f"以下检查失败:{', '.join(failed_checks)}")

技巧七:性能度量

测试不仅可以验证功能,还可以度量性能:

import time
from seleniumbase import BaseCase

class PerformanceTests(BaseCase):
    """性能测试"""

    def test_page_load_time(self):
        """测量页面加载时间"""
        start_time = time.time()
        self.open("https://example.com")
        load_time = time.time() - start_time

        # 验证页面在 3 秒内加载完成
        self.assert_true(
            load_time < 3,
            f"页面加载时间过长:{load_time:.2f}秒"
        )

    def test_api_response_time(self):
        """测量 API 响应时间"""
        import requests

        start_time = time.time()
        response = requests.get("https://api.example.com/data")
        api_time = time.time() - start_time

        self.assert_equal(response.status_code, 200)
        self.assert_true(
            api_time < 1,
            f"API 响应时间过长:{api_time:.2f}秒"
        )

    def test_element_interaction_speed(self):
        """测量元素交互响应速度"""
        self.open("https://example.com/form")

        start_time = time.time()
        self.type("#search-input", "test query")
        self.click("#search-btn")
        self.wait_for_element_visible(".results", timeout=10)
        interaction_time = time.time() - start_time

        self.assert_true(
            interaction_time < 2,
            f"交互响应时间过长:{interaction_time:.2f}秒"
        )

测试配置与维护

使用 YAML 管理测试配置

将测试配置与代码分离,便于管理和维护:

# config/test_config.yaml

environments:
  dev:
    base_url: "https://dev.example.com"
    api_url: "https://api-dev.example.com"
  staging:
    base_url: "https://staging.example.com"
    api_url: "https://api-staging.example.com"
  prod:
    base_url: "https://www.example.com"
    api_url: "https://api.example.com"

test_data:
  valid_user:
    email: "test@example.com"
    password: "TestPassword123!"
  test_products:
    - name: "测试商品1"
      price: 99.00
    - name: "测试商品2"
      price: 199.00

timeouts:
  default: 10
  long_wait: 30
  api_call: 5

在测试中读取配置:

import yaml
from seleniumbase import BaseCase

def load_config(env="dev"):
    """加载测试配置"""
    with open("config/test_config.yaml", "r", encoding="utf-8") as f:
        config = yaml.safe_load(f)
    return config["environments"][env]


class ConfigBasedTests(BaseCase):
    """基于配置的测试"""

    def setUp(self):
        super().setUp()
        self.config = load_config()

    def test_example(self):
        """使用配置中的 URL"""
        self.open(self.config["base_url"])
        # ...

维护测试代码的建议

编写易于维护的测试代码是长期成功的关键。以下是一些实践建议:

保持测试独立:每个测试应该能够独立运行,不依赖其他测试的执行结果。使用 setUp 和 tearDown 方法来管理测试状态。

使用描述性的测试名称:测试方法名应该清晰表达测试意图。pytest 会直接使用方法名作为测试报告中的名称。

合理组织测试文件:按照功能模块或测试类型组织测试文件,便于查找和维护。

定期重构测试代码:随着应用迭代,测试代码也需要更新。移除过时的测试,更新定位符,保持测试的有效性。

监控测试稳定性:跟踪 flaky 测试(不稳定测试),及时修复或移除它们。


集成与扩展

与 CI/CD 集成

SeleniumBase 可以轻松集成到主流的持续集成系统中。

GitHub Actions 配置:

# .github/workflows/test.yml

name: Selenium Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: |
          pip install seleniumbase
          seleniumbase install chromium

      - name: Run tests
        run: |
          pytest tests/ --dashboard --html=report.html
        env:
          BROWSER: chrome

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: test-results
          path: |
            report.html
            htmlcov/

Jenkins 配置:

pipeline {
    agent any

    stages {
        stage('Test') {
            steps {
                sh '''
                    pip install seleniumbase
                    seleniumbase install chromium
                    pytest tests/ -n=4 --dashboard
                '''
            }
            post {
                always {
                    junit 'test-results/*.xml'
                    publishHTML target: [
                        allowMissing: false,
                        alwaysLinkToLastBuild: true,
                        keepAll: true,
                        reportDir: 'htmlcov',
                        reportFiles: 'report.html',
                        reportName: 'Test Report'
                    ]
                }
            }
        }
    }
}

与视觉回归测试集成

结合视觉回归测试可以发现 UI 意外变化:

from seleniumbase import BaseCase
from seleniumbase.plugins.enhanced_drivers import VisualTester

class VisualRegressionTests(BaseCase, VisualTester):
    """视觉回归测试"""

    def test_homepage_visual(self):
        """检测首页视觉变化"""
        self.open("https://example.com")

        # 首次运行会保存基准截图
        # 后续运行会与基准进行像素对比
        self.assert视觉_match("homepage_base")

总结

SeleniumBase 将 Selenium WebDriver 的强大功能与实用的测试工具完美结合,为 Web 自动化测试提供了一个高效、可靠的解决方案。通过本文的详细介绍,你应该已经掌握了从环境搭建到高级技巧的完整知识体系。

核心要点回顾

SeleniumBase 简化了测试代码的编写,智能等待机制提高了测试稳定性,自动截图和报告功能让问题排查变得轻松,内置的并行执行能力大幅提升了测试效率,Page Object 模式让代码更易维护。掌握这些核心概念,你就能编写出高质量的自动化测试。

进一步学习资源

SeleniumBase 官方文档提供了最权威的参考资料:https://www.seleniumbase.io/

SeleniumBase 的 GitHub 仓库包含完整的源代码和示例:https://github.com/seleniumbase/SeleniumBase

pytest 官方文档帮助你深入了解测试框架的高级特性:https://docs.pytest.org/


立即开始你的 SeleniumBase 之旅,将自动化测试从负担转变为提升软件质量的有力武器。无论是个人项目还是团队协作,SeleniumBase 都能帮助你构建更可靠的 Web 应用。

如果内容对您有帮助,欢迎打赏

您的支持是我继续创作的动力

前往打赏页面

评论区

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注