Skip to content

Commit

Permalink
Fix shadow DOM 'findElements'; add unit tests (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
sbabcoc authored May 2, 2022
1 parent e3adadb commit d7cff50
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 47 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ dependencies {
constraints {
api 'com.nordstrom.tools:java-utils:2.1.0'
api 'com.nordstrom.tools:settings:2.3.10'
api 'com.nordstrom.tools:junit-foundation:15.3.4'
api 'com.nordstrom.tools:junit-foundation:16.0.1'
api 'com.github.sbabcoc:logback-testng:1.3.4'
api 'org.hamcrest:hamcrest-core:2.2'
api 'org.yaml:snakeyaml:1.28'
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<source-plugin.version>3.2.1</source-plugin.version>
<javadoc-plugin.version>3.3.0</javadoc-plugin.version>
<settings.version>2.3.10</settings.version>
<junit-foundation.version>15.3.4</junit-foundation.version>
<junit-foundation.version>16.0.1</junit-foundation.version>
<logback-testng.version>1.3.4</logback-testng.version>
<sonar.language>java</sonar.language>
<jacoco.version>0.8.7</jacoco.version>
Expand Down Expand Up @@ -325,7 +325,7 @@
<guava.version>30.1.1-jre</guava.version>
<httpcomponents.version>4.5.13</httpcomponents.version>
<jsoup.version>1.14.2</jsoup.version>
<htmlunit.version>2.59.0</htmlunit.version>
<htmlunit.version>2.61.0</htmlunit.version>
<jcommander.version>1.78</jcommander.version>
<mockito.version>3.1.0</mockito.version>
<!-- managed to resolve identified threat -->
Expand Down
2 changes: 1 addition & 1 deletion selenium2Deps.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sourceSets {

dependencies {
constraints {
api 'com.nordstrom.tools:testng-foundation:3.0.6-j7'
api 'com.nordstrom.tools:testng-foundation:3.0.9-j7'
api 'commons-io:commons-io:2.4'
api 'org.seleniumhq.selenium:selenium-server:2.53.1'
api 'org.seleniumhq.selenium:selenium-support:2.53.1'
Expand Down
4 changes: 2 additions & 2 deletions selenium3Deps.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sourceSets {

dependencies {
constraints {
api 'com.nordstrom.tools:testng-foundation:3.0.6-j8'
api 'com.nordstrom.tools:testng-foundation:3.0.9-j8'
api 'org.seleniumhq.selenium:selenium-server:3.141.59'
api 'org.seleniumhq.selenium:selenium-support:3.141.59'
api 'org.apache.httpcomponents:httpclient:4.5.13'
Expand All @@ -35,7 +35,7 @@ dependencies {
api 'com.squareup.okhttp3:okhttp:4.9.1'
api 'com.squareup.okio:okio:2.10.0'
api 'org.eclipse.jetty.websocket:websocket-client:9.4.43.v20210629'
testImplementation 'org.seleniumhq.selenium:htmlunit-driver:2.59.0'
testImplementation 'org.seleniumhq.selenium:htmlunit-driver:2.61.0'
testImplementation 'org.mockito:mockito-core:3.1.0'
}
api 'com.nordstrom.tools:testng-foundation'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public final class JsUtility {
private static final String MESSAGE_KEY = "message";

private static final String DOCUMENT_READY = getScriptResource("documentReady.js");
private static final String CREATE_SCRIPT_NODE = getScriptResource("createScriptNode.js");

private static final List<String> JS_EXCEPTIONS = Arrays.asList(
"org.openqa.selenium.WebDriverException",
Expand Down Expand Up @@ -198,7 +199,7 @@ public TimeoutException differentiateTimeout(TimeoutException e) {
public static void injectGlueLib(final WebDriver driver) {
JavascriptExecutor executor = WebDriverUtils.getExecutor(driver);
if ((boolean) executor.executeScript("return (typeof isObject != 'function');")) {
executor.executeScript(getScriptResource(JsUtility.JAVA_GLUE_LIB));
executor.executeScript(CREATE_SCRIPT_NODE, getScriptResource(JAVA_GLUE_LIB));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.nordstrom.automation.selenium.examples;

import java.util.Arrays;
import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;

import com.nordstrom.automation.selenium.model.ComponentContainer;
import com.nordstrom.automation.selenium.model.RobustWebElement;
Expand All @@ -22,7 +26,8 @@ public ShadowRootComponent(final RobustWebElement element, final ComponentContai
}

private enum Using implements ByEnum {
HEADING(By.cssSelector("h1"));
HEADING(By.cssSelector("h1")),
PARA(By.cssSelector("p[id^='para-']"));

private By locator;

Expand All @@ -36,10 +41,15 @@ public By locator() {
}
}

public String getContent() {
public String getHeading() {
return findElement(Using.HEADING).getText();
}


public List<String> getParagraphs() {
List<WebElement> paraList = findElements(Using.PARA);
return Arrays.asList(paraList.get(0).getText(), paraList.get(1).getText(), paraList.get(2).getText());
}

public static Object getKey(final SearchContext context) {
return getShadowRoot(context).findElement(Using.HEADING.locator).getText();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public List<WebElement> findElements(final By by) {
@Override
public WebElement findElement(final By by) {
String script = SearchContextUtils.buildScriptToLocateElement(ContextType.SHADOW, by, 0);
return JsUtility.runAndReturn(driver, script, context);
return RobustElementFactory.makeRobustElement(null, (RobustWebElement) context, script);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ private enum Strategy { LOCATOR, SCRIPT }

private final WebDriver driver;
private final WrapsContext context;
private WebElement wrapped;
private By locator;
private int index;
private final String script;
private final By locator;
private final int index;
private final Strategy strategy;

private String script;
private Strategy strategy = Strategy.LOCATOR;
private WebElement wrapped;

private long acquiredAt;

Expand All @@ -77,30 +77,40 @@ public RobustElementWrapper(
// if specified element is already robust
if (element instanceof RobustWebElement) {
RobustElementWrapper wrapper = ((InterceptionAccessor) element).getInterceptor();
this.acquiredAt = wrapper.acquiredAt;

this.wrapped = wrapper.wrapped;
this.driver = wrapper.driver;
this.context = wrapper.context;

this.script = wrapper.script;
this.locator = wrapper.locator;
this.index = wrapper.index;
this.strategy = wrapper.strategy;

this.wrapped = wrapper.wrapped;
this.acquiredAt = wrapper.acquiredAt;
} else {
Objects.requireNonNull(context, "[context] must be non-null");
Objects.requireNonNull(locator, "[locator] must be non-null");
if (index < OPTIONAL) {
throw new IndexOutOfBoundsException("Specified index is invalid");
}

this.wrapped = element;
this.driver = WebDriverUtils.getDriver(context.getWrappedContext());
this.context = context;
this.locator = locator;
this.index = index;
}

driver = WebDriverUtils.getDriver(this.context.getWrappedContext());

if ((this.index == OPTIONAL) || (this.index > 0)) {
script = SearchContextUtils.buildScriptToLocateElement(this.context, this.locator, this.index);
strategy = Strategy.SCRIPT;

if ((index == OPTIONAL) || (index > 0)) {
this.script = SearchContextUtils.buildScriptToLocateElement(context, locator, index);
this.locator = null;
this.index = (index == OPTIONAL) ? OPTIONAL : CARDINAL;
this.strategy = Strategy.SCRIPT;
} else {
this.script = null;
this.locator = locator;
this.index = index;
this.strategy = Strategy.LOCATOR;
}

this.wrapped = element;
}

if (this.wrapped == null) {
Expand All @@ -124,21 +134,18 @@ public RobustElementWrapper(
public RobustElementWrapper(
final WebElement element, final WrapsContext context, final String script) {

this.wrapped = element;
this.driver = WebDriverUtils.getDriver(context.getWrappedContext());
this.context = context;
this.index = CARDINAL;

driver = WebDriverUtils.getDriver(this.context.getWrappedContext());

this.script = script;
strategy = Strategy.SCRIPT;
this.locator = null;
this.index = CARDINAL;
this.strategy = Strategy.SCRIPT;

this.wrapped = element;

if (this.wrapped == null) {
if (this.index == OPTIONAL) {
acquireReference(this);
} else {
refreshReference(null);
}
refreshReference(null);
} else if (acquiredAt == 0) {
acquiredAt = System.currentTimeMillis();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,17 @@ public static String buildScriptToLocateElements(final WrapsContext context, fin
*/
public static String buildScriptToLocateElements(final ContextType type, final By locator) {
String format;
String shadow = (type == ContextType.SHADOW) ? CHECK_SHADOW_HOST : "";

String selector = ByType.cssLocatorFor(locator);
String selector = quoteSelector(ByType.cssLocatorFor(locator));
if (selector != null) {
format = LOCATE_EVERY_BY_CSS;
} else {
selector = ByType.xpathLocatorFor(locator);
selector = quoteSelector(ByType.xpathLocatorFor(locator));
format = LOCATE_EVERY_BY_XPATH;
}

return String.format(format, type.name, selector);
return String.format(shadow + format, type.name, selector);
}

/**
Expand Down
26 changes: 26 additions & 0 deletions src/main/resources/ExamplePage.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,38 @@
var rootA = elemA.attachShadow({mode: 'open'});
var h1_a = document.createElement('h1');
h1_a.textContent = 'Shadow DOM A';
h1_a.id = 'shadow-heading-a';
var para_1_a = document.createElement('p');
para_1_a.textContent = '[A] This is paragraph one.';
para_1_a.id = 'para-1-a';
var para_2_a = document.createElement('p');
para_2_a.textContent = '[A] This is paragraph two.';
para_2_a.id = 'para-2-a';
var para_3_a = document.createElement('p');
para_3_a.textContent = '[A] This is paragraph three.';
para_3_a.id = 'para-3-a';
rootA.appendChild(h1_a);
rootA.appendChild(para_1_a);
rootA.appendChild(para_2_a);
rootA.appendChild(para_3_a);

var rootB = elemB.attachShadow({mode: 'open'});
var h1_b = document.createElement('h1');
h1_b.textContent = 'Shadow DOM B';
h1_b.id = 'shadow-heading-b';
var para_1_b = document.createElement('p');
para_1_b.textContent = '[B] This is paragraph one.';
para_1_b.id = 'para-1-b';
var para_2_b = document.createElement('p');
para_2_b.textContent = '[B] This is paragraph two.';
para_2_b.id = 'para-2-b';
var para_3_b = document.createElement('p');
para_3_b.textContent = '[B] This is paragraph three.';
para_3_b.id = 'para-3-b';
rootB.appendChild(h1_b);
rootB.appendChild(para_1_b);
rootB.appendChild(para_2_b);
rootB.appendChild(para_3_b);
}
}
</script>
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/createScriptNode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.textContent = arguments[0];
head.appendChild(script);
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public static Runnable testShadowRootByLocator(final TestBase instance) {
@Override
public void run() {
ShadowRootComponent shadowRoot = page.getShadowRootByLocator();
assertEquals(shadowRoot.getContent(), SHADOW_DOM_A);
assertEquals(shadowRoot.getHeading(), SHADOW_DOM_A);
}
};
}
Expand All @@ -126,7 +126,7 @@ public static Runnable testShadowRootByElement(final TestBase instance) {
@Override
public void run() {
ShadowRootComponent shadowRoot = page.getShadowRootByElement();
assertEquals(shadowRoot.getContent(), SHADOW_DOM_B);
assertEquals(shadowRoot.getHeading(), SHADOW_DOM_B);
}
};
}
Expand All @@ -139,8 +139,8 @@ public static Runnable testShadowRootList(final TestBase instance) {
public void run() {
List<ShadowRootComponent> shadowRootList = page.getShadowRootList();
assertEquals(shadowRootList.size(), 2);
assertEquals(shadowRootList.get(0).getContent(), SHADOW_DOM_A);
assertEquals(shadowRootList.get(1).getContent(), SHADOW_DOM_B);
assertEquals(shadowRootList.get(0).getHeading(), SHADOW_DOM_A);
assertEquals(shadowRootList.get(1).getHeading(), SHADOW_DOM_B);
}
};
}
Expand All @@ -153,8 +153,8 @@ public static Runnable testShadowRootMap(final TestBase instance) {
public void run() {
Map<Object, ShadowRootComponent> shadowRootMap = page.getShadowRootMap();
assertEquals(shadowRootMap.size(), 2);
assertEquals(shadowRootMap.get(SHADOW_DOM_A).getContent(), SHADOW_DOM_A);
assertEquals(shadowRootMap.get(SHADOW_DOM_B).getContent(), SHADOW_DOM_B);
assertEquals(shadowRootMap.get(SHADOW_DOM_A).getHeading(), SHADOW_DOM_A);
assertEquals(shadowRootMap.get(SHADOW_DOM_B).getHeading(), SHADOW_DOM_B);
}
};
}
Expand Down Expand Up @@ -235,4 +235,25 @@ public static void testBogusOptional(TestBase instance) {
assertFalse(page.hasBogusOptional());
}

public static Runnable testShadowParagraphs(final TestBase instance) {
return new Runnable() {
final ExamplePage page = instance.getInitialPage();

@Override
public void run() {
ShadowRootComponent shadowRoot = page.getShadowRootByLocator();
List<String> paraList = shadowRoot.getParagraphs();
assertEquals(paraList.size(), 3);

String[] expect = new String[3];
String heading = shadowRoot.getHeading();
String marker = String.format("[%s] ", heading.substring(11));
for (int i = 0; i < 3; i++) {
expect[i] = marker + PARAS[i];
}
assertArrayEquals(paraList.toArray(), expect);
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.junit.Ignore;
import org.junit.Test;

import com.nordstrom.automation.selenium.annotations.InitialPage;
import com.nordstrom.automation.selenium.core.ModelTestCore;
import com.nordstrom.automation.selenium.examples.ExamplePage;
Expand Down Expand Up @@ -146,4 +147,14 @@ public void testBogusOptional() {
ModelTestCore.testBogusOptional(this);
}

@Test
@Ignore
public void testShadowParagraphs() {
try {
ModelTestCore.testShadowParagraphs(this).run();
} catch (ShadowRootContextException e) {
assumeNoException(e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,13 @@ public void testBogusOptional() {
ModelTestCore.testBogusOptional(this);
}

@Test
public void testShadowParagraphs() {
try {
ModelTestCore.testShadowParagraphs(this).run();
} catch (ShadowRootContextException e) {
throw new SkipException(e.getMessage(), e);
}
}

}

0 comments on commit d7cff50

Please sign in to comment.