Python Selenium 自动化测试设计模式

一、页面对象模型(Page Object Model, POM)—— 基石模式

✅ 核心思想

将每个 Web 页面封装为独立对象,分离页面元素定位测试逻辑,实现高内聚、低耦合。

📁 推荐项目结构

project/
├── base/               # 基础层(二次封装)
│   └── base_page.py
├── pages/              # 对象层(页面对象)
│   ├── login_page.py
│   └── dashboard_page.py
├── workflows/          # 逻辑层(业务流程封装)
│   └── user_workflow.py
├── tests/              # 业务层(测试用例)
│   └── test_login.py
├── utils/              # 工具层
│   ├── logger.py
│   └── screenshot.py
└── config.py           # 配置管理

1. 基础层 base/base_page.py

from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import logging, os, datetime

class BasePage:
    def __init__(self, driver: WebDriver, timeout=15):
        self.driver = driver
        self.timeout = timeout
        self.logger = logging.getLogger(__name__)
    
    def wait_and_find(self, locator, desc="element"):
        """智能等待 + 元素查找(含日志与异常处理)"""
        try:
            WebDriverWait(self.driver, self.timeout).until(
                EC.presence_of_element_located(locator)
            )
            ele = self.driver.find_element(*locator)
            self.logger.info(f"✓ 找到元素 [{desc}]")
            return ele
        except Exception as e:
            self._capture_screenshot(desc)
            self.logger.error(f"✗ 未找到元素 [{desc}]: {str(e)}")
            raise
    
    def click(self, locator, desc="button"):
        ele = self.wait_and_find(locator, desc)
        ele.click()
        self.logger.info(f"✓ 点击 [{desc}]")
    
    def input_text(self, locator, text, desc="input"):
        ele = self.wait_and_find(locator, desc)
        ele.clear()
        ele.send_keys(text)
        self.logger.info(f"✓ 输入 [{desc}]: {text}")
    
    def _capture_screenshot(self, name):
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        path = f"screenshots/{name}_{timestamp}.png"
        os.makedirs("screenshots", exist_ok=True)
        self.driver.save_screenshot(path)
        self.logger.info(f"📸 截图保存: {path}")

2. 对象层 pages/login_page.py

from selenium.webdriver.common.by import By
from base.base_page import BasePage

class LoginPage(BasePage):
    # 元素定位器集中管理(便于维护)
    USERNAME = (By.ID, "username")
    PASSWORD = (By.ID, "password")
    LOGIN_BTN = (By.ID, "loginButton")
    ERROR_MSG = (By.CLASS_NAME, "error-message")
    
    def __init__(self, driver):
        super().__init__(driver)
        self.driver.get("https://example.com/login")  # 可选:自动跳转
    
    def enter_username(self, username):
        self.input_text(self.USERNAME, username, "用户名框")
        return self  # 支持链式调用
    
    def enter_password(self, password):
        self.input_text(self.PASSWORD, password, "密码框")
        return self
    
    def click_login(self):
        self.click(self.LOGIN_BTN, "登录按钮")
        return self
    
    def login(self, username, password):
        """封装完整登录流程(原子操作组合)"""
        return (self.enter_username(username)
                    .enter_password(password)
                    .click_login())
    
    def get_error_message(self):
        return self.wait_and_find(self.ERROR_MSG, "错误提示").text

3. 测试用例 tests/test_login.py

import pytest
from selenium import webdriver
from pages.login_page import LoginPage

class TestLogin:
    @pytest.fixture(autouse=True)
    def setup(self):
        self.driver = webdriver.Chrome()
        yield
        self.driver.quit()
    
    def test_valid_login(self):
        login_page = LoginPage(self.driver)
        login_page.login("admin", "secure123")
        # 验证跳转(可结合DashboardPage进一步验证)
        assert "dashboard" in self.driver.current_url
    
    def test_invalid_login(self):
        login_page = LoginPage(self.driver)
        login_page.login("wrong", "pass")
        assert "用户名或密码错误" in login_page.get_error_message()

二、可加载组件模式(Loadable Component)—— 状态保障

✅ 核心思想

确保页面在操作前已完全加载且状态正确,避免因加载时序导致的测试不稳定。

💡 Python 实现方案(Selenium 无内置,需自定义)

from abc import ABC, abstractmethod
from selenium.common.exceptions import TimeoutException

class LoadableComponent(ABC):
    """可加载组件基类(Python版实现)"""
    def __init__(self, driver):
        self.driver = driver
        self.is_loaded_flag = False
    
    def open(self):
        """统一入口:加载页面 + 验证状态"""
        self.load()
        try:
            self.is_loaded()
            self.is_loaded_flag = True
            logging.info(f"✓ {self.__class__.__name__} 加载成功")
        except AssertionError as e:
            logging.error(f"✗ {self.__class__.__name__} 加载失败: {e}")
            raise
        return self
    
    @abstractmethod
    def load(self):
        """子类实现:导航到页面"""
        pass
    
    @abstractmethod
    def is_loaded(self):
        """子类实现:验证页面加载完成(断言关键元素/URL)"""
        pass


# 应用示例:继承 LoadableComponent 的登录页
class SafeLoginPage(LoadableComponent, BasePage):  # 多继承
    URL = "https://example.com/login"
    TITLE_KEYWORD = "登录"
    
    def load(self):
        self.driver.get(self.URL)
    
    def is_loaded(self):
        # 双重验证:URL + 标题
        assert self.URL in self.driver.current_url, "URL 不匹配"
        assert self.TITLE_KEYWORD in self.driver.title, "标题验证失败"
        # 可补充:关键元素可见性检查
        self.wait_and_find((By.ID, "loginForm"), "登录表单")
    
    # 保留原有操作方法...
    def login(self, user, pwd):
        if not self.is_loaded_flag:
            self.open()  # 自动触发加载验证
        # ...执行登录操作

🌟 优势

  • 防错机制:操作前自动验证页面状态

  • 调试友好:失败时明确提示“加载失败”而非“元素未找到”

  • 提升稳定性:解决动态内容加载导致的时序问题


三、机器人模式(Robot Pattern)—— 业务语义封装

✅ 核心思想

将测试步骤封装为高可读性、业务导向的命令,使测试脚本如自然语言般清晰。

💡 实现方案:工作流层 workflows/user_workflow.py

class UserRobot:
    """用户操作机器人:封装跨页面业务流程"""
    def __init__(self, driver):
        self.driver = driver
        self.login_page = None
        self.dashboard_page = None
    
    def login_as_admin(self):
        """高级别业务命令:登录管理员账号"""
        from pages.login_page import LoginPage
        from pages.dashboard_page import DashboardPage
        
        self.login_page = LoginPage(self.driver).open()  # 结合LoadableComponent
        self.login_page.login("admin", "secure123")
        
        # 自动跳转并验证仪表盘
        self.dashboard_page = DashboardPage(self.driver).open()
        return self.dashboard_page  # 返回目标页面对象
    
    def search_product(self, keyword):
        """封装搜索全流程(含结果验证)"""
        self.dashboard_page.search_bar.input(keyword)
        self.dashboard_page.search_button.click()
        assert keyword in self.driver.title, "搜索结果页标题异常"
        return self.dashboard_page.get_search_results()
    
    def logout(self):
        self.dashboard_page.click_settings().click_logout()
        assert "login" in self.driver.current_url

📝 测试脚本示例(极致可读性)

def test_admin_workflow():
    robot = UserRobot(driver)
    
    # 脚本如业务描述:
    dashboard = robot.login_as_admin()
    results = robot.search_product("Selenium设计模式")
    assert len(results) > 0
    
    robot.logout()
    logging.info("✅ 管理员全流程测试通过")

🔑 综合最佳实践建议

维度

推荐策略

说明

分层架构

基础层→对象层→逻辑层→业务层

职责清晰,修改隔离

等待策略

显式等待 + 封装(非time.sleep)

提升稳定性

配置管理

.env + config.py

敏感信息隔离,环境切换便捷

异常处理

基础层统一截图+日志

快速定位失败原因

扩展支持

无头模式/插件加载

ChromeOptions灵活配置

合规提醒

严格遵守网站ToS

避免法律风险


💎 总结

  1. POM 是骨架:解决元素定位与操作的封装问题

  2. Loadable Component 是安全锁:保障页面状态可信,提升测试鲁棒性

  3. Robot Pattern 是语言翻译器:将技术操作转化为业务语言,提升脚本可读性与协作效率

黄金组合POM(页面细节) + LoadableComponent(状态保障) + Robot(业务语义)
三者协同,可构建出高可维护、高稳定性、高可读性的企业级自动化测试体系。建议从小型项目开始实践分层,逐步完善工具链,避免过度设计。


Python Selenium 自动化测试设计模式
https://blog.cikaros.cn/archives/python-selenium-zi-dong-hua-ce-shi-she-ji-mo-shi
作者
Cikaros
发布于
2026年02月04日
许可协议