# fn_auto_test **Repository Path**: wego/fn_auto_test ## Basic Information - **Project Name**: fn_auto_test - **Description**: 自动化测试 - **Primary Language**: Python - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-07-19 - **Last Updated**: 2025-07-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # UI自动化测试框架 ## 框架介绍 ui自动化测试框架集成了playwright和pytest,提供简单、快捷、可视化的用例录制手段,以及结果统计、报告生成、持续构建等特性,实现UI自动化测试的全链路解决方案。 ## 安装部署 ### 依赖环境 | 软件 | 版本 | 备注 | | :-----| :---- | :---- | | 操作系统 | win7及以上; centos7及以上 | centos系统要求:CLIBC版本为2.18+,字符集为zn_CN.UTF8 | | python | v3.7+ | | | pip/pip3 | v22.0.3+ | | PS:可使用命令:python -m pip install --upgrade pip,升级pip/pip3版本。 ### 本地部署 - 用VSCode或Pycharm打开本地项目代码,根目录下的requirements.txt文件中有UI自动化测试所用的所有依赖包,终端中执行命令即可安装: ```shell $ pip install -r requirements.txt ``` - 安装浏览器驱动 ```shell python -m playwright install ``` 说明:以上步骤操作完毕后,可在本地运行测试框架自带的测试用例,检验环境是否安装成功。 ## 框架介绍 ### 框架文件结构 ``` FN-AutoTest # UI自动化测试工程 ├── common # 通用方法 │ └── get_env.py # 获取环境变量 └── get_log.py # 日志装饰器 ├── config # 配置 │ └── setting.py # 配置文件 ├── page # 测试用例page页面流程 │ ├── fn_workbench # 功能模块名 │ └── index.py # UI测试用例page页面流程--首页功能 ├── report # 测试用例结果输出路径 │ ├── logs # 日志文件 │ └── xxx.log │ ├── report.html # html格式的测试报告 ├── testcase # 测试用例存放路径 │ ├── fn_workbench # 功能模块名 │ | └── test_index.py # UI测试用例-测试首页功能 └── conftest.py # UI自动化测试框架的自带的底层方法 ├── testdata # 测试用例用到的测试数据 │ ├── fn_workbench # 功能模块存放参数化数据的路径 │ └── indexData.py # 存放参数化的数据(若不使用参数化,此文件可忽略) ├── tools # 存放公共方法的目录 ├── requirements.txt # 所有UI自动化测试实施时所需的依赖环境 └── main.py # 批量运行全部/部分用例 ``` ## 开发测试用例 ### 写在前面 - 项目testcase目录下有一个 conftest.py,这个文件可以理解为自动化测试用例运行的前置条件,用于定义登录 Token、浏览器驱动等复用逻辑,供其他测试文件直接调用。文件名是 **pytest 框架强制规定的,不可更改**。pytest 通过该特定名称自动发现并加载配置,若重命名将导致配置失效 - 当前项目 conftest.py 如下样例所示: ``` import pytest from playwright.sync_api import sync_playwright from common.get_env import env @pytest.fixture(scope="session") def browser_context(): """浏览器初始化""" with sync_playwright() as p: browser = p.chromium.launch(headless=False,slow_mo=500) context = browser.new_context(viewport={"width": 1920, "height": 1080}) page = context.new_page() yield page page.close() context.close() browser.close() @pytest.fixture(scope="session") def login(browser_context): """登录TOKEN""" browser_context.goto(fr'{env["host"]}/#/login') browser_context.get_by_placeholder("请输入用户账号").fill(env['username']) browser_context.get_by_placeholder("请输入登录密码").fill(env['pwd']) browser_context.get_by_placeholder("请输入手机验证码").fill(env['sms_code']) browser_context.get_by_placeholder("验证码").nth(1).fill(env['captcha_code']) browser_context.get_by_role("button", name="登录").click() yield browser_context ``` 说明: 1. 自动化测试实际执行只会登录一次,其他测试用例只需要将上面login函数作为入参传入,就可以复用登录token,无需执行每个用例都重新登录一次 ### 如何编写一个新的自动化测试用例 - #### 项目代码是如何封装的: ``` main.py >>> testcase目录下的用例主流程 >>> page目录下的实际自测执行流程 >>> testdata目录下的测试数据(如果样例有配置参数化测试数据) ``` ##### 项目目录结构说明: ​ 1. main.py 作为项目的启动文件,读取testcase目录下的自动化测试用例. ​ 注意:testcase目录下的自动化测试用例文件名称需以“test_”开头,否则工程中将不会执行此用例 ​ 2.testcase目录下可以基于实际项目新建业务的子目录,用于区分用例文件。子目录下的 test_xx.py 文件为业务主流程,如:新增、删除等 ​ 3.page目录下可基于实际项目创建业务的子目录。testcase目录记录用例主流程,具体每个用例实际执行的操作则保存在page目录。 ​ 4.当执行新增或者编辑用例,如果数据不是直接写死在page里面,则可以将数据配置到testdata对应目录下,page引用testdata变量,作为用例的入参 - #### 写一个新的用例需要做什么: ​ 项目中的流程已经全部规范化配置好,当我们需要编写一个新的自动化测试用例,只需要: ​ 1. 基于业务新建一个对应的testcase的子目录,eg: fn_industrial_service 。子目录下的主流程当前代码中有一套样例参考,包含:新增 删除 编辑 查询 流程 ​ 2. page目录下新建子目录及用例文件,根据 testcase的主流程实现在page下实现具体的操作流程 ​ 3.(非必须项)如果项目有参数化数据需要配置,testcdata目录下配置用例执行中需要的参数化数据 ##### 综上所述: ​ 基于业务需求补充page里的实际业务操作流程用例即可。playwright 工具自身具备录制操作命令,并将操作流程转换成代码的能力,因此只需要通过 playwright录制操作流程,将工具生成的代码做适当调整基于保存到page目录下作为新的测试用例。 ​ 注意:项目可能因为前端编码风格的差异,有时候playwright录制的脚本运行可能会失败,需要基于实际项目做调整,建议脚本调整及验证后确认执行无误,再放到page目录下 - #### 如何通过playwright录制用例: 1. 在cmd或编辑器的终端中,进入到UI自动化测试工程的testcase或者page目录; 2. 执行录制命令,如:浏览器输入地址为 http://test-env.jcinfo.com/jcdp ,将录制生成的文件名称指定为test_userCenter_login.py,可输入以下命令 ``` python -m playwright codegen -o test_userCenter_login.py http://test-env.jcinfo.com/jcdp ``` 3. 录制完毕后关闭浏览器,录制内容可自动保存至文件中,内容如下: ``` import re from playwright.sync_api import Playwright, sync_playwright, expect def run(playwright: Playwright) -> None: browser = playwright.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto("http://www.testweb.com/fn_front/#/login?redirect=/ruralGovernance/villageManagement/ruralPeople") page.get_by_role("textbox", name="请输入用户账号").click() page.get_by_role("textbox", name="请输入用户账号").fill("test") page.get_by_role("textbox", name="请输入登录密码").fill("Aa123456") page.get_by_role("button", name="登录").click() page.get_by_role("menuitem", name="乡村治理").locator("svg").click() page.get_by_role("menuitem", name="村务管理").locator("svg").click() page.get_by_text("村民管理").click() page.wait_for_selector("div:has-text('性别请选择性别') svg", state="visible") page.locator("div").filter(has_text=re.compile(r"^性别请选择性别$")).locator("svg").click() page.locator("div").filter(has_text=re.compile(r"^男$")).nth(1).click() page.wait_for_selector("div:has-text('民族请选择民族') svg", state="visible") page.locator("div").filter(has_text=re.compile(r"^民族请选择民族$")).locator("svg").click() page.locator("div").filter(has_text=re.compile(r"^汉族$")).first.click() page.get_by_role("button", name="查询").click() page.get_by_role("button", name="重置").click() page.close() # --------------------- context.close() browser.close() with sync_playwright() as playwright: run(playwright) ``` 4. 双击录制生成的脚本文件可以回放整个录制过程,检查是录制内容是否正确。 ​ **说明:UI自动化测试用例文件名称需以“test_”开头,否则工程中将不会执行此用例。** ### 编辑用例 ##### 引入工程包 使用pycharm或VSCode打开UI自动化测试工程,在录制好的脚本最上方,添加引入工程包的语句(不需要修改,直接复制即可) ``` from page.basepage import BasePage from common.get_log import printlog import re from config.setting import index_url from common.get_env import env ``` ##### 参数化 测试用例中经常使用的变量(如:网址、用户名、密码等),可放在testdata目录下的xxx.py文件中,每个测试用例均可引用此变量作为方法的参数使用。若用例需要更换用户名及密码进行登录,则不需要修改用例本身,可直接修改xxx.py文件中的参数值即可。 如:将登录时使用的网址、用户名、密码配置在testdata\fn_rural_governance\villageManagementData.py文件中 ``` update_data = { "realName": "测试张山0605", "groupName": "五组", "nationality":"白族", "iDNumber": "321321198502091839", "mobile": "13245125648", "employment": "骑驴找马", "currentAddress":"当前地址在当前", "moveInfo":"搬家信息", "religiousBeliefs": "我命由我不由天", } ``` 在测试用例文件中,定义方法时传入并使用用户名参数、密码参数(定义方法后,在注释中说明测试用例名称及步骤): ``` from page.basepage import BasePage from common.get_log import printlog import re class VillagePage(BasePage): """ 村民管理页面""" def __init__(self, page): super().__init__(page) # 编辑村民 @printlog def update_village(self,data): self.page.get_by_role("menuitem", name="乡村治理").locator("svg").click() self.page.get_by_role("menuitem", name="村务管理").locator("svg").click() self.page.get_by_text("村民管理").click() self.page.get_by_role("row", name="1").get_by_role("button").nth(1).click() self.page.locator("div > .n-form-item > .n-form-item-blank > .n-input > .n-input-wrapper > .n-input__input > .n-input__input-el").first.click() self.page.locator("div > .n-form-item > .n-form-item-blank > .n-input > .n-input-wrapper > .n-input__input > .n-input__input-el").first.fill(data["realName"]) # 小组下拉框 self.page.locator("div").filter(has_text=re.compile(r"^所属小组")).locator("svg").click() self.page.locator("div").filter(has_text=re.compile(rf"^{data['groupName']}$")).nth(1).click() # 编辑民族 self.page.locator("div").filter(has_text=re.compile(r"^民族:")).locator("svg").click() self.page.locator("div").filter(has_text=re.compile(rf"^{data['nationality']}$")).first.click() # 家庭住址 self.page.locator("div").filter(has_text=re.compile(r"^现在住址:")).get_by_placeholder("请输入").click() self.page.locator("div").filter(has_text=re.compile(r"^现在住址")).get_by_placeholder("请输入").fill(data["currentAddress"]) self.page.locator("div").filter(has_text=re.compile(r"^学历")).locator("svg").first.click() self.page.locator("div").filter(has_text=re.compile(r"^高中$")).first.click() self.page.locator("div").filter(has_text=re.compile(r"^宗教信仰")).get_by_placeholder("请输入").click() self.page.locator("div").filter(has_text=re.compile(r"^宗教信仰")).get_by_placeholder("请输入").fill(data["religiousBeliefs"]) self.page.locator("div").filter(has_text=re.compile(r"^就业情况")).get_by_placeholder("请输入").click() self.page.locator("div").filter(has_text=re.compile(r"^就业情况")).get_by_placeholder("请输入").fill(data["employment"]) self.page.get_by_role("button", name="确定").click() ``` 说明: 测试用例文件若不使用参数化方式对变量进行设置,则变量值发生变化时直接在测试用例文件中修改即可。 ##### 添加断言 在录制生成的脚本中,需要根据用例添加对应的断言 ``` # 执行操作后,获取浏览器弹窗是否为“操作成功”,如果不是,则抛出异常 ··· try: v.assert_text_visible("操作成功") except Exception as e: # 日志添加 print(e) finally: v.page.reload() v.goto_index_page() ``` ##### 注释或删除无用代码 录制生成的脚本中,需要将以下代码注释或删除,脚本才可运行成功。 ``` # browser = playwright.chromium.launch(headless=False) # context = browser.newContext() # page = context.newPage() # context.close() # browser.close() # with sync_playwright() as playwright: # run(playwright) ``` ### 本地调试用例 * 运行所有用例,可在终端中执行 ```shell $ python main.py testcase ``` * 运行某个功能模块中的所有用例,可在终端中执行 ```shell $ python main.py 功能模块名称 ``` * 运行单个脚本,需要进入到此用例所在目录 ```shell $ python main.py 功能模块名称\用例文件名称 ``` 说明: 1. 控制台中可输出脚本执行成功及失败情况,具体失败原因可查看log文件或report; 2. 执行脚本时,可在命令行中指定运行模式(headed/headless)、测试服务器地址、是否开启企业微信通知,若不指定,则按照config.py文件中默认配置执行。如:python main.py testCases mode:headed server_address:[http://uitest.jcinfo.com](http://uitest.jcinfo.com/) notice_status:True。 ## 执行测试及结果查看 ### 执行测试 - 本地执行:在编辑器的终端中执行命令即可实现本地执行,如执行:python main.py testcase; ### 结果查看 - 控制台查看:执行过程中控制台有输出执行成功的用例,执行失败的用例 - 日志查看:在项目./report/logs 目录下会有执行自动化测试用例的日志,记录成功和失败的用例 - html报告查看:用浏览器打开项目./report/report.html 文件,可以查看此次执行结果的报告 ## 常用方法 ### 常用断言 - page.text_content("nav:first-child"):获取文本内容; - page.get_attribute("input", "alt"):获取属性值; - page.is_checked("input"):获取checkbox状态; - page.is_visible("input"):是否可见; - page.is_enabled("input"):是否可不输入。 更多断言方法可参考:https://playwright.dev/docs/test-assertions ### 元素定位 - path方式定位:page.click("xpath=//h2")或page.click("//h2") - 文本方式定位:page.click("text=退出登录")或page.click("'退出登录"') - CSS方式定位:page.click("css=h2")或page.click("h2") 更多元素定位方法可参考:https://playwright.dev/docs/locators ## 常见问题及解决办法 ### 浏览器驱动未安装或版本不正确 问题描述:若浏览器驱动未安装或版本不正确时,执行脚本时会报以下错误: problem01 解决办法:使用上文提到的命令,下载正确版本的浏览器驱动。 ### 页面未加载完全,导致断言中获取元素内容失败 问题描述:运行用例时,由于操作过快,页面未加载完全,获取的断言不正确,导致脚本运行失败。 解决办法:引入sleep方法,在对应的操作步骤或断言前添加等待时间(等待时间以秒为单位)。 ```shell #引入sleep方法块 from time import sleep #对应的操作步骤或断言前添加等待时间 sleep(1) ```