代码层级:
python
项目根目录/
├── conftest.py # 必选:pytest全局夹具(前置/后置/失败截图)
├── pytest.ini # 必选:pytest配置文件(自定义用例规则/日志)
├── requirements.txt # 必选:环境依赖清单(一键还原环境)
├── run.py # 必选:用例执行入口(统一运行/生成报告)
├── commons/ # 必选:通用工具层
│ ├── base_page.py # 必选:基础数据封装
│ ├── login_page.py # 必选:页面对象
└── tests/ # 必选:测试用例层
└── test_login.py # 示例:登录测试用例
base_page.py
python
import allure
from allure_commons.types import AttachmentType
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage:
message_xpath = '//p[@class="msg"]'
def __init__(self,driver):
self.driver = driver
self.wait = WebDriverWait(driver,10)
allure.attach(
driver.get_screenshot_as_png(),
attachment_type=AttachmentType.PNG
)
@allure.step('元素定位')
def find_element(self,locator):
el = self.wait.until(EC.visibility_of_element_located(locator))
return el
# 点击事件
@allure.step('点击按钮')
def click(self,locator):
el = self.wait.until(EC.element_to_be_clickable(locator))
el.click()
# 输入事件
@allure.step('输入内容')
def send_keys(self,locator,text):
el = self.wait.until((EC.element_to_be_clickable(locator)))
el.clear()
el.send_keys(text)
# 框架切换
@allure.step('切换iframe')
def frame(self,url):
el = self.driver.find_element(By.XPATH,f'//iframe[starts-with(@src,"{url}")]')
self.driver.switch_to.frame(el)
# 获取提示语句
@allure.step('获取提示')
def get_msg(self):
el = self.find_element((By.XPATH,self.message_xpath))
msg = self.wait.until(lambda d: el.text)
return msg
login_page.py
python
import allure
from selenium.webdriver.common.by import By
from commons.base_page import BasePage
class LoginPage(BasePage):
username_locator = (By.XPATH, '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/input')
password_locator = (By.XPATH, '/html/body/div[4]/div/div[2]/div[2]/div/div/div[2]/input')
login_btn_locator = (By.XPATH, '/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/button')
@allure.step('进行登录')
def login(self, username, password):
self.send_keys(self.username_locator, username)
self.send_keys(self.password_locator, password)
self.click(self.login_btn_locator)
msg = self.get_msg()
return msg
test_login.py
python
import allure
import pytest
from commons.login_page import LoginPage
@allure.epic("用户中心")
@allure.feature("登录功能")
@pytest.mark.parametrize(
"username,password,_msg",
[
["ces", "111111", "密码错误"],
["ces", "1111", "密码格式6~18个字符"],
["ces", "123456", "登录成功"],
]
)
def test_login(driver,username,password,_msg):
allure.dynamic.title(_msg)
driver.get('http://116.62.11/logininfo.html')
msg = LoginPage(driver).login(username, password)
assert msg == _msg
python
import allure
import pytest
from allure_commons.types import AttachmentType
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@pytest.fixture()
def driver():
d = webdriver.Chrome()
d.maximize_window()
d.implicitly_wait(2)
yield d
d.quit()
@pytest.fixture(scope='session')
def user_driver():
d = webdriver.Chrome()
d.maximize_window()
d.implicitly_wait(2)
login_user(d)
yield d
d.quit()
def login_user(driver):
username = 'ces'
password = '123456'
success_msg = '登录成功'
username_input_xpath = '//input[@placeholder="请输入用户名"]'
password_input_xpath = '//input[@placeholder="请输入密码"]'
login_button_xpath = '//button[text()="登录"]'
success_message_xpath = '//p[@class="prompt-msg"]'
driver.get('http://116.62.1/logininfo.html')
driver.find_element('xpath', username_input_xpath).send_keys(username)
driver.find_element('xpath', password_input_xpath).send_keys(password)
driver.find_element('xpath', login_button_xpath).click()
success_message = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.XPATH, success_message_xpath))
)
assert success_message.text == success_msg
pytest.ini
python
[pytest]
# 命令行参数
addopts = -vs --alluredir=./temps --clean-alluredir tests/test_login.py
; --reruns 3
markers:
run
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true
#日志配置
#日志文件的配置
log_file = ./logs/frame.log
#日志的级别(DEBUG调试,INFO信息,WARNING警告,ERRO错误,CRITICAL非常严重)
log_file_level = info
#日志格式
log_file_format = %(levelname)s|%(asctime)s|%(message)s
requirements.txt
python
pytest~=9.0.2
selenium~=4.40.0
pytest-html
pytest-xdist
pytest-order
pytest-rerunfailures
pytest-base_url
allure-pytest
pymysql
pyyaml
allure-python-commons~=2.15.3
python
import shutil
from datetime import datetime
import pytest
import os
import glob
# 执行测试用例
pytest.main()
# 1. 定义日志相关配置(便于维护)
LOG_DIR = "logs"
LOG_PREFIX = "frame_"
LOG_SUFFIX = ".log"
MAX_LOG_NUM = 10 # 最多保留10个日志文件
# 2. 生成新日志文件名并复制
new_file_name = f"{LOG_DIR}/frame_{datetime.now().strftime('%Y%m%d_%H%M%S')}{LOG_SUFFIX}"
shutil.copy(f"{LOG_DIR}/frame.log", new_file_name)
# 3. 清理超出数量的最早日志文件
def clean_old_logs():
# 筛选出指定格式的日志文件(避免误删其他文件)
log_files = glob.glob(f"{LOG_DIR}/{LOG_PREFIX}*{LOG_SUFFIX}")
# 按文件创建时间排序(升序:最早的在前)
log_files.sort(key=lambda x: os.path.getctime(x))
# 若文件数量超过阈值,删除最早的
while len(log_files) > MAX_LOG_NUM:
oldest_file = log_files.pop(0) # 取出最早的文件
try:
os.remove(oldest_file)
print(f"删除最早的日志文件:{oldest_file}")
except Exception as e:
print(f"删除日志文件失败:{oldest_file},错误:{e}")
# 执行日志清理
clean_old_logs()
# 生成allure报告
os.system("allure generate ./temps -o ./reports --clean")