Skip to content

Commit b2e25c8

Browse files
Merge pull request #74 from AutomateThePlanet/shadow-dom-update
Shadow Dom GetHtml and Retry Logic Update
2 parents 744aec6 + 86f553c commit b2e25c8

File tree

3 files changed

+106
-42
lines changed

3 files changed

+106
-42
lines changed

bellatrix.web/src/main/java/solutions/bellatrix/web/components/WebComponent.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,18 +1081,16 @@ protected String defaultGetWidthAttribute() {
10811081
}
10821082

10831083
protected String defaultGetInnerHtmlAttribute() {
1084-
if (!this.inShadowContext()) {
1084+
if (this instanceof ShadowRoot) {
1085+
return ShadowDomService.getShadowHtml(this, true);
1086+
} else if (this.inShadowContext()) {
1087+
return ShadowDomService.getShadowHtml(this, false);
1088+
} else {
10851089
try {
10861090
return Optional.ofNullable(getAttribute("innerHTML")).orElse("");
10871091
} catch (StaleElementReferenceException e) {
10881092
return Optional.ofNullable(findElement().getAttribute("innerHTML")).orElse("");
10891093
}
1090-
} else {
1091-
if (this instanceof ShadowRoot) {
1092-
return ShadowDomService.getShadowHtml(this, true);
1093-
} else {
1094-
return ShadowDomService.getShadowHtml(this, false);
1095-
}
10961094
}
10971095
}
10981096

bellatrix.web/src/main/java/solutions/bellatrix/web/components/shadowdom/ShadowDomService.java

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515

1616
import lombok.experimental.UtilityClass;
1717
import org.openqa.selenium.By;
18+
import org.openqa.selenium.NoSuchElementException;
1819
import solutions.bellatrix.core.configuration.ConfigurationService;
1920
import solutions.bellatrix.core.utilities.InstanceFactory;
2021
import solutions.bellatrix.core.utilities.Ref;
2122
import solutions.bellatrix.core.utilities.Wait;
2223
import solutions.bellatrix.web.components.WebComponent;
24+
import solutions.bellatrix.web.components.contracts.Component;
2325
import solutions.bellatrix.web.configuration.WebSettings;
2426
import solutions.bellatrix.web.findstrategies.CssFindStrategy;
2527
import solutions.bellatrix.web.findstrategies.FindStrategy;
@@ -43,7 +45,7 @@ public static String getShadowHtml(WebComponent shadowComponent, boolean isShado
4345
}
4446

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

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

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

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

106-
return getCss(js, locator);
108+
return getCss(js);
107109
}
108110

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

116-
return getCss(js, locator);
118+
return getCss(js);
117119
}
118120

119-
private static String[] getCss(Callable<String[]> callable, String locator) {
120-
if (Wait.retry(() -> {
121-
String[] foundElements;
122-
try {
123-
foundElements = callable.call();
124-
} catch (Exception e) {
125-
throw new RuntimeException(e);
126-
}
127-
128-
if (foundElements == null || foundElements.length == 0) {
129-
throw new IllegalArgumentException();
130-
}
131-
}, Duration.ofSeconds(ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()), Duration.ofSeconds(1), false)) {
132-
try {
133-
return callable.call();
134-
} catch (Exception e) {
135-
throw new RuntimeException(e);
136-
}
137-
} else {
138-
throw new IllegalArgumentException("No elements inside the shadow DOM were found with the locator: " + locator);
121+
private static String[] getCss(Callable<String[]> callable) {
122+
String[] foundElements;
123+
try {
124+
foundElements = callable.call();
125+
} catch (Exception e) {
126+
throw new RuntimeException(e);
139127
}
128+
129+
return foundElements;
140130
}
141131

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

208198
StringBuilder finalCss = new StringBuilder();
209-
while(!findStrategies.isEmpty()) {
199+
while (!findStrategies.isEmpty()) {
210200
finalCss.append(findStrategies.pop());
211201
}
212202

@@ -254,6 +244,28 @@ private static String convertToCssOrXpath(FindStrategy findStrategy) {
254244
return null;
255245
}
256246

247+
private static <TComponent extends WebComponent> TComponent retryFindingSingleComponent(Callable<List<TComponent>> callable, FindStrategy findStrategy) {
248+
if (Wait.retry(() -> {
249+
List<TComponent> foundElements;
250+
try {
251+
foundElements = callable.call();
252+
} catch (Exception e) {
253+
throw new RuntimeException(e);
254+
}
255+
256+
if (foundElements.isEmpty()) throw new IllegalArgumentException();
257+
258+
}, Duration.ofSeconds(ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()), Duration.ofSeconds(1), false)) {
259+
try {
260+
return callable.call().get(0);
261+
} catch (Exception e) {
262+
throw new RuntimeException(e);
263+
}
264+
} else {
265+
throw new NoSuchElementException("No element inside the shadow DOM was found with the findStrategy: " + findStrategy.toString());
266+
}
267+
}
268+
257269
private static final String javaScript = /* lang=js */ """
258270
function (element, locator, relativeElementCss) {
259271
const child_combinator = " > ";
@@ -338,11 +350,11 @@ function getAbsoluteCss(xpath) {
338350
}
339351
340352
let startPoint = temporaryDiv;
341-
353+
342354
if (relativeElementCss) {
343355
startPoint = temporaryDiv.querySelector(relativeElementCss);
344356
}
345-
357+
346358
let elements;
347359
if (locator.startsWith("/") || locator.startsWith("./") || locator.startsWith("(")) {
348360
let result = document.evaluate(locator, startPoint, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
@@ -354,20 +366,20 @@ function getAbsoluteCss(xpath) {
354366
} else {
355367
elements = Array.from(startPoint.querySelectorAll(locator));
356368
}
357-
369+
358370
let finalLocators = [];
359371
elements.forEach((el) => {
360372
finalLocators.push(getAbsoluteCss(getAbsoluteXpath(el)));
361373
});
362-
374+
363375
return finalLocators;
364376
}""";
365377

366378
private static final String getInnerHtmlScript = """
367379
function (element, isShadowRoot) {
368380
const child_combinator = " > ";
369381
const node = "/";
370-
382+
371383
function clone(element, tag) {
372384
let cloneElement;
373385
if (element instanceof ShadowRoot && !tag) {
@@ -381,20 +393,20 @@ function clone(element, tag) {
381393
cloneElement.appendChild(element.firstChild.cloneNode());
382394
}
383395
}
384-
396+
385397
if (element.shadowRoot) {
386398
cloneElement.appendChild(clone(element.shadowRoot, "shadow-root"));
387399
}
388-
400+
389401
if (element.children) {
390402
for (const child of element.children) {
391403
cloneElement.appendChild(clone(child, undefined));
392404
}
393405
}
394-
406+
395407
return cloneElement;
396408
}
397-
409+
398410
let temporaryDiv = document.createElement("temporary-div");
399411
if (element.shadowRoot) {
400412
temporaryDiv.appendChild(clone(element.shadowRoot, undefined));
@@ -404,7 +416,7 @@ function clone(element, tag) {
404416
temporaryDiv.appendChild(clone(element, "redundant-el"));
405417
temporaryDiv = temporaryDiv.querySelector("redundant-el");
406418
}
407-
419+
408420
return temporaryDiv.innerHTML;
409421
}
410422
""";

framework-tests/bellatrix.web.tests/src/test/java/controls/shadowdom/ShadowDomTests.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
import org.junit.jupiter.api.Assertions;
55
import org.junit.jupiter.api.BeforeEach;
66
import org.junit.jupiter.api.Test;
7+
import org.openqa.selenium.NoSuchElementException;
8+
import org.testng.asserts.Assertion;
79
import solutions.bellatrix.core.configuration.ConfigurationService;
810
import solutions.bellatrix.web.components.Anchor;
911
import solutions.bellatrix.web.components.Div;
1012
import solutions.bellatrix.web.components.Select;
1113
import solutions.bellatrix.web.components.advanced.grid.Grid;
1214
import solutions.bellatrix.web.components.advanced.grid.GridCell;
1315
import solutions.bellatrix.web.components.shadowdom.ShadowRoot;
16+
import solutions.bellatrix.web.configuration.WebSettings;
1417
import solutions.bellatrix.web.infrastructure.Browser;
1518
import solutions.bellatrix.web.infrastructure.ExecutionBrowser;
1619
import solutions.bellatrix.web.infrastructure.Lifecycle;
@@ -107,5 +110,56 @@ public void findingElementByAnotherElementInNestedShadowRoot_withCss() {
107110
Assertions.assertEquals("edit", edit.getText());
108111
}
109112

113+
@Test
114+
public void exceptionThrown_when_tryingToFindNonExistentElement() {
115+
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
116+
var shadowRoot = shadowHost.getShadowRoot();
117+
118+
Assertions.assertThrows(NoSuchElementException.class, () -> shadowRoot.createByXPath(Div.class, "//nonExistentElement"));
119+
}
120+
121+
@Test
122+
public void returnedEmptyList_when_tryingToFindNonExistentElements() {
123+
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
124+
var shadowRoot = shadowHost.getShadowRoot();
125+
126+
Assertions.assertAll(
127+
() -> Assertions.assertDoesNotThrow(() -> shadowRoot.createAllByXPath(Div.class, "//nonExistentElement")),
128+
() -> Assertions.assertTrue(shadowRoot.createAllByXPath(Div.class, "//nonExistentElement").isEmpty())
129+
);
130+
}
131+
132+
@Test
133+
public void waitedTimeout_when_tryingToFindNonExistentElement() {
134+
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
135+
var shadowRoot = shadowHost.getShadowRoot();
136+
137+
long startTime = System.currentTimeMillis();
138+
try {
139+
shadowRoot.createByXPath(Div.class, "//nonExistentElement");
140+
} catch (NoSuchElementException ignored) {
141+
var elapsedTime = System.currentTimeMillis() - startTime;
142+
143+
Assertions.assertTrue(elapsedTime > ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()*1000);
144+
}
145+
}
146+
147+
@Test
148+
public void returnedEmptyListWithoutWaiting_when_tryingToFindNonExistentElements() {
149+
var shadowHost = app().create().byId(Div.class, "complexShadowHost");
150+
var shadowRoot = shadowHost.getShadowRoot();
151+
152+
long startTime = System.currentTimeMillis();
153+
154+
var isEmpty = shadowRoot.createAllByXPath(Div.class, "//nonExistentElement").isEmpty();
155+
156+
var elapsedTime = System.currentTimeMillis() - startTime;
157+
158+
Assertions.assertAll(
159+
() -> Assertions.assertTrue(isEmpty),
160+
() -> Assertions.assertTrue(elapsedTime < ConfigurationService.get(WebSettings.class).getTimeoutSettings().getElementWaitTimeout()*1000)
161+
);
162+
}
163+
110164
// TODO: Test Relative Finding of Elements
111165
}

0 commit comments

Comments
 (0)