SlideShare a Scribd company logo
1 of 89
S
Generic UXD Legos
How to Build the Page Object API of Your Dreams
By Selena Phillips | Akamai Technologies
Introduction
The Page Object design pattern has always had some
weaknesses. Refinements on basic design pattern have emerged
to address some of these shortcomings – the
SlowLoadableComponent pattern and the practice of modeling
the business layer separately from the UI layer, for example.
These refinements don’t make developing page objects any easier
or faster, nor do they provide a solution to the frustrating inevitability
that some page object interactions work perfectly in some
environments while failing silently in others.
Table of
Contents
S Page Object API
Requirements
S Recipe for a New
Component Model
S Specification
S Design
S Implementation
S Examples
S Loadable
S Expandable
S Decorator
S Menu
S
Page Object API
Requirements
API Requirements
S Generic UXD Legos for composing page objects
A library of ready-to-use models of common UI components
would significantly reduce the amount of boilerplate code.
S Dynamically configurable page objects
It should be possible to specify that your page object should
click that pesky button using a JavaScript workaround for the
environments where it fails silently without boilerplate if-then-
else clauses.
API Requirements – page 2
S Standardized approach for expanding the component
library
There should be a virtually cookie-cutter approach for adding
new components to the library.
S Easy substitution of custom component implementations
Because there is no such thing as one-size-fits-all, it should be
trivial to substitute a custom implementation of a component
type that utilizes the same basic interface as the default.
S
Recipe for a New
Component Model
Specification
Design
Implementation
Recipe for a New Component Model:
Specification
S Identify the basic interface and interaction model for the
component
S Identify the minimal state that must be specified to construct
the component
S Identify the component's place in the inheritance hierarchy of
other components
S Identify the dynamically configurable behaviors to make the
component robust across different environments
Recipe for a New Component Model:
Design
S Write a Java interface for the component
S Write a Java interface for a state bean to specify the state
necessary to instantiate the component
S Write a Java interface for a fluent builder for the component
S Write a Java interface for a configuration bean for the
component
Recipe for a New Component Model:
Implementation
S Write a default implementation of the state bean
S Write abstract and default implementations of the builder
S Write an abstract and a default implementation of the
component, if it makes sense
S Write a default implementation of the configuration bean
S
Examples
Loadable
Expandable
Decorator
Menu
S
Examples: Loadable
Loadable:
The most basic component
At the top of the inheritance hierarchy is the Loadable. This
minimal component requires:
S A WebDriver reference
S A load timeout
LoadableBean:
Loadable state bean
LoadableBean is a simple POJO for specifying the state
necessary to construct a Loadable:
S A WebDriver reference
S A load timeout
public interface Loadable {
int DEFAULT_LOAD_TIMEOUT = 30;
WebDriver getDriver();
int getLoadTimeout();
}
public interface LoadableBean {
void setDriver(WebDriver driver);
WebDriver getDriver();
void setLoadTimeout(int timeout);
int getLoadTimeout();
}
LoadableConfig:
Loadable configuration bean
LoadableConfig is a POJO for specifying environment sensitive
state information for a Loadable.
S Needs to configure the load timeout value which can be
affected by the test environment
S Needs to be deserializable with Jackson, so it can be
instantiated with configuration data retrieved from an external
datastore
LoadableConfig:
Loadable configuration bean – page 2
S Needs Optional fields so that a distinction can be made
between a configuration value that is absent and one that is
explicitly null
S Needs to specify type information because configuration beans
for more specialized components extend beans for less
specialized components. Jackson needs this type information
to properly handle the polymorphism.
public interface LoadableConfig {
Class<? extends LoadableConfig> getType();
void setType(Class<? Extends LoadableConfig> type);
Optional<Integer> getLoadTimeout();
void setLoadTimeout(Optional<Integer> timeout);
}
LoadableBuilder:
Fluent Loadable builder
S Needs to be extensible because builders for more specialized
components will extend it
S Needs to have setters that return the runtime type of the
builder, so that they can be called in any order because a
complex component requires specification of numerous state
fields before it can be instantiated
public interface LoadableBuilder<
LoadableT extends Loadable,
BeanT extends LoadableBean,
//The reflexive parameter makes it possible to
//return the runtime type of the builder
BuilderT extends
LoadableBuilder<LoadableT,BeanT, BuilderT>> {
BeanT getState();
BuilderT setComponentClass(Class<LoadableT> clazz);
Class<LoadableT> getComponentClass();
BuilderT setDriver(WebDriver driver);
BuilderT setLoadTimeout(int timeout);
LoadableT build();
}
LoadableBeanImpl:
Default Loadable state bean
S Uses Lombok to streamline the source code and reduce
boilerplate code
S Intializes fields with interface defaults where possible
public class LoadableBeanImpl implements
LoadableBean {
private @Getter @Setter WebDriver driver;
private @Getter @Setter int loadTimeout =
DEFAULT_LOAD_TIMEOUT;
}
LoadableConfigImpl:
Default Loadable configuration bean
S Uses Lombok to streamline the source code and reduce
boilerplate code
S Uses JSON as the external datastore
@JsonTypeInfo(
//Determines how type information is specified in the
//source JSON
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "type”, visible = true)
@JsonSubTypes({
@Type(value = PolleableConfigImpl.class),
//Omitted for brevity. All sub-types of
//LoadableConfigImpl must be listed })
public class LoadableConfigImpl implements
LoadableConfig {
//The type field must be present in the source JSON.
@JsonProperty(required = true)
private @Getter @Setter Class<? extends
LoadableConfig> type;
@JsonProperty
private @Getter @Setter Optional<Integer> loadTimeout;
}
AbstractLoadableBuilder:
Parent of all API builders
S Uses Lombok to streamline the source code and reduce
boilerplate code
S Needs to be extensible because builder implementations for
more specialized components will extend it
S Needs to implement all methods in the LoadableBuilder
interface because its main purpose is to be extended by builder
implementations for more specialized components
public abstract class AbstractLoadableBuilder<
LoadableT extends Loadable,
BeanT extends LoadableBean,
//Use the same reflexive parameter that is used in
//the LoadableBuilder interface because this class
//is intended to be extended by more specialized
//builder implementations, just as the interface
//is intended to be extended by interfaces for
//more specialized builders
BuilderT extends
LoadableBuilder<LoadableT,BeanT,BuilderT>>
implements LoadableBuilder<LoadableT,BeanT,BuilderT> {
private @Getter BeanT state;
private @Getter Class<LoadableT> componentClass;
public AbstractLoadableBuilder(BeanT bean) {
this.state = bean;
}
…continued…
…AbstractLoadableBuilder continued…
public BuilderT setComponentClass(Class<LoadableT>
clazz) {
this.componentClass = clazz;
return (BuilderT)this;
}
public BuilderT setDriver(WebDriver driver) {
getState().setDriver(driver);
return (BuilderT)this;
}
public BuilderT setLoadTimeout(int timeout) {
getState().setLoadTimeout(timeout);
return (BuilderT)this;
}
…continued…
…AbstractLoadableBuilder continued…
//The factory and its interface are not shown in this
//presentation. The default implementation is a
//singleton class with a static accessor method for
//the singleton instance. To substitute a non-default
//implementation of the interface, a sub-class can
//override this method.
protected LoadableFactory getFactory() {
return LoadableFactoryImpl.getInstance();
}
//The default factory implementation uses reflection
//to get a constructor for the component. Therefore,
//the component must define a single-arg constructor
//that accepts the state bean as a parameter.
public LoadableT build() {
return getFactory().create(getState(),
componentClass);
}
}
LoadableBuilderImpl:
Default Loadable builder
S Uses the default implementation of LoadableBean
S Extends AbstractLoadableBuilder
S Doesn’t need a reflexive generic type parameter in the class
declaration because it is not intended to be extended
S Needs to define only a constructor because
AbstractLoadableBuilder implements all the methods in
LoadableBuilder
//The only generic type parameter that is necessary for
//this default builder is for the component it builds
public class LoadableBuilderImpl<LoadableT extends
Loadable>
extends AbstractLoadableBuilder<
LoadableT,
LoadableBean,
LoadableBuilderImpl<LoadableT>
> {
//Use the default LoadableBean implementation
public LoadableBuilderImpl() {
super(new LoadableBeanImpl());
}
}
AbstractLoadable:
Parent of all components
S Uses Lombok to reduce boilerplate
S Extends SlowLoadableComponent
S Assumes child classes annotate their WebElements with
locators, so uses PageFactory in load()
S Provides the means to query a configuration service for a
configuration by ID
public abstract class AbstractLoadable<LoadableT
extends AbstractLoadable<LoadableT>>
extends SlowLoadableComponent<LoadableT>
implements Loadable {
private @Getter WebDriver driver;
private @Getter int loadTimeout;
//Use a state bean to provide all the state
//data necessary to instantiate a component
public AbstractLoadable(LoadableBean bean) {
super(new SystemClock(),
bean.getLoadTimeout());
this.driver = bean.getDriver();
this.loadTimeout =bean.getLoadTimeout();
}
protected void load() {
PageFactory.initElements(getDriver(), this);
}
…continued…
…AbstractLoadableComponent continued…
//A non-default implementation can be substituted by
//overriding this method.
protected ConfigService getConfigService() {
return ConfigServiceImpl.getInstance();
}
protected <ConfigT extends LoadableConfig> ConfigT
getConfig(String id) {
//A profile is a collection of configurations
//based on environment.
if(getConfigService().getProfile() != null) {
return service.getConfig(id);
}
}
}
ConfigService:
Dynamic configuration service
S Needs to provide a means to set the active configuration profile
S Needs to provide a means to query the active configuration
profile for a component configuration by ID
public interface ConfigService {
void setProfile(String profile);
String getProfile();
<ConfigT extends LoadableConfig> ConfigT
getConfig(String id);
}
ConfigServiceImpl:
Default configuration service
S Is a singleton class with a static accessor method for the
singleton instance
S Expects configuration profiles to be in the
resources/pageobject_config folder
S Expects a filename to be specifed for locating a configuration
profile in the above folder
S Expects a map data structure in the JSON source
public class ConfigServiceImpl implements ConfigService {
//Filename of the current configuration profile, based
//on the OS, browser and browser version. The file is
//a JSON file with a map structure, with a String id
//for each component configuration in the file
private @Getter String profile;
private Map<String,LoadableConfig> configs =
new HashMap<>();
private static final class Loader {
private static final ConfigServiceImpl INSTANCE =
new ConfigServiceImpl();
}
private ConfigServiceImpl() { }
public static ConfigServiceImpl getInstance() {
return Loader.INSTANCE;
}
…continued…
…ConfigServiceImpl continued…
public void setProfile(String profile) {
this.profile = profile;
URL configUrl = ConfigServiceImpl.class
.getClassLoader()
.getResource("./pageobject_config/” + profile);
ObjectMapper mapper = new ObjectMapper()
.registerModule(new Jdk8Module());
//Give type info to Jackson for the Map field
MapType mapType = mapper.getTypeFactory()
.constructMapType(HashMap.class, String.class,
LoadableConfigImpl.class);
//try-catch blocks omitted for brevity
File configFile = new File(configUrl.toURI());
configs = mapper.readValue(configFile, mapType);
}
…continued…
…ConfigServiceImpl continued…
public <ConfigT extends LoadableConfig> ConfigT
getConfig(String id) {
return (ConfigT)configs.get(id);
}
}
//Example configuration JSON source. Three
//components have configurations for their load
//timeout values
{
“com.akamai.SomeLoadable":{
"type":"com.akamai.LoadableConfigImpl",
”loadTimeout”:120
},
“com.akamai.SomeOtherLoadable”:{
"type":"com.akamai.LoadableConfigImpl",
”loadTimeout”:90
},
“com.akamai.YetAnotherLoadable”:{
"type":"com.akamai.LoadableConfigImpl",
”loadTimeout”:45
}
}
S
Examples: Expandable
Expandable component:
Specification
S An expandable component’s visibility can be toggled
S Components that can be dismissed cannot necessarily be
accessed. Example: Announcement dialogs
S Components that can be accessed or dismissed need some
mechanism for polling their visibility
S An expandable component has a content pane that is visible
when it is expanded and not visible when it is collapsed
Expandable component:
Design
S A polleable component needs to be separated out as a
component type which can be extended by more specialized
component types
S A content pane needs to be separated out as a component type
which can be extended by more specialized components
S Accessible and dismissable components need to be modeled
separately
Expandable component:
Design – page 2
S It is possible for controls to access and dismiss components to
be hoverable
S Selenium native hover and click actions are prone to silent
failure in some environments, so the use of workarounds needs
to be dynamically configurable
S Use marker interfaces that extend interfaces for accessible and
dismissable components to specify components that are both
//A component that is polled for a state change needs a
//polling timeout and a polling interval
public interface Polleable extends Loadable {
int DEFAULT_POLLING_TIMEOUT = 30;
int DEFAULT_POLLING_INTERVAL = 1;
int getPollingTimeout();
int getPollingInterval();
}
public interface PolleableBean extends LoadableBean {
void setPollingTimeout(int timeout);
int getPollingTimeout();
void setPollingInterval(int timeout);
int getPollingInterval();
}
//The time for a component to reach an expected state is
//environment sensitive
public interface PolleableConfig extends LoadableConfig {
Optional<Integer> getPollingTimeout();
void setPollingTimeout(Optional<Integer> timeout);
Optional<Integer> getPollingInterval();
void setPollingInterval(Optional<Integer> interval);
}
public interface PolleableBuilder<
PolleableT extends Polleable,
BeanT extends PolleableBean,
BuilderT extends PolleableBuilder<PolleableT,
BeanT, BuilderT>
> extends LoadableBuilder<PolleableT, BeanT,
BuilderT> {
BuilderT setPollingTimeout(int timeout);
BuilderT setPollingInterval(int timeout);
}
public interface ElementWrapperBean extends
LoadableBean {
void setWrappedElement(WebElement e);
WebElement getWrappedElement();
}
public interface ElementWrapperBuilder<
LoadableT extends Loadable,
BeanT extends ElementWrapperBean,
BuilderT extends ElementWrapperBuilder<
LoadableT, BeanT, BuilderT>
> extends LoadableBuilder<LoadableT, BeanT, BuilderT> {
BuilderT setWrappedElement(WebElement wrappedElement);
}
public interface AccessibleBean extends
ElementWrapperBean, PolleableBean {
void setAccessor(WebElement accessor);
WebElement getAccessor();
void setAccessorHoverable(boolean hoverable);
boolean isAccessorHoverable();
void setHoverAccessorWithJavascript(boolean
hoverWithJavascript);
boolean getHoverAccessorWithJavascript();
void setClickAccessorWithJavascript(boolean
clickWithJavascript);
boolean getClickAccessorWithJavascript();
}
//Selenium’s native click and hover actions are
//environment sensitive, so the the use of
//workarounds should be dynamically configurable
public interface AccessibleConfig extends PolleableConfig {
Optional<Boolean> getHoverAccessorWithJavascript();
void setHoverAccessorWithJavascript(Optional<Boolean>
hoverWithJavascript);
Optional<Boolean> getClickAccessorWithJavascript();
void setClickAccessorWithJavascript(Optional<Boolean>
clickWithJavascript);
}
public interface AccessibleBuilder<
PolleableT extends Polleable,
BeanT extends AccessibleBean,
BuilderT extends AccessibleBuilder<
PolleableT,BeanT,BuilderT>>
extends PolleableBuilder<PolleableT,BeanT,BuilderT>,
ElementWrapperBuilder<PolleableT,BeanT,BuilderT> {
BuilderT setAccessor(WebElement accessor);
BuilderT setAccessorHoverable(boolean hoverable);
BuilderT setHoverAccessorWithJavascript(boolean
hoverWithJavascript);
BuilderT setClickAccessorWithJavascript(boolean
clickWithJavascript);
}
//The state bean for a component that can be dismissed
//from view is a mirror image to the state bean for a
//component that can be rendered visible
public interface DismissableBean extends PolleableBean,
ElementWrapperBean {
void setDismisser(WebElement dismisser);
WebElement getDismisser();
void setDismisserHoverable(boolean hoverable);
boolean isDismisserHoverable();
void setHoverDismisserWithJavascript(boolean
hoverWithJavascript);
boolean getHoverDismisserWithJavascript();
void setClickDismisserWithJavascript(boolean
clickWithJavascript);
boolean getClickDismisserWithJavascript();
}
//The configuration bean for a component that can be
//dismissed is a mirror to the one for a component that
//can be rendered visible
public interface DismissableConfig extends PolleableConfig {
Optional<Boolean> getHoverDismisserWithJavascript();
void setHoverDismisserWithJavascript(Optional<Boolean>
hoverWithJavascript);
Optional<Boolean> getClickDismisserWithJavascript();
void setClickDismisserWithJavascript(Optional<Boolean>
clickWithJavascript);
}
public interface DismissableBuilder<
PolleableT extends Polleable,
BeanT extends DismissableBean,
BuilderT extends DismissableBuilder<
PolleableT, BeanT, BuilderT>>
extends PolleableBuilder<PolleableT, BeanT, BuilderT>,
ElementWrapperBuilder<PolleableT,BeanT,BuilderT> {
BuilderT setDismisser(WebElement dismisser);
BuilderT setDismisserHoverable(boolean hoverable);
BuilderT setHoverDismisserWithJavascript(boolean
hoverWithJavascript);
BuilderT setClickDismisserWithJavascript(boolean
clickWithJavaScript);
}
public interface ToggleableVisibilityBean
extends DismissableBean, AccessibleBean { }
public interface ToggleableVisibilityConfig
extends AccessibleConfig, DismissableConfig { }
public interface ToggleableVisibilityBuilder<
PolleableT extends Polleable,
BeanT extends ToggleableVisibilityBean,
BuilderT extends ToggleableVisibilityBuilder
<PolleableT,BeanT,BuilderT>>
extends AccessibleBuilder<PolleableT,BeanT,BuilderT>,
DismissableBuilder<PolleableT,BeanT,BuilderT> { }
public interface Expandable extends Polleable {
void expand();
void collapse();
boolean isExpanded();
boolean isCollapsed();
}
Expandable component:
Implementation
S Depends on the abstract and concrete implementations for a
Polleable
S Depends on the abstract implementation for an
ElementWrapper
S Expandable should be modeled in both abstract and concrete
implementations because expandable behavior is usable by
even more specialized component types
public abstract class AbstractPolleable<PolleableT extends
AbstractPolleable<PolleableT>>
extends AbstractLoadable<PolleableT>
implements Polleable {
private @Getter int pollingTimeout;
private @Getter int pollingInterval;
public AbstractPolleable(PolleableBean bean) {
super(bean);
this.pollingTimeout = bean.getPollingTimeout();
this.pollingInterval = bean.getPollingInterval();
}
}
//The concrete implementation only requires a constructor
//because all the methods of the Polleable interface are
//implemented in the abstract parent class.
public class PolleableImpl extends
AbstractPolleable<PolleableImpl> {
public PolleableImpl(PolleableBean bean) {
super(bean);
}
protected void isLoaded() throws Error {
//Do nothing;
}
}
public abstract class AbstractContentPane<ContentPaneT
extends AbstractContentPane<ContentPaneT>>
extends AbstractLoadable<ContentPaneT> {
public AbstractContentPane(LoadableBean bean) {
super(bean);
}
protected abstract WebElement getContainer();
protected void isLoaded() throws Error {
try {
assertTrue(getContainer().isDisplayed());
} catch(NoSuchElementException e) {
throw new Error(e));
}
}
}
//An Expandable is both a content pane and a polleable
//component, but it can inherit from only one of
//AbstractContentPane and AbstractPolleable. Decorators
//allow for components like Expandable to implement
//behavior from multiple component types without a lot of
//extra code. More on this to come.
public abstract class AbstractExpandable<ExpandableT
extends AbstractExpandable<ExpandableT>>
extends AbstractContentPane<ExpandableT>
implements Expandable, PolleableDecorator {
private @Getter LoadableProvider<? Extends Polleable>
polleableProvider;
public AbstractExpandable(PolleableBean bean) {
super(bean);
this.polleableProvider =
new LoadableProvider<>(new PolleableImpl(bean));
}
…continued…
…AbstractExpandable continued…
public boolean isExpanded() {
try {
return getContainer().isDisplayed();
} catch(NoSuchElementException e) {
return false;
}
}
public boolean isCollapsed() {
return !isExpanded();
}
…continued…
…AbstractExpandable continued…
public void expand() {
if(!isExpanded() && isAccessorHoverable()) {
hoverControl(getAccessor(),
hoverAccessorWithJavascript());
clickControl(getAccessor(),
clickAccessorWithJavascript(), true);
ComponentLoader.waitForExpand(this);
}
if(!isExpanded()) {
clickControl(getAccessor(),
clickAccessorWithJavascript(), true);
ComponentLoader.waitForExpand(this);
}
}
continued…
…AbstractExpandable continued…
public void collapse() {
if(!isCollapsed() && isDismisserHoverable()) {
hoverControl(getDismisser(),
hoverDismisserWithJavascript());
clickControl(getDismisser(),
clickDismisserWithJavascript(), true);
ComponentLoader.waitForCollapse(this);
}
if(!isCollapsed()) {
clickControl(getDismisser(),
clickDismisserWithJavascript(), true);
ComponentLoader.waitForCollapse(this);
}
}
continued…
…AbstractExpandable continued…
private void hoverOverControl(WebElement control,
boolean useJavascript) {
if(useJavascript) {
//Lambda function for using JavascriptExecutor
//to hover over the control
new HoverWithJavascript().accept(getDriver(),
control);
} else {
Actions action = new Actions(getDriver());
action.moveToElement(control).perform();
}
}
…continued…
…AbstractExpandable continued…
private void clickControl(WebElement control, boolean
useJavascript, boolean isControlHoverable) {
if(useJavascript) {
//Lambda function to use JavascriptExecutor to
//click control
new ClickWithJavascript().accept(getDriver(),
control);
} else if(isControlHoverable) {
Actions action = new Actions(getDriver());
action.click(control).perform();
} else { control.click(); }
}
…continued…
…AbstractExpandable continued…
protected abstract WebElement getAccessor();
protected abstract WebElement getDismisser();
protected abstract boolean isAccessorHoverable();
protected abstract boolean isDismisserHoverable();
protected abstract boolean
hoverAccessorWithJavascript();
protected abstract boolean
hoverDismisserWithJavascript();
protected abstract boolean
clickAccessorWithJavascript();
protected abstract boolean
clickDismisserWithJavascript();
…continued…
…AbstractExpandable continued…
protected void isLoaded() throws Error {
try {
if(!isAccessorHoverable()) {
assertTrue(getAccessor().isDisplayed());
} else {
//If this throws a NoSuchElementException,
//the Expandable is not loaded
getAccessor().isDisplayed();
}
} catch(NoSuchElementException e) {
throw new Error(e));
}
}
}
public class ExpandableImpl extends
AbstractExpandable<ExpandableImpl> {
private @Getter(value = AccessLevel.PROTECTED)
WebElement accessor;
private @Getter(value = AccessLevel.PROTECTED)
WebElement dismisser;
private @Getter(value = AccessLevel.PROTECTED)
WebElement container;
private @Getter(value = AccessLevel.PROTECTED)
boolean isAccessorHoverable;
private @Getter(value = AccessLevel.PROTECTED)
boolean isDismisserHoverable;
//Cannot use Lombok because getters for these booleans
//do not have ‘is’ as prefix, but ‘get’
private boolean hoverAccessorWithJavascript;
private boolean hoverDismisserWithJavascript;
private boolean clickAccessorWithJavascript;
private boolean clickDismisserWithJavascript;
…continued…
…ExpandableImpl continued…
public ExpandableImpl(ToggleableVisibilityBean bean) {
super(bean);
this.accessor = bean.getAccessor();
this.dismisser = bean.getDismisser ();
this.containerElement = bean.getWrappedElement();
this.isAccessorHoverable =
bean.isAccessorHoverable();
this.isDismisserHoverable =
bean.isDismisserHoverable();
this.hoverAccessorWithJavascript =
bean.getHoverAccessorWithJavascript();
this.hoverDismisserWithJavascript =
bean.getHoverDismisserWithJavascript();
this.clickAccessorWithJavascript =
bean.getClickAccessorWithJavascript();
this.clickDismisserWithJavascript =
bean.getClickDismisserWithJavascript();
} //Omitting the getters for the booleans
}
S
Examples: Decorator
Decorator:
New features without boilerplate
S A component implementation can inherit from only one class,
but may need to combine behavior from multiple component
types
S Pre-Java 8 interfaces allow this type of combination, but have
the drawback of requiring lots of duplicated code
S Java 8 allows interfaces with default method implementations,
so you can define the behavior in a Decorator interface
S A class can implement multiple Decorator interfaces with
default method implementations, thereby adding functionality
without boilerplate code
public interface PolleableDecorator extends
Polleable {
LoadableProvider<? extends Polleable>
getPolleableProvider();
default int getPollingTimeout() {
return getPolleableProvider()
.getLoadable()
.getPollingTimeout();
}
default int getPollingInterval() {
return getPolleableProvider()
.getLoadable()
.getPollingInterval();
}
}
public class LoadableProvider<LoadableT extends
Loadable> {
private @Getter(value = AccessLevel.PROTECTED) LoadableT
loadable;
public LoadableProvider(LoadableT loadable) {
this.loadable = loadable;
}
}
public interface ExpandableDecorator extends
Expandable {
LoadableProvider<? extends Expandable>
getExpandableProvider();
default void expand() {
getExpandableProvider()
.getLoadable()
.expand();
}
default void collapse() {
getExpandableProvider()
.getLoadable()
.collapse();
}
…continued…
…ExpandableDecorator continued…
default boolean isExpanded() {
return getExpandableProvider()
.getLoadable()
.isExpanded();
}
default boolean isCollapsed() {
return getExpandableProvider()
.getLoadable()
.isCollapsed();
}
default int getPollingTimeout() {
return getExpandableProvider()
.getLoadable()
.getPollingTimeout();
}
…continued…
…ExpandableDecorator continued…
default int getPollingInterval() {
return getExpandableProvider()
.getLoadable()
.getPollingInterval();
}
}
S
Examples: Menu
Menu component:
Specification
S Menus have a content container that contains their options
S Menus can be flat or expandable
S A menu component needs to provide a getter for the available
options
S A menu component needs to provide the ability to click an
option
Menu component:
Design
S The basic menu interface should be agnostic to whether it is
implemented by a flat or dropdown menu component
S Decorators and marker interfaces should be used to define a
dropdown menu
S The click action for a menu option can fail silently in some
environments, so the use of a JavaScript workaround to click
an option should be dynamically configurable
public interface Menu<OptionT extends Clickable>
extends Loadable {
List<OptionT> getOptions();
void clickOption(final OptionT option);
}
public interface MenuBean extends ElementWrapperBean {
void setOptionElements(List<WebElement> options);
List<WebElement> getOptionElements();
void setClickOptionWithJavascript(boolean
clickWithJavascript);
boolean getClickOptionWithJavascript();
}
public interface MenuConfig extends LoadableConfig {
Optional<Boolean> getClickOptionWithJavascript();
void setClickOptionWithJavascript(Optional<Boolean>
clickWithJavascript);
}
public interface MenuBuilder<
MenuT extends Menu<? extends Clickable>,
BeanT extends MenuBean,
BuilderT extends MenuBuilder<MenuT,BeanT,BuilderT>
> extends ElementWrapperBuilder<MenuT,BeanT,BuilderT> {
BuilderT setOptionElements(List<WebElement> options);
BuilderT setClickOptionWithJavascript(boolean
clickWithJavascript);
}
public interface DropDownMenuBean extends MenuBean,
ToggleableVisibilityBean { }
public interface DropDownMenuConfig extends MenuConfig,
ToggleableVisibilityConfig { }
public interface DropDownMenuBuilder<
MenuT extends Menu<? extends Clickable> &
Expandable,
BeanT extends DropDownMenuBean,
BuilderT extends
DropDownMenuBuilder< MenuT,BeanT,BuilderT>
> extends MenuBuilder<MenuT,BeanT,BuilderT>,
ToggleableVisibilityBuilder<MenuT,
BeanT,BuilderT> { }
public abstract class AbstractMenu<
OptionT extends Clickable,
MenuT extends AbstractMenu<OptionT,MenuT>>
extends AbstractContentPane<MenuT>
implements Menu<OptionT> {
public AbstractMenu(LoadableBean bean) { super(bean); }
public List<OptionT> getOptions() {
if (isDropDownMenu()) {
((Expandable) this).expand();
}
List<OptionT> options = buildOptions();
if (isDropDownMenu()) {
((Expandable) this).collapse();
}
return options;
}
continued…
…AbstractMenu continued…
public void clickOption(OptionT option) {
if(getOptions().contains(option)) {
if(isDropDownMenu()) {
((Expandable)this).expand();
}
option.click();
if(isDropDownMenu()) {
((Expandable) this).collapse();
}
} else {
throw new IllegalArgumentException();
}
}
…continued…
…AbstractMenu continued…
protected abstract List<WebElement>
getOptionElements();
protected abstract boolean
clickOptionWithJavascript();
protected List<OptionT> buildOptions() {
final List<OptionT> options = new ArrayList<>();
for(WebElement element : getOptionElements()) {
options.add(buildOption(element));
}
return options;
}
protected boolean isDropDownMenu() {
return Expandable.class
.isAssignableFrom(this.getClass());
}
continued…
…AbstractMenu continued…
protected ClickableBuilder<OptionT,? extends
ClickableBean,?> getOptionBuilder() {
return new ClickableBuilderImpl<>();
}
private OptionT buildOption(WebElement element) {
return getOptionBuilder()
.setComponentClass(getOptionClass())
.setDriver(getDriver())
.setWrappedElement(optionElement)
.setUseJavascriptClick(
clickOptionWithJavascript())
.build());
}
private Class<OptionT> getOptionClass() {
//Omitted for brevity. Fancy Guava
//reflection API is used here.
}
}
public class DropDownBasicMenu extends
AbstractBasicMenu<DropDownBasicMenu>
implements ExpandableDecorator {
private @Getter LoadableProvider<? Extends Expandable>
expandableProvider;
private @Getter(value = AccessLevel.PROTECTED)
List<WebElement> optionElements;
private @Getter(value = AccessLevel.PROTECTED)
WebElement container;
private @Getter(value = AccessLevel.PRIVATE)
WebElement accessor;
private boolean clickOptionWithJavascript;
…continued…
…DropDownBasicMenu continued…
public DropDownBasicMenu(DropDownMenuBean bean) {
super(bean);
this.expandableProvider = new
LoadableProvider<>(new ExpandableImpl(bean));
this.optionElements = bean.getOptionElements();
this.container = bean.getWrappedElement();
this.accessor = bean.getAccessor();
this.clickOptionWithJavascript =
bean.getClickOptionWithJavascript();
}
protected boolean clickOptionWithJavascript() {
return clickOptionWithJavascript;
}
…continued…
…DropDownBasicMenu continued…
protected void isLoaded() throws Error {
try {
expandableProvider.getLoadable().isLoaded();
} catch(NoSuchElementException e) {
throw new Error(e));
}
}
}

More Related Content

What's hot

Overview of MVC Framework - by software outsourcing company india
Overview of MVC Framework - by software outsourcing company indiaOverview of MVC Framework - by software outsourcing company india
Overview of MVC Framework - by software outsourcing company indiaJignesh Aakoliya
 
0105 abap programming_overview
0105 abap programming_overview0105 abap programming_overview
0105 abap programming_overviewvkyecc1
 
How to upgrade your application with no downtime (using edition-based redefin...
How to upgrade your application with no downtime (using edition-based redefin...How to upgrade your application with no downtime (using edition-based redefin...
How to upgrade your application with no downtime (using edition-based redefin...Oren Nakdimon
 
Introduction to java netbeans
Introduction to java netbeansIntroduction to java netbeans
Introduction to java netbeansShrey Goswami
 
VS Saturday 2019 - Xamarin.Forms 4.x
VS Saturday 2019 - Xamarin.Forms 4.xVS Saturday 2019 - Xamarin.Forms 4.x
VS Saturday 2019 - Xamarin.Forms 4.xMarco Bortolin
 
Component interface
Component interfaceComponent interface
Component interfaceJAYAARC
 
RESTful API Design & Implementation with CodeIgniter PHP Framework
RESTful API Design & Implementation with CodeIgniter PHP FrameworkRESTful API Design & Implementation with CodeIgniter PHP Framework
RESTful API Design & Implementation with CodeIgniter PHP FrameworkBo-Yi Wu
 
Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Hitesh-Java
 
Knockout.js components&routing
Knockout.js components&routingKnockout.js components&routing
Knockout.js components&routingCarlo Bernaschina
 
9780538745840 ppt ch01 PHP
9780538745840 ppt ch01 PHP9780538745840 ppt ch01 PHP
9780538745840 ppt ch01 PHPTerry Yoast
 

What's hot (17)

Overview of MVC Framework - by software outsourcing company india
Overview of MVC Framework - by software outsourcing company indiaOverview of MVC Framework - by software outsourcing company india
Overview of MVC Framework - by software outsourcing company india
 
SQL Server Data Tools
SQL Server Data ToolsSQL Server Data Tools
SQL Server Data Tools
 
Gokul bok
Gokul bokGokul bok
Gokul bok
 
Core java
Core javaCore java
Core java
 
0105 abap programming_overview
0105 abap programming_overview0105 abap programming_overview
0105 abap programming_overview
 
Colloquium Report
Colloquium ReportColloquium Report
Colloquium Report
 
How to upgrade your application with no downtime (using edition-based redefin...
How to upgrade your application with no downtime (using edition-based redefin...How to upgrade your application with no downtime (using edition-based redefin...
How to upgrade your application with no downtime (using edition-based redefin...
 
Interoperation
InteroperationInteroperation
Interoperation
 
topic_perlcgi
topic_perlcgitopic_perlcgi
topic_perlcgi
 
Introduction to java netbeans
Introduction to java netbeansIntroduction to java netbeans
Introduction to java netbeans
 
Badis
Badis Badis
Badis
 
VS Saturday 2019 - Xamarin.Forms 4.x
VS Saturday 2019 - Xamarin.Forms 4.xVS Saturday 2019 - Xamarin.Forms 4.x
VS Saturday 2019 - Xamarin.Forms 4.x
 
Component interface
Component interfaceComponent interface
Component interface
 
RESTful API Design & Implementation with CodeIgniter PHP Framework
RESTful API Design & Implementation with CodeIgniter PHP FrameworkRESTful API Design & Implementation with CodeIgniter PHP Framework
RESTful API Design & Implementation with CodeIgniter PHP Framework
 
Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans
 
Knockout.js components&routing
Knockout.js components&routingKnockout.js components&routing
Knockout.js components&routing
 
9780538745840 ppt ch01 PHP
9780538745840 ppt ch01 PHP9780538745840 ppt ch01 PHP
9780538745840 ppt ch01 PHP
 

Viewers also liked

сироткин+передач энергий без проводов+клиенты
сироткин+передач энергий без проводов+клиентысироткин+передач энергий без проводов+клиенты
сироткин+передач энергий без проводов+клиентыВлад Сироткин
 
Clases de hiervas
Clases de hiervasClases de hiervas
Clases de hiervasmovienet
 
02 guia enfermeriamedicoquirur 03
02 guia enfermeriamedicoquirur 0302 guia enfermeriamedicoquirur 03
02 guia enfermeriamedicoquirur 03Adriana Correa
 
Норакия тимур+идея
Норакия тимур+идеяНоракия тимур+идея
Норакия тимур+идеяTimur Norakiya
 
LinkedIN -Jerome Vittoz - CRM 12 - Thesis MA Final
LinkedIN -Jerome Vittoz - CRM 12 - Thesis MA FinalLinkedIN -Jerome Vittoz - CRM 12 - Thesis MA Final
LinkedIN -Jerome Vittoz - CRM 12 - Thesis MA FinalJerome Vittoz
 
Juegos ecológicos con... botellas de plástico de manuel gutiérrez toca
Juegos ecológicos con... botellas de plástico de manuel gutiérrez tocaJuegos ecológicos con... botellas de plástico de manuel gutiérrez toca
Juegos ecológicos con... botellas de plástico de manuel gutiérrez tocaJorge Nicanor Castro Martínez
 

Viewers also liked (10)

O teri Report
O teri ReportO teri Report
O teri Report
 
Corporate Analysis 5
Corporate Analysis 5Corporate Analysis 5
Corporate Analysis 5
 
сироткин+передач энергий без проводов+клиенты
сироткин+передач энергий без проводов+клиентысироткин+передач энергий без проводов+клиенты
сироткин+передач энергий без проводов+клиенты
 
Clases de hiervas
Clases de hiervasClases de hiervas
Clases de hiervas
 
Describing others
Describing othersDescribing others
Describing others
 
02 guia enfermeriamedicoquirur 03
02 guia enfermeriamedicoquirur 0302 guia enfermeriamedicoquirur 03
02 guia enfermeriamedicoquirur 03
 
Норакия тимур+идея
Норакия тимур+идеяНоракия тимур+идея
Норакия тимур+идея
 
LinkedIN -Jerome Vittoz - CRM 12 - Thesis MA Final
LinkedIN -Jerome Vittoz - CRM 12 - Thesis MA FinalLinkedIN -Jerome Vittoz - CRM 12 - Thesis MA Final
LinkedIN -Jerome Vittoz - CRM 12 - Thesis MA Final
 
Juegos ecológicos con... botellas de plástico de manuel gutiérrez toca
Juegos ecológicos con... botellas de plástico de manuel gutiérrez tocaJuegos ecológicos con... botellas de plástico de manuel gutiérrez toca
Juegos ecológicos con... botellas de plástico de manuel gutiérrez toca
 
Asepsia en los quirófanos
Asepsia en los quirófanosAsepsia en los quirófanos
Asepsia en los quirófanos
 

Similar to Generic UXD Legos - Selenium Conference 2015

Dependency injection in scala
Dependency injection in scalaDependency injection in scala
Dependency injection in scalaMichal Bigos
 
Spring IOC and DAO
Spring IOC and DAOSpring IOC and DAO
Spring IOC and DAOAnushaNaidu
 
2-0. Spring ecosytem.pdf
2-0. Spring ecosytem.pdf2-0. Spring ecosytem.pdf
2-0. Spring ecosytem.pdfDeoDuaNaoHet
 
Building a p2 update site using Buckminster
Building a p2 update site using BuckminsterBuilding a p2 update site using Buckminster
Building a p2 update site using Buckminsterguest5e2b6b
 
How to code to code less
How to code to code lessHow to code to code less
How to code to code lessAnton Novikau
 
Spring from a to Z
Spring from  a to ZSpring from  a to Z
Spring from a to Zsang nguyen
 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsJeff Durta
 
Introduction to Spring Framework
Introduction to Spring FrameworkIntroduction to Spring Framework
Introduction to Spring FrameworkRajind Ruparathna
 
dokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdf
dokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdfdokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdf
dokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdfAppster1
 
dokumen.tips_rediscovering-spring-with-spring-boot1.pdf
dokumen.tips_rediscovering-spring-with-spring-boot1.pdfdokumen.tips_rediscovering-spring-with-spring-boot1.pdf
dokumen.tips_rediscovering-spring-with-spring-boot1.pdfAppster1
 
Dependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony ContainerDependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony ContainerDiego Lewin
 
Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1Michał Orman
 
Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)Gunith Devasurendra
 
Java EE web project introduction
Java EE web project introductionJava EE web project introduction
Java EE web project introductionOndrej Mihályi
 
React & Redux for noobs
React & Redux for noobsReact & Redux for noobs
React & Redux for noobs[T]echdencias
 
Declarative Services Dependency Injection OSGi style
Declarative Services Dependency Injection OSGi styleDeclarative Services Dependency Injection OSGi style
Declarative Services Dependency Injection OSGi styleFelix Meschberger
 

Similar to Generic UXD Legos - Selenium Conference 2015 (20)

Os Johnson
Os JohnsonOs Johnson
Os Johnson
 
Dependency injection in scala
Dependency injection in scalaDependency injection in scala
Dependency injection in scala
 
Spring IOC and DAO
Spring IOC and DAOSpring IOC and DAO
Spring IOC and DAO
 
2-0. Spring ecosytem.pdf
2-0. Spring ecosytem.pdf2-0. Spring ecosytem.pdf
2-0. Spring ecosytem.pdf
 
Building a p2 update site using Buckminster
Building a p2 update site using BuckminsterBuilding a p2 update site using Buckminster
Building a p2 update site using Buckminster
 
How to code to code less
How to code to code lessHow to code to code less
How to code to code less
 
Spring boot jpa
Spring boot jpaSpring boot jpa
Spring boot jpa
 
Spring from a to Z
Spring from  a to ZSpring from  a to Z
Spring from a to Z
 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applications
 
Introduction to Spring Framework
Introduction to Spring FrameworkIntroduction to Spring Framework
Introduction to Spring Framework
 
Spring Basics
Spring BasicsSpring Basics
Spring Basics
 
Spring core
Spring coreSpring core
Spring core
 
dokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdf
dokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdfdokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdf
dokumen.tips_rediscovering-spring-with-spring-boot1 (1).pdf
 
dokumen.tips_rediscovering-spring-with-spring-boot1.pdf
dokumen.tips_rediscovering-spring-with-spring-boot1.pdfdokumen.tips_rediscovering-spring-with-spring-boot1.pdf
dokumen.tips_rediscovering-spring-with-spring-boot1.pdf
 
Dependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony ContainerDependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony Container
 
Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1Integration of Backbone.js with Spring 3.1
Integration of Backbone.js with Spring 3.1
 
Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)Rediscovering Spring with Spring Boot(1)
Rediscovering Spring with Spring Boot(1)
 
Java EE web project introduction
Java EE web project introductionJava EE web project introduction
Java EE web project introduction
 
React & Redux for noobs
React & Redux for noobsReact & Redux for noobs
React & Redux for noobs
 
Declarative Services Dependency Injection OSGi style
Declarative Services Dependency Injection OSGi styleDeclarative Services Dependency Injection OSGi style
Declarative Services Dependency Injection OSGi style
 

Recently uploaded

Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfkalichargn70th171
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastPapp Krisztián
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park masabamasaba
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesVictorSzoltysek
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnAmarnathKambale
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024VictoriaMetrics
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Bert Jan Schrijver
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdfPearlKirahMaeRagusta1
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrainmasabamasaba
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsJhone kinadey
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in sowetomasabamasaba
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplatePresentation.STUDIO
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfonteinmasabamasaba
 
tonesoftg
tonesoftgtonesoftg
tonesoftglanshi9
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...masabamasaba
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...masabamasaba
 

Recently uploaded (20)

Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 

Generic UXD Legos - Selenium Conference 2015

  • 1. S Generic UXD Legos How to Build the Page Object API of Your Dreams By Selena Phillips | Akamai Technologies
  • 2. Introduction The Page Object design pattern has always had some weaknesses. Refinements on basic design pattern have emerged to address some of these shortcomings – the SlowLoadableComponent pattern and the practice of modeling the business layer separately from the UI layer, for example. These refinements don’t make developing page objects any easier or faster, nor do they provide a solution to the frustrating inevitability that some page object interactions work perfectly in some environments while failing silently in others.
  • 3. Table of Contents S Page Object API Requirements S Recipe for a New Component Model S Specification S Design S Implementation S Examples S Loadable S Expandable S Decorator S Menu
  • 5. API Requirements S Generic UXD Legos for composing page objects A library of ready-to-use models of common UI components would significantly reduce the amount of boilerplate code. S Dynamically configurable page objects It should be possible to specify that your page object should click that pesky button using a JavaScript workaround for the environments where it fails silently without boilerplate if-then- else clauses.
  • 6. API Requirements – page 2 S Standardized approach for expanding the component library There should be a virtually cookie-cutter approach for adding new components to the library. S Easy substitution of custom component implementations Because there is no such thing as one-size-fits-all, it should be trivial to substitute a custom implementation of a component type that utilizes the same basic interface as the default.
  • 7. S Recipe for a New Component Model Specification Design Implementation
  • 8. Recipe for a New Component Model: Specification S Identify the basic interface and interaction model for the component S Identify the minimal state that must be specified to construct the component S Identify the component's place in the inheritance hierarchy of other components S Identify the dynamically configurable behaviors to make the component robust across different environments
  • 9. Recipe for a New Component Model: Design S Write a Java interface for the component S Write a Java interface for a state bean to specify the state necessary to instantiate the component S Write a Java interface for a fluent builder for the component S Write a Java interface for a configuration bean for the component
  • 10. Recipe for a New Component Model: Implementation S Write a default implementation of the state bean S Write abstract and default implementations of the builder S Write an abstract and a default implementation of the component, if it makes sense S Write a default implementation of the configuration bean
  • 13. Loadable: The most basic component At the top of the inheritance hierarchy is the Loadable. This minimal component requires: S A WebDriver reference S A load timeout
  • 14. LoadableBean: Loadable state bean LoadableBean is a simple POJO for specifying the state necessary to construct a Loadable: S A WebDriver reference S A load timeout
  • 15. public interface Loadable { int DEFAULT_LOAD_TIMEOUT = 30; WebDriver getDriver(); int getLoadTimeout(); } public interface LoadableBean { void setDriver(WebDriver driver); WebDriver getDriver(); void setLoadTimeout(int timeout); int getLoadTimeout(); }
  • 16. LoadableConfig: Loadable configuration bean LoadableConfig is a POJO for specifying environment sensitive state information for a Loadable. S Needs to configure the load timeout value which can be affected by the test environment S Needs to be deserializable with Jackson, so it can be instantiated with configuration data retrieved from an external datastore
  • 17. LoadableConfig: Loadable configuration bean – page 2 S Needs Optional fields so that a distinction can be made between a configuration value that is absent and one that is explicitly null S Needs to specify type information because configuration beans for more specialized components extend beans for less specialized components. Jackson needs this type information to properly handle the polymorphism.
  • 18. public interface LoadableConfig { Class<? extends LoadableConfig> getType(); void setType(Class<? Extends LoadableConfig> type); Optional<Integer> getLoadTimeout(); void setLoadTimeout(Optional<Integer> timeout); }
  • 19. LoadableBuilder: Fluent Loadable builder S Needs to be extensible because builders for more specialized components will extend it S Needs to have setters that return the runtime type of the builder, so that they can be called in any order because a complex component requires specification of numerous state fields before it can be instantiated
  • 20. public interface LoadableBuilder< LoadableT extends Loadable, BeanT extends LoadableBean, //The reflexive parameter makes it possible to //return the runtime type of the builder BuilderT extends LoadableBuilder<LoadableT,BeanT, BuilderT>> { BeanT getState(); BuilderT setComponentClass(Class<LoadableT> clazz); Class<LoadableT> getComponentClass(); BuilderT setDriver(WebDriver driver); BuilderT setLoadTimeout(int timeout); LoadableT build(); }
  • 21. LoadableBeanImpl: Default Loadable state bean S Uses Lombok to streamline the source code and reduce boilerplate code S Intializes fields with interface defaults where possible
  • 22. public class LoadableBeanImpl implements LoadableBean { private @Getter @Setter WebDriver driver; private @Getter @Setter int loadTimeout = DEFAULT_LOAD_TIMEOUT; }
  • 23. LoadableConfigImpl: Default Loadable configuration bean S Uses Lombok to streamline the source code and reduce boilerplate code S Uses JSON as the external datastore
  • 24. @JsonTypeInfo( //Determines how type information is specified in the //source JSON use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "type”, visible = true) @JsonSubTypes({ @Type(value = PolleableConfigImpl.class), //Omitted for brevity. All sub-types of //LoadableConfigImpl must be listed }) public class LoadableConfigImpl implements LoadableConfig { //The type field must be present in the source JSON. @JsonProperty(required = true) private @Getter @Setter Class<? extends LoadableConfig> type; @JsonProperty private @Getter @Setter Optional<Integer> loadTimeout; }
  • 25. AbstractLoadableBuilder: Parent of all API builders S Uses Lombok to streamline the source code and reduce boilerplate code S Needs to be extensible because builder implementations for more specialized components will extend it S Needs to implement all methods in the LoadableBuilder interface because its main purpose is to be extended by builder implementations for more specialized components
  • 26. public abstract class AbstractLoadableBuilder< LoadableT extends Loadable, BeanT extends LoadableBean, //Use the same reflexive parameter that is used in //the LoadableBuilder interface because this class //is intended to be extended by more specialized //builder implementations, just as the interface //is intended to be extended by interfaces for //more specialized builders BuilderT extends LoadableBuilder<LoadableT,BeanT,BuilderT>> implements LoadableBuilder<LoadableT,BeanT,BuilderT> { private @Getter BeanT state; private @Getter Class<LoadableT> componentClass; public AbstractLoadableBuilder(BeanT bean) { this.state = bean; } …continued…
  • 27. …AbstractLoadableBuilder continued… public BuilderT setComponentClass(Class<LoadableT> clazz) { this.componentClass = clazz; return (BuilderT)this; } public BuilderT setDriver(WebDriver driver) { getState().setDriver(driver); return (BuilderT)this; } public BuilderT setLoadTimeout(int timeout) { getState().setLoadTimeout(timeout); return (BuilderT)this; } …continued…
  • 28. …AbstractLoadableBuilder continued… //The factory and its interface are not shown in this //presentation. The default implementation is a //singleton class with a static accessor method for //the singleton instance. To substitute a non-default //implementation of the interface, a sub-class can //override this method. protected LoadableFactory getFactory() { return LoadableFactoryImpl.getInstance(); } //The default factory implementation uses reflection //to get a constructor for the component. Therefore, //the component must define a single-arg constructor //that accepts the state bean as a parameter. public LoadableT build() { return getFactory().create(getState(), componentClass); } }
  • 29. LoadableBuilderImpl: Default Loadable builder S Uses the default implementation of LoadableBean S Extends AbstractLoadableBuilder S Doesn’t need a reflexive generic type parameter in the class declaration because it is not intended to be extended S Needs to define only a constructor because AbstractLoadableBuilder implements all the methods in LoadableBuilder
  • 30. //The only generic type parameter that is necessary for //this default builder is for the component it builds public class LoadableBuilderImpl<LoadableT extends Loadable> extends AbstractLoadableBuilder< LoadableT, LoadableBean, LoadableBuilderImpl<LoadableT> > { //Use the default LoadableBean implementation public LoadableBuilderImpl() { super(new LoadableBeanImpl()); } }
  • 31. AbstractLoadable: Parent of all components S Uses Lombok to reduce boilerplate S Extends SlowLoadableComponent S Assumes child classes annotate their WebElements with locators, so uses PageFactory in load() S Provides the means to query a configuration service for a configuration by ID
  • 32. public abstract class AbstractLoadable<LoadableT extends AbstractLoadable<LoadableT>> extends SlowLoadableComponent<LoadableT> implements Loadable { private @Getter WebDriver driver; private @Getter int loadTimeout; //Use a state bean to provide all the state //data necessary to instantiate a component public AbstractLoadable(LoadableBean bean) { super(new SystemClock(), bean.getLoadTimeout()); this.driver = bean.getDriver(); this.loadTimeout =bean.getLoadTimeout(); } protected void load() { PageFactory.initElements(getDriver(), this); } …continued…
  • 33. …AbstractLoadableComponent continued… //A non-default implementation can be substituted by //overriding this method. protected ConfigService getConfigService() { return ConfigServiceImpl.getInstance(); } protected <ConfigT extends LoadableConfig> ConfigT getConfig(String id) { //A profile is a collection of configurations //based on environment. if(getConfigService().getProfile() != null) { return service.getConfig(id); } } }
  • 34. ConfigService: Dynamic configuration service S Needs to provide a means to set the active configuration profile S Needs to provide a means to query the active configuration profile for a component configuration by ID
  • 35. public interface ConfigService { void setProfile(String profile); String getProfile(); <ConfigT extends LoadableConfig> ConfigT getConfig(String id); }
  • 36. ConfigServiceImpl: Default configuration service S Is a singleton class with a static accessor method for the singleton instance S Expects configuration profiles to be in the resources/pageobject_config folder S Expects a filename to be specifed for locating a configuration profile in the above folder S Expects a map data structure in the JSON source
  • 37. public class ConfigServiceImpl implements ConfigService { //Filename of the current configuration profile, based //on the OS, browser and browser version. The file is //a JSON file with a map structure, with a String id //for each component configuration in the file private @Getter String profile; private Map<String,LoadableConfig> configs = new HashMap<>(); private static final class Loader { private static final ConfigServiceImpl INSTANCE = new ConfigServiceImpl(); } private ConfigServiceImpl() { } public static ConfigServiceImpl getInstance() { return Loader.INSTANCE; } …continued…
  • 38. …ConfigServiceImpl continued… public void setProfile(String profile) { this.profile = profile; URL configUrl = ConfigServiceImpl.class .getClassLoader() .getResource("./pageobject_config/” + profile); ObjectMapper mapper = new ObjectMapper() .registerModule(new Jdk8Module()); //Give type info to Jackson for the Map field MapType mapType = mapper.getTypeFactory() .constructMapType(HashMap.class, String.class, LoadableConfigImpl.class); //try-catch blocks omitted for brevity File configFile = new File(configUrl.toURI()); configs = mapper.readValue(configFile, mapType); } …continued…
  • 39. …ConfigServiceImpl continued… public <ConfigT extends LoadableConfig> ConfigT getConfig(String id) { return (ConfigT)configs.get(id); } }
  • 40. //Example configuration JSON source. Three //components have configurations for their load //timeout values { “com.akamai.SomeLoadable":{ "type":"com.akamai.LoadableConfigImpl", ”loadTimeout”:120 }, “com.akamai.SomeOtherLoadable”:{ "type":"com.akamai.LoadableConfigImpl", ”loadTimeout”:90 }, “com.akamai.YetAnotherLoadable”:{ "type":"com.akamai.LoadableConfigImpl", ”loadTimeout”:45 } }
  • 42. Expandable component: Specification S An expandable component’s visibility can be toggled S Components that can be dismissed cannot necessarily be accessed. Example: Announcement dialogs S Components that can be accessed or dismissed need some mechanism for polling their visibility S An expandable component has a content pane that is visible when it is expanded and not visible when it is collapsed
  • 43. Expandable component: Design S A polleable component needs to be separated out as a component type which can be extended by more specialized component types S A content pane needs to be separated out as a component type which can be extended by more specialized components S Accessible and dismissable components need to be modeled separately
  • 44. Expandable component: Design – page 2 S It is possible for controls to access and dismiss components to be hoverable S Selenium native hover and click actions are prone to silent failure in some environments, so the use of workarounds needs to be dynamically configurable S Use marker interfaces that extend interfaces for accessible and dismissable components to specify components that are both
  • 45. //A component that is polled for a state change needs a //polling timeout and a polling interval public interface Polleable extends Loadable { int DEFAULT_POLLING_TIMEOUT = 30; int DEFAULT_POLLING_INTERVAL = 1; int getPollingTimeout(); int getPollingInterval(); } public interface PolleableBean extends LoadableBean { void setPollingTimeout(int timeout); int getPollingTimeout(); void setPollingInterval(int timeout); int getPollingInterval(); }
  • 46. //The time for a component to reach an expected state is //environment sensitive public interface PolleableConfig extends LoadableConfig { Optional<Integer> getPollingTimeout(); void setPollingTimeout(Optional<Integer> timeout); Optional<Integer> getPollingInterval(); void setPollingInterval(Optional<Integer> interval); } public interface PolleableBuilder< PolleableT extends Polleable, BeanT extends PolleableBean, BuilderT extends PolleableBuilder<PolleableT, BeanT, BuilderT> > extends LoadableBuilder<PolleableT, BeanT, BuilderT> { BuilderT setPollingTimeout(int timeout); BuilderT setPollingInterval(int timeout); }
  • 47. public interface ElementWrapperBean extends LoadableBean { void setWrappedElement(WebElement e); WebElement getWrappedElement(); } public interface ElementWrapperBuilder< LoadableT extends Loadable, BeanT extends ElementWrapperBean, BuilderT extends ElementWrapperBuilder< LoadableT, BeanT, BuilderT> > extends LoadableBuilder<LoadableT, BeanT, BuilderT> { BuilderT setWrappedElement(WebElement wrappedElement); }
  • 48. public interface AccessibleBean extends ElementWrapperBean, PolleableBean { void setAccessor(WebElement accessor); WebElement getAccessor(); void setAccessorHoverable(boolean hoverable); boolean isAccessorHoverable(); void setHoverAccessorWithJavascript(boolean hoverWithJavascript); boolean getHoverAccessorWithJavascript(); void setClickAccessorWithJavascript(boolean clickWithJavascript); boolean getClickAccessorWithJavascript(); }
  • 49. //Selenium’s native click and hover actions are //environment sensitive, so the the use of //workarounds should be dynamically configurable public interface AccessibleConfig extends PolleableConfig { Optional<Boolean> getHoverAccessorWithJavascript(); void setHoverAccessorWithJavascript(Optional<Boolean> hoverWithJavascript); Optional<Boolean> getClickAccessorWithJavascript(); void setClickAccessorWithJavascript(Optional<Boolean> clickWithJavascript); }
  • 50. public interface AccessibleBuilder< PolleableT extends Polleable, BeanT extends AccessibleBean, BuilderT extends AccessibleBuilder< PolleableT,BeanT,BuilderT>> extends PolleableBuilder<PolleableT,BeanT,BuilderT>, ElementWrapperBuilder<PolleableT,BeanT,BuilderT> { BuilderT setAccessor(WebElement accessor); BuilderT setAccessorHoverable(boolean hoverable); BuilderT setHoverAccessorWithJavascript(boolean hoverWithJavascript); BuilderT setClickAccessorWithJavascript(boolean clickWithJavascript); }
  • 51. //The state bean for a component that can be dismissed //from view is a mirror image to the state bean for a //component that can be rendered visible public interface DismissableBean extends PolleableBean, ElementWrapperBean { void setDismisser(WebElement dismisser); WebElement getDismisser(); void setDismisserHoverable(boolean hoverable); boolean isDismisserHoverable(); void setHoverDismisserWithJavascript(boolean hoverWithJavascript); boolean getHoverDismisserWithJavascript(); void setClickDismisserWithJavascript(boolean clickWithJavascript); boolean getClickDismisserWithJavascript(); }
  • 52. //The configuration bean for a component that can be //dismissed is a mirror to the one for a component that //can be rendered visible public interface DismissableConfig extends PolleableConfig { Optional<Boolean> getHoverDismisserWithJavascript(); void setHoverDismisserWithJavascript(Optional<Boolean> hoverWithJavascript); Optional<Boolean> getClickDismisserWithJavascript(); void setClickDismisserWithJavascript(Optional<Boolean> clickWithJavascript); }
  • 53. public interface DismissableBuilder< PolleableT extends Polleable, BeanT extends DismissableBean, BuilderT extends DismissableBuilder< PolleableT, BeanT, BuilderT>> extends PolleableBuilder<PolleableT, BeanT, BuilderT>, ElementWrapperBuilder<PolleableT,BeanT,BuilderT> { BuilderT setDismisser(WebElement dismisser); BuilderT setDismisserHoverable(boolean hoverable); BuilderT setHoverDismisserWithJavascript(boolean hoverWithJavascript); BuilderT setClickDismisserWithJavascript(boolean clickWithJavaScript); }
  • 54. public interface ToggleableVisibilityBean extends DismissableBean, AccessibleBean { } public interface ToggleableVisibilityConfig extends AccessibleConfig, DismissableConfig { } public interface ToggleableVisibilityBuilder< PolleableT extends Polleable, BeanT extends ToggleableVisibilityBean, BuilderT extends ToggleableVisibilityBuilder <PolleableT,BeanT,BuilderT>> extends AccessibleBuilder<PolleableT,BeanT,BuilderT>, DismissableBuilder<PolleableT,BeanT,BuilderT> { }
  • 55. public interface Expandable extends Polleable { void expand(); void collapse(); boolean isExpanded(); boolean isCollapsed(); }
  • 56. Expandable component: Implementation S Depends on the abstract and concrete implementations for a Polleable S Depends on the abstract implementation for an ElementWrapper S Expandable should be modeled in both abstract and concrete implementations because expandable behavior is usable by even more specialized component types
  • 57. public abstract class AbstractPolleable<PolleableT extends AbstractPolleable<PolleableT>> extends AbstractLoadable<PolleableT> implements Polleable { private @Getter int pollingTimeout; private @Getter int pollingInterval; public AbstractPolleable(PolleableBean bean) { super(bean); this.pollingTimeout = bean.getPollingTimeout(); this.pollingInterval = bean.getPollingInterval(); } }
  • 58. //The concrete implementation only requires a constructor //because all the methods of the Polleable interface are //implemented in the abstract parent class. public class PolleableImpl extends AbstractPolleable<PolleableImpl> { public PolleableImpl(PolleableBean bean) { super(bean); } protected void isLoaded() throws Error { //Do nothing; } }
  • 59. public abstract class AbstractContentPane<ContentPaneT extends AbstractContentPane<ContentPaneT>> extends AbstractLoadable<ContentPaneT> { public AbstractContentPane(LoadableBean bean) { super(bean); } protected abstract WebElement getContainer(); protected void isLoaded() throws Error { try { assertTrue(getContainer().isDisplayed()); } catch(NoSuchElementException e) { throw new Error(e)); } } }
  • 60. //An Expandable is both a content pane and a polleable //component, but it can inherit from only one of //AbstractContentPane and AbstractPolleable. Decorators //allow for components like Expandable to implement //behavior from multiple component types without a lot of //extra code. More on this to come. public abstract class AbstractExpandable<ExpandableT extends AbstractExpandable<ExpandableT>> extends AbstractContentPane<ExpandableT> implements Expandable, PolleableDecorator { private @Getter LoadableProvider<? Extends Polleable> polleableProvider; public AbstractExpandable(PolleableBean bean) { super(bean); this.polleableProvider = new LoadableProvider<>(new PolleableImpl(bean)); } …continued…
  • 61. …AbstractExpandable continued… public boolean isExpanded() { try { return getContainer().isDisplayed(); } catch(NoSuchElementException e) { return false; } } public boolean isCollapsed() { return !isExpanded(); } …continued…
  • 62. …AbstractExpandable continued… public void expand() { if(!isExpanded() && isAccessorHoverable()) { hoverControl(getAccessor(), hoverAccessorWithJavascript()); clickControl(getAccessor(), clickAccessorWithJavascript(), true); ComponentLoader.waitForExpand(this); } if(!isExpanded()) { clickControl(getAccessor(), clickAccessorWithJavascript(), true); ComponentLoader.waitForExpand(this); } } continued…
  • 63. …AbstractExpandable continued… public void collapse() { if(!isCollapsed() && isDismisserHoverable()) { hoverControl(getDismisser(), hoverDismisserWithJavascript()); clickControl(getDismisser(), clickDismisserWithJavascript(), true); ComponentLoader.waitForCollapse(this); } if(!isCollapsed()) { clickControl(getDismisser(), clickDismisserWithJavascript(), true); ComponentLoader.waitForCollapse(this); } } continued…
  • 64. …AbstractExpandable continued… private void hoverOverControl(WebElement control, boolean useJavascript) { if(useJavascript) { //Lambda function for using JavascriptExecutor //to hover over the control new HoverWithJavascript().accept(getDriver(), control); } else { Actions action = new Actions(getDriver()); action.moveToElement(control).perform(); } } …continued…
  • 65. …AbstractExpandable continued… private void clickControl(WebElement control, boolean useJavascript, boolean isControlHoverable) { if(useJavascript) { //Lambda function to use JavascriptExecutor to //click control new ClickWithJavascript().accept(getDriver(), control); } else if(isControlHoverable) { Actions action = new Actions(getDriver()); action.click(control).perform(); } else { control.click(); } } …continued…
  • 66. …AbstractExpandable continued… protected abstract WebElement getAccessor(); protected abstract WebElement getDismisser(); protected abstract boolean isAccessorHoverable(); protected abstract boolean isDismisserHoverable(); protected abstract boolean hoverAccessorWithJavascript(); protected abstract boolean hoverDismisserWithJavascript(); protected abstract boolean clickAccessorWithJavascript(); protected abstract boolean clickDismisserWithJavascript(); …continued…
  • 67. …AbstractExpandable continued… protected void isLoaded() throws Error { try { if(!isAccessorHoverable()) { assertTrue(getAccessor().isDisplayed()); } else { //If this throws a NoSuchElementException, //the Expandable is not loaded getAccessor().isDisplayed(); } } catch(NoSuchElementException e) { throw new Error(e)); } } }
  • 68. public class ExpandableImpl extends AbstractExpandable<ExpandableImpl> { private @Getter(value = AccessLevel.PROTECTED) WebElement accessor; private @Getter(value = AccessLevel.PROTECTED) WebElement dismisser; private @Getter(value = AccessLevel.PROTECTED) WebElement container; private @Getter(value = AccessLevel.PROTECTED) boolean isAccessorHoverable; private @Getter(value = AccessLevel.PROTECTED) boolean isDismisserHoverable; //Cannot use Lombok because getters for these booleans //do not have ‘is’ as prefix, but ‘get’ private boolean hoverAccessorWithJavascript; private boolean hoverDismisserWithJavascript; private boolean clickAccessorWithJavascript; private boolean clickDismisserWithJavascript; …continued…
  • 69. …ExpandableImpl continued… public ExpandableImpl(ToggleableVisibilityBean bean) { super(bean); this.accessor = bean.getAccessor(); this.dismisser = bean.getDismisser (); this.containerElement = bean.getWrappedElement(); this.isAccessorHoverable = bean.isAccessorHoverable(); this.isDismisserHoverable = bean.isDismisserHoverable(); this.hoverAccessorWithJavascript = bean.getHoverAccessorWithJavascript(); this.hoverDismisserWithJavascript = bean.getHoverDismisserWithJavascript(); this.clickAccessorWithJavascript = bean.getClickAccessorWithJavascript(); this.clickDismisserWithJavascript = bean.getClickDismisserWithJavascript(); } //Omitting the getters for the booleans }
  • 71. Decorator: New features without boilerplate S A component implementation can inherit from only one class, but may need to combine behavior from multiple component types S Pre-Java 8 interfaces allow this type of combination, but have the drawback of requiring lots of duplicated code S Java 8 allows interfaces with default method implementations, so you can define the behavior in a Decorator interface S A class can implement multiple Decorator interfaces with default method implementations, thereby adding functionality without boilerplate code
  • 72. public interface PolleableDecorator extends Polleable { LoadableProvider<? extends Polleable> getPolleableProvider(); default int getPollingTimeout() { return getPolleableProvider() .getLoadable() .getPollingTimeout(); } default int getPollingInterval() { return getPolleableProvider() .getLoadable() .getPollingInterval(); } }
  • 73. public class LoadableProvider<LoadableT extends Loadable> { private @Getter(value = AccessLevel.PROTECTED) LoadableT loadable; public LoadableProvider(LoadableT loadable) { this.loadable = loadable; } }
  • 74. public interface ExpandableDecorator extends Expandable { LoadableProvider<? extends Expandable> getExpandableProvider(); default void expand() { getExpandableProvider() .getLoadable() .expand(); } default void collapse() { getExpandableProvider() .getLoadable() .collapse(); } …continued…
  • 75. …ExpandableDecorator continued… default boolean isExpanded() { return getExpandableProvider() .getLoadable() .isExpanded(); } default boolean isCollapsed() { return getExpandableProvider() .getLoadable() .isCollapsed(); } default int getPollingTimeout() { return getExpandableProvider() .getLoadable() .getPollingTimeout(); } …continued…
  • 76. …ExpandableDecorator continued… default int getPollingInterval() { return getExpandableProvider() .getLoadable() .getPollingInterval(); } }
  • 78. Menu component: Specification S Menus have a content container that contains their options S Menus can be flat or expandable S A menu component needs to provide a getter for the available options S A menu component needs to provide the ability to click an option
  • 79. Menu component: Design S The basic menu interface should be agnostic to whether it is implemented by a flat or dropdown menu component S Decorators and marker interfaces should be used to define a dropdown menu S The click action for a menu option can fail silently in some environments, so the use of a JavaScript workaround to click an option should be dynamically configurable
  • 80. public interface Menu<OptionT extends Clickable> extends Loadable { List<OptionT> getOptions(); void clickOption(final OptionT option); } public interface MenuBean extends ElementWrapperBean { void setOptionElements(List<WebElement> options); List<WebElement> getOptionElements(); void setClickOptionWithJavascript(boolean clickWithJavascript); boolean getClickOptionWithJavascript(); } public interface MenuConfig extends LoadableConfig { Optional<Boolean> getClickOptionWithJavascript(); void setClickOptionWithJavascript(Optional<Boolean> clickWithJavascript); }
  • 81. public interface MenuBuilder< MenuT extends Menu<? extends Clickable>, BeanT extends MenuBean, BuilderT extends MenuBuilder<MenuT,BeanT,BuilderT> > extends ElementWrapperBuilder<MenuT,BeanT,BuilderT> { BuilderT setOptionElements(List<WebElement> options); BuilderT setClickOptionWithJavascript(boolean clickWithJavascript); }
  • 82. public interface DropDownMenuBean extends MenuBean, ToggleableVisibilityBean { } public interface DropDownMenuConfig extends MenuConfig, ToggleableVisibilityConfig { } public interface DropDownMenuBuilder< MenuT extends Menu<? extends Clickable> & Expandable, BeanT extends DropDownMenuBean, BuilderT extends DropDownMenuBuilder< MenuT,BeanT,BuilderT> > extends MenuBuilder<MenuT,BeanT,BuilderT>, ToggleableVisibilityBuilder<MenuT, BeanT,BuilderT> { }
  • 83. public abstract class AbstractMenu< OptionT extends Clickable, MenuT extends AbstractMenu<OptionT,MenuT>> extends AbstractContentPane<MenuT> implements Menu<OptionT> { public AbstractMenu(LoadableBean bean) { super(bean); } public List<OptionT> getOptions() { if (isDropDownMenu()) { ((Expandable) this).expand(); } List<OptionT> options = buildOptions(); if (isDropDownMenu()) { ((Expandable) this).collapse(); } return options; } continued…
  • 84. …AbstractMenu continued… public void clickOption(OptionT option) { if(getOptions().contains(option)) { if(isDropDownMenu()) { ((Expandable)this).expand(); } option.click(); if(isDropDownMenu()) { ((Expandable) this).collapse(); } } else { throw new IllegalArgumentException(); } } …continued…
  • 85. …AbstractMenu continued… protected abstract List<WebElement> getOptionElements(); protected abstract boolean clickOptionWithJavascript(); protected List<OptionT> buildOptions() { final List<OptionT> options = new ArrayList<>(); for(WebElement element : getOptionElements()) { options.add(buildOption(element)); } return options; } protected boolean isDropDownMenu() { return Expandable.class .isAssignableFrom(this.getClass()); } continued…
  • 86. …AbstractMenu continued… protected ClickableBuilder<OptionT,? extends ClickableBean,?> getOptionBuilder() { return new ClickableBuilderImpl<>(); } private OptionT buildOption(WebElement element) { return getOptionBuilder() .setComponentClass(getOptionClass()) .setDriver(getDriver()) .setWrappedElement(optionElement) .setUseJavascriptClick( clickOptionWithJavascript()) .build()); } private Class<OptionT> getOptionClass() { //Omitted for brevity. Fancy Guava //reflection API is used here. } }
  • 87. public class DropDownBasicMenu extends AbstractBasicMenu<DropDownBasicMenu> implements ExpandableDecorator { private @Getter LoadableProvider<? Extends Expandable> expandableProvider; private @Getter(value = AccessLevel.PROTECTED) List<WebElement> optionElements; private @Getter(value = AccessLevel.PROTECTED) WebElement container; private @Getter(value = AccessLevel.PRIVATE) WebElement accessor; private boolean clickOptionWithJavascript; …continued…
  • 88. …DropDownBasicMenu continued… public DropDownBasicMenu(DropDownMenuBean bean) { super(bean); this.expandableProvider = new LoadableProvider<>(new ExpandableImpl(bean)); this.optionElements = bean.getOptionElements(); this.container = bean.getWrappedElement(); this.accessor = bean.getAccessor(); this.clickOptionWithJavascript = bean.getClickOptionWithJavascript(); } protected boolean clickOptionWithJavascript() { return clickOptionWithJavascript; } …continued…
  • 89. …DropDownBasicMenu continued… protected void isLoaded() throws Error { try { expandableProvider.getLoadable().isLoaded(); } catch(NoSuchElementException e) { throw new Error(e)); } } }