本文描述本仓库从 python run.py smoke / pytest 启动到单条 YAML 用例执行完毕的真实调用顺序,与代码位置对应,便于排查问题或二次开发。

相关源码run.pyfixtures/conftest.pytests/test_yaml_cases.pyutils/yaml_case_loader.pyutils/yaml_pom_engine.pypages/*.py


1. 参与者(泳道说明)

参与者 含义
用户 / CI 执行命令行
run.py 可选:清理报告目录后调用子进程 pytest
pytest 测试收集、fixture 解析、用例调度
test_yaml_cases 模块 tests/test_yaml_cases.pyimport 时即加载 YAML
yaml_case_loader 读取并校验 test-data/test_cases.yaml
fixtures fixtures/conftest.py 中的 session / function 级 fixture
config_loader load_settings(),合并 config/*.json.env
测试函数 test_yaml_pom_cases
YamlPomEngine 逐步执行 steps
pages 动态 import 的 Page 类实例
Playwright 浏览器、Context、Page、APIRequestContext

2. 总览:从命令到第一条业务步骤

pages.*Page YamlPomEngine test_yaml_pom_cases config_loader fixtures/conftest yaml_case_loader test_yaml_cases (模块 import) pytest run.py pages.*Page YamlPomEngine test_yaml_pom_cases config_loader fixtures/conftest yaml_case_loader test_yaml_cases (模块 import) pytest run.py 收集用例阶段:import tests.test_yaml_cases alt [page + call] [builtin] loop [每一步 step] loop [每条启用的 YAML 用例 × marker 匹配] 用户/CI python run.py smoke 1 clean_report_artifacts() 2 subprocess pytest -m smoke 3 import 模块 4 load_yaml_cases() 5 读盘 test_cases.yaml 校验 name/steps 6 List[case dict] 7 构建 PARAMS(含 smoke mark) 8 session: load_settings() 9 load_settings() 10 settings 11 session: env_vars(读 .env) 12 session: playwright + browser 13 function: context / page / api_request 14 test_yaml_pom_cases(..., case_data) 15 allure.dynamic 元数据 16 YamlPomEngine(...) 构造 17 run_case(case_data) 18 run_steps(steps) 19 import + new + method(**kwargs) 20 BUILTIN_STEPS[name](...) 21 通过/失败 22 exit code 23 generate_allure_html()(可选) 24 用户/CI

3. 收集阶段:为何一改 YAML 就要重新跑 pytest

load_yaml_cases()tests/test_yaml_cases.py 被 import 时执行一次,结果赋给 ALL_CASES。因此:

  • 修改 test-data/test_cases.yaml 后,需重新启动 pytest 进程才能看到新用例(正常重跑即可)。
  • 不在「单个测试函数内部」重复读盘。
test_cases.yaml yaml_case_loader test_yaml_cases pytest test_cases.yaml yaml_case_loader test_yaml_cases pytest import tests.test_yaml_cases load_yaml_cases() 读取 YAML 原始树 过滤 enabled 校验 name 唯一 校验每步 builtin 或 page+call enabled_cases PARAMS = [pytest.param(case, marks=...) ...]

4. Session 级 Fixture:配置与浏览器只建一次

Playwright config_loader dotenv / os.environ fixtures/conftest pytest Playwright config_loader dotenv / os.environ fixtures/conftest pytest fixture settings (session) load_settings() load_dotenv(.env) 合并 base.json + TEST_ENV.json + 环境变量覆盖 settings dict fixture env_vars (session) 校验 TEST_PHONE、STATIC_OTP_CODE 等 env_vars dict fixture playwright_instance (session) sync_playwright() playwright fixture browser (session) chromium.launch(headless=...) browser

5. Function 级 Fixture:每条 YAML 用例一个 Tab

每条 test_yaml_pom_cases[case_data] 执行前,pytest 会新建 BrowserContextPage(及 APIRequestContext),用例结束释放。

Playwright API Browser fixtures/conftest pytest Playwright API Browser fixtures/conftest pytest context (function) new_context(record_video_dir=...) context page (function) context.new_page() page api_request (function) playwright.request.new_context() api_request

6. 单条用例内:YamlPomEngine 执行一步(Page 调用)

引擎对「非 builtin」步骤的等价逻辑:动态 import → 按 __init__ 签名注入依赖 → getattr 取方法 → 解析 kwargs 中的 {{VAR}}method(**kwargs)

Page 实例方法 pages.xxx.XxxPage importlib YamlPomEngine test_yaml_pom_cases Page 实例方法 pages.xxx.XxxPage importlib YamlPomEngine test_yaml_pom_cases run_case(case) run_steps(steps) _load_page_class("login_page.LoginPage") import_module(pages.login_page) module getattr(module, "LoginPage") _instantiate_page(LoginPage) inspect.signature(__init__) 注入 page/settings/api_request LoginPage(**kwargs) instance _resolve_obj(raw_kwargs) login_with_phone_otp(phone=..., ...) return / assert

7. 单条用例内:builtin 步骤(不实例化 Page)

BUILTIN_STEPS YamlPomEngine BUILTIN_STEPS YamlPomEngine run_steps 遇到 builtin _run_builtin(step, idx) get("assert_url_contains") _builtin_assert_url_contains _resolve_obj(value) assert substring in page.url

8. 失败时:截图与 Allure 附件

仅在 call 阶段失败且 fixture 中能取到 page 时触发。

Page pytest_runtest_makereport pytest Page pytest_runtest_makereport pytest alt [when == "call" and not passed] hookwrapper(after test call) screenshot(full_page=True) allure.attach.file(png)

9. run.py smoke 在 pytest 结束之后

与单条用例执行串行发生在 pytest 进程退出之后:根据 reports/allure-results 尝试本机 Allure CLI / npx,失败再尝试 Docker 生成 reports/allure-html

generate_allure_html pytest run.py generate_allure_html pytest run.py subprocess pytest -m smoke returncode generate_allure_html() allure generate 或 docker run ...

10. 与「第五节流程图」的关系

docs/FRAMEWORK_FULL_GUIDE.md 第五节中的 Mermaid flowchart 偏「模块级数据流」;本文 sequenceDiagram 偏「随时间推进的调用顺序」。二者互补:排错时先看总览流程图,再按本文对照具体函数与 fixture 边界。


11. 常见误读澄清

  1. YAML 不是每一步都重新读盘:只在 import test_yaml_cases 时加载一次。
  2. 每个 YAML 用例是否共用一个 Page:每条用例对应一次 page fixture,默认 新 Tab;同一条用例内多步 共用同一 page
  3. 每一步是否新建 Page 对象:是;YamlPomEngine每一步若走 page+call,会 importnew 一次该 Page 类(短生命周期,符合当前实现)。
  4. api_request 与浏览器 CookieAPIRequestContext 为独立 HTTP 客户端,与页面 Cookie 不共享;仅适合 mock 取码等场景。

更多推荐