智云码UI自动化框架介绍

发布时间:2026/5/20 7:44:02

智云码UI自动化框架介绍 目录一、项目介绍二、项目结构说明三、核心代码1.程序入口run.py代码说明:2.主程序入口3.Allure 报告处理逻辑2.测试用例testcases添加项目测试示例代码说明expect断言3.UI测试 unlogin_page测试方法示例unlogin_page方法解析步骤详解_build_artifact_test_folder4.Mock模拟API的响应1.返回400测试示例json.dump:用于将python字典转化成json2.返回500测试示例page.route5.插件pytest_base_url_plugin组件定义6.插件pytest_playwright组件定义四、项目总结一、项目介绍基于PythonpytestplaywrightAjaxallureJenkinsdockerLinux该项目是一个UI自动化框架的封装平台,主要基于PythonPlayWright搭建了接口自动化框架二、项目结构说明project-root/ ├── .idea/ # IDE配置文件IntelliJ IDEA ├── .pytest_cache/ # pytest运行缓存文件 ├── .venv/ # Python虚拟环境目录 ├── allure_report/ # 生成的Allure测试报告 │ ├── data/ # 报告数据文件 │ │ ├── attachments/ # 截图、视频等附件 │ │ └── test-cases/ # 测试用例数据 │ └── index.html # 报告入口文件 ├── cases/ # 测试用例集 │ ├── __pycache__/ # Python编译缓存 │ ├── more_accounts/ # 多账号测试用例目录 │ ├── conftest.py # 测试用例级别的配置 │ ├── test_add_module.py # 新增模块测试 │ ├── test_add_project.py # 新增项目测试 │ ├── test_env_list.py # 环境列表测试 │ ├── test_login.py # 登录功能测试 │ ├── test_project_list.py # 项目列表测试 │ └── test_register.py # 注册功能测试 ├── mocks/ # 模拟数据和API │ ├── __pycache__/ # Python编译缓存 │ └── mock_api.py # API模拟配置 ├── pages/ # 页面元素封装Page Object Model │ ├── __pycache__/ # Python编译缓存 │ ├── add_module_page.py # 新增模块页面 │ ├── add_project_page.py # 新增项目页面 │ ├── list_env_page.py # 环境列表页面 │ ├── login_page.py # 登录页面 │ ├── project_list_page.py # 项目列表页面 │ └── register_page.py # 注册页面 ├── plugins/ # 自定义插件 │ ├── __pycache__/ # Python编译缓存 │ ├── pytest_base_url_plugin.py # Base URL插件 │ └── pytest_playwright.py # Playwright扩展插件 ├── reports/ # 测试报告临时文件 ├── test-results/ # 测试过程中的临时文件截图、视频、trace ├── conftest.py # pytest全局配置文件 ├── pytest.ini # pytest运行配置文件 ├── README.md # 项目说明文档 ├── requirements.txt # 项目依赖库列表 └── run.py # 框架运行入口脚本三、核心代码1.程序入口run.pyimport os import pytest if __name__ __main__: # 运行测试用例 pytest.main([--alluredir, ./reports]) # 生成测试报告 os.system(allure generate ./reports -o ./allure_report --clean) # 打开测试报告 # os.system(allure serve ./reports)代码说明:2.主程序入口if __name__ __main__:Python 标准写法表示当脚本作为主程序运行时才执行以下代码块。防止模块被其他脚本导入时意外执行测试逻辑。3.Allure 报告处理逻辑pytest.main([--alluredir, ./reports]) # 生成测试报告 os.system(allure generate ./reports -o ./allure_report --clean)参数说明参数含义--alluredirpytest将测试结果以Allure格式输出到指定目录allure generate用于将测试结果转换为HTML报告-o ./allure_report输出目录指定生成HTML报告的目标文件夹--clean在生成新报告前先删除目标目录中的所有内容2.测试用例testcases添加项目测试示例from pages.add_project_page import AddProjectPage from playwright.sync_api import expect, Page import pytest import uuid from mocks import mock_api class TestAddProject: 添加项目 pytest.fixture(autouseTrue) def start_for_each(self, login_first, page: Page): print(for each--start: 打开添加项目页) self.add_project AddProjectPage(page) self.add_project.navigate() yield print(for each--end: 后置操作) pytest.mark.parametrize(name, app, desc, [ [abc!, , ], [aaaaabbbbbcccccdddddeeeeefffff1, , ], [abc, aa!, ] ]) def test_add_project_disabled(self, name, app, desc): 异常场景-项目名称无效等价特殊字符/大于30个字符 self.add_project.fill_project_name(name) self.add_project.fill_publish_app(app) self.add_project.fill_project_desc(desc) # 断言提交按钮状态 不可点击 expect(self.add_project.locator_save_button).to_be_disabled()代码说明pytest.fixture前/后置夹具pytest.fixture(autouseTrue)作用定义一个测试前置/后置操作的fixture在yield关键字前后执行前后置操作参数说明autouseTrue 自动应用于当前类的所有测试方法无需显式调用。pytest.mark.parametrize参数化装饰器pytest.mark.parametrize(name, app, desc, [ [abc!, , ], [aaaaabbbbbcccccdddddeeeeefffff1, , ], [abc, aa!, ] ])作用实现参数化测试一个测试方法可以运行多个不同的数据name,app,desc每一组参数可以执行一次完整的用例参数说明nameappdesc表示每一组测试的参数名称在测试函数中作参数如 [abc!, , ]将数据存入到列表数据机构当中参数以 , 分开测试方法定义def test_add_project_disabled(self, name, app, desc):这是一个pytest测试方法nameappdesc代表其传入的参数用例。self表示这是类中的一个实例方法。nameappdesc等参数通过parametrize参数化关键字传入。expect断言定义expect 是 Playwright特有的断言API 用于 验证页面元素的状态和行为与Python内置的 assert 语句不同专门为UI自动化测试设计提供了自动等待和页面元素断言方法自动等待元素满足条件# Python方式 assert login_page.locator_login_btn.is_visible() # 可能元素还没加载 # Playwright方式 expect(login_page.locator_login_btn).to_be_visible() # 自动等待直到可见可见性 to_be_visible() 、 to_be_hidden()文本内容 to_have_text() 、 to_contain_text()属性 to_have_attribute() 、 to_have_class()交互性 to_be_enabled() 、 to_be_disabled() 、 to_be_checked()URL和标题 to_have_url() 、 to_have_title()验证元素可见性def test_login_1(self): 用户名为空点注册 # 执行操作 self.login.fill_username() self.login.fill_password(123456) self.login.click_login_button() # 断言错误提示可见 expect(self.login.locator_username_tip1).to_be_visible() expect(self.login.locator_username_tip1).to_contain_text(不能为空)验证元素不可点击def test_login_2(self): 用户名大于30字符 self.login.fill_username(hello world hello world hello world) # 断言保存按钮不可点击 expect(self.login.locator_login_btn).not_to_be_enabled()非法用户名不可点击验证URL和标题def test_login_success_1(self): 登录成功断言url 和title self.login.fill_username(py) self.login.fill_password(123456) self.login.click_login_button() # 验证跳转后的URL和标题 expect(self.login.page).to_have_title(首页) expect(self.login.page).to_have_url(/index.html)跳转到参数中页面3.UI测试 unlogin_page测试方法示例def _build_artifact_test_folder(pytestconfig: Any, request: pytest.FixtureRequest, folder_or_file_name: str) - str: output_dir pytestconfig.getoption(--output) return os.path.join(output_dir, slugify(request.node.nodeid), folder_or_file_name) pytest.fixture(scopesession) def login_first(context, base_url, pytestconfig) - None: 有些网站网页关闭cookie就失效了全局登录一次 # context browser.new_context(base_urlbase_url, no_viewportTrue) print(base_url----, base_url) page context.new_page() LoginPage(page).navigate() LoginPage(page).login(py, 123456) # 等待登录成功页面重定向 page.wait_for_url(url**/index.html) pytest.fixture(scopemodule) def unlogin_context(browser, base_url, pytestconfig, browser_context_args: Dict): 登录注册页面不依赖于先登录单独创建独立的 context 上下文 避免全局先登录加载cookie导致有些打开登录页直接跳到首页去了 :return: context browser.new_context(**browser_context_args) yield context context.close() pytest.fixture def unlogin_page(unlogin_context: BrowserContext, pytestconfig: Any, request: pytest.FixtureRequest): 登录注册页面不依赖于先登录单独创建独立的 page 对象 带上用例失败截图和添加视频功能 pages: List[Page] [] unlogin_context.on(page, lambda page: pages.append(page)) page unlogin_context.new_page() yield page failed request.node.rep_call.failed if hasattr(request.node, rep_call) else True # 截图判断 screenshot_option pytestconfig.getoption(--screenshot) capture_screenshot screenshot_option on or (failed and screenshot_option only-on-failure) print(fcapture_screenshot:{capture_screenshot}) if capture_screenshot: for index, page in enumerate(pages): human_readable_status failed if failed else finished screenshot_path _build_artifact_test_folder( pytestconfig, request, ftest-{human_readable_status}-{index 1}.png ) print(f-----------------{screenshot_path}) try: page.screenshot(timeout5000, pathscreenshot_path) # 把截图放入allure报告 allure.attach.file(screenshot_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.PNG ) except Error: pass page.close() # 用例添加视频 video_option pytestconfig.getoption(--video) preserve_video video_option on or (failed and video_option retain-on-failure) if preserve_video: for page in pages: video page.video if not video: continue try: video_path video.path() file_name os.path.basename(video_path) file_path _build_artifact_test_folder(pytestconfig, request, file_name) video.save_as(pathfile_path) # 放入视频 allure.attach.file(file_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.WEBM) except Error: # Silent catch empty videos. passunlogin_page方法解析该方法依托于unlogin_context适用于创建独立的 context 上下文避免全局先登录加载cookie导致有些打开登录页直接跳到首页去unlogin_page则适用于单独创建独立的 page 对象以及包含用例失败截图和添加视频功能方法定义def unlogin_page(unlogin_context: BrowserContext, pytestconfig: Any, request: pytest.FixtureRequest): 登录注册页面不依赖于先登录单独创建独立的 page 对象 带上用例失败截图和添加视频功能 步骤详解1.定义存储变量pages: List[Page] [] unlogin_context.on(page, lambda page: pages.append(page)) page unlogin_context.new_page()创建一个空列表用于存储所有页面实例注册页面创建事件监听器当有新页面创建时自动添加到列表中page unlogin_context.new_page()2.测试执行yield page将页面实例提供给测试用例使用测试用例执行期间fixture暂停执行3.判断测试结果failed request.node.rep_call.failed if hasattr(request.node, rep_call) else Truerequest.node.rep_call 获取测试用例的执行结果hasattr(request.node, rep_call) 检查是否有执行结果信息4.截图处理screenshot_option pytestconfig.getoption(--screenshot) capture_screenshot screenshot_option on or (failed and screenshot_option only-on-failure) if capture_screenshot: for index, page in enumerate(pages): human_readable_status failed if failed else finished screenshot_path _build_artifact_test_folder(...) try: page.screenshot(timeout5000, pathscreenshot_path) allure.attach.file(screenshot_path, ...) except Error: pass4.1获取截图配置screenshot_option pytestconfig.getoption(--screenshot)4.2判断是否需要截图capture_screenshot screenshot_option on or (failed and screenshot_option only-on-failure) if capture_screenshot:4.3遍历所以页面实例构建测试结果状态生成截图路径for index, page in enumerate(pages): human_readable_status failed if failed else finished screenshot_path _build_artifact_test_folder( pytestconfig, request, ftest-{human_readable_status}-{index 1}.png ) print(f-----------------{screenshot_path})4.4保存截图并添加到Allure报告捕获可能的错误并关闭页面try: page.screenshot(timeout5000, pathscreenshot_path) # 把截图放入allure报告 allure.attach.file(screenshot_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.PNG ) except Error: pass page.close()5.视频处理5.1获取配置选项video_option pytestconfig.getoption(--video)根据“--video”参数获取视频配置选项5.2根据配置选项判断preserve_video video_option on or (failed and video_option retain-on-failure)on始终开启录制视频retain-on-failure仅在测试失败是开启5.3遍历页面获取视频对象if preserve_video: for page in pages: video page.video if not video: continue5.4视频路径处理try: video_path video.path() file_name os.path.basename(video_path) file_path _build_artifact_test_folder(pytestconfig, request, file_name) video.save_as(pathfile_path) allure.attach.file(file_path, namef{request.node.name}-{human_readable_status}-{index 1} ,attachment_typeallure.attachment_type.WEBM) except Error: pass文件路径构建通过_build_artifact_test_folder方法5.5提取视频名称并附加到Allure报告allure.attach.file(file_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.WEBM)file_path文件存储路径name报告名称attachment_type添加的文件类型_build_artifact_test_folderdef _build_artifact_test_folder(pytestconfig: Any, request: pytest.FixtureRequest, folder_or_file_name: str) - str: output_dir pytestconfig.getoption(--output) return os.path.join(output_dir, slugify(request.node.nodeid), folder_or_file_name)参数作用pytestconfig: Anypytest配置对象通过getoption() 方法获取pytest框架的全局配置request: pytest.FixtureRequestpytest测试请求对象代表当前正在执行的测试请求通过request.node 访问当前测试用例节点folder_or_file_name: str字符串调用方法传入指定要创建的文件夹或文件名p4.Mock模拟API的响应1.返回400测试示例mock_project_400 { url: **/api/project, handler: lambda route: route.fulfill( status400, bodyjson.dumps({ errors: { project_name: yo yo 已存在 }, message: Input payload validation failed }) ) }1.1API模拟配置mock_project_400 { url: **/api/project, handler: lambda route: route.fulfill(参数) }1.2url使用通配符 ** 匹配API路径url: **/api/project1.3handler 一个lambda函数用于处理匹配的请求handler: lambda route: route.fulfill(参数)route.fulfill() Playwright的API用于模拟响应1.4响应回报status400, bodyjson.dumps({ errors: {project_name: yo yo 已存在}, message: Input payload validation failed })响应回报status HTTP状态码body 响应内容可以是字符串或JSON对象json.dump:用于将python字典转化成json2.返回500测试示例mock_project_500 { url: **/api/project, handler: lambda route: route.fulfill( status500, body服务端错误 ) }创建项目失败返回服务端错误3.返回200测试示例mock_project_200 { url: **/api/project, handler: lambda route: route.fulfill( status200, body{code: 0, message: success, data: {}} ) }创建项目成功status返回200bodycode0messagesuccessdata{}page.route# 导入mock_api from mocks import mock_api # 在测试方法中使用 with self.page.expect_navigation(): # 应用mock配置拦截API请求 self.page.route(**mock_api.mock_project_400) # 执行触发API请求的操作 self.add_project.click_save_button()测试用例调用 page.route(**mock_config) 应用mock配置5.插件pytest_base_url_pluginpytest.fixture(scopesession) def base_url(request): Return a base URL config request.config base_url config.getoption(base_url) or config.getini(base_url) if base_url is not None: return base_url pytest.fixture(scopesession, autouseTrue) def _verify_url(request, base_url): Verifies the base URL verify request.config.option.verify_base_url if base_url and verify: # Lazy load requests to reduce cost for tests that dont use the plugin import requests from requests.packages.urllib3.util.retry import Retry from requests.adapters import HTTPAdapter session requests.Session() retries Retry(backoff_factor0.1, status_forcelist[500, 502, 503, 504]) session.mount(base_url, HTTPAdapter(max_retriesretries)) session.get(base_url) def pytest_configure(config): if hasattr(config, workerinput): return # dont run configure on xdist worker nodes base_url config.getoption(base_url) or config.getini(base_url) if base_url is not None: config.option.base_url base_url if hasattr(config, _metadata): config._metadata[Base URL] base_url def pytest_report_header(config, start_path): base_url config.getoption(base_url) if base_url: return baseurl: {0}.format(base_url) def pytest_addoption(parser): parser.addini(base_url, helpbase url for the application under test.) parser.addoption( --base-url, metavarurl, defaultos.getenv(PYTEST_BASE_URL, None), helpbase url for the application under test., ) parser.addoption( --verify-base-url, actionstore_true, defaultnot os.getenv(VERIFY_BASE_URL, false).lower() false, helpverify the base url., )组件定义组件类型名称作用fixturebase_url提供基础URLfixture_verify_url验证基础URL可用性钩子函数pytest_configure配置测试环境钩子函数pytest_report_header在报告头部显示URL钩子函数pytest_addoption添加命令行选项base_url fixturepytest.fixture(scopesession) def base_url(request): Return a base URL config request.config base_url config.getoption(base_url) or config.getini(base_url) if base_url is not None: return base_urlconfig.getoption(base_url) 获取命令行参数 --base-url 的值config.getini(base_url) 获取配置文件pytest.ini中 base_url 的值_vertify_url fixturepytest.fixture(scopesession, autouseTrue) def _verify_url(request, base_url): Verifies the base URL verify request.config.option.verify_base_url if base_url and verify: # Lazy load requests to reduce cost for tests that dont use the plugin import requests from requests.packages.urllib3.util.retry import Retry from requests.adapters import HTTPAdapter session requests.Session() retries Retry(backoff_factor0.1, status_forcelist[500, 502, 503, 504]) session.mount(base_url, HTTPAdapter(max_retriesretries)) session.get(base_url)verify_base_url 获取验证URL的配置选项懒加载requests 只有在需要验证时才导入requests库减少资源消耗重试机制 配置了对500/502/503/504状态码的重试pytest_configure钩子def pytest_configure(config): if hasattr(config, workerinput): return # dont run configure on xdist worker nodes base_url config.getoption(base_url) or config.getini(base_url) if base_url is not None: config.option.base_url base_url if hasattr(config, _metadata): config._metadata[Base URL] base_urlhasattr(config, workerinput) 检查是否是分布式测试的工作节点设置全局配置 将基础URL保存到全局配置中报告元数据 将基础URL添加到测试报告的元数据中pytest_report_header钩子def pytest_report_header(config, start_path): base_url config.getoption(base_url) if base_url: return baseurl: {0}.format(base_url)pytest_addoption钩子def pytest_addoption(parser): parser.addini(base_url, helpbase url for the application under test.) parser.addoption( --base-url, metavarurl, defaultos.getenv(PYTEST_BASE_URL, None), helpbase url for the application under test., ) parser.addoption( --verify-base-url, actionstore_true, defaultnot os.getenv(VERIFY_BASE_URL, false).lower() false, helpverify the base url., )parser.addini 添加配置文件选项parser.addoption 添加命令行选项插件注册pytest_plugins [plugins.pytest_base_url_plugin]URL使用def navigate(self): self.page.goto(/login.html) # 实际访问base_url /login.html总conftest.py中注册6.插件pytest_playwrightpytest.fixture(scopesession) def browser_context_args( pytestconfig: Any, playwright: Playwright, device: Optional[str], ) - Dict: context_args {} if device: context_args.update(playwright.devices[device]) base_url pytestconfig.getoption(--base-url) if base_url: context_args[base_url] base_url video_option pytestconfig.getoption(--video) capture_video video_option in [on, retain-on-failure] if capture_video: context_args[record_video_dir] artifacts_folder.name return context_args pytest.fixture(scopesession) def playwright() - Generator[Playwright, None, None]: pw sync_playwright().start() yield pw pw.stop() pytest.fixture(scopesession) def browser_type(playwright: Playwright, browser_name: str) - BrowserType: return getattr(playwright, browser_name) pytest.fixture(scopesession) def launch_browser( browser_type_launch_args: Dict, browser_type: BrowserType, ) - Callable[..., Browser]: def launch(**kwargs: Dict) - Browser: launch_options {**browser_type_launch_args, **kwargs} browser browser_type.launch(**launch_options) return browser return launch pytest.fixture(scopesession) def browser(launch_browser: Callable[[], Browser]) - Generator[Browser, None, None]: browser launch_browser() yield browser browser.close() artifacts_folder.cleanup() pytest.fixture(scopesession) def context( browser: Browser, browser_context_args: Dict, pytestconfig: Any, request: pytest.FixtureRequest, ) - Generator[BrowserContext, None, None]: pages: List[Page] [] context browser.new_context(**browser_context_args) context.on(page, lambda page: pages.append(page)) tracing_option pytestconfig.getoption(--tracing) capture_trace tracing_option in [on, retain-on-failure] if capture_trace: context.tracing.start( nameslugify(request.node.nodeid), screenshotsTrue, snapshotsTrue, sourcesTrue, ) yield context # If requst.node is missing rep_call, then some error happened during execution # that prevented teardown, but should still be counted as a failure print(f----------{hasattr(request.node, rep_call)}) failed request.node.rep_call.failed if hasattr(request.node, rep_call) else True if capture_trace: retain_trace tracing_option on or ( failed and tracing_option retain-on-failure ) if retain_trace: trace_path _build_artifact_test_folder(pytestconfig, request, trace.zip) context.tracing.stop(pathtrace_path) else: context.tracing.stop() screenshot_option pytestconfig.getoption(--screenshot) print(fxxxxxxxxxxx:{screenshot_option}) print(fxxxxxxxxxxx:{failed}) capture_screenshot screenshot_option on or ( failed and screenshot_option only-on-failure ) print(fcapture_screenshot:{capture_screenshot}) if capture_screenshot: for index, page in enumerate(pages): human_readable_status failed if failed else finished screenshot_path _build_artifact_test_folder( pytestconfig, request, ftest-{human_readable_status}-{index 1}.png ) print(f-----------------{screenshot_path}) try: page.screenshot(timeout5000, pathscreenshot_path) # 把截图放入allure报告 allure.attach.file(screenshot_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.PNG ) except Error: pass context.close() video_option pytestconfig.getoption(--video) preserve_video video_option on or ( failed and video_option retain-on-failure ) if preserve_video: for page in pages: video page.video if not video: continue try: video_path video.path() file_name os.path.basename(video_path) file_path _build_artifact_test_folder(pytestconfig, request, file_name) video.save_as( pathfile_path ) # 放入视频 allure.attach.file(file_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.WEBM) except Error: # Silent catch empty videos. pass pytest.fixture def page(context: BrowserContext, pytestconfig: Any, request: pytest.FixtureRequest, ) - Generator[Page, None, None]: pages: List[Page] [] context.on(page, lambda page: pages.append(page)) page context.new_page() yield page failed request.node.rep_call.failed if hasattr(request.node, rep_call) else True # 截图判断 screenshot_option pytestconfig.getoption(--screenshot) capture_screenshot screenshot_option on or ( failed and screenshot_option only-on-failure ) print(fcapture_screenshot:{capture_screenshot}) if capture_screenshot: for index, page in enumerate(pages): human_readable_status failed if failed else finished screenshot_path _build_artifact_test_folder( pytestconfig, request, ftest-{human_readable_status}-{index 1}.png ) print(f-----------------{screenshot_path}) try: page.screenshot(timeout5000, pathscreenshot_path) # 把截图放入allure报告 allure.attach.file(screenshot_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.PNG ) except Error: pass page.close() # 用例添加视频 video_option pytestconfig.getoption(--video) preserve_video video_option on or ( failed and video_option retain-on-failure ) if preserve_video: for page in pages: video page.video if not video: continue try: video_path video.path() file_name os.path.basename(video_path) file_path _build_artifact_test_folder(pytestconfig, request, file_name) video.save_as( pathfile_path ) # 放入视频 allure.attach.file(file_path, namef{request.node.name}-{human_readable_status}-{index 1}, attachment_typeallure.attachment_type.WEBM) except Error: # Silent catch empty videos. pass组件定义组件作用逻辑browser_context_args配置浏览器上下文的参数模拟设备基础URL视频录制playwright管理Playwright实例的生命周期启动Playwright提供Playwright实例给其他fixture关闭Playwright释放资源browser_type获取指定类型的浏览器使用 getattr() 动态获取浏览器类型支持多浏览器测试launch_browser创建一个启动浏览器的函数闭包设计合并默认和自定义参数浏览器在调用函数时启动browser管理浏览器实例的生命周期launch_browser() 调用前面定义的启动函数提供浏览器实例资源清理context管理浏览器上下文和测试报告集成创建上下文启动测试追踪处理截图处理视频page管理页面实例创建页面提供给其他测试用例截图处理视频处理四、项目总结该项目是一个UI自动化框架的封装平台,主要基于PythonPlayWright搭建了接口自动化框架,实现注册,登录,新增项目,新增环境等功能用例转自动化的实现,并且合理使用断言,生成可视化的测试报告

相关新闻