Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 888ec15

Browse files
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)

AltStyle によって変換されたページ (->オリジナル) /