目录
概述
优点
示例
项目结构:
基础页面类BasePage
业务页面类BaiduHomePage
测试类test_baidu:
文件工具类file_util
运行日志:
测试结果:
在web应用程序的UI中,有一些区域可以与测试交互。页面对象仅将这些对象建模为测试代码中的对象。这减少了重复代码的数量,意味着如果UI发生更改,则只需在一个位置应用修复。
页面对象是一种在测试自动化中流行的设计模式,用于增强测试维护和减少代码重复。页面对象是一个面向对象的类,用作AUT页面的接口。然后,每当需要与该页面的UI交互时,测试就会使用该页面对象类的方法。好处是,如果页面的UI发生了更改,则测试本身不需要更改,只需要更改页面对象中的代码。随后,所有支持新UI的更改都位于同一位置。
测试代码和特定于页面的代码之间有一个清晰的分离,例如定位器(或者如果您使用的是UI映射,则使用它们)和布局。
演示一个百度搜索功能搜索关键字selenium并做断言,然后保存断言结果截图到对应日期文件夹。
封装页面基本操作,主要是跟业务不相关的公共方法,如:元素查找、点击、文本输入、截图、双击、获取元素坐标等,跟页面相关的方法不建议封装在基础页面类中,以下是一个简单的实例:
import logging
import time
import traceback
from selenium.webdriver import ActionChains
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
import selenium.webdriver.support.expected_conditions as EC
import os
from datetime import datetime
class Action:
def __init__(self, dirver):
self.driver = dirver
def find_element(self, loc):
try:
# WebDriverWait(self.driver, 10).until(EC.visibility_of_all_elements_located(loc))
WebDriverWait(self.driver, 15).until(lambda x: x.find_element(*loc).is_displayed())
element = self.driver.find_element(*loc)
self.get_element_coordinate(element)
except Exception as e:
traceback.print_exception(e)
traceback.print_stack()
logging.info("页面中没有%s %" % (self.loc[1]))
else:
return element
def find_elements(self, loc):
try:
# 保证元素可见
WebDriverWait(self.driver, 15).until(EC.visibility_of_all_elements_located(loc))
elements = self.driver.find_elements(*loc)
except Exception as e:
traceback.print_exception(e)
traceback.print_stack()
logging.info("页面中没有%s %" % (self.loc[1]))
else:
return elements
def get_element_coordinate(self, element):
rect = element.rect
x, y = rect['x'], rect['y']
size = element.size
width = size['width']
height = size['height']
left_up = (x, y)
left_down = (x, y + height)
center = (x + 0.5 * width, y + 0.5 * height)
right_up = (x + width, y)
right_down = (x + width, y + height)
element_coordinate = dict(left_up=left_up, left_down=left_down, center=center, right_up=right_up,
right_down=right_down)
logging.info(element_coordinate)
print(element_coordinate)
return element_coordinate
def click_element(self, loc):
self.find_element(loc).click()
def save_element_picture(self, loc, file_name):
element = self.find_element(loc)
element.screenshot(file_name)
def save_picture(self, filename, browser='FireFox'):
file_path = os.path.dirname(os.path.dirname(__file__))
now = datetime.now()
picture_date_dir = now.strftime("%Y-%m-%d")
formatted_date = now.strftime("%Y-%m-%d-%H-%M-%S")
file_path = file_path + '\\picture\\' + picture_date_dir
picture_path = file_path + '\\' + filename + '_' + formatted_date + '.png'
print(picture_path)
# 检查文件路径是否存在
if not os.path.exists(file_path):
print('创建文件路径')
# 如果文件路径不存在,创建它
os.makedirs(file_path)
if browser == 'FireFox':
self.driver.save_full_page_screenshot(picture_path)
elif browser == 'Chrome':
self.driver.get_screenshot_as_file(picture_path)
def save_long_picture(self, filename, browser='FireFox'):
# file_path=
# 截长图
self.driver.save_full_page_screenshot(filename)
def click_element_with_coordinate(self, x, y):
action = ActionBuilder(self.driver)
action.pointer_action.move_to_location(x, y).click()
action.perform()
def double_click(self, loc):
element = self.find_element(loc)
ActionChains(self.driver).double_click(element).perform()
def set_high_light_elment(self, element):
script = '''
// 高亮显示元素
arguments[0].style.backgroundColor = "yellow";
// 设置红色边框
arguments[0].style.border = "3px solid red";
'''
self.driver.execute_script(script, element)
pass
def browser_2_windows_coordinates_v2(self, browserX, browserY, screenWidth=1360, screenHeight=768,
desktopScale=1):
# location = self.get_element_location(element)
# x, y = location['left_up'][0], location['left_up'][1]
script = '''
function getDesktopCoordinates(browserX, browserY,screenWidth,screenHeight,desktopScale) {{
// 浏览器中的坐标(x, y)
var browserX = browserX;
var browserY = browserY;
// 屏幕分辨率
var screenWidth = screenWidth;
var screenHeight = screenHeight;
// 桌面缩放比例
var desktopScale = desktopScale;
//- 浏览器窗口左上角的桌面坐标为(win_x, win_y)。
var win_x = window.screenX || window.screenLeft;
var win_y = window.screenY || window.screenTop;
//计算工具栏高度
var toolbarHeight = window.outerHeight - window.innerHeight;
// 计算桌面坐标
var desktopX =(win_x+ browserX) * (screenWidth/window.innerWidth) ;
var desktopY =(win_y+ browserY+toolbarHeight) * (screenHeight/ window.innerHeight );
console.log("桌面坐标 (x, y):", desktopX, desktopY);
// 创建包含坐标的对象
var desktopCoordinates = {{
desktopX: desktopX,
desktopY: desktopY
}};
return desktopCoordinates;
}}
var coordinates = getDesktopCoordinates({browserX}, {browserY},{screenWidth},{screenHeight},{desktopScale});
return coordinates;
'''.format(browserX=browserX, browserY=browserY, screenWidth=screenWidth, screenHeight=screenHeight,
desktopScale=desktopScale)
logging.info(script)
desktopCoordinates = self.driver.execute_script(script)
logging.info(desktopCoordinates)
return desktopCoordinates
def mark_dom(self, x, y, color='red'):
script = '''
// 创建黑点DOM
const dot = document.createElement('div');
dot.style.position = 'absolute';
dot.style.width = '10px';
dot.style.height = '10px';
dot.style.backgroundColor = '{}';
dot.style.borderRadius = '50%';
dot.style.left = {} + 'px';
dot.style.top = {} + 'px';
document.body.appendChild(dot);
'''.format(color, round(x, 0), round(y, 0))
logging.info(script)
print(script)
self.driver.execute_script(script)
def mark_dom_text(self, text, x, y):
script = '''
// 创建一个新的标记元素
var newElement = document.createElement("span");
// 设置标记元素的文本内容
newElement.innerText = "{}";
// 设置标记元素的位置样式
newElement.style.position = "absolute";
newElement.style.left = "{}px";
newElement.style.top = "{}px";
newElement.style.color = "red";
// 将新的标记元素附加到目标元素中
document.body.appendChild(newElement);
'''.format(text, round(x, 0), round(y, 0))
logging.info(script)
self.driver.execute_script(script)
该类主要是描述待测页面中的元素及对应的操作,当页面元素发生变更时可以快速在该页面进行修改降低了业务代码和测试代码的耦合性
import time
import pyautogui as pyautogui
from selenium.webdriver.common.by import By
from PythonPractise.selenium_po.page import BasePage
class BaiduHomePage(BasePage.Action):
search_input_loc = (By.ID, '''kw''')
search_btn_loc = (By.ID, '''su''')
def search_text(self):
search_input=self.find_element(self.search_input_loc)
self.set_high_light_elment(search_input)
location=self.get_element_coordinate(search_input)
browser_x,browser_y=location['center'][0],location['center'][1]
self.mark_dom(browser_x,browser_y)
desktopCoordinates=self.browser_2_windows_coordinates_v2(browser_x,browser_y)
x,y=desktopCoordinates['desktopX'],desktopCoordinates['desktopY']
# 移动到拖拽元素中心坐标
pyautogui.moveTo(x, y, duration=1, tween=pyautogui.linear)
time.sleep(10)
self.find_element(self.search_input_loc).send_keys('Selenium')
self.click_element(self.search_btn_loc)
self.save_picture('selenium')
测试方法类,主要描述对页面逻辑测试的验证,在此方法中会初始话待测页面类,如本例中的百度首页的测试,本类主要关注业务逻辑的操作及验证,引入页面对象降低测试代码和业务代码的耦合性增强了代码的可读性,也降低了和页面代码的耦合性。
该方法首先打开百度首页,然后搜索对应的关键字并做校验,方法结束后关闭测试会话。
import time
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from webdriver_manager.firefox import GeckoDriverManager
from PythonPractise.selenium_po.page import BaiduHomePage
from selenium import webdriver
class TestCase:
def setup_class(self):
# self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
# self.driver = webdriver.Chrome(executable_path='F:\\PycharmProjects\\PythonPractise\\selenium_po\\driver\\chromedriver.exe')
self.driver= webdriver.Firefox(service=FirefoxService(GeckoDriverManager().install()))
self.driver.get("http://www.baidu.com")
def test_baidu_search(self):
baidu_home_page = BaiduHomePage.BaiduHomePage(self.driver)
baidu_home_page.search_text()
time.sleep(5)
baidu_home_page.save_picture('selenium')
assert 'selenium' in self.driver.page_source
def teardown_class(self):
self.driver.quit()
此方法主要是定义项目的文件位置方便后面的截图文件、日志文件等其他测试过程中产生的文件的保存,大家根据实际需求进行扩展。
import os
from datetime import datetime
# 获取当前日期和时间
now = datetime.now()
# 格式化为 yyyy-mm-dd
formatted_date = now.strftime("%Y-%m-%d :%H:%M:%S")
print(formatted_date)
project_path = os.path.dirname(os.path.dirname(__file__))
print(project_path)
E:\Python3.11\python.exe "E:/PyCharm Community Edition 2023.1.1/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path F:\PycharmProjects\PythonPractise\selenium_po\testcase\test_baidu.py
Testing started at 15:42 ...
Launching pytest with arguments F:\PycharmProjects\PythonPractise\selenium_po\testcase\test_baidu.py --no-header --no-summary -q in F:\PycharmProjects\PythonPractise\selenium_po\testcase
============================= test session starts =============================
collecting ... collected 1 item
test_baidu.py::TestCase::test_baidu_search
============================= 1 passed in 42.88s ==============================
PASSED [100%]{'left_up': (353.0, 188.3999938964844), 'left_down': (353.0, 232.3999938964844), 'center': (628.0, 210.3999938964844), 'right_up': (903.0, 188.3999938964844), 'right_down': (903.0, 232.3999938964844)}
{'left_up': (353.0, 188.3999938964844), 'left_down': (353.0, 234.3999938964844), 'center': (629.0, 211.3999938964844), 'right_up': (905.0, 188.3999938964844), 'right_down': (905.0, 234.3999938964844)}
// 创建黑点DOM
const dot = document.createElement('div');
dot.style.position = 'absolute';
dot.style.width = '10px';
dot.style.height = '10px';
dot.style.backgroundColor = 'red';
dot.style.borderRadius = '50%';
dot.style.left = 629.0 + 'px';
dot.style.top = 211.0 + 'px';
document.body.appendChild(dot);
{'left_up': (353.0, 188.3999938964844), 'left_down': (353.0, 234.3999938964844), 'center': (629.0, 211.3999938964844), 'right_up': (905.0, 188.3999938964844), 'right_down': (905.0, 234.3999938964844)}
{'left_up': (725.0, 15.0), 'left_down': (725.0, 55.0), 'center': (781.0, 35.0), 'right_up': (837.0, 15.0), 'right_down': (837.0, 55.0)}
F:\PycharmProjects\PythonPractise\selenium_po\picture\2023-12-02\selenium_2023-12-02-15-42-56.png
F:\PycharmProjects\PythonPractise\selenium_po\picture\2023-12-02\selenium_2023-12-02-15-43-01.png
进程已结束,退出代码0