Skip to content

Commit 888ec15

Browse files
authored
Support Finding of Multiple elements from ShadowRoot (#125)
* Support Finding of Multiple elements from ShadowRoot - add JavaScript to generate CSS selector from element - try to generate CSS selector if XPath generation fails - necessary for ShadowRoot elements since XPath doesn't work for them Related to aquality-automation/aquality-selenium-dotnet#235 * Update ElementFactory to use generate CSS locator logic in generateLocator method instead of generateXPathLocator
1 parent 165e135 commit 888ec15

File tree

6 files changed

+118
-9
lines changed

6 files changed

+118
-9
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
<dependency>
8383
<groupId>com.github.aquality-automation</groupId>
8484
<artifactId>aquality-selenium-core</artifactId>
85-
<version>3.1.1</version>
85+
<version>3.1.2</version>
8686
</dependency>
8787
<dependency>
8888
<groupId>org.apache.commons</groupId>

src/main/java/aquality/selenium/browser/JavaScript.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public enum JavaScript {
2222
GET_COMBOBOX_SELECTED_TEXT("getCmbText.js"),
2323
GET_COMBOBOX_TEXTS("getCmbValues.js"),
2424
GET_ELEMENT_BY_XPATH("getElementByXpath.js"),
25+
GET_ELEMENT_CSS_SELECTOR("getElementCssSelector.js"),
2526
GET_ELEMENT_XPATH("getElementXPath.js"),
2627
GET_ELEMENT_TEXT("getElementText.js"),
2728
GET_TEXT_FIRST_CHILD("getTextFirstChild.js"),

src/main/java/aquality/selenium/elements/ElementFactory.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
package aquality.selenium.elements;
22

3-
import aquality.selenium.browser.AqualityServices;
43
import aquality.selenium.browser.JavaScript;
54
import aquality.selenium.core.elements.interfaces.IElementFinder;
65
import aquality.selenium.core.elements.interfaces.IElementSupplier;
76
import aquality.selenium.core.localization.ILocalizationManager;
87
import aquality.selenium.core.waitings.IConditionalWait;
98
import aquality.selenium.elements.interfaces.*;
109
import com.google.inject.Inject;
11-
import org.openqa.selenium.By;
10+
import org.openqa.selenium.*;
1211
import org.openqa.selenium.By.ByClassName;
1312
import org.openqa.selenium.By.ById;
1413
import org.openqa.selenium.By.ByName;
15-
import org.openqa.selenium.WebElement;
14+
import org.openqa.selenium.remote.RemoteWebDriver;
1615
import org.openqa.selenium.support.ByIdOrName;
1716

1817
import java.util.HashMap;
1918
import java.util.Map;
19+
import java.util.Objects;
2020

2121
public class ElementFactory extends aquality.selenium.core.elements.ElementFactory implements IElementFactory {
2222

23+
private final IConditionalWait conditionalWait;
2324
private final IElementFinder elementFinder;
2425

2526
@Inject
2627
public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager) {
2728
super(conditionalWait, elementFinder, localizationManager);
29+
this.conditionalWait = conditionalWait;
2830
this.elementFinder = elementFinder;
2931
}
3032

@@ -50,6 +52,24 @@ protected Map<Class<? extends aquality.selenium.core.elements.interfaces.IElemen
5052
return typesMap;
5153
}
5254

55+
/**
56+
* Generates xpath locator for target element.
57+
*
58+
* @param multipleElementsLocator locator used to find elements.
59+
* @param webElement target element.
60+
* @param elementIndex index of target element.
61+
* @return target element's locator
62+
*/
63+
@Override
64+
protected By generateLocator(By multipleElementsLocator, WebElement webElement, int elementIndex) {
65+
try {
66+
return generateXpathLocator(multipleElementsLocator, webElement, elementIndex);
67+
} catch (InvalidArgumentException | JavascriptException ex) {
68+
return By.cssSelector((String) conditionalWait.waitFor(driver -> ((RemoteWebDriver) Objects.requireNonNull(driver))
69+
.executeScript(JavaScript.GET_ELEMENT_CSS_SELECTOR.getScript(), webElement), ex.getMessage() + ". CSS selector generation failed too."));
70+
}
71+
}
72+
5373
/**
5474
* Generates xpath locator for target element.
5575
*
@@ -60,9 +80,14 @@ protected Map<Class<? extends aquality.selenium.core.elements.interfaces.IElemen
6080
*/
6181
@Override
6282
protected By generateXpathLocator(By multipleElementsLocator, WebElement webElement, int elementIndex) {
63-
return isLocatorSupportedForXPathExtraction(multipleElementsLocator)
64-
? super.generateXpathLocator(multipleElementsLocator, webElement, elementIndex)
65-
: By.xpath((String) AqualityServices.getBrowser().executeScript(JavaScript.GET_ELEMENT_XPATH, webElement));
83+
if (isLocatorSupportedForXPathExtraction(multipleElementsLocator)) {
84+
By locator = super.generateXpathLocator(multipleElementsLocator, webElement, elementIndex);
85+
if (elementFinder.findElements(locator).size() == 1) {
86+
return locator;
87+
}
88+
}
89+
return By.xpath((String) conditionalWait.waitFor(driver -> ((RemoteWebDriver) Objects.requireNonNull(driver))
90+
.executeScript(JavaScript.GET_ELEMENT_XPATH.getScript(), webElement), "XPath generation failed"));
6691
}
6792

6893
/**
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function previousElementSibling (element) {
2+
if (element.previousElementSibling !== 'undefined') {
3+
return element.previousElementSibling;
4+
} else {
5+
// Loop through ignoring anything not an element
6+
while (element = element.previousSibling) {
7+
if (element.nodeType === 1) {
8+
return element;
9+
}
10+
}
11+
}
12+
}
13+
function getCssPath (element) {
14+
// Empty on non-elements
15+
if (!(element instanceof HTMLElement)) { return ''; }
16+
let path = [];
17+
while (element.nodeType === Node.ELEMENT_NODE) {
18+
let selector = element.nodeName;
19+
if (element.id) { selector += ('#' + element.id); }
20+
else {
21+
// Walk backwards until there is no previous sibling
22+
let sibling = element;
23+
// Will hold nodeName to join for adjacent selection
24+
let siblingSelectors = [];
25+
while (sibling !== null && sibling.nodeType === Node.ELEMENT_NODE) {
26+
siblingSelectors.unshift(sibling.nodeName);
27+
sibling = previousElementSibling(sibling);
28+
}
29+
// :first-child does not apply to HTML
30+
if (siblingSelectors[0] !== 'HTML') {
31+
siblingSelectors[0] = siblingSelectors[0] + ':first-child';
32+
}
33+
selector = siblingSelectors.join(' + ');
34+
}
35+
path.unshift(selector);
36+
element = element.parentNode;
37+
}
38+
return path.join(' > ');
39+
}
40+
return getCssPath(arguments[0]);

src/test/java/forms/ChromeDownloadsForm.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66
import org.openqa.selenium.By;
77
import org.openqa.selenium.SearchContext;
88

9+
import java.util.List;
10+
911
public class ChromeDownloadsForm extends Form {
1012
private static final String ADDRESS = "chrome://downloads/";
1113
public static final By NESTED_SHADOW_ROOT_LOCATOR = By.id("moreActionsMenu");
14+
public static final By DIV_ELEMENTS_LOCATOR = By.cssSelector("div");
15+
16+
private final ILabel lblDownloadsToolbar = getFormLabel().findElementInShadowRoot(By.cssSelector("downloads-toolbar"), "Downloads toolbar", ILabel.class);
17+
private final ILabel lblMainContainer = getFormLabel().findElementInShadowRoot(By.id("mainContainer"), "Main container", ILabel.class);
1218

1319
private final ILabel lblDownloadsToolbarFromJs = getFormLabel().getJsActions().findElementInShadowRoot(By.cssSelector("downloads-toolbar"), "Downloads toolbar", ILabel.class);
1420
private final ILabel lblMainContainerFromJs = getFormLabel().getJsActions().findElementInShadowRoot(By.id("mainContainer"), "Main container", ILabel.class);
@@ -31,11 +37,11 @@ public SearchContext expandShadowRootViaJs() {
3137
}
3238

3339
public ILabel getDownloadsToolbarLabel() {
34-
return getFormLabel().findElementInShadowRoot(By.cssSelector("downloads-toolbar"), "Downloads toolbar", ILabel.class);
40+
return lblDownloadsToolbar;
3541
}
3642

3743
public ILabel getMainContainerLabel() {
38-
return getFormLabel().findElementInShadowRoot(By.id("mainContainer"), "main container", ILabel.class);
44+
return lblMainContainer;
3945
}
4046

4147
public ILabel getDownloadsToolbarLabelFromJs() {
@@ -45,4 +51,20 @@ public ILabel getDownloadsToolbarLabelFromJs() {
4551
public ILabel getMainContainerLabelFromJs() {
4652
return lblMainContainerFromJs;
4753
}
54+
55+
public List<ILabel> getDivElementLabels() {
56+
return getFormLabel().findElementsInShadowRoot(DIV_ELEMENTS_LOCATOR, "div", ILabel.class);
57+
}
58+
59+
public List<ILabel> getDivElementLabelsFromJs() {
60+
return getFormLabel().getJsActions().findElementsInShadowRoot(DIV_ELEMENTS_LOCATOR, "div", ILabel.class);
61+
}
62+
63+
public List<ILabel> getMainContainerLabels() {
64+
return getFormLabel().findElementsInShadowRoot(lblMainContainer.getLocator(), lblMainContainer.getName(), ILabel.class);
65+
}
66+
67+
public List<ILabel> getMainContainerLabelsFromJs() {
68+
return getFormLabel().getJsActions().findElementsInShadowRoot(lblMainContainer.getLocator(), lblMainContainer.getName(), ILabel.class);
69+
}
4870
}

src/test/java/tests/usecases/ShadowRootTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import aquality.selenium.elements.interfaces.ILabel;
44
import forms.ChromeDownloadsForm;
5+
import org.openqa.selenium.By;
56
import org.testng.Assert;
67
import org.testng.annotations.BeforeMethod;
78
import org.testng.annotations.Test;
89
import tests.BaseTest;
910

11+
import java.util.List;
12+
1013
public class ShadowRootTests extends BaseTest {
1114
private static final ChromeDownloadsForm form = new ChromeDownloadsForm();
1215

@@ -19,16 +22,34 @@ public void beforeMethod() {
1922
@Test
2023
public void testExpandShadowRoot() {
2124
Assert.assertNotNull(form.expandShadowRoot(), "Should be possible to expand shadow root and get Selenium native ShadowRoot object");
25+
}
26+
27+
@Test
28+
public void testFindElementInShadowRoot() {
2229
Assert.assertNotNull(form.getDownloadsToolbarLabel().getElement(), "Should be possible do get the element hidden under the shadow");
2330
Assert.assertNotNull(form.getDownloadsToolbarLabel().findElementInShadowRoot(ChromeDownloadsForm.NESTED_SHADOW_ROOT_LOCATOR, "More actions menu", ILabel.class).getElement(),
2431
"Should be possible to expand the nested shadow root and get the element from it");
2532
Assert.assertTrue(form.getMainContainerLabel().state().isDisplayed(), "Should be possible to check that element under the shadow is displayed");
2633
}
2734

35+
@Test
36+
public void testFindElementsInShadowRoot() {
37+
List<ILabel> elementLabels = form.getDivElementLabels();
38+
Assert.assertTrue(elementLabels.size() > 1, "Should be possible to find multiple elements hidden under the shadow");
39+
Assert.assertTrue(elementLabels.get(0).getLocator() instanceof By.ByCssSelector, "Unique locator of correct type should be generated");
40+
Assert.assertEquals(elementLabels.get(0).getElement().getTagName(), "div", "Should be possible to work with one of found elements");
41+
Assert.assertEquals(form.getMainContainerLabels().get(0).getElement().getTagName(), "div", "Should be possible to work with one of found elements found by id");
42+
}
43+
2844
@Test
2945
public void testExpandShadowRootViaJs() {
3046
Assert.assertNotNull(form.expandShadowRootViaJs(), "Should be possible to expand shadow root and get Selenium native ShadowRoot object");
3147
Assert.assertNotNull(form.getDownloadsToolbarLabelFromJs().getElement(), "Should be possible do get the element hidden under the shadow");
48+
List<ILabel> elementLabels = form.getDivElementLabelsFromJs();
49+
Assert.assertTrue(elementLabels.size() > 1, "Should be possible to find multiple elements hidden under the shadow");
50+
Assert.assertTrue(elementLabels.get(0).getLocator() instanceof By.ByCssSelector, "Unique locator of correct type should be generated");
51+
Assert.assertEquals(elementLabels.get(0).getElement().getTagName(), "div", "Should be possible to work with one of found elements");
52+
Assert.assertEquals(form.getMainContainerLabelsFromJs().get(0).getElement().getTagName(), "div", "Should be possible to work with one of found elements found by id");
3253
Assert.assertNotNull(form.getDownloadsToolbarLabelFromJs().findElementInShadowRoot(ChromeDownloadsForm.NESTED_SHADOW_ROOT_LOCATOR, "More actions menu", ILabel.class).getElement(),
3354
"Should be possible to expand the nested shadow root and get the element from it");
3455
Assert.assertTrue(form.getMainContainerLabelFromJs().state().isDisplayed(), "Should be possible to check that element under the shadow is displayed");

0 commit comments

Comments
 (0)