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
Original file line number Diff line number Diff line change
Expand Up @@ -1081,18 +1081,16 @@ protected String defaultGetWidthAttribute() {
}

protected String defaultGetInnerHtmlAttribute() {
if (!this.inShadowContext()) {
if (this instanceof ShadowRoot) {
return ShadowDomService.getShadowHtml(this, true);
} else if (this.inShadowContext()) {
return ShadowDomService.getShadowHtml(this, false);
} else {
try {
return Optional.ofNullable(getAttribute("innerHTML")).orElse("");
} catch (StaleElementReferenceException e) {
return Optional.ofNullable(findElement().getAttribute("innerHTML")).orElse("");
}
} else {
if (this instanceof ShadowRoot) {
return ShadowDomService.getShadowHtml(this, true);
} else {
return ShadowDomService.getShadowHtml(this, false);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@

import lombok.experimental.UtilityClass;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import solutions.bellatrix.core.configuration.ConfigurationService;
import solutions.bellatrix.core.utilities.InstanceFactory;
import solutions.bellatrix.core.utilities.Ref;
import solutions.bellatrix.core.utilities.Wait;
import solutions.bellatrix.web.components.WebComponent;
import solutions.bellatrix.web.components.contracts.Component;
import solutions.bellatrix.web.configuration.WebSettings;
import solutions.bellatrix.web.findstrategies.CssFindStrategy;
import solutions.bellatrix.web.findstrategies.FindStrategy;
Expand All @@ -43,7 +45,7 @@ public static String getShadowHtml(WebComponent shadowComponent, boolean isShado
}

public static <TComponent extends WebComponent, TFindStrategy extends FindStrategy> TComponent createFromShadowRoot(Class<TComponent> componentClass, ShadowRoot parentComponent, TFindStrategy findStrategy) {
return createAllFromShadowRoot(componentClass, parentComponent, findStrategy).get(0);
return retryFindingSingleComponent(() -> createAllFromShadowRoot(componentClass, parentComponent, findStrategy), findStrategy);
}

public static <TComponent extends WebComponent, TFindStrategy extends FindStrategy> List<TComponent> createAllFromShadowRoot(Class<TComponent> componentClass, ShadowRoot parentComponent, TFindStrategy findStrategy) {
Expand All @@ -69,7 +71,7 @@ public static <TComponent extends WebComponent, TFindStrategy extends FindStrate
}

public static <TComponent extends WebComponent, TFindStrategy extends FindStrategy> TComponent createInShadowContext(Class<TComponent> componentClass, WebComponent parentComponent, TFindStrategy findStrategy) {
return createAllInShadowContext(componentClass, parentComponent, findStrategy).get(0);
return retryFindingSingleComponent(() -> createAllInShadowContext(componentClass, parentComponent, findStrategy), findStrategy);
}

public static <TComponent extends WebComponent, TFindStrategy extends FindStrategy> List<TComponent> createAllInShadowContext(Class<TComponent> componentClass, WebComponent parentComponent, TFindStrategy findStrategy) {
Expand Down Expand Up @@ -103,7 +105,7 @@ private static String[] getAbsoluteCss(ShadowRoot shadowRoot, String locator) {
shadowRoot.findElement(), locator, null).toArray(String[]::new);
};

return getCss(js, locator);
return getCss(js);
}

private static String[] getRelativeCss(ShadowRoot shadowRoot, String locator, String parentLocator) {
Expand All @@ -113,30 +115,18 @@ private static String[] getRelativeCss(ShadowRoot shadowRoot, String locator, St
shadowRoot.findElement(), locator, parentLocator).toArray(String[]::new);
};

return getCss(js, locator);
return getCss(js);
}

private static String[] getCss(Callable<String[]> callable, String locator) {
if (Wait.retry(() -> {
String[] foundElements;
try {
foundElements = callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}

if (foundElements == null || foundElements.length == 0) {
throw new IllegalArgumentException();
}
}, Duration.ofSeconds(ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()), Duration.ofSeconds(1), false)) {
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new IllegalArgumentException("No elements inside the shadow DOM were found with the locator: " + locator);
private static String[] getCss(Callable<String[]> callable) {
String[] foundElements;
try {
foundElements = callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}

return foundElements;
}

private static <TComponent extends WebComponent> TComponent buildMissingShadowRootsAndCreate(Class<TComponent> clazz, ShadowRoot parentComponent, Ref<String> fullCss) {
Expand Down Expand Up @@ -206,7 +196,7 @@ private static String retraceParentShadowRoots(WebComponent component) {
}

StringBuilder finalCss = new StringBuilder();
while(!findStrategies.isEmpty()) {
while (!findStrategies.isEmpty()) {
finalCss.append(findStrategies.pop());
}

Expand Down Expand Up @@ -254,6 +244,28 @@ private static String convertToCssOrXpath(FindStrategy findStrategy) {
return null;
}

private static <TComponent extends WebComponent> TComponent retryFindingSingleComponent(Callable<List<TComponent>> callable, FindStrategy findStrategy) {
if (Wait.retry(() -> {
List<TComponent> foundElements;
try {
foundElements = callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}

if (foundElements.isEmpty()) throw new IllegalArgumentException();

}, Duration.ofSeconds(ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()), Duration.ofSeconds(1), false)) {
try {
return callable.call().get(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new NoSuchElementException("No element inside the shadow DOM was found with the findStrategy: " + findStrategy.toString());
}
}

private static final String javaScript = /* lang=js */ """
function (element, locator, relativeElementCss) {
const child_combinator = " > ";
Expand Down Expand Up @@ -338,11 +350,11 @@ function getAbsoluteCss(xpath) {
}

let startPoint = temporaryDiv;

if (relativeElementCss) {
startPoint = temporaryDiv.querySelector(relativeElementCss);
}

let elements;
if (locator.startsWith("/") || locator.startsWith("./") || locator.startsWith("(")) {
let result = document.evaluate(locator, startPoint, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
Expand All @@ -354,20 +366,20 @@ function getAbsoluteCss(xpath) {
} else {
elements = Array.from(startPoint.querySelectorAll(locator));
}

let finalLocators = [];
elements.forEach((el) => {
finalLocators.push(getAbsoluteCss(getAbsoluteXpath(el)));
});

return finalLocators;
}""";

private static final String getInnerHtmlScript = """
function (element, isShadowRoot) {
const child_combinator = " > ";
const node = "/";

function clone(element, tag) {
let cloneElement;
if (element instanceof ShadowRoot && !tag) {
Expand All @@ -381,20 +393,20 @@ function clone(element, tag) {
cloneElement.appendChild(element.firstChild.cloneNode());
}
}

if (element.shadowRoot) {
cloneElement.appendChild(clone(element.shadowRoot, "shadow-root"));
}

if (element.children) {
for (const child of element.children) {
cloneElement.appendChild(clone(child, undefined));
}
}

return cloneElement;
}

let temporaryDiv = document.createElement("temporary-div");
if (element.shadowRoot) {
temporaryDiv.appendChild(clone(element.shadowRoot, undefined));
Expand All @@ -404,7 +416,7 @@ function clone(element, tag) {
temporaryDiv.appendChild(clone(element, "redundant-el"));
temporaryDiv = temporaryDiv.querySelector("redundant-el");
}

return temporaryDiv.innerHTML;
}
""";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.NoSuchElementException;
import org.testng.asserts.Assertion;
import solutions.bellatrix.core.configuration.ConfigurationService;
import solutions.bellatrix.web.components.Anchor;
import solutions.bellatrix.web.components.Div;
import solutions.bellatrix.web.components.Select;
import solutions.bellatrix.web.components.advanced.grid.Grid;
import solutions.bellatrix.web.components.advanced.grid.GridCell;
import solutions.bellatrix.web.components.shadowdom.ShadowRoot;
import solutions.bellatrix.web.configuration.WebSettings;
import solutions.bellatrix.web.infrastructure.Browser;
import solutions.bellatrix.web.infrastructure.ExecutionBrowser;
import solutions.bellatrix.web.infrastructure.Lifecycle;
Expand Down Expand Up @@ -107,5 +110,56 @@ public void findingElementByAnotherElementInNestedShadowRoot_withCss() {
Assertions.assertEquals("edit", edit.getText());
}

@Test
public void exceptionThrown_when_tryingToFindNonExistentElement() {
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
var shadowRoot = shadowHost.getShadowRoot();

Assertions.assertThrows(NoSuchElementException.class, () -> shadowRoot.createByXPath(Div.class, "//nonExistentElement"));
}

@Test
public void returnedEmptyList_when_tryingToFindNonExistentElements() {
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
var shadowRoot = shadowHost.getShadowRoot();

Assertions.assertAll(
() -> Assertions.assertDoesNotThrow(() -> shadowRoot.createAllByXPath(Div.class, "//nonExistentElement")),
() -> Assertions.assertTrue(shadowRoot.createAllByXPath(Div.class, "//nonExistentElement").isEmpty())
);
}

@Test
public void waitedTimeout_when_tryingToFindNonExistentElement() {
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
var shadowRoot = shadowHost.getShadowRoot();

long startTime = System.currentTimeMillis();
try {
shadowRoot.createByXPath(Div.class, "//nonExistentElement");
} catch (NoSuchElementException ignored) {
var elapsedTime = System.currentTimeMillis() - startTime;

Assertions.assertTrue(elapsedTime > ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()*1000);
}
}

@Test
public void returnedEmptyListWithoutWaiting_when_tryingToFindNonExistentElements() {
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
var shadowRoot = shadowHost.getShadowRoot();

long startTime = System.currentTimeMillis();

var isEmpty = shadowRoot.createAllByXPath(Div.class, "//nonExistentElement").isEmpty();

var elapsedTime = System.currentTimeMillis() - startTime;

Assertions.assertAll(
() -> Assertions.assertTrue(isEmpty),
() -> Assertions.assertTrue(elapsedTime < ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()*1000)
);
}

// TODO: Test Relative Finding of Elements
}