from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import render
from request.app_ui_zhh.model.app_ui_model import AppUiTestCase, AppPackageInfo, AppDeviceInfo, AppReport
from request.app_ui_zhh.service.common import up_app_ui_case, add_device_info, add_package_info, delete_package_info, delete_device_info
from request.app_ui_zhh.app_ui_test_main import execute_test_process
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL
log = CLogger(LOG_LEVEL)
@login_required
def app_execute_ui_task(request):
# 执行测试用例的入口
platform = request.POST.get("platform")
project = request.POST.get("project")
product = request.POST.get("product")
device_name = request.POST.get("device_name")
app_name = request.POST.get("app_name")
if platform is None or platform == "" or project is None or project == "" or product is None or product == "" or device_name is None or device_name == "" or app_name is None or app_name == "":
log.error("获取数据错误,platform={}, project={}, product={}, device_name={}, app_name={}!".format(platform, project, product, device_name, app_name))
return JsonResponse({'result': 'false'})
if execute_test_process(platform=platform, project=project, product=product, device_name=device_name, app_name=app_name) is False:
return JsonResponse({'result': 'false'})
return JsonResponse({'result': 'ok'})
@login_required
def app_add_device_info(request):
device_sn = request.POST.get("device_sn")
device_name = request.POST.get("device_name")
platform_name = request.POST.get("platform_name")
platform_version = request.POST.get("platform_version")
version = request.POST.get("version")
server = request.POST.get("server")
remarks = request.POST.get("remarks")
if device_sn is None or device_sn == "" or device_name is None or device_name == "" or platform_name is None or platform_name == "" or server is None or server == "":
log.error("获取数据错误,device_sn={}, device_name={}, platform_name={}, server={}!".format(device_sn, device_name, platform_name, server))
return JsonResponse({'result': 'false'})
add_device_info(device_sn, device_name, platform_name, platform_version, version, server, remarks)
return JsonResponse({'result': 'ok'})
@login_required
def app_add_package_info(request):
app_name = request.POST.get("app_name")
platform = request.POST.get("platform")
package_name = request.POST.get("package_name")
activity_name = request.POST.get("activity_name")
remarks = request.POST.get("remarks")
if platform == "Android":
if app_name is None or app_name == "" or package_name is None or package_name == "" or activity_name is None or activity_name == "":
log.error("获取数据错误,app_name={}, platform={}, package_name={}, activity_name={}!".format(app_name, platform, package_name, activity_name))
return JsonResponse({'result': 'false'})
elif platform == "IOS":
if app_name is None or app_name == "" or package_name is None or package_name == "":
log.error("获取数据错误,app_name={}, platform={}, package_name={}!".format(app_name, platform, package_name))
return JsonResponse({'result': 'false'})
else:
return JsonResponse({'result': 'false'})
add_package_info(app_name, platform, package_name, activity_name, remarks)
return JsonResponse({'result': 'ok'})
@login_required
def app_delete_package_info(request):
id = request.POST.get("id")
if id is None or id == "":
log.error("获取数据错误,id={}!".format(id))
return JsonResponse({'result': 'false'})
delete_package_info(id)
return JsonResponse({'result': 'ok'})
@login_required
def app_delete_device_info(request):
id = request.POST.get("id")
if id is None or id == "":
log.error("获取数据错误,id={}!".format(id))
return JsonResponse({'result': 'false'})
delete_device_info(id)
return JsonResponse({'result': 'ok'})
@login_required
def app_get_ui_case_html(request):
# 跳转到app测试用例页面
platform = request.GET.get("platform")
return render(request, "app_ui_case.html", {"platform": platform})
@login_required
def app_get_ui_case(request):
# 查询用例展示在页面的接口
platform = request.POST.get("platform")
project = request.POST.get("project")
product = request.POST.get("product")
if platform is None or platform == "" or project is None or project == "" or product is None or product == "":
return JsonResponse({"tasks": [], "platform": platform, "project": project, "product": product})
else:
tasks = list(AppUiTestCase.objects.all().filter(platform=platform, project=project, product=product).values())
return JsonResponse({"tasks": tasks, "platform": platform, "project": project, "product": product})
@login_required
def app_up_ui_case(request):
# 上传Excel测试用例到写入数据库
try:
if request.method == "POST":
file_contents = request.FILES.get('fileContent')
if up_app_ui_case(file_contents) is False:
raise ImportError("提交失败")
return JsonResponse({'result': 'ok'})
except Exception:
raise ImportError("提交失败")
@login_required
def app_get_device_type(request):
book_list = AppUiTestCase.objects.values("platform").distinct()
results = []
for temp in book_list:
results.append(temp["platform"])
data = {'data': results}
return JsonResponse(data)
@login_required
def app_get_up_ui(request):
return render(request, "app_ui_case_up.html")
@login_required
def app_get_app_info_html(request):
return render(request, "app_package_info.html")
@login_required
def app_get_app_info(request):
# 查询app信息展示在页面
if request.method == "GET":
info_data = list(AppPackageInfo.objects.all().values())
elif request.method == "POST":
platform = request.POST.get("platform")
info_data = list(AppPackageInfo.objects.all().filter(platform=platform).values())
else:
return JsonResponse({"info_data": []})
return JsonResponse({"info_data": info_data})
@login_required
def app_get_device_info_html(request):
return render(request, "app_device_info.html")
@login_required
def app_get_device_info(request):
# 查询设备信息展示在页面
if request.method == "GET":
info_data = list(AppDeviceInfo.objects.all().values())
elif request.method == "POST":
platform = request.POST.get("platform")
info_data = list(AppDeviceInfo.objects.all().filter(platform_name=platform).values())
else:
return JsonResponse({"info_data": []})
return JsonResponse({"info_data": info_data})
@login_required
def app_delete_ui_case(request):
platform = request.POST.get("platform")
project = request.POST.get("project")
product = request.POST.get("product")
if platform is None or platform == "" or project is None or project == "" or product is None or product == "":
return JsonResponse({'result': 'false'})
try:
AppUiTestCase.objects.filter(platform=platform, project=project, product=product).delete()
except Exception:
raise ImportError("删除失败")
return JsonResponse({'result': 'ok'})
@login_required
def app_get_report_html(request):
return render(request, "app_report.html")
@login_required
def app_get_report_data(request):
# 查询app信息展示在页面
platform_name = request.GET.get("platform_name")
if platform_name is not None and platform_name != "":
report_data = list(AppReport.objects.all().filter(platform_name=platform_name).order_by("-id").values())
else:
report_data = list(AppReport.objects.all().order_by("-id").values())
return JsonResponse({"report_data": report_data})
"""
Excel的读写方法
"""
import xlrd
import xlwt
from xlutils.copy import copy
import os
def write_excel(file_name, sheet_name, row, col, data):
"""
写一个单元格数据。
:param file_name: 需要写入的文件名称,如不存在会新建一个,字符串
:param sheet_name: 需要写入的sheet页名称,如不存在会新建一个,字符串
:param row: 需要写入的行,unsignedint
:param col: 需要写入的列,unsignedint
:param data: 需要写入的数据,字符串
:return:
"""
if os.access(file_name, os.R_OK):
old_wb = xlrd.open_workbook(file_name) # 打开已存在的表
sheets = old_wb.sheet_names()
new_wb = copy(old_wb) # 复制
if sheet_name not in sheets:
new_ws = new_wb.add_sheet(sheet_name, cell_overwrite_ok=True)
else:
new_ws = new_wb.get_sheet(sheets.index(sheet_name))
new_ws.write(row, col, data) # 写入单元格数据
new_wb.save(file_name) # 保存
else:
f, sheet = add_excel_sheet(sheet_name)
my_style = set_style('Times New Roman', 220)
sheet.write(row, col, data, my_style)
excel_close(f, file_name)
def set_style(name, height, bold=False):
"""
设置表格字体样式。
:param name: 字体名称
:param height: 字体大小
:param bold: 是否加粗
:return: 返回字体格式类。
"""
style = xlwt.XFStyle()
font = xlwt.Font()
font.name = name
font.bold = bold
font.color_index = 4
font.height = height
style.font = font
return style
def add_excel_sheet(sheet_name):
"""
在Excel中创建新sheet。
:param sheet_name: sheet名称。
:return: 返回Excel和sheet的对象
"""
f = xlwt.Workbook()
sheet = f.add_sheet(sheet_name, cell_overwrite_ok=True)
return f, sheet
def write_new_excel(sheet, row_num, row):
"""
写一个sheet页面,如果sheet页面中有内容,会将原有内容覆盖。
:param sheet: sheet对象
:param row_num: 从第几行开始写,第一行为0
:param row: 写的内容,是一个数组,按顺序写入单元格,[(),()...]
:return:
"""
i = 0
j = 0
my_style = set_style('Times New Roman', 220)
while i < len(row):
while j < len(row[i]):
sheet.write(row_num + i, j, row[i][j], my_style)
j = j + 1
i = i + 1
def excel_close(f, file_name):
"""
保存并关闭Excel。
:param f: Excel的对象
:param file_name: 文件名称。
:return: 无
"""
f.save(file_name)
def read_excel(file_name, sheet_name, row=-1, col=-1):
"""
读取Excel文件内容。
:param file_name: 需要读取的Excel文件名称,字符串
:param sheet_name: 需要读取的Excel中的sheet名称,字符串
:param row: 需要读取的行,row与col都为-1表示读取整个sheet,row为-1且col为正数表示读取整列数据,row为正数且col为-1表示读取整行数据
:param col: 参考row
:return: 返回读取的内容,列表
"""
if os.access(file_name, os.R_OK):
wb = xlrd.open_workbook(filename=file_name)
else:
print('Exception occurred: ' + "文件不存在!")
return False
try:
sheet = wb.sheet_by_name(sheet_name)
except xlrd.XLRDError:
print('Exception occurred: ' + "sheet_name不存在!")
return False
rows = sheet.nrows
cols = sheet.ncols
data_values = []
if rows < 1 or cols < 1:
print('Exception occurred: ' + "sheet中无数据!")
return False
if row >= rows or col >= cols:
print('Exception occurred: ' + "获取的内容超过sheet页中的内容行数或列数")
return False
if row == -1 and col == -1: # 获取整个sheet页的内容
for i in range(rows):
data_values.append(sheet.row_values(i))
elif row == -1 and col > -1: # 获取sheet页的某列数据
data_values = sheet.col_values(col)
elif row > -1 and col == -1: # 获取sheet页的某行数据
data_values = sheet.row_values(row)
elif row > -1 and col > -1: # 获取sheet页的某个单元格数据
data_values = sheet.cell_value(row, col)
else:
return False
return data_values
def get_excel_sheet(file_name):
"""
读取Excel中的所有sheet名称。
:param file_name: 需要读取的Excel文件名称,字符串
:return: 返回获取的sheet名称
"""
wb = xlrd.open_workbook(filename=file_name)
return wb.sheet_names()
def read_excel_from_contents(file_contents=None):
"""
读取Excel。
"""
data = xlrd.open_workbook(filename=None, file_contents=file_contents)
return data
"""
定义全局变量,使用方法:
引用:import global_env
设置:global_env.set_global_value('env_data', 0)
获取:global_env.get_global_value('env_data')
删除变量:global_env.get_global_value('del_global_env')
如果变量是字典,删除字典的某个键值对:global_env.get_global_value('del_global_env', "key")
"""
_global_dict = {}
def global_init():
global _global_dict
def set_global_value(key, value):
_global_dict[key] = value
return True
def get_global_value(key):
if key in _global_dict:
return _global_dict[key]
else:
return None
def del_global_env(key, value=None):
if isinstance(key, dict) and value is not None:
if value in _global_dict[key]:
del _global_dict[key][value]
else:
del _global_dict[key]
return True
import cv2
from shutil import copy
import os
def img_resize(image, ratio):
image_path = os.path.join(os.path.dirname(image), "image_origin")
copy(image, image_path)
img = cv2.imread(image)
height, width = img.shape[0], img.shape[1]
width_new = int(width * ratio)
height_new = int(height * ratio)
img_new = cv2.resize(img, (width_new, height_new))
cv2.imwrite(image, img_new)
import os
import sys
import time
import logging
from logging.handlers import TimedRotatingFileHandler
from request.app_ui_zhh.common.singleton import Singleton
import datetime
from request.app_ui_zhh.config.config import LOG_CONSOLE
@Singleton
class CLogger:
def __init__(self, set_level="Info",
name=os.path.split(os.path.splitext(sys.argv[0])[0])[-1],
log_name=time.strftime("%Y-%m-%d.log", time.localtime()),
log_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "log"),
use_console=True):
"""
:param set_level: 日志级别["NOTSET"|"DEBUG"|"INFO"|"WARNING"|"ERROR"|"CRITICAL"],默认为INFO
:param name: 日志中打印的name,默认为运行程序的name
:param log_name: 日志文件的名字,默认为当前时间(年-月-日 时:分.log)
:param log_path: 日志文件夹的路径,默认为logger.py同级目录中的log文件夹
:param use_console: 是否在控制台打印,默认为True
"""
if not set_level:
set_level = self._exec_type()
self.__logger = logging.getLogger(name)
self.setLevel(getattr(logging, set_level.upper()) if hasattr(logging, set_level.upper()) else logging.INFO) # 设置日志级别
if not os.path.exists(log_path):
os.makedirs(log_path)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") # 设置日志格式
handler_list = list()
logfile_name = os.path.join(log_path, "app_ui_test.log") # 设置日志名称
log_handler = logging.handlers.TimedRotatingFileHandler(logfile_name, when='midnight', interval=1, backupCount=0, encoding="UTF-8", # backupCount为0表示不限制文件个数
atTime=datetime.datetime(2022, 1, 1, hour=0, minute=0, second=0, microsecond=0))
handler_list.append(log_handler) # 日志根据时间分文件,不限制文件个数,根据atTime的0点整进行分文件,atTime中的年月日是忽略掉的,只需设置时分秒毫秒
if use_console and LOG_CONSOLE: # 如果是使用终端输出,在文件输出的同时会输出到控制台
handler_list.append(logging.StreamHandler())
for handler in handler_list:
handler.setFormatter(formatter)
self.addHandler(handler)
@staticmethod
def _exec_type():
return "DEBUG" if os.environ.get("IPYTHONENABLE") else "INFO"
def __getattr__(self, item):
return getattr(self.logger, item)
@property
def logger(self):
return self.__logger
@logger.setter
def logger(self, func):
self.__logger = func
class Singleton:
"""
单例装饰器。
"""
__cls = dict()
def __init__(self, cls):
self.__key = cls
def __call__(self, *args, **kwargs):
if self.__key not in self.cls:
self[self.__key] = self.__key(*args, **kwargs)
return self[self.__key]
def __setitem__(self, key, value):
self.cls[key] = value
def __getitem__(self, item):
return self.cls[item]
@property
def cls(self):
return self.__cls
@cls.setter
def cls(self, cls):
self.__cls = cls
"""
本模块是Yaml文件的读写方法。
"""
import yaml
class CYaml(object):
def __init__(self, yaml_path):
"""
:param yaml_path: Yaml文件的路径,字符串
"""
self.yaml_path = yaml_path
def read_yaml(self):
"""
读Yaml文件的数据。
:return: 返回读取的数据,字典
"""
with open(self.yaml_path, 'r', encoding='utf-8') as fp_yaml:
yaml_data_str = fp_yaml.read()
yaml_data_dict = yaml.safe_load(yaml_data_str)
return yaml_data_dict
def write_yaml(self, yaml_write_data):
"""
写Yaml文件。
:param yaml_write_data: 需要写入的数据,字典
:return: 无
"""
with open(self.yaml_path, 'w') as fp_yaml:
yaml.dump(yaml_write_data, fp_yaml, default_flow_style=False)
from request.app_ui_zhh.common import yml_rw
import os
def get_sys_config_from_yml():
"""
获取yml配置。
:return: 配置信息,字典
"""
pro_base_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
config_yml_path = os.path.join(pro_base_dir, "config/config.yml")
config_obj = yml_rw.CYaml(config_yml_path)
config_data = config_obj.read_yaml()
log_console = True
log_level = "info"
report_type = 0
data_type = 1
report_save_path = ""
airtest_threshold = 0.7
airtest_find_timeout = 20
if"SYSTEM" in config_data:
if "LOG_CONSOLE" in config_data["SYSTEM"]:
log_console = config_data["SYSTEM"]["LOG_CONSOLE"]
if "LOG_LEVEL" in config_data["SYSTEM"]:
log_level = config_data["SYSTEM"]["LOG_LEVEL"]
if "REPORT_TYPE" in config_data["SYSTEM"]:
report_type = config_data["SYSTEM"]["REPORT_TYPE"]
if "DATA_TYPE" in config_data["SYSTEM"]:
data_type = config_data["SYSTEM"]["DATA_TYPE"]
if "REPORT_SAVE_PATH" in config_data["SYSTEM"]:
report_save_path = config_data["SYSTEM"]["REPORT_SAVE_PATH"]
if "AIRTEST_THRESHOLD" in config_data["SYSTEM"]:
airtest_threshold = config_data["SYSTEM"]["AIRTEST_THRESHOLD"]
if "AIRTEST_FIND_TIMEOUT" in config_data["SYSTEM"]:
airtest_find_timeout = config_data["SYSTEM"]["AIRTEST_FIND_TIMEOUT"]
return log_console, log_level, report_type, data_type, report_save_path, airtest_threshold, airtest_find_timeout
def get_test_config_from_yml():
"""
获取yml配置。
:return: 配置信息,字典
"""
pro_base_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
config_yml_path = os.path.join(pro_base_dir, "config/config.yml")
config_obj = yml_rw.CYaml(config_yml_path)
config_data = config_obj.read_yaml()
image_ratio = None
if"TEST_ENV" in config_data:
if "IMAGE_RATIO" in config_data["TEST_ENV"]:
image_ratio = config_data["TEST_ENV"]["IMAGE_RATIO"]
return image_ratio
LOG_CONSOLE, LOG_LEVEL, REPORT_TYPE, DATA_TYPE, REPORT_SAVE_PATH, AIRTEST_THRESHOLD, AIRTEST_FIND_TIMEOUT = get_sys_config_from_yml()
IMAGE_RATIO = get_test_config_from_yml()
if __name__ == "__main__":
get_sys_config_from_yml()
SYSTEM:
LOG_CONSOLE: True # 是否在控制台打印日志信息
LOG_LEVEL: "info" # 日志等级,包括warning/info/debug
REPORT_TYPE: 1 # 0-报告类型为allure,1-报告类型为python-html
DATA_TYPE: 0 # 0-数据源为数据库,1-数据源为Excel
REPORT_SAVE_PATH: "\\\\10.99.10.251/sf_local/data/测试报告/消费级BP/3bp go app/APP_UI_Test_Report/" # 报告存放的路径
AIRTEST_THRESHOLD: 0.7 #[0,1] 图像识别的阈值,当识别结果的可信度大于阈值时,才认为找到了匹配结果
AIRTEST_FIND_TIMEOUT: 20 # 查找元素的超时时间,单位秒
TEST_ENV:
IMAGE_RATIO: 0.5 # 截图缩放比率,小于1,缩放后减少存储空间
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL, AIRTEST_THRESHOLD, AIRTEST_FIND_TIMEOUT
from airtest.core.api import *
from airtest.core.settings import Settings as ST
import os
class AirtestBaseAction(object):
def __init__(self):
self.log = CLogger(LOG_LEVEL)
self.platform_name = None
pro_base_dir = os.path.dirname(os.path.dirname(__file__))
self.airtest_img_path = os.path.join(pro_base_dir, "data/airtest_img/")
def device_connect(self, device_app_info):
ST.THRESHOLD = AIRTEST_THRESHOLD
ST.FIND_TIMEOUT = AIRTEST_FIND_TIMEOUT
ST.SAVE_IMAGE = False
pro_base_dir = os.path.dirname(os.path.dirname(__file__))
log_path = os.path.join(pro_base_dir, "log/airtest_log")
if "platformName" in device_app_info:
self.platform_name = device_app_info["platformName"]
else:
self.log.error("平台类型获取失败!")
return False
if "deviceName" in device_app_info:
if self.platform_name == "Android":
auto_setup(__file__, logdir=log_path, devices=["android://127.0.0.1:5037/" + device_app_info["deviceName"]])
elif self.platform_name == "IOS":
auto_setup(__file__, logdir=log_path, devices=["ios:http+usbmux://" + device_app_info["deviceName"]])
else:
self.log.error("平台类型错误,当前只支持Android和IOS!")
return False
else:
self.log.error("设备名称获取失败!")
return False
return True
def device_disconnect(self):
device().disconnect()
self.log.info("停止Airtest相关服务!")
def start_app(self, package_name, activity=None):
start_app(package_name, activity)
self.log.error("启动APP:{},{}".format(package_name, activity))
return True
def stop_app(self, package_name):
stop_app(package_name)
self.log.error("停止APP:{}".format(package_name))
return True
def sleep_wait(self, value):
self.log.info('睡眠{}秒'.format(int(value)))
time.sleep(value)
def touch_img_one(self, image_name, duration=0.5, threshold=AIRTEST_THRESHOLD):
try:
touch(Template(self.airtest_img_path + image_name, target_pos=5, threshold=threshold), times=1, duration=duration)
except Exception as e:
self.log.error("点击图片实例失败:{}", e)
return False
else:
return True
def is_exists_img(self, image_name):
try:
result = exists(Template(self.airtest_img_path + image_name))
except Exception as e:
self.log.error("查找图片实例失败:{}", e)
return False
else:
if result is False:
return False
return True
import time
from selenium.webdriver.support.wait import WebDriverWait
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL
from request.app_ui_zhh.drivers.check_devices import is_devices_link, is_app_installed
from appium import webdriver
from selenium.common.exceptions import WebDriverException
from urllib3.exceptions import MaxRetryError
from request.app_ui_zhh.drivers.config import AndroidKeyCode
class AppiumBaseAction(object):
def __init__(self):
self.log = CLogger(LOG_LEVEL)
self.driver = None
self.app_package_name = None
self.platform_name = None
def device_driver(self, device_app_info):
if "appPackage" in device_app_info:
self.app_package_name = device_app_info["appPackage"]
else:
self.log.error("APP包名/bundleId获取失败!")
return False
if "platformName" in device_app_info:
self.platform_name = device_app_info["platformName"]
else:
self.log.error("平台类型获取失败!")
return False
if self.platform_name == "Android":
if "server" in device_app_info:
server_ip = device_app_info["server"].split("//")[1].split(":")[0]
if server_ip == "localhost" or server_ip == "127.0.0.1":
if is_devices_link(device_type=self.platform_name) is False:
self.log.error("未找到设备!")
return False
if is_app_installed(device_name=device_app_info["deviceName"], app_package_name=self.app_package_name, device_type=self.platform_name) is False:
self.log.error("APP未安装!")
return False
else:
self.log.error("设备连接地址获取失败!")
return False
try:
server = device_app_info["server"]
if device_app_info["platformName"] == "Android":
desired_capabilities = {
'platformName': device_app_info["platformName"],
'deviceName': device_app_info["deviceName"],
'appPackage': device_app_info["appPackage"],
'appActivity': device_app_info["appActivity"],
'noReset': device_app_info["noReset"],
'fullReset': device_app_info["fullReset"]
}
elif device_app_info["platformName"] == "IOS":
wda_ip = server.split("//")[1].split(":")[0]
desired_capabilities = {
"platformName": device_app_info["platformName"],
"appium:deviceName": device_app_info["device_sn"],
"appium:platformVersion": device_app_info["platform_version"],
"appium:udid": device_app_info["deviceName"],
"appium:bundleId": device_app_info["appPackage"],
"appium:webDriverAgentUrl": "http://" + wda_ip + ":8100",
"appium:automationName": device_app_info["automationName"],
"appium:usePrebuiltWDA": device_app_info["usePrebuiltWDA"],
"appium:useXctestrunFile": device_app_info["useXctestrunFile"],
"appium:skipLogCapture": device_app_info["skipLogCapture"]
}
else:
self.log.error("平台类型错误,当前只支持Android和IOS!")
return False
self.driver = webdriver.Remote(server, desired_capabilities)
except MaxRetryError:
self.log.error("连接设备失败,请检查appium server是否启动!")
return False
except WebDriverException:
self.log.error("连接设备失败,请检查连接设备的参数,比如APP包名、启动名等")
return False
else:
return True
def app_close(self):
try:
self.driver.terminate_app(self.app_package_name)
except Exception as e:
self.log.error("APP关闭失败:{}", e)
return False
else:
return True
def find_element(self, by, value, timeout=20.0, time_p=0.5):
try:
ele = WebDriverWait(self.driver, timeout, time_p).until(lambda x: x.find_element(by, value))
self.log.info("找到元素:{},{}".format(by, value))
except TimeoutError:
self.log.error("没有找到元素:{},{},或者寻找超时".format(by, value))
return False
else:
return ele
def find_elements(self, by, value, timeout=20.0, time_p=0.5):
try:
ele = WebDriverWait(self.driver, timeout, time_p).until(lambda x: x.find_elements(by, value))
self.log.info("找到元素:{},{}".format(by, value))
except TimeoutError:
self.log.error("没有找到元素:{},{},或者寻找超时".format(by, value))
return False
else:
return ele
def is_element_present(self, by, value):
"""判断元素是否存在"""
try:
self.driver.find_element(by=by, value=value)
except Exception as e:
self.log.error("没有找到元素:{},{},或者寻找超时,错误:{}".format(by, value, e))
return False
else:
return True
def click(self, by, value):
try:
self.find_element(by, value).click()
self.log.info("click元素")
except Exception as e:
self.log.error("click元素失败:{},{},错误:{}".format(by, value, e))
return False
else:
return True
def click_s(self, by, value, index):
try:
self.find_elements(by, value)[index].click()
self.log.info("click元素{}".format(index))
except Exception as e:
self.log.error("click元素失败:{},{},错误:{}".format(by, value, e))
return False
else:
return True
def sleep_wait(self, value):
self.log.info('睡眠{}秒'.format(int(value)))
time.sleep(int(value))
def input_text(self, by, value, text):
try:
self.find_element(by, value).send_keys(text)
self.log.info("输入文本:{}".format(text))
except Exception as e:
self.log.error("元素文本输入失败:{},{},错误:{}".format(by, value, e))
return False
else:
return True
def click_back_key(self):
try:
self.driver.press_keycode(AndroidKeyCode["KEYCODE_BACK"])
self.log.info("click返回键")
except Exception as e:
self.log.error("返回失败,错误:{}".format(e))
return False
else:
return True
def device_quit(self):
try:
self.driver.quit()
self.log.info("关闭设备连接")
except Exception as e:
self.log.error("关闭设备连接失败,错误:{}".format(e))
return False
else:
return True
def get_screenshot_png(self, file_name):
try:
self.driver.get_screenshot_as_file(file_name)
self.log.info("获取设备截图")
except Exception as e:
self.log.error("获取设备截图失败,错误:{}".format(e))
return False
else:
return True
def wait_ele_num(self, by, value, num, time_out):
time_start = int(time.time())
while True:
now = int(time.time())
if now - time_start > int(time_out):
break
ele = self.find_elements(self, by, value, time_out)
if isinstance(ele, list) is True:
if len(ele) == int(num):
self.log.error('等待元素数量正确{},数量{}'.format((by, value), num))
return True
self.log.error('等待元素数量错误{},数量{}'.format((by, value), num))
return False
def swipe(self, start_x, start_y, end_x, end_y, duration=1000):
try:
size = self.driver.get_window_size()
if start_x == 0 or start_y == 0 or end_x == 0 or end_y == 0:
self.log.info('不能选取边界值{},{},{},{}'.format(start_x, start_y, end_x, end_y))
if start_x == size["width"] or start_y == size["height"] or end_x == size["width"] or end_y == size["height"]:
self.log.info('不能选取边界值{},{},{},{}'.format(start_x, start_y, end_x, end_y))
self.driver.swipe(start_x, start_y, end_x, end_y, duration)
except Exception as e:
self.log.error('滑动屏幕出现错误,错误{}'.format(e))
return False
else:
return True
def swipe_left_to_right(self):
try:
size = self.driver.get_window_size()
start_x = int(size["width"] / 10)
start_y = int(size["height"] / 2)
end_x = int(size["width"] * 3 / 4)
end_y = int(size["height"] / 2)
self.driver.swipe(start_x, start_y, end_x, end_y, 500)
except Exception as e:
self.log.error('滑动屏幕出现错误,错误{}'.format(e))
return False
else:
return True
def swipe_down_to_up(self):
try:
size = self.driver.get_window_size()
start_x = int(size["width"] / 2)
start_y = int(size["height"] * 3 / 4)
end_x = int(size["width"] / 2)
end_y = int(size["height"] / 10)
self.driver.swipe(start_x, start_y, end_x, end_y, 500)
except Exception as e:
self.log.error('滑动屏幕出现错误,错误{}'.format(e))
return False
else:
return True
def swipe_screen_back(self):
return self.swipe_left_to_right()
def swipe_screen_ele(self, by, value, direction, px=None):
"""
在元素范围内滑动
:param by:
:param value:
:param direction: 1-从左至右,2-从右至左,3-从下到上,4-从上到下
:param px: 滑动的点数
:return:
"""
try:
ele = self.find_element(by, value)
ele_location = ele.location
ele_size = ele.size
if direction == 1:
start_x = int(ele_location["x"] + (ele_size["width"] / 10))
start_y = int(ele_location["y"] + (ele_size["height"] / 2))
end_x = int(ele_location["x"] + (ele_size["width"] * 3 / 4))
end_y = int(ele_location["y"] + (ele_size["height"] / 2))
if px is not None and (start_x + px) < (ele_location["x"] + ele_size["width"]):
end_x = start_x + px
self.driver.swipe(start_x, start_y, end_x, end_y, 500)
elif direction == 2:
start_x = int(ele_location["x"] + (ele_size["width"] * 3 / 4))
start_y = int(ele_location["y"] + (ele_size["height"] / 2))
end_x = int(ele_location["x"] + (ele_size["width"] / 10))
end_y = int(ele_location["y"] + (ele_size["height"] / 2))
if px is not None and (start_x - px) > ele_location["x"]:
end_x = start_x - px
self.driver.swipe(start_x, start_y, end_x, end_y, 500)
elif direction == 3:
start_x = int(ele_location["x"] + (ele_size["width"] / 2))
start_y = int(ele_location["y"] + (ele_size["height"] * 3 / 4))
end_x = int(ele_location["x"] + (ele_size["width"] / 2))
end_y = int(ele_location["y"] + (ele_size["height"] / 10))
if px is not None and (start_y - px) > ele_location["y"]:
end_y = start_y - px
self.driver.swipe(start_x, start_y, end_x, end_y, 500)
elif direction == 4:
start_x = int(ele_location["x"] + (ele_size["width"] / 2))
start_y = int(ele_location["y"] + (ele_size["height"] / 10))
end_x = int(ele_location["x"] + (ele_size["width"] / 2))
end_y = int(ele_location["y"] + (ele_size["height"] * 3 / 4))
if px is not None and (start_y + px) < (ele_location["y"] + ele_size["height"]):
end_y = start_y + px
self.driver.swipe(start_x, start_y, end_x, end_y, 500)
else:
self.log.info('direction值错误,错误{}'.format(direction))
return False
except Exception as e:
self.log.error('元素范围内滑动出现错误,错误{}'.format(e))
return False
else:
return True
def get_ele_attribute(self, by, value, attribute):
""" 获取元素的属性 """
try:
ele = self.find_element(by, value)
attribute_value = ele.get_attribute(attribute)
except Exception as e:
self.log.error('获取元素属性出现错误,错误{}'.format(e))
return False
else:
return attribute_value
def swipe_ele_to_expect_attribute(self, by, value, attribute, attribute_value, direction, px=None, time_out=30):
time_start = int(time.time())
while True:
now = int(time.time())
if now - time_start > int(time_out):
break
ele = self.find_element(self, by, value, time_out)
if ele is False:
break
else:
while ele.get_attribute(attribute) == attribute_value:
return True
self.swipe_screen_ele(by, value, direction, px)
self.log.error('滑动元素等待元素属性符合预期出现错误{}'.format(by, value))
return False
def swipe_to_page_bottom(self, time_out=20):
time_start = int(time.time())
while True:
now = int(time.time())
if now - time_start > int(time_out):
break
before_swipe = self.driver.page_resource
if self.swipe_down_to_up() is False:
return False
else:
after_swipe = self.driver.page_resource
if before_swipe == after_swipe:
return True
self.log.error('滑动到页面底部出现错误')
return False
def is_expect_ele_attribute(self, by, value, attribute, expect):
""" 判断元素的属性 """
attribute_value = self.get_ele_attribute(by, value, attribute)
if attribute_value == expect:
return True
else:
return False
import os
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL
log = CLogger(LOG_LEVEL)
def is_devices_link(device_type):
"""
检查是否有设备连接PC,有则返回True
:param device_type: 设备类型,Android-安卓,IOS-ios
:return:
"""
devices_list_finally = []
if device_type == "Android":
devices_list_start = []
devices_cmd = os.popen('adb devices').readlines()
devices_list_start_count = len(devices_cmd)
devices_list_start_count = devices_list_start_count - 2
if devices_list_start_count >= 1:
log.info('find devices linked')
for devices_num in range(devices_list_start_count):
devices_list_start.append(devices_cmd[devices_num + 1])
device_list_pers = devices_list_start[devices_num].index('\t')
devices_list_finally.append(devices_list_start[devices_num][:device_list_pers])
log.info('devices list :' + '%d ' % (devices_num + 1) + '%s' % devices_list_finally[devices_num])
return devices_list_finally
else:
log.error("无法连接到手机,试试重新插拔手机")
return False
else:
log.warning("检测安卓外的其它设备类型是否在线暂未实现")
return False
def is_app_installed(device_name, app_package_name, device_type):
"""
判断手机是否安装了待测试APP,安装则返回True
:param app_package_name: APP包名称,字符串
:param device_type: 设备类型,Android-安卓,IOS-ios
:param device_name: 设备名称
:return:
"""
if device_type == "Android":
app_package_name = "package:" + app_package_name + "\n"
all_packages = list(os.popen("adb -s " + device_name + " shell pm list package"))
if app_package_name in all_packages:
log.info("设备已安装指定APP:" + app_package_name)
return True
else:
log.warning("未安装指定APP:" + app_package_name)
return False
else:
log.warning("安卓外的其它设备类型检测安装包暂未实现")
return False
if __name__ == '__main__':
is_devices_link("Android")
is_app_installed("ACMFUT2519008078", "com.heygears.blueprintgo", "Android")
AndroidKeyCode = {
"KEYCODE_HOME": 33,
"KEYCODE_BACK": 4,
"KEYCODE_MENU": 82
}
import time
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL
import os
log = CLogger(LOG_LEVEL)
def tidevice_wda_server_start(udid, bundle_id="com.heygears.WebDriverAgentRunner.xctrunner", port="8100"):
val = os.popen("tasklist | findstr /s /i tidevice.exe").read()
if "tidevice.exe" in val:
os.popen("taskkill /im tidevice.exe /t /f").read()
time.sleep(5)
os.popen("tidevice -u " + udid + " wdaproxy -B " + bundle_id + " --port " + port)
time.sleep(15)
val = os.popen("tasklist | findstr /s /i tidevice.exe").read()
if "tidevice.exe" in val:
log.info("Tidevice启动成功!")
return True
log.error("Tidevice启动失败!")
return False
def tidevice_wda_server_stop():
val = os.popen("tasklist | findstr /s /i tidevice.exe").read()
if "tidevice.exe" in val:
os.popen("taskkill /im tidevice.exe /t /f").read()
time.sleep(5)
log.info("Tidevice进程关闭成功!")
return True
from django.db import models
class AppDeviceInfo(models.Model):
id = models.AutoField(primary_key=True, max_length=200, unique=True)
device_sn = models.CharField('设备编号', max_length=100)
device_name = models.CharField('设备名称', max_length=100)
platform_name = models.CharField('平台类型', max_length=100)
platform_version = models.CharField('平台版本', max_length=100)
version = models.CharField('系统版本', max_length=100)
server = models.CharField('连接到的服务链接', max_length=100)
create_time = models.DateTimeField(auto_now=True)
remarks = models.CharField('备注', max_length=100, null=True)
def __str__(self):
return self.device_name
class Meta:
managed = False
db_table = "app_device_info"
class AppPackageInfo(models.Model):
id = models.AutoField(primary_key=True, max_length=200, unique=True)
app_name = models.CharField('app名称', max_length=100)
platform = models.CharField('平台类型', max_length=100)
package_name = models.CharField('包名称', max_length=100)
activity_name = models.CharField('启动activity名称', max_length=100)
create_time = models.DateTimeField(auto_now=True)
remarks = models.CharField('备注', max_length=100, null=True)
def __str__(self):
return self.app_name
class Meta:
managed = False
db_table = "app_package_info"
class AppUiTestCase(models.Model):
id = models.AutoField(primary_key=True, max_length=200, unique=True)
platform = models.CharField('平台类型', max_length=100)
project = models.CharField('项目', max_length=100)
product = models.CharField('产品', max_length=100)
module = models.CharField('模块', max_length=100)
level = models.CharField('用例等级', max_length=20)
lib = models.CharField('库', max_length=100)
method = models.CharField('方法', max_length=100)
parameter = models.CharField('参数', max_length=200)
expect = models.CharField('参数', max_length=200)
label = models.CharField('参数', max_length=20)
create_time = models.DateTimeField(auto_now=True)
remarks = models.CharField('备注', max_length=100, null=True)
def __str__(self):
return self.method
class Meta:
managed = False
db_table = "app_ui_test_case"
class AppReport(models.Model):
id = models.AutoField(primary_key=True, max_length=200, unique=True)
platform_name = models.CharField('平台类型', max_length=100)
product_name = models.CharField('产品', max_length=100)
result = models.CharField('结果', max_length=100)
path = models.CharField('报告路径', max_length=100)
report_type = models.CharField('报告类型', max_length=100)
create_time = models.DateTimeField(auto_now=True)
remarks = models.CharField('备注', max_length=100, null=True)
def __str__(self):
return self.product_name
class Meta:
managed = False
db_table = "app_report"
#service common.py
import allure
from request.app_ui_zhh.common.image_processing import img_resize
import json
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL, REPORT_TYPE, REPORT_SAVE_PATH, DATA_TYPE
import os
from shutil import copy, copytree
from request.app_ui_zhh.common.excel_rw import read_excel, read_excel_from_contents
from request.app_ui_zhh.model.app_ui_model import AppUiTestCase, AppDeviceInfo, AppPackageInfo, AppReport
from request.app_ui_zhh.common.global_env import set_global_value, get_global_value
import time
log = CLogger(LOG_LEVEL)
def init_global_env(platform, project, product, device_name, app_name):
device_app_info = {
'server': 'http://localhost:' + '4723' + '/wd/hub',
# 'device_sn': "ANY-AN00",
'platformName': platform,
'device_sn': "iPhone Pro 13",
'deviceName': device_name, # iPhone的udid也用这个字段
'appPackage': "com.heygears.blueprintgo", # ISO目标APP的bundleId也用该字段
'appActivity': "com.heygears.blueprintgo.MainActivity",
'noReset': True,
'fullReset': False,
'platform_version': "12",
'version': "Magic UI 6.1",
'webDriverAgentUrl': "http://localhost:8100", # 连接IOS需要,WDA地址,暂时开放成变量修改IP和端口
"usePrebuiltWDA": False,
"useXctestrunFile": False,
"skipLogCapture": True,
"automationName": "XCUITest",
"AppName": app_name
}
set_global_value("screenshot_file_png", "")
set_global_value("test_case_remarks", "")
set_global_value("test_stop", 0)
set_global_value("test_result", "succ")
set_global_value("exist_airtest_case", False)
pro_base_dir = os.path.dirname(os.path.dirname(__file__))
test_case_excel_file = os.path.join(pro_base_dir, "data/testcase/blueprint_go_ui_test_case.xlsx")
if DATA_TYPE == 1:
# 调试脚本,测试数据从Excel中获取
test_case_data = get_test_case_from_excel(test_case_excel_file, device_app_info["platformName"])
else:
test_device_app_info = get_test_device_app_info_from_db(platform, device_name, app_name)
if test_device_app_info is False or len(test_device_app_info) < 1:
return False
device_app_info.update(test_device_app_info)
test_case_data = []
test_case_data_temp = get_test_case_from_db(platform=platform, project=project, product=product)
if len(test_case_data_temp) < 1:
return False
for data in test_case_data_temp:
test_case_data.append([value for value in data.values()])
set_global_value("test_case_data", test_case_data)
set_global_value("test_device_app_info", device_app_info)
for data in test_case_data:
if data[5] == "airtest":
set_global_value("exist_airtest_case", True)
break
return True
def save_screenshot(driver, path, file_name, ratio=None):
file = path + file_name
driver.get_screenshot_png(file)
if isinstance(ratio, (float, int)) and ratio < 1:
img_resize(file, ratio)
with open(file, mode='rb') as f:
file_data = f.read()
if REPORT_TYPE == 0:
allure.attach(file_data, "test", allure.attachment_type.PNG)
return file
def allure_trend_data_processing(history_trend_file, new_trend_file):
# allure趋势图处理方法
history_trend_data = []
build_order = None
if os.path.exists(history_trend_file):
# 获取历史文件数据
with open(history_trend_file) as f:
try:
history_trend_data = json.load(f)
if "history-trend.json" == new_trend_file.split("/")[-1]:
build_order = int(history_trend_data[0]['buildOrder'])
except Exception as e:
log.error("文件内容读取错误:{}".format(history_trend_file))
log.error("错误信息:{}".format(e))
# 获取新文件数据
with open(new_trend_file) as f:
try:
new_trend_data = json.load(f)
if "history-trend.json" == new_trend_file.split("/")[-1]:
if build_order is not None:
new_trend_data[0]['buildOrder'] = build_order + 1
else:
new_trend_data[0]['buildOrder'] = 1
history_trend_data.insert(0, new_trend_data[0])
except Exception as e:
log.error("文件内容读取错误:{}".format(new_trend_file))
log.error("错误信息:{}".format(e))
# 写入新文件
with open(new_trend_file, "w") as f:
json.dump(history_trend_data, f)
# 将新文件写入save_history文件夹
copy(new_trend_file, history_trend_file)
def get_test_case_from_excel(file_name, sheet_name):
data = read_excel(file_name, sheet_name, row=-1, col=-1)
if len(data) > 1:
del data[0]
return data
def get_test_case_from_db(platform, project, product):
data = list(AppUiTestCase.objects.filter(platform=platform, project=project, product=product).values('id', 'project', 'product', 'module', 'level', 'lib', 'method', 'parameter', 'expect', 'label', 'remarks'))
return data
def get_test_device_app_info_from_db(platform, device_name, app_name):
# 从数据库获取测试设备和APP的信息
device_info_data = AppDeviceInfo.objects.filter(platform_name=platform, device_name=device_name).values_list()
if len(device_info_data) != 1:
log.error("获取设备信息出错,未识别到唯一设备:{}".format(device_name))
return False
app_info_data = AppPackageInfo.objects.filter(platform=platform, app_name=app_name).values_list()
if len(app_info_data) != 1:
log.error("获取APP信息出错:{}".format(app_name))
return False
test_device_app_info = {}
for data in device_info_data:
if len(data) < 7:
log.error("获取设备信息出错,未识别到唯一设备:{}".format(device_name))
return False
test_device_app_info["server"] = data[6]
test_device_app_info["device_sn"] = data[1]
test_device_app_info["deviceName"] = data[2]
test_device_app_info["platformName"] = data[3]
test_device_app_info["platform_version"] = data[4]
test_device_app_info["version"] = data[5]
for data in app_info_data:
if len(data) < 4:
log.error("获取APP信息出错:{}".format(app_name))
return False
test_device_app_info["appPackage"] = data[3]
test_device_app_info["appActivity"] = data[4]
return test_device_app_info
def up_app_ui_case(file_contents):
data = read_excel_from_contents(file_contents=file_contents.read())
sheets = data.sheets()
if sheets is not None:
for sheet in sheets:
if sheet.name == "Android" or sheet.name == "IOS":
test_case_data = []
times = time.strftime('%y-%m-%d', time.localtime())
num = 0
for row in range(sheet.nrows):
platform = sheet.name
project = sheet.cell_value(row, 1)
product = sheet.cell_value(row, 2)
module = sheet.cell_value(row, 3)
level = sheet.cell_value(row, 4)
lib = sheet.cell_value(row, 5)
method = sheet.cell_value(row, 6)
parameter = sheet.cell_value(row, 7)
expect = sheet.cell_value(row, 8)
label = sheet.cell_value(row, 9)
create_time = times
remarks = sheet.cell_value(row, 10)
if num == 0:
num = num + 1
continue
test_case_data.append(AppUiTestCase(platform=str(platform), project=str(project), product=str(product), module=str(module),
level=str(level), lib=str(lib), method=str(method), parameter=str(parameter), expect=str(expect),
label=str(label), create_time=create_time, remarks=remarks))
AppUiTestCase.objects.bulk_create(test_case_data)
return True
else:
return False
def add_device_info(device_sn, device_name, platform_name, platform_version, version, server, remarks):
AppDeviceInfo.objects.create(device_sn=str(device_sn), device_name=str(device_name), platform_name=str(platform_name), platform_version=str(platform_version),
version=str(version), server=str(server), create_time=time.strftime('%y-%m-%d', time.localtime()), remarks=str(remarks))
def add_package_info(app_name, platform, package_name, activity_name, remarks):
AppPackageInfo.objects.create(app_name=str(app_name), platform=str(platform), package_name=str(package_name), activity_name=str(activity_name),
create_time=time.strftime('%y-%m-%d', time.localtime()), remarks=str(remarks))
def delete_device_info(id_num):
AppDeviceInfo.objects.filter(id=id_num).delete()
def delete_package_info(id_num):
AppPackageInfo.objects.filter(id=id_num).delete()
def save_report(platform_name, product_name, report_file_name):
start_check_time = int(time.time())
report_path_dist = ""
report_type = ""
test_result = get_global_value("test_result")
while int(time.time()) - start_check_time < 30:
if REPORT_TYPE == 1:
# 检测报告是否存在
report_type = "pytest-html"
pro_base_dir = os.path.dirname(os.path.dirname(__file__))
report_path_src = os.path.join(pro_base_dir, "report/pytest_html/" + report_file_name)
report_path_dist = REPORT_SAVE_PATH + "pytest-html/" + report_file_name
if os.path.exists(report_path_src):
copy(report_path_src, report_path_dist)
break
else:
# allure报告
report_type = "allure"
pro_base_dir = os.path.dirname(os.path.dirname(__file__))
report_path_src = os.path.join(pro_base_dir, "report/" + report_file_name)
report_path_dist = REPORT_SAVE_PATH + "allure/" + report_file_name
if not os.path.exists(report_path_dist) and os.path.isdir(report_path_src):
copytree(report_path_src, report_path_dist)
break
time.sleep(5)
AppReport.objects.create(platform_name=str(platform_name), product_name=str(product_name), result=str(test_result), path=str(report_path_dist), report_type=str(report_type),
create_time=time.strftime('%y-%m-%d', time.localtime()), remarks=str(""))
from request.app_ui_zhh.common.global_env import set_global_value, get_global_value
from pytest_assume.plugin import assume
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL
log = CLogger(LOG_LEVEL)
def result_assert(driver, expect):
if expect != "":
expect_value_list = expect.split(";")
if len(expect_value_list) > 0:
method = expect_value_list[0]
parameters = []
if len(expect_value_list) > 1:
i = 1
while i < len(expect_value_list):
parameters.append(expect_value_list[i])
i = i + 1
try:
if parameters is []:
result = getattr(driver, method)()
else:
result = getattr(driver, method)(*parameters)
except Exception as e:
log.info("断言过程出现错误:{}".format(e))
set_global_value("test_stop", 1)
set_global_value("test_result", "Fail")
else:
if result is False:
log.info("断言失败:{}".format(expect))
set_global_value("test_stop", 1)
set_global_value("test_result", "Fail")
assume(False)
else:
assume(True)
import time
import allure
from pytest_assume.plugin import assume
import pytest
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL, IMAGE_RATIO
from request.app_ui_zhh.service.common import save_screenshot
from request.app_ui_zhh.service.result_assert import result_assert
from request.app_ui_zhh.drivers.appium_driver import AppiumBaseAction
from request.app_ui_zhh.drivers.airtest_driver import AirtestBaseAction
from request.app_ui_zhh.drivers.server_manager import tidevice_wda_server_start, tidevice_wda_server_stop
import os
from request.app_ui_zhh.common.global_env import set_global_value, get_global_value
pro_base_dir = os.path.dirname(os.path.dirname(__file__))
image_path = os.path.join(pro_base_dir, "report/image/")
log = CLogger(LOG_LEVEL)
exist_airtest_case = get_global_value("exist_airtest_case")
device_app_info = get_global_value("test_device_app_info")
test_case_data = get_global_value("test_case_data")
test_case_num = 0
device_driver = AppiumBaseAction()
airtest_driver = AirtestBaseAction()
def setup_module():
global device_driver, device_app_info, exist_airtest_case
# 连接IOS设备需要先启动tidevice
if device_app_info["platformName"] == "IOS" and ("localhost" in device_app_info["service"] or "127.0.0.1" in device_app_info["service"]):
if tidevice_wda_server_start(device_app_info["deviceName"]) is False:
pytest.exit("Tidevice启动失败,退出测试!")
# 如果用例存在airtest库方法,需要airtest连接到设备
if exist_airtest_case is True:
if airtest_driver.device_connect(device_app_info) is False:
pytest.exit("Airtest连接设备失败,退出测试!")
# appium连接设备
if device_driver.device_driver(device_app_info) is False:
pytest.exit("Appium连接设备失败,退出测试!")
log.info("测试开始!")
def teardown_module():
global device_driver, exist_airtest_case
device_driver.device_quit()
if exist_airtest_case is True:
airtest_driver.device_disconnect()
if device_app_info["platformName"] == "IOS" and ("localhost" in device_app_info["service"] or "127.0.0.1" in device_app_info["service"]):
tidevice_wda_server_stop()
log.info("测试完成,退出测试!")
@allure.feature("测试安卓APP应用BluePrintGo")
class TestCase(object):
@staticmethod
def setup_method(self):
global test_case_num
test_stop = get_global_value("test_stop")
if test_stop == 1 and test_case_data[test_case_num][5] != "not_skip":
pytest.exit("测试进程主动终止,可能是执行过程遇到了错误,请检查!")
test_case_num = test_case_num + 1
@pytest.fixture()
def test_case_dict(self, request):
test_case_data_temp = request.param
test_case_dict = {
"case_id": test_case_data_temp[0],
"project": test_case_data_temp[1],
"product": test_case_data_temp[2],
"module": test_case_data_temp[3],
"level": test_case_data_temp[4],
"lib": test_case_data_temp[5],
"method": test_case_data_temp[6],
"parameter": test_case_data_temp[7],
"expect": test_case_data_temp[8],
"label": test_case_data_temp[9],
"remarks": test_case_data_temp[10]
}
return test_case_dict
@allure.story(device_app_info["platformName"])
@allure.title("{test_case_dict[remarks]}")
@pytest.mark.android_all_case
@pytest.mark.parametrize('test_case_dict', test_case_data, indirect=True)
def test_app_ui(self, test_case_dict):
log.info("正在执行用例:{}".format(test_case_dict['remarks']))
allure.dynamic.severity(test_case_dict['level'])
set_global_value("test_case_remarks", test_case_dict['remarks'])
parameters = test_case_dict["parameter"].split(";")
try:
if test_case_dict["lib"] == "appium":
lib_driver = device_driver
elif test_case_dict["lib"] == "airtest":
lib_driver = airtest_driver
else:
raise Exception("用例中lib库错误!")
if test_case_dict["parameter"] == "":
res = getattr(lib_driver, test_case_dict["method"])()
else:
res = getattr(lib_driver, test_case_dict["method"])(*parameters)
if res is not True:
raise Exception("执行APP自动化操作出现异常!")
except Exception as e:
log.info("执行测试出现错误:{}".format(e))
set_global_value("test_stop", 1)
set_global_value("test_result", "Fail")
assume(False)
time.sleep(1)
screenshot_file = save_screenshot(device_driver, image_path, device_app_info["platformName"] + " Test " + str(int(test_case_dict["case_id"])) + ".png", IMAGE_RATIO)
set_global_value("screenshot_file_png", screenshot_file)
result_assert(device_driver, test_case_dict["expect"])
import os
import pytest
from shutil import copy
import time
import django
from request.app_ui_zhh.config.config import REPORT_TYPE
from request.app_ui_zhh.common.logger import CLogger
from request.app_ui_zhh.config.config import LOG_LEVEL
import multiprocessing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AutoTest.settings')
django.setup()
from request.app_ui_zhh.service.common import allure_trend_data_processing, save_report, init_global_env
log = CLogger(LOG_LEVEL)
process_execute_state = multiprocessing.Value("i", 0)
def execute_test_process(platform, project, product, device_name, app_name):
global process_execute_state
if process_execute_state.value == 1:
return False
else:
process_execute_state.value = 1
p = multiprocessing.Process(target=execute_test, args=(platform, project, product, device_name, app_name, process_execute_state))
log.info("启动测试进程")
p.start()
return True
def execute_test(platform, project, product, device_name, app_name, process_state):
os.chdir(os.path.dirname(__file__))
if init_global_env(platform, project, product, device_name, app_name) is False:
log.error("初始化数据失败!未执行任何测试!")
process_state.value = 0
return False
if REPORT_TYPE == 0:
# allure报告执行代码
pro_base_dir = os.path.dirname(__file__)
report_path = os.path.join(pro_base_dir, "report/")
save_history_path = os.path.join(report_path, "save_history/")
report_xml_dir = os.path.join(report_path, "xml")
report_file_name = "html_" + str(int(time.time()))
report_html_dir = os.path.join(report_path, report_file_name)
widgets_dir = os.path.join(report_html_dir, "widgets/")
# pytest.main(['-s', '-q', '--clean-alluredir', '--alluredir', report_xml_dir, '-m', 'android_all_case'])
pytest.main(['-s', '-q', '--alluredir', report_xml_dir, '-m', 'android_all_case'])
copy(os.path.join(report_path, "environment.properties"), report_xml_dir)
copy(os.path.join(report_path, "categories.json"), report_xml_dir)
os.system('allure generate ' + report_xml_dir + ' -o ' + report_html_dir + ' --clean')
copy(os.path.join(report_path, "executors.json"), widgets_dir)
allure_trend_data_processing(save_history_path + "history-trend.json", widgets_dir + "history-trend.json")
allure_trend_data_processing(save_history_path + "duration-trend.json", widgets_dir + "duration-trend.json")
allure_trend_data_processing(save_history_path + "retry-trend.json", widgets_dir + "retry-trend.json")
allure_trend_data_processing(save_history_path + "categories-trend.json", widgets_dir + "categories-trend.json")
save_report(platform, product, report_file_name)
elif REPORT_TYPE == 1:
# pytest-html执行代码
report_file_name = 'report_' + str(int(time.time())) + '.html'
pytest.main(['-q', '--html=./report/pytest_html/' + report_file_name, '--self-contained-html', '--capture=tee-sys', '-m', 'android_all_case'])
save_report(platform, product, report_file_name)
else:
log.error("报告类型设置错误!未执行任何测试!")
process_state.value = 0
return False
process_state.value = 0
return True
if __name__ == '__main__':
execute_test_process("Android", "消费级", "Blueprint Go", "ACMFUT2519008078", "Blueprint Go")
# execute_test_process("IOS", "消费级", "Blueprint Go", "00008110-000A20D014F9801E", "Blueprint Go")
import base64
import time
from time import strftime
from py.xml import html
import pytest
from request.app_ui_zhh.common.global_env import get_global_value
from request.app_ui_zhh.config.config import DATA_TYPE
import cv2
import logging
report_time = []
# 编辑报告标题
def pytest_html_report_title(report):
report.title = "Blueprint Go APP Test Report"
# 运行测试后修改环境信息
@pytest.hookimpl(tryfirst=True)
def pytest_sessionfinish(session, exitstatus):
device_app_info = get_global_value("test_device_app_info")
session.config._metadata["Platform"] = device_app_info["platformName"]
session.config._metadata["System"] = device_app_info["version"]
session.config._metadata["DeviceName"] = device_app_info["deviceName"]
session.config._metadata["Version"] = device_app_info["platform_version"]
session.config._metadata["DeviceSn"] = device_app_info["device_sn"]
session.config._metadata['Start Time'] = strftime('%Y-%m-%d %H:%M:%S')
session.config._metadata['AppName'] = device_app_info["AppName"]
if DATA_TYPE == 1:
session.config._metadata['Testcase From'] = "Excel"
else:
session.config._metadata['Testcase From'] = "DB"
session.config._metadata.pop("JAVA_HOME")
session.config._metadata.pop("Plugins")
session.config._metadata.pop("Packages")
# 测试结果表格
def pytest_html_results_table_header(cells):
cells.insert(2, html.th("Description", class_="sortable desc", col="desc"))
cells.insert(3, html.th('Time', class_='sortable time', col='time'))
cells.pop()
def pytest_html_results_table_row(report, cells):
global report_time
cells.insert(2, html.th(report.description))
cells.insert(3, html.th(time.strftime('%Y.%m.%d %H:%M:%S', time.localtime(report_time[0])), class_="col-time"))
del report_time[0]
cells.pop()
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
screenshot_file_png = get_global_value("screenshot_file_png")
test_case_remarks = get_global_value("test_case_remarks")
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
report = outcome.get_result()
report.description = test_case_remarks
setattr(report, "duration_formatter", "%H:%M:%S.%f")
extra = getattr(report, "extra", [])
global report_time
if report.when == "setup":
report_time.append(call.start)
if report.when == "call":
image = cv2.imread(screenshot_file_png)
width_height_ratio = float(image.shape[1] / image.shape[0])
with open(screenshot_file_png, 'rb') as f:
image_base64 = str(base64.b64encode(f.read()), encoding='utf-8')
f.close()
if image_base64 is not None:
height = 484
html_script = '<div><img src="data:image/png;base64,{}" alt="screenshot" style="width:{}px;height:{}px;"" align="right"/></div>'\
.format(image_base64, str(int(width_height_ratio*height)), height)
extra.append(pytest_html.extras.html(html_script))
report.extra = extra
def pytest_sessionfinish():
# pytest's capsys is enabled by default, see
# https://docs.pytest.org/en/7.1.x/how-to/capture-stdout-stderr.html.
# but pytest closes its internal stream for capturing the stdout and
# stderr, so we are not able to write the logger anymore once the test
# session finishes. see https://github.com/pytest-dev/pytest/issues/5577,
# to silence the warnings, let's just prevent logging from raising
# exceptions.
logging.raiseExceptions = False