优化了测试报告

This commit is contained in:
毛鹏
2025-03-02 19:28:21 +08:00
parent f8019f8493
commit ce9db82154
30 changed files with 619 additions and 563 deletions

View File

@@ -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"]

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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,

View File

@@ -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

View File

@@ -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="测试环境")

View File

@@ -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)

View File

@@ -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:

View File

@@ -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)

View File

@@ -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'})),

View File

@@ -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

View File

@@ -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(),
})

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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']

View File

@@ -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 }
},
})
}

View 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>

View File

@@ -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%);
}

View 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>

View File

@@ -1,7 +0,0 @@
<template>
<div class="editor-wrapper"> 开发整理中 </div>
</template>
<script lang="ts"></script>
<style lang="less" scoped></style>

View File

@@ -107,7 +107,7 @@ export const useTableColumn = function (
export const useTableIndexColumn = function () {
return {
title: '序号',
title: 'ID',
key: 'index',
width: 80,
dataIndex: 'index',

View File

@@ -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">

View File

@@ -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>()

View File

@@ -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,
},
{

View File

@@ -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>

View File

@@ -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>