html网站建设源码,wordpress 目录 模板,媒体库wordpress,软件综合课设做网站软件测试经理工作日常随记【7】-UI自动化#xff08;多端集成测试#xff09;
自动化测试前篇在此
前言
今天开这篇的契机是#xff0c;最近刚好是运维开发频繁更新证书的#xff0c;每次更新都在0点#xff0c;每次一更新都要走一次冒烟流程。为了不让我的美容觉被阉割…软件测试经理工作日常随记【7】-UI自动化多端集成测试
自动化测试前篇在此
前言
今天开这篇的契机是最近刚好是运维开发频繁更新证书的每次更新都在0点每次一更新都要走一次冒烟流程。为了不让我的美容觉被阉割bushi为了方便同事儿不用每次更新都求爷告奶地通知大家辛苦半夜走一遍测试。我紧赶慢赶于是有了此。因为此次冒烟涉及三个端其中两个端采用接口自动化另外一个端采用UI自动化集成运行。
正文
工具类
用于UI自动化的工具类
# utils.py 用于UI自动化的工具类包含在pc端有界面的执行和linux服务器无界面的执行本段代码以linux服务器无界面运行为例设置驱动
驱动停止
获取弹窗信息
获取data数据import json
import timeimport allure
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from log.log import Loggerclass UtilsDriver:classmethoddef get_driver(cls)::return:浏览器驱动if cls._driver is None:# 创建一个ChromeOptions对象chrome_options Options() # linux服务运行1# 添加--headless参数来启用无头模式chrome_options.add_argument(--headless) # linux服务运行2# 指定ChromeDriver的路径如果不在PATH环境变量中# driver_path /path/to/chromedriver# driver webdriver.Chrome(executable_pathdriver_path, optionschrome_options)# 如果ChromeDriver已经在PATH中或者你可以直接调用它则可以省略executable_pathcls._driver webdriver.Chrome(optionschrome_options) # linux服务运行3# cls._driver webdriver.Chrome() # 谷歌如在pc电脑有UI界面则不执行以上服务器执行的步骤执行该行代码cls._driver.maximize_window()cls._driver.implicitly_wait(5)cls._driver.get(http://********) time.sleep(1)return cls._driverclassmethod关闭浏览器驱动def quit_driver(cls):if cls._driver is not None:cls.get_driver().quit()cls._driver Noneclassmethod获取元素信息用于断言def get_mes(cls, xpath):return UtilsDriver.get_driver().find_element(By.XPATH, xpath).textclassmethoddef get_mes_wait(cls, xpath):显性等待获取元素信息wait WebDriverWait(UtilsDriver.get_driver(), 10, 1)element wait.until(lambda x: x.find_element(By.XPATH, xpath))return elementclassmethoddef get_mes_s(cls, xpath)::param xpath: 元素的路径:return: 返回的是以元素列表不可以直接取text,只能用for循环历遍各个元素并读取文本值eles UtilsDriver.get_driver().find_elements(By.XPATH, xpath)alist []for ele in eles:ele_mes ele.textalist.append(ele_mes)print(alist)return alistclassmethod显性等待获取元素定位def get_element_utils_wait(cls, location): # page页对象层的基类显式等待wait WebDriverWait(UtilsDriver.get_driver(), 10, 1)element_wait wait.until(lambda x: x.find_element(By.XPATH, location))return element_waitclassmethoddef get_elements(cls, xpath)::param xpath: 表示元素定位的路径:return: 返回找到的元素return UtilsDriver.get_driver().find_elements(By.XPATH, xpath)classmethoddef get_attribute(cls, xpath, attribute):以元素的属性值来断言断言前必须延迟2s:param xpath: 找到元素的路径只取到前面的标签然后根据标签中的元素名来判断属性值对不对:param attribute: 标签中的元素名:return: 属性值return UtilsDriver.get_driver().find_element(By.XPATH, xpath).get_attribute(attribute)classmethoddef get_text(cls, xpath, expected_msg, xpath2, expected_msg2):有两个断言元素获取元素的文本来断言断言前必须延迟2s:param xpath: 定位元素的路径1:param expected_msg: 断言参数1:param xpath2: 定位元素的路径2:param expected_msg2: 断言参数2actual_mes UtilsDriver.get_driver().find_element(By.XPATH, xpath).textactual_mes2 UtilsDriver.get_driver().find_element(By.XPATH, xpath2).textprint(生成截图)allure.attach(UtilsDriver.get_driver().get_screenshot_as_png(), 截图,allure.attachment_type.PNG)print(第一个断言的实际mes: actual_mes 第一个断言的预期结果 expected_msg)Logger.logger_in().info(第一个断言的实际mes: actual_mes 第一个断言的预期结果 expected_msg)print(第二个断言的实际mes: actual_mes2 第二个断言的预期结果: expected_msg2)Logger.logger_in().info(第二个断言的实际mes: actual_mes2 第二个断言的预期结果: expected_msg2)assert expected_msg in actual_mesprint(1断言成功)Logger.logger_in().info(1断言成功)assert expected_msg2 in actual_mes2print(2断言成功)Logger.logger_in().info(2断言成功)classmethoddef get_text_1(cls, xpath, expected_msg):有一个断言元素获取元素的文本来断言断言前必须延迟2s:param xpath:找到元素的路径只取到前面的标签然后根据标签中的元素名来判断属性值对不对:param expected_msg:期待定位的元素获取的值actual_mes UtilsDriver.get_driver().find_element(By.XPATH, xpath).text# actual_mes2 UtilsDriver.get_driver().find_element(By.XPATH, xpath2).textprint(生成截图)allure.attach(UtilsDriver.get_driver().get_screenshot_as_png(), 截图,allure.attachment_type.PNG)print(实际mes: actual_mes 预期结果 expected_msg)Logger.logger_in().info(实际mes: actual_mes 预期结果 expected_msg)assert expected_msg in actual_mesprint(1断言成功)Logger.logger_in().info(1断言成功)
用于接口自动化的工具类
# utils_api.py用于接口自动化的工具类设置驱动
驱动停止
获取弹窗信息
获取data数据import datetime
import requests
from log.log import Loggerclass RequestUtils:session requests.session()classmethod定义发送请求的方法参数为datadef send_request_data(cls, url, method, data, **kwargs):try:Logger.logger_in().info(-----------------{}接口开始执行-----------------.format(url))response RequestUtils.session.request(urlurl, methodmethod, datadata, **kwargs)Logger.logger_in().info(接口请求成功响应值为{}.format(response.text))return responseexcept Exception as e:Logger.logger_in().error(接口请求失败原因为{}.format(repr(e)))return eclassmethod定义发送请求的方法参数为jsondef send_request_json(cls, url, method, data, **kwargs):try:Logger.logger_in().info(-----------------{}接口开始执行-----------------.format(url))print(-----------------{}接口开始执行-----------------.format(url))response RequestUtils.session.request(urlurl, methodmethod, jsondata, **kwargs)Logger.logger_in().info(接口请求成功响应值为{}.format(response.text))Logger.logger_in().info(请求体为{}.format(response.request.body))print(请求体为{}.format(response.request.body))print(接口请求成功响应值为{}.format(response.text))return responseexcept Exception as e:Logger.logger_in().error(接口请求失败原因为{}.format(repr(e)))return eclassmethod定义发送请求的方法get请求参数为拼接方式参数为dictsdicts {a: 1, b: 2, c: 3}def send_request_splicing(cls, dicts, url): # 对应请求的入参及请求的函数Logger.logger_in().info(-----------------{}接口开始执行-----------------.format(url))print(-----------------{}接口开始执行-----------------.format(url))def parse_url(data: dict): # 将一个字典data转换成一个 URL 查询字符串query stringitem data.items()urls ?for i in item:(key, value) itemp_str key valueurls urls temp_str urls urls[:len(urls) - 1]print(请求体为{}.format(urls))Logger.logger_in().info(请求体为{}.format(urls))return urlsresponse RequestUtils.session.get(url parse_url(dicts))Logger.logger_in().info(接口请求成功响应值为{}.format(response.json()))print(接口请求成功响应值为{}.format(response.json()))print(response.json()[data][0][a]) #json串被解析为一个字典data对应的值是一个列表列表包含字典取data列表的第一个字典中a键对应的值return response
base类
用于ui自动化定位元素继承使用
# base_ui.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from utils_app import UtilsDriverclass BaseApp:def __init__(self):print(引用基类BaseApp)self.driver UtilsDriver.get_app_driver() # get_driver方法的引用就有隐形等待# self.driver.implicitly_wait(10)print(已获取app驱动)def get_element(self, location): # page页对象层的基类显式等待wait WebDriverWait(self.driver, 10, 1)element wait.until(lambda x: x.find_element(location))return elementdef get_elements(self, xpath)::param xpath: 表示元素定位的路径:return: 返回找到的元素wait WebDriverWait(self.driver, 10, 1)element wait.until(lambda x: x.find_element(By.XPATH, xpath))return elementdef get_element_id(self, ID)::param ID::return:wait WebDriverWait(self.driver, 15, 1)element wait.until(lambda x: x.find_element(By.ID, ID))return elementdef get_element_text(self, XPATH):wait WebDriverWait(self.driver, 15, 1)element wait.until(lambda x: x.find_element(By.XPATH, XPATH))return elementdef get_app_element(self, location):wait WebDriverWait(self.driver, 15, 1)element wait.until(lambda x: x.find_element(*location))return elementdef get_element_wait(self, location):# page页对象层的基类显式等待# 定义等待条件当条件发生时才执行后续代码。程序会轮询查看条件是否发生默认 10 秒# 如果条件成立则执行下一步否则继续等待直到超过设置的最长时间程序抛出异常。# 相较于隐性等待这个显性等待要明确等待条件和等待上限。比如隐性等待只要元素存在可找到就可以但显性等待我要明确条件是我的元素可见。而元素存在并不一定是元素可见。# 显性等待的场景操作引起了页面的变化而接下来要操作变化的元素的时候就需要使用显性等待wait WebDriverWait(self.driver, 10, 1)element_wait wait.until(lambda x: x.find_element(By.XPATH, location))return element_waitdef get_switch_to_frame(self, ida):self.driver.implicitly_wait(10)ele_frame self.driver.find_element(By.ID, ida)return self.driver.switch_to.frame(ele_frame)def get_element_1(self, xpath)::param xpath: 表示元素定位的路径:return: 返回找到的元素self.driver.implicitly_wait(10)return self.driver.find_element(By.XPATH, xpath)class BaseHandle:def input_text(self, element, text)::param element: 表示元素得对象:param text: 表示要输入的内容:return:element.clear()element.send_keys(text)
log类
用于记录所有执行目录
# log.py
import logging
import datetime
import osclass Logger:__logger Noneclassmethoddef logger_in(cls):if cls.__logger is None:# 创建日志器cls.__logger logging.getLogger(APIlogger)cls.__logger.setLevel(logging.DEBUG)# 判断是否存在handler不然每次都会新建一个handler导致日志重复输出if not cls.__logger.handlers:# 获取当前日期为文件名年份最后2位月份日期file_name str(datetime.datetime.now().strftime(%g %m %d)) .log# 创建处理器handler logging.FileHandler(os.path.join(, file_name))# handler logging.StreamHandler()# 创建格式器formatter logging.Formatter(%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s %(message)s,%Y-%m-%d %H:%M:%S)cls.__logger.addHandler(handler)handler.setFormatter(formatter)return cls.__loggerpage类
用于定位UI元素
形成业务用例的执行流程以登录为例
# page_ui.py
import timeimport allurefrom utils import UtilsDriver
from base.base_page import BasePage
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from log.log import Loggerclass PageLogin(BasePage): # 对象库层def __init__(self):super().__init__()def find_username(self):return self.get_element_1(//*/input[placeholder用户名])def find_password(self):return self.get_element_1(//*/input[placeholder密码])def find_login_bt(self):return self.get_element_1((//div[contains(text(),登录)]))class HandleLogin: # 操作层def __init__(self):self.driver UtilsDriver.get_driver()self.login_page PageLogin()self.keys Keys()self.ac ActionChains(self.driver)def click_and_input_find_username(self, username): # 点击用户名输入框self.login_page.find_username().click()for i in range(10):self.login_page.find_username().send_keys(Keys.BACK_SPACE) # 无法使用clear只能点10次BACK_SPACEself.login_page.find_username().send_keys(username)def click_and_input_find_password(self, password):self.login_page.find_password().click()for i in range(20):self.login_page.find_password().send_keys(Keys.BACK_SPACE) # 无法使用clear只能点10次BACK_SPACEself.login_page.find_password().send_keys(password)def click_login_bt(self):self.login_page.find_login_bt().click()class LoginProxy: # 业务层def __init__(self):self.handle_login HandleLogin()def login(self, username, password, xpath, expected_msg, xpath2, expected_msg2):time.sleep(1)self.handle_login.click_and_input_find_username(username)print(输入用户名)Logger.logger_in().info(输入用户名)self.handle_login.click_and_input_find_password(password)print(输入密码)Logger.logger_in().info(输入密码)self.handle_login.click_login_bt()print(点击登录)Logger.logger_in().info(点击登录)time.sleep(2)UtilsDriver.get_text(xpath, expected_msg, xpath2, expected_msg2)
用于封装请求断言的方法
会引用到utils_api.py中的方法
# page_api.py
class PageUrl:def __init__(self):self.session requests.session()def post(self, url, method, data, assert_msg):response RequestUtils().send_request_json(url, method, data)print(实际response response.text 预期响应 assert_msg)assert response.text in assert_msgreturn responsedef get(self, url, params, assert_msg): # 对应请求的断言的函数response RequestUtils().send_request_splicing(url, params)print(实际response str(response.json()) 预期响应 assert_msg)print(实际response.json()[‘data’][0][‘a’] response.json()[data][0][a])assert response.json()[data][0][a] assert_msgreturn response
test文件
接口自动化脚本的入口
# test_api.py
import os
import allure
import time
import sys
import pytest
import hashlib
import urllib.parse
# from page.page_login import LoginProxy
from page_url.page_url import PageUrl
from utils_app import DbMysql
from utils_url_def import RequestUtils # 有导入就有可能执行
BASE_DIR os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)class TestUrl:def setup_class(self): # 实例化page中的业务对象pytest中的测试类必须以“Test”开头且不能有init方法你试下把Login_test更改以“Test”开头的命名如果还不行的话文件名更改成“test_”开头或者以“_test”结尾因为pytest命名规范有3点文件名以“test_”开头或者以“_test”结尾测试类必须以“Test”开头且不能有init方法测试方法必须以test开头self.page_url PageUrl()self.absolute_xpath os.path.abspath(os.path.dirname(os.getcwd()))print(当前文件路径BASE_DIR) # 输入当前的文件路径pytest.mark.run(order1)# 执行顺序数字越小越先执行allure.title(用例名) # 命名用例名称方式1allure.severity(allure.severity_level.CRITICAL)# 关键用例pytest.mark.skipif(conditionTrue, reason暂停) # 跳过pytest.mark.parametrize(参数名1,参数名2,参数名3,参数名4,参数名5,参数名6,[(f{RequestUtils.参数A1}, f{RequestUtils.参数B1}, 1, , 断言1), (f{RequestUtils.参数A2}, f{RequestUtils.参数B2}, 1, , 断言2),]) # 参数化可用于执行多条用例 表参数为空def test_001_entrance(self, A, B, C, D, E, assert_msg):data {a: 1,b: 2}self.page_url.patrol_add_new_001(http:***, post, data, assert_msg) time.sleep(2)pytest.mark.run(order2) # 执行顺序数字越小越先执行allure.title(用例名2) # 命名用例名称方式1allure.severity(allure.severity_level.NORMAL)# 正常级别用例# pytest.mark.skipif(conditionTrue, reason暂停) # 暂停%s%RequestUtils.test_numberdef test_003_wechat_api(self):dicts {a: 1, b: 2}url http:***time.sleep(2)self.page_url.wechat_public_account_api(dicts, url, RequestUtils.test_number2)time.sleep(0.3)
UI自动化脚本的入口
# test_ui.py以模块名称作为测试套件的名称不要全部堆在一个测试套件里
[pytest]#标识当前配置文件是pytest的配置文件
addopts-s -v #标识pytest执行的参数
testpaths./scripts #匹配搜索的目录
python_filestest_*.py #匹配搜索的文件
python_classesTest* #匹配搜索的类
python_functionstest_* #匹配测试的方法执行结果生成测试报告步骤
1先生成json数据---pytest 测试文件在pytest.ini在addopts参数后--alluredir report2
2再把生成的json生成测试报告---allure generate report/ -o report/html --clean
allure generate report2/ -o report2/html --clean
注意目录的路径及名称import os
import allure
import time
import datetime
import sys
import pytest
from page.page_login import LoginProxy
from page_url.page_url import PageUrl
from page.page_inside_the_road_in_backstage import InsideTheRoadProxy
from page.page_finance_in_backstage import FinanceProxy
from utils import UtilsDriver
from utils_url_def import RequestUtils
BASE_DIR os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)class TestLogin:def setup_class(self): # 实例化page中的业务对象pytest中的测试类必须以“Test”开头且不能有init方法你试下把Login_test更改以“Test”开头的命名如果还不行的话文件名更改成“test_”开头或者以“_test”结尾因为pytest命名规范有3点文件名以“test_”开头或者以“_test”结尾测试类必须以“Test”开头且不能有init方法测试方法必须以test开头self.driver UtilsDriver.get_driver()self.driver.implicitly_wait(6)self.login_proxy LoginProxy()self.inside_the_road_proxy InsideTheRoadProxy()self.finance_proxy FinanceProxy()self.page_url PageUrl()self.current_time str(datetime.datetime.now().strftime(%Y-%m-%d))self.absolute_xpath os.path.abspath(os.path.dirname(os.getcwd()))print(当前文件路径BASE_DIR) # 输入当前的文件路径print(self.current_time)def teardown_class(self):time.sleep(3)UtilsDriver.get_driver().quit()allure.step(title正向登录)allure.title(用例名字)allure.severity(allure.severity_level.BLOCKER)# 冒烟测试用例pytest.mark.run(order4)# pytest.mark.skipif(conditionTrue, reason暂停) # 跳过该用例def test_001_login(self):self.login_proxy.login(UtilsDriver.user,UtilsDriver.pwd,UtilsDriver.login_actual_xpath,UtilsDriver.login_expected_mes,UtilsDriver.login_actual_xpath,UtilsDriver.login_expected_mes)#在UtilsDriver的方法中订单变量login_expected_mesmain文件
# main.py
import os
import pytest
if __name__ __main__:pytest.main() # 这里已经执行了pytest.ini成为临时文件了(pytest的配置文件自己根据需求配置)os.system(allure generate report/ -o report/html --clean) # 再次生成测试报告依赖库
# requirements.txt
allure_python_commons2.13.5
Appium_Python_Client2.11.0
Appium_Python_Client4.0.1
PyMySQL1.1.0
pytest8.1.1
Requests2.32.3
selenium4.23.1后言
以上为多端集成联动测试的完全代码也包含UI自动化和接口自动化的结合用的是pytest框架执行后自动生成allure测试报告。