Python 中搭建一个完整的 Selenium +unitest框架需要考虑多个方面,包括浏览器驱动管理、页面元素定位、测试用例组织、日志记录和报告生成等。以下是一个结构化的实现方案:
推荐采用分层架构,将不同功能分离:
dsr_selenium/
├── config/ # 配置文件
│ ├── config.ini # 全局配置
│ └── browser.yaml # 浏览器配置
├── drivers/ # 浏览器驱动(可通过WebDriverManager自动管理)
├── pages/ # 页面对象模型
│ ├── base_page.py # 基础页面对象
│ ├── login_page.py # 登录页面对象
│ └── home_page.py # 主页对象
├── tests/ # 测试用例
│ ├── test_base.py # 测试基类
│ ├── test_login.py # 登录测试
│ └── test_search.py # 搜索测试
├── utils/ # 工具类
│ ├── logger.py # 日志工具
│ ├── driver_factory.py # 驱动工厂
│ └── excel_reader.py # 数据读取工具
├── reports/ # 测试报告
├── screenshots/ # 截图目录
├── requirements.txt # 依赖包
└── run_tests.py # 测试执行入口
#设置测试链接base_url
[environment]
base_url = https://example.com
timeout = 10
#设置测试浏览器chrome
[browser]
browser_name = chrome
headless = false
window_size = 1920x1080
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
#浏览器渠道管理(工厂模式),这里是适配谷歌浏览器、火狐浏览器,
class DriverFactory:
@staticmethod
def create_driver(browser_name, headless=False, window_size=None):
if browser_name.lower() == "chrome":
#这里我设置直接使用我之前用的镜像,如果使用镜像管理会慢很多,如果本身下载了镜像建议使用我这种写法
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(30)
return driver
#使用driver服务管理,自动适配镜像,但是这种会很慢,容易报错,不建议使用这个写法
# options = webdriver.ChromeOptions()
# if headless:
# options.add_argument("--headless")
# if window_size:
# options.add_argument(f"--window-size={window_size}")
# service = Service(ChromeDriverManager().install())
# return webdriver.Chrome(service=service, options=options)
elif browser_name.lower() == "firefox":
options = webdriver.FirefoxOptions()
if headless:
options.add_argument("--headless")
service = Service(GeckoDriverManager().install())
return webdriver.Firefox(service=service, options=options)
else:
raise ValueError(f"Unsupported browser: {browser_name}")
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# pages/base_page.py,页面对象模型基础类,属于父类,这里设置页面操作组件,后面子类可以直接继承使用
class BasePage:
def __init__(self, driver, timeout=10):
self.driver = driver
self.timeout = timeout
def find_element(self, by):
return WebDriverWait(self.driver, self.timeout).until(
EC.presence_of_element_located((by))
)
def click(self, by):
element = WebDriverWait(self.driver, self.timeout).until(
EC.element_to_be_clickable((by))
)
element.click()
def send_keys(self, by, text):
element = self.find_element(by)
element.clear()
element.send_keys(text)
def get_title(self):
return self.driver.title
from pages.base_page import BasePage
from selenium.webdriver.common.by import By
# pages/login_page.py,页面对象模型,子类,继承base_page,存放具体的页面属性
class LoginPage(BasePage):
USERNAME_INPUT = (By.ID, "username")
PASSWORD_INPUT = (By.ID, "password")
LOGIN_BUTTON = (By.ID, "login-button")
def login(self, username, password):
self.send_keys(self.USERNAME_INPUT, username)
self.send_keys(self.PASSWORD_INPUT, password)
self.click(self.LOGIN_BUTTON)
#测试用例基本类,读取config.ini配置,设置浏览器驱动,测试链接
import time
import unittest
from utils.driver_factory import DriverFactory
from configparser import ConfigParser
#设置浏览器配置,setUpClass()方法初始化测试环境,这里设置浏览器对象属性(也可以连接数据库),在测试所有用例之前执行,只执行一次
class TestBase(unittest.TestCase):
@classmethod
def setUpClass(cls):
config = ConfigParser()
config.read("config/config.ini")
cls.browser = config.get("browser", "browser_name")
cls.headless = config.getboolean("browser", "headless")
cls.window_size = config.get("browser", "window_size")
cls.base_url = config.get("environment", "base_url")
cls.timeout = config.getint("environment", "timeout")
cls.driver = DriverFactory.create_driver(
cls.browser, cls.headless, cls.window_size
)
cls.driver.maximize_window()
cls.driver.implicitly_wait(cls.timeout)
#setUp()方法为单个测试用例准备环境,这里设置driver浏览器对象,在setUpClass()后面执行
def setUp(self):
self.driver.get(self.base_url)
#tearDownClass()方法清理共享环境,这里清除driver浏览器对象,即关闭浏览器(也可以设置关闭连接数据库)
@classmethod
def tearDownClass(cls):
if cls.driver:
cls.driver.quit()
from selenium.webdriver.common.by import By
from tests.test_base import TestBase
from pages.login_page import LoginPage
#创建测试用例实例类,存放具体的用例,包括正向、反向
class TestLogin(TestBase):
#创建登录成功用例方法
def test_successful_login(self):
login_page = LoginPage(self.driver)
login_page.login("valid_username", "valid_password")
# 验证登录成功后的页面元素
welcome_text = self.driver.find_element(By.CSS_SELECTOR, ".welcome-message").text
self.assertEqual(welcome_text, "Welcome, User!")
#创建登录失败用例方法
def test_failed_login(self):
login_page = LoginPage(self.driver)
login_page.login("invalid_username", "invalid_password")
#验证登录失败提示是否正常
error_message = self.driver.find_element(By.CSS_SELECTOR, ".error-message").text
self.assertEqual(error_message, "Invalid credentials")
import logging
import os
#报错打印报错内容,report/report报告显示这个报错内容
def setup_logger(name, log_file, level=logging.INFO):
os.makedirs(os.path.dirname(log_file), exist_ok=True)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger = logging.getLogger(name)
logger.setLevel(level)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
return logger
# 使用示例
logger = setup_logger("selenium_logger", "logs/selenium.log")
import unittest
from unittest import TestLoader, TestSuite
from HtmlTestRunner import HTMLTestRunner
import os
# 创建报告目录
os.makedirs("reports", exist_ok=True)
print("创建测试报告目录")
# 加载测试用例
loader = TestLoader()
suite = TestSuite()
print("加载测试用例成功")
# 添加测试模块,将模块加入测试用例队列中,然后按照排序执行
suite.addTests(loader.loadTestsFromName("tests.test_login"))
# suite.addTests(loader.loadTestsFromName("tests.test_search"))
print('添加测试模板成功')
# 运行测试并生成HTML报告
with open("reports/test_report.html", "w") as f:
runner = HTMLTestRunner(
stream=f,
report_title="Selenium Test Report",
descriptions=True,
verbosity=2
)
runner.run(suite)
print('测试报告已生成,请到reports/test_report.html目录查看')
selenium==4.0.0
webdriver-manager==4.0.0
html-testRunner==1.3.0
configparser==5.3.0
PyYAML==6.0
注意selenium必须是安装4.0.0以上的版本,可以自己更新,如pip install selenium==4.0.0
使用parameterized
库实现多组测试数据:
from parameterized import parameterized
class TestLogin(TestBase):
@parameterized.expand([
("valid_user", "user1", "pass1", True),
("invalid_user", "user2", "wrong", False),
])
def test_login(self, name, username, password, expected):
login_page = LoginPage(self.driver)
login_page.login(username, password)
# 验证结果
self.assertEqual(self.is_logged_in(), expected)
在基类中添加异常处理和截图功能:
def tearDown(self):
if self._outcome.errors[1][1]: # 如果测试失败
test_name = self._testMethodName
self.driver.save_screenshot(f"screenshots/{test_name}.png")
pip install -r requirements.txt
#如果文件类型安装依赖失败,可以逐个进行安装如
pip install selenium==4.0.0
pip install webdriver-manager==4.0.0
pip install thml-testRunner==1.2.1
pip install configparser==5.3.0
pip install PyYAML==6.0
python run_tests.py
reports/test_report.html
查看测试结果。如下图,如果显示这样的测试报告证明已经搭建成功,可以进行业务编写。(非VIP用户,评论后领取免费源代码)
这个框架包含了 Selenium 自动化的核心组件:
根据项目需求,你还可以进一步扩展,如集成 Allure 报告、添加 API 测试模块、实现分布式测试等。
备注
安装依赖时可能会多次失败,可能时网络问题可以失败可以重复安装,如果多次失败可以更改安装的版本,如遇到具体的安装错误可以网上查询解决方案。