mirror of
https://gitee.com/mao-peng/MangoTestingPlatform.git
synced 2025-12-06 11:59:15 +08:00
优化了测试报告
This commit is contained in:
@@ -1,29 +1,7 @@
|
||||
FROM python:3.10.16-slim
|
||||
FROM mcr.microsoft.com/playwright/python:latest
|
||||
WORKDIR /app
|
||||
RUN echo "deb http://mirrors.aliyun.com/debian bookworm main non-free non-free-firmware" > /etc/apt/sources.list && \
|
||||
echo "deb http://mirrors.aliyun.com/debian bookworm-updates main non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||
echo "deb http://mirrors.aliyun.com/debian bookworm-backports main non-free non-free-firmware" >> /etc/apt/sources.list && \
|
||||
echo "deb http://mirrors.aliyun.com/debian-security bookworm-security main non-free non-free-firmware" >> /etc/apt/sources.list
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libnss3 \
|
||||
libatk1.0-0 \
|
||||
libatk-bridge2.0-0 \
|
||||
libcups2 \
|
||||
libdrm2 \
|
||||
libxkbcommon0 \
|
||||
libxcomposite1 \
|
||||
libxdamage1 \
|
||||
libxfixes3 \
|
||||
libxrandr2 \
|
||||
libgbm1 \
|
||||
libasound2 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY . .
|
||||
RUN pip install --no-cache-dir --user -r linux_requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
|
||||
ENV PLAYWRIGHT_DOWNLOAD_HOST=https://registry.npmmirror.com/-/binary/playwright
|
||||
RUN playwright install chromium
|
||||
RUN pip install --no-cache-dir --user -r linux_requirements.txt -i https://mirror.sjtu.edu.cn/pypi/web/simple/
|
||||
|
||||
CMD ["python", "start_linux.py", "--ip=121.37.174.56", "--port=8000", "--username=admin", "--password=as123456"]
|
||||
@@ -6,6 +6,7 @@ import asyncio
|
||||
import json
|
||||
import traceback
|
||||
|
||||
import time
|
||||
from mangokit import singleton
|
||||
|
||||
from src.consumer.api import API
|
||||
@@ -13,7 +14,9 @@ from src.consumer.perf import Perf
|
||||
from src.consumer.tools import Tools
|
||||
from src.consumer.ui import UI
|
||||
from src.models.socket_model import QueueModel
|
||||
from src.models.ui_model import PageObject, GetTaskModel
|
||||
from src.models.user_model import UserModel
|
||||
from src.settings import settings
|
||||
from src.tools.log_collector import log
|
||||
|
||||
|
||||
@@ -34,12 +37,36 @@ class SocketConsumer(UI, API, Perf, Tools):
|
||||
await cls.queue.put(task)
|
||||
|
||||
async def consumer(self):
|
||||
s = time.time()
|
||||
while True:
|
||||
if not self.queue.empty():
|
||||
data: QueueModel = await self.queue.get()
|
||||
task = self.parent.loop.create_task(getattr(self, data.func_name)(data.func_args))
|
||||
task.add_done_callback(self.handle_task_result)
|
||||
await asyncio.sleep(0.2)
|
||||
if time.time() - s > 5:
|
||||
s = time.time()
|
||||
await self.get_case()
|
||||
else:
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
async def get_case(self):
|
||||
try:
|
||||
if PageObject.case_flow is not None:
|
||||
from src.network import UiSocketEnum, WebSocketClient
|
||||
if PageObject.case_flow.running_tasks < PageObject.case_flow.max_tasks \
|
||||
or PageObject.case_flow.queue.empty():
|
||||
await WebSocketClient().async_send(
|
||||
'请求获取任务',
|
||||
func_name=UiSocketEnum.GET_TASK.value,
|
||||
func_args=GetTaskModel(username=settings.USERNAME)
|
||||
)
|
||||
self.parent.set_tips_info('正在主动获取任务')
|
||||
else:
|
||||
from src.services.ui.service.case_flow import CaseFlow
|
||||
PageObject.case_flow = CaseFlow(self.parent)
|
||||
except Exception as error:
|
||||
traceback.print_exc()
|
||||
log.error(f'获取任务异常:{error}')
|
||||
|
||||
@classmethod
|
||||
def handle_task_result(cls, task):
|
||||
@@ -76,7 +103,6 @@ class Test:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from src.settings import settings
|
||||
from src.network.http import HTTP
|
||||
|
||||
settings.IP = '121.37.174.56'
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
from src.enums import BaseEnum
|
||||
|
||||
|
||||
class InputEnum(BaseEnum):
|
||||
INPUT = 0
|
||||
SELECT = 1
|
||||
CASCADER = 2
|
||||
TOGGLE = 3
|
||||
|
||||
|
||||
class TipsTypeEnum(BaseEnum):
|
||||
ERROR = 0
|
||||
SUCCESS = 1
|
||||
|
||||
@@ -131,7 +131,7 @@ class PageStepsResultModel(BaseModel):
|
||||
|
||||
cache_data: dict
|
||||
test_object: dict # url或者软件包
|
||||
equipment: dict # 设备名称或者浏览器类型
|
||||
equipment: EquipmentModel # 设备名称或者浏览器类型
|
||||
|
||||
status: int
|
||||
error_message: str | None = None
|
||||
@@ -151,6 +151,10 @@ class UiCaseResultModel(BaseModel):
|
||||
steps: list[PageStepsResultModel] = []
|
||||
|
||||
|
||||
class GetTaskModel(BaseModel):
|
||||
username: str
|
||||
|
||||
|
||||
class PageObject:
|
||||
test_page_steps = None
|
||||
case_flow = None
|
||||
|
||||
@@ -17,6 +17,7 @@ class UiSocketEnum(Enum):
|
||||
PAGE_STEPS = 'u_page_steps' # 步骤详情
|
||||
TEST_CASE = 'u_test_case' #
|
||||
TEST_CASE_BATCH = 'u_test_suite_details' #
|
||||
GET_TASK = 'u_get_task' #
|
||||
|
||||
|
||||
class ToolsSocketEnum(Enum):
|
||||
|
||||
@@ -45,7 +45,7 @@ class WebSocketClient:
|
||||
res = self.__output_method(response_str)
|
||||
if res.code == 200:
|
||||
await self.async_send(f'{ClientNameEnum.DRIVER.value} 连接服务成功!',
|
||||
is_notice=ClientTypeEnum.WEB)
|
||||
is_notice=ClientTypeEnum.WEB)
|
||||
self.parent.set_tips_info("心跳已连接")
|
||||
return True
|
||||
else:
|
||||
@@ -94,7 +94,7 @@ class WebSocketClient:
|
||||
async def async_send(self,
|
||||
msg: str,
|
||||
code: int = 200,
|
||||
func_name: None = None,
|
||||
func_name: None | str = None,
|
||||
func_args: Optional[Union[list[T], T]] | None = None,
|
||||
is_notice: ClientTypeEnum | None = None,
|
||||
):
|
||||
@@ -107,14 +107,17 @@ class WebSocketClient:
|
||||
)
|
||||
if func_name:
|
||||
send_data.data = QueueModel(func_name=func_name, func_args=func_args)
|
||||
try:
|
||||
if not settings.IS_DEBUG or self.websocket:
|
||||
|
||||
if self.websocket and self.websocket.open:
|
||||
try:
|
||||
await self.websocket.send(self.__serialize(send_data))
|
||||
else:
|
||||
self.__serialize(send_data)
|
||||
except WebSocketException:
|
||||
await self.client_run()
|
||||
await self.websocket.send(self.__serialize(send_data))
|
||||
except WebSocketException:
|
||||
await self.client_run()
|
||||
if self.websocket and self.websocket.open:
|
||||
await self.websocket.send(self.__serialize(send_data))
|
||||
else:
|
||||
if settings.IS_DEBUG:
|
||||
log.debug(f"调试模式:序列化数据 ->{self.__serialize(send_data)}")
|
||||
|
||||
def sync_send(self,
|
||||
msg: str,
|
||||
|
||||
@@ -36,7 +36,7 @@ class PageSteps(ElementOperation):
|
||||
case_step_details_id=self.page_steps_model.case_step_details_id,
|
||||
cache_data={},
|
||||
test_object={},
|
||||
equipment={},
|
||||
equipment=self.page_steps_model.equipment_config,
|
||||
status=StatusEnum.FAIL.value,
|
||||
element_result_list=[]
|
||||
)
|
||||
@@ -72,7 +72,7 @@ class PageSteps(ElementOperation):
|
||||
self.progress.emit(element_result)
|
||||
self.page_step_result_model.cache_data = self.test_data.get_all()
|
||||
self.page_step_result_model.test_object = {'url': self.url, 'package_name': self.package_name}
|
||||
self.page_step_result_model.equipment = {'name': self.driver_object.web.config}
|
||||
self.page_step_result_model.equipment = self.driver_object.web.config
|
||||
self.page_step_result_model.element_result_list.append(element_result)
|
||||
|
||||
@async_memory
|
||||
|
||||
@@ -253,7 +253,7 @@ class TestSuite(models.Model):
|
||||
project_product = models.ForeignKey(to=ProjectProduct, to_field="id", on_delete=models.PROTECT)
|
||||
test_env = models.SmallIntegerField(verbose_name="测试环境")
|
||||
user = models.ForeignKey(to=User, to_field="id", verbose_name='用例执行人', on_delete=models.PROTECT)
|
||||
tasks = models.ForeignKey(to=Tasks, to_field="id",on_delete=models.SET_NULL, null=True)
|
||||
tasks = models.ForeignKey(to=Tasks, to_field="id", on_delete=models.SET_NULL, null=True)
|
||||
|
||||
status = models.SmallIntegerField(verbose_name="测试结果")
|
||||
is_notice = models.SmallIntegerField(verbose_name="是否发送通知")
|
||||
@@ -272,7 +272,7 @@ class TestSuiteDetails(models.Model):
|
||||
create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
|
||||
update_time = models.DateTimeField(verbose_name="修改时间", auto_now=True)
|
||||
test_suite = models.ForeignKey(to=TestSuite, to_field="id", on_delete=models.PROTECT)
|
||||
# type=0是UI,=1是接口,=2是性能
|
||||
# type=0是UI,=1是接口,=2是pytest
|
||||
type = models.SmallIntegerField(verbose_name="类型")
|
||||
project_product = models.ForeignKey(to=ProjectProduct, to_field="id", on_delete=models.PROTECT)
|
||||
test_env = models.SmallIntegerField(verbose_name="测试环境")
|
||||
|
||||
@@ -40,7 +40,8 @@ class ConsumerThread:
|
||||
try:
|
||||
test_suite_details = TestSuiteDetails.objects.filter(
|
||||
status=TaskEnum.STAY_BEGIN.value,
|
||||
retry__lt=self.retry_frequency
|
||||
retry__lt=self.retry_frequency,
|
||||
type__in=[TestCaseTypeEnum.API.value, TestCaseTypeEnum.PYTEST.value]
|
||||
).first()
|
||||
if test_suite_details:
|
||||
test_suite = TestSuite.objects.get(id=test_suite_details.test_suite.id)
|
||||
|
||||
@@ -9,10 +9,10 @@ import traceback
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
from mangokit import singleton
|
||||
from src.models.socket_model import QueueModel
|
||||
from src.settings import DEBUG
|
||||
from src.tools.log_collector import log
|
||||
from mangokit import singleton
|
||||
from .api_consumer import APIConsumer
|
||||
from .perf_consumer import PerfConsumer
|
||||
from .system_consumer import SystemConsumer
|
||||
@@ -32,7 +32,7 @@ class ServerInterfaceReflection(APIConsumer, SystemConsumer, UIConsumer, PerfCon
|
||||
data = kwargs.get('data')
|
||||
if isinstance(data, QueueModel):
|
||||
if DEBUG:
|
||||
log.system.info(f"开始处理接收的消息:{data.model_dump_json()}")
|
||||
log.system.warning(f"开始处理接收的消息:{data.model_dump_json()}")
|
||||
future = self.executor.submit(getattr(self, data.func_name), data.func_args)
|
||||
future.add_done_callback(self.handle_task_result) # 添加任务完成后的回调函数
|
||||
else:
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
# @Time : 2023-04-29 11:20
|
||||
# @Author : 毛鹏
|
||||
from src.auto_test.auto_system.service.update_test_suite import UpdateTestSuite
|
||||
from src.auto_test.auto_ui.service.test_case.case_flow import UiCaseFlow
|
||||
from src.auto_test.auto_ui.service.test_report_writing import TestReportWriting
|
||||
from src.models.system_model import TestSuiteDetailsResultModel
|
||||
from src.models.ui_model import PageStepsResultModel, UiCaseResultModel
|
||||
from src.models.ui_model import PageStepsResultModel, UiCaseResultModel, GetTaskModel
|
||||
from src.tools.decorator.convert_args import convert_args
|
||||
|
||||
|
||||
@@ -26,3 +27,8 @@ class UIConsumer:
|
||||
@convert_args(UiCaseResultModel)
|
||||
def u_test_case(cls, data: UiCaseResultModel):
|
||||
TestReportWriting.update_test_case(data)
|
||||
|
||||
@classmethod
|
||||
@convert_args(GetTaskModel)
|
||||
def u_get_task(cls, data: GetTaskModel):
|
||||
UiCaseFlow.get_case(data)
|
||||
|
||||
@@ -62,6 +62,7 @@ urlpatterns = [
|
||||
path('test/suite/details/report', TestSuiteDetailsViews.as_view({'get': 'test_suite_details_report'})),
|
||||
path('test/suite/details/all/retry', TestSuiteDetailsViews.as_view({'get': 'get_all_retry'})),
|
||||
path('test/suite/details/retry', TestSuiteDetailsViews.as_view({'get': 'get_retry'})),
|
||||
path('test/suite/details/summary', TestSuiteDetailsViews.as_view({'get': 'get_summary'})),
|
||||
#
|
||||
path('index/sum', IndexViews.as_view({'get': 'case_sum'})),
|
||||
path('index/result/week/sum', IndexViews.as_view({'get': 'case_result_week_sum'})),
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# @Time : 2023-03-25 13:25
|
||||
# @Author : 毛鹏
|
||||
|
||||
from django.forms.models import model_to_dict
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.request import Request
|
||||
@@ -12,10 +11,10 @@ from rest_framework.viewsets import ViewSet
|
||||
|
||||
from src.auto_test.auto_api.models import ApiCase
|
||||
from src.auto_test.auto_api.views.api_case import ApiCaseSerializers
|
||||
from src.auto_test.auto_ui.views.ui_case import UiCaseSerializers
|
||||
from src.auto_test.auto_system.models import TasksDetails
|
||||
from src.auto_test.auto_system.views.tasks import TasksSerializers
|
||||
from src.auto_test.auto_ui.models import UiCase
|
||||
from src.auto_test.auto_ui.views.ui_case import UiCaseSerializers
|
||||
from src.enums.tools_enum import TestCaseTypeEnum
|
||||
from src.tools.decorator.error_response import error_response
|
||||
from src.tools.view.model_crud import ModelCRUD
|
||||
|
||||
@@ -13,7 +13,7 @@ from rest_framework.viewsets import ViewSet
|
||||
from src.auto_test.auto_system.models import TestSuiteDetails
|
||||
from src.auto_test.auto_system.views.project_product import ProjectProductSerializersC
|
||||
from src.auto_test.auto_system.views.test_suite import TestSuiteSerializers
|
||||
from src.enums.tools_enum import StatusEnum, TaskEnum
|
||||
from src.enums.tools_enum import StatusEnum, TaskEnum, TestCaseTypeEnum
|
||||
from src.tools.decorator.error_response import error_response
|
||||
from src.tools.view import *
|
||||
from src.tools.view.model_crud import ModelCRUD
|
||||
@@ -168,3 +168,19 @@ class TestSuiteDetailsViews(ViewSet):
|
||||
data['fail'] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
data['success'] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
return ResponseData.success(RESPONSE_MSG_0129, data)
|
||||
|
||||
@action(methods=['get'], detail=False)
|
||||
@error_response('system')
|
||||
def get_summary(self, request: Request):
|
||||
test_suite_id = request.query_params.get('test_suite_id')
|
||||
model = self.model.objects.filter(test_suite_id=test_suite_id)
|
||||
return ResponseData.success(RESPONSE_MSG_0065, {
|
||||
'count': model.count(),
|
||||
'fail_count': model.filter(status=TaskEnum.FAIL.value).count(),
|
||||
'success_count': model.filter(status=TaskEnum.SUCCESS.value).count(),
|
||||
'stay_begin_count': model.filter(status=TaskEnum.STAY_BEGIN.value).count(),
|
||||
'proceed_count': model.filter(status=TaskEnum.PROCEED.value).count(),
|
||||
'api_count': model.filter(type=TestCaseTypeEnum.API.value).count(),
|
||||
'ui_count': model.filter(type=TestCaseTypeEnum.UI.value).count(),
|
||||
'pytest_count': model.filter(type=TestCaseTypeEnum.PYTEST.value).count(),
|
||||
})
|
||||
|
||||
@@ -5,17 +5,20 @@
|
||||
# @Author : 毛鹏
|
||||
|
||||
import time
|
||||
from django.utils import timezone
|
||||
|
||||
from src.auto_test.auto_system.models import TestSuite, TestSuiteDetails
|
||||
from src.auto_test.auto_system.service.socket_link.socket_user import SocketUser
|
||||
from src.auto_test.auto_ui.service.test_case.test_case import TestCase
|
||||
from src.enums.tools_enum import TaskEnum
|
||||
from src.auto_test.auto_user.models import User
|
||||
from src.enums.tools_enum import TaskEnum, TestCaseTypeEnum
|
||||
from src.models.system_model import ConsumerCaseModel
|
||||
from src.models.ui_model import GetTaskModel
|
||||
from src.tools.log_collector import log
|
||||
|
||||
|
||||
class UiCaseFlow:
|
||||
current_index = 0
|
||||
retry_frequency = 3
|
||||
|
||||
@classmethod
|
||||
def execute_task(cls, case_model: ConsumerCaseModel, retry=0, max_retry=3):
|
||||
@@ -31,33 +34,65 @@ class UiCaseFlow:
|
||||
except IndexError:
|
||||
time.sleep(3)
|
||||
return cls.execute_task(case_model, retry, max_retry)
|
||||
cls.send_case(user.user_id, user.username, case_model)
|
||||
|
||||
@classmethod
|
||||
def add_task(cls, case_model: ConsumerCaseModel):
|
||||
cls.execute_task(case_model)
|
||||
|
||||
@classmethod
|
||||
def get_case(cls, data: GetTaskModel):
|
||||
model = User.objects.get(name=data.username)
|
||||
test_suite_details = TestSuiteDetails.objects.filter(
|
||||
status=TaskEnum.STAY_BEGIN.value,
|
||||
retry__lt=cls.retry_frequency,
|
||||
type=TestCaseTypeEnum.UI.value
|
||||
).first()
|
||||
if test_suite_details:
|
||||
test_suite = TestSuite.objects.get(id=test_suite_details.test_suite.id)
|
||||
case_model = ConsumerCaseModel(
|
||||
test_suite_details=test_suite_details.id,
|
||||
test_suite=test_suite_details.test_suite.id,
|
||||
case_id=test_suite_details.case_id,
|
||||
test_env=test_suite_details.test_env,
|
||||
user_id=test_suite.user.id,
|
||||
tasks_id=test_suite.tasks.id if test_suite.tasks else None,
|
||||
)
|
||||
cls.send_case(model.id, model.username, case_model)
|
||||
cls.update_status_proceed(test_suite, test_suite_details)
|
||||
|
||||
@classmethod
|
||||
def update_status_proceed(cls, test_suite, test_suite_details):
|
||||
test_suite.status = TaskEnum.PROCEED.value
|
||||
test_suite.save()
|
||||
|
||||
test_suite_details.status = TaskEnum.PROCEED.value
|
||||
test_suite_details.retry += 1
|
||||
test_suite_details.push_time = timezone.now()
|
||||
test_suite_details.save()
|
||||
|
||||
@classmethod
|
||||
def send_case(cls, user_id, username, case_model):
|
||||
from src.auto_test.auto_ui.service.test_case.test_case import TestCase
|
||||
send_case = TestCase(
|
||||
user_id=user.user_id,
|
||||
username=user.username,
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
test_env=case_model.test_env,
|
||||
tasks_id=case_model.tasks_id,
|
||||
is_send=True
|
||||
)
|
||||
inspect = send_case.inspect_environment_config(case_model.case_id)
|
||||
if not inspect:
|
||||
if retry > max_retry:
|
||||
test_suite = TestSuite.objects.get(id=case_model.test_suite)
|
||||
test_suite.status = TaskEnum.FAIL.value
|
||||
test_suite.save()
|
||||
test_suite_details = TestSuiteDetails.objects.get(id=case_model.test_suite_details)
|
||||
test_suite_details.status = TaskEnum.FAIL.value
|
||||
test_suite_details.error_message = f'你配置了不同UI自动化类型,但是你没有准备好UI设备配置,请先前往界面自动化->设备配置中添加配置!'
|
||||
test_suite.save()
|
||||
else:
|
||||
time.sleep(3)
|
||||
return cls.execute_task(case_model, retry, max_retry)
|
||||
test_suite = TestSuite.objects.get(id=case_model.test_suite)
|
||||
test_suite.status = TaskEnum.FAIL.value
|
||||
test_suite.save()
|
||||
test_suite_details = TestSuiteDetails.objects.get(id=case_model.test_suite_details)
|
||||
test_suite_details.status = TaskEnum.FAIL.value
|
||||
test_suite_details.error_message = f'你配置了不同UI自动化类型,但是你没有准备好UI设备配置,请先前往界面自动化->设备配置中添加配置!'
|
||||
test_suite.save()
|
||||
else:
|
||||
send_case.test_case(
|
||||
case_id=case_model.case_id,
|
||||
test_suite=case_model.test_suite,
|
||||
test_suite_details=case_model.test_suite_details
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def add_task(cls, case_model: ConsumerCaseModel):
|
||||
cls.execute_task(case_model)
|
||||
|
||||
@@ -148,3 +148,7 @@ class UiCaseResultModel(BaseModel):
|
||||
error_message: str | None = None
|
||||
video_path: str | None = None
|
||||
steps: list[PageStepsResultModel]
|
||||
|
||||
|
||||
class GetTaskModel(BaseModel):
|
||||
username: str
|
||||
|
||||
@@ -15,7 +15,7 @@ MYSQL_PORT = 3306
|
||||
MYSQL_DB_NAME = 'dev_mango_server'
|
||||
MYSQL_USER = 'root'
|
||||
MYSQL_PASSWORD = 'mP123456&'
|
||||
MYSQL_IP = '172.25.239.230'
|
||||
MYSQL_IP = '172.19.85.178'
|
||||
|
||||
# ************************ DEBUG配置 ************************ #
|
||||
# 这里也控制了是否使用minio
|
||||
|
||||
@@ -3,4 +3,4 @@ VITE_APP_ENV='dev'
|
||||
VITE_APP_BASE_URL = 'http://127.0.0.1:8000'
|
||||
VITE_APP_SOCKET_URL = 'ws://127.0.0.1:8000/web/socket?'
|
||||
VITE_APP_MINIO_URL = 'http://127.0.0.1:8000'
|
||||
VITE_IS_INDEX_WINDOW = 'true'
|
||||
VITE_IS_INDEX_WINDOW = 'false'
|
||||
1
mango-console/components.d.ts
vendored
1
mango-console/components.d.ts
vendored
@@ -67,7 +67,6 @@ declare module 'vue' {
|
||||
ATreeSelect: typeof import('@arco-design/web-vue')['TreeSelect']
|
||||
DeleteButton: typeof import('./src/components/DeleteButton.vue')['default']
|
||||
IconSelector: typeof import('./src/components/IconSelector.vue')['default']
|
||||
MarkdownEditor: typeof import('./src/components/MarkdownEditor.vue')['default']
|
||||
MessageContent: typeof import('./src/components/MessageContent.vue')['default']
|
||||
ModalDialog: typeof import('./src/components/ModalDialog.vue')['default']
|
||||
PasswordStrong: typeof import('./src/components/PasswordStrong.vue')['default']
|
||||
|
||||
@@ -8,6 +8,7 @@ export function getSystemTestSuiteDetails(test_suite_id: number) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function postSystemTestSuiteDetails(data: object) {
|
||||
return post({
|
||||
url: 'system/test/suite/details',
|
||||
@@ -16,6 +17,7 @@ export function postSystemTestSuiteDetails(data: object) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function putSystemTestSuiteDetails(data: object) {
|
||||
return put({
|
||||
url: 'system/test/suite/details',
|
||||
@@ -35,6 +37,7 @@ export function deleteSystemTestSuiteDetails(id: number | string[] | number[]) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getSystemTestSuiteDetailsReport() {
|
||||
return get({
|
||||
url: 'system/test/suite/details/report',
|
||||
@@ -43,6 +46,7 @@ export function getSystemTestSuiteDetailsReport() {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getSystemTestSuiteDetailsAllRetry(test_suite_id: number) {
|
||||
return get({
|
||||
url: 'system/test/suite/details/all/retry',
|
||||
@@ -51,3 +55,12 @@ export function getSystemTestSuiteDetailsAllRetry(test_suite_id: number) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getSystemTestSuiteDetailsSummary(test_suite_id: number) {
|
||||
return get({
|
||||
url: 'system/test/suite/details/summary',
|
||||
data: () => {
|
||||
return { test_suite_id: test_suite_id }
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
79
mango-console/src/components/ApiTestReport.vue
Normal file
79
mango-console/src/components/ApiTestReport.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" title="基础信息">
|
||||
<a-space direction="vertical">
|
||||
<h1>接口ID:{{ resultData?.id }}</h1>
|
||||
<p>接口名称:{{ resultData?.name }}</p>
|
||||
<p>Method:{{ resultData?.request?.method }}</p>
|
||||
<p>URL:{{ resultData?.request?.url }}</p>
|
||||
<p>Status Code:{{ resultData?.response?.code }}</p>
|
||||
<p>响应时间:{{ resultData?.response?.time }}</p>
|
||||
<p :style="{ color: resultData?.status === 0 ? 'red' : 'inherit' }">
|
||||
测试结果:{{ resultData?.status === 1 ? '通过' : resultData?.status === 0 ? '失败' : '' }}
|
||||
</p>
|
||||
<p v-if="resultData?.status === 0">错误提示语:{{ resultData?.error_message }}</p>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" title="请求信息">
|
||||
<a-tabs default-active-key="21" size="small" position="left">
|
||||
<a-tab-pane key="21" title="请求头">
|
||||
<pre>{{ strJson(resultData?.request?.headers) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="22" title="参数">
|
||||
<pre>{{ strJson(resultData?.request?.params) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="23" title="data">
|
||||
<pre>{{ strJson(resultData?.request?.data) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="24" title="json">
|
||||
<pre>{{ strJson(resultData?.request?.json) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="25" title="文件">
|
||||
<pre>{{ strJson(resultData?.request?.file) }}</pre>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" title="响应信息">
|
||||
<a-tabs default-active-key="31" size="small" position="left">
|
||||
<a-tab-pane key="31" title="响应头">
|
||||
<pre>{{ strJson(resultData?.response?.headers) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="32" title="文本">
|
||||
<pre>{{ strJson(resultData?.response?.text) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="33" title="JSON">
|
||||
<pre>{{ strJson(resultData?.response?.json) }}</pre>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="11" title="缓存数据">
|
||||
<pre>{{ strJson(resultData?.cache_data) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="12" title="断言数据">
|
||||
<pre>{{ strJson(resultData?.ass) }}</pre>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue'
|
||||
import { strJson } from '@/utils/tools'
|
||||
|
||||
defineProps({
|
||||
resultData: {
|
||||
type: Object as () => any,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式 */
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CSSProperties, ref, defineProps, withDefaults, watch, defineEmits } from 'vue'
|
||||
import { CSSProperties, ref, watch } from 'vue'
|
||||
import { Codemirror } from 'vue-codemirror'
|
||||
import { python } from '@codemirror/lang-python'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
@@ -25,18 +25,14 @@
|
||||
autoDestroy: true,
|
||||
})
|
||||
|
||||
// 创建 emit 函数
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void
|
||||
}>()
|
||||
|
||||
// 根据主题设置扩展
|
||||
const extensions = props.dark ? [python(), oneDark] : [python()]
|
||||
|
||||
// 使用 ref 来存储代码值
|
||||
const codeValue = ref(props.modelValue)
|
||||
|
||||
// 监听 props.modelValue 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newCode) => {
|
||||
@@ -44,23 +40,24 @@
|
||||
}
|
||||
)
|
||||
|
||||
// 监听 codeValue 的变化并发出更新事件
|
||||
watch(codeValue, (newCode) => {
|
||||
emit('update:modelValue', newCode)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@themeColor: #1890ff; // 定义主题颜色变量
|
||||
@themeColor: #1890ff;
|
||||
|
||||
:deep(.cm-editor) {
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
|
||||
.cm-scroller {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.cm-focused) {
|
||||
border: 1px solid fade(@themeColor, 48%);
|
||||
}
|
||||
|
||||
158
mango-console/src/components/ElementTestReport.vue
Normal file
158
mango-console/src/components/ElementTestReport.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" title="执行过程">
|
||||
<a-collapse
|
||||
v-for="item of resultData?.element_result_list"
|
||||
:bordered="false"
|
||||
:key="item.id"
|
||||
destroy-on-hide
|
||||
>
|
||||
<a-collapse-item :header="item.name" :style="customStyle" :key="item.id">
|
||||
<div>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p>
|
||||
操作类型:{{
|
||||
item.type
|
||||
? getLabelByValue(data.ass, item.ope_key)
|
||||
: getLabelByValue(data.ope, item.ope_key)
|
||||
}}
|
||||
</p>
|
||||
<p> 表达式类型:{{ item.exp ? enumStore.element_exp[item.exp].title : item.exp }} </p>
|
||||
<p>
|
||||
测试结果:{{ item.status === 1 ? '通过' : item.status === 0 ? '失败' : '未测试' }}
|
||||
</p>
|
||||
<p>等待时间:{{ item.sleep ? item.sleep : '-' }}</p>
|
||||
<p v-if="item.status === 0">错误提示:{{ item.error_message }}</p>
|
||||
<p v-if="item.expect">预期:{{ item.expect }}</p>
|
||||
<p v-if="item.status === 0">视频路径:{{ item.video_path }}</p>
|
||||
</a-space>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p style="word-wrap: break-word">元素表达式:{{ item.loc }}</p>
|
||||
<p>元素个数:{{ item.ele_quantity }}</p>
|
||||
<p>元素下标:{{ item.sub ? item.sub : '-' }}</p>
|
||||
<div v-if="item.status === 0">
|
||||
<a-image
|
||||
:src="minioURL + '/mango-file/failed_screenshot/' + item.picture_path"
|
||||
title="失败截图"
|
||||
width="260"
|
||||
style="margin-right: 67px; vertical-align: top"
|
||||
:preview-visible="visible"
|
||||
@preview-visible-change="visible = false"
|
||||
>
|
||||
<template #extra>
|
||||
<div class="actions">
|
||||
<span class="action" @click="visible = true">
|
||||
<icon-eye />
|
||||
</span>
|
||||
<span class="action">
|
||||
<icon-download />
|
||||
</span>
|
||||
<a-tooltip content="失败截图">
|
||||
<span class="action">
|
||||
<icon-info-circle />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-image>
|
||||
</div>
|
||||
<p v-if="item.expect">实际:{{ item.actual }}</p>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" title="其他信息">
|
||||
<a-space direction="vertical" size="large">
|
||||
<div v-if="resultData?.status === 0">
|
||||
<span>失败描述:{{ resultData?.error_message || '无' }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span
|
||||
>测试对象:{{
|
||||
resultData?.test_object?.url
|
||||
? resultData?.test_object?.url
|
||||
: resultData?.test_object?.package_name
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>缓存数据:</span>
|
||||
<pre>{{ resultData?.cache_data }}</pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>浏览器路径:{{ resultData?.equipment?.web_path || '无' }}</span>
|
||||
</div>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useEnum } from '@/store/modules/get-enum'
|
||||
import { minioURL } from '@/api/axios.config'
|
||||
import {
|
||||
getUiPageStepsDetailedAss,
|
||||
getUiPageStepsDetailedOpe,
|
||||
} from '@/api/uitest/page-steps-detailed'
|
||||
|
||||
defineProps({
|
||||
resultData: {
|
||||
type: Array as () => any,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const data: any = reactive({
|
||||
ass: [],
|
||||
ope: [],
|
||||
})
|
||||
const enumStore = useEnum()
|
||||
const visible = ref(false)
|
||||
|
||||
const customStyle = reactive({
|
||||
borderRadius: '6px',
|
||||
marginBottom: '2px',
|
||||
border: 'none',
|
||||
overflow: 'hidden',
|
||||
})
|
||||
|
||||
function getLabelByValue(opeData: any, value: string): string {
|
||||
const list = [...opeData]
|
||||
for (const item of list) {
|
||||
if (item.children) {
|
||||
list.push(...item.children)
|
||||
}
|
||||
}
|
||||
return list.find((item: any) => item.value === value)?.label
|
||||
}
|
||||
|
||||
function getUiRunSortAss() {
|
||||
getUiPageStepsDetailedAss(null)
|
||||
.then((res) => {
|
||||
data.ass = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getUiRunSortOpe() {
|
||||
getUiPageStepsDetailedOpe(null)
|
||||
.then((res) => {
|
||||
data.ope = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getLabelByValue,
|
||||
})
|
||||
onMounted(() => {
|
||||
getUiRunSortOpe()
|
||||
getUiRunSortAss()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<div class="editor-wrapper"> 开发整理中 </div>
|
||||
</template>
|
||||
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -107,7 +107,7 @@ export const useTableColumn = function (
|
||||
|
||||
export const useTableIndexColumn = function () {
|
||||
return {
|
||||
title: '序号',
|
||||
title: 'ID',
|
||||
key: 'index',
|
||||
width: 80,
|
||||
dataIndex: 'index',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<TableBody ref="tableBody">
|
||||
<template #header>
|
||||
<a-card title="测试报告详情" :bordered="false">
|
||||
<a-card title="测试报告" :bordered="false">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button status="danger" size="small" @click="doResetSearch">返回</a-button>
|
||||
@@ -10,19 +10,43 @@
|
||||
<div>
|
||||
<a-space direction="vertical" style="width: 30%">
|
||||
<p>测试套ID:{{ pageData.record.id }}</p>
|
||||
<p>所属项目:{{ pageData.record.project_product?.project?.name }}</p>
|
||||
<p>任务名称:{{ pageData.record.tasks?.name }}</p>
|
||||
<p>执行时间:{{ pageData.record.create_time }}</p>
|
||||
<p>当前状态:{{ enumStore.task_status[pageData.record.status].title }}</p>
|
||||
<!-- <p>当前状态:{{ enumStore.test_case_type[pageData.record.type].title }}</p>-->
|
||||
<p>测试环境:{{ enumStore.environment_type[pageData.record.test_env].title }}</p>
|
||||
<p>执行人:{{ pageData.record.user?.name }}</p>
|
||||
<p>是否通知:{{ enumStore.status[pageData.record.is_notice].title }}</p>
|
||||
</a-space>
|
||||
<a-space size="large" v-for="item of data.summary" :key="item.name" style="width: 7%">
|
||||
<a-statistic :title="item.name" :value="item.value" show-group-separator />
|
||||
<a-space size="large">
|
||||
<a-statistic title="用例总数" :value="data.summary.count" show-group-separator />
|
||||
<a-statistic
|
||||
title="成功总数"
|
||||
:value="data.summary.success_count"
|
||||
:value-style="{ color: '#33ff57' }"
|
||||
/>
|
||||
<a-statistic
|
||||
title="失败总数"
|
||||
:value="data.summary.fail_count"
|
||||
:value-style="{ color: '#ff5733' }"
|
||||
/>
|
||||
<a-statistic
|
||||
title="待开始数"
|
||||
:value="data.summary.stay_begin_count"
|
||||
:value-style="{ color: '#dbff33' }"
|
||||
/>
|
||||
<a-statistic
|
||||
title="进行中数"
|
||||
:value="data.summary.proceed_count"
|
||||
:value-style="{ color: '#ffbd33' }"
|
||||
/>
|
||||
<a-statistic title="UI总数" :value="data.summary.ui_count" />
|
||||
<a-statistic title="API总数" :value="data.summary.api_count" />
|
||||
<a-statistic title="Pytest总数" :value="data.summary.pytest_count" />
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
<template #default>
|
||||
<a-card title="测试套" :bordered="false">
|
||||
<a-card title="测试套详情" :bordered="false">
|
||||
<div class="box">
|
||||
<div class="left">
|
||||
<a-tree blockNode ref="childRef" :data="data.treeData" @select="clickTree">
|
||||
@@ -35,152 +59,23 @@
|
||||
</template>
|
||||
</template>
|
||||
<template #title="{ title }">
|
||||
<div>
|
||||
<span>{{ getNodeTitle(title) }}</span>
|
||||
</div>
|
||||
<div v-html="title"></div>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div v-if="data.caseType === 0">
|
||||
<a-collapse
|
||||
v-for="item of data.selectData.element_result_list"
|
||||
:bordered="false"
|
||||
:key="item.id"
|
||||
destroy-on-hide
|
||||
>
|
||||
<a-collapse-item :header="item.name" :style="customStyle" :key="item.id">
|
||||
<div>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p
|
||||
>操作类型:{{
|
||||
item.type === 0
|
||||
? getLabelByValue(data.ope, item.ope_key)
|
||||
: getLabelByValue(data.ass, item.ope_key)
|
||||
}}</p
|
||||
>
|
||||
<p
|
||||
>表达式类型:{{
|
||||
item.exp ? enumStore.element_exp[item.exp].title : item.exp
|
||||
}}</p
|
||||
>
|
||||
<p
|
||||
>测试结果:{{
|
||||
item.status === 1 ? '通过' : item.status === 0 ? '失败' : '未测试'
|
||||
}}</p
|
||||
>
|
||||
<p>等待时间:{{ item.sleep ? item.sleep : '-' }}</p>
|
||||
<p v-if="item.status === 0">错误提示:{{ item.error_message }}</p>
|
||||
<p v-if="item.expect">预期:{{ item.expect }}</p>
|
||||
<p v-if="item.status === 0">视频路径:{{ item.video_path }}</p>
|
||||
</a-space>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p style="word-wrap: break-word">元素表达式:{{ item.loc }}</p>
|
||||
<p>元素个数:{{ item.ele_quantity }}</p>
|
||||
<p>元素下标:{{ item.sub ? item.sub : '-' }}</p>
|
||||
<div v-if="item.status === 0">
|
||||
<a-image
|
||||
:src="minioURL + '/mango-file/failed_screenshot/' + item.picture_path"
|
||||
title="失败截图"
|
||||
width="260"
|
||||
style="margin-right: 67px; vertical-align: top"
|
||||
:preview-visible="visible1"
|
||||
@preview-visible-change="
|
||||
() => {
|
||||
visible1 = false
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #extra>
|
||||
<div class="actions">
|
||||
<span
|
||||
class="action"
|
||||
@click="
|
||||
() => {
|
||||
visible1 = true
|
||||
}
|
||||
"
|
||||
><icon-eye
|
||||
/></span>
|
||||
<span class="action"><icon-download /></span>
|
||||
<a-tooltip content="失败截图">
|
||||
<span class="action"><icon-info-circle /></span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-image>
|
||||
</div>
|
||||
<p v-if="item.expect">实际:{{ item.actual }}</p>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
<a-card :title="data.resultData.name" :bordered="false">
|
||||
<ElementTestReport :resultData="data.resultData" />
|
||||
</a-card>
|
||||
</div>
|
||||
<div v-else-if="data.caseType === 1">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" title="接口信息">
|
||||
<a-space direction="vertical">
|
||||
<p>接口ID:{{ data.selectData?.id }}</p>
|
||||
<p>接口名称:{{ data.selectData?.name }}</p>
|
||||
<p>请求方法:{{ data.selectData?.request?.method }}</p>
|
||||
<p>请求url:{{ data.selectData?.request?.url }}</p>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" title="请求头">
|
||||
<pre>{{ strJson(data.selectData?.request?.headers) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" title="参数">
|
||||
<pre>{{ strJson(data.selectData?.request?.params) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" title="data">
|
||||
<pre>{{ strJson(data.selectData?.request?.data) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" title="json">
|
||||
<pre>{{ strJson(data.selectData?.request?.json) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="6" title="文件">
|
||||
<pre>{{ strJson(data.selectData?.request?.file) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="7" title="响应信息">
|
||||
<a-space direction="vertical">
|
||||
<p>响应code码:{{ data.selectData?.response?.code }}</p>
|
||||
<p>响应时间:{{ data.selectData?.response?.time }}</p>
|
||||
<p :style="{ color: data.selectData?.status === 0 ? 'red' : 'inherit' }">
|
||||
测试结果:{{
|
||||
data.selectData?.status === 1
|
||||
? '通过'
|
||||
: data.selectData?.status === 0
|
||||
? '失败'
|
||||
: ''
|
||||
}}
|
||||
</p>
|
||||
<p v-if="data.selectData?.status === 0"
|
||||
>错误提示语:{{ data.selectData?.error_message }}</p
|
||||
>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="8" title="响应头">
|
||||
<pre>{{ strJson(data.selectData?.response?.headers) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="9" title="响应文本">
|
||||
<pre>{{ strJson(data.selectData?.response?.text) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="10" title="响应JSON">
|
||||
<pre>{{ strJson(data.selectData?.response?.json) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="11" title="缓存数据">
|
||||
<pre>{{ strJson(data.selectData?.cache_data) }}</pre>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="12" title="断言数据">
|
||||
<pre>{{ strJson(data.selectData?.ass) }}</pre>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-card :title="data.resultData.name" :bordered="false">
|
||||
<ApiTestReport :resultData="data.resultData" />
|
||||
</a-card>
|
||||
</div>
|
||||
<div v-else-if="data.caseType === 2">
|
||||
<span>界面pytest</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>接口pytest</span>
|
||||
<span>pytest</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,48 +84,40 @@
|
||||
</TableBody>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { usePageData } from '@/store/page-data'
|
||||
import {
|
||||
getUiPageStepsDetailedAss,
|
||||
getUiPageStepsDetailedOpe,
|
||||
} from '@/api/uitest/page-steps-detailed'
|
||||
import { getSystemTestSuiteDetails } from '@/api/system/test_sute_details'
|
||||
import { minioURL } from '@/api/axios.config'
|
||||
import { useEnum } from '@/store/modules/get-enum'
|
||||
import { strJson } from '@/utils/tools'
|
||||
getSystemTestSuiteDetails,
|
||||
getSystemTestSuiteDetailsSummary,
|
||||
} from '@/api/system/test_sute_details'
|
||||
import { nanoid } from 'nanoid'
|
||||
import ElementTestReport from '@/components/ElementTestReport.vue'
|
||||
import ApiTestReport from '@/components/ApiTestReport.vue'
|
||||
import { useEnum } from '@/store/modules/get-enum'
|
||||
|
||||
const enumStore = useEnum()
|
||||
|
||||
const childRef: any = ref(null)
|
||||
const enumStore = useEnum()
|
||||
|
||||
const pageData: any = usePageData()
|
||||
|
||||
const data: any = reactive({
|
||||
stepName: '',
|
||||
treeData: [],
|
||||
selectData: {},
|
||||
summary: [],
|
||||
ass: [],
|
||||
ope: [],
|
||||
caseType: 0,
|
||||
resultData: {},
|
||||
summary: {},
|
||||
caseType: null,
|
||||
})
|
||||
const customStyle = reactive({
|
||||
borderRadius: '6px',
|
||||
marginBottom: '2px',
|
||||
border: 'none',
|
||||
overflow: 'hidden',
|
||||
})
|
||||
const visible1 = ref(false)
|
||||
|
||||
function clickTree(selectedKeys: any, eventData: any) {
|
||||
if (typeof selectedKeys[0] === 'number') {
|
||||
if (eventData.node?.expand) {
|
||||
childRef.value.expandNode(selectedKeys, true)
|
||||
return
|
||||
} else {
|
||||
data.caseType = eventData.node.type
|
||||
data.resultData = eventData.node.data
|
||||
}
|
||||
data.caseType = eventData.node.type
|
||||
data.selectData = eventData.node.data
|
||||
}
|
||||
|
||||
function doResetSearch() {
|
||||
window.history.back()
|
||||
}
|
||||
@@ -240,17 +127,26 @@
|
||||
.then((res) => {
|
||||
res.data.forEach((item: any) => {
|
||||
const children: any = {
|
||||
title: `${item.case_id}--用例名称:${item.case_name}`,
|
||||
key: item.case_id,
|
||||
title:
|
||||
item.status === 0
|
||||
? `<span style="color: #ff5733;">${item.case_name}</span>`
|
||||
: `<span>${item.case_name}</span>`,
|
||||
key: nanoid(),
|
||||
children: [],
|
||||
expand: true,
|
||||
status: item.status,
|
||||
}
|
||||
if (item.result_data) {
|
||||
item.result_data.forEach((item1: any) => {
|
||||
item.result_data.forEach((result_data: any) => {
|
||||
children['children'].push({
|
||||
title: item1.name,
|
||||
title:
|
||||
result_data.status === 0
|
||||
? `<span style="color: #ff5733;">${result_data.name}</span>`
|
||||
: `<span>${result_data.name}</span>`,
|
||||
key: nanoid(),
|
||||
data: result_data,
|
||||
type: item.type,
|
||||
data: item1,
|
||||
status: result_data.status,
|
||||
children: [],
|
||||
})
|
||||
})
|
||||
@@ -261,47 +157,17 @@
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getLabelByValue(data: any, value: string): string {
|
||||
const list = [...data]
|
||||
for (const item of list) {
|
||||
if (item.children) {
|
||||
list.push(...item.children)
|
||||
}
|
||||
}
|
||||
return list.find((item: any) => item.value === value)?.label
|
||||
}
|
||||
|
||||
function getUiRunSortAss() {
|
||||
getUiPageStepsDetailedAss(null)
|
||||
function doRefreshSummary() {
|
||||
getSystemTestSuiteDetailsSummary(pageData.record.id)
|
||||
.then((res) => {
|
||||
data.ass = res.data
|
||||
data.summary = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getUiRunSortOpe() {
|
||||
getUiPageStepsDetailedOpe(null)
|
||||
.then((res) => {
|
||||
data.ope = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getNodeTitle(title: string) {
|
||||
for (const item of data.treeData) {
|
||||
if (item.title == title && item.error_msg) {
|
||||
return title + '-' + item.error_msg
|
||||
}
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(async () => {
|
||||
doRefresh()
|
||||
getUiRunSortOpe()
|
||||
getUiRunSortAss()
|
||||
})
|
||||
doRefresh()
|
||||
doRefreshSummary()
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
|
||||
@@ -89,21 +89,21 @@
|
||||
{{ record?.project_product?.project?.name + '/' + record?.project_product?.name }}
|
||||
</template>
|
||||
<template v-else-if="item.key === 'test_env'" #cell="{ record }">
|
||||
<a-tag :color="enumStore.status_colors[record.test_env]" size="small">{{
|
||||
enumStore.environment_type[record.test_env]?.title
|
||||
}}</a-tag>
|
||||
<a-tag :color="enumStore.status_colors[record.test_env]" size="small"
|
||||
>{{ enumStore.environment_type[record.test_env]?.title }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.key === 'user'" #cell="{ record }">
|
||||
{{ record.user?.name }}
|
||||
</template>
|
||||
<template v-else-if="item.key === 'tasks'" #cell="{ record }">
|
||||
{{ record.tasks.name }}
|
||||
{{ record.tasks?.name }}
|
||||
</template>
|
||||
<template v-else-if="item.key === 'status'" #cell="{ record }">
|
||||
<a-tag :color="enumStore.status_colors[record.status]" size="small">{{
|
||||
enumStore.task_status[record.status].title
|
||||
}}</a-tag>
|
||||
<a-tag :color="enumStore.status_colors[record.status]" size="small"
|
||||
>{{ enumStore.task_status[record.status].title }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="item.key === 'actions'" #cell="{ record }">
|
||||
<a-space>
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePagination, useRowKey, useRowSelection, useTable } from '@/hooks/table'
|
||||
import { onMounted, nextTick, ref } from 'vue'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { fieldNames } from '@/setting'
|
||||
import * as echarts from 'echarts'
|
||||
@@ -149,6 +149,7 @@
|
||||
const table = useTable()
|
||||
const rowKey = useRowKey('id')
|
||||
const router = useRouter()
|
||||
|
||||
function doRefresh() {
|
||||
let value = getFormItems(conditionItems)
|
||||
value['page'] = pagination.page
|
||||
@@ -160,11 +161,13 @@
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function tableScrollHeight() {
|
||||
const headerHeight = 460
|
||||
const footerHeight = 45
|
||||
return `calc(94vh - ${headerHeight}px - ${footerHeight}px)`
|
||||
}
|
||||
|
||||
function onResetSearch() {
|
||||
conditionItems.forEach((it) => {
|
||||
it.value = ''
|
||||
@@ -181,6 +184,7 @@
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function onRetry(record: any) {
|
||||
getSystemTestSuiteDetailsAllRetry(record.id)
|
||||
.then((res) => {
|
||||
@@ -188,6 +192,7 @@
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
const barChart = ref<HTMLElement>()
|
||||
const myChart1 = ref<any>()
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { reactive, ref } from 'vue'
|
||||
import { FormItem } from '@/types/components'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
export const columns = reactive([
|
||||
export const columns: any = reactive([
|
||||
{
|
||||
title: '步骤名称',
|
||||
dataIndex: 'page_step_name',
|
||||
@@ -10,6 +10,7 @@ export const columns = reactive([
|
||||
{
|
||||
title: '测试结果',
|
||||
dataIndex: 'status',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '错误提示',
|
||||
@@ -17,6 +18,7 @@ export const columns = reactive([
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -31,15 +31,14 @@
|
||||
</a-space>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<span>用例执行顺序:{{ pageData.record.case_flow }}</span>
|
||||
<span v-if="data.elementLocator">元素表达式:{{ data.elementLocator }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
<template #default>
|
||||
<a-card :bordered="false">
|
||||
<div style="display: flex">
|
||||
<div style="width: 50%; margin-right: 10px">
|
||||
<div class="box">
|
||||
<div class="left">
|
||||
<a-tabs default-active-key="2" @tab-click="(key) => switchType(key)">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
@@ -133,20 +132,20 @@
|
||||
{{ record.page_step?.name }}
|
||||
</template>
|
||||
<template v-else-if="item.dataIndex === 'status'" #cell="{ record }">
|
||||
<a-tag :color="enumStore.status_colors[record.status]" size="small">{{
|
||||
enumStore.task_status[record.status].title
|
||||
}}</a-tag>
|
||||
<a-tag :color="enumStore.status_colors[record.status]" size="small"
|
||||
>{{ enumStore.task_status[record.status].title }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="item.dataIndex === 'actions'" #cell="{ record }">
|
||||
<a-button type="text" size="mini" @click="onPageStep(record)"
|
||||
>单步执行</a-button
|
||||
>
|
||||
>单步执行
|
||||
</a-button>
|
||||
<a-button type="text" size="mini" @click="oeFreshSteps(record)"
|
||||
>更新数据</a-button
|
||||
>
|
||||
>更新数据
|
||||
</a-button>
|
||||
<a-button status="danger" type="text" size="mini" @click="onDelete(record)"
|
||||
>删除</a-button
|
||||
>
|
||||
>删除
|
||||
</a-button>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
@@ -184,11 +183,11 @@
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<div style="width: 50%; margin-left: 10px">
|
||||
<div class="right">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" title="步骤数据">
|
||||
<a-list :bordered="false">
|
||||
<template #header> {{ data.selectData?.page_step?.name }} </template>
|
||||
<template #header> {{ data.selectData?.page_step?.name }}</template>
|
||||
<a-list-item
|
||||
v-for="item of data.selectData?.case_data"
|
||||
:key="item.page_step_details_id"
|
||||
@@ -218,13 +217,23 @@
|
||||
v-for="key in Object.keys(item.page_step_details_data)"
|
||||
:key="key"
|
||||
>
|
||||
<div style="display: flex">
|
||||
<span style="width: 13%">{{ key + ':' }}</span>
|
||||
<div style="display: flex; align-items: center; margin-bottom: 12px">
|
||||
<span
|
||||
style="
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
"
|
||||
>
|
||||
{{ key + ':' }}
|
||||
</span>
|
||||
<a-textarea
|
||||
v-model="item.page_step_details_data[key]"
|
||||
@blur="onUpdate"
|
||||
:auto-size="{ minRows: 1, maxRows: 5 }"
|
||||
style="width: 90%"
|
||||
style="flex: 1; margin-left: 12px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -234,79 +243,7 @@
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" title="测试结果">
|
||||
<a-collapse
|
||||
:default-active-key="data.eleResultKey"
|
||||
v-for="item of data.selectData.result_data?.element_result_list"
|
||||
:bordered="false"
|
||||
:key="item.id"
|
||||
destroy-on-hide
|
||||
>
|
||||
<a-collapse-item :header="item.name" :style="customStyle" :key="item.id">
|
||||
<div>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p
|
||||
>操作类型:{{
|
||||
item.type === 0
|
||||
? getLabelByValue(data.ope, item.ope_key)
|
||||
: getLabelByValue(data.ass, item.ope_key)
|
||||
}}</p
|
||||
>
|
||||
<p
|
||||
>表达式类型:{{
|
||||
item.exp ? enumStore.element_exp[item.exp].title : item.exp
|
||||
}}</p
|
||||
>
|
||||
<p
|
||||
>测试结果:{{
|
||||
item.status === 1 ? '通过' : item.status === 0 ? '失败' : '未测试'
|
||||
}}</p
|
||||
>
|
||||
<p>等待时间:{{ item.sleep ? item.sleep : '-' }}</p>
|
||||
<p v-if="item.status === 0">错误提示:{{ item.error_message }}</p>
|
||||
<p v-if="item.expect">预期:{{ item.expect }}</p>
|
||||
<p v-if="item.status === 0">视频路径:{{ item.video_path }}</p>
|
||||
</a-space>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p style="word-wrap: break-word">元素表达式:{{ item.loc }}</p>
|
||||
<p>元素个数:{{ item.ele_quantity }}</p>
|
||||
<p>元素下标:{{ item.sub ? item.sub : '-' }}</p>
|
||||
<div v-if="item.status === 0">
|
||||
<a-image
|
||||
:src="minioURL + '/mango-file/failed_screenshot/' + item.picture_path"
|
||||
title="失败截图"
|
||||
width="260"
|
||||
style="margin-right: 67px; vertical-align: top"
|
||||
:preview-visible="visible1"
|
||||
@preview-visible-change="
|
||||
() => {
|
||||
visible1 = false
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #extra>
|
||||
<div class="actions">
|
||||
<span
|
||||
class="action"
|
||||
@click="
|
||||
() => {
|
||||
visible1 = true
|
||||
}
|
||||
"
|
||||
><icon-eye
|
||||
/></span>
|
||||
<span class="action"><icon-download /></span>
|
||||
<a-tooltip content="失败截图">
|
||||
<span class="action"><icon-info-circle /></span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-image>
|
||||
</div>
|
||||
<p v-if="item.expect">实际:{{ item.actual }}</p>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
<ElementTestReport :result-data="data.selectData.result_data" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
@@ -374,24 +311,25 @@
|
||||
import { usePageData } from '@/store/page-data'
|
||||
import { columns, formItems } from './config'
|
||||
import {
|
||||
putUiCasePutCaseSort,
|
||||
postUiCaseStepsDetailed,
|
||||
deleteUiCaseStepsDetailed,
|
||||
getUiCaseStepsDetailed,
|
||||
getUiCaseStepsRefreshCacheData,
|
||||
postUiCaseStepsDetailed,
|
||||
putUiCasePutCaseSort,
|
||||
putUiCaseStepsDetailed,
|
||||
deleteUiCaseStepsDetailed,
|
||||
} from '@/api/uitest/case-steps-detailed'
|
||||
import { getUserProductAllModuleName } from '@/api/system/product'
|
||||
import { putUiCase, getUiCaseRun } from '@/api/uitest/case'
|
||||
import {
|
||||
getUiPageStepsDetailedOpe,
|
||||
getUiPageStepsDetailedAss,
|
||||
} from '@/api/uitest/page-steps-detailed'
|
||||
import { getUiCaseRun, putUiCase } from '@/api/uitest/case'
|
||||
import { getUiStepsPageStepsName, getUiStepsTest } from '@/api/uitest/page-steps'
|
||||
import { getUiPageName } from '@/api/uitest/page'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { minioURL } from '@/api/axios.config'
|
||||
import { useEnum } from '@/store/modules/get-enum'
|
||||
import ElementTestReport from '@/components/ElementTestReport.vue'
|
||||
import {
|
||||
getUiPageStepsDetailedAss,
|
||||
getUiPageStepsDetailedOpe,
|
||||
} from '@/api/uitest/page-steps-detailed'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const enumStore = useEnum()
|
||||
|
||||
@@ -404,24 +342,14 @@
|
||||
pageName: [],
|
||||
pageStepsName: [],
|
||||
data: [],
|
||||
isAdd: false,
|
||||
updateId: 0,
|
||||
selectData: {},
|
||||
actionTitle: '添加用例步骤',
|
||||
elementLocator: null,
|
||||
ope: [],
|
||||
ass: [],
|
||||
uiType: '2',
|
||||
uiSonType: '11',
|
||||
ass: [],
|
||||
ope: [],
|
||||
})
|
||||
const visible1 = ref(false)
|
||||
|
||||
const customStyle = reactive({
|
||||
borderRadius: '6px',
|
||||
marginBottom: '2px',
|
||||
border: 'none',
|
||||
overflow: 'hidden',
|
||||
})
|
||||
function switchType(key: any) {
|
||||
if (key === '1') {
|
||||
data.uiSonType = '11'
|
||||
@@ -430,9 +358,11 @@
|
||||
}
|
||||
data.uiType = key
|
||||
}
|
||||
|
||||
function switchSonType(key: any) {
|
||||
data.uiSonType = key
|
||||
}
|
||||
|
||||
function addData() {
|
||||
if (data.uiType == '2') {
|
||||
doAppend()
|
||||
@@ -446,10 +376,12 @@
|
||||
pageData.record.posterior_sql.push({ sql: '' })
|
||||
}
|
||||
}
|
||||
|
||||
function removeFrontSql(item: any, index: number) {
|
||||
item.splice(index, 1)
|
||||
upDataCase()
|
||||
}
|
||||
|
||||
function upDataCase() {
|
||||
putUiCase({
|
||||
id: pageData.record.id,
|
||||
@@ -464,6 +396,7 @@
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function doAppend() {
|
||||
modalDialogRef.value?.toggle()
|
||||
formItems.forEach((it) => {
|
||||
@@ -585,6 +518,7 @@
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function onCaseRun() {
|
||||
if (userStore.selected_environment == null) {
|
||||
Message.error('请先选择用例执行的环境')
|
||||
@@ -596,9 +530,11 @@
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function select(record: any) {
|
||||
data.selectData = record
|
||||
}
|
||||
|
||||
function onUpdate() {
|
||||
putUiCaseStepsDetailed(
|
||||
{
|
||||
@@ -612,6 +548,7 @@
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function onPageStep(record: any) {
|
||||
if (userStore.selected_environment == null) {
|
||||
Message.error('请先选择用例执行的环境')
|
||||
@@ -623,8 +560,9 @@
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
function getLabelByValue(data: any, value: string): string {
|
||||
const list = [...data]
|
||||
|
||||
function getLabelByValue(opeData: any, value: string): string {
|
||||
const list = [...opeData]
|
||||
for (const item of list) {
|
||||
if (item.children) {
|
||||
list.push(...item.children)
|
||||
@@ -633,17 +571,18 @@
|
||||
return list.find((item: any) => item.value === value)?.label
|
||||
}
|
||||
|
||||
function getUiRunSortOpe() {
|
||||
getUiPageStepsDetailedOpe(route.query.pageType)
|
||||
function getUiRunSortAss() {
|
||||
getUiPageStepsDetailedAss(null)
|
||||
.then((res) => {
|
||||
data.ope = res.data
|
||||
data.ass = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
function getUiRunSortAss() {
|
||||
getUiPageStepsDetailedAss(route.query.pageType)
|
||||
|
||||
function getUiRunSortOpe() {
|
||||
getUiPageStepsDetailedOpe(null)
|
||||
.then((res) => {
|
||||
data.ass = res.data
|
||||
data.ope = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
@@ -651,25 +590,34 @@
|
||||
onMounted(() => {
|
||||
nextTick(async () => {
|
||||
doRefresh()
|
||||
getUiRunSortOpe()
|
||||
getUiRunSortAss()
|
||||
onProductModuleName()
|
||||
getUiRunSortAss()
|
||||
getUiRunSortOpe()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex; /* 开启flex布局 */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.left {
|
||||
width: 30%; /* 左边区域占据50%的宽度 */
|
||||
margin-right: 10px; /* 设置左边盒子的右边距 */
|
||||
flex: 5;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 70%; /* 右边区域占据50%的宽度 */
|
||||
margin-left: 10px; /* 设置右边盒子的左边距 */
|
||||
flex: 5;
|
||||
padding: 5px;
|
||||
max-width: 60%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</a-card>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="container">
|
||||
<div class="box">
|
||||
<div class="left">
|
||||
<a-card style="border-radius: 10px; overflow: hidden" :bordered="false">
|
||||
<a-table
|
||||
@@ -58,9 +58,11 @@
|
||||
</template>
|
||||
<template v-else-if="item.dataIndex === 'ope_key'" #cell="{ record }">
|
||||
{{
|
||||
record.ope_key
|
||||
? getLabelByValue(record.type, record.ope_key)
|
||||
: record.key
|
||||
record.type === 0
|
||||
? getLabelByValue(data.ope, record.ope_key)
|
||||
: record.type === 1
|
||||
? getLabelByValue(data.ass, record.ope_key)
|
||||
: record.type === 2
|
||||
? record.key
|
||||
: record.key_list
|
||||
}}
|
||||
@@ -89,81 +91,7 @@
|
||||
</a-card>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a-card title="最近一次步骤执行过程" style="overflow: hidden" :bordered="false">
|
||||
<a-collapse
|
||||
:default-active-key="data.eleResultKey"
|
||||
v-for="item of data.result_data?.element_result_list"
|
||||
:bordered="false"
|
||||
:key="item.id"
|
||||
destroy-on-hide
|
||||
>
|
||||
<a-collapse-item :header="item.name" :style="customStyle" :key="item.id">
|
||||
<div>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p
|
||||
>操作类型:{{
|
||||
item.type === 0
|
||||
? getLabelByValue(data.ope, item.ope_key)
|
||||
: getLabelByValue(data.ass, item.ope_key)
|
||||
}}</p
|
||||
>
|
||||
<p
|
||||
>表达式类型:{{
|
||||
item.exp ? enumStore.element_exp[item.exp].title : item.exp
|
||||
}}</p
|
||||
>
|
||||
<p
|
||||
>测试结果:{{
|
||||
item.status === 1 ? '通过' : item.status === 0 ? '失败' : '未测试'
|
||||
}}</p
|
||||
>
|
||||
<p>等待时间:{{ item.sleep ? item.sleep : '-' }}</p>
|
||||
<p v-if="item.status === 0">错误提示:{{ item.error_message }}</p>
|
||||
<p v-if="item.expect">预期:{{ item.expect }}</p>
|
||||
<p v-if="item.status === 0">视频路径:{{ item.video_path }}</p>
|
||||
</a-space>
|
||||
<a-space direction="vertical" style="width: 50%">
|
||||
<p style="word-wrap: break-word">元素表达式:{{ item.loc }}</p>
|
||||
<p>元素个数:{{ item.ele_quantity }}</p>
|
||||
<p>元素下标:{{ item.sub ? item.sub : '-' }}</p>
|
||||
<div v-if="item.status === 0">
|
||||
<a-image
|
||||
:src="minioURL + '/mango-file/failed_screenshot/' + item.picture_path"
|
||||
title="失败截图"
|
||||
width="260"
|
||||
style="margin-right: 67px; vertical-align: top"
|
||||
:preview-visible="visible1"
|
||||
@preview-visible-change="
|
||||
() => {
|
||||
visible1 = false
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #extra>
|
||||
<div class="actions">
|
||||
<span
|
||||
class="action"
|
||||
@click="
|
||||
() => {
|
||||
visible1 = true
|
||||
}
|
||||
"
|
||||
><icon-eye
|
||||
/></span>
|
||||
<span class="action"><icon-download /></span>
|
||||
<a-tooltip content="失败截图">
|
||||
<span class="action"><icon-info-circle /></span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-image>
|
||||
</div>
|
||||
<p v-if="item.expect">实际:{{ item.actual }}</p>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
</a-card>
|
||||
<ElementTestReport :result-data="data.result_data" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -294,7 +222,7 @@
|
||||
import { getUiUiElementName } from '@/api/uitest/element'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { useEnum } from '@/store/modules/get-enum'
|
||||
import { minioURL } from '@/api/axios.config'
|
||||
import ElementTestReport from '@/components/ElementTestReport.vue'
|
||||
|
||||
const enumStore = useEnum()
|
||||
|
||||
@@ -308,21 +236,13 @@
|
||||
isAdd: false,
|
||||
updateId: 0,
|
||||
actionTitle: '添加测试对象',
|
||||
ass: [],
|
||||
ope: [],
|
||||
dataList: [],
|
||||
uiPageName: [],
|
||||
type: 0,
|
||||
plainOptions: [],
|
||||
result_data: {},
|
||||
})
|
||||
const visible1 = ref(false)
|
||||
|
||||
const customStyle = reactive({
|
||||
borderRadius: '6px',
|
||||
marginBottom: '2px',
|
||||
border: 'none',
|
||||
overflow: 'hidden',
|
||||
ass: [],
|
||||
ope: [],
|
||||
})
|
||||
|
||||
function changeStatus(event: number) {
|
||||
@@ -363,26 +283,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getLabelByValue(type: any, value: string): string {
|
||||
if (type === 0) {
|
||||
const data_list = [...data.ope]
|
||||
for (const item of data_list) {
|
||||
if (item.children) {
|
||||
data_list.push(...item.children)
|
||||
}
|
||||
}
|
||||
return data_list.find((item: any) => item.value === value)?.label
|
||||
} else {
|
||||
const data_list = [...data.ass]
|
||||
for (const item of data_list) {
|
||||
if (item.children) {
|
||||
data_list.push(...item.children)
|
||||
}
|
||||
}
|
||||
return data_list.find((item: any) => item.value === value)?.label
|
||||
}
|
||||
}
|
||||
|
||||
function doAppend() {
|
||||
changeStatus(0)
|
||||
data.type = 0
|
||||
@@ -503,22 +403,6 @@
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getUiRunSortAss() {
|
||||
getUiPageStepsDetailedAss(route.query.pageType)
|
||||
.then((res) => {
|
||||
data.ass = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getUiRunSortOpe() {
|
||||
getUiPageStepsDetailedOpe(route.query.pageType)
|
||||
.then((res) => {
|
||||
data.ope = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getEleName() {
|
||||
getUiUiElementName(route.query.pageId)
|
||||
.then((res) => {
|
||||
@@ -647,12 +531,38 @@
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
function getUiRunSortAss() {
|
||||
getUiPageStepsDetailedAss(route.query.pageType)
|
||||
.then((res) => {
|
||||
data.ass = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getUiRunSortOpe() {
|
||||
getUiPageStepsDetailedOpe(route.query.pageType)
|
||||
.then((res) => {
|
||||
data.ope = res.data
|
||||
})
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getLabelByValue(opeData: any, value: string): string {
|
||||
const list = [...opeData]
|
||||
for (const item of list) {
|
||||
if (item.children) {
|
||||
list.push(...item.children)
|
||||
}
|
||||
}
|
||||
return list.find((item: any) => item.value === value)?.label
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
doRefresh()
|
||||
doRefreshSteps(pageData.record.id)
|
||||
getEleName()
|
||||
getUiRunSortAss()
|
||||
getUiRunSortOpe()
|
||||
getEleName()
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
@@ -661,4 +571,23 @@
|
||||
grid-template-columns: 60% 40%; /* 左侧60%,右侧40% */
|
||||
gap: 10px; /* 添加间距 */
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.left {
|
||||
flex: 6;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 4;
|
||||
padding: 5px;
|
||||
max-width: 60%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user