Metadata-Version: 2.4
Name: steamedbun
Version: 2.6.3
Summary: 馒头的第三方库
Home-page: https://github.com/neihanshenshou/SteamedBun
Author: 馒头
Author-email: neihanshenshou@163.com
License: Apache License 2.0
Platform: MacOS
Platform: Windows
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.7,<3.17
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: colorama==0.4.6
Requires-Dist: pymysql>=1.1.1
Requires-Dist: python-dateutil==2.8.2
Requires-Dist: PyYAML>=6.0.3
Requires-Dist: requests>=2.30.0
Requires-Dist: retry==0.9.2
Requires-Dist: urllib3>=1.26.12
Requires-Dist: selenium==4.4.3
Requires-Dist: playwright==1.9.0; python_version == "3.7"
Requires-Dist: allure-pytest==2.13.5; python_version == "3.7"
Requires-Dist: numpy<1.25.0; python_version == "3.7"
Requires-Dist: pandas<1.5.0; python_version == "3.7"
Requires-Dist: openpyxl==3.1.0; python_version == "3.7"
Requires-Dist: Pillow<10.0.0; python_version == "3.7"
Requires-Dist: pytest<7.0.0; python_version == "3.7"
Requires-Dist: pytest-repeat~=0.9.3; python_version == "3.7"
Requires-Dist: pytest-ordering==0.6; python_version == "3.7"
Requires-Dist: pytest-xdist<3.0.0; python_version == "3.7"
Requires-Dist: setuptools<60.0.0,>=60.2.0; python_version == "3.7"
Requires-Dist: playwright>=1.44.0; python_version >= "3.8"
Requires-Dist: allure-pytest==2.15.3; python_version >= "3.8"
Requires-Dist: numpy<1.27.0,>=1.24.0; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: pandas<2.1.0,>=2.0.3; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: openpyxl==3.1.0; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: Pillow<10.3.0,>=9.5.0; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: pytest<8.0.0,>=7.3.2; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: pytest-repeat~=0.9.3; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: pytest-order==1.1.0; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: pytest-xdist<3.7.0,>=3.5.0; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: setuptools<69.0.0,>=60.2.0; python_version >= "3.8" and python_version < "3.12"
Requires-Dist: setuptools<68.0.0,>=62.0.0; python_version == "3.9"
Requires-Dist: numpy>=1.27.0; python_version >= "3.12"
Requires-Dist: pandas>=2.1.0; python_version >= "3.12"
Requires-Dist: openpyxl~=3.1.5; python_version >= "3.12"
Requires-Dist: Pillow>=10.3.0; python_version >= "3.12"
Requires-Dist: pytest>=8.0.0; python_version >= "3.12"
Requires-Dist: pytest-repeat~=0.9.3; python_version >= "3.12"
Requires-Dist: pytest-order==1.1.0; python_version >= "3.12"
Requires-Dist: pytest-xdist>=3.7.0; python_version >= "3.12"
Requires-Dist: setuptools>=69.0.0; python_version >= "3.12"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: platform
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# SteamedBun

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

![Python](https://img.shields.io/badge/Python-3.7+-blue.svg)
![License](https://img.shields.io/badge/License-MIT-green.svg)

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` | 截图打标（元素中心点标记） |

### 🎨 接口覆盖率工具

| 工具 | 说明 |
|------|------|
| `dye_color` | 接口覆盖率染色 |

---

## 使用示例

### 🖥️ UI 自动化 - Selenium

```python
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

```python
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

```python
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 识别

```python
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}")
```

### 📸 截图开关控制

```python
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 到上一级判断

### 🧪 测试用例装饰器

```python
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)
```

### ✅ 断言示例

```python
from SteamedBun import should_equal, should_contains, should_true

should_equal(1, 1)
should_contains("hello world", "world")
should_true(1 > 0)
```

### 🌐 HTTP 请求

```python
from SteamedBun import request

# 默认显示日志
request("https://www.baidu.com")

# 关闭日志
request("https://www.baidu.com", show=False)
```

### 🗄️ 数据库操作

```python
from SteamedBun import MySQL

with MySQL() as db:
    print(db.execute(sql=""))
```

### ⚙️ 配置管理

```python
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")
```

### 📁 文件操作

```python
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]}
    }
)
```

### 📧 邮件发送

```python
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
})
```

### 📝 日志

```python
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 | 这是自定义级别的日志
```

### ⏰ 时间回溯

```python
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
```

### 🔢 数值格式化

```python
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%
```

### 🧩 单例模式

```python
from SteamedBun import singleton, SingletonMeta


# 装饰器方式
@singleton
class MyClass:
    pass


# 元类方式
class MyClass2(metaclass=SingletonMeta):
    pass
```

### 🎨 创建 pytest 项目

```shell
cpt my_pytest_project

# 执行上述命令后，根据后续指令配置项目环境即可
```

---

## 安装

```shell
pip install SteamedBun
```

## 环境要求

- Python 3.7+

## License

MIT License
