从零开始,手把手带你搭建一套企业级UI自动化测试框架。从环境安装到框架设计,从编写第一条用例到生成Allure测试报告,每一步都有截图和代码,保证你能跑通。
一、阅读前:为什么选Pytest+Selenium
| 对比维度 | Pytest+Selenium | unittest+Selenium | Robot Framework |
|---|---|---|---|
| 上手难度 | ★★☆ 中等 | ★★★ 偏高 | ★☆☆ 低(但学习曲线陡) |
| 扩展性 | ★★★ 极强(插件生态) | ★★☆ 一般 | ★★☆ 受限于框架 |
| 社区活跃度 | ★★★ 最活跃 | ★★☆ 经典但少更新 | ★☆☆ 用户较少 |
| 报告美观度 | ★★★ Allure报告 | ★☆☆ HTMLTestRunner | ★★☆ 自带报告 |
| 适合场景 | 企业级项目 | 简单项目 | 不写代码的测试 |
一句话总结:Pytest+Selenium 是当前市场上 UI 自动化最主流的技术组合,面试必问、工作必用。
二、环境搭建:从零到运行第一条用例
2.1 Python环境准备
bash
# 确认Python版本(建议3.8+)
python --version
# 未安装Python?去官网下载:https://www.python.org/downloads/
# 安装时务必勾选 "Add Python to PATH"
2.2 创建项目目录结构
bash
# 工作目录中执行,创建如下结构
mkdir selenium_pytest_demo
cd selenium_pytest_demo
# 推荐目录结构
selenium_pytest_demo/
├── config/ # 配置文件
│ └── config.yaml
├── page/ # Page Object页面层
│ ├── base_page.py
│ ├── login_page.py
│ └── home_page.py
├── testcases/ # 测试用例
│ ├── conftest.py
│ ├── test_login.py
│ └── test_search.py
├── data/ # 测试数据
│ └── test_data.yaml
├── utils/ # 工具类
│ ├── driver_util.py
│ └── log_util.py
├── reports/ # 测试报告
│ └── allure-results/
├── pytest.ini # Pytest配置
└── requirements.txt # 依赖清单
2.3 安装依赖
bash
# 创建requirements.txt
pip install selenium # Web自动化核心
pip install pytest # 测试框架
pip install pytest-xdist # 并行执行
pip install pytest-rerunfailures # 失败重跑
pip install allure-pytest # Allure报告
pip install pyyaml # YAML配置文件解析
pip install webdriver-manager # 自动管理浏览器驱动
或者一次性安装:
bash
# requirements.txt内容
selenium>=4.15.0
pytest>=7.4.0
pytest-xdist>=3.5.0
pytest-rerunfailures>=13.0
allure-pytest>=2.13.0
pyyaml>=6.0
webdriver-manager>=4.0.0
# 执行安装
pip install -r requirements.txt
⚠️ 新手常见坑 :selenium 4.x 之后不再需要手动下载 chromedriver,
webdriver-manager会自动帮你搞定!
三、核心代码:一步步搭建
3.1 浏览器驱动管理(driver_util.py)
python
# utils/driver_util.py
"""
浏览器驱动管理工具
自动下载匹配版本的浏览器驱动,支持Chrome、Firefox、Edge
"""
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
class DriverManager:
"""浏览器驱动管理器"""
# 支持浏览器类型映射
BROWSER_MAP = {
'chrome': (webdriver.Chrome, ChromeDriverManager),
'firefox': (webdriver.Firefox, GeckoDriverManager),
'edge': (webdriver.Edge, EdgeChromiumDriverManager),
}
@staticmethod
def get_driver(browser='chrome', headless=False):
"""
获取浏览器驱动实例
Args:
browser: 浏览器类型 chrome/firefox/edge
headless: 是否无头模式(不显示浏览器界面)
Returns:
WebDriver实例
"""
if browser not in DriverManager.BROWSER_MAP:
raise ValueError(f"不支持的浏览器类型: {browser},可选: {list(DriverManager.BROWSER_MAP.keys())}")
driver_class, manager_class = DriverManager.BROWSER_MAP[browser]
# 自动下载匹配的浏览器驱动
driver_path = manager_class().install()
service = Service(driver_path)
# 浏览器选项配置
options = webdriver.ChromeOptions() if browser == 'chrome' else \
webdriver.FirefoxOptions() if browser == 'firefox' else \
webdriver.EdgeOptions()
# 反自动化检测(防止网站识别为自动化脚本)
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
# 无头模式配置
if headless:
options.add_argument('--headless=new') # Chrome 112+ 推荐写法
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--window-size=1920,1080')
# 窗口最大化
options.add_argument('--start-maximized')
# 忽略证书错误(测试环境常见问题)
options.add_argument('--ignore-certificate-errors')
# 创建并返回驱动
driver = driver_class(service=service, options=options)
driver.implicitly_wait(10) # 全局隐式等待10秒
return driver
if __name__ == '__main__':
# 快速测试:启动浏览器并打开百度
drv = DriverManager.get_driver()
drv.get('https://www.baidu.com')
print(f"当前页面标题: {drv.title}")
drv.quit()
3.2 Page Object 基类(base_page.py)
python
# page/base_page.py
"""
Page Object 模式基类
封装所有页面共用的操作方法,子页面继承即可
"""
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import logging
logger = logging.getLogger(__name__)
class BasePage:
"""页面基类,封装通用操作"""
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, timeout=15, poll_frequency=0.5)
# ==================== 元素定位封装 ====================
def find_element(self, locator):
"""
查找单个元素
Args:
locator: (By.XXX, 'value') 元组
Returns:
WebElement对象
Example:
find_element((By.ID, 'username'))
"""
try:
return self.wait.until(EC.presence_of_element_located(locator))
except TimeoutException:
logger.error(f"元素定位超时: {locator}")
raise
def find_elements(self, locator):
"""查找多个元素"""
try:
return self.wait.until(EC.presence_of_all_elements_located(locator))
except TimeoutException:
logger.error(f"元素列表定位超时: {locator}")
return []
# ==================== 点击操作封装 ====================
def click(self, locator):
"""等待元素可点击后点击"""
element = self.wait.until(EC.element_to_be_clickable(locator))
element.click()
logger.info(f"点击元素: {locator}")
def js_click(self, locator):
"""通过JavaScript点击(适用于普通点击失效的情况)"""
element = self.find_element(locator)
self.driver.execute_script("arguments[0].click();", element)
logger.info(f"JS点击元素: {locator}")
# ==================== 输入操作封装 ====================
def input_text(self, locator, text, clear_first=True):
"""
输入文本
Args:
locator: 元素定位器
text: 输入的文本
clear_first: 是否先清空
"""
element = self.find_element(locator)
if clear_first:
element.clear()
element.send_keys(text)
logger.info(f"输入文本到 {locator}: {text}")
# ==================== 获取信息 ====================
def get_text(self, locator):
"""获取元素文本"""
return self.find_element(locator).text
def get_attribute(self, locator, attr_name):
"""获取元素属性值"""
return self.find_element(locator).get_attribute(attr_name)
# ==================== 断言辅助 ====================
def is_element_present(self, locator, timeout=5):
"""判断元素是否存在于DOM"""
try:
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located(locator)
)
return True
except TimeoutException:
return False
def is_element_visible(self, locator, timeout=5):
"""判断元素是否可见"""
try:
WebDriverWait(self.driver, timeout).until(
EC.visibility_of_element_located(locator)
)
return True
except TimeoutException:
return False
# ==================== 页面操作 ====================
def get_title(self):
"""获取当前页面标题"""
return self.driver.title
def get_current_url(self):
"""获取当前URL"""
return self.driver.current_url
def switch_to_new_window(self):
"""切换到最新打开的窗口"""
handles = self.driver.window_handles
self.driver.switch_to.window(handles[-1])
logger.info(f"切换到新窗口: {self.driver.title}")
def scroll_to_element(self, locator):
"""滚动到元素可见位置"""
element = self.find_element(locator)
self.driver.execute_script("arguments[0].scrollIntoView(true);", element)
def take_screenshot(self, filename=None):
"""截图"""
import os
from datetime import datetime
if filename is None:
filename = f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
filepath = os.path.join('reports', 'screenshots', filename)
os.makedirs(os.path.dirname(filepath), exist_ok=True)
self.driver.save_screenshot(filepath)
logger.info(f"截图已保存: {filepath}")
return filepath
3.3 业务页面对象(login_page.py)
python
# page/login_page.py
"""
登录页面对象
以百度登录页为例
"""
from selenium.webdriver.common.by import By
from page.base_page import BasePage
class LoginPage(BasePage):
"""百度登录页面"""
# ==================== 元素定位(页面对象的核心) ====================
# 首页的"登录"按钮
LOGIN_BTN = (By.LINK_TEXT, "登录")
# 用户名登录方式的切换按钮
USERNAME_LOGIN_TAB = (By.ID, "TANGRAM__PSP_11__changePwdCodeItem")
# 用户名输入框
USERNAME_INPUT = (By.ID, "TANGRAM__PSP_11__userName")
# 密码输入框
PASSWORD_INPUT = (By.ID, "TANGRAM__PSP_11__password")
# 登录提交按钮
SUBMIT_BTN = (By.ID, "TANGRAM__PSP_11__submit")
# 登录错误提示
ERROR_MSG = (By.CLASS_NAME, "error-text")
# ==================== 页面操作方法 ====================
def goto_login(self):
"""进入登录页面"""
self.driver.get("https://www.baidu.com")
self.click(self.LOGIN_BTN)
def switch_to_username_login(self):
"""切换到用户名密码登录方式"""
self.click(self.USERNAME_LOGIN_TAB)
def login(self, username, password):
"""
执行登录操作
Args:
username: 用户名
password: 密码
"""
self.input_text(self.USERNAME_INPUT, username)
self.input_text(self.PASSWORD_INPUT, password)
self.click(self.SUBMIT_BTN)
def get_error_message(self):
"""获取登录错误提示信息"""
if self.is_element_visible(self.ERROR_MSG, timeout=3):
return self.get_text(self.ERROR_MSG)
return None
def login_and_get_result(self, username, password):
"""
完整登录流程并返回结果
Returns:
tuple: (是否成功, 错误信息)
"""
self.goto_login()
self.switch_to_username_login()
self.login(username, password)
# 判断是否登录成功(跳转到首页即为成功)
import time
time.sleep(2)
if "百度首页" in self.get_title() or "baidu" in self.get_current_url():
return True, None
else:
return False, self.get_error_message()
3.4 conftest.py(Pytest夹具配置)
python
# testcases/conftest.py
"""
Pytest全局配置和夹具
conftest.py是Pytest特有的配置文件,同级目录及子目录的用例自动生效
"""
import pytest
import os
import allure
from utils.driver_util import DriverManager
@pytest.fixture(scope='session')
def browser_type():
"""
浏览器类型配置
优先级:命令行参数 > 环境变量 > 默认值(chrome)
"""
return os.environ.get('TEST_BROWSER', 'chrome')
@pytest.fixture(scope='function')
def driver(browser_type):
"""
浏览器驱动夹具
scope='function' 表示每个测试用例都会创建新的浏览器实例
yield 后面的代码会在测试用例结束后自动执行
"""
# 测试前:创建浏览器驱动
drv = DriverManager.get_driver(browser=browser_type)
drv.maximize_window()
allure.attach(browser_type, '浏览器类型', allure.attachment_type.TEXT)
yield drv # 将driver传递给测试用例
# 测试后:关闭浏览器(无论用例成功还是失败都会执行)
drv.quit()
@pytest.fixture(scope='function')
def driver_headless(browser_type):
"""
无头浏览器夹具
适用于CI/CD环境或不需要查看界面的场景
"""
drv = DriverManager.get_driver(browser=browser_type, headless=True)
yield drv
drv.quit()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""
用例失败时自动截图
Pytest Hook钩子函数,每个用例执行后触发
"""
outcome = yield
report = outcome.get_result()
# 仅处理测试执行阶段且失败的用例
if report.when == 'call' and report.failed:
driver_fixture = item.funcargs.get('driver')
if driver_fixture:
# 截图并附加到Allure报告
screenshot = driver_fixture.get_screenshot_as_png()
allure.attach(
screenshot,
name=f"失败截图_{item.name}",
attachment_type=allure.attachment_type.PNG
)
def pytest_addoption(parser):
"""添加自定义命令行参数"""
parser.addoption(
'--browser',
action='store',
default='chrome',
help='指定浏览器类型: chrome, firefox, edge'
)
parser.addoption(
'--headless',
action='store_true',
default=False,
help='是否使用无头模式'
)
3.5 测试用例编写(test_login.py)
python
# testcases/test_login.py
"""
登录功能测试用例
用例编号规则:TPL-模块-编号
"""
import pytest
import allure
from page.login_page import LoginPage
@allure.feature('登录功能')
class TestLogin:
"""登录功能测试套件"""
@allure.story('正常登录')
@allure.title('TC-LOGIN-001:使用正确的用户名和密码登录')
@allure.description('验证使用有效凭据可以成功登录系统')
@allure.severity(allure.severity_level.CRITICAL)
def test_login_success(self, driver):
"""正常登录用例"""
login_page = LoginPage(driver)
with allure.step('1. 进入百度首页并打开登录页面'):
login_page.goto_login()
with allure.step('2. 切换到用户名密码登录方式'):
login_page.switch_to_username_login()
with allure.step('3. 输入正确的用户名和密码'):
login_page.login('your_username', 'your_password')
with allure.step('4. 验证登录成功'):
# 此处为示例断言,实际测试应使用真实账号
# 验证页面跳转或出现用户昵称
assert '百度' in login_page.get_title(), "登录后页面标题应包含'百度'"
@allure.story('异常登录')
@allure.title('TC-LOGIN-002:使用错误的密码登录')
@allure.description('验证使用无效凭据时系统给出明确错误提示')
@allure.severity(allure.severity_level.NORMAL)
def test_login_wrong_password(self, driver):
"""错误密码登录用例"""
login_page = LoginPage(driver)
with allure.step('1. 进入登录页面'):
login_page.goto_login()
with allure.step('2. 切换到用户名密码登录'):
login_page.switch_to_username_login()
with allure.step('3. 输入用户名和错误密码'):
login_page.login('test@qq.com', 'wrong_password_123')
with allure.step('4. 验证出现错误提示'):
error = login_page.get_error_message()
assert error is not None, "应出现密码错误提示"
allure.attach(error, '错误提示信息', allure.attachment_type.TEXT)
@allure.story('异常登录')
@allure.title('TC-LOGIN-003:用户名为空登录')
@allure.description('验证用户名为空时的提示')
@allure.severity(allure.severity_level.NORMAL)
@pytest.mark.parametrize('username,password,expected', [
('', '123456', '请输入用户名'),
('test@qq.com', '', '请输入密码'),
('', '', '请输入用户名'),
])
def test_login_empty_fields(self, driver, username, password, expected):
"""
数据驱动测试:空值校验
一条用例自动生成3组测试数据执行
参数化是Pytest的核心特性,大幅减少重复代码
"""
login_page = LoginPage(driver)
login_page.goto_login()
login_page.switch_to_username_login()
login_page.login(username, password)
error = login_page.get_error_message()
assert error is not None, f"当用户名为'{username}'、密码为'{password}'时应有提示"
3.6 pytest.ini配置
ini
# pytest.ini
[pytest]
# 测试用例路径
testpaths = testcases
# 命令行默认参数
addopts =
-v # verbose详细输出
-s # 不捕获print输出
--strict-markers # 严格标记模式
--tb=short # 简短的错误追踪
--alluredir=reports/allure-results # Allure报告输出目录
--maxfail=3 # 失败3个用例后停止
# 自定义标记(用于用例分类和执行筛选)
markers =
smoke: 冒烟测试用例
regression: 回归测试用例
slow: 执行较慢的测试
login: 登录相关用例
search: 搜索相关用例
# 日志配置
log_cli = true
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)s] %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
四、运行测试
4.1 基础运行命令
bash
# 运行全部用例
pytest
# 运行指定文件
pytest testcases/test_login.py
# 运行指定类/方法
pytest testcases/test_login.py::TestLogin::test_login_success
# 运行带特定标记的用例
pytest -m smoke # 只跑冒烟测试
pytest -m "login and smoke" # 同时满足两个标记
# 运行并输出详细日志
pytest -v -s
4.2 高级运行模式
bash
# 并行执行(2个进程同时跑,大幅缩短总耗时)
pytest -n 2
# 自动根据CPU核心数并行
pytest -n auto
# 失败重跑(失败用例自动重试2次)
pytest --reruns 2
# 失败重跑+间隔等待
pytest --reruns 2 --reruns-delay 5
# 只运行上次失败的用例
pytest --lf # --last-failed
# 先跑失败的,再跑其他的
pytest --ff # --failed-first
# 按用例名关键词筛选
pytest -k "login" # 跑所有包含login的用例
pytest -k "login and not slow" # 跑login但不跑slow的
4.3 指定浏览器运行
bash
# 用Firefox跑
pytest --browser=firefox
# 用Edge跑
pytest --browser=edge
# 无头模式(不弹浏览器窗口,适合CI环境)
pytest --headless
五、生成Allure测试报告
5.1 安装Allure
bash
# Windows (推荐用Scoop)
scoop install allure
# 或手动下载:https://github.com/allure-framework/allure2/releases
# 解压后将bin目录添加到PATH环境变量
# Mac
brew install allure
# 验证安装
allure --version
5.2 生成并查看报告
bash
# 第1步:运行测试(同时生成报告数据)
pytest --alluredir=reports/allure-results
# 第2步:启动报告服务器查看
allure serve reports/allure-results
# 或生成本地静态报告
allure generate reports/allure-results -o reports/allure-report --clean
allure open reports/allure-report
5.3 Allure常用装饰器
python
import allure
# 功能模块
@allure.feature('用户管理')
# 用户故事
@allure.story('用户注册')
# 用例标题(报告中的展示名)
@allure.title('TC-001:注册成功')
# 用例描述
@allure.description('验证使用合法信息注册用户成功')
# 严重级别
@allure.severity(allure.severity_level.BLOCKER) # 阻断
@allure.severity(allure.severity_level.CRITICAL) # 严重
@allure.severity(allure.severity_level.NORMAL) # 一般
@allure.severity(allure.severity_level.MINOR) # 轻微
# 测试步骤(报告中可视化展示每步)
with allure.step('1. 打开登录页面'):
page.open()
# 附加信息
allure.attach('文本内容', '附件名', allure.attachment_type.TEXT)
allure.attach(driver.get_screenshot_as_png(), '截图', allure.attachment_type.PNG)
六、进阶:框架优化实战
6.1 YAML数据驱动
python
# data/test_data.yaml
login_cases:
- case_id: 'TC-LOGIN-001'
title: '正确账号密码登录'
username: 'admin'
password: '123456'
expected: 'success'
tags: ['smoke', 'p0']
- case_id: 'TC-LOGIN-002'
title: '错误密码登录'
username: 'admin'
password: 'wrong'
expected: '密码错误'
tags: ['p1']
- case_id: 'TC-LOGIN-003'
title: '空用户名登录'
username: ''
password: '123456'
expected: '请输入用户名'
tags: ['p1']
python
# 在testcases中使用YAML数据
import yaml
import pytest
def load_test_data(file_path='data/test_data.yaml'):
"""从YAML文件加载测试数据"""
with open(file_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
@pytest.mark.parametrize(
'case',
load_test_data()['login_cases'],
ids=lambda case: case['case_id']
)
def test_login_with_yaml_data(driver, case):
"""使用YAML数据驱动的登录测试"""
login_page = LoginPage(driver)
login_page.goto_login()
login_page.switch_to_username_login()
login_page.login(case['username'], case['password'])
error = login_page.get_error_message()
if case['expected'] == 'success':
assert '百度' in login_page.get_title()
else:
assert case['expected'] in (error or '')
6.2 日志配置
python
# utils/log_util.py
import logging
import os
from datetime import datetime
def setup_logger(name='auto_test', log_dir='reports/logs'):
"""
配置日志器
同时输出到控制台和文件,按日期滚动
"""
os.makedirs(log_dir, exist_ok=True)
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
# 避免重复添加handler
if logger.handlers:
return logger
# 日志格式
formatter = logging.Formatter(
'[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 控制台输出
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
# 文件输出(按日期命名)
log_file = os.path.join(
log_dir,
f"test_{datetime.now().strftime('%Y%m%d')}.log"
)
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger
# 全局使用
logger = setup_logger()
6.3 Jenkins集成配置
groovy
// Jenkinsfile
pipeline {
agent any
// 环境变量
environment {
TEST_BROWSER = 'chrome'
}
stages {
stage('安装依赖') {
steps {
sh 'pip install -r requirements.txt'
}
}
stage('执行测试') {
steps {
sh '''
pytest testcases/ \
--alluredir=reports/allure-results \
--headless \
-n auto \
--reruns 2 \
-v
'''
}
post {
always {
// 无论测试成功还是失败都生成报告
allure includeProperties: false,
results: [[path: 'reports/allure-results']]
}
}
}
}
post {
// 发送邮件通知
failure {
emailext(
subject: "[自动化测试] 构建失败 - ${env.JOB_NAME}",
body: "详情见:${env.BUILD_URL}",
to: 'test-team@company.com'
)
}
}
}
七、常见问题排坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
selenium.common.exceptions.WebDriverException |
浏览器驱动版本不匹配 | 使用 webdriver-manager 自动管理 |
| 元素定位超时 | 页面加载慢/元素未渲染 | 增加隐式等待时间,或使用显式等待 |
| Chrome自动关闭 | Chrome版本更新导致驱动不兼容 | pip install --upgrade webdriver-manager |
pytest: error: unrecognized arguments |
allure命令行参数未识别 | pip install allure-pytest |
| 执行无头模式找不到元素 | 无头模式viewport不同 | 添加 --window-size=1920,1080 |
| 并行执行用例互相干扰 | 测试数据未隔离 | 每个用例使用独立的测试数据 |
| 提示"不是私密连接" | 证书问题 | 添加 --ignore-certificate-errors |
| Allure报告中文乱码 | 系统字体问题 | allure open 命令在报告目录执行 |
八、总结
框架能力一览
Pytest+Selenium UI自动化框架
├── 用例管理:Pytest框架 + conftest.py 统一配置
├── 页面封装:Page Object模式,定位与操作分离
├── 数据驱动:YAML + @pytest.mark.parametrize
├── 失败处理:自动截图 + 失败重跑
├── 并行执行:pytest-xdist 多进程
├── 报告展示:Allure美观可视化报告
├── CI集成:Jenkins Pipeline一键运行
└── 日志追踪:控制台+文件双输出
下一步可以做什么
- 增加API测试 :结合
requests+ Pytest 做接口自动化 - 数据库校验:用例中直接查询数据库验证数据正确性
- 邮件通知:自定义测试结果通知策略
- 测试平台化:将框架包装成Web平台,非技术人员也能执行
📌 核心代码已开源到本文示例项目。如果你想看更完整的实战项目(包含更多页面对象、复杂场景处理、多环境切换等),欢迎收藏+关注,下一篇我们深入讲讲怎么把这个框架用在实际项目里。
🔥 下一篇预告:《Charles抓包实战-10个测试必会场景》------接口调试、HTTPS抓包、断点修改请求、模拟弱网、Map Local挡板...每个场景一节实操。