diff --git a/README.md b/README.md index ceaf9ce..8ec8c6e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Self-healing library for Selenium Web-based tests for Gradle projects: ``` dependencies { - compile group: 'com.epam.healenium', name: 'healenium-web', version: '3.5.7' + compile group: 'com.epam.healenium', name: 'healenium-web', version: '3.5.8' } ``` @@ -22,7 +22,7 @@ for Maven projects: com.epam.healenium healenium-web - 3.5.7 + 3.5.8 ``` ### 1. Init driver instance of SelfHealingDriver diff --git a/pom.xml b/pom.xml index 358137c..e281af3 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 4.0.0 com.epam.healenium healenium-web - 3.5.7 + 3.5.8 jar healenium-web healenium web client @@ -55,13 +55,13 @@ 1.15 23.0.0 1.4.2.Final - 1.18.22 + 1.18.38 0.8.1 12.0.17 2.2 7.0.0 - 1.19.8 - 5.8.2 + 1.21.4 + 5.11.3 6.1.0 17 17 @@ -240,7 +240,7 @@ org.slf4j slf4j-simple - 2.0.9 + 2.0.17 one.util diff --git a/src/main/java/com/epam/healenium/client/RestClient.java b/src/main/java/com/epam/healenium/client/RestClient.java index c63c8c4..00455eb 100644 --- a/src/main/java/com/epam/healenium/client/RestClient.java +++ b/src/main/java/com/epam/healenium/client/RestClient.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -68,20 +69,27 @@ public class RestClient { private final String serverUrl; private final String imitateUrl; + private final String aiServiceUrl; private final String sessionKey; + private final String selectorType; private ObjectMapper objectMapper; private HealeniumMapper mapper; private HttpClient serverHttpClient; private HttpClient imitateHttpClient; + private HttpClient aiServiceHttpClient; public RestClient(Config config) { this.objectMapper = initMapper(); this.sessionKey = config.getString("sessionKey"); this.serverUrl = getHttpUrl(config.getString("hlm.server.url"), "/healenium"); this.imitateUrl = getHttpUrl(config.getString("hlm.imitator.url"), "/imitate"); + this.aiServiceUrl = getHttpUrl(config.getString("hlm.ai.url"), "/healenium-ai"); + this.selectorType = config.hasPath("selector-type") ? config.getString("selector-type") : "cssSelector"; this.serverHttpClient = getHttpClient(serverUrl); this.imitateHttpClient = getHttpClient(imitateUrl); - log.debug("[Init] sessionKey: {}, serverUrl: {}, imitateUrl: {}", sessionKey, serverUrl, imitateUrl); + this.aiServiceHttpClient = getHttpClient(aiServiceUrl); + log.debug("[Init] sessionKey: {}, serverUrl: {}, imitateUrl: {}, selectorType: {}", + sessionKey, serverUrl, imitateUrl, selectorType); } private String getHttpUrl(String hlmServerUrl, String path) { @@ -261,6 +269,42 @@ public void initReport(String sessionId) { } } + public String getXpathSelector(Node node, String sessionId) { + String xpath = null; + try { + if (node == null) { + log.error("[Get Xpath Selector] Node is null, cannot proceed with request"); + return null; + } + + log.debug("[Get Xpath Selector] Node details - Tag: {}, ID: {}, Classes: {}", + node.getTag(), node.getId(), node.getClasses()); + + HttpRequest request = new HttpRequest(HttpMethod.POST, "/selectors/xpath"); + String content = objectMapper.writeValueAsString(node); + log.debug("[Get Xpath Selector] Request body: {}", content); + byte[] data = content.getBytes(StandardCharsets.UTF_8); + request.setHeader("Content-Length", String.valueOf(data.length)); + request.setHeader("Content-Type", JSON_UTF_8); + request.setHeader("X-Session-Id", sessionId); + request.setContent(Contents.bytes(data)); + HttpResponse response = aiServiceExecute(request); + + if (HTTP_NOT_FOUND == response.getStatus()) { + throw new RuntimeException("[Get Xpath Selector] Compatibility error. You must have a paid hlm-ai service."); + } + Supplier result = response.getContent(); + Map responseMap = objectMapper.readValue(result.get(), new TypeReference>() {}); + xpath = responseMap.get("xpath"); + log.debug("[Get Xpath Selector] Received xpath: {}", xpath); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + log.warn("[Get Xpath Selector] Error during call. Message: {}, Exception: {}", e.getMessage(), e.toString()); + } + return xpath; + } + private HttpResponse serverExecute(HttpRequest request) { try { return serverHttpClient.execute(request); @@ -272,6 +316,17 @@ private HttpResponse serverExecute(HttpRequest request) { } } + private HttpResponse aiServiceExecute(HttpRequest request) { + try { + return aiServiceHttpClient.execute(request); + } catch (UncheckedIOException e) { + String errorMessage = "[Execute Error] Unable to connect to the hlm-ai service. " + + "Please check if the service is up and running, and verify that the connection URL is correct."; + log.error(errorMessage); + throw new HealeniumException(errorMessage, e); + } + } + private HttpResponse imitateExecute(HttpRequest request) { try { return imitateHttpClient.execute(request); diff --git a/src/main/java/com/epam/healenium/service/HealingService.java b/src/main/java/com/epam/healenium/service/HealingService.java index 24d8bca..200e0f9 100644 --- a/src/main/java/com/epam/healenium/service/HealingService.java +++ b/src/main/java/com/epam/healenium/service/HealingService.java @@ -2,6 +2,7 @@ import com.epam.healenium.SelectorComponent; import com.epam.healenium.SelfHealingEngine; +import com.epam.healenium.client.RestClient; import com.epam.healenium.model.*; import com.epam.healenium.treecomparing.HeuristicNodeDistance; import com.epam.healenium.treecomparing.LCSPathDistance; @@ -78,36 +79,64 @@ public void findNewLocations(List paths, Node destination, Context context } } - /** - * @param node convert source node to locator - * @param context chain context - * @param engine - * @return healedElement - */ protected HealedElement toLocator(Scored node, Context context, SelfHealingEngine engine) { - for (Set detailLevel : selectorDetailLevels) { - By locator = construct(node.getValue(), detailLevel); - if (isUnsuccessLocator(locator, context, engine)) { - return null; + By locator; + if (useXPath(engine)) { + String xpath = createXPathFromElement(node.getValue(), engine); + locator = By.xpath(xpath); + HealedElement healedElement = getElementBySelectorType(locator, context, node, "XPath", engine); + if (healedElement != null) { + return healedElement; } - List elements = driver.findElements(locator); - if (elements.size() == 1 && !context.getElementIds().contains(((RemoteWebElement) elements.get(0)).getId()) ) { - Scored byScored = new Scored<>(node.getScore(), locator); - context.getElementIds().add(((RemoteWebElement) elements.get(0)).getId()); - HealedElement healedElement = new HealedElement(); - healedElement.setElement(elements.get(0)).setScored(byScored); + } + for (Set detailLevel : selectorDetailLevels) { + locator = construct(node.getValue(), detailLevel); + HealedElement healedElement = getElementBySelectorType(locator, context, node, "Css", engine); + if (healedElement != null) { return healedElement; } } return null; } + private HealedElement getElementBySelectorType(By locator, Context context, Scored node, + String selectorType, SelfHealingEngine engine) { + if (isUnsuccessLocator(locator, context, engine)) { + return null; + } + List elements = driver.findElements(locator); + if (elements.size() == 1 && !context.getElementIds().contains(((RemoteWebElement) elements.get(0)).getId())) { + Scored byScored = new Scored<>(node.getScore(), locator); + context.getElementIds().add(((RemoteWebElement) elements.get(0)).getId()); + HealedElement healedElement = new HealedElement(); + healedElement.setElement(elements.get(0)).setScored(byScored); + log.debug("Using {}, selector: {}", selectorType, locator); + return healedElement; + } + return null; + } + private boolean isUnsuccessLocator(By locator, Context context, SelfHealingEngine engine) { Locator convertLocator = engine.getClient().getMapper().byToLocator(locator); List unsuccessfulLocators = context.getUnsuccessfulLocators(); return unsuccessfulLocators != null && unsuccessfulLocators.contains(convertLocator); } + /** + * Determine if XPath should be used based on configuration + * + * @param engine SelfHealingEngine instance + * @return true if XPath should be used, false for CSS selector + */ + private boolean useXPath(SelfHealingEngine engine) { + if (engine == null || engine.getClient() == null) { + return false; + } + + String selectorType = engine.getClient().getSelectorType(); + return "xpath".equals(selectorType); + } + /** * @param curPathHeightToScores - all PathToNode candidate collection * @return list healingCandidateDto for metrics @@ -136,4 +165,9 @@ protected By construct(Node node, Set detailLevel) { .map(component -> component.createComponent(node)) .collect(Collectors.joining())); } + + public static String createXPathFromElement(Node node, SelfHealingEngine engine) { + RestClient client = engine.getClient(); + return client.getXpathSelector(node, engine.getClient().getSessionKey()); + } } diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 8e4f410..85d529f 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -4,4 +4,5 @@ heal-enabled = true backlight-healing = true proxy = false hlm.server.url = "http://localhost:7878" -hlm.imitator.url = "http://localhost:8000" \ No newline at end of file +hlm.imitator.url = "http://localhost:8000" +hlm.ai.url = "http://localhost:6565" \ No newline at end of file