From 3f17c18741b0253b817cd9b6234865dd5f377a1d Mon Sep 17 00:00:00 2001 From: Le Zhou <2428499107@qq.com> Date: Fri, 14 Jul 2023 15:43:50 +0800 Subject: [PATCH 1/8] draft: parser xml file to tree node --- center/build.gradle | 1 + .../center/service/CaseGenerationService.java | 82 ++++++ .../TestCaseGenerationServiceTest.java | 19 ++ center/src/test/resources/test_route_map.xml | 272 ++++++++++++++++++ .../hydralab/common/util/PageNode.java | 44 +++ 5 files changed, 418 insertions(+) create mode 100644 center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java create mode 100644 center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java create mode 100644 center/src/test/resources/test_route_map.xml create mode 100644 common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java diff --git a/center/build.gradle b/center/build.gradle index c2f4c4172..411dc4caf 100644 --- a/center/build.gradle +++ b/center/build.gradle @@ -27,6 +27,7 @@ tasks.withType(JavaCompile) { } dependencies { + implementation 'org.dom4j:dom4j:2.1.4' testCompile 'org.mockito:mockito-core:3.12.4' testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootWebVersion testCompile 'me.paulschwarz:spring-dotenv:2.3.0' diff --git a/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java b/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java new file mode 100644 index 000000000..f38b19d5e --- /dev/null +++ b/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.center.service; + +import com.microsoft.hydralab.common.util.PageNode; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author zhoule + * @date 07/14/2023 + */ + +@Service +public class CaseGenerationService { + + public PageNode parserXMLToPageNode(String xmlFilePath) { + // leverage dom4j to load xml + Document document = null; + SAXReader saxReader = new SAXReader(); + try { + document = saxReader.read(xmlFilePath); + } catch (DocumentException e) { + throw new RuntimeException(e); + } + List pages = document.getRootElement().element("graph").element("nodes").elements("node"); + List actions = document.getRootElement().element("graph").element("edges").elements("edge"); + + Map pageNodes = new HashMap<>(); + + for (Element page : pages) { + PageNode pageNode = new PageNode(); + int id = Integer.parseInt(page.attributeValue("id")); + pageNode.setId(id); + pageNodes.put(id, pageNode); + } + + for (Element action : actions) { + int source = Integer.parseInt(action.attributeValue("source")); + int target = Integer.parseInt(action.attributeValue("target")); + if(source == target) { + continue; + } + int actionId = Integer.parseInt(action.attributeValue("id")); + pageNodes.get(source).getActionInfoList().add(parserAction(action)); + pageNodes.get(source).getChildPageNodeMap().put(actionId, pageNodes.get(target)); + } + + return pageNodes.get(0); + } + + private PageNode.ActionInfo parserAction(Element element) { + PageNode.ActionInfo actionInfo = new PageNode.ActionInfo(); + actionInfo.setId(Integer.parseInt(element.attributeValue("id"))); + actionInfo.setActionType("click"); + + PageNode.ElementInfo elementInfo = new PageNode.ElementInfo(); + String sourceCode = element.element("attvalues").element("attvalue").attributeValue("value"); + elementInfo.setText(extractElementAttr("Text", sourceCode)); + elementInfo.setClassName(extractElementAttr("Class", sourceCode)); + elementInfo.setClickable(Boolean.parseBoolean(extractElementAttr("Clickable", sourceCode))); + elementInfo.setResourceId(extractElementAttr("ResourceID", sourceCode)); + actionInfo.setTestElement(elementInfo); + return actionInfo; + } + + private static String extractElementAttr(String attrName, String elementStr) { + String[] attrs = elementStr.split(attrName + ": "); + if (attrs.length > 1 && !attrs[1].startsWith(",")) { + return attrs[1].split(",")[0]; + } + return ""; + } +} diff --git a/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java b/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java new file mode 100644 index 000000000..97b00eb7c --- /dev/null +++ b/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java @@ -0,0 +1,19 @@ +package com.microsoft.hydralab.center.service; + +import com.microsoft.hydralab.center.test.BaseTest; +import com.microsoft.hydralab.common.util.PageNode; +import org.junit.jupiter.api.Test; + +import javax.annotation.Resource; + +class TestCaseGenerationServiceTest extends BaseTest { + + @Resource + CaseGenerationService caseGenerationService; + + @Test + void testParserXMLToPageNode() { + PageNode rootNode = caseGenerationService.parserXMLToPageNode("src/test/resources/test_route_map.xml"); + System.out.println(rootNode); + } +} \ No newline at end of file diff --git a/center/src/test/resources/test_route_map.xml b/center/src/test/resources/test_route_map.xml new file mode 100644 index 000000000..d203f0a3c --- /dev/null +++ b/center/src/test/resources/test_route_map.xml @@ -0,0 +1,272 @@ + + + + NetworkX 2.8.8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java b/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java new file mode 100644 index 000000000..6133047c6 --- /dev/null +++ b/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.common.util; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author zhoule + * @date 07/14/2023 + */ + +@Data +public class PageNode { + int id; + List actionInfoList = new ArrayList<>(); + Map childPageNodeMap = new HashMap<>(); + + @Data + public static class ElementInfo { + int index; + String className; + String text; + boolean clickable; + String resourceId; + } + + @Data + public static class ActionInfo { + Integer id; + ElementInfo testElement; + String actionType; + String driverId; + + String description; + Map arguments; + boolean isOptional; + } +} From 2f11560c3b77ae558105a51620cbde0cb066def1 Mon Sep 17 00:00:00 2001 From: Le Zhou <2428499107@qq.com> Date: Fri, 14 Jul 2023 15:48:52 +0800 Subject: [PATCH 2/8] Update PageNode.java --- .../main/java/com/microsoft/hydralab/common/util/PageNode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java b/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java index 6133047c6..b854db540 100644 --- a/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java +++ b/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java @@ -19,6 +19,7 @@ public class PageNode { int id; List actionInfoList = new ArrayList<>(); + // key: action id, value: child page node Map childPageNodeMap = new HashMap<>(); @Data From 1b980a6e3db2a0cf9279f7d1e3aa6efcbb8a55d9 Mon Sep 17 00:00:00 2001 From: Le Zhou <2428499107@qq.com> Date: Tue, 18 Jul 2023 10:00:49 +0800 Subject: [PATCH 3/8] update Prompt --- center/build.gradle | 2 + .../center/service/LongChainExample.java | 41 ++++++++++++++++ .../center/service/LongChainExampleTest.java | 48 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 center/src/main/java/com/microsoft/hydralab/center/service/LongChainExample.java create mode 100644 center/src/test/java/com/microsoft/hydralab/center/service/LongChainExampleTest.java diff --git a/center/build.gradle b/center/build.gradle index 411dc4caf..c9e4798d6 100644 --- a/center/build.gradle +++ b/center/build.gradle @@ -27,6 +27,8 @@ tasks.withType(JavaCompile) { } dependencies { + implementation 'dev.langchain4j:langchain4j:0.11.0' + implementation 'dev.langchain4j:langchain4j-pinecone:0.11.0' implementation 'org.dom4j:dom4j:2.1.4' testCompile 'org.mockito:mockito-core:3.12.4' testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootWebVersion diff --git a/center/src/main/java/com/microsoft/hydralab/center/service/LongChainExample.java b/center/src/main/java/com/microsoft/hydralab/center/service/LongChainExample.java new file mode 100644 index 000000000..8aa738f25 --- /dev/null +++ b/center/src/main/java/com/microsoft/hydralab/center/service/LongChainExample.java @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.center.service; + +import dev.langchain4j.model.input.structured.StructuredPrompt; + +/** + * @author zhoule + * @date 07/13/2023 + */ + +public class LongChainExample { + + @StructuredPrompt({ + "I want you to act as a software tester. I will provide a route map of a mobile application and it will be your job to write a test case. ", + "The case should be in maestro script format. This is a maestro example", + "{{maestroExample}}", + "Firstly I will introduce the format of the route map.", + "1. It is a unidirectional ordered graph in xml format, the nodes attribute are the pages of app and the id property of each node is the unique id of page. " + + "By the way the id of node equals -1 means the app has not been opened.", + "2. The edges attributes means the only way of jumping from a page to another page. The source property is the unique id of original page and the target property " + + "is the unique id of the page after jumping. The attvalue of each edge means the operation type such launch app, click button, click testview etc..", + "The commands that maestro supported is in the site https://maestro.mobile.dev/api-reference/commands.", + "Requirements:", + "1. the case should start from node which id is -1.", + "2. the case must follow the direction of the edge.", + "3. the case should jump as many pages as possible of the app.", + "4. the page can be visited only once", + "5. you can't use the back command", + "6. add comment to case declare current page id", + "The first route map is {{routeMap}}", + "please generate a maestro script for this route map." + }) + static class MaestroCaseGeneration { + + String maestroExample; + String routeMap; + } + +} diff --git a/center/src/test/java/com/microsoft/hydralab/center/service/LongChainExampleTest.java b/center/src/test/java/com/microsoft/hydralab/center/service/LongChainExampleTest.java new file mode 100644 index 000000000..5963d239d --- /dev/null +++ b/center/src/test/java/com/microsoft/hydralab/center/service/LongChainExampleTest.java @@ -0,0 +1,48 @@ +package com.microsoft.hydralab.center.service; + +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.output.Result; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.io.SAXReader; +import org.junit.jupiter.api.Test; + +class LongChainExampleTest { + static String apiKey = "**************"; // https://platform.openai.com/account/api-keys + static ChatLanguageModel model = OpenAiChatModel.withApiKey(apiKey); + + @Test + void testChain() { + + LongChainExample.MaestroCaseGeneration prompt = new LongChainExample.MaestroCaseGeneration(); + Document document = null; + SAXReader saxReader = new SAXReader(); + try { + document = saxReader.read("src/test/resources/test_route_map.xml"); + } catch (DocumentException e) { + throw new RuntimeException(e); + } + prompt.maestroExample = "appId: {the id of test app }\n" + + "---\n" + + "- launchApp\n" + + "- tapOn: \"Create new contact\"\n" + + "- tapOn: \"First name\"\n" + + "- inputRandomPersonName\n" + + "- tapOn: \"Last name\"\n" + + "- inputRandomPersonName\n" + + "- tapOn: \"Phone\"\n" + + "- inputRandomNumber:\n" + + " length: 10\n" + + "- back\n" + + "- tapOn: \"Email\"\n" + + "- inputRandomEmail\n" + + "- tapOn: \"Save\""; + prompt.routeMap = document.asXML(); + + Result result = model.sendUserMessage(prompt); + System.out.println(result.get().text()); + } + +} \ No newline at end of file From 7affe360533aaca5a307f28478f8b98d6e465921 Mon Sep 17 00:00:00 2001 From: dexterdreeeam <43837899+DexterDreeeam@users.noreply.github.com> Date: Tue, 18 Jul 2023 20:16:52 +0800 Subject: [PATCH 4/8] update --- center/src/main/resources/prompts/case_generation/_.template | 0 center/src/main/resources/prompts/exploration/_.template | 0 .../resources/prompts/result_analysis/exception_analysis.template | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 center/src/main/resources/prompts/case_generation/_.template create mode 100644 center/src/main/resources/prompts/exploration/_.template create mode 100644 center/src/main/resources/prompts/result_analysis/exception_analysis.template diff --git a/center/src/main/resources/prompts/case_generation/_.template b/center/src/main/resources/prompts/case_generation/_.template new file mode 100644 index 000000000..e69de29bb diff --git a/center/src/main/resources/prompts/exploration/_.template b/center/src/main/resources/prompts/exploration/_.template new file mode 100644 index 000000000..e69de29bb diff --git a/center/src/main/resources/prompts/result_analysis/exception_analysis.template b/center/src/main/resources/prompts/result_analysis/exception_analysis.template new file mode 100644 index 000000000..e69de29bb From 42c3a4e5ca13d3b0a76ced841cad447bca26ffd1 Mon Sep 17 00:00:00 2001 From: Le Zhou <2428499107@qq.com> Date: Wed, 19 Jul 2023 10:09:40 +0800 Subject: [PATCH 5/8] Maestro case generation --- .../center/service/CaseGenerationService.java | 124 +++++++++++++++++- .../TestCaseGenerationServiceTest.java | 12 +- .../hydralab/common/util/PageNode.java | 14 +- 3 files changed, 144 insertions(+), 6 deletions(-) diff --git a/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java b/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java index f38b19d5e..e443264b8 100644 --- a/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java +++ b/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java @@ -3,13 +3,20 @@ package com.microsoft.hydralab.center.service; +import com.microsoft.hydralab.center.util.CenterConstant; +import com.microsoft.hydralab.common.util.DateUtil; +import com.microsoft.hydralab.common.util.FileUtil; +import com.microsoft.hydralab.common.util.HydraLabRuntimeException; import com.microsoft.hydralab.common.util.PageNode; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import java.io.File; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,7 +30,7 @@ public class CaseGenerationService { public PageNode parserXMLToPageNode(String xmlFilePath) { - // leverage dom4j to load xml + // read xml file, get page node and action info Document document = null; SAXReader saxReader = new SAXReader(); try { @@ -35,14 +42,14 @@ public PageNode parserXMLToPageNode(String xmlFilePath) { List actions = document.getRootElement().element("graph").element("edges").elements("edge"); Map pageNodes = new HashMap<>(); - + // init page node for (Element page : pages) { PageNode pageNode = new PageNode(); int id = Integer.parseInt(page.attributeValue("id")); pageNode.setId(id); pageNodes.put(id, pageNode); } - + // init action info for (Element action : actions) { int source = Integer.parseInt(action.attributeValue("source")); int target = Integer.parseInt(action.attributeValue("target")); @@ -50,15 +57,37 @@ public PageNode parserXMLToPageNode(String xmlFilePath) { continue; } int actionId = Integer.parseInt(action.attributeValue("id")); + //link action to page pageNodes.get(source).getActionInfoList().add(parserAction(action)); + //link page to page pageNodes.get(source).getChildPageNodeMap().put(actionId, pageNodes.get(target)); } - return pageNodes.get(0); } + /** + * explore all path of page node + * @param pageNode + * @param nodePath + * @param action + * @param explorePaths + */ + public void explorePageNodePath(PageNode pageNode, String nodePath, String action, List explorePaths) { + if (pageNode.getChildPageNodeMap().isEmpty()) { + explorePaths.add(new PageNode.ExplorePath(nodePath + "_" + pageNode.getId(), action)); + return; + } + + for (Map.Entry entry : pageNode.getChildPageNodeMap().entrySet()) { + explorePageNodePath(entry.getValue(), + StringUtils.isEmpty(nodePath) ? String.valueOf(pageNode.getId()) : nodePath + "_" + pageNode.getId(), + StringUtils.isEmpty(action) ? String.valueOf(entry.getKey()) : action + "," + entry.getKey(), explorePaths); + } + } + private PageNode.ActionInfo parserAction(Element element) { PageNode.ActionInfo actionInfo = new PageNode.ActionInfo(); + Map arguments = new HashMap<>(); actionInfo.setId(Integer.parseInt(element.attributeValue("id"))); actionInfo.setActionType("click"); @@ -69,6 +98,12 @@ private PageNode.ActionInfo parserAction(Element element) { elementInfo.setClickable(Boolean.parseBoolean(extractElementAttr("Clickable", sourceCode))); elementInfo.setResourceId(extractElementAttr("ResourceID", sourceCode)); actionInfo.setTestElement(elementInfo); + if (!StringUtils.isEmpty(elementInfo.getText())) { + arguments.put("defaultValue", elementInfo.getText()); + } else if (!StringUtils.isEmpty(elementInfo.getResourceId())) { + arguments.put("id", elementInfo.getResourceId()); + } + actionInfo.setArguments(arguments); return actionInfo; } @@ -79,4 +114,85 @@ private static String extractElementAttr(String attrName, String elementStr) { } return ""; } + + /** + * generate maestro case files and zip them + * @param pageNode + * @param explorePaths + * @return + */ + public File generateMaestroCases(PageNode pageNode, List explorePaths) { + // create temp folder to store case files + File tempFolder = new File(CenterConstant.CENTER_TEMP_FILE_DIR, DateUtil.fileNameDateFormat.format(new Date())); + if (!tempFolder.exists()) { + tempFolder.mkdirs(); + } + // generate case files + for (PageNode.ExplorePath explorePath : explorePaths) { + generateMaestroCase(pageNode, explorePath, tempFolder); + } + if (tempFolder.listFiles().length == 0) { + return null; + } + // zip temp folder + File zipFile = new File(tempFolder.getParent() + "/" + tempFolder.getName() + ".zip"); + FileUtil.zipFile(tempFolder.getAbsolutePath(), zipFile.getAbsolutePath()); + FileUtil.deleteFile(tempFolder); + return zipFile; + } + + private File generateMaestroCase(PageNode pageNode, PageNode.ExplorePath explorePath, File caseFolder) { + File maestroCaseFile = new File(caseFolder, explorePath.getPath() + ".yaml"); + String caseContent = buildConfigSection(pageNode.getPageName()); + caseContent += buildDelimiter(); + caseContent += buildCommandSection("launch", null); + String[] actionIds = explorePath.getActions().split(","); + PageNode pageNodeCopy = pageNode; + for (String actionId : actionIds) { + PageNode.ActionInfo action = pageNodeCopy.getActionInfoList().stream().filter(actionInfo -> actionInfo.getId() == Integer.parseInt(actionId)).findFirst().get(); + caseContent += buildCommandSection(action.getActionType(), action.getArguments()); + pageNodeCopy = pageNodeCopy.getChildPageNodeMap().get(Integer.parseInt(actionId)); + } + caseContent += buildCommandSection("stop", null); + FileUtil.writeToFile(caseContent, maestroCaseFile.getAbsolutePath()); + return maestroCaseFile; + } + + private String buildConfigSection(String appId) { + return "appId: " + appId + "\n"; + } + + private String buildDelimiter() { + return "---\n"; + } + + public String buildCommandSection(String actionType, Map arguments) { + String command = "-"; + switch (actionType) { + case "launch": + command = command + " launchApp\n"; + break; + case "click": + command = command + " tapOn:"; + if (arguments.size() == 0) { + throw new HydraLabRuntimeException("arguments is empty"); + } + if (arguments.containsKey("defaultValue")) { + command = command + " " + arguments.get("defaultValue") + "\n"; + break; + } + command = command + "\n"; + for (String key : arguments.keySet()) { + command = command + " " + key + ": \"" + arguments.get(key) + "\"\n"; + } + break; + case "stop": + command = command + " stopApp\n"; + break; + default: + throw new HydraLabRuntimeException("Unsupported action type: " + actionType); + } + return command; + } + } diff --git a/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java b/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java index 97b00eb7c..47a11800e 100644 --- a/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java +++ b/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java @@ -2,9 +2,13 @@ import com.microsoft.hydralab.center.test.BaseTest; import com.microsoft.hydralab.common.util.PageNode; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import javax.annotation.Resource; +import java.io.File; +import java.util.ArrayList; +import java.util.List; class TestCaseGenerationServiceTest extends BaseTest { @@ -14,6 +18,12 @@ class TestCaseGenerationServiceTest extends BaseTest { @Test void testParserXMLToPageNode() { PageNode rootNode = caseGenerationService.parserXMLToPageNode("src/test/resources/test_route_map.xml"); - System.out.println(rootNode); + Assertions.assertNotNull(rootNode, "parser xml to page node failed"); + rootNode.setPageName("com.microsoft.appmanager"); + List explorePaths = new ArrayList<>(); + Assertions.assertEquals(explorePaths.size(), 16, "explore path size is not correct"); + caseGenerationService.explorePageNodePath(rootNode, "", "", explorePaths); + File caseZipFile = caseGenerationService.generateMaestroCases(rootNode, explorePaths); + Assertions.assertTrue(caseZipFile.exists()); } } \ No newline at end of file diff --git a/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java b/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java index b854db540..65ef1633b 100644 --- a/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java +++ b/common/src/main/java/com/microsoft/hydralab/common/util/PageNode.java @@ -18,6 +18,7 @@ @Data public class PageNode { int id; + String pageName; List actionInfoList = new ArrayList<>(); // key: action id, value: child page node Map childPageNodeMap = new HashMap<>(); @@ -33,7 +34,7 @@ public static class ElementInfo { @Data public static class ActionInfo { - Integer id; + int id; ElementInfo testElement; String actionType; String driverId; @@ -42,4 +43,15 @@ public static class ActionInfo { Map arguments; boolean isOptional; } + + @Data + public static class ExplorePath { + String path; + String actions; + + public ExplorePath(String path, String action) { + this.path = path; + this.actions = action; + } + } } From ddf93f04392a298c51fd7e5bb3e37e8161ddaa8b Mon Sep 17 00:00:00 2001 From: Le Zhou <2428499107@qq.com> Date: Mon, 24 Jul 2023 15:31:22 +0800 Subject: [PATCH 6/8] rebuild case generation code --- .../center/service/CaseGenerationService.java | 198 ------------------ .../generation/AbstractCaseGeneration.java | 112 ++++++++++ .../MaestroCaseGenerationService.java | 107 ++++++++++ .../generation/T2CCaseGenerationService.java | 28 +++ .../TestCaseGenerationServiceTest.java | 8 +- 5 files changed, 252 insertions(+), 201 deletions(-) delete mode 100644 center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java create mode 100644 center/src/main/java/com/microsoft/hydralab/center/service/generation/AbstractCaseGeneration.java create mode 100644 center/src/main/java/com/microsoft/hydralab/center/service/generation/MaestroCaseGenerationService.java create mode 100644 center/src/main/java/com/microsoft/hydralab/center/service/generation/T2CCaseGenerationService.java diff --git a/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java b/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java deleted file mode 100644 index e443264b8..000000000 --- a/center/src/main/java/com/microsoft/hydralab/center/service/CaseGenerationService.java +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -package com.microsoft.hydralab.center.service; - -import com.microsoft.hydralab.center.util.CenterConstant; -import com.microsoft.hydralab.common.util.DateUtil; -import com.microsoft.hydralab.common.util.FileUtil; -import com.microsoft.hydralab.common.util.HydraLabRuntimeException; -import com.microsoft.hydralab.common.util.PageNode; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.Element; -import org.dom4j.io.SAXReader; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.io.File; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author zhoule - * @date 07/14/2023 - */ - -@Service -public class CaseGenerationService { - - public PageNode parserXMLToPageNode(String xmlFilePath) { - // read xml file, get page node and action info - Document document = null; - SAXReader saxReader = new SAXReader(); - try { - document = saxReader.read(xmlFilePath); - } catch (DocumentException e) { - throw new RuntimeException(e); - } - List pages = document.getRootElement().element("graph").element("nodes").elements("node"); - List actions = document.getRootElement().element("graph").element("edges").elements("edge"); - - Map pageNodes = new HashMap<>(); - // init page node - for (Element page : pages) { - PageNode pageNode = new PageNode(); - int id = Integer.parseInt(page.attributeValue("id")); - pageNode.setId(id); - pageNodes.put(id, pageNode); - } - // init action info - for (Element action : actions) { - int source = Integer.parseInt(action.attributeValue("source")); - int target = Integer.parseInt(action.attributeValue("target")); - if(source == target) { - continue; - } - int actionId = Integer.parseInt(action.attributeValue("id")); - //link action to page - pageNodes.get(source).getActionInfoList().add(parserAction(action)); - //link page to page - pageNodes.get(source).getChildPageNodeMap().put(actionId, pageNodes.get(target)); - } - return pageNodes.get(0); - } - - /** - * explore all path of page node - * @param pageNode - * @param nodePath - * @param action - * @param explorePaths - */ - public void explorePageNodePath(PageNode pageNode, String nodePath, String action, List explorePaths) { - if (pageNode.getChildPageNodeMap().isEmpty()) { - explorePaths.add(new PageNode.ExplorePath(nodePath + "_" + pageNode.getId(), action)); - return; - } - - for (Map.Entry entry : pageNode.getChildPageNodeMap().entrySet()) { - explorePageNodePath(entry.getValue(), - StringUtils.isEmpty(nodePath) ? String.valueOf(pageNode.getId()) : nodePath + "_" + pageNode.getId(), - StringUtils.isEmpty(action) ? String.valueOf(entry.getKey()) : action + "," + entry.getKey(), explorePaths); - } - } - - private PageNode.ActionInfo parserAction(Element element) { - PageNode.ActionInfo actionInfo = new PageNode.ActionInfo(); - Map arguments = new HashMap<>(); - actionInfo.setId(Integer.parseInt(element.attributeValue("id"))); - actionInfo.setActionType("click"); - - PageNode.ElementInfo elementInfo = new PageNode.ElementInfo(); - String sourceCode = element.element("attvalues").element("attvalue").attributeValue("value"); - elementInfo.setText(extractElementAttr("Text", sourceCode)); - elementInfo.setClassName(extractElementAttr("Class", sourceCode)); - elementInfo.setClickable(Boolean.parseBoolean(extractElementAttr("Clickable", sourceCode))); - elementInfo.setResourceId(extractElementAttr("ResourceID", sourceCode)); - actionInfo.setTestElement(elementInfo); - if (!StringUtils.isEmpty(elementInfo.getText())) { - arguments.put("defaultValue", elementInfo.getText()); - } else if (!StringUtils.isEmpty(elementInfo.getResourceId())) { - arguments.put("id", elementInfo.getResourceId()); - } - actionInfo.setArguments(arguments); - return actionInfo; - } - - private static String extractElementAttr(String attrName, String elementStr) { - String[] attrs = elementStr.split(attrName + ": "); - if (attrs.length > 1 && !attrs[1].startsWith(",")) { - return attrs[1].split(",")[0]; - } - return ""; - } - - /** - * generate maestro case files and zip them - * @param pageNode - * @param explorePaths - * @return - */ - public File generateMaestroCases(PageNode pageNode, List explorePaths) { - // create temp folder to store case files - File tempFolder = new File(CenterConstant.CENTER_TEMP_FILE_DIR, DateUtil.fileNameDateFormat.format(new Date())); - if (!tempFolder.exists()) { - tempFolder.mkdirs(); - } - // generate case files - for (PageNode.ExplorePath explorePath : explorePaths) { - generateMaestroCase(pageNode, explorePath, tempFolder); - } - if (tempFolder.listFiles().length == 0) { - return null; - } - // zip temp folder - File zipFile = new File(tempFolder.getParent() + "/" + tempFolder.getName() + ".zip"); - FileUtil.zipFile(tempFolder.getAbsolutePath(), zipFile.getAbsolutePath()); - FileUtil.deleteFile(tempFolder); - return zipFile; - } - - private File generateMaestroCase(PageNode pageNode, PageNode.ExplorePath explorePath, File caseFolder) { - File maestroCaseFile = new File(caseFolder, explorePath.getPath() + ".yaml"); - String caseContent = buildConfigSection(pageNode.getPageName()); - caseContent += buildDelimiter(); - caseContent += buildCommandSection("launch", null); - String[] actionIds = explorePath.getActions().split(","); - PageNode pageNodeCopy = pageNode; - for (String actionId : actionIds) { - PageNode.ActionInfo action = pageNodeCopy.getActionInfoList().stream().filter(actionInfo -> actionInfo.getId() == Integer.parseInt(actionId)).findFirst().get(); - caseContent += buildCommandSection(action.getActionType(), action.getArguments()); - pageNodeCopy = pageNodeCopy.getChildPageNodeMap().get(Integer.parseInt(actionId)); - } - caseContent += buildCommandSection("stop", null); - FileUtil.writeToFile(caseContent, maestroCaseFile.getAbsolutePath()); - return maestroCaseFile; - } - - private String buildConfigSection(String appId) { - return "appId: " + appId + "\n"; - } - - private String buildDelimiter() { - return "---\n"; - } - - public String buildCommandSection(String actionType, Map arguments) { - String command = "-"; - switch (actionType) { - case "launch": - command = command + " launchApp\n"; - break; - case "click": - command = command + " tapOn:"; - if (arguments.size() == 0) { - throw new HydraLabRuntimeException("arguments is empty"); - } - if (arguments.containsKey("defaultValue")) { - command = command + " " + arguments.get("defaultValue") + "\n"; - break; - } - command = command + "\n"; - for (String key : arguments.keySet()) { - command = command + " " + key + ": \"" + arguments.get(key) + "\"\n"; - } - break; - case "stop": - command = command + " stopApp\n"; - break; - default: - throw new HydraLabRuntimeException("Unsupported action type: " + actionType); - } - return command; - } - -} diff --git a/center/src/main/java/com/microsoft/hydralab/center/service/generation/AbstractCaseGeneration.java b/center/src/main/java/com/microsoft/hydralab/center/service/generation/AbstractCaseGeneration.java new file mode 100644 index 000000000..838640aab --- /dev/null +++ b/center/src/main/java/com/microsoft/hydralab/center/service/generation/AbstractCaseGeneration.java @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.center.service.generation; + +import com.microsoft.hydralab.common.util.PageNode; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author zhoule + * @date 07/21/2023 + */ + +public abstract class AbstractCaseGeneration { + public PageNode parserXMLToPageNode(String xmlFilePath) { + // read xml file, get page node and action info + Document document = null; + SAXReader saxReader = new SAXReader(); + try { + document = saxReader.read(xmlFilePath); + } catch (DocumentException e) { + throw new RuntimeException(e); + } + List pages = document.getRootElement().element("graph").element("nodes").elements("node"); + List actions = document.getRootElement().element("graph").element("edges").elements("edge"); + + Map pageNodes = new HashMap<>(); + // init page node + for (Element page : pages) { + PageNode pageNode = new PageNode(); + int id = Integer.parseInt(page.attributeValue("id")); + pageNode.setId(id); + pageNodes.put(id, pageNode); + } + // init action info + for (Element action : actions) { + int source = Integer.parseInt(action.attributeValue("source")); + int target = Integer.parseInt(action.attributeValue("target")); + if (source == target) { + continue; + } + int actionId = Integer.parseInt(action.attributeValue("id")); + //link action to page + pageNodes.get(source).getActionInfoList().add(parserAction(action)); + //link page to page + pageNodes.get(source).getChildPageNodeMap().put(actionId, pageNodes.get(target)); + } + return pageNodes.get(0); + } + + private PageNode.ActionInfo parserAction(Element element) { + PageNode.ActionInfo actionInfo = new PageNode.ActionInfo(); + Map arguments = new HashMap<>(); + actionInfo.setId(Integer.parseInt(element.attributeValue("id"))); + actionInfo.setActionType("click"); + + PageNode.ElementInfo elementInfo = new PageNode.ElementInfo(); + String sourceCode = element.element("attvalues").element("attvalue").attributeValue("value"); + elementInfo.setText(extractElementAttr("Text", sourceCode)); + elementInfo.setClassName(extractElementAttr("Class", sourceCode)); + elementInfo.setClickable(Boolean.parseBoolean(extractElementAttr("Clickable", sourceCode))); + elementInfo.setResourceId(extractElementAttr("ResourceID", sourceCode)); + actionInfo.setTestElement(elementInfo); + if (!StringUtils.isEmpty(elementInfo.getText())) { + arguments.put("defaultValue", elementInfo.getText()); + } else if (!StringUtils.isEmpty(elementInfo.getResourceId())) { + arguments.put("id", elementInfo.getResourceId()); + } + actionInfo.setArguments(arguments); + return actionInfo; + } + + private String extractElementAttr(String attrName, String elementStr) { + String[] attrs = elementStr.split(attrName + ": "); + if (attrs.length > 1 && !attrs[1].startsWith(",")) { + return attrs[1].split(",")[0]; + } + return ""; + } + + /** + * explore all path of page node + * + * @param pageNode + * @param nodePath + * @param action + * @param explorePaths + */ + public void explorePageNodePath(PageNode pageNode, String nodePath, String action, List explorePaths) { + if (pageNode.getChildPageNodeMap().isEmpty()) { + explorePaths.add(new PageNode.ExplorePath(nodePath + "_" + pageNode.getId(), action)); + return; + } + for (Map.Entry entry : pageNode.getChildPageNodeMap().entrySet()) { + explorePageNodePath(entry.getValue(), StringUtils.isEmpty(nodePath) ? String.valueOf(pageNode.getId()) : nodePath + "_" + pageNode.getId(), + StringUtils.isEmpty(action) ? String.valueOf(entry.getKey()) : action + "," + entry.getKey(), explorePaths); + } + } + + public abstract File generateCaseFile(PageNode pageNode, List explorePaths); + + public abstract File generateCaseFile(PageNode pageNode, PageNode.ExplorePath explorePaths, File caseFolder); +} diff --git a/center/src/main/java/com/microsoft/hydralab/center/service/generation/MaestroCaseGenerationService.java b/center/src/main/java/com/microsoft/hydralab/center/service/generation/MaestroCaseGenerationService.java new file mode 100644 index 000000000..cf22a7e71 --- /dev/null +++ b/center/src/main/java/com/microsoft/hydralab/center/service/generation/MaestroCaseGenerationService.java @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.center.service.generation; + +import com.microsoft.hydralab.center.util.CenterConstant; +import com.microsoft.hydralab.common.util.DateUtil; +import com.microsoft.hydralab.common.util.FileUtil; +import com.microsoft.hydralab.common.util.HydraLabRuntimeException; +import com.microsoft.hydralab.common.util.PageNode; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * @author zhoule + * @date 07/14/2023 + */ + +@Service +public class MaestroCaseGenerationService extends AbstractCaseGeneration { + /** + * generate maestro case files and zip them + * + * @param pageNode + * @param explorePaths + * @return + */ + @Override + public File generateCaseFile(PageNode pageNode, List explorePaths) { + // create temp folder to store case files + File tempFolder = new File(CenterConstant.CENTER_TEMP_FILE_DIR, DateUtil.fileNameDateFormat.format(new Date())); + if (!tempFolder.exists()) { + tempFolder.mkdirs(); + } + // generate case files + for (PageNode.ExplorePath explorePath : explorePaths) { + generateCaseFile(pageNode, explorePath, tempFolder); + } + if (tempFolder.listFiles().length == 0) { + return null; + } + // zip temp folder + File zipFile = new File(tempFolder.getParent() + "/" + tempFolder.getName() + ".zip"); + FileUtil.zipFile(tempFolder.getAbsolutePath(), zipFile.getAbsolutePath()); + FileUtil.deleteFile(tempFolder); + return zipFile; + } + + @Override + public File generateCaseFile(PageNode pageNode, PageNode.ExplorePath explorePath, File caseFolder) { + File maestroCaseFile = new File(caseFolder, explorePath.getPath() + ".yaml"); + String caseContent = buildConfigSection(pageNode.getPageName()); + caseContent += buildDelimiter(); + caseContent += buildCommandSection("launch", null); + String[] actionIds = explorePath.getActions().split(","); + PageNode pageNodeCopy = pageNode; + for (String actionId : actionIds) { + PageNode.ActionInfo action = pageNodeCopy.getActionInfoList().stream().filter(actionInfo -> actionInfo.getId() == Integer.parseInt(actionId)).findFirst().get(); + caseContent += buildCommandSection(action.getActionType(), action.getArguments()); + pageNodeCopy = pageNodeCopy.getChildPageNodeMap().get(Integer.parseInt(actionId)); + } + caseContent += buildCommandSection("stop", null); + FileUtil.writeToFile(caseContent, maestroCaseFile.getAbsolutePath()); + return maestroCaseFile; + } + + private String buildConfigSection(String appId) { + return "appId: " + appId + "\n"; + } + + private String buildDelimiter() { + return "---\n"; + } + + private String buildCommandSection(String actionType, Map arguments) { + String command = "-"; + switch (actionType) { + case "launch": + command = command + " launchApp\n"; + break; + case "click": + command = command + " tapOn:"; + if (arguments.size() == 0) { + throw new HydraLabRuntimeException("arguments is empty"); + } + if (arguments.containsKey("defaultValue")) { + command = command + " " + arguments.get("defaultValue") + "\n"; + break; + } + command = command + "\n"; + for (String key : arguments.keySet()) { + command = command + " " + key + ": \"" + arguments.get(key) + "\"\n"; + } + break; + case "stop": + command = command + " stopApp\n"; + break; + default: + throw new HydraLabRuntimeException("Unsupported action type: " + actionType); + } + return command; + } +} diff --git a/center/src/main/java/com/microsoft/hydralab/center/service/generation/T2CCaseGenerationService.java b/center/src/main/java/com/microsoft/hydralab/center/service/generation/T2CCaseGenerationService.java new file mode 100644 index 000000000..91f91d2c4 --- /dev/null +++ b/center/src/main/java/com/microsoft/hydralab/center/service/generation/T2CCaseGenerationService.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package com.microsoft.hydralab.center.service.generation; + +import com.microsoft.hydralab.common.util.PageNode; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; + +/** + * @author zhoule + * @date 07/21/2023 + */ + +@Service +public class T2CCaseGenerationService extends AbstractCaseGeneration { + @Override + public File generateCaseFile(PageNode pageNode, List explorePaths) { + return null; + } + + @Override + public File generateCaseFile(PageNode pageNode, PageNode.ExplorePath explorePaths, File caseFolder) { + return null; + } +} diff --git a/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java b/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java index 47a11800e..4ed2b43c1 100644 --- a/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java +++ b/center/src/test/java/com/microsoft/hydralab/center/service/TestCaseGenerationServiceTest.java @@ -1,5 +1,6 @@ package com.microsoft.hydralab.center.service; +import com.microsoft.hydralab.center.service.generation.MaestroCaseGenerationService; import com.microsoft.hydralab.center.test.BaseTest; import com.microsoft.hydralab.common.util.PageNode; import org.junit.jupiter.api.Assertions; @@ -13,17 +14,18 @@ class TestCaseGenerationServiceTest extends BaseTest { @Resource - CaseGenerationService caseGenerationService; + MaestroCaseGenerationService caseGenerationService; @Test void testParserXMLToPageNode() { PageNode rootNode = caseGenerationService.parserXMLToPageNode("src/test/resources/test_route_map.xml"); Assertions.assertNotNull(rootNode, "parser xml to page node failed"); rootNode.setPageName("com.microsoft.appmanager"); + System.out.println(rootNode); List explorePaths = new ArrayList<>(); - Assertions.assertEquals(explorePaths.size(), 16, "explore path size is not correct"); caseGenerationService.explorePageNodePath(rootNode, "", "", explorePaths); - File caseZipFile = caseGenerationService.generateMaestroCases(rootNode, explorePaths); + Assertions.assertEquals(explorePaths.size(), 16, "explore path size is not correct"); + File caseZipFile = caseGenerationService.generateCaseFile(rootNode, explorePaths); Assertions.assertTrue(caseZipFile.exists()); } } \ No newline at end of file From 2146fdb9ff44a7bd4b7c76d868ed27ef90a0c9f7 Mon Sep 17 00:00:00 2001 From: Le Zhou <2428499107@qq.com> Date: Wed, 26 Jul 2023 11:46:35 +0800 Subject: [PATCH 7/8] Update build.gradle --- center/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/center/build.gradle b/center/build.gradle index c9e4798d6..b73a69c93 100644 --- a/center/build.gradle +++ b/center/build.gradle @@ -27,8 +27,8 @@ tasks.withType(JavaCompile) { } dependencies { - implementation 'dev.langchain4j:langchain4j:0.11.0' - implementation 'dev.langchain4j:langchain4j-pinecone:0.11.0' + compileOnly 'dev.langchain4j:langchain4j:0.11.0' + compileOnly 'dev.langchain4j:langchain4j-pinecone:0.11.0' implementation 'org.dom4j:dom4j:2.1.4' testCompile 'org.mockito:mockito-core:3.12.4' testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootWebVersion From af3ce99661f55a940eb03d09fd55a752a4b0129c Mon Sep 17 00:00:00 2001 From: Le Zhou <2428499107@qq.com> Date: Wed, 26 Jul 2023 17:32:39 +0800 Subject: [PATCH 8/8] add an api to download cases --- .../controller/TestDetailController.java | 55 ++++++++++++ react/src/component/TestReportView.jsx | 90 +++++++++++++------ 2 files changed, 117 insertions(+), 28 deletions(-) diff --git a/center/src/main/java/com/microsoft/hydralab/center/controller/TestDetailController.java b/center/src/main/java/com/microsoft/hydralab/center/controller/TestDetailController.java index 0cdbd1b4e..0ae79721e 100644 --- a/center/src/main/java/com/microsoft/hydralab/center/controller/TestDetailController.java +++ b/center/src/main/java/com/microsoft/hydralab/center/controller/TestDetailController.java @@ -8,6 +8,7 @@ import com.alibaba.fastjson.JSONObject; import com.microsoft.hydralab.center.service.StorageTokenManageService; import com.microsoft.hydralab.center.service.TestDataService; +import com.microsoft.hydralab.center.service.generation.MaestroCaseGenerationService; import com.microsoft.hydralab.common.entity.agent.Result; import com.microsoft.hydralab.common.entity.center.SysUser; import com.microsoft.hydralab.common.entity.common.AndroidTestUnit; @@ -26,9 +27,11 @@ import com.microsoft.hydralab.common.util.FileUtil; import com.microsoft.hydralab.common.util.HydraLabRuntimeException; import com.microsoft.hydralab.common.util.LogUtils; +import com.microsoft.hydralab.common.util.PageNode; import com.microsoft.hydralab.t2c.runner.T2CJsonGenerator; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Assertions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -47,11 +50,13 @@ import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import static com.microsoft.hydralab.center.util.CenterConstant.CENTER_TEMP_FILE_DIR; @@ -72,6 +77,8 @@ public class TestDetailController { AttachmentService attachmentService; @Resource StorageServiceClientProxy storageServiceClientProxy; + @Resource + MaestroCaseGenerationService maestroCaseGenerationService; /** * Authenticated USER: @@ -412,4 +419,52 @@ public Result generateT2CJsonFromSmartTest(@CurrentSecurityContext SysUs return Result.ok(t2cJson); } + @GetMapping(value = {"/api/test/generateMaestro/{fileId}"}, produces = MediaType.APPLICATION_JSON_VALUE) + public Result generateMaestroFromSmartTest(@CurrentSecurityContext SysUser requestor, + @PathVariable(value = "fileId") String fileId, + @RequestParam(value = "testRunId") String testRunId, + HttpServletResponse response) throws IOException { + if (requestor == null) { + return Result.error(HttpStatus.UNAUTHORIZED.value(), "unauthorized"); + } + + File graphZipFile = loadGraphFile(fileId); + File graphFile = new File(graphZipFile.getParentFile().getAbsolutePath(), Const.SmartTestConfig.GRAPH_FILE_NAME); + TestRun testRun = testDataService.findTestRunById(testRunId); + TestTask testTask = testDataService.getTestTaskDetail(testRun.getTestTaskId()); + + PageNode rootNode = maestroCaseGenerationService.parserXMLToPageNode(graphFile.getAbsolutePath()); + Assertions.assertNotNull(rootNode, "parser xml to page node failed"); + rootNode.setPageName(testTask.getPkgName()); + System.out.println(rootNode); + List explorePaths = new ArrayList<>(); + maestroCaseGenerationService.explorePageNodePath(rootNode, "", "", explorePaths); + File caseZipFile = maestroCaseGenerationService.generateCaseFile(rootNode, explorePaths); + + if (caseZipFile == null) { + return Result.error(HttpStatus.BAD_REQUEST.value(), "The file was not downloaded"); + } + try { + FileInputStream in = new FileInputStream(caseZipFile); + ServletOutputStream out = response.getOutputStream(); + response.setContentType("application/octet-stream;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment;filename=" + caseZipFile.getName()); + int len; + byte[] buffer = new byte[1024 * 10]; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + out.flush(); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + response.flushBuffer(); + caseZipFile.delete(); + } + + return Result.ok(); + } + } diff --git a/react/src/component/TestReportView.jsx b/react/src/component/TestReportView.jsx index dc59d8ed6..e88d98749 100644 --- a/react/src/component/TestReportView.jsx +++ b/react/src/component/TestReportView.jsx @@ -631,35 +631,69 @@ export default class TestReportView extends BaseView { generateJSON() { if (this.state.selectedPath.length === 0) { - this.setState({ - snackbarIsShown: true, - snackbarSeverity: "error", - snackbarMessage: "Please select a path!" - }) - return + axios({ + url: `/api/test/generateMaestro/` + this.state.graphFileId + "?testRunId=" + this.state.task.deviceTestResults[0].id, + method: 'GET', + responseType: 'blob' + }).then((res) => { + if (res.data.type.includes('application/json')) { + let reader = new FileReader() + reader.onload = function () { + let result = JSON.parse(reader.result) + if (result.code !== 200) { + this.setState({ + snackbarIsShown: true, + snackbarSeverity: "error", + snackbarMessage: "The file could not be downloaded" + }) + } + } + reader.readAsText(res.data) + } else { + const href = URL.createObjectURL(res.data); + const link = document.createElement('a'); + link.href = href; + const filename = res.headers["content-disposition"].replace('attachment;filename=', ''); + link.setAttribute('download', 'maestro_case_'+filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(href); + + if (res.data.code === 200) { + this.setState({ + snackbarIsShown: true, + snackbarSeverity: "success", + snackbarMessage: "Maestro case file downloaded" + }) + } + } + }).catch(this.snackBarError); + }else{ + axios({ + url: `/api/test/generateT2C/` + this.state.graphFileId + "?testRunId=" + this.state.task.deviceTestResults[0].id + "&path=" + this.state.selectedPath.join(','), + method: 'GET', + }).then((res) => { + var blob = new Blob([res.data.content]); + const href = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = href; + link.setAttribute('download', this.state.task.pkgName + '_t2c.json'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(href); + + if (res.data.code === 200) { + this.setState({ + snackbarIsShown: true, + snackbarSeverity: "success", + snackbarMessage: "T2C JSON file downloaded" + }) + } + }).catch(this.snackBarError); } - axios({ - url: `/api/test/generateT2C/` + this.state.graphFileId + "?testRunId=" + this.state.task.deviceTestResults[0].id + "&path=" + this.state.selectedPath.join(','), - method: 'GET', - }).then((res) => { - var blob = new Blob([res.data.content]); - const href = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = href; - link.setAttribute('download', this.state.task.pkgName + '_t2c.json'); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(href); - - if (res.data.code === 200) { - this.setState({ - snackbarIsShown: true, - snackbarSeverity: "success", - snackbarMessage: "T2C JSON file downloaded" - }) - } - }).catch(this.snackBarError); + } getDeviceLabel = (name) => {