提交 0743db2b authored 作者: 陈泽健's avatar 陈泽健

补充预定配套件的项目目录,并补充公用方法类,以及调试门口屏的功能。

上级 53e8321d
from appium.webdriver.common.appiumby import AppiumBy
from time import sleep
from appium.options.android import UiAutomator2Options
from django.db.models.fields import return_None
from hytest import *
from pywinauto.mouse import click
from selenium import webdriver
# 创建一个函数,用于初始化Appium驱动程序
def app_setup_driver(platformName, platformVersion, deviceName, appPackage, appActivity, udid):
"""
根据提供的参数设置 Appium 驱动程序。
参数:
- platformName: 操作系统名称
- platformVersion: 操作系统版本
- deviceName: 设备名称
- appPackage: 应用程序包名
- appActivity: 应用程序活动名
- udid: 设备唯一标识符
返回:
- driver: Appium 驱动程序对象
"""
# 定义设备和应用的相关参数,以便 Appium 能够识别和控制设备
desired_caps = {
'platformName': platformName, # 被测手机是安卓
'platformVersion': platformVersion, # 手机安卓版本,如果是鸿蒙系统,依次尝试 12、11、10 这些版本号
'deviceName': deviceName, # 设备名,安卓手机可以随意填写
'appPackage': appPackage, # 启动APP Package名称
'appActivity': appActivity, # 启动Activity名称
'unicodeKeyboard': True, # 自动化需要输入中文时填True
'resetKeyboard': True, # 执行完程序恢复原来输入法
'noReset': True, # 不要重置App
'newCommandTimeout': 6000,
'automationName': 'UiAutomator2',
'skipUnlock': True,
'autoGrantPermissions': True,
'udid':udid
}
# 记录 desired_caps 参数信息
logging.info(f"desired_caps参数:{desired_caps}")
try:
# 记录初始化 Appium 驱动程序的过程
logging.info("正在初始化 Appium 驱动程序...")
# 创建 Appium 驱动程序对象
driver = webdriver.Remote('http://localhost:4723/wd/hub',
options=UiAutomator2Options().load_capabilities(desired_caps))
# 记录 Appium 驱动程序初始化成功
logging.info("Appium 驱动程序初始化成功。")
# 返回 Appium 驱动程序对象
return driver
except Exception as e:
# 记录初始化驱动程序失败的错误信息
logging.error(f"初始化驱动程序失败: {e}")
# 重新抛出异常
raise
# 封装滑动操作
def swipe_up(app_driver):
"""
在应用程序中执行上滑操作。
参数:
- app_driver: 应用程序的驱动对象,用于与设备交互。
返回值:
"""
# 获取屏幕尺寸
size = app_driver.get_window_size()
# 计算滑动的起始和结束坐标
start_x = size['width'] // 2
start_y = int(size['height'] * 0.2) # 起始y坐标,屏幕高度的20%
end_x = start_x
end_y = int(size['height'] * 0.8) # 结束y坐标,屏幕高度的80%
# 执行滑动操作
app_driver.swipe(start_x, start_y, end_x, end_y, duration=500)
# 图片亮度对比函数
# 请使用“pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple”安装PIL库
from PIL import Image
import numpy as np
import os
import logging
def compare_brightness(light_down_path, light_on_path, threshold=1):
"""
对比两张图片的亮度,返回亮度是否增加的布尔值。
light_on_path:传入暗色的图片
light_down_path:传入亮色的图片
threshold:亮度变化的阈值,默认为1
"""
try:
# 打开图片并转换为灰度图像,以便后续处理
image1 = Image.open(light_down_path).convert('L') # 转换为灰度图像
image2 = Image.open(light_on_path).convert('L') # 转换为灰度图像
# 将图像转换为numpy数组,便于计算
array1 = np.array(image1)
array2 = np.array(image2)
# 计算两张图片的平均亮度
avg_brightness1 = np.mean(array1)
avg_brightness2 = np.mean(array2)
# 记录日志,输出两张图片的平均亮度
logging.info(f"关闭灯光时的平均亮度: {avg_brightness1}")
logging.info(f"打开灯光时的平均亮度: {avg_brightness2}")
# 计算亮度变化量
brightness_increase = avg_brightness2 - avg_brightness1
# 记录日志,输出亮度变化量
logging.info(f"亮度变化量: {brightness_increase}")
# 判断亮度变化量是否超过阈值
return brightness_increase > threshold
except Exception as e:
# 异常处理,记录错误日志
logging.error(f"对比亮度时发生错误: {e}", exc_info=True)
return False
# 调用示例
# if __name__ == '__main__':
# logging.info("开始对比亮度")
#
# image1_path = r'D:\GithubData\自动化\ubains-module-test\预定系统\Base\captured_frame2.jpg'
# image2_path = r'D:\GithubData\自动化\ubains-module-test\预定系统\Base\captured_frame.jpg'
#
# # 检查图片路径是否存在
# if not os.path.exists(image1_path):
# logging.error(f"图片 {image1_path} 不存在")
# exit(1)
# if not os.path.exists(image2_path):
# logging.error(f"图片 {image2_path} 不存在")
# exit(1)
#
# # 对比两张截图的亮度
# result = compare_brightness(image1_path, image2_path)
# logging.info(f"亮度比较结果: {result}")
#
# if result:
# logging.info("灯光已成功打开")
# else:
# logging.error("灯光未成功打开")
# 提取特征点并比较图片函数
# 请使用“pip install opencv-python”安装cv2库
import cv2
import logging
from PIL import Image
import numpy as np
import os
def compare_images_feature_matching(image1_path, image2_path):
"""
比较两张图片是否相同
使用特征匹配的方法比较两张图片是否相同。首先验证图片路径和格式,然后根据图片尺寸进行调整,
最后转换为numpy数组进行比较。
参数:
image1_path (str): 第一张图片的路径
image2_path (str): 第二张图片的路径
返回:
dict: 包含比较结果和可能的错误信息
"""
try:
# 验证图片路径是否存在且为有效图片文件
if not os.path.isfile(image1_path) or not os.path.isfile(image2_path):
logging.error("图片路径无效")
return {"result": False, "error": "图片路径无效"}
# 打开两张图片
img1 = Image.open(image1_path)
img2 = Image.open(image2_path)
# 验证图片是否为有效格式
if img1.format not in ['JPEG', 'PNG'] or img2.format not in ['JPEG', 'PNG']:
logging.error("图片格式无效")
return {"result": False, "error": "图片格式无效"}
# 如果尺寸不同,先调整大小
if img1.size != img2.size:
logging.info("图片尺寸不同,调整为相同尺寸进行比较")
img2 = img2.resize(img1.size, Image.ANTIALIAS) # 保持纵横比
# 将图片转换为相同的模式
img1 = img1.convert("RGB")
img2 = img2.convert("RGB")
# 转换为 numpy 数组进行比较
img1_array = np.array(img1)
img2_array = np.array(img2)
# 输出numpy数组信息
logging.info(f"图片1数组: {img1_array},图片2数组: {img2_array}")
# 比较两个数组是否相同
return {"result": np.array_equal(img1_array, img2_array), "error": None}
except FileNotFoundError as e:
logging.error(f"文件未找到: {e}")
return {"result": False, "error": f"文件未找到: {e}"}
except OSError as e:
logging.error(f"图片处理错误: {e}")
return {"result": False, "error": f"图片处理错误: {e}"}
except MemoryError as e:
logging.error(f"内存不足: {e}")
return {"result": False, "error": f"内存不足: {e}"}
except Exception as e:
logging.error(f"未知错误: {e}")
return {"result": False, "error": f"未知错误: {e}"}
# 示例调用
# if __name__ == '__main__':
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# logging.info("开始对比图片")
#
# image1_path = r'D:\GithubData\自动化\ubains-module-test\预定系统\reports\imgs\Exhibit_Inspect\No_PaperLess\DeviceA-ShareScreen.png'
# image2_path = r'D:\GithubData\自动化\ubains-module-test\预定系统\reports\imgs\Exhibit_Inspect\No_PaperLess\同屏前-无纸化设备B界面截屏.png'
#
# if not os.path.exists(image1_path):
# logging.error(f"图片 {image1_path} 不存在")
# exit(1)
# if not os.path.exists(image2_path):
# logging.error(f"图片 {image2_path} 不存在")
# exit(1)
#
# # 对比两张截图的相似度
# if compare_images_feature_matching(image1_path, image2_path):
# logging.info("图片相同")
# else:
# logging.error("图片不同")
# 计算直方图相似度函数
import cv2
import numpy
from PIL import Image
import logging
import os
def calculate(image1, image2):
"""
计算两张图片的直方图重合度
通过将图片转换为BGR格式,并计算每张图片的蓝色通道直方图,然后比较这两个直方图的重合度来评估图片的相似度
参数:
image1: 第一张图片,应为RGB格式
image2: 第二张图片,应为RGB格式
返回值:
返回两张图片直方图的重合度,范围在0到1之间,1表示完全重合,即图片高度相似
"""
image1 = cv2.cvtColor(numpy.asarray(image1), cv2.COLOR_RGB2BGR)
image2 = cv2.cvtColor(numpy.asarray(image2), cv2.COLOR_RGB2BGR)
hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0])
hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0])
# 计算直方图的重合度
degree = 0
for i in range(len(hist1)):
if hist1[i] != hist2[i]:
degree = degree + (1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i]))
else:
degree = degree + 1
degree = degree / len(hist1)
return degree
# 图片相似性对比函数
def classify_hist_with_split(image1, image2, size=(256, 256)):
"""
根据两张图片的RGB直方图比较它们的相似性。
参数:
image1: 第一张图片的路径。
image2: 第二张图片的路径。
size: 将图片调整到的统一尺寸,默认为(256, 256)。
返回:
两张图片的相似度,值越小表示两张图片越相似。
"""
# 打开图片文件
image1 = Image.open(image1)
image2 = Image.open(image2)
# 将PIL图像转换为OpenCV格式(BGR)
image1 = cv2.cvtColor(numpy.asarray(image1), cv2.COLOR_RGB2BGR)
image2 = cv2.cvtColor(numpy.asarray(image2), cv2.COLOR_RGB2BGR)
# 调整图片尺寸,以确保比较是在相同尺寸下进行
image1 = cv2.resize(image1, size)
image2 = cv2.resize(image2, size)
# 分离图片的RGB通道
sub_image1 = cv2.split(image1)
sub_image2 = cv2.split(image2)
sub_data = 0
# 遍历每个通道,计算并累加相似度
for im1, im2 in zip(sub_image1, sub_image2):
sub_data += calculate(im1, im2)
# 计算平均相似度
sub_data = sub_data / 3
# 返回最终的相似度结果
return sub_data
# 示例调用
# if __name__ == '__main__':
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# logging.info("开始对比图片")
#
# image1_path = r'D:\GithubData\自动化\ubains-module-test\预定系统\reports\imgs\Exhibit_Inspect\No_PaperLess\同屏后-无纸化设备A界面截屏.png'
# image2_path = r'D:\GithubData\自动化\ubains-module-test\预定系统\reports\imgs\Exhibit_Inspect\No_PaperLess\同屏后-无纸化设备B界面截屏.png'
#
# if not os.path.exists(image1_path):
# logging.error(f"图片 {image1_path} 不存在")
# exit(1)
# if not os.path.exists(image2_path):
# logging.error(f"图片 {image2_path} 不存在")
# exit(1)
#
# # 对比两张截图的相似度
# result1 = classify_hist_with_split(image1_path, image2_path)
#
# # 确保 result1 是一个标量值
# if isinstance(result1, numpy.ndarray):
# result1 = result1.item()
#
# print("相似度为:" + "%.2f%%" % (result1 * 100))
# 检查输出路径是否有效函数
import cv2
import logging
import os
import shutil # 导入 shutil 模块以检查磁盘空间
def check_output_path(output_path):
"""
检查输出路径是否有效
如果输出目录不存在,则尝试创建它,并检查是否有写权限
参数:
output_path (str): 输出文件的路径
返回:
bool: 如果输出路径有效且可写,则返回True,否则返回False
"""
# 获取输出文件的目录部分
output_dir = os.path.dirname(output_path)
# 检查输出目录是否存在
if not os.path.exists(output_dir):
try:
# 尝试创建输出目录
os.makedirs(output_dir)
logging.info(f"创建目录: {output_dir}")
except Exception as e:
# 如果创建目录失败,记录错误信息并返回False
logging.error(f"无法创建目录 {output_dir}: {e}")
return False
# 检查文件权限
if not os.access(output_dir, os.W_OK):
# 如果没有写权限,记录错误信息并返回False
logging.error(f"没有写权限: {output_dir}")
return False
# 如果一切正常,返回True
return True
# 捕获RTSP流并保存为图像文件函数
def capture_frame_from_rtsp(rtsp_url, file_name, output_path=None):
"""
从RTSP流中捕获一帧并保存为图像文件。
参数:
- rtsp_url: RTSP流的URL。
- file_name: 保存图像文件的名称。
- output_path: 保存图像文件的路径,默认为None,如果未提供则使用默认路径。
返回:
- 成功捕获并保存帧时返回True,否则返回False。
"""
try:
# 验证输入参数
if not rtsp_url:
logging.error("RTSP URL 为空")
return False
# 获取当前脚本所在的根目录
script_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(script_dir)
# 构建默认输出路径
if output_path is None:
output_path = os.path.join(root_dir, "reports", "imgs", "Exhibit_Inspect", "Control_Manage", file_name)
# 检查并创建输出目录
if not check_output_path(output_path):
return False
# 打开RTSP流
cap = cv2.VideoCapture(rtsp_url)
if not cap.isOpened():
logging.error("无法打开RTSP流")
return False
# 尝试多次读取帧以确保获取有效帧
for _ in range(5): # 尝试读取5次
ret, frame = cap.read()
if ret and frame is not None:
break
else:
logging.error("无法从RTSP流中读取有效帧")
cap.release()
return False
# 确认帧不为空
if frame is None or frame.size == 0:
logging.error("捕获到的帧为空")
cap.release()
return False
# 检查帧的形状和类型
logging.info(f"捕获到的帧尺寸: {frame.shape}, 数据类型: {frame.dtype}")
# 尝试保存帧为图像文件
success = False
try:
# 使用 cv2.imencode 保存图像到内存中,再写入文件
_, img_encoded = cv2.imencode('.png', frame)
with open(output_path, 'wb') as f:
f.write(img_encoded.tobytes())
success = True
except Exception as e:
logging.error(f"无法保存帧到 {output_path}: {e}")
logging.error(f"检查路径是否存在: {os.path.exists(os.path.dirname(output_path))}")
logging.error(f"检查路径是否可写: {os.access(os.path.dirname(output_path), os.W_OK)}")
# 使用 shutil.disk_usage 检查磁盘空间
try:
total, used, free = shutil.disk_usage(os.path.dirname(output_path))
logging.error(f"检查磁盘空间: {free // (2 ** 20)} MB available")
except Exception as e:
logging.error(f"无法检查磁盘空间: {e}")
if success:
logging.info(f"帧已保存到 {output_path}")
else:
logging.error(f"帧保存失败")
# 释放资源
cap.release()
return success
except Exception as e:
logging.error(f"捕获帧时发生错误: {e}", exc_info=True)
return False
# 中控屏灯光控制函数
def light_control(app_drive):
"""
控制灯光的函数。
该函数通过Appium驱动定位并点击应用中的灯光控制按钮,以开启不同区域的灯光。
参数:
- app_drive: Appium驱动实例,用于与移动应用交互。
"""
# 开启所有区域灯光
# 定位【接待区】灯光
light_reception_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.ImageView[1]")
sleep(2)
logging.info("尝试定位【接待区】按钮元素,并点击按钮")
click_with_retry(light_reception_button)
sleep(2)
# 定位【指挥中心】灯光
light_command_center_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[3]")
sleep(2)
logging.info("尝试定位【指挥中心】按钮元素,并点击按钮")
click_with_retry(light_command_center_button)
sleep(2)
# 定位【影音室】灯光
light_audio_room_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[2]")
sleep(2)
logging.info("尝试定位【影音室】按钮元素,并点击按钮")
click_with_retry(light_audio_room_button)
sleep(2)
# 定位【会议室】灯光
light_meeting_room_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[5]")
sleep(2)
logging.info("尝试定位【会议室】按钮元素,并点击按钮")
click_with_retry(light_meeting_room_button)
sleep(2)
# 定位【会商区】灯光
light_meeting_area_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[4]")
sleep(2)
logging.info("尝试定位【会商区】按钮元素,并点击按钮")
click_with_retry(light_meeting_area_button)
sleep(2)
# 定位【培训室】灯光
light_training_room_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[6]")
sleep(2)
logging.info("尝试定位【培训室】按钮元素,并点击按钮")
click_with_retry(light_training_room_button)
sleep(2)
# 中控屏窗帘控制函数
def curtain_control(app_drive, wd):
"""
控制窗帘的上升和下降,并捕获相应状态的截图。
参数:
app_drive: Appium驱动对象,用于操作App。
wd: WebDriver对象,用于捕获屏幕截图。
此函数无返回值。
"""
# 所有窗帘全部上升
logging.info("尝试定位所有【窗帘上升】按钮元素,并点击按钮")
# 上升按钮的定位
curtain_up_locator = ['3', '4', '5', '1', '13']
for i in curtain_up_locator:
curtain_up_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
f"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[{i}]")
click_with_retry(curtain_up_button)
sleep(2)
INFO("请检查窗帘上升状态是否正常")
# 截图获取当前中控屏软件窗帘上升的界面
get_screenshot_with_retry(wd, app_drive, "Exhibit_Inspect", "Control_Manage", "curtain_up")
sleep(30)
# # 测试报告中补充窗帘上升的截图
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "curtain_rtsp_up")
# 通过rtsp流获取当前窗帘的上升效果图
curtain_rtsp_url = "rtsp://admin:huawei@123@192.168.4.18/LiveMedia/ch1/Media2"
logging.info("开始捕获RTSP流中的帧")
if capture_frame_from_rtsp(curtain_rtsp_url, "curtain_rtsp_up.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# 所有窗帘全部下降
logging.info("尝试定位所有【窗帘下降】按钮元素,并点击按钮")
# 下降按钮的定位
curtain_down_locator = ['10', '11', '12', '6', '15']
for i in curtain_down_locator:
curtain_down_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
f"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[{i}]")
click_with_retry(curtain_down_button)
sleep(2)
sleep(30)
INFO("请检查窗帘下降状态是否正常")
get_screenshot_with_retry(wd, app_drive, "Exhibit_Inspect", "Control_Manage", "curtain_down")
# 截图获取当前中控屏软件窗帘上升的界面
# 测试报告中补充窗帘下降的截图
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "curtain_rtsp_down")
logging.info("开始捕获RTSP流中的帧")
if capture_frame_from_rtsp(curtain_rtsp_url, "curtain_rtsp_down.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# 中控屏空调控制函数
def air_condition_control(app_drive, wd):
"""
控制空调的打开与关闭,并检查其状态显示。
参数:
- app_drive: Appium驱动对象,用于操作移动端应用。
- wd: WebDriver对象,用于捕获屏幕截图。
此函数不返回任何值。
"""
# 点击【打开空调】按钮
logging.info("尝试定位【打开空调】按钮元素,并点击按钮")
open_air_conditioner_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[14]")
click_with_retry(open_air_conditioner_button)
sleep(20)
# 这是空调开启的状态显示
INFO("请检查空调开启的状态是否正常")
get_screenshot_with_retry(wd, app_drive, "Exhibit_Inspect", "Control_Manage", "air_condition_on")
sleep(2)
# 点击【关闭空调】按钮
logging.info("尝试定位【关闭空调】按钮元素,并点击按钮")
close_air_conditioner_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[14]")
click_with_retry(close_air_conditioner_button)
sleep(20)
# 这是空调关闭的状态显示
INFO("请检查空调关闭的状态是否正常")
get_screenshot_with_retry(wd, app_drive, "Exhibit_Inspect", "Control_Manage", "air_condition_off")
sleep(2)
# 中控屏信息发布控制函数
def information_control(app_drive, wd):
"""
控制信息展示和捕获RTSP流中的帧。
参数:
- app_drive: Appium驱动实例,用于操作移动应用。
- wd: WebDriver实例,用于操作网页。
此函数依次选择不同的内容进行播放,捕获RTSP流中的帧,并记录屏幕状态。
"""
# 选择生日快乐内容播放
brithday_information_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[1]")
click_with_retry(brithday_information_button)
logging.info("选择生日快乐内容播放")
sleep(5)
# 这是生日快乐主题内容发布
INFO("请检查中控屏软件信息发布界面是否正常")
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "information_brithday_on")
information_rtsp_url = "rtsp://admin:huawei@123@192.168.4.19/LiveMedia/ch1/Media2" # 替换为你的RTSP流地址
logging.info("开始捕获RTSP流中的帧")
if capture_frame_from_rtsp(information_rtsp_url, "information_brithday_on.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# 选择欢迎领导发布内容播放
meeting_information_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[2]")
click_with_retry(meeting_information_button)
logging.info("选择会议发布内容播放")
sleep(5)
# 这是会议欢迎主题内容发布
INFO("请检查中控屏软件信息发布界面是否正常")
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "information_meeting_on")
logging.info("开始捕获RTSP流中的帧")
if capture_frame_from_rtsp(information_rtsp_url, "information_meeting_on.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# 选择展厅空间结构内容播放
information_space_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[3]")
click_with_retry(information_space_button)
logging.info("选择展厅空间结构内容播放")
sleep(5)
# 这是展厅空间结构内容发布
INFO("请检查中控屏软件信息发布界面是否正常显示")
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "information_space_on")
if capture_frame_from_rtsp(information_rtsp_url, "information_space_on.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# 点击信息发布关闭按钮
INFO("点击信息发布关闭按钮")
information_close_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[6]")
click_with_retry(information_close_button)
# 这是信发屏已关闭的界面
INFO("请检查中控屏软件信息发布界面是否正常显示为关闭")
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "information_off")
if capture_frame_from_rtsp(information_rtsp_url, "information_off.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# 中控屏音乐控制函数
def music_control(app_drive, wd):
"""
控制音乐播放的函数,包括播放和停止音乐。
:param app_drive: Appium驱动对象,用于操作App。
:param wd: WebDriver对象,用于浏览器自动化操作。
"""
# 点击【播放音乐】
logging.info("尝试定位【播放音乐】按钮元素,并点击按钮")
play_music_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[8]")
click_with_retry(play_music_button)
sleep(2)
sleep(5)
# 这是音乐开启播放后的界面显示
INFO("请检查中控屏软件打开音乐播放后的界面状态显示")
get_screenshot_with_retry(wd, app_drive, "Exhibit_Inspect", "Control_Manage", "music_on")
# 点击【关闭播放音乐】
logging.info("尝试定位【关闭播放音乐】按钮元素,并点击按钮")
close_play_music_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button[8]")
click_with_retry(close_play_music_button)
sleep(5)
# 这是音乐关闭播放后的界面显示
INFO("请检查中控屏软件关闭音乐播放后的界面状态显示")
get_screenshot_with_retry(wd, app_drive, "Exhibit_Inspect", "Control_Manage", "music_off")
# 中控屏控制函数
def command_centre_control(rtsp_url, app_drive, wd):
"""
控制指挥中心大屏的开启和关闭,并 capture RTSP 流的一帧作为日志。
参数:
- rtsp_url: RTSP 流的 URL。
- app_drive: Appium 驱动对象,用于操作移动应用。
- wd: WebDriver 对象,用于执行 Selenium 相关操作。
此函数会尝试打开指挥中心大屏,capture 并记录大屏开启和关闭时的监控视频帧。
"""
# 打开指挥中心大屏
open_center_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button")
click_with_retry(open_center_button)
sleep(10)
# 这是指挥大屏开启的监控视频显示
INFO("请检查指挥大屏开启的监控视频状态是否正常")
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "command_screen_on")
# 从rtsp流中截取一帧保存为图片
logging.info("开始捕获RTSP流中的帧")
if capture_frame_from_rtsp(rtsp_url, "command_screen_on.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# 这是指挥大屏关闭的监控视频显示
INFO("请检查指挥大屏关闭的监控视频状态是否正常")
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Control_Manage", "command_screen_down")
# 关闭指挥中心大屏幕
logging.info("尝试定位【关闭指挥中心控制】按钮元素,并点击按钮")
close_center_button = find_element_with_retry(app_drive, AppiumBy.XPATH,
"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.RelativeLayout/android.widget.RelativeLayout[2]/android.widget.Button")
click_with_retry(close_center_button)
sleep(10)
# 从rtsp流中截取一帧保存为图片
logging.info("开始捕获RTSP流中的帧")
if capture_frame_from_rtsp(rtsp_url,
"command_screen_down.png"):
logging.info("帧捕获成功")
else:
logging.error("帧捕获失败")
# app设备初始化adb连接函数
import subprocess
from venv import logger
def app_init(device_ip, port=5555):
"""
初始化浏览器设置和实例。
此函数旨在初始化程序与app设备之间的adb连接,判断adb连接状态是否可控
"""
# 标记初始化过程的开始
INFO("'----------' 正在初始化ADB连接 '----------'")
"""
通过 ADB 连接设备并检查设备状态
:param device_ip: 设备的 IP 地址
:param port: 端口号,默认为 5555
"""
try:
# 构建设备地址
device_address = f"{device_ip}:{port}"
# 连接设备
subprocess.run(['adb', 'connect', device_address], check=True)
INFO(f"尝试连接到设备: {device_address}")
# 检查设备状态
result = subprocess.run(['adb', 'devices'], capture_output=True, text=True, check=True)
devices = result.stdout.strip().split('\n')[1:] # 去掉标题行
for device in devices:
ip, status = device.split()
if ip == device_address and status == 'device':
INFO(f"设备 {device_address} 已连接并可用")
return True
elif ip == device_address and status == 'offline':
INFO(f"设备 {device_address} 处于 offline 状态,当前设备不可控制,请检查设备网络状态")
return False
elif ip == device_address and status == 'unauthorized':
logger.error(f"设备 {device_address} 未授权调试")
return False
INFO(f"设备 {device_address} 未找到,请检查设备IP是否正确!")
return False
except subprocess.CalledProcessError as e:
INFO(f"连接设备失败: {e}")
return False
# app设备退出adb连接函数
def app_quit(device_ip,port=5555):
"""
退出浏览器并释放资源。
该函数从全局存储中获取设备adb连接状态
"""
# 断开特定 IP 和端口的 ADB 连接
device_address = f"{device_ip}:{port}"
subprocess.run(['adb', 'disconnect', device_address])
INFO(f"ADB 连接已断开: {device_address}")
# app截屏函数
def get_screenshot_with_retry(wd,app_drive, module_name, function_name, step_name, max_retries=3, retry_delay=5):
"""
使用重试机制获取并保存截图。
参数:
app_drive: 实现了get_screenshot_as_file方法的对象,用于获取截图。
module_name: 用于构造保存截图的目录名称。
function_name: 用于构造截图的目录名称。
setp_name:用于构造截图文件的名称
max_retries: 最大重试次数,默认为3次。
retry_delay: 重试间隔时间,默认为5秒。
返回值:
无。如果多次尝试截图失败,则抛出异常。
"""
# 获取当前文件的绝对路径
current_file_path = os.path.abspath(__file__)
# 获取当前文件的父级目录
parent_dir = os.path.dirname(current_file_path)
# 构造目标目录路径
target_dir = os.path.join(parent_dir, '..', 'reports', 'imgs', module_name, function_name)
# 确保目标目录存在,如果不存在则创建
os.makedirs(target_dir, exist_ok=True)
# 构造文件路径
file_path = os.path.join(target_dir, f"{step_name}.png")
#截屏
SELENIUM_LOG_SCREEN(wd, "75%", module_name, function_name, f"{step_name}")
# 使用循环实现重试机制
for _ in range(max_retries):
try:
# 尝试保存截图
app_drive.get_screenshot_as_file(file_path)
# 如果成功,记录日志并退出函数
logging.info(f"截图保存成功: {file_path}")
return
except Exception as e:
# 如果失败,记录日志并等待重试
logging.warning(f"截图失败,重试中... ({e})")
sleep(retry_delay)
# 如果多次尝试均失败,则抛出异常
raise Exception(f"多次尝试截图失败: {file_path}")
# app查找元素函数
def find_element_with_retry(app_driver, by, value, max_retries=3, retry_delay=5):
"""
使用重试机制查找元素。
在WebDriver(driver)中通过给定的查找方式(by)和值(value)来查找页面元素。
如果在指定的最大重试次数(max_retries)内仍然找不到元素,则抛出异常。
每次重试之间会有指定的延迟时间(retry_delay)。
参数:
- driver: WebDriver实例,用于执行查找操作。
- by: 查找元素的方式,如XPath、ID等。
- value: 元素的值,根据'by'参数指定的查找方式对应的具体值。
- max_retries: 最大重试次数,默认为3次。
- retry_delay: 每次重试之间的延迟时间,默认为5秒。
返回:
- 返回找到的元素。
异常:
- 如果超过最大重试次数仍未找到元素,则抛出异常。
"""
for _ in range(max_retries):
try:
# 尝试查找元素,如果成功则立即返回元素
return app_driver.find_element(by, value)
except Exception as e:
# 如果查找元素失败,记录日志并等待一段时间后重试
logging.warning(f"查找元素失败,重试中... ({e})")
sleep(retry_delay)
# 如果达到最大重试次数仍未找到元素,则抛出异常
raise Exception(f"多次尝试查找元素失败: {by}={value}")
# app点击事件函数
def click_with_retry(element, max_retries=3, retry_delay=5):
"""
点击元素的函数,带有重试机制。
参数:
element (obj): 要点击的元素对象。
max_retries (int): 最大重试次数,默认为3次。
retry_delay (int): 每次重试之间的延迟时间,默认为5秒。
异常:
如果超过最大重试次数仍未成功点击元素,则抛出异常。
"""
# 尝试点击元素,直到达到最大重试次数
for _ in range(max_retries):
try:
# 尝试点击元素
element.click()
# 如果点击成功,记录日志并退出函数
logging.info(f"点击元素成功: {element}")
return
except Exception as e:
# 如果点击失败,记录日志并等待下一次重试
logging.warning(f"点击元素失败,重试中... ({e})")
sleep(retry_delay)
# 如果所有重试都失败,抛出异常
raise Exception(f"多次尝试点击元素失败: {element}")
\ No newline at end of file
import urllib
from selenium.webdriver.chrome.service import Service
import json
import hmac
import hashlib
import base64
import subprocess
import logging
import requests
from hytest import *
from selenium import webdriver
from selenium.common import ElementNotInteractableException
from urllib.parse import urlencode
from datetime import datetime
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
# import datetime
# 获取当前脚本的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
# 获取当前脚本的父目录
parent_dir = os.path.dirname(current_dir)
logging.info(parent_dir)
# 添加路径
sys.path.append(current_dir)
# 配置日志记录器,仅输出到控制台
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler()
]
)
# 浏览器初始化函数
def browser_init(login_type):
"""
初始化浏览器设置和实例。
此函数旨在创建并配置一个Chrome浏览器实例,包括设置Chrome选项以排除不必要的日志,
并尝试打开特定的登录页面。任何初始化过程中出现的错误都会被捕获并记录。
参数:
login_type (str): 指定登录类型,根据不同的登录类型选择不同的URL。
返回:
"""
# 标记初始化过程的开始
INFO("'----------' 正在初始化浏览器 '----------'")
# 创建Chrome选项实例,用于配置浏览器行为
options = webdriver.ChromeOptions()
# 添加实验性选项,排除某些命令行开关以减少输出日志
options.add_experimental_option('excludeSwitches', ['enable-Logging'])
# 忽略证书错误,允许在本地主机上运行时不安全
options.add_argument('--ignore-certificate-errors')
# 禁用自动化控制特征检测,避免被网站识别为自动化流量
options.add_argument('--disable-blink-features=AutomationControlled')
# 允许不安全的本地主机运行,通常用于开发和测试环境
options.add_argument('--allow-insecure-localhost')
# 使用webdriver_manager自动下载并管理chromedriver
# service = ChromeService(ChromeDriverManager().install())
# 使用备用的ChromeDriver下载源
# service = Service(ChromeDriverManager().install())
# 手动指定ChromeDriver的路径
# 自动化运行服务器的chromedriver路径:
service = Service(r'C:\Users\29194\AppData\Local\Programs\Python\Python310\Scripts\chromedriver.exe')
# service = Service(r'C:\Program Files\Python310\Scripts\chromedriver.exe')
# 尝试创建WebDriver实例并执行初始化操作
try:
# 创建WebDriver实例
wd = webdriver.Chrome(service=service, options=options)
# 设置隐式等待时间为10秒,以允许元素加载
wd.implicitly_wait(60)
# 获取登录URL
login_url = get_login_url_from_config(login_type)
# 打开对应类型的登录页面
wd.get(login_url)
# 最大化浏览器窗口
wd.maximize_window()
# 将WebDriver实例存储在全局存储器中,以便后续使用
GSTORE['wd'] = wd
# 标记初始化过程完成
INFO("'----------' 浏览器初始化完成 '----------'")
except Exception as e:
# 捕获并记录初始化过程中的任何异常
logging.error(f"浏览器初始化失败:{e}")
# 从配置项config中获取登录URL
def get_login_url_from_config(login_type):
"""
从配置文件中读取登录URL。
参数:
login_type (str): 指定登录类型,根据不同的登录类型选择不同的URL。
返回:
str: 对应的登录URL。
"""
# 检查 login_type 是否为空或 None
if not login_type:
raise ValueError("login_type 不能为空")
# 获取当前脚本的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
# 构建配置文件的绝对路径,指向 ubains-module-test 目录下的 config.json
config_path = os.path.abspath(os.path.join(current_dir, '..', '..', 'config.json'))
# 规范化路径,防止路径遍历攻击
config_path = os.path.normpath(config_path)
# 记录配置文件路径以便调试,对路径进行脱敏处理
logging.info(f"配置文件路径: {os.path.basename(config_path)}")
# 检查文件是否存在
if not os.path.exists(config_path):
# 如果配置文件不存在,则抛出异常
raise FileNotFoundError(f"配置文件 {config_path} 不存在")
try:
# 读取配置文件
with open(config_path, 'r', encoding='utf-8') as config_file:
# 将配置文件内容解析为 JSON 格式
config = json.load(config_file)
# 根据 login_type 获取对应的登录 URL
login_url = config.get(login_type)
# 记录正在打开的登录页面类型和 URL
logging.info(f"正在打开 {login_type} 的登录页面:{login_url}")
except IOError as e:
# 处理文件读取异常
raise IOError(f"读取配置文件失败: {e}")
except json.JSONDecodeError as e:
# 处理 JSON 解析异常
raise json.JSONDecodeError(f"解析配置文件失败: {e}")
# 检查是否成功获取到 URL
if not login_url:
# 如果未找到对应的 URL,则抛出异常
raise ValueError(f"未找到对应的 URL 配置项: {login_type}")
# 返回登录 URL
return login_url
# 管理员登录函数
def user_login(username, password):
"""
管理员登录函数。
该函数通过模拟用户输入用户名、密码和验证码,并点击登录按钮,以实现管理员登录。
"""
# 获取webdriver实例
wd = GSTORE['wd']
# 打印用户名输入信息
INFO(f"输入用户名:{username}")
# 向用户名输入框发送用户名
safe_send_keys((By.XPATH, "//input[@placeholder='请输入账号或手机号或邮箱号']"), f'{username}', wd)
# 打印密码输入信息
INFO(f"输入密码:{password}")
# 向密码输入框发送密码
safe_send_keys((By.XPATH, "//input[@placeholder='请输入密码']"), f"{password}", wd)
# 打印验证码输入信息
INFO("输入验证码:csba")
# 向验证码输入框发送验证码
safe_send_keys((By.XPATH, "//input[@placeholder='请输入图形验证码']"), "csba", wd)
# 隐式等待5秒,以确保验证码被正确处理
wd.implicitly_wait(5)
# 打印登录按钮点击信息
INFO("点击登录按钮")
# 点击登录按钮
safe_click((By.XPATH, "//input[@value='登 录']"), wd)
# 输入框输入值函数
def safe_send_keys(element_locator, value, wd):
"""
安全地向网页元素发送键值。
该函数尝试在指定时间内找到指定的网页元素,如果找到并且元素可见,将先清除元素内容,然后发送指定的键值。
如果在指定时间内未能找到元素或元素不可点击,则捕获相应的异常并打印错误信息。
参数:
element_locator (tuple): 用于定位网页元素的策略和定位器的元组,例如(By.ID, 'element_id')。
value (str): 要发送到元素的键值字符串。
wd: WebDriver实例,用于与浏览器交互。
异常处理:
- TimeoutException: 如果元素在指定时间内未被找到或不可点击。
- NoSuchElementException: 如果元素不存在。
- ElementNotInteractableException: 如果元素存在但不可交互。
"""
try:
# 等待元素在指定时间内可见
element = WebDriverWait(wd, 20).until(EC.visibility_of_element_located(element_locator))
element.clear() # 清除元素的当前值
element.send_keys(value) # 向元素发送指定的键值
except TimeoutException:
# 如果元素在指定时间内未被找到或不可点击,打印超时异常信息
INFO(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
except NoSuchElementException:
# 如果元素不存在,打印相应异常信息
INFO(f"NoSuchElementException: Element {element_locator} not found.")
except ElementNotInteractableException:
# 如果元素不可交互,打印相应异常信息
INFO(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
# 点击按钮函数
def safe_click(element_locator, wd):
"""
对其定位器指定的元素执行安全单击。
此函数尝试以处理潜在异常的方式单击元素。
它等待元素可见并可单击,然后再尝试单击它。
如果该元素在20秒内无法点击,或者它不存在,
或者不可交互,它会捕获相应的异常并记录一条信息性消息。
参数:
-element_locator:要单击的元素的定位器,指定为元组。
-wd:用于查找元素并与之交互的WebDriver实例。
"""
try:
# Wait up to 20 seconds for the element to be visible
element = WebDriverWait(wd, 20).until(EC.visibility_of_element_located(element_locator))
# Attempt to click the element
element.click()
except TimeoutException:
# Log a message if the element is not found or not clickable within 20 seconds
INFO(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
except NoSuchElementException:
# Log a message if the element is not found
INFO(f"NoSuchElementException: Element {element_locator} not found.")
except ElementNotInteractableException:
# Log a message if the element is not interactable
INFO(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
from time import sleep
from selenium.webdriver.common.by import By
# 议题输入和上传议题文件函数
def issue_send_and_upload(wd, issue_num, issue_name):
"""
输入议题名称以及上传议题文件。
参数:
wd: WebDriver实例,用于操作浏览器。
issue_num: 需要上传的议题文件数量。
issue_name: 会议议题的名称。
"""
# 议题文件的路径列表
issue_file_path = [
r"D:\GithubData\自动化\ubains-module-test\预定系统\reports\issue_file\5.164Scan 安全报告.pdf",
r"D:\GithubData\自动化\ubains-module-test\预定系统\reports\issue_file\IdeaTop软件配置&操作说明文档.docx",
r"D:\GithubData\自动化\ubains-module-test\预定系统\reports\issue_file\ideaTop部署配置视频.mp4",
r"D:\GithubData\自动化\ubains-module-test\预定系统\reports\issue_file\IdeaTop软件配置&操作说明文档.docx",
r"D:\GithubData\自动化\ubains-module-test\预定系统\reports\issue_file\议题图片.png"
]
# 打印并输入议题名称
INFO(f"输入议题名称:{issue_name}")
safe_send_keys((By.XPATH, f"(//input[@placeholder='请输入会议议题'])[1]"), f"{issue_name}", wd)
# 点击【上传文件】按钮以开始上传议题文件
INFO("点击【上传文件】按钮")
safe_click((By.XPATH, f"(//div[@class='topicsHandleButton uploadFile'][contains(text(),'上传文件(0)')])[1]"), wd)
sleep(2)
# 遍历每个议题文件进行上传
for i in range(issue_num):
# 检查文件是否存在
if not os.path.exists(issue_file_path[i]):
INFO(f"文件 {issue_file_path[i]} 不存在,跳出函数")
return
# 定位【选择文件】按钮
upload_button = wd.find_element(By.XPATH, '//*[@id="global-uploader-btn"]/input')
sleep(2)
# 选择议题文件上传
upload_button.send_keys(issue_file_path[i])
# 等待文件上传完成
sleep(15)
confirmbutton = ""
confirmbutton = WebDriverWait(wd, 10).until(
EC.visibility_of_element_located((By.XPATH, "//button[contains(@class,'el-button el-button--default el-button--small el-button--primary')]//span[contains(text(),'确定')]"))
)
if confirmbutton is not None:
confirmbutton.click()
else:
# 截取上传完成后的屏幕日志
SELENIUM_LOG_SCREEN(wd, "50%", "Exhibit_Inspect", "Meeting_Message", "添加议题文件")
# 点击【确定】按钮完成上传
safe_click((By.XPATH,
"//div[@aria-label='会议文件上传']//div[@class='el-dialog__footer']//div//span[contains(text(),'确定')]"),
wd)
sleep(2)
# 清除输入框函数
def input_clear(element_locator, wd):
"""
清空输入框中的文本。
该函数通过元素定位器找到指定的输入框,并尝试清空其内容。它使用显式等待来确保元素在尝试清除之前是可见的。
参数:
- element_locator: 用于定位输入框的元素定位器,通常是一个元组,包含定位方法和定位表达式。
- wd: WebDriver实例,用于与浏览器交互。
异常处理:
- TimeoutException: 如果在指定时间内元素不可见,则捕获此异常并打印超时异常消息。
- NoSuchElementException: 如果找不到指定的元素,则捕获此异常并打印未找到元素的消息。
- ElementNotInteractableException: 如果元素不可操作(例如,元素不可见或不可点击),则捕获此异常并打印相应消息。
"""
try:
# 等待元素可见,并在可见后清空输入框。
input_element = WebDriverWait(wd, 20).until(EC.visibility_of_element_located(element_locator))
input_element.clear()
except TimeoutException:
# 如果元素在20秒内不可见,打印超时异常消息。
print(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
except NoSuchElementException:
# 如果找不到元素,打印未找到元素的消息。
print(f"NoSuchElementException: Element {element_locator} not found.")
except ElementNotInteractableException:
# 如果元素不可操作,打印元素不可操作的消息。
print(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
# 获取列表的查询结果文本函数
def elment_get_text(element_locator, wd):
"""
获取页面元素的文本。
该函数通过显式等待的方式,确保页面元素在20秒内可见并获取其文本。
如果在规定时间内元素不可见、不存在或不可交互,则会捕获相应的异常并打印错误信息。
参数:
- element_locator: 用于定位页面元素的元组,通常包含定位方式和定位值。
- wd: WebDriver对象,用于与浏览器进行交互。
返回:
- element_text: 页面元素的文本。如果发生异常,则返回None。
"""
try:
# 使用WebDriverWait等待页面元素在20秒内可见,并获取其文本。
element_text = WebDriverWait(wd, 20).until(EC.visibility_of_element_located(element_locator)).text
return element_text
except TimeoutException:
# 如果超过20秒元素仍未可见,则捕获TimeoutException异常并打印错误信息。
print(f"TimeoutException: Element {element_locator} not found or not clickable within 20 seconds.")
except NoSuchElementException:
# 如果页面上不存在该元素,则捕获NoSuchElementException异常并打印错误信息。
print(f"NoSuchElementException: Element {element_locator} not found.")
except ElementNotInteractableException:
# 如果元素存在但不可交互(例如被遮挡),则捕获ElementNotInteractableException异常并打印错误信息。
print(f"ElementNotInteractableException: Element {element_locator} is not interactable.")
# 读取测试用例xlsx文件中的JSON数据进行数据驱动函数
import openpyxl
def read_xlsx_data(xlsx_file_path, sheet_name=None, case_type=None):
"""
读取XLSX文件中的数据,并将其转换为一个包含字典的列表,每个字典代表一行测试用例数据。
参数:
xlsx_file_path (str): XLSX文件的路径。
sheet_name (str, optional): 工作表的名称。如果未指定,则使用活动工作表。
case_type (str, optional): 测试用例类型,例如 '标准版' 或 'XX项目需求'。如果未指定,则读取所有测试用例。
返回:
list: 包含字典的列表,每个字典包含测试用例的名称和参数。
"""
try:
# 打开XLSX文件
workbook = openpyxl.load_workbook(xlsx_file_path)
except FileNotFoundError:
raise FileNotFoundError(f"文件未找到: {xlsx_file_path}")
except Exception as e:
raise Exception(f"无法打开文件: {e}")
# 选择工作表
if sheet_name:
try:
sheet = workbook[sheet_name]
except KeyError:
raise KeyError(f"工作表未找到: {sheet_name}")
else:
sheet = workbook.active
# 读取表头,从第三行开始
headers = [cell.value for cell in sheet[3]]
# 打印表头列名
# INFO(f"表头列名: {headers}")
# 找到表头中名为 'JSON' 和 '功能类别' 的列索引
try:
json_index = headers.index('JSON')
except ValueError as e:
raise ValueError(f"表头中没有找到所需的列: {e}")
try:
category_index = headers.index('功能类别')
except ValueError as e:
raise ValueError(f"表头中没有找到所需的列: {e}")
ddt_cases = []
# 遍历XLSX文件中的每一行数据,从第四行开始
for row_num, row in enumerate(sheet.iter_rows(min_row=4, values_only=True), start=4):
# 获取 JSON 列的数据
json_data = row[json_index]
# 打印 JSON 数据以进行调试
INFO(f"行 {row_num} 的 JSON 数据: {json_data}")
# 检查 JSON 数据是否为空
if json_data is None or json_data.strip() == "":
# INFO(f"跳过行 {row_num},JSON 数据为空")
continue
# 解析 JSON 字符串
try:
parsed_json = json.loads(json_data)
except json.JSONDecodeError:
raise ValueError(f"行 {row_num} 的 JSON 数据无法解析: {json_data}")
# 获取功能类别
category = row[category_index]
# 检查是否需要过滤测试用例类型
if case_type and category != case_type:
continue
# 将解析后的 JSON 数据添加到列表中
ddt_cases.append(parsed_json)
# 日志记录:XLSX文件已读取
INFO("XLSX文件已读取")
# 返回包含所有测试用例数据的列表
return ddt_cases
import openpyxl
def clear_columns_in_xlsx(xlsx_file_path, sheet_name=None, columns_to_clear=None):
"""
将XLSX文件中指定列的单元格值设置为空。
参数:
xlsx_file_path (str): XLSX文件的路径。
sheet_name (str, optional): 工作表的名称。如果未指定,则使用活动工作表。
columns_to_clear (list, optional): 需要清空的列名列表。如果未指定,则不执行任何操作。
返回:
"""
if not columns_to_clear:
logging.warning("未指定需要清空的列名列表,函数将不执行任何操作。")
return
try:
# 打开XLSX文件
workbook = openpyxl.load_workbook(xlsx_file_path)
except FileNotFoundError:
raise FileNotFoundError(f"文件未找到: {xlsx_file_path}")
except Exception as e:
raise Exception(f"无法打开文件: {e}")
# 选择工作表
if sheet_name:
try:
sheet = workbook[sheet_name]
except KeyError:
raise KeyError(f"工作表未找到: {sheet_name}")
else:
sheet = workbook.active
# 读取表头,从第三行开始
headers = [cell.value for cell in sheet[3]]
# 打印表头列名
logging.info(f"表头列名: {headers}")
# 找到需要清空的列的索引
column_indices_to_clear = [headers.index(column) for column in columns_to_clear if column in headers]
if not column_indices_to_clear:
logging.warning("指定的列名在表头中未找到,函数将不执行任何操作。")
return
# 遍历XLSX文件中的每一行数据,从第四行开始
for row in sheet.iter_rows(min_row=4):
for col_index in column_indices_to_clear:
row[col_index].value = None # 将单元格值设置为空
# 保存修改后的文件
try:
workbook.save(xlsx_file_path)
logging.info(f"文件 {xlsx_file_path} 已保存,指定列的单元格值已清空。")
except Exception as e:
logging.error(f"保存文件时出错: {e}")
# 退出浏览器并释放资源函数
def browser_quit():
"""
退出浏览器并释放资源。
该函数从全局存储中获取浏览器驱动实例,并调用其quit方法来关闭浏览器并释放相关资源。
"""
# 清除浏览器
INFO("清除浏览器")
# 从全局存储中获取浏览器驱动实例
wd = GSTORE['wd']
# 调用浏览器驱动实例的quit方法,关闭浏览器并释放资源
wd.quit()
import os
import glob
import logging
# from datetime import datetime
# 获取最新的HTML报告文件,并拼接网页访问连接函数
def get_latest_report_file(report_dir, base_url):
"""
获取指定目录下最新的HTML报告文件,并返回带有基础URL的完整路径。
:param report_dir: 报告文件所在的目录
:param base_url: 基础URL
:return: 最新的HTML报告文件的完整URL,如果没有找到则返回None
"""
# 记录调用此函数的日志
logging.info("开始调用get_latest_report_file函数获取报告文件")
# 确保报告目录存在
if not os.path.exists(report_dir):
logging.error(f"报告目录 {report_dir} 不存在。")
return None
# 获取指定目录下所有符合模式的HTML报告文件
report_files = glob.glob(os.path.join(report_dir, 'report_*.html'))
# 打印找到的文件列表
logging.debug(f"找到的报告文件: {report_files}")
# 如果没有找到报告文件,记录警告信息并返回None
if not report_files:
logging.warning("在指定目录中没有找到报告文件。")
return None
# 找到最新修改的报告文件
latest_file = max(report_files, key=os.path.getmtime)
# 获取最新报告文件的最后修改时间
last_modified_time = datetime.fromtimestamp(os.path.getmtime(latest_file)).strftime('%Y-%m-%d %H:%M:%S')
# 记录最新报告文件的信息
logging.info(f"最新报告文件: {latest_file}, 最后修改时间: {last_modified_time}")
# 将文件路径转换为相对于基础URL的相对路径
relative_path = os.path.relpath(latest_file, report_dir)
# 生成完整的URL
full_url = f"{base_url}/{relative_path}".replace("\\", "/")
# 返回完整的URL
return full_url
# 钉钉群机器人消息发送函数
def dingding_send_message(latest_report, title, mobile, ding_type):
"""
发送钉钉机器人消息
参考接口文档:https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages#title-7fs-kgs-36x
:param latest_report: 测试报告链接
:param title: 消息标题
:param mobile: 需要@的手机号列表
:param ding_type: 钉钉机器人类型,用于选择不同的 Webhook URL 和密钥
"""
# 记录调用此函数的日志
logging.info("开始构建并发送钉钉机器人消息")
# 钉钉机器人的 Webhook URL 和密钥(正式环境)
# webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=b0eea0bbf097ce3badb4c832d2cd0267a50486f395ec8beca6e2042102bb295b'
# secret = 'SEC928b11659c5fd6476cfa2042edbf56da876abf759289f7e4d3c671fb9a81bf43'
# 钉钉机器人的 Webhook URL 和密钥(测试环境)
if ding_type == '标准版巡检':
webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=7fbf40798cad98b1b5db55ff844ba376b1816e80c5777e6f47ae1d9165dacbb4'
secret = 'SEC610498ed6261ae2df1d071d0880aaa70abf5e67efe47f75a809c1f2314e0dbd6'
elif ding_type == '展厅巡检':
webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=061b6e9b1ae436f356cfda7fe19b6e58e46b62670046a78bd3a4d869118c612d'
secret = 'SEC93212bd880aad638cc0df2b28a72ef4fdf6651cacb8a6a4bc71dcf09705d458d'
# 生成时间戳
timestamp = str(round(time.time() * 1000))
# 生成签名
secret_enc = secret.encode('utf-8')
string_to_sign = f'{timestamp}\n{secret}'
string_to_sign_enc = string_to_sign.encode('utf-8')
hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
# 构建最终的 Webhook URL
params = {
'access_token': webhook_url.split('=')[1],
'timestamp': timestamp,
'sign': sign
}
encoded_params = urllib.parse.urlencode(params)
final_webhook_url = f'https://oapi.dingtalk.com/robot/send?{encoded_params}'
# 记录最终的 Webhook URL
logging.info(f"钉钉机器人Webhook URL: {final_webhook_url}")
# 调用测试结果获取函数
browser_init("标准版预定系统")
wd = GSTORE['wd']
# print(latest_report)
test_result = get_test_result(latest_report, wd)
browser_quit()
# 构建消息体
headers = {'Content-Type': 'application/json'}
message = {
'msgtype': 'link',
'link': {
'title': title,
'messageUrl': latest_report,
'text': f"通过:{test_result['pass_percent']}" + f"失败:{test_result['fail_percent']}" + f"异常:{test_result['exception_percent']}",
},
"at": {
"atMobiles": [mobile],
"isAtAll": True
}
}
try:
# 发送 POST 请求
response = requests.post(final_webhook_url, data=json.dumps(message), headers=headers)
# 检查响应状态码
if response.status_code == 200:
logging.info('消息发送成功!')
logging.info(f'响应内容: {response.text}')
else:
logging.error(f'消息发送失败,状态码: {response.status_code}')
logging.error(f'响应内容: {response.text}')
except requests.exceptions.RequestException as e:
logging.error(f'请求异常: {e}')
# 运行自动化测试函数,并调用获取测试报告链接和钉钉机器人消息发送函数
def run_automation_test(report_title, report_url_prefix, test_case , ding_type):
"""
运行自动化测试并生成报告。
参数:
- report_title: 报告的标题
- report_url_prefix: 报告URL的前缀
- test_case: 测试用例脚本执行的标签名
- ding_type: 钉钉通知的类型,备用参数,当前代码中未使用
"""
# 记录测试开始的日志
logging.info("开始自动化测试...")
# 构建运行测试命令
command = [
'hytest',
'--report_title', report_title,
'--report_url_prefix', report_url_prefix,
'--tag', test_case
]
# 记录将要执行的命令日志
logging.info(f"执行命令: {' '.join(command)}")
try:
# 执行测试命令并获取结果
result = subprocess.run(command, capture_output=True, text=True, check=True)
# 记录命令的标准输出和错误输出
logging.debug(f"命令标准输出: {result.stdout}")
logging.debug(f"命令错误输出: {result.stderr}")
except subprocess.CalledProcessError as e:
# 处理子进程调用失败的异常
logging.error(f"命令执行失败,返回码 {e.returncode}: {e.output}")
except OSError as e:
# 处理操作系统相关的异常
logging.error(f"发生操作系统错误: {e}")
finally:
# 无论测试是否成功,都记录测试结束的日志
logging.info("自动化测试完成。")
# 调用回调函数处理后续操作
get_reportfile_send_dingding(f"{report_title}", report_url_prefix, ding_type)
# 定义一个函数,用于获取最新的报告文件,并返回其URL,并调用钉钉消息发送函数
def get_reportfile_send_dingding(report_title, report_url_prefix, ding_type):
"""
获取最新的报告文件并通过钉钉发送报告链接。
参数:
report_title (str): 报告的标题。
report_url_prefix (str): 报告URL的前缀。
ding_type (str): 钉钉消息的类型。
返回:
"""
# print(GSTORE['case_pass'])
try:
# 获取报告文件所在的目录
report_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..','reports')
# 获取基础URLs
base_url = report_url_prefix
# 获取最新的报告文件
latest_report = get_latest_report_file(report_dir, base_url)
# 如果找到了最新的报告文件,则发送报告链接到钉钉
if latest_report:
logging.info(f"最新报告文件URL: {latest_report}")
try:
# 记录调用钉钉消息通知函数的日志
logging.info("开始调用钉钉消息通知函数")
# 调用钉钉发送消息接口进行推送测试报告链接
dingding_send_message(latest_report, report_title, "13724387318", ding_type)
# 记录钉钉消息通知函数调用成功的日志
logging.info("钉钉消息通知函数调用成功")
except Exception as e:
# 记录钉钉消息通知函数调用失败的日志
logging.error(f"钉钉消息通知函数调用失败: {e}")
else:
# 记录没有找到报告文件的日志
logging.warning("没有找到报告文件以发送。")
except subprocess.CalledProcessError as e:
# 处理子进程调用失败的异常
logging.error(f"命令执行失败,返回码 {e.returncode}: {e.output}")
except OSError as e:
# 处理操作系统相关的异常
logging.error(f"发生操作系统错误: {e}")
finally:
# 无论是否成功,都记录测试结束的日志
logging.info("自动化测试完成。")
# 获取测试报告通过率等参数的函数
import logging
import re
from selenium.webdriver.common.by import By
def get_test_result(latest_report, wd):
"""
获取测试结果页面的通过率、失败率和异常率
:param latest_report: 测试结果页面的URL
:param wd: WebDriver实例,用于访问和操作网页
:return: 包含通过率、失败率和异常率的字典
"""
# 初始化测试结果字典
test_result = {
"pass_percent": "",
"fail_percent": "",
"exception_percent": ""
}
# 访问测试结果页面
wd.get(latest_report)
sleep(5)
# 点击简略显示
safe_click((By.XPATH,"//div[@id='display_mode']"), wd)
sleep(5)
# 定义一个函数来获取和解析百分比
def get_percentage(selector, wd):
text = elment_get_text(selector, wd)
logging.info(f"获取的文本:{text}")
match = re.search(r'(\d+(\.\d+)?)%', text)
if match:
return match.group(0)
else:
logging.error(f"未找到百分比匹配项,文本内容: {text}")
return "0"
# 获取通过率
pass_percent = get_percentage((By.CSS_SELECTOR, "div[class='result_barchart'] div:nth-child(1) span:nth-child(1)"), wd)
test_result["pass_percent"] = pass_percent
# 获取失败率
fail_percent = get_percentage(
(By.CSS_SELECTOR, "body > div.main_section > div.result > div > div:nth-child(2) > span"), wd)
test_result["fail_percent"] = fail_percent
# 获取异常率
exception_percent = get_percentage(
(By.CSS_SELECTOR, "body > div.main_section > div.result > div > div:nth-child(3) > span"), wd)
test_result["exception_percent"] = exception_percent
# 输出test_result
logging.info(test_result)
print(test_result)
sleep(5)
# 返回测试结果字典
return test_result
# if __name__ == "__main__":
# browser_init("展厅预定巡检")
# wd = GSTORE['wd']
# test_result = get_test_result("http://nat.ubainsyun.com:31134/report_20250217_094401.html",wd)
# print(test_result)
# 字符串转换枚举类型函数
def get_by_enum(type_str):
"""
将字符串类型的定位器类型转换为 selenium.webdriver.common.by.By 枚举类型。
参数:
type_str (str): 定位器类型字符串,例如 'XPATH'。
返回:
selenium.webdriver.common.by.By: 对应的 By 枚举类型。
"""
# 将输入的定位器类型字符串转换为大写,以匹配 By 枚举类型的命名
type_str = type_str.upper()
# 根据输入的字符串类型返回对应的 By 枚举类型
if type_str == 'XPATH':
return By.XPATH
elif type_str == 'ID':
return By.ID
elif type_str == 'NAME':
return By.NAME
elif type_str == 'CLASS_NAME':
return By.CLASS_NAME
elif type_str == 'CSS_SELECTOR':
return By.CSS_SELECTOR
elif type_str == 'TAG_NAME':
return By.TAG_NAME
elif type_str == 'LINK_TEXT':
return By.LINK_TEXT
elif type_str == 'PARTIAL_LINK_TEXT':
return By.PARTIAL_LINK_TEXT
else:
# 如果输入的定位器类型字符串不匹配任何已知的 By 枚举类型,抛出 ValueError 异常
raise ValueError(f"未知的定位器类型: {type_str}")
# xlsx文件写入函数
import os
import openpyxl
import logging
from openpyxl.drawing.image import Image
from openpyxl.utils import get_column_letter
def write_xlsx_data(xlsx_file_path, sheet_name, function_number, test_result, log_screenshot):
"""
在XLSX文件的指定行中填充测试结果和日志截图。
参数:
xlsx_file_path (str): XLSX文件的路径。
sheet_name (str): 工作表的名称。
function_number (str): 功能编号,用于匹配行。
test_result (str): 测试结果。
log_screenshot (str): 日志截图路径。
"""
try:
# 打开XLSX文件
workbook = openpyxl.load_workbook(xlsx_file_path)
except FileNotFoundError:
raise FileNotFoundError(f"文件未找到: {xlsx_file_path}")
except Exception as e:
raise Exception(f"无法打开文件: {e}")
# 选择工作表
if sheet_name:
try:
sheet = workbook[sheet_name]
except KeyError:
raise KeyError(f"工作表未找到: {sheet_name}")
else:
sheet = workbook.active
# 读取表头,从第三行开始
headers = [cell.value for cell in sheet[3]]
# 打印表头列名
logging.info(f"表头列名: {headers}")
# 找到表头中名为 '功能编号'、'测试结果' 和 '日志截图' 的列索引
try:
function_number_index = headers.index("功能编号")
except ValueError as e:
raise ValueError(f"表头中没有找到 '功能编号' 列: {e}")
try:
test_result_index = headers.index("测试结果")
except ValueError as e:
raise ValueError(f"表头中没有找到 '测试结果' 列: {e}")
try:
log_screenshot_index = headers.index("日志截图")
except ValueError as e:
raise ValueError(f"表头中没有找到 '日志截图' 列: {e}")
# 遍历数据行,找到与给定功能编号匹配的行
for row in sheet.iter_rows(min_row=4, values_only=False):
if row[function_number_index].value == function_number:
# 填充测试结果
row[test_result_index].value = test_result
# 插入日志截图
if log_screenshot:
img = Image(log_screenshot)
# 获取单元格的宽度和高度
cell = row[log_screenshot_index]
cell_width = sheet.column_dimensions[get_column_letter(cell.column)].width
cell_height = sheet.row_dimensions[cell.row].height
# 调整图片大小以适应单元格
img.width = cell_width * 7 # 7 是一个经验值,根据需要调整
img.height = cell_height * 1.5 # 1.5 是一个经验值,根据需要调整
# 设置图片位置
sheet.add_image(img, cell.coordinate)
break
else:
raise ValueError(f"未找到功能编号为 {function_number} 的行")
# 保存修改后的文件
try:
workbook.save(xlsx_file_path)
logging.info(f"文件 {xlsx_file_path} 已保存,数据已写入功能编号为 {function_number} 的行。")
except Exception as e:
logging.error(f"保存文件时出错: {e}")
# 预定服务日志获取函数
import paramiko
import time
def get_remote_log_with_paramiko(host, username, private_key_path, passphrase, log_path, num_lines=100, timeout=30):
"""
使用 Paramiko 获取远程服务器的日志文件内容.
Args:
host (str): 服务器 IP 地址或域名.
username (str): 用户名.
private_key_path (str): SSH 私钥文件路径.
passphrase (str): 私钥文件的 passphrase.
log_path (str): 日志文件路径.
num_lines (int): 要获取的日志行数 (默认 100).
timeout (int): SSH 命令执行的超时时间(秒).
Returns:
str: 获取的日志内容,如果出错返回 None.
"""
try:
private_key = paramiko.RSAKey.from_private_key_file(private_key_path, password=passphrase)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, username=username, pkey=private_key, timeout=timeout)
command = f"tail -n {num_lines} {log_path}"
print(f"Executing command: {command}")
stdin, stdout, stderr = client.exec_command(command, timeout=timeout)
error = stderr.read().decode('utf-8')
if error:
print(f"Error: {error}")
return None
output = stdout.read().decode('utf-8')
print("Successfully retrieved log content.")
return output
except Exception as e:
print(f"An error occurred: {e}")
return None
finally:
client.close()
# if __name__ == "__main__":
# host = "192.168.5.218"
# username = "root"
# private_key_path = "C:\\Users\\29194\\.ssh\\id_rsa" # 替换为你的私钥文件路径
# passphrase = "Ubains@123" # 替换为你的 passphrase
# log_path = "/var/www/java/api-java-meeting2.0/logs/ubains-INFO-AND-ERROR.log"
# log_content = get_remote_log_with_paramiko(host, username, private_key_path, passphrase, log_path)
#
# if log_content:
# print(log_content)
# else:
# print("Failed to retrieve log content.")
\ No newline at end of file
1. 2025-04-14
- 补充预定配套件的项目目录,并补充公用方法类,以及调试门口屏的功能。
\ No newline at end of file
import sys
import os
from venv import logger
# 获取当前脚本的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
# 构建预定系统的绝对路径
预定配套件_path = os.path.abspath(os.path.join(current_dir, '..','..','..','..'))
# 添加路径
sys.path.append(预定配套件_path)
# 导入模块
try:
from 预定配套件.Base.app_base import *
from 预定配套件.Base.base import *
except ModuleNotFoundError as e:
print(f"ModuleNotFoundError: {e}")
print("尝试使用绝对路径导入")
from 预定配套件.Base.app_base import *
from 预定配套件.Base.base import *
def suite_setup():
STEP(1, "初始化设备adb连接")
device_ip = '192.168.1.160'
app_init(device_ip)
# 检查设备adb连接状态
CHECK_POINT("设备1的adb连接初始化检测", app_init(device_ip) == True)
browser_init("展厅预定巡检")
wd = GSTORE['wd']
def suite_teardown():
device_ip = '192.168.1.160'
app_quit(device_ip)
browser_quit()
\ No newline at end of file
from appium.webdriver.common.appiumby import AppiumBy
from 预定系统.Base.app_base import *
import logging
from time import sleep
from hytest import *
# 配置日志记录
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
class DoorScreen:
"""
执行指令:
1.cd 预定系统
2.
"""
tags = ['门口屏测试']
def teststeps(self):
app_drive = None
wd = GSTORE['wd']
try:
app_drive = app_setup_driver("Android", "11", "门口屏测试", "com.ubains.local.gviewer", "com.ubains.ub.gview.SplashActivity","192.168.1.160:5555")
app_drive.implicitly_wait(20) # 设置缺省等待时间
except Exception as e:
logging.error(f"发生错误: {e}", exc_info=True)
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title>测试报告</title>
<meta charset="UTF-8">
<style>body {
font-family: consolas, Verdana, sans-serif;
font-size: 1.2em;
color: #696e71;
display: grid;
grid-template-columns: 1fr 5rem;
}
.main_section {
width: 90%;
margin: 0 auto;
}
#float_menu{
position:fixed;
top:0;
right:0;
text-align: center;
}
#float_menu .menu-item {
cursor: pointer;
padding: .5em;
margin: .5em 0;
color: #c08580;
background-color: #f8f0ef;
font-size: 1.2em;
}
.result{
display: flex;
}
.result_table{
border-collapse: collapse;
border: 1px solid #f0e0e5;
width: 30em;
text-align: center;
font-size: 1.0em;
}
.result_table td{
border: 1px solid #f0e0e5;
padding: .3em;
}
.result_barchart{
width: 30em;
margin: 0 5em 0 5em;
}
.barchar_item{
margin: 2.5rem 0;
}
.barchart_barbox {
margin: 0.5em 0;
width: 100%;
background-color: #fff;
border: 1px solid #86c2dd;
border-radius: .2em;
}
.barchart_bar {
text-align: right;
height: 1.2rem;
}
.h3_button {
margin: 1.5em;
cursor: pointer;
color: #03a9f4;
}
.info
{
white-space:pre-wrap;
margin: .8em 1.5em;
}
.error-info
{
color: #a64747
}
.suite_dir {
margin: 1em .2em;
padding: .3em;
/* background-color: #dfeff6; */
border: 1px solid #bcd8e4;
}
.suite_file {
margin: 1em .2em;
padding: .3em;
border: 1px solid #bcd8e4;
}
.case {
margin: 1em .2em;
/* padding: .3em; */
border: 1px solid #e7d4d4;
}
.case_class_path{
margin: 0em 1em;
}
.folder_header {
padding: .2em .7em;
background-color: #fffaf9;
cursor: pointer;
}
.setup{
margin: .2em;
/* padding: .3em; */
/* border: 1px solid #e7d4d4; */
}
.teardown{
margin: .2em;
/* padding: .3em;*/
/* border: 1px solid #e7d4d4; */
}
.test_steps{
margin: .2em;
padding: .3em;
/* border: 1px solid #e7d4d4; */
}
.label {
display: inline-block;
padding: .1em .5em;
font-size: .88em;
letter-spacing: 1px;
white-space: nowrap;
color: #0d6ebc;
border-radius: .2em;
min-width: 5em;
margin-right: 2em;
font-family: consolas;
}
/* .suite_setup .label{
color: #219e26 ;
}
.suite_teardown .label{
color: #219e26;
} */
/* .case.pass .casename{
color: #329132 ;
} */
.case.pass .caselabel{
color: white;
background-color: #3b9e3f;
}
/* .case.fail .casename{
color: #a64747;
} */
.case.fail .caselabel{
color: white;
background-color: #a64747;
}
/* .case.abort .casename{
color: #953ab7;
} */
.case.abort .caselabel{
color: white;
background-color: #9c27b0;
}
.case_step {
margin: .8em;
}
.checkpoint_pass {
margin: .8em;
}
.checkpoint_fail {
margin: .8em;
}
.case_step .tag{
color: #2196f3;;
margin: .3em 1em .3em 0;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_pass .tag{
color: #009806;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_fail .tag{
color: #9c2020;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.screenshot {
border: 1px solid #86c2dd;
}
.executetime {
float: right;
}
/* 模态框内容 */
.modal-content {
margin: auto;
display: block;
width: 95%;
max-width: 700px;
max-height: 80vh; /* 设置最大高度为视口高度的80% */
object-fit: contain; /* 保持图片的宽高比 */
zoom: 3;
}
/* 模态框 */
.modal {
display: none; /* 隐藏 */
position: fixed; /* 固定位置 */
z-index: 1; /* 坐在顶部 */
padding-top: 40px; /* 在图片上方添加一些内边距 */
left: 0;
top: 0;
width: 100%; /* 宽度 */
height: 100%; /* 高度 */
overflow: auto; /* 启用滚动 */
background-color: rgb(0,0,0); /* 背景颜色 */
background-color: rgba(0,0,0,0.9); /* 黑色背景半透明 */
}
/* 关闭按钮 */
.close {
position: absolute; /* 定义元素的定位方式为绝对定位 */
top: 10px; /* 距离最近的已定位祖先元素顶部15像素 */
right: 30px; /* 距离最近的已定位祖先元素右侧35像素 */
color: #f1f1f1; /* 文本颜色为浅灰色 */
font-size: 15px; /* 字体大小为40像素 */
font-weight: bold; /* 字体加粗 */
transition: 0.3s; /* 过渡效果,0.3秒内完成 */
}
.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
</style>
<script type="text/javascript">var FOLDER_ALL_CASES = false; // 是否为精简模式的标记
var ERROR_INFOS = []; // 错误信息列表
var current_error_idx = -1;
// 页面加载后执行的函数
window.addEventListener("load", function(){
// 所有 .folder_header 添加点击事件处理
let folderHeaderEles = document.querySelectorAll(".folder_header");
folderHeaderEles.forEach(function(ele) {
ele.addEventListener("click", function(event) {
let fb = event.target.closest('.folder_header').nextElementSibling;
fb.style.display = fb.style.display === 'none' ? 'block' : 'none';
});
});
// 找到所有的错误信息对象
ERROR_INFOS = document.querySelectorAll(".error-info");
// 获取所有图片元素
let images = document.querySelectorAll('.modal-image');
// 获取模态框元素
let modal = document.getElementById("imageModal");
// 获取模态框中的图片元素
let modalImg = document.getElementById("img01");
// 获取关闭按钮元素
let span = document.getElementsByClassName("close")[0];
// 为每个图片添加点击事件监听器
images.forEach(function(img) {
img.addEventListener("click", function() {
modal.style.display = "block"; // 显示模态框
modalImg.src = this.src; // 设置模态框中的图片为点击的图片
});
});
// 当点击关闭按钮时,隐藏模态框
span.onclick = function() {
modal.style.display = "none";
};
// 当点击模态框外区域时,隐藏模态框
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
});
function toggle_folder_all_cases(){
let eles = document.querySelectorAll(".folder_body");
FOLDER_ALL_CASES = !FOLDER_ALL_CASES;
document.getElementById('display_mode').innerHTML = FOLDER_ALL_CASES ? "Detail" : "Summary";
for (const ele of eles){
ele.style.display = FOLDER_ALL_CASES ? "none" : "block";
}
}
function previous_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx -= 1;
if (current_error_idx < 0)
current_error_idx = 0;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
function next_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx += 1;
if (current_error_idx > ERROR_INFOS.length - 1)
current_error_idx = ERROR_INFOS.length - 1;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
</script>
</head>
<body>
<div class="modal" id="imageModal">
<div>
<img class="modal-content" id="img01">
<span class="close">关闭</span>
</div>
</div>
<div class="main_section">
<h1 style="font-family: auto">测试报告</h1>
<h3>统计结果</h3>
<div class="result">
<table class="result_table">
<tbody>
<tr>
<td>hytest 版本</td>
<td>0.8.12</td>
</tr>
<tr>
<td>开始时间</td>
<td>2025/04/14 18:14:41</td>
</tr>
<tr>
<td>结束时间</td>
<td>2025/04/14 18:14:48</td>
</tr>
<tr>
<td>耗时</td>
<td>6.849 秒</td>
</tr>
<tr>
<td>预备执行用例数量</td>
<td>1</td>
</tr>
<tr>
<td>实际执用例行数量</td>
<td>1</td>
</tr>
<tr>
<td>通过</td>
<td>1</td>
</tr>
<tr>
<td>失败</td>
<td style="">0</td>
</tr>
<tr>
<td>异常</td>
<td style="">0</td>
</tr>
<tr>
<td>阻塞</td>
<td style="">0</td>
</tr>
<tr>
<td>套件初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>套件清除 失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例清除 失败</td>
<td style="">0</td>
</tr>
</tbody>
</table>
<div class="result_barchart">
<div class="barchar_item">
<span>用例通过 100% : 1 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 100.0%; background-color: #04AA6D;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例失败 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #bb4069;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例异常 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #9c27b0;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例阻塞 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #dcbdbd;"></div>
</div>
</div>
</div>
</div>
<div style="margin-top:2em">
<h3 style="display:inline">执行日志</h3>
</div>
<div class="exec_log">
<div class="suite_dir" id="suite_dir cases\测试目录\">
<div>
<span class="label">进入目录</span>
<span>cases\测试目录\</span>
</div>
<div class="suite_setup setup" id="suite_setup setup cases\测试目录\">
<div class="folder_header">
<span class="label">套件初始化</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:14:41</span>
</div>
<div class="folder_body">
<div class="case_step">
<span class="tag">步骤 #1</span>
<span>初始化设备adb连接</span>
</div>
<div class="info">'----------' 正在初始化ADB连接 '----------'</div>
<div class="info">尝试连接到设备: 192.168.5.112:5555</div>
<div class="info">设备 192.168.5.112:5555 已连接并可用</div>
<div class="checkpoint_pass">
<span class="tag">检查点 PASS</span>
<span>设备1的adb连接初始化检测</span>
</div>
<div class="info">'----------' 正在初始化浏览器 '----------'</div>
<div class="info">'----------' 浏览器初始化完成 '----------'</div>
</div>
</div>
</div>
<div class="suite_file" id="suite_file cases\测试目录\门口屏测试.py">
<div>
<span class="label">进入文件</span>
<span>cases\测试目录\门口屏测试.py</span>
</div>
<div class="case pass" id="case_00000001">
<div class="folder_header">
<span class="label caselabel">用例 PASS</span>
<span class="casename">DoorScreen</span>
<span class="executetime">2025-04-14 18:14:43</span>
</div>
<div class="folder_body">
<span class="case_class_path">cases\测试目录\门口屏测试.py::DoorScreen</span>
<div class="test_steps" id="test_steps DoorScreen">
<span class="label">测试步骤</span>
</div>
</div>
</div>
<div class="suite_teardown teardown" id="suite_teardown teardown cases\测试目录\">
<div class="folder_header">
<span class="label">套件清除</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:14:45</span>
</div>
<div class="folder_body">
<div class="info">ADB 连接已断开: 192.168.5.112:5555</div>
<div class="info">清除浏览器</div>
</div>
</div>
</div>
</div>
</div>
<div id="float_menu">
<div class="menu-item" onclick="document.querySelector(&quot;body&quot;).scrollIntoView()">页首</div>
<div class="menu-item" onclick="window.open(&quot;http://www.byhy.net/tut/auto/hytest/01&quot;, &quot;_blank&quot;); ">帮助</div>
<div class="menu-item" id="display_mode" onclick="toggle_folder_all_cases()">Summary</div>
<div class="error_jumper" display="none">
<div class="menu-item" onclick="previous_error()" title="上一个错误"></div>
<div class="menu-item" onclick="next_error()" title="下一个错误"></div>
</div>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title>测试报告</title>
<meta charset="UTF-8">
<style>body {
font-family: consolas, Verdana, sans-serif;
font-size: 1.2em;
color: #696e71;
display: grid;
grid-template-columns: 1fr 5rem;
}
.main_section {
width: 90%;
margin: 0 auto;
}
#float_menu{
position:fixed;
top:0;
right:0;
text-align: center;
}
#float_menu .menu-item {
cursor: pointer;
padding: .5em;
margin: .5em 0;
color: #c08580;
background-color: #f8f0ef;
font-size: 1.2em;
}
.result{
display: flex;
}
.result_table{
border-collapse: collapse;
border: 1px solid #f0e0e5;
width: 30em;
text-align: center;
font-size: 1.0em;
}
.result_table td{
border: 1px solid #f0e0e5;
padding: .3em;
}
.result_barchart{
width: 30em;
margin: 0 5em 0 5em;
}
.barchar_item{
margin: 2.5rem 0;
}
.barchart_barbox {
margin: 0.5em 0;
width: 100%;
background-color: #fff;
border: 1px solid #86c2dd;
border-radius: .2em;
}
.barchart_bar {
text-align: right;
height: 1.2rem;
}
.h3_button {
margin: 1.5em;
cursor: pointer;
color: #03a9f4;
}
.info
{
white-space:pre-wrap;
margin: .8em 1.5em;
}
.error-info
{
color: #a64747
}
.suite_dir {
margin: 1em .2em;
padding: .3em;
/* background-color: #dfeff6; */
border: 1px solid #bcd8e4;
}
.suite_file {
margin: 1em .2em;
padding: .3em;
border: 1px solid #bcd8e4;
}
.case {
margin: 1em .2em;
/* padding: .3em; */
border: 1px solid #e7d4d4;
}
.case_class_path{
margin: 0em 1em;
}
.folder_header {
padding: .2em .7em;
background-color: #fffaf9;
cursor: pointer;
}
.setup{
margin: .2em;
/* padding: .3em; */
/* border: 1px solid #e7d4d4; */
}
.teardown{
margin: .2em;
/* padding: .3em;*/
/* border: 1px solid #e7d4d4; */
}
.test_steps{
margin: .2em;
padding: .3em;
/* border: 1px solid #e7d4d4; */
}
.label {
display: inline-block;
padding: .1em .5em;
font-size: .88em;
letter-spacing: 1px;
white-space: nowrap;
color: #0d6ebc;
border-radius: .2em;
min-width: 5em;
margin-right: 2em;
font-family: consolas;
}
/* .suite_setup .label{
color: #219e26 ;
}
.suite_teardown .label{
color: #219e26;
} */
/* .case.pass .casename{
color: #329132 ;
} */
.case.pass .caselabel{
color: white;
background-color: #3b9e3f;
}
/* .case.fail .casename{
color: #a64747;
} */
.case.fail .caselabel{
color: white;
background-color: #a64747;
}
/* .case.abort .casename{
color: #953ab7;
} */
.case.abort .caselabel{
color: white;
background-color: #9c27b0;
}
.case_step {
margin: .8em;
}
.checkpoint_pass {
margin: .8em;
}
.checkpoint_fail {
margin: .8em;
}
.case_step .tag{
color: #2196f3;;
margin: .3em 1em .3em 0;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_pass .tag{
color: #009806;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_fail .tag{
color: #9c2020;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.screenshot {
border: 1px solid #86c2dd;
}
.executetime {
float: right;
}
/* 模态框内容 */
.modal-content {
margin: auto;
display: block;
width: 95%;
max-width: 700px;
max-height: 80vh; /* 设置最大高度为视口高度的80% */
object-fit: contain; /* 保持图片的宽高比 */
zoom: 3;
}
/* 模态框 */
.modal {
display: none; /* 隐藏 */
position: fixed; /* 固定位置 */
z-index: 1; /* 坐在顶部 */
padding-top: 40px; /* 在图片上方添加一些内边距 */
left: 0;
top: 0;
width: 100%; /* 宽度 */
height: 100%; /* 高度 */
overflow: auto; /* 启用滚动 */
background-color: rgb(0,0,0); /* 背景颜色 */
background-color: rgba(0,0,0,0.9); /* 黑色背景半透明 */
}
/* 关闭按钮 */
.close {
position: absolute; /* 定义元素的定位方式为绝对定位 */
top: 10px; /* 距离最近的已定位祖先元素顶部15像素 */
right: 30px; /* 距离最近的已定位祖先元素右侧35像素 */
color: #f1f1f1; /* 文本颜色为浅灰色 */
font-size: 15px; /* 字体大小为40像素 */
font-weight: bold; /* 字体加粗 */
transition: 0.3s; /* 过渡效果,0.3秒内完成 */
}
.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
</style>
<script type="text/javascript">var FOLDER_ALL_CASES = false; // 是否为精简模式的标记
var ERROR_INFOS = []; // 错误信息列表
var current_error_idx = -1;
// 页面加载后执行的函数
window.addEventListener("load", function(){
// 所有 .folder_header 添加点击事件处理
let folderHeaderEles = document.querySelectorAll(".folder_header");
folderHeaderEles.forEach(function(ele) {
ele.addEventListener("click", function(event) {
let fb = event.target.closest('.folder_header').nextElementSibling;
fb.style.display = fb.style.display === 'none' ? 'block' : 'none';
});
});
// 找到所有的错误信息对象
ERROR_INFOS = document.querySelectorAll(".error-info");
// 获取所有图片元素
let images = document.querySelectorAll('.modal-image');
// 获取模态框元素
let modal = document.getElementById("imageModal");
// 获取模态框中的图片元素
let modalImg = document.getElementById("img01");
// 获取关闭按钮元素
let span = document.getElementsByClassName("close")[0];
// 为每个图片添加点击事件监听器
images.forEach(function(img) {
img.addEventListener("click", function() {
modal.style.display = "block"; // 显示模态框
modalImg.src = this.src; // 设置模态框中的图片为点击的图片
});
});
// 当点击关闭按钮时,隐藏模态框
span.onclick = function() {
modal.style.display = "none";
};
// 当点击模态框外区域时,隐藏模态框
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
});
function toggle_folder_all_cases(){
let eles = document.querySelectorAll(".folder_body");
FOLDER_ALL_CASES = !FOLDER_ALL_CASES;
document.getElementById('display_mode').innerHTML = FOLDER_ALL_CASES ? "Detail" : "Summary";
for (const ele of eles){
ele.style.display = FOLDER_ALL_CASES ? "none" : "block";
}
}
function previous_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx -= 1;
if (current_error_idx < 0)
current_error_idx = 0;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
function next_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx += 1;
if (current_error_idx > ERROR_INFOS.length - 1)
current_error_idx = ERROR_INFOS.length - 1;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
</script>
</head>
<body>
<div class="modal" id="imageModal">
<div>
<img class="modal-content" id="img01">
<span class="close">关闭</span>
</div>
</div>
<div class="main_section">
<h1 style="font-family: auto">测试报告</h1>
<h3>统计结果</h3>
<div class="result">
<table class="result_table">
<tbody>
<tr>
<td>hytest 版本</td>
<td>0.8.12</td>
</tr>
<tr>
<td>开始时间</td>
<td>2025/04/14 18:15:13</td>
</tr>
<tr>
<td>结束时间</td>
<td>2025/04/14 18:15:21</td>
</tr>
<tr>
<td>耗时</td>
<td>7.758 秒</td>
</tr>
<tr>
<td>预备执行用例数量</td>
<td>1</td>
</tr>
<tr>
<td>实际执用例行数量</td>
<td>1</td>
</tr>
<tr>
<td>通过</td>
<td>1</td>
</tr>
<tr>
<td>失败</td>
<td style="">0</td>
</tr>
<tr>
<td>异常</td>
<td style="">0</td>
</tr>
<tr>
<td>阻塞</td>
<td style="">0</td>
</tr>
<tr>
<td>套件初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>套件清除 失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例清除 失败</td>
<td style="">0</td>
</tr>
</tbody>
</table>
<div class="result_barchart">
<div class="barchar_item">
<span>用例通过 100% : 1 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 100.0%; background-color: #04AA6D;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例失败 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #bb4069;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例异常 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #9c27b0;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例阻塞 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #dcbdbd;"></div>
</div>
</div>
</div>
</div>
<div style="margin-top:2em">
<h3 style="display:inline">执行日志</h3>
</div>
<div class="exec_log">
<div class="suite_dir" id="suite_dir cases\测试目录\">
<div>
<span class="label">进入目录</span>
<span>cases\测试目录\</span>
</div>
<div class="suite_setup setup" id="suite_setup setup cases\测试目录\">
<div class="folder_header">
<span class="label">套件初始化</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:15:13</span>
</div>
<div class="folder_body">
<div class="case_step">
<span class="tag">步骤 #1</span>
<span>初始化设备adb连接</span>
</div>
<div class="info">'----------' 正在初始化ADB连接 '----------'</div>
<div class="info">尝试连接到设备: 192.168.1.160:5555</div>
<div class="info">设备 192.168.1.160:5555 已连接并可用</div>
<div class="checkpoint_pass">
<span class="tag">检查点 PASS</span>
<span>设备1的adb连接初始化检测</span>
</div>
<div class="info">'----------' 正在初始化浏览器 '----------'</div>
<div class="info">'----------' 浏览器初始化完成 '----------'</div>
</div>
</div>
</div>
<div class="suite_file" id="suite_file cases\测试目录\门口屏测试.py">
<div>
<span class="label">进入文件</span>
<span>cases\测试目录\门口屏测试.py</span>
</div>
<div class="case pass" id="case_00000001">
<div class="folder_header">
<span class="label caselabel">用例 PASS</span>
<span class="casename">DoorScreen</span>
<span class="executetime">2025-04-14 18:15:15</span>
</div>
<div class="folder_body">
<span class="case_class_path">cases\测试目录\门口屏测试.py::DoorScreen</span>
<div class="test_steps" id="test_steps DoorScreen">
<span class="label">测试步骤</span>
</div>
</div>
</div>
<div class="suite_teardown teardown" id="suite_teardown teardown cases\测试目录\">
<div class="folder_header">
<span class="label">套件清除</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:15:17</span>
</div>
<div class="folder_body">
<div class="info">ADB 连接已断开: 192.168.1.160:5555</div>
<div class="info">清除浏览器</div>
</div>
</div>
</div>
</div>
</div>
<div id="float_menu">
<div class="menu-item" onclick="document.querySelector(&quot;body&quot;).scrollIntoView()">页首</div>
<div class="menu-item" onclick="window.open(&quot;http://www.byhy.net/tut/auto/hytest/01&quot;, &quot;_blank&quot;); ">帮助</div>
<div class="menu-item" id="display_mode" onclick="toggle_folder_all_cases()">Summary</div>
<div class="error_jumper" display="none">
<div class="menu-item" onclick="previous_error()" title="上一个错误"></div>
<div class="menu-item" onclick="next_error()" title="下一个错误"></div>
</div>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title>测试报告</title>
<meta charset="UTF-8">
<style>body {
font-family: consolas, Verdana, sans-serif;
font-size: 1.2em;
color: #696e71;
display: grid;
grid-template-columns: 1fr 5rem;
}
.main_section {
width: 90%;
margin: 0 auto;
}
#float_menu{
position:fixed;
top:0;
right:0;
text-align: center;
}
#float_menu .menu-item {
cursor: pointer;
padding: .5em;
margin: .5em 0;
color: #c08580;
background-color: #f8f0ef;
font-size: 1.2em;
}
.result{
display: flex;
}
.result_table{
border-collapse: collapse;
border: 1px solid #f0e0e5;
width: 30em;
text-align: center;
font-size: 1.0em;
}
.result_table td{
border: 1px solid #f0e0e5;
padding: .3em;
}
.result_barchart{
width: 30em;
margin: 0 5em 0 5em;
}
.barchar_item{
margin: 2.5rem 0;
}
.barchart_barbox {
margin: 0.5em 0;
width: 100%;
background-color: #fff;
border: 1px solid #86c2dd;
border-radius: .2em;
}
.barchart_bar {
text-align: right;
height: 1.2rem;
}
.h3_button {
margin: 1.5em;
cursor: pointer;
color: #03a9f4;
}
.info
{
white-space:pre-wrap;
margin: .8em 1.5em;
}
.error-info
{
color: #a64747
}
.suite_dir {
margin: 1em .2em;
padding: .3em;
/* background-color: #dfeff6; */
border: 1px solid #bcd8e4;
}
.suite_file {
margin: 1em .2em;
padding: .3em;
border: 1px solid #bcd8e4;
}
.case {
margin: 1em .2em;
/* padding: .3em; */
border: 1px solid #e7d4d4;
}
.case_class_path{
margin: 0em 1em;
}
.folder_header {
padding: .2em .7em;
background-color: #fffaf9;
cursor: pointer;
}
.setup{
margin: .2em;
/* padding: .3em; */
/* border: 1px solid #e7d4d4; */
}
.teardown{
margin: .2em;
/* padding: .3em;*/
/* border: 1px solid #e7d4d4; */
}
.test_steps{
margin: .2em;
padding: .3em;
/* border: 1px solid #e7d4d4; */
}
.label {
display: inline-block;
padding: .1em .5em;
font-size: .88em;
letter-spacing: 1px;
white-space: nowrap;
color: #0d6ebc;
border-radius: .2em;
min-width: 5em;
margin-right: 2em;
font-family: consolas;
}
/* .suite_setup .label{
color: #219e26 ;
}
.suite_teardown .label{
color: #219e26;
} */
/* .case.pass .casename{
color: #329132 ;
} */
.case.pass .caselabel{
color: white;
background-color: #3b9e3f;
}
/* .case.fail .casename{
color: #a64747;
} */
.case.fail .caselabel{
color: white;
background-color: #a64747;
}
/* .case.abort .casename{
color: #953ab7;
} */
.case.abort .caselabel{
color: white;
background-color: #9c27b0;
}
.case_step {
margin: .8em;
}
.checkpoint_pass {
margin: .8em;
}
.checkpoint_fail {
margin: .8em;
}
.case_step .tag{
color: #2196f3;;
margin: .3em 1em .3em 0;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_pass .tag{
color: #009806;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_fail .tag{
color: #9c2020;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.screenshot {
border: 1px solid #86c2dd;
}
.executetime {
float: right;
}
/* 模态框内容 */
.modal-content {
margin: auto;
display: block;
width: 95%;
max-width: 700px;
max-height: 80vh; /* 设置最大高度为视口高度的80% */
object-fit: contain; /* 保持图片的宽高比 */
zoom: 3;
}
/* 模态框 */
.modal {
display: none; /* 隐藏 */
position: fixed; /* 固定位置 */
z-index: 1; /* 坐在顶部 */
padding-top: 40px; /* 在图片上方添加一些内边距 */
left: 0;
top: 0;
width: 100%; /* 宽度 */
height: 100%; /* 高度 */
overflow: auto; /* 启用滚动 */
background-color: rgb(0,0,0); /* 背景颜色 */
background-color: rgba(0,0,0,0.9); /* 黑色背景半透明 */
}
/* 关闭按钮 */
.close {
position: absolute; /* 定义元素的定位方式为绝对定位 */
top: 10px; /* 距离最近的已定位祖先元素顶部15像素 */
right: 30px; /* 距离最近的已定位祖先元素右侧35像素 */
color: #f1f1f1; /* 文本颜色为浅灰色 */
font-size: 15px; /* 字体大小为40像素 */
font-weight: bold; /* 字体加粗 */
transition: 0.3s; /* 过渡效果,0.3秒内完成 */
}
.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
</style>
<script type="text/javascript">var FOLDER_ALL_CASES = false; // 是否为精简模式的标记
var ERROR_INFOS = []; // 错误信息列表
var current_error_idx = -1;
// 页面加载后执行的函数
window.addEventListener("load", function(){
// 所有 .folder_header 添加点击事件处理
let folderHeaderEles = document.querySelectorAll(".folder_header");
folderHeaderEles.forEach(function(ele) {
ele.addEventListener("click", function(event) {
let fb = event.target.closest('.folder_header').nextElementSibling;
fb.style.display = fb.style.display === 'none' ? 'block' : 'none';
});
});
// 找到所有的错误信息对象
ERROR_INFOS = document.querySelectorAll(".error-info");
// 获取所有图片元素
let images = document.querySelectorAll('.modal-image');
// 获取模态框元素
let modal = document.getElementById("imageModal");
// 获取模态框中的图片元素
let modalImg = document.getElementById("img01");
// 获取关闭按钮元素
let span = document.getElementsByClassName("close")[0];
// 为每个图片添加点击事件监听器
images.forEach(function(img) {
img.addEventListener("click", function() {
modal.style.display = "block"; // 显示模态框
modalImg.src = this.src; // 设置模态框中的图片为点击的图片
});
});
// 当点击关闭按钮时,隐藏模态框
span.onclick = function() {
modal.style.display = "none";
};
// 当点击模态框外区域时,隐藏模态框
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
});
function toggle_folder_all_cases(){
let eles = document.querySelectorAll(".folder_body");
FOLDER_ALL_CASES = !FOLDER_ALL_CASES;
document.getElementById('display_mode').innerHTML = FOLDER_ALL_CASES ? "Detail" : "Summary";
for (const ele of eles){
ele.style.display = FOLDER_ALL_CASES ? "none" : "block";
}
}
function previous_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx -= 1;
if (current_error_idx < 0)
current_error_idx = 0;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
function next_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx += 1;
if (current_error_idx > ERROR_INFOS.length - 1)
current_error_idx = ERROR_INFOS.length - 1;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
</script>
</head>
<body>
<div class="modal" id="imageModal">
<div>
<img class="modal-content" id="img01">
<span class="close">关闭</span>
</div>
</div>
<div class="main_section">
<h1 style="font-family: auto">测试报告</h1>
<h3>统计结果</h3>
<div class="result">
<table class="result_table">
<tbody>
<tr>
<td>hytest 版本</td>
<td>0.8.12</td>
</tr>
<tr>
<td>开始时间</td>
<td>2025/04/14 18:15:32</td>
</tr>
<tr>
<td>结束时间</td>
<td>2025/04/14 18:15:39</td>
</tr>
<tr>
<td>耗时</td>
<td>6.960 秒</td>
</tr>
<tr>
<td>预备执行用例数量</td>
<td>1</td>
</tr>
<tr>
<td>实际执用例行数量</td>
<td>1</td>
</tr>
<tr>
<td>通过</td>
<td>1</td>
</tr>
<tr>
<td>失败</td>
<td style="">0</td>
</tr>
<tr>
<td>异常</td>
<td style="">0</td>
</tr>
<tr>
<td>阻塞</td>
<td style="">0</td>
</tr>
<tr>
<td>套件初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>套件清除 失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例清除 失败</td>
<td style="">0</td>
</tr>
</tbody>
</table>
<div class="result_barchart">
<div class="barchar_item">
<span>用例通过 100% : 1 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 100.0%; background-color: #04AA6D;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例失败 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #bb4069;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例异常 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #9c27b0;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例阻塞 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #dcbdbd;"></div>
</div>
</div>
</div>
</div>
<div style="margin-top:2em">
<h3 style="display:inline">执行日志</h3>
</div>
<div class="exec_log">
<div class="suite_dir" id="suite_dir cases\测试目录\">
<div>
<span class="label">进入目录</span>
<span>cases\测试目录\</span>
</div>
<div class="suite_setup setup" id="suite_setup setup cases\测试目录\">
<div class="folder_header">
<span class="label">套件初始化</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:15:32</span>
</div>
<div class="folder_body">
<div class="case_step">
<span class="tag">步骤 #1</span>
<span>初始化设备adb连接</span>
</div>
<div class="info">'----------' 正在初始化ADB连接 '----------'</div>
<div class="info">尝试连接到设备: 192.168.1.160:5555</div>
<div class="info">设备 192.168.1.160:5555 已连接并可用</div>
<div class="info">'----------' 正在初始化ADB连接 '----------'</div>
<div class="info">尝试连接到设备: 192.168.1.160:5555</div>
<div class="info">设备 192.168.1.160:5555 已连接并可用</div>
<div class="checkpoint_pass">
<span class="tag">检查点 PASS</span>
<span>设备1的adb连接初始化检测</span>
</div>
<div class="info">'----------' 正在初始化浏览器 '----------'</div>
<div class="info">'----------' 浏览器初始化完成 '----------'</div>
</div>
</div>
</div>
<div class="suite_file" id="suite_file cases\测试目录\门口屏测试.py">
<div>
<span class="label">进入文件</span>
<span>cases\测试目录\门口屏测试.py</span>
</div>
<div class="case pass" id="case_00000001">
<div class="folder_header">
<span class="label caselabel">用例 PASS</span>
<span class="casename">DoorScreen</span>
<span class="executetime">2025-04-14 18:15:34</span>
</div>
<div class="folder_body">
<span class="case_class_path">cases\测试目录\门口屏测试.py::DoorScreen</span>
<div class="test_steps" id="test_steps DoorScreen">
<span class="label">测试步骤</span>
</div>
</div>
</div>
<div class="suite_teardown teardown" id="suite_teardown teardown cases\测试目录\">
<div class="folder_header">
<span class="label">套件清除</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:15:36</span>
</div>
<div class="folder_body">
<div class="info">ADB 连接已断开: 192.168.1.160:5555</div>
<div class="info">清除浏览器</div>
</div>
</div>
</div>
</div>
</div>
<div id="float_menu">
<div class="menu-item" onclick="document.querySelector(&quot;body&quot;).scrollIntoView()">页首</div>
<div class="menu-item" onclick="window.open(&quot;http://www.byhy.net/tut/auto/hytest/01&quot;, &quot;_blank&quot;); ">帮助</div>
<div class="menu-item" id="display_mode" onclick="toggle_folder_all_cases()">Summary</div>
<div class="error_jumper" display="none">
<div class="menu-item" onclick="previous_error()" title="上一个错误"></div>
<div class="menu-item" onclick="next_error()" title="下一个错误"></div>
</div>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<title>测试报告</title>
<meta charset="UTF-8">
<style>body {
font-family: consolas, Verdana, sans-serif;
font-size: 1.2em;
color: #696e71;
display: grid;
grid-template-columns: 1fr 5rem;
}
.main_section {
width: 90%;
margin: 0 auto;
}
#float_menu{
position:fixed;
top:0;
right:0;
text-align: center;
}
#float_menu .menu-item {
cursor: pointer;
padding: .5em;
margin: .5em 0;
color: #c08580;
background-color: #f8f0ef;
font-size: 1.2em;
}
.result{
display: flex;
}
.result_table{
border-collapse: collapse;
border: 1px solid #f0e0e5;
width: 30em;
text-align: center;
font-size: 1.0em;
}
.result_table td{
border: 1px solid #f0e0e5;
padding: .3em;
}
.result_barchart{
width: 30em;
margin: 0 5em 0 5em;
}
.barchar_item{
margin: 2.5rem 0;
}
.barchart_barbox {
margin: 0.5em 0;
width: 100%;
background-color: #fff;
border: 1px solid #86c2dd;
border-radius: .2em;
}
.barchart_bar {
text-align: right;
height: 1.2rem;
}
.h3_button {
margin: 1.5em;
cursor: pointer;
color: #03a9f4;
}
.info
{
white-space:pre-wrap;
margin: .8em 1.5em;
}
.error-info
{
color: #a64747
}
.suite_dir {
margin: 1em .2em;
padding: .3em;
/* background-color: #dfeff6; */
border: 1px solid #bcd8e4;
}
.suite_file {
margin: 1em .2em;
padding: .3em;
border: 1px solid #bcd8e4;
}
.case {
margin: 1em .2em;
/* padding: .3em; */
border: 1px solid #e7d4d4;
}
.case_class_path{
margin: 0em 1em;
}
.folder_header {
padding: .2em .7em;
background-color: #fffaf9;
cursor: pointer;
}
.setup{
margin: .2em;
/* padding: .3em; */
/* border: 1px solid #e7d4d4; */
}
.teardown{
margin: .2em;
/* padding: .3em;*/
/* border: 1px solid #e7d4d4; */
}
.test_steps{
margin: .2em;
padding: .3em;
/* border: 1px solid #e7d4d4; */
}
.label {
display: inline-block;
padding: .1em .5em;
font-size: .88em;
letter-spacing: 1px;
white-space: nowrap;
color: #0d6ebc;
border-radius: .2em;
min-width: 5em;
margin-right: 2em;
font-family: consolas;
}
/* .suite_setup .label{
color: #219e26 ;
}
.suite_teardown .label{
color: #219e26;
} */
/* .case.pass .casename{
color: #329132 ;
} */
.case.pass .caselabel{
color: white;
background-color: #3b9e3f;
}
/* .case.fail .casename{
color: #a64747;
} */
.case.fail .caselabel{
color: white;
background-color: #a64747;
}
/* .case.abort .casename{
color: #953ab7;
} */
.case.abort .caselabel{
color: white;
background-color: #9c27b0;
}
.case_step {
margin: .8em;
}
.checkpoint_pass {
margin: .8em;
}
.checkpoint_fail {
margin: .8em;
}
.case_step .tag{
color: #2196f3;;
margin: .3em 1em .3em 0;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_pass .tag{
color: #009806;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_fail .tag{
color: #9c2020;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.screenshot {
border: 1px solid #86c2dd;
}
.executetime {
float: right;
}
/* 模态框内容 */
.modal-content {
margin: auto;
display: block;
width: 95%;
max-width: 700px;
max-height: 80vh; /* 设置最大高度为视口高度的80% */
object-fit: contain; /* 保持图片的宽高比 */
zoom: 3;
}
/* 模态框 */
.modal {
display: none; /* 隐藏 */
position: fixed; /* 固定位置 */
z-index: 1; /* 坐在顶部 */
padding-top: 40px; /* 在图片上方添加一些内边距 */
left: 0;
top: 0;
width: 100%; /* 宽度 */
height: 100%; /* 高度 */
overflow: auto; /* 启用滚动 */
background-color: rgb(0,0,0); /* 背景颜色 */
background-color: rgba(0,0,0,0.9); /* 黑色背景半透明 */
}
/* 关闭按钮 */
.close {
position: absolute; /* 定义元素的定位方式为绝对定位 */
top: 10px; /* 距离最近的已定位祖先元素顶部15像素 */
right: 30px; /* 距离最近的已定位祖先元素右侧35像素 */
color: #f1f1f1; /* 文本颜色为浅灰色 */
font-size: 15px; /* 字体大小为40像素 */
font-weight: bold; /* 字体加粗 */
transition: 0.3s; /* 过渡效果,0.3秒内完成 */
}
.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
</style>
<script type="text/javascript">var FOLDER_ALL_CASES = false; // 是否为精简模式的标记
var ERROR_INFOS = []; // 错误信息列表
var current_error_idx = -1;
// 页面加载后执行的函数
window.addEventListener("load", function(){
// 所有 .folder_header 添加点击事件处理
let folderHeaderEles = document.querySelectorAll(".folder_header");
folderHeaderEles.forEach(function(ele) {
ele.addEventListener("click", function(event) {
let fb = event.target.closest('.folder_header').nextElementSibling;
fb.style.display = fb.style.display === 'none' ? 'block' : 'none';
});
});
// 找到所有的错误信息对象
ERROR_INFOS = document.querySelectorAll(".error-info");
// 获取所有图片元素
let images = document.querySelectorAll('.modal-image');
// 获取模态框元素
let modal = document.getElementById("imageModal");
// 获取模态框中的图片元素
let modalImg = document.getElementById("img01");
// 获取关闭按钮元素
let span = document.getElementsByClassName("close")[0];
// 为每个图片添加点击事件监听器
images.forEach(function(img) {
img.addEventListener("click", function() {
modal.style.display = "block"; // 显示模态框
modalImg.src = this.src; // 设置模态框中的图片为点击的图片
});
});
// 当点击关闭按钮时,隐藏模态框
span.onclick = function() {
modal.style.display = "none";
};
// 当点击模态框外区域时,隐藏模态框
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
});
function toggle_folder_all_cases(){
let eles = document.querySelectorAll(".folder_body");
FOLDER_ALL_CASES = !FOLDER_ALL_CASES;
document.getElementById('display_mode').innerHTML = FOLDER_ALL_CASES ? "Detail" : "Summary";
for (const ele of eles){
ele.style.display = FOLDER_ALL_CASES ? "none" : "block";
}
}
function previous_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx -= 1;
if (current_error_idx < 0)
current_error_idx = 0;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
function next_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx += 1;
if (current_error_idx > ERROR_INFOS.length - 1)
current_error_idx = ERROR_INFOS.length - 1;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
</script>
</head>
<body>
<div class="modal" id="imageModal">
<div>
<img class="modal-content" id="img01">
<span class="close">关闭</span>
</div>
</div>
<div class="main_section">
<h1 style="font-family: auto">测试报告</h1>
<h3>统计结果</h3>
<div class="result">
<table class="result_table">
<tbody>
<tr>
<td>hytest 版本</td>
<td>0.8.12</td>
</tr>
<tr>
<td>开始时间</td>
<td>2025/04/14 18:16:47</td>
</tr>
<tr>
<td>结束时间</td>
<td>2025/04/14 18:17:32</td>
</tr>
<tr>
<td>耗时</td>
<td>44.542 秒</td>
</tr>
<tr>
<td>预备执行用例数量</td>
<td>1</td>
</tr>
<tr>
<td>实际执用例行数量</td>
<td>1</td>
</tr>
<tr>
<td>通过</td>
<td>1</td>
</tr>
<tr>
<td>失败</td>
<td style="">0</td>
</tr>
<tr>
<td>异常</td>
<td style="">0</td>
</tr>
<tr>
<td>阻塞</td>
<td style="">0</td>
</tr>
<tr>
<td>套件初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>套件清除 失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例初始化失败</td>
<td style="">0</td>
</tr>
<tr>
<td>用例清除 失败</td>
<td style="">0</td>
</tr>
</tbody>
</table>
<div class="result_barchart">
<div class="barchar_item">
<span>用例通过 100% : 1 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 100.0%; background-color: #04AA6D;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例失败 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #bb4069;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例异常 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #9c27b0;"></div>
</div>
</div>
<div class="barchar_item">
<span>用例阻塞 0% : 0 个</span>
<div class="barchart_barbox">
<div class="barchart_bar" style="width: 0.0%; background-color: #dcbdbd;"></div>
</div>
</div>
</div>
</div>
<div style="margin-top:2em">
<h3 style="display:inline">执行日志</h3>
</div>
<div class="exec_log">
<div class="suite_dir" id="suite_dir cases\测试目录\">
<div>
<span class="label">进入目录</span>
<span>cases\测试目录\</span>
</div>
<div class="suite_setup setup" id="suite_setup setup cases\测试目录\">
<div class="folder_header">
<span class="label">套件初始化</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:16:47</span>
</div>
<div class="folder_body">
<div class="case_step">
<span class="tag">步骤 #1</span>
<span>初始化设备adb连接</span>
</div>
<div class="info">'----------' 正在初始化ADB连接 '----------'</div>
<div class="info">尝试连接到设备: 192.168.1.160:5555</div>
<div class="info">设备 192.168.1.160:5555 已连接并可用</div>
<div class="info">'----------' 正在初始化ADB连接 '----------'</div>
<div class="info">尝试连接到设备: 192.168.1.160:5555</div>
<div class="info">设备 192.168.1.160:5555 已连接并可用</div>
<div class="checkpoint_pass">
<span class="tag">检查点 PASS</span>
<span>设备1的adb连接初始化检测</span>
</div>
<div class="info">'----------' 正在初始化浏览器 '----------'</div>
<div class="info">'----------' 浏览器初始化完成 '----------'</div>
</div>
</div>
</div>
<div class="suite_file" id="suite_file cases\测试目录\门口屏测试.py">
<div>
<span class="label">进入文件</span>
<span>cases\测试目录\门口屏测试.py</span>
</div>
<div class="case pass" id="case_00000001">
<div class="folder_header">
<span class="label caselabel">用例 PASS</span>
<span class="casename">DoorScreen</span>
<span class="executetime">2025-04-14 18:16:49</span>
</div>
<div class="folder_body">
<span class="case_class_path">cases\测试目录\门口屏测试.py::DoorScreen</span>
<div class="test_steps" id="test_steps DoorScreen">
<span class="label">测试步骤</span>
</div>
</div>
</div>
<div class="suite_teardown teardown" id="suite_teardown teardown cases\测试目录\">
<div class="folder_header">
<span class="label">套件清除</span>
<span>cases\测试目录\</span>
<span class="executetime">2025-04-14 18:17:29</span>
</div>
<div class="folder_body">
<div class="info">ADB 连接已断开: 192.168.1.160:5555</div>
<div class="info">清除浏览器</div>
</div>
</div>
</div>
</div>
</div>
<div id="float_menu">
<div class="menu-item" onclick="document.querySelector(&quot;body&quot;).scrollIntoView()">页首</div>
<div class="menu-item" onclick="window.open(&quot;http://www.byhy.net/tut/auto/hytest/01&quot;, &quot;_blank&quot;); ">帮助</div>
<div class="menu-item" id="display_mode" onclick="toggle_folder_all_cases()">Summary</div>
<div class="error_jumper" display="none">
<div class="menu-item" onclick="previous_error()" title="上一个错误"></div>
<div class="menu-item" onclick="next_error()" title="下一个错误"></div>
</div>
</div>
</body>
</html>
\ No newline at end of file
=== [ 收集测试用例 ] ===
== cases\测试目录\__st__.py
== cases\测试目录\门口屏测试.py
=== [ 执行测试用例 ] ===
预备执行用例数量 : 1
========= 测试开始 : 20250414_181647 =========
>>> cases\测试目录\
[ suite setup ] cases\测试目录\
-- 第 1 步 -- 初始化设备adb连接
'----------' 正在初始化ADB连接 '----------'
尝试连接到设备: 192.168.1.160:5555
设备 192.168.1.160:5555 已连接并可用
'----------' 正在初始化ADB连接 '----------'
尝试连接到设备: 192.168.1.160:5555
设备 192.168.1.160:5555 已连接并可用
** 检查点 ** 设备1的adb连接初始化检测 ----> 通过
'----------' 正在初始化浏览器 '----------'
'----------' 浏览器初始化完成 '----------'
>>> cases\测试目录\门口屏测试.py
* DoorScreen - 2025-04-14 18:16:49
[ case execution steps ]
PASS
[ suite teardown ] cases\测试目录\
ADB 连接已断开: 192.168.1.160:5555
清除浏览器
========= 测试结束 : 20250414_181732 =========
耗时 : 44.542 秒
预备执行用例数量 : 1
实际执行用例数量 : 1
通过 : 1
失败 : 0
异常 : 0
套件初始化失败 : 0
套件清除 失败 : 0
用例初始化失败 : 0
用例清除 失败 : 0
=== [ 收集测试用例 ] ===
== cases\测试目录\__st__.py
== cases\测试目录\门口屏测试.py
=== [ 执行测试用例 ] ===
预备执行用例数量 : 1
========= 测试开始 : 20250414_181532 =========
>>> cases\测试目录\
[ suite setup ] cases\测试目录\
-- 第 1 步 -- 初始化设备adb连接
'----------' 正在初始化ADB连接 '----------'
尝试连接到设备: 192.168.1.160:5555
设备 192.168.1.160:5555 已连接并可用
'----------' 正在初始化ADB连接 '----------'
尝试连接到设备: 192.168.1.160:5555
设备 192.168.1.160:5555 已连接并可用
** 检查点 ** 设备1的adb连接初始化检测 ----> 通过
'----------' 正在初始化浏览器 '----------'
'----------' 浏览器初始化完成 '----------'
>>> cases\测试目录\门口屏测试.py
* DoorScreen - 2025-04-14 18:15:34
[ case execution steps ]
PASS
[ suite teardown ] cases\测试目录\
ADB 连接已断开: 192.168.1.160:5555
清除浏览器
========= 测试结束 : 20250414_181539 =========
耗时 : 6.960 秒
预备执行用例数量 : 1
实际执行用例数量 : 1
通过 : 1
失败 : 0
异常 : 0
套件初始化失败 : 0
套件清除 失败 : 0
用例初始化失败 : 0
用例清除 失败 : 0
=== [ 收集测试用例 ] ===
== cases\测试目录\__st__.py
== cases\测试目录\门口屏测试.py
=== [ 执行测试用例 ] ===
预备执行用例数量 : 1
========= 测试开始 : 20250414_181513 =========
>>> cases\测试目录\
[ suite setup ] cases\测试目录\
-- 第 1 步 -- 初始化设备adb连接
'----------' 正在初始化ADB连接 '----------'
尝试连接到设备: 192.168.1.160:5555
设备 192.168.1.160:5555 已连接并可用
** 检查点 ** 设备1的adb连接初始化检测 ----> 通过
'----------' 正在初始化浏览器 '----------'
'----------' 浏览器初始化完成 '----------'
>>> cases\测试目录\门口屏测试.py
* DoorScreen - 2025-04-14 18:15:15
[ case execution steps ]
PASS
[ suite teardown ] cases\测试目录\
ADB 连接已断开: 192.168.1.160:5555
清除浏览器
========= 测试结束 : 20250414_181521 =========
耗时 : 7.758 秒
预备执行用例数量 : 1
实际执行用例数量 : 1
通过 : 1
失败 : 0
异常 : 0
套件初始化失败 : 0
套件清除 失败 : 0
用例初始化失败 : 0
用例清除 失败 : 0
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论