Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
```

Expand All @@ -22,7 +22,7 @@ for Maven projects:
<dependency>
<groupId>com.epam.healenium</groupId>
<artifactId>healenium-web</artifactId>
<version>3.5.7</version>
<version>3.5.8</version>
</dependency>
```
### 1. Init driver instance of SelfHealingDriver
Expand Down
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.epam.healenium</groupId>
<artifactId>healenium-web</artifactId>
<version>3.5.7</version>
<version>3.5.8</version>
<packaging>jar</packaging>
<name>healenium-web</name>
<description>healenium web client</description>
Expand Down Expand Up @@ -55,13 +55,13 @@
<commonscodec.version>1.15</commonscodec.version>
<annotations.version>23.0.0</annotations.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
<lombok.version>1.18.22</lombok.version>
<lombok.version>1.18.38</lombok.version>
<streamex.version>0.8.1</streamex.version>
<jettyserver.version>12.0.17</jettyserver.version>
<hamcrestcore.version>2.2</hamcrestcore.version>
<selenide.version>7.0.0</selenide.version>
<testcontainersjunit.version>1.19.8</testcontainersjunit.version>
<junit.version>5.8.2</junit.version>
<testcontainersjunit.version>1.21.4</testcontainersjunit.version>
<junit.version>5.11.3</junit.version>
<webdrivermanager.version>6.1.0</webdrivermanager.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
Expand Down Expand Up @@ -240,7 +240,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>one.util</groupId>
Expand Down
57 changes: 56 additions & 1 deletion src/main/java/com/epam/healenium/client/RestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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<InputStream> result = response.getContent();
Map<String, String> responseMap = objectMapper.readValue(result.get(), new TypeReference<Map<String, String>>() {});
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);
Expand All @@ -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);
Expand Down
66 changes: 50 additions & 16 deletions src/main/java/com/epam/healenium/service/HealingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -78,36 +79,64 @@ public void findNewLocations(List<Node> 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> node, Context context, SelfHealingEngine engine) {
for (Set<SelectorComponent> 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<WebElement> elements = driver.findElements(locator);
if (elements.size() == 1 && !context.getElementIds().contains(((RemoteWebElement) elements.get(0)).getId()) ) {
Scored<By> 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<SelectorComponent> 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> node,
String selectorType, SelfHealingEngine engine) {
if (isUnsuccessLocator(locator, context, engine)) {
return null;
}
List<WebElement> elements = driver.findElements(locator);
if (elements.size() == 1 && !context.getElementIds().contains(((RemoteWebElement) elements.get(0)).getId())) {
Scored<By> 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<Locator> 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
Expand Down Expand Up @@ -136,4 +165,9 @@ protected By construct(Node node, Set<SelectorComponent> 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());
}
}
3 changes: 2 additions & 1 deletion src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ heal-enabled = true
backlight-healing = true
proxy = false
hlm.server.url = "http://localhost:7878"
hlm.imitator.url = "http://localhost:8000"
hlm.imitator.url = "http://localhost:8000"
hlm.ai.url = "http://localhost:6565"
Loading