Skip to content

Commit c7d693e

Browse files
authored
Add ContainerResolver interface (#297)
1 parent 176e081 commit c7d693e

File tree

18 files changed

+400
-12
lines changed

18 files changed

+400
-12
lines changed

selenium4Deps.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ dependencies {
3232
api 'org.seleniumhq.selenium:selenium-firefox-driver:4.33.0'
3333
api 'org.seleniumhq.selenium:selenium-opera-driver:4.4.0'
3434
api 'org.seleniumhq.selenium:selenium-safari-driver:4.33.0'
35-
api 'com.nordstrom.ui-tools:htmlunit-remote:4.32.0'
36-
api 'org.seleniumhq.selenium:htmlunit3-driver:4.32.0'
35+
api 'com.nordstrom.ui-tools:htmlunit-remote:4.33.0'
36+
api 'org.seleniumhq.selenium:htmlunit3-driver:4.33.0'
3737
api 'org.htmlunit:htmlunit:4.12.0'
3838
api 'com.codeborne:phantomjsdriver:1.5.0'
3939
api 'org.apache.httpcomponents:httpclient:4.5.14'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.nordstrom.automation.selenium.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
import com.nordstrom.automation.selenium.interfaces.ContainerResolver;
9+
10+
/**
11+
* This annotation enables you to specify a resolver class for pages and components that vary based on browser window
12+
* dimensions (responsive layout) or UI/UX variations (A/B testing). This feature can also select between old and new
13+
* versions of site-wide components (e.g. - common navigation) during the transition between versions when some pages
14+
* have been updated but others haven't.
15+
*
16+
* @see ContainerResolver
17+
*/
18+
@Target(ElementType.TYPE)
19+
@Retention(RetentionPolicy.RUNTIME)
20+
public @interface Resolver {
21+
/**
22+
* Get the value class of the container resolver.
23+
*
24+
* @return container resolver class
25+
*/
26+
@SuppressWarnings("rawtypes")
27+
Class<? extends ContainerResolver> value();
28+
}

src/main/java/com/nordstrom/automation/selenium/examples/ExamplePage.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ protected enum Using implements ByEnum {
131131
/** prompt button */
132132
PROMPT(By.cssSelector("button#prompt")),
133133
/** result paragraph */
134-
RESULT(By.cssSelector("p#result"));
134+
RESULT(By.cssSelector("p#result")),
135+
/** open tab button */
136+
OPEN_TAB(By.cssSelector("button#open-tab"));
135137

136138
private final By locator;
137139

@@ -552,6 +554,16 @@ public String getModalResult() {
552554
return findElement(Using.RESULT).getText();
553555
}
554556

557+
/**
558+
* Open the example tab.
559+
*
560+
* @return tab page object (either {@link TabPageA} or {@link TabPageB})
561+
*/
562+
public TabPage openTab() {
563+
findElement(Using.OPEN_TAB).click();
564+
return new TabPage(driver).setWindowState(WindowState.WILL_OPEN);
565+
}
566+
555567
/**
556568
* Set the active Grid hub as the base URI for all relative loads of test pages.
557569
*
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.nordstrom.automation.selenium.examples;
2+
3+
import org.openqa.selenium.By;
4+
import org.openqa.selenium.WebDriver;
5+
6+
import com.nordstrom.automation.selenium.annotations.Resolver;
7+
import com.nordstrom.automation.selenium.interfaces.DetectsLoadCompletion;
8+
import com.nordstrom.automation.selenium.model.Page;
9+
10+
/**
11+
* This class is the abstract model for the tab pages opened by the "Open A/B Tab" button on the 'Example' page.
12+
* The {@link Resolver} annotation specifies the container resolver that selects the concrete subclass model for
13+
* the specific page that gets opened ({@link TabPageA} or {@link TabPageB}).
14+
*/
15+
@Resolver(TabPageResolver.class)
16+
public class TabPage extends Page implements DetectsLoadCompletion {
17+
18+
/**
19+
* Constructor for tab page view context.
20+
*
21+
* @param driver driver object
22+
*/
23+
public TabPage(WebDriver driver) {
24+
super(driver);
25+
}
26+
27+
/**
28+
* This enumeration defines element locator constants.
29+
*/
30+
enum Using implements ByEnum {
31+
/** page heading locator */
32+
HEADING(By.cssSelector("h1"));
33+
34+
private final By locator;
35+
36+
Using(By locator) {
37+
this.locator = locator;
38+
}
39+
40+
@Override
41+
public By locator() {
42+
return locator;
43+
}
44+
}
45+
46+
/**
47+
* Get content of tab page.
48+
*
49+
* @return tab page content
50+
*/
51+
public String getPageContent() {
52+
return findElement(Using.HEADING).getText();
53+
}
54+
55+
/**
56+
* Verify content of tab page.
57+
*
58+
* @return {@code true} if verification succeeds; otherwise {@code false}
59+
*/
60+
public boolean verifyContent() {
61+
throw new UnsupportedOperationException();
62+
}
63+
64+
/**
65+
* {@inheritDoc}
66+
*/
67+
@Override
68+
public boolean isLoadComplete() {
69+
return verifyContent();
70+
}
71+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.nordstrom.automation.selenium.examples;
2+
3+
import org.openqa.selenium.WebDriver;
4+
5+
import com.nordstrom.automation.selenium.annotations.PageUrl;
6+
7+
/**
8+
* This class is the model for the first concrete tab pages opened by the "Open A/B Tab" button on the 'Example'
9+
* page.
10+
*/
11+
@PageUrl("/grid/admin/FrameA_Servlet")
12+
public class TabPageA extends TabPage {
13+
14+
/** expected content */
15+
public static String EXPECT_CONTENT = "Frame A";
16+
17+
/**
18+
* Constructor for tab page view context.
19+
*
20+
* @param driver driver object
21+
*/
22+
public TabPageA(WebDriver driver) {
23+
super(driver);
24+
}
25+
26+
/**
27+
* {@inheritDoc}
28+
*/
29+
@Override
30+
public boolean verifyContent() {
31+
return getPageContent().equals(EXPECT_CONTENT);
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.nordstrom.automation.selenium.examples;
2+
3+
import org.openqa.selenium.WebDriver;
4+
5+
import com.nordstrom.automation.selenium.annotations.PageUrl;
6+
7+
/**
8+
* This class is the model for the second concrete tab pages opened by the "Open A/B Tab" button on the 'Example'
9+
* page.
10+
*/
11+
@PageUrl("/grid/admin/FrameB_Servlet")
12+
public class TabPageB extends TabPage {
13+
14+
/** expected content */
15+
public static String EXPECT_CONTENT = "Frame B";
16+
17+
/**
18+
* Constructor for tab page view context.
19+
*
20+
* @param driver driver object
21+
*/
22+
public TabPageB(WebDriver driver) {
23+
super(driver);
24+
}
25+
26+
/**
27+
* {@inheritDoc}
28+
*/
29+
@Override
30+
public boolean verifyContent() {
31+
return getPageContent().equals(EXPECT_CONTENT);
32+
}
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.nordstrom.automation.selenium.examples;
2+
3+
import com.nordstrom.automation.selenium.exceptions.UnresolvedContainerTypeException;
4+
import com.nordstrom.automation.selenium.interfaces.ContainerResolver;
5+
import com.nordstrom.automation.selenium.model.RobustWebElement;
6+
7+
/**
8+
* This class implements the container resolver for the tab pages opened by the "Open A/B Tab" button on the 'Example'
9+
* page.
10+
*/
11+
public class TabPageResolver implements ContainerResolver<TabPage> {
12+
@Override
13+
public TabPage resolve(TabPage container) {
14+
RobustWebElement element = container.findOptional(TabPage.Using.HEADING);
15+
if (element.hasReference()) {
16+
String pageContent = element.getText();
17+
if (pageContent.equals(TabPageA.EXPECT_CONTENT)) {
18+
return new TabPageA(container.getWrappedDriver());
19+
}
20+
if (pageContent.equals(TabPageB.EXPECT_CONTENT)) {
21+
return new TabPageB(container.getWrappedDriver());
22+
}
23+
throw new UnresolvedContainerTypeException("Unsupported tab page heading: " + pageContent);
24+
}
25+
throw new UnresolvedContainerTypeException("Tab page heading not found");
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.nordstrom.automation.selenium.exceptions;
2+
3+
import com.nordstrom.automation.selenium.interfaces.ContainerResolver;
4+
5+
/**
6+
* This exception is thrown if specified container resolver cannot be instantiated or if
7+
* invocation of the {@link ContainerResolver#resolve resolve} method fails.
8+
*/
9+
public class ContainerResolverInvocationException extends RuntimeException {
10+
11+
private static final long serialVersionUID = -422498744356462151L;
12+
13+
/**
14+
* Constructor for "container resolver invocation" exception with specified detail message
15+
* and associated cause.
16+
*
17+
* @param message the detail message (which is saved for later retrieval
18+
* by the {@link #getMessage()} method).
19+
* @param cause the cause (which is saved for later retrieval by the
20+
* {@link #getCause()} method). (A {@code null} value is
21+
* permitted, and indicates that the cause is nonexistent or
22+
* unknown.)
23+
*/
24+
public ContainerResolverInvocationException(String message, Throwable cause) {
25+
super(message, cause);
26+
}
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.nordstrom.automation.selenium.exceptions;
2+
3+
/**
4+
* This exception is thrown if the concrete container type cannot be resolved.
5+
*/
6+
public class UnresolvedContainerTypeException extends RuntimeException {
7+
8+
private static final long serialVersionUID = -422498744356462151L;
9+
10+
/**
11+
* Constructor for "unresolved container type" exception with default message.
12+
*/
13+
public UnresolvedContainerTypeException() {
14+
super("Concrete container type cannot be resolved");
15+
}
16+
17+
/**
18+
* Constructor for "unresolved container type" exception with specified detail message.
19+
*
20+
* @param message detail message
21+
*/
22+
public UnresolvedContainerTypeException(String message) {
23+
super(message);
24+
}
25+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.nordstrom.automation.selenium.interfaces;
2+
3+
import com.nordstrom.automation.selenium.annotations.Resolver;
4+
import com.nordstrom.automation.selenium.exceptions.UnresolvedContainerTypeException;
5+
import com.nordstrom.automation.selenium.model.ComponentContainer;
6+
7+
/**
8+
* Implementations of this interface are used to resolve concrete {@link ComponentContainer} types based on current DOM
9+
* state. This can be used to handle multiple versions of a page or component in which an equivalent set of features is
10+
* presented in multiple ways based on browser window dimensions (responsive layout) or UI/UX variations (A/B testing).
11+
* This feature can also select between old and new versions of site-wide components (e.g. - common navigation) during
12+
* the transition between versions when some pages have been updated but others haven't.
13+
*
14+
* @param <T> context container type
15+
* @see Resolver
16+
*/
17+
@FunctionalInterface
18+
public interface ContainerResolver<T extends ComponentContainer> {
19+
/**
20+
* Resolve this container to its context-specific type.
21+
*
22+
* @param container context container object
23+
* @return context-specific container object
24+
* @throws UnresolvedContainerTypeException if concrete type cannot be resolved
25+
*/
26+
T resolve(T container);
27+
}

0 commit comments

Comments
 (0)