fix(测试跟踪): UI测试用例列表批量执行后执行结果未更新

--bug=1018918 --user=李玉号 【UI测试】测试跟踪-测试计划-UI测试用例列表-操作-执行/批量执行后执行结果未更新
https://www.tapd.cn/55049933/s/1347660
This commit is contained in:
shiziyuan9527 2023-03-09 17:31:23 +08:00 committed by fit2-zhao
parent 3f00e66597
commit 0ae8294305
6 changed files with 232 additions and 51 deletions

View File

@ -0,0 +1,11 @@
package io.metersphere.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TestPlanCaseStatusDTO {
private String planCaseId;
private String planCaseStatus;
}

View File

@ -7,7 +7,7 @@ import io.metersphere.base.mapper.ApiExecutionQueueDetailMapper;
import io.metersphere.base.mapper.ApiExecutionQueueMapper;
import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.commons.constants.TestPlanReportStatus;
import io.metersphere.plan.service.TestCaseSyncStatusService;
import io.metersphere.plan.service.AutomationCaseExecOverService;
import io.metersphere.plan.service.TestPlanReportService;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
@ -30,7 +30,7 @@ public class ExecReportListener {
@Resource
private TestPlanReportService testPlanReportService;
@Resource
private TestCaseSyncStatusService testCaseSyncStatusService;
private AutomationCaseExecOverService automationCaseExecOverService;
@KafkaListener(id = CONSUME_ID, topics = KafkaTopicConstants.TEST_PLAN_REPORT_TOPIC, groupId = "${spring.application.name}")
public void consume(ConsumerRecord<?, String> record) {
@ -45,14 +45,9 @@ public class ExecReportListener {
}
/**
* 测试计划相关的自动化用例结束后会根据测试计划的配置判断是否要同步功能用例的状态
* 目前暂时只有这一个需求后续如果有了更多操作建议将该方法内的逻辑处理放入一个共有方法中
*
* @param testId
*/
public void automationCaseTestEnd(String testId) {
testCaseSyncStatusService.checkAndUpdateFunctionCaseStatus(testId);
//自动化用例执行完成之后的后续操作
automationCaseExecOverService.automationCaseExecOver(testId);
}
public void testPlanReportTestEnded(String testPlanReportId) {

View File

@ -0,0 +1,78 @@
package io.metersphere.plan.service;
import io.metersphere.base.domain.TestPlanApiCase;
import io.metersphere.base.domain.TestPlanApiScenario;
import io.metersphere.base.domain.TestPlanLoadCase;
import io.metersphere.base.domain.TestPlanUiScenario;
import io.metersphere.base.mapper.ext.*;
import io.metersphere.dto.TestPlanCaseStatusDTO;
import io.metersphere.utils.JsonUtils;
import io.metersphere.websocket.UICaseStatusHandleSocket;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(rollbackFor = Exception.class)
public class AutomationCaseExecOverService {
@Resource
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
@Resource
private ExtTestPlanLoadCaseMapper extTestPlanLoadCaseMapper;
@Resource
private ExtTestPlanUiCaseMapper extTestPlanUiCaseMapper;
@Resource
private TestCaseSyncStatusService testCaseSyncStatusService;
public void automationCaseExecOver(String testId) {
TestPlanApiCase testPlanApiCase = null;
TestPlanApiScenario testPlanApiScenario = null;
TestPlanLoadCase testPlanLoadCase = null;
TestPlanUiScenario testPlanUiScenario = null;
testPlanApiCase = extTestPlanApiCaseMapper.selectBaseInfoById(testId);
if (testPlanApiCase == null) {
testPlanApiScenario = extTestPlanScenarioCaseMapper.selectBaseInfoById(testId);
}
if (ObjectUtils.allNull(testPlanApiCase, testPlanApiScenario)) {
testPlanLoadCase = extTestPlanLoadCaseMapper.selectBaseInfoById(testId);
}
if (ObjectUtils.allNull(testPlanApiCase, testPlanApiScenario, testPlanLoadCase)) {
testPlanUiScenario = extTestPlanUiCaseMapper.selectBaseInfoById(testId);
}
String automationCaseId = null, planId = null;
String triggerCaseExecResult = null;
if (testPlanApiCase != null) {
automationCaseId = testPlanApiCase.getApiCaseId();
planId = testPlanApiCase.getTestPlanId();
triggerCaseExecResult = testPlanApiCase.getStatus();
} else if (testPlanApiScenario != null) {
automationCaseId = testPlanApiScenario.getApiScenarioId();
planId = testPlanApiScenario.getTestPlanId();
triggerCaseExecResult = testPlanApiScenario.getLastResult();
} else if (testPlanLoadCase != null) {
automationCaseId = testPlanLoadCase.getLoadCaseId();
planId = testPlanLoadCase.getTestPlanId();
triggerCaseExecResult = testPlanLoadCase.getStatus();
} else if (testPlanUiScenario != null) {
automationCaseId = testPlanUiScenario.getUiScenarioId();
planId = testPlanUiScenario.getTestPlanId();
triggerCaseExecResult = testPlanUiScenario.getLastResult();
}
if (StringUtils.isNoneEmpty(automationCaseId, planId, triggerCaseExecResult)) {
//检查是否需要自动更新功能用例状态
testCaseSyncStatusService.updateFunctionCaseStatusByAutomationCaseId(automationCaseId, planId, triggerCaseExecResult);
}
if (testPlanUiScenario != null) {
//UI执行完成发送Socket
UICaseStatusHandleSocket.sendMessageSingle(planId, JsonUtils.toJSONString(TestPlanCaseStatusDTO.builder().planCaseId(testPlanUiScenario.getId()).planCaseStatus(triggerCaseExecResult)));
}
}
}

View File

@ -20,7 +20,6 @@ import io.metersphere.utils.BatchProcessingUtil;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -112,49 +111,9 @@ public class TestCaseSyncStatusService {
}
}
public void checkAndUpdateFunctionCaseStatus(String testId) {
TestPlanApiCase testPlanApiCase = null;
TestPlanApiScenario testPlanApiScenario = null;
TestPlanLoadCase testPlanLoadCase = null;
TestPlanUiScenario testPlanUiScenario = null;
testPlanApiCase = extTestPlanApiCaseMapper.selectBaseInfoById(testId);
if (testPlanApiCase == null) {
testPlanApiScenario = extTestPlanScenarioCaseMapper.selectBaseInfoById(testId);
}
if (ObjectUtils.allNull(testPlanApiCase, testPlanApiScenario)) {
testPlanLoadCase = extTestPlanLoadCaseMapper.selectBaseInfoById(testId);
}
if (ObjectUtils.allNull(testPlanApiCase, testPlanApiScenario, testPlanLoadCase)) {
testPlanUiScenario = extTestPlanUiCaseMapper.selectBaseInfoById(testId);
}
String automationCaseId = null, planId = null;
String triggerCaseExecResult = null;
if (testPlanApiCase != null) {
automationCaseId = testPlanApiCase.getApiCaseId();
planId = testPlanApiCase.getTestPlanId();
triggerCaseExecResult = testPlanApiCase.getStatus();
} else if (testPlanApiScenario != null) {
automationCaseId = testPlanApiScenario.getApiScenarioId();
planId = testPlanApiScenario.getTestPlanId();
triggerCaseExecResult = testPlanApiScenario.getLastResult();
} else if (testPlanLoadCase != null) {
automationCaseId = testPlanLoadCase.getLoadCaseId();
planId = testPlanLoadCase.getTestPlanId();
triggerCaseExecResult = testPlanLoadCase.getStatus();
} else if (testPlanUiScenario != null) {
automationCaseId = testPlanUiScenario.getUiScenarioId();
planId = testPlanUiScenario.getTestPlanId();
triggerCaseExecResult = testPlanUiScenario.getLastResult();
}
if (StringUtils.isNoneEmpty(automationCaseId, planId, triggerCaseExecResult)) {
this.updateFunctionCaseStatusByAutomationCaseId(automationCaseId, planId, triggerCaseExecResult);
}
}
private void updateFunctionCaseStatusByAutomationCaseId(String automationCaseId, String testPlanId, String triggerCaseRunResult) {
public void updateFunctionCaseStatusByAutomationCaseId(String automationCaseId, String testPlanId, String triggerCaseRunResult) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(testPlanId);
if (testPlan != null && testPlan.getAutomaticStatusUpdate()) {
HttpHeaderUtils.runAsUser(baseUserService.getUserDTO(testPlan.getCreator()));

View File

@ -0,0 +1,103 @@
package io.metersphere.websocket;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.utils.LoggerUtil;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 推送消息更新UI列表用例状态
*/
@Component
@ServerEndpoint("/websocket/plan/ui/{planId}")
public class UICaseStatusHandleSocket {
public static final Map<String, Set<Session>> ONLINE_PLAN_UI_SESSIONS = new ConcurrentHashMap<>();
public static void sendMessage(Session session, String message) {
if (session == null) {
return;
}
// 替换了web容器后 jetty没有设置永久有效的参数这里暂时设置超时时间为一天
session.setMaxIdleTimeout(86400000L);
RemoteEndpoint.Async async = session.getAsyncRemote();
if (async == null) {
return;
}
async.sendText(message);
}
public static void sendMessageSingle(String planId, String message) {
ONLINE_PLAN_UI_SESSIONS.get(planId).forEach(session -> sendMessage(session, message));
}
/**
* 连接成功响应
*/
@OnOpen
public void openSession(@PathParam("planId") String planId, Session session) {
Set<Session> sessions = ONLINE_PLAN_UI_SESSIONS.getOrDefault(planId, new HashSet<>());
sessions.add(session);
ONLINE_PLAN_UI_SESSIONS.put(planId, sessions);
sendMessage(session, "CONN_SUCCEEDED");
LoggerUtil.info("客户端: [" + planId + "] : 连接成功!" + ONLINE_PLAN_UI_SESSIONS.size());
}
/**
* 收到消息响应
*/
@OnMessage
public void onMessage(@PathParam("planId") String planId, String message) {
LoggerUtil.info("服务器收到:[" + planId + "] : " + message);
sendMessageSingle(planId, message);
}
/**
* 连接关闭响应
*/
@OnClose
public void onClose(@PathParam("planId") String planId, Session session) throws IOException {
//当前的Session 移除
Set<Session> sessions = ONLINE_PLAN_UI_SESSIONS.get(planId);
if (CollectionUtils.isNotEmpty(sessions)) {
LogUtil.info("剔除一个socket链接: {} - {}", planId, session.getId());
sessions.remove(session);
LogUtil.info("在线socket链接: {}, size: {}", planId, sessions.size());
} else {
ONLINE_PLAN_UI_SESSIONS.remove(planId);
LoggerUtil.info("[" + planId + "] : 断开连接!" + ONLINE_PLAN_UI_SESSIONS.size());
//并且通知其他人当前用户已经断开连接了
session.close();
}
}
/**
* 连接异常响应
*/
@OnError
public void onError(Session session, Throwable throwable) throws IOException {
session.close();
}
/**
* 每一分钟群发一次心跳检查
*/
@Scheduled(cron = "0 0/1 * * * ?")
public void heartbeatCheck() {
for (Set<Session> sessions : ONLINE_PLAN_UI_SESSIONS.values()) {
sessions.forEach(session -> sendMessage(session, "heartbeat check"));
}
}
}

View File

@ -221,6 +221,7 @@ import i18n from "@/i18n";
import MicroApp from "metersphere-frontend/src/components/MicroApp";
import MsTestPlanApiStatus from "@/business/plan/view/comonents/api/TestPlanApiStatus";
import UiRunMode from "@/business/plan/view/comonents/ui/UiRunMode";
import {baseSocket} from "@/api/base-network";
export default {
name: "MsTestPlanUiScenarioList",
@ -340,7 +341,13 @@ export default {
return editTestPlanUiScenarioCaseOrder;
}
},
beforeDestroy() {
if (this.websocket && this.websocket.close instanceof Function) {
this.websocket.close();
}
},
created() {
this.initWebSocket();
this.search();
this.getVersionOptions();
},
@ -354,6 +361,34 @@ export default {
}
},
methods: {
initWebSocket() {
this.websocket = baseSocket("/plan/ui/" + this.planId);
this.websocket.onmessage = this.onMessage;
this.websocket.onopen = this.onOpen;
this.websocket.onerror = this.onError;
this.websocket.onclose = this.onClose;
},
onOpen() {
},
onError() {
},
onMessage(e) {
if (e.data === 'CONN_SUCCEEDED') {
return;
}
try {
let obj = JSON.parse(e.data);
let {planCaseId, planCaseStatus} = obj;
let data = this.tableData.filter(d => d.id === planCaseId);
if (data.length > 0) {
data[0]['lastResult'] = planCaseStatus;
}
} catch (ex) {
// nothing
}
},
onClose() {
},
filterSearch() {
//
this.currentPage = 1;