下载地址:https://gitee.com/xiaopo1998/web_ui_test.git
Selenium 页面对象模型框架使用说明
本框架基于 Selenium WebDriver 实现了一套模块化、易维护的页面对象模型(Page Object Model)架构,将不同类型的操作按职责分离到不同的类中。
框架结构
框架采用分层设计,由以下主要组件组成:
core/
├── base_page.py # 基础页面类,包含核心操作方法及其他组件的访问
├── element_actions.py # 元素等待和检查操作
├── action_chains.py # 鼠标和键盘高级操作
├── browser_operations.py # 窗口、框架和警告框操作
├── browser_utils.py # Cookie、JavaScript和截图等辅助功能
├── select_operations.py # 下拉框专用操作
├── webdriver.py # WebDriver管理
└── logger.py # 日志管理
组件说明
- BasePage (base_page.py)
基础页面类是整个框架的核心,负责提供基本的页面操作方法,并作为访问其他专用组件的入口点。它包含以下核心功能:
提供基本的元素查找方法
提供常用的元素操作方法(点击、输入、获取文本等)
负责实例化和提供对所有专用组件的访问
- ElementActions (element_actions.py)
元素等待和检查操作类,专门负责处理元素的可见性、存在性等检查和等待操作。主要功能包括:
提供各种元素等待方法(等待可见、等待可点击等)
提供元素状态检查方法(是否存在、是否可见等)
提供文本和属性相关的等待方法
- ActionChainOperations (action_chains.py)
鼠标和键盘高级操作类,负责处理复杂的交互操作。主要功能包括:
鼠标操作(悬停、拖放、右键点击等)
键盘操作(按键、组合键等)
其他高级交互操作
- BrowserOperations (browser_operations.py)
浏览器操作类,负责处理窗口、框架和警告框等操作。主要功能包括:
窗口操作(切换、关闭、获取句柄等)
框架操作(切换框架、返回主文档等)
警告框操作(接受、取消、获取文本等)
页面导航(前进、后退、刷新等)
- BrowserUtils (browser_utils.py)
浏览器工具类,提供 Cookie 管理、JavaScript 执行和截图等辅助功能。主要功能包括:
Cookie 管理(获取、添加、删除等)
JavaScript 执行(滚动页面等)
截图功能(全页面截图、元素截图等)
- SelectOperations (select_operations.py)
下拉框操作类,专门处理下拉框元素的操作。主要功能包括:
选择选项(通过文本、值、索引等)
获取选中选项信息
多选下拉框操作(选择多个、取消选择等)
使用方法
初始化
首先需要初始化 BasePage 对象,它是框架的核心入口点:
from selenium import webdriver
from core.base_page import BasePage
初始化WebDriver
driver = webdriver.Chrome()
创建BasePage实例
page = BasePage(driver)
导航到页面
page.navigate_to("https://example.com")
基本元素操作
BasePage 类提供了最基本的元素操作方法:
定义元素定位器
from selenium.webdriver.common.by import By
username_locator = (By.ID, "username")
password_locator = (By.ID, "password")
login_button_locator = (By.ID, "login")
查找元素
username_element = page.find_element(username_locator)
基本元素操作
page.input_text(username_locator, "test_user")
page.input_text(password_locator, "password123")
page.click(login_button_locator)
获取元素文本
text = page.get_text((By.ID, "welcome_message"))
print(text)
获取元素属性
value = page.get_attribute((By.ID, "username"), "value")
元素等待与检查
通过 element 属性访问元素等待和检查功能:
等待元素可见
page.element.wait_for_element_visible((By.ID, "dashboard"))
检查元素是否存在
if page.element.is_element_present((By.ID, "error_message")):
print("登录失败")
等待文本出现
page.element.wait_for_text_present((By.ID, "status"), "成功")
高亮显示元素(调试时很有用)
page.element.highlight_element((By.LINK_TEXT, "用户设置"))
等待元素不可见
page.element.wait_for_element_not_visible((By.ID, "loading"))
滚动到元素位置
page.element.scroll_to_element((By.ID, "footer"))
鼠标和键盘操作
通过 actions 属性访问鼠标和键盘高级操作:
from selenium.webdriver.common.keys import Keys
鼠标悬停
page.actions.mouse_hover((By.ID, "menu"))
右键点击
page.actions.right_click((By.ID, "context_menu"))
双击
page.actions.double_click((By.ID, "double_click_element"))
拖放操作
page.actions.drag_and_drop((By.ID, "source"), (By.ID, "target"))
按键操作
page.actions.press_key((By.ID, "search_box"), Keys.ENTER)
组合键
page.actions.press_key_sequence((By.ID, "text_editor"), Keys.CONTROL, "a")
点击并按住
page.actions.click_and_hold((By.ID, "slider"))
释放
page.actions.release()
移动鼠标
page.actions.move_by_offset(10, 20)
浏览器窗口和框架操作
通过 browser 属性访问窗口、框架和警告框操作:
窗口操作
original_handle = page.browser.get_current_window_handle()
page.browser.open_new_tab("https://example.com/other_page")
page.browser.switch_to_window(window_index=0) # 切回原窗口
page.browser.close_current_window()
框架操作
page.browser.switch_to_frame((By.ID, "iframe_id"))
page.browser.switch_to_default_content()
page.browser.switch_to_parent_frame()
警告框操作
page.browser.accept_alert()
alert_text = page.browser.get_alert_text()
page.browser.dismiss_alert()
page.browser.send_text_to_alert("输入内容")
导航操作
page.browser.refresh_page()
page.browser.go_back()
page.browser.go_forward()
获取页面源码
html = page.browser.get_page_source()
等待页面加载
page.browser.wait_for_page_load()
Cookie和JavaScript操作
通过 utils 属性访问 Cookie、JavaScript 和截图功能:
Cookie操作
cookies = page.utils.get_cookies()
cookie = page.utils.get_cookie("session_id")
page.utils.add_cookie({"name": "session", "value": "123456"})
page.utils.delete_cookie("old_cookie")
page.utils.delete_all_cookies()
JavaScript操作
page.utils.scroll_to_top()
page.utils.scroll_to_bottom()
page.utils.scroll_by(0, 500) # 向下滚动500像素
截图
screenshot_path = page.utils.take_screenshot("login_page.png")
element_screenshot = page.utils.take_element_screenshot((By.ID, "error"), "error.png")
下拉框操作
通过 select 属性访问下拉框专用操作:
选择下拉框选项
page.select.select_by_text((By.ID, "country"), "中国")
page.select.select_by_value((By.ID, "country"), "CN")
page.select.select_by_index((By.ID, "country"), 0)
获取选中的选项
selected_text = page.select.get_selected_text((By.ID, "country"))
selected_value = page.select.get_selected_value((By.ID, "country"))
获取所有选项
all_options = page.select.get_all_options((By.ID, "country"))
all_option_texts = page.select.get_all_options_text((By.ID, "country"))
all_option_values = page.select.get_all_options_value((By.ID, "country"))
判断下拉框是否支持多选
is_multiple = page.select.is_multiple((By.ID, "multi_select"))
多选下拉框操作
if page.select.is_multiple((By.ID, "multi_select")):
page.select.select_by_text((By.ID, "multi_select"), "选项1")
page.select.select_by_text((By.ID, "multi_select"), "选项2")
page.select.deselect_by_text((By.ID, "multi_select"), "选项1")
page.select.deselect_all((By.ID, "multi_select"))
自定义页面类
在实际项目中,建议为每个页面创建一个继承自 BasePage 的子类:
from core.base_page import BasePage
from selenium.webdriver.common.by import By
class LoginPage(BasePage):
定义页面元素定位器
USERNAME_INPUT = (By.ID, "username")
PASSWORD_INPUT = (By.ID, "password")
LOGIN_BUTTON = (By.ID, "login")
ERROR_MESSAGE = (By.ID, "error")
def login(self, username, password):
"""
登录操作
:param username: 用户名
:param password: 密码
"""
self.input_text(self.USERNAME_INPUT, username)
self.input_text(self.PASSWORD_INPUT, password)
self.click(self.LOGIN_BUTTON)
def get_error_message(self):
"""
获取错误信息
:return: 错误信息文本
"""
if self.element.is_element_visible(self.ERROR_MESSAGE):
return self.get_text(self.ERROR_MESSAGE)
return None
def wait_for_login_success(self):
"""
等待登录成功
"""
self.element.wait_for_element_not_visible(self.ERROR_MESSAGE)
self.browser.wait_for_page_load()
然后可以这样使用:
初始化页面
login_page = LoginPage(driver)
login_page.navigate_to("https://example.com/login")
执行登录
login_page.login("user123", "password123")
检查结果
error = login_page.get_error_message()
if error:
print(f"登录失败: {error}")
else:
print("登录成功")
最佳实践
按页面组织代码:为每个页面创建一个继承自 BasePage 的类
封装业务操作:将多个基本操作封装为有意义的业务方法
使用显式等待:始终使用等待方法确保元素就绪
保持定位器在页面类中集中定义:便于维护
使用适当的组件:根据操作类型选择合适的组件(element, actions, browser, utils, select)
采用合理的命名:方法名和变量名应当清晰地表明其作用
添加详细注释:对复杂操作添加注释说明
处理异常情况:捕获和处理可能的异常
异常处理
框架中的大多数方法都会记录日志并抛出异常(如 TimeoutException)。建议在测试代码中捕获这些异常:
try:
page.click((By.ID, "non_existent_button"))
except Exception as e:
print(f"操作失败: {e}")
page.utils.take_screenshot("error.png")
扩展框架
如需添加新功能,可以扩展现有类或创建新的操作类:
扩展ElementActions类
from core.element_actions import ElementActions
from selenium.webdriver.support.wait import WebDriverWait
class ExtendedElementActions(ElementActions):
def wait_for_element_count(self, locator, count, timeout=None):
"""
等待元素数量达到预期值
:param locator: 元素定位器
:param count: 期望的元素数量
:param timeout: 超时时间
"""
timeout = timeout or self.config['browser']['implicit_wait']
self.log.debug(f"等待元素 {locator} 数量达到 {count}")
def check_count(driver):
elements = driver.find_elements(*locator)
return len(elements) == count
WebDriverWait(self.driver, timeout).until(check_count)
常见问题
元素找不到
确保使用正确的定位器
检查元素是否在页面中可见
考虑使用等待方法确保元素已加载
操作没有效果
检查元素是否被其他元素遮挡
确保元素处于可交互状态
尝试使用 JavaScript 执行操作
框架切换问题
操作完iframe中的元素后,记得切回默认内容
嵌套iframe需要逐层切换
多窗口操作
切换到新窗口后,如需返回原窗口,记得保存原窗口的句柄
注意关闭窗口后句柄列表会变化
元素选择问题
如果一个定位器匹配多个元素,find_element会返回第一个匹配的元素
可以使用更精确的定位策略或组合多个定位条件
更多示例
表单操作
加载表单页面
page.navigate_to("https://example.com/form")
输入文本
page.input_text((By.NAME, "first_name"), "张")
page.input_text((By.NAME, "last_name"), "三")
选择下拉框
page.select.select_by_text((By.NAME, "country"), "中国")
选择单选按钮
page.click((By.CSS_SELECTOR, "input[name='gender'][value='male']"))
勾选复选框
page.click((By.NAME, "agreement"))
提交表单
page.click((By.CSS_SELECTOR, "button[type='submit']"))
等待结果
page.element.wait_for_text_present((By.ID, "result"), "提交成功")
表格操作
获取表格所有行
rows = page.find_elements((By.CSS_SELECTOR, "table tr"))
遍历行
for row in rows[1:]: # 跳过表头
获取单元格
cells = row.find_elements_by_tag_name("td")
if len(cells) > 0:
print(f"第一列: {cells[0].text}, 第二列: {cells[1].text}")
# 点击行中的按钮
if len(cells) > 3:
edit_button = cells[3].find_element_by_tag_name("button")
edit_button.click()
break
文件上传
import os
上传文件
file_path = os.path.abspath("./test_file.txt")
page.input_text((By.ID, "file_input"), file_path)
点击上传按钮
page.click((By.ID, "upload_button"))
等待上传完成
page.element.wait_for_text_present((By.ID, "upload_status"), "上传成功")
测试用例示例
完整登录-搜索-购买流程
以下是一个完整的电商网站测试案例,展示如何将所有知识点整合起来:
import unittest
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from pages.login_page import LoginPage
from pages.home_page import HomePage
from pages.product_page import ProductPage
from pages.cart_page import CartPage
from pages.checkout_page import CheckoutPage
import time
class ShoppingTest(unittest.TestCase):
def setUp(self):
# 初始化WebDriver
self.driver = webdriver.Chrome()
self.driver.maximize_window()
# 初始化所有页面对象
self.login_page = LoginPage(self.driver)
self.home_page = HomePage(self.driver)
self.product_page = ProductPage(self.driver)
self.cart_page = CartPage(self.driver)
self.checkout_page = CheckoutPage(self.driver)
def tearDown(self):
# 测试结束,关闭浏览器
if self.driver:
self.driver.quit()
@screenshot_on_failure # 使用前面定义的装饰器
def test_purchase_flow(self):
"""测试完整购买流程"""
# 测试数据
username = "test_user"
password = "Test@123"
product_name = "测试商品"
# 1. 登录
self.login_page.navigate_to("https://example.com/login")
self.login_page.wait_for_page_load()
self.login_page.login(username, password)
# 验证登录成功
user_welcome = self.home_page.get_welcome_message()
self.assertIn(username, user_welcome, "登录后欢迎信息未显示用户名")
# 2. 搜索商品
self.home_page.search_product(product_name)
# 验证搜索结果
search_count = self.home_page.get_search_results_count()
self.assertGreater(search_count, 0, "搜索无结果")
# 3. 打开商品详情
self.home_page.click_first_product()
# 验证商品详情页
product_title = self.product_page.get_product_title()
self.assertIn(product_name, product_title, f"商品详情页不匹配,期望包含'{product_name}'")
# 4. 添加到购物车
self.product_page.add_to_cart()
# 5. 查看购物车
self.product_page.go_to_cart()
# 验证购物车
cart_items = self.cart_page.get_cart_items()
self.assertGreaterEqual(len(cart_items), 1, "购物车为空")
# 6. 进入结账流程
self.cart_page.proceed_to_checkout()
# 7. 填写收货信息
self.checkout_page.fill_shipping_info({
"name": "张三",
"phone": "13800138000",
"address": "北京市海淀区某某街道",
"zipcode": "100000"
})
# 8. 选择支付方式
self.checkout_page.select_payment_method("cod") # cash on delivery
# 9. 提交订单
order_number = self.checkout_page.place_order()
# 验证订单提交成功
success_message = self.checkout_page.get_success_message()
self.assertIn("订单已成功提交", success_message)
self.assertIsNotNone(order_number, "订单号未生成")
# 10. 截取成功页面截图
self.checkout_page.utils.take_screenshot(f"order_success_{order_number}.png")
print(f"测试完成,订单号: {order_number}")
if name == "main ":
unittest.main()