# 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
## 常见问题及解决办法
### 浏览器驱动未安装或版本不正确
问题描述:若浏览器驱动未安装或版本不正确时,执行脚本时会报以下错误:
解决办法:使用上文提到的命令,下载正确版本的浏览器驱动。
### 页面未加载完全,导致断言中获取元素内容失败
问题描述:运行用例时,由于操作过快,页面未加载完全,获取的断言不正确,导致脚本运行失败。
解决办法:引入sleep方法,在对应的操作步骤或断言前添加等待时间(等待时间以秒为单位)。
```shell
#引入sleep方法块
from time import sleep
#对应的操作步骤或断言前添加等待时间
sleep(1)
```