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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dependencies {
implementation 'org.springframework.retry:spring-retry:2.0.12'

implementation 'org.springframework.boot:spring-boot-starter-aspectj'
implementation 'org.liquibase:liquibase-core:4.33.0'
implementation 'org.springframework.boot:spring-boot-starter-liquibase'
implementation 'org.postgresql:postgresql:42.7.7'
implementation 'com.zaxxer:HikariCP:3.3.1'
implementation 'com.vladmihalcea:hibernate-types-52:2.7.0'
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/epam/healenium/config/DynamicSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,15 @@ public class DynamicSettings {

@Value("${app.healing.elements:false}")
private boolean findElementsAutoHealing;

@Value("${healenium.score-cap:.6}")
private double scoreCap;

// Selector type must be 'cssSelector' or 'xpath'
@Value("${healenium.selector-type:cssSelector}")
private String selectorType;

@Value("${healenium.recovery-tries:1}")
private int recoveryTries;
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import com.epam.healenium.converter.NodeDeserializer;
import com.epam.healenium.converter.NodeSerializer;
import com.epam.healenium.converter.ScoredDeserializer;
import com.epam.healenium.converter.ScoredSerializer;
import com.epam.healenium.treecomparing.Node;
import com.epam.healenium.treecomparing.Scored;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
Expand All @@ -21,7 +24,9 @@ public class JacksonConfiguration implements JsonMapperBuilderCustomizer {
public void customize(JsonMapper.Builder builder) {
SimpleModule nodeModule = new SimpleModule("node")
.addSerializer(Node.class, new NodeSerializer())
.addDeserializer(Node.class, new NodeDeserializer());
.addDeserializer(Node.class, new NodeDeserializer())
.addSerializer(Scored.class, new ScoredSerializer())
.addDeserializer(Scored.class, new ScoredDeserializer());
builder.addModule(nodeModule)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS, false)
Expand Down
24 changes: 22 additions & 2 deletions src/main/java/com/epam/healenium/controller/HealingController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import com.epam.healenium.model.dto.RecordDto;
import com.epam.healenium.model.dto.ReferenceElementsDto;
import com.epam.healenium.model.dto.RequestDto;
import com.epam.healenium.model.dto.SelectorCandidate;
import com.epam.healenium.model.dto.SelectorDto;
import com.epam.healenium.model.dto.SelectorRequestDto;
import com.epam.healenium.model.dto.SessionDto;
import com.epam.healenium.service.HealingService;
import com.epam.healenium.service.SelectorCandidateService;
import com.epam.healenium.service.SelectorService;
import com.epam.healenium.util.Utils;
import com.epam.healenium.tenant.TenantTxFacade;
Expand Down Expand Up @@ -45,11 +47,13 @@ public class HealingController {
private final TenantTxFacade tenantTx;
private final HealingService healingService;
private final SelectorService selectorService;
private final SelectorCandidateService selectorCandidateService;

public HealingController(TenantTxFacade tenantTx, HealingService healingService, SelectorService selectorService) {
public HealingController(TenantTxFacade tenantTx, HealingService healingService, SelectorService selectorService, SelectorCandidateService selectorCandidateService) {
this.tenantTx = tenantTx;
this.healingService = healingService;
this.selectorService = selectorService;
this.selectorCandidateService = selectorCandidateService;
}

/**
Expand Down Expand Up @@ -210,7 +214,7 @@ public ResponseEntity<List<Locator>> getCandidate(@RequestBody RequestDto dto) {
ReferenceElementsDto referenceElements = selectorService.getReferenceElements(dto);
if (healingService.validateReference(referenceElements)) {
List<Locator> candidates = healingService.getCandidates(dto, referenceElements).stream()
.map(c -> new Locator().setUsing("css selector")
.map(c -> new Locator().setType("css selector")
.setValue((String) ((By.ByCssSelector) c).getRemoteParameters().value()))
.collect(Collectors.toList());
log.info("[Get Candidate] Candidates: {})", candidates);
Expand All @@ -221,6 +225,22 @@ public ResponseEntity<List<Locator>> getCandidate(@RequestBody RequestDto dto) {
});
}

@PostMapping("/selector-candidates")
public ResponseEntity<List<SelectorCandidate>> getElementCandidates(@RequestBody RequestDto dto) {
return tenantTx.required(() -> {
log.debug("[Get Selector Candidates] Request: {})", dto);

ReferenceElementsDto referenceElements = selectorService.getReferenceElements(dto);
if (selectorCandidateService.validateReference(referenceElements)) {
List<SelectorCandidate> candidates = selectorCandidateService.getCandidates(dto, referenceElements);
log.info("[Get Selector Candidates] Candidates: {})", candidates);
return ResponseEntity.ok(candidates);
} else {
return ResponseEntity.notFound().build();
}
});
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/epam/healenium/converter/ScoredDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.epam.healenium.converter;

import com.epam.healenium.treecomparing.Node;
import com.epam.healenium.treecomparing.Scored;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.deser.std.StdDeserializer;

public class ScoredDeserializer extends StdDeserializer<Scored> {

public ScoredDeserializer() {
super(Scored.class);
}

@Override
public Scored deserialize(JsonParser parser, DeserializationContext ctxt) throws JacksonException {
JsonNode tree = ctxt.readTree(parser);
double score = ctxt.readTreeAsValue(tree.path("score"), Double.class);
Node value = ctxt.readTreeAsValue(tree.path("value"), Node.class);
return new Scored<>(score, value);
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/epam/healenium/converter/ScoredSerializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.epam.healenium.converter;

import com.epam.healenium.treecomparing.Scored;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ser.std.StdSerializer;

public class ScoredSerializer extends StdSerializer<Scored> {

public ScoredSerializer() {
super(Scored.class);
}

@Override
public void serialize(Scored value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
gen.writeStartObject();
gen.writeNumberProperty("score", value.getScore());
gen.writePOJOProperty("value", value.getValue());
gen.writeEndObject();
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/epam/healenium/model/dto/ElementCandidate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.epam.healenium.model.dto;

import com.epam.healenium.model.Locator;
import com.epam.healenium.treecomparing.Node;
import com.epam.healenium.treecomparing.Scored;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.List;

@Data
@Accessors(chain = true)
public class ElementCandidate {
private List<Locator> locators;
@ToString.Exclude
private Scored<Node> nodeScored;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.epam.healenium.model.dto;

import com.epam.healenium.treecomparing.Node;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;

/**
* healenium-web :: HealingCandidateDto
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
public class HealingCandidateMetaData {

private Double score;
private Integer LCSDistance;
private Integer curPathHeight;
private Node node;

}
2 changes: 1 addition & 1 deletion src/main/java/com/epam/healenium/model/dto/Locator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
@Data
@Accessors(chain = true)
public class Locator {
private String using;
private String value;
private String type;
}
1 change: 1 addition & 0 deletions src/main/java/com/epam/healenium/model/dto/RequestDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public class RequestDto {

private String locator;
private String locatorType;
private String className;
private String methodName;
private String command;
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/com/epam/healenium/model/dto/SelectorCandidate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.epam.healenium.model.dto;

import com.epam.healenium.treecomparing.Node;
import com.epam.healenium.treecomparing.Scored;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.List;

@Data
@Accessors(chain = true)
public class SelectorCandidate {
List<ElementCandidate> elementCandidates;

// -- to save healing result --
// one List<Node> from ReferenceElementsDto::List<List<Node>> paths
// in playwright usually there is only one List<Node>
@ToString.Exclude
List<Node> path;
@ToString.Exclude
private List<Scored<Node>> scores;
@ToString.Exclude
List<HealingCandidateMetaData> candidateMetaData;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.epam.healenium.service;

import com.epam.healenium.model.dto.ReferenceElementsDto;
import com.epam.healenium.model.dto.RequestDto;
import com.epam.healenium.model.dto.SelectorCandidate;

import java.util.List;

public interface SelectorCandidateService {

boolean validateReference(ReferenceElementsDto referenceElements);

List<SelectorCandidate> getCandidates(RequestDto dto, ReferenceElementsDto referenceElements);

}
6 changes: 6 additions & 0 deletions src/main/java/com/epam/healenium/service/SettingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public interface SettingService {
boolean setCollectMetrics(boolean enabled);

boolean setFindElementsAutoHealing(boolean enabled);

boolean setScoreCap(double scoreCap);

boolean setSelectorType(String selectorType);

boolean setRecoveryTries(int recoveryTries);

Map<String, Object> updateSetting(String key, String value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,7 @@ public List<By> findNewLocations(List<Node> paths, Node destination, ReferenceEl
PathFinder pathFinder = new PathFinder(new LCSPathDistance(), new HeuristicNodeDistance());
AbstractMap.SimpleImmutableEntry<Integer, Map<Double, List<AbstractMap.SimpleImmutableEntry<Node, Integer>>>> scoresToNodes =
pathFinder.findScoresToNodes(new Path(paths.toArray(new Node[0])), destination);
// TODO get guessCap from settings
List<Scored<Node>> scoreds = pathFinder.getSortedNodes(scoresToNodes.getValue(), 1000, 0.6);
List<Scored<Node>> scoreds = pathFinder.getSortedNodes(scoresToNodes.getValue(), 1000, dynamicSettings.getScoreCap());

List<By> healedElements = scoreds.stream()
.map(node -> toLocator(node, referenceElements))
Expand All @@ -236,13 +235,6 @@ public List<By> findNewLocations(List<Node> paths, Node destination, ReferenceEl

public By toLocator(Scored<Node> node, ReferenceElementsDto referenceElements) {
By locator = null;
// if (useXPath(engine)) {
// String xpath = createXPathFromElement(node.getValue(), engine);
// locator = By.xpath(xpath);
// if (isUnsuccessLocator(locator, context, engine)) {
// return null;
// }
// }

for (Set<SelectorComponent> detailLevel : selectorDetailLevels) {
locator = construct(node.getValue(), detailLevel);
Expand All @@ -260,7 +252,6 @@ public By construct(Node node, Set<SelectorComponent> detailLevel) {
.collect(Collectors.joining()));
}


private boolean isUnsuccessLocator(By locator, ReferenceElementsDto referenceElements) {
List<Locator> unsuccessfulLocators = referenceElements.getUnsuccessfulLocators();
Locator candidate = new Locator(((By.ByCssSelector) locator).getRemoteParameters().using(),
Expand Down
Loading
Loading