UI测试

自动录制

python -m playwright codegen --viewport-size "1920,1080" -o .\test_w.py
#指定窗口大小
#输出录制内容到指定文件

pytest + playwright 三种执行方式

1.命令行运行

pytest + run.py #模块名称 检测当前目录下的测试case执行 可以在pytest后添加参数 import os import pytest if name=='main': pytest.main(['-vs', './testcase/login/test_login_usererror.py', '--alluredir', './temp']) #pytest.main(['-vs', './testcase/', '--alluredir', './temp']) os.system('allure generate ./temp -o ./report --clean') #生成正式的测试报告 timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S') #截取当前时间戳 report_path = f'./report/report_{timestamp}' #生成新的时间戳文件夹路径 os.system(f'allure generate ./temp -o {report_path} --clean') #生成每一次当前时间执行的的测试报告 注意下main内的参数文件名称 指定文件就执行指定文件 指定文件夹就执行指定文件夹的测试用例

2.IDE直接运行

3.pytest.ini运行

配置文件为pytest.ini

[pytest]
addopts=-vs --alluredir=./temp --clean-alluredir #命令行参数使用空格进行分割
testpaths=./testcase #测试用例文件夹
python_files=test_.py #模块文件
python_classes=Test #测试类名
python_functions=test #测试函数名

使用yaml文件存储测试数据

loginpage:
	- title: 登录
		des: 登录的测试用例
		cases:
			- name: 登录页
				method: goto
				url: http://172.11.32.82:9002/Account/Login?ReturnUrl=%2F
			- name: 输入用户名
				method: fill
				selector: //*[@id="UserName"]
				values: "*****" 此处输入用户名
			- name: 输入密码
				method: fill
				selector: //*[@id="password_inputs"]
				values: "*****" 此处输入密码\
			- name: 点击登录
        method: click
        selector: //*[@id="app"]/main/div/div[2]/div/button/span

目前使用结构

common #存放通用模块

testcase #存放测试用例

testdata #存放测试数据

temp #存放临时文件

report #存放最新测试报告

使用allure的测试报告插件

模块间引用

在A.py种编写Test_a类如果需要在B.py中引用

在B.py文件中编写如下

from A import Test_a

公共方法

import pytest
from playwright.sync_api import sync_playwright
@allure.feature('Playwright EMS2.0 Forend')
class TestRun:
	def setup_class(self):
		self.playwright = sync_playwright().start()
        self.browser = self.playwright.chromium.launch(headless=False, args=['--start-maximized'])
		self.context = self.browser.new_context()
		self.page = self.context.new_page()
	def teardown_class(self):
    	self.page.wait_for_timeout(3000)
    	self.browser.close()
    	self.playwright.stop()

	def run_step(self, func, *args):
    	func(*args)

	def run_case(self, cases):
    	allure.dynamic.title(cases[0]['title'])
    	allure.dynamic.description(cases[0]['des'])
    	cases = cases[0]['cases']
    	try:
            for case in cases:
                self.page.wait_for_timeout(3000)
                method = case['method']
                if method == 'goto':
                    url = case['url']
                    with allure.step(case['name']):
                        self.run_step(self.page.goto, url)
                elif method == 'click':
                    selector = case['selector']
                    with allure.step(case['name']):
                        self.run_step(self.page.click, selector)
                elif method == 'fill':
                    selector = case['selector']
                    values = case['values']
                    with allure.step(case['name']):
                        self.run_step(self.page.fill, selector,values)

        except Exception:
            self.page.wait_for_timeout(2000)
            allure.attach(self.page.screenshot(), '用例失败截图', allure.attachment_type.PNG)
            pytest.fail('测试用例失败')
        self.page.wait_for_timeout(2000)
        allure.attach(self.page.screenshot(), '用例执行成功截图', allure.attachment_type.PNG)

例如:

登录调用统一执行每一步用例

#读取测试用例数据

def load_test_ui_file(case_type):
	test_files = [
	'F:/data_test/test_w.yaml'
	]
	test_data = {}
	for file in test_files:
		with open(file, encoding='utf-8') as f:
			data = yaml.safe_load(f)
			test_data.update(data)
	return test_data[case_type]
class TestUi(TestRun):
	@pytest.mark.parametrize('cases', [
	load_test_ui_file('loginpage'),
	#load_test_ui_file('passworderrorpage'),
	# load_test_ui_file('getscreen2')
	])
	def test_ui(self, cases):
	self.run_case(cases)

断言使用

#获取页面文字做断言

content = self.page.text_content('//html/body/div[1]/div[2]/div[2]/div[2]/div/div[1]/div[5]/div/div[1]/div[2]')
#print(content)
assert content == "调度信息"

接口测试

pytest + requests + allure 三种执行方式

1.命令行运行 pytest + run.py #模块名称 检测当前目录下的测试case执行 可以在pytest后添加参数 import os import pytest if name=='main': pytest.main(['-vs', './testcase/login/test_login_usererror.py', '--alluredir', './temp']) #pytest.main(['-vs', './testcase/', '--alluredir', './temp']) os.system('allure generate ./temp -o ./report --clean') #生成正式的测试报告 timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S') #截取当前时间戳 report_path = f'./report/report_{timestamp}' #生成新的时间戳文件夹路径 os.system(f'allure generate ./temp -o {report_path} --clean') #生成每一次当前时间执行的的测试报告 注意下main内的参数文件名称 指定文件就执行指定文件 指定文件夹就执行指定文件夹的测试用例

2.IDE直接运行

3.pytest.ini运行 配置文件为

pytest.ini

[pytest]
addopts=-vs --alluredir=./temp --clean-alluredir #命令行参数使用空格进行分割
testpaths=./testcase #测试用例文件夹
python_files=test_.py #模块文件
python_classes=Test #测试类名
python_functions=test #测试函数名

使用yaml文件存储测试数据 getadapterlist 三个测试用例

getadapterlist:
  - title: 获取控制单元列表
		des: 获取所有控制单元的基础信息
		cases:
  	  - name: 获取所有控制单元的基础信息
				url: http://172.11.32.82:8802/api/InstitutionMng/GetAdapterList/
				method: POST
				headers:
				Content-Type: application/json
				payload:
					{"pageIndex":1,"pageSize":10,"queryModel":{"Id":"","Name":"","StationId":[],"CompanyId":""}}
			- name: 获取所有控制单元的基础信息url地址错误
				url: http://172.11.32.82:8802/api/InstitutionMng/GetAdapterList1/
				method: POST
				headers:
				Content-Type: application/json
				payload:
					{ "pageIndex": 1,"pageSize": 10,"queryModel": { "Id": "","Name": "","StationId": [ ],"CompanyId": "" } }
			- name: 获取所有控制单元的基础信息分页错误
				url: http://172.11.32.82:8802/api/InstitutionMng/GetAdapterList/
				method: POST
				headers:
				Content-Type: application/json
				payload:
					{ "pageIndex": 0,"pageSize": 10,"queryModel": { "Id": "","Name": "","StationId": [ ],"CompanyId": "" } }

目前使用结构

common #存放通用模块

testcase #存放测试用例

testdata #存放测试数据

temp #存放临时文件

report #存放最新测试报告

使用allure的测试报告插件

测试报告标题

allure.dynamic.title(cases['title'])

测试报告描述

allure.dynamic.description(cases['des'])
allure.story('登录成功')
执行步骤
allure.step("开始执行")


class Test_api:
	def api_case(self,cases):
	# 测试报告标题
	allure.dynamic.title(cases['title'])
	# 测试报告描述
	allure.dynamic.description(cases['des'])
	#indexpage = cases['indexpage']
	cases = cases['cases']
	with allure.step("开始执行"):
		try:
    		for case in cases:
                # 添加Allure测试步骤

                url = case['url']
                method = case['method']
                headers = case['headers']
                payload = case['payload']
                name = case['name']
                with allure.step(f"请求说明: {name}"):
                    allure.attach(name, "请求说明")
                with allure.step(f"请求URL: {url}"):
                    allure.attach(url, "请求URL")

                self.response = requests.request(method, url, headers=headers, json=payload)
                with allure.step(f"请求参数: {payload}"):
                    allure.attach(str(payload), "请求参数")
                #context_dict2 = self.response.headers
                #返回数据转成json格式 其实是一个字典
                #context_dict = self.response.json()

                with allure.step("验证响应状态码"):
                    allure.attach(str(self.response.status_code), "响应状态码")
                with allure.step("响应时间"):
                    allure.attach(str(self.response.elapsed.total_seconds()), "输出响应时间")
                    #assert self.response.status_code == 200

                if self.response.status_code == 200 and self.response.json()['code'] == 0:
                    with allure.step("执行成功信息"):
                        allure.attach(self.response.content, "执行成功返回信息", allure.attachment_type.JSON)

                elif self.response.status_code != 200:
                    with allure.step("用例执行失败信息"):
                        allure.attach(str(self.response.status_code), '执行失败信息')

                else:
                    with allure.step("用例执行失败信息"):
                        allure.attach(self.response.content, "执行失败信息", allure.attachment_type.JSON)
        except Exception:
            with allure.step("用例执行失败信息"):
                allure.attach(self.response.content, "执行失败信息", allure.attachment_type.JSON)
                pytest.fail(f"用例执行失败状态码: {self.response.status_code},请求url:{case['url']}")

例如:登录调用统一执行每一步用例

class Test_adapter(Test_api):
	@pytest.mark.parametrize('cases',load_test_data())
	def test_api(self,cases):
		self.api_case(cases)

断言使用

#返回数据转成json格式 其实是一个字典

context_dict = self.response.json()
assert response.status_code == 200
assert response.方法

两种方式实现测试用例

第一种: 一个测试用例一个文件

getadapterlist:
  - title: 获取控制单元列表
	des: 获取所有控制单元的基础信息
	cases:
	  - name: 获取所有控制单元的基础信息
		url: http://172.11.32.82:8802/api/InstitutionMng/GetAdapterList/
		method: POST
		headers:
		Content-Type: application/json
		payload:
			{"pageIndex":1,"pageSize":10,"queryModel":{"Id":"","Name":"","StationId":[],"CompanyId":""}}

一个py文件调用公共方法

import allure
import pytest
import requests
import yaml
from common.run_api import Test_api
	def load_test_data():
		with open('F:/EMS2.1.0/testdata/Institutionmng/adapter/test_adapter.yaml', 'r', encoding='utf-8') as file:
			test_data = yaml.safe_load(file)
			return test_data['getadapterlist']
class Test_adapter(Test_api):
	@pytest.mark.parametrize('cases',load_test_data())
	def test_api(self,cases):
		self.api_case(cases)

第二种: 一个测试用例多个文件

例如: 测试一个控制单元列表 一个yaml文件存放多条测试数据

getadapterinfoid:
  - title: 控制单元详情
    des: 获取控制单元详情
    cases:
      - name: 获取控制单元详情
        url: http://172.11.32.82:8802/api/InstitutionMng/GetAdapterInfoById?adapterId=DAT012
        method: GET
        headers:
        Content-Type: application/json
        payload: {}

getadapterlist:
  - title: 获取控制单元列表
    des: 获取所有控制单元的基础信息
    cases:
      - name: 获取所有控制单元的基础信息
        url: http://172.11.32.82:8802/api/InstitutionMng/GetAdapterList/
        method: POST
        headers:
        Content-Type: application/json
        payload:
          {"pageIndex":1,"pageSize":10,"queryModel":{"Id":"","Name":"","StationId":[],"CompanyId":""}}


saveadapter:
  - title: 保存控制单元信息
    des: 保存控制单元的基础信息
    cases:
      - name: 保存控制单元的基础信息
        url: http://172.11.32.82:8802/api/InstitutionMng/SaveAdapter/
        method: POST
        headers:
        Content-Type: application/json
        payload:
          {"id":"","name":"12111","stationId":"TC004","stationName":"天顺五股泉风储电站"}

deleteadapter:
  - title: 删除控制单元信息
    des: 删除控制单元的基础信息
    cases:
      - name: 删除控制单元信息
        url: http://172.11.32.82:8802/api/InstitutionMng/DeleteAdapter?ids=DAT013
        method: GET
        headers:
        Content-Type: application/json
        payload:
          {}

exportadapter:
  - title: 导出控制单元信息
    des: 导出控制单元的基础信息
    cases:
      - name: 导出控制单元信息
        url: http://172.11.32.82:8802/api/InstitutionMng/ExportAdapterData/
        method: POST
        headers:
        Content-Type: application/json
        payload:
          {"pageIndex":1,"pageSize":10,"queryModel":{"Id":"","Name":"","StationId":[],"CompanyId":""}}

一个py文件参数化的方式调用公共方法

import allure
import pytest
import requests
import yaml
import os
def load_test_data(case_type):
		test_files = [
			'F:/EMS2.1.0/testdata/Institutionmng/adapter/test_adapter.yaml',
			'F:/EMS2.1.0/testdata/Institutionmng/company/test_company.yaml',
			'F:/EMS2.1.0/testdata/Institutionmng/station/test_station.yaml',
		]
	test_data = {}
	for file in test_files:
		with open(file, 'r', encoding='utf-8') as f:
			data = yaml.safe_load(f)
			test_data.update(data)
			#print(test_data)
	return test_data[case_type]
  
@allure.feature('查看控制单元列表')
class TestAdapter(TestApi):
	@allure.story('测试用例')
	@pytest.mark.parametrize('cases', [
		pytest.param(load_test_data('getadapterlist'), id='获取所有控制单元的基础信息'),
		pytest.param(load_test_data('getadapterlist1'), id='获取所有控制单元的基础信息url地址错误'),
		pytest.param(load_test_data('getadapterlist2'), id='获取所有控制单元的基础信息分页错误'),
		pytest.param(load_test_data('getadapterinfoid'),id='获取控制单元的详细信息')
		])
#另一种参数写法
#@pytest.mark.parametrize('cases',[load_test_data('getadapterlist'),load_test_data('getadapterlist1'),load_test_data('getadapterlist2')])
	def test_api(self, cases):
    	self.api_case(cases)

实现多个参数化的形式实现的多个用例执行