SteamedBun
【馒头】 一个 Python 自动化测试工程化的工具集(也作底层框架)

SteamedBun(馒头)是一个集成了 UI 自动化、接口测试、数据驱动、报告生成等能力的 Python 测试框架,基于 pytest、selenium、playwright 等工具构建。
工具概览
🖥️ UI 自动化工具
| 工具 |
说明 |
Browser |
Selenium 浏览器驱动,支持 Chrome/Firefox/Edge |
Page |
页面对象基类(Selenium) |
Element |
单元素定位器(Selenium) |
Elements |
多元素定位器(Selenium) |
playwright |
Playwright 命名空间 |
Locator |
单元素定位器(Playwright) |
Locators |
多元素定位器(Playwright) |
BrowserObject |
浏览器全局配置对象 |
🧪 测试用例装饰器
| 装饰器 |
说明 |
case_title |
用例标题 |
case_step |
用例步骤 |
case_story |
用例所属故事 |
case_priority |
用例优先级(order 参数) |
case_tag |
用例标签 |
case_feature |
用例所属特性 |
case_severity |
用例严重级别 |
case_skip |
跳过用例 |
case_skip_if |
条件跳过 |
case_desc_ok |
标记通过描述 |
case_desc_error |
标记失败描述 |
case_desc_up |
标记升级描述 |
case_mark |
自定义标记 |
fixture |
pytest fixture 封装 |
param_data |
数据驱动(内联数据) |
param_file |
数据驱动(文件数据) |
timer |
性能计时装饰器 |
case_attach |
Allure 报告附件 |
✅ 断言工具
| 方法 |
说明 |
should_equal |
断言相等 |
should_not_equal |
断言不相等 |
should_true |
断言为真 |
should_contains |
断言包含 |
should_be_empty |
断言为空 |
should_not_empty |
断言非空 |
should_be_instance_of |
断言类型 |
should_greater_than |
断言大于 |
should_greater_or_equal |
断言大于等于 |
should_less_than |
断言小于 |
should_less_or_equal |
断言小于等于 |
should_length_equal |
断言长度 |
should_start_with |
断言开头 |
should_end_with |
断言结尾 |
should_match |
断言正则匹配 |
should_contains_key |
断言包含键 |
should_contains_value |
断言包含值 |
📁 文件操作工具
| 工具 |
说明 |
FileOperate |
文件读写、Excel/JSON/CSV/YAML 处理 |
FileNames |
文件名常量定义 |
⚙️ 配置管理工具
| 工具 |
说明 |
ReadConfig |
读取 INI 配置文件 |
get_env |
获取环境变量 |
put_env |
设置环境变量 |
set_env |
设置当前环境 |
set_env_by_file |
通过配置文件设置环境 |
🗄️ 数据库工具
| 工具 |
说明 |
MySQL |
MySQL 数据库操作(上下文管理器) |
🌐 网络请求工具
| 工具 |
说明 |
request |
HTTP 请求封装,支持 GET/POST 等 |
📧 邮件工具
| 工具 |
说明 |
Email |
邮件发送,支持 Allure 报告附件 |
📝 日志工具
| 工具 |
说明 |
logger |
日志记录,支持多级别/文件输出 |
LogLevels |
日志级别枚举 |
⏰ 时间工具
| 工具 |
说明 |
TimeFormat |
时间格式化与回溯(支持天/月加减) |
🔢 数值工具
| 工具 |
说明 |
percent |
百分数格式化 |
point |
小数位数格式化 |
⚠️ 异常处理工具
| 工具 |
说明 |
hook_exceptions |
全局异常钩子 |
ignore_exceptions |
忽略指定异常 |
🧩 单例工具
| 工具 |
说明 |
singleton |
类装饰器,实现单例模式 |
SingletonMeta |
元类,实现参数化单例 |
🖼️ 图像处理工具
| 工具 |
说明 |
compress_image |
图片压缩 |
processing |
截图打标(元素中心点标记) |
🎨 接口覆盖率工具
使用示例
🖥️ UI 自动化 - Selenium
from SteamedBun import Browser, Element, Page
class BaiDuPage(Page):
example_url = "https://baidu.com"
input_search = Element(id_="kw")
def test_chrome_browser():
driver = Browser(browser_type="chrome")
page = BaiDuPage(driver=driver)
page.open(page.example_url)
page.input_search.send_keys("321")
🖥️ UI 自动化 - Selenium WAP
from SteamedBun import Browser
driver = Browser(headless=False, wap=True, width=476, height=776)
driver.get("https://www.example.com")
driver.find_element("id", "id_v1").send_keys("馒头")
driver.close()
🖥️ UI 自动化 - Playwright
from SteamedBun import playwright, Locator, Locators
from playwright.sync_api import sync_playwright
class PypiPage(playwright.Page):
pypi_url = "https://pypi.org/"
search_input = Locator(selector="[id='search']", describe="搜索框")
search_btn = Locator(selector="form > button", describe="搜索按钮")
txt = Locators(selector="//*[text()='三方库']", describe="随机文案")
with sync_playwright().start().chromium.launch(headless=False) as browser:
driver = browser.new_page()
page = PypiPage(page=driver)
page.goto(page.pypi_url)
page.search_input.send_keywords(value="SteamedBun")
page.search_btn.click()
print([each.inner_text() for each in page.txt])
📸 截图与 OCR 识别
from SteamedBun import Browser, Element, Page, case_title, case_step
class QuotePage(Page):
example_url = "https://so.gushiwen.cn/user/login.aspx"
img_code = Element(id_="imgCode", describe="动态验证码")
@case_title(title="识别动态验证码")
def test_ocr_dynamic_code():
with case_step("进入古诗词网站"):
driver = Browser()
page = QuotePage(driver=driver)
page.open(page.example_url)
with case_step("截图指定的组件"):
img_name = "img_code.png"
page.img_code.screenshots(filename=img_name)
with case_step("OCR识别验证码"):
code = "1234"
print(f"code: {code}")
📸 截图开关控制
from SteamedBun import BrowserObject, Element, Page
# 方式1:Page 级别开关(显式开启)
class LoginPageV1(Page):
page_screenshot_enabled = True # 开启整个页面截图
# 方式2:Element 级别开关(优先级最高)
class LoginPageV2(Page):
name_input = Element(id_="username", screenshot_enabled=True) # 开启截图
pwd_input = Element(id_="password", screenshot_enabled=False) # 关闭截图
# 方式3:仅设置全局路径无效,需配合 Page 或 Element 显式开启
BrowserObject.selenium_screenshot_path = "."
BrowserObject.selenium_screenshot_path = "NewFolder"
说明:
- 优先级:Element > Page > 默认
page_screenshot_enabled = False 或 screenshot_enabled = False 时,无论全局路径是否设置都不会截图
None 表示未设置,会 fallback 到上一级判断
🧪 测试用例装饰器
from SteamedBun import case_priority, param_file, case_title
@case_priority(order=1)
def test_01():
pass
@param_file("测试数据文件")
@case_title("用例标题")
def test_example(param):
print(param)
✅ 断言示例
from SteamedBun import should_equal, should_contains, should_true
should_equal(1, 1)
should_contains("hello world", "world")
should_true(1 > 0)
🌐 HTTP 请求
from SteamedBun import request
# 默认显示日志
request("https://www.baidu.com")
# 关闭日志
request("https://www.baidu.com", show=False)
🗄️ 数据库操作
from SteamedBun import MySQL
with MySQL() as db:
print(db.execute(sql=""))
⚙️ 配置管理
from SteamedBun import set_env_by_file, set_env
# conf.ini
# [environments]
# current_env = test
# enable_envs = [test, tce, online, perf, wiseperf1v1]
set_env_by_file(env_path="conf.ini")
set_env(env="test")
📁 文件操作
from SteamedBun import FileOperate, FileNames
# 读取 JSON
FileOperate.read_file(filename="some.json", jsonify=True)
# 读取 Excel
FileOperate.read_excel(filename="some.xlsx", sheet_name="Sheet1")
# 写入文件
FileOperate.write_file(filename="some.json", data="{}")
# 写入 Excel
FileOperate.write_excel(
filename="some.xlsx",
data={"a列头": [1, 2, 3], "b列头": ["a", "b", "c"]}
)
# 写入多 Sheet Excel
FileOperate.write_excel_with_many_sheets(
filename=FileNames.ExcelFile,
data={
"sheet1": {"A列": ["a", "b", "c"], "B列": ["1", "2", "3"]},
"sheet2": {"学科": ["en", "cn", "mt"], "分数": [30, 80, 70]}
}
)
📧 邮件发送
from SteamedBun import Email
email = Email(
sender="yours@163.com",
password="your_password", # 发件人邮箱授权码
receiver=["yours@163.com", "hers@qq.com"]
)
email.send_case_report(data={
"pass": 100,
"fail": 1,
"error": 2
})
📝 日志
from SteamedBun import logger, FileNames
# 默认打印 INFO 级别到控制台
logger.info("info")
# 打印 DEBUG 级别到文件
logger.set_log_file(filename=FileNames.LogFile, level="DEBUG")
# 设置全局日志级别
logger.set_stream_level(level="DEBUG")
# 自定义级别
logger.step(msg="这是自定义级别的日志")
# 输出: 2024-10-09 22:27:34,510 | tmp.py | 8 | STEP | 这是自定义级别的日志
⏰ 时间回溯
from SteamedBun import TimeFormat
dt1 = TimeFormat.flash_back(days=-1)
print(f"往前回溯了一天: {dt1}")
dt2 = TimeFormat.flash_back(month=1)
print(f"往后穿越一个月: {dt2}")
print(dt2 < dt1) # True
🔢 数值格式化
from SteamedBun import point, percent
num = point(number=100, rate=2)
print(num) # 输出: 100.00
pct = percent(number=90, rate=2)
print(pct) # 输出: 90.00%
pct = percent(number=80, rate=1)
print(pct) # 输出: 80.0%
🧩 单例模式
from SteamedBun import singleton, SingletonMeta
# 装饰器方式
@singleton
class MyClass:
pass
# 元类方式
class MyClass2(metaclass=SingletonMeta):
pass
🎨 创建 pytest 项目
cpt my_pytest_project
# 执行上述命令后,根据后续指令配置项目环境即可
安装
pip install SteamedBun
环境要求
License
MIT License