Skip to content

Commit

Permalink
Merge pull request #25 from CoeJoder/widgetobjects
Browse files Browse the repository at this point in the history
feat: nested page objects i.e. widget objects
  • Loading branch information
wakaleo committed Feb 17, 2015
2 parents 26f09b0 + 3883042 commit 4d131ed
Show file tree
Hide file tree
Showing 21 changed files with 778 additions and 168 deletions.
23 changes: 23 additions & 0 deletions core/src/main/java/net/serenitybdd/core/pages/WidgetObject.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.serenitybdd.core.pages;

import org.openqa.selenium.support.FindBy;

import net.serenitybdd.core.annotations.ImplementedBy;

/**
* Represents a page fragment which occurs across pages or multiple times in a single
* page. Instance members with {@link FindBy @FindBy} style annotations are located
* within this context.
*
* @author Joe Nasca
*/
@ImplementedBy(WidgetObjectImpl.class)
public interface WidgetObject extends WebElementFacade {

/**
* Get the page containing this widget.
* @return
*/
public PageObject getPage();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package net.serenitybdd.core.pages;

import net.thucydides.core.webdriver.DefaultWidgetObjectInitialiser;

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.ElementLocator;

/**
* Base implementation for {@link WidgetObject}.
*
* @author Joe Nasca
*/
public class WidgetObjectImpl extends WebElementFacadeImpl implements WidgetObject {

private final PageObject page;

public WidgetObjectImpl(PageObject page, ElementLocator locator, WebElement webElement, long timeoutInMilliseconds) {
super(page.getDriver(), locator, webElement, timeoutInMilliseconds);
this.page = page;
new DefaultWidgetObjectInitialiser(page.getDriver(), (int) timeoutInMilliseconds).apply(this);
}

public WidgetObjectImpl(PageObject page, ElementLocator locator, long timeoutInMilliseconds) {
this(page, locator, (WebElement) null, timeoutInMilliseconds);
}

public PageObject getPage() {
return page;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import net.serenitybdd.core.pages.PageObject;
import net.serenitybdd.core.pages.WebElementDescriber;
import net.thucydides.core.annotations.NotImplementedException;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.ElementLocator;

Expand All @@ -22,7 +22,7 @@ public abstract class AbstractListItemHandler<T> implements InvocationHandler {

protected final ElementLocator locator;
protected final WebElement element;
protected final WebDriver driver;
protected final PageObject page;
protected final Class<?> implementerClass;
protected final long timeoutInMilliseconds;

Expand All @@ -35,9 +35,9 @@ public abstract class AbstractListItemHandler<T> implements InvocationHandler {
* @param driver
* @param timeoutInMilliseconds
*/
public AbstractListItemHandler(Class<T> targetInterface, Class<?> interfaceType, ElementLocator locator, WebElement element, WebDriver driver, long timeoutInMilliseconds) {
public AbstractListItemHandler(Class<T> targetInterface, Class<?> interfaceType, ElementLocator locator, WebElement element, PageObject page, long timeoutInMilliseconds) {
this.locator = locator;
this.driver = driver;
this.page = page;
this.element = element;
if (!targetInterface.isAssignableFrom(interfaceType)) {
throw new NotImplementedException("interface not assignable to " + targetInterface.getSimpleName());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package net.thucydides.core.annotations.locators;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import net.serenitybdd.core.pages.PageObject;
import net.serenitybdd.core.pages.WebElementDescriber;
import net.thucydides.core.annotations.NotImplementedException;

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.ElementLocator;

/**
* Base class for handlers of non-List members.
* @author Joe Nasca
* @param <T> the target interface
*/
public abstract class AbstractSingleItemHandler<T> implements InvocationHandler {

protected final ElementLocator locator;
protected final PageObject page;
protected final Class<?> implementerClass;
protected final long timeoutInMilliseconds;

public AbstractSingleItemHandler(Class<T> targetInterface, Class<?> interfaceType, ElementLocator locator,
PageObject page, long timeoutInMilliseconds) {
this.page = page;
this.locator = locator;
if (!targetInterface.isAssignableFrom(interfaceType)) {
throw new NotImplementedException("interface not assignable to " + targetInterface.getSimpleName());
}

this.implementerClass = new WebElementFacadeImplLocator().getImplementer(interfaceType);
this.timeoutInMilliseconds = timeoutInMilliseconds;
}

@Override
public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
try {
if ("getWrappedElement".equals(method.getName())) {
return locator.findElement();
} else if ("toString".equals(method.getName())) {
return toStringForElement();
}
Object webElementFacadeExt = newElementInstance(timeoutInMilliseconds);

return method.invoke(implementerClass.cast(webElementFacadeExt), objects);
} catch (InvocationTargetException e) {
// Unwrap the underlying exception
throw e.getCause();
}
}

protected abstract Object newElementInstance(long timeoutInMilliseconds) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException;

private String toStringForElement() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Object webElementFacadeExt = newElementInstance(100);
if (webElementFacadeExt == null) {
return "<" + locator.toString() + ">";
} else {
return new WebElementDescriber().webElementDescription((WebElement) webElementFacadeExt,locator);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
package net.thucydides.core.annotations.locators;

import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.util.List;

import net.serenitybdd.core.annotations.locators.SmartAnnotations;
import net.thucydides.core.steps.StepEventBus;
import net.thucydides.core.webdriver.MobilePlatform;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Clock;
import org.openqa.selenium.support.ui.SlowLoadableComponent;
import org.openqa.selenium.support.ui.SystemClock;

import java.lang.reflect.Field;
import java.util.List;
import com.google.common.collect.Lists;

public class SmartAjaxElementLocator extends SmartElementLocator {
protected final int timeOutInSeconds;
private final Clock clock;

private final Field field;
private final WebDriver driver;
private final SearchContext searchContext;
private final MobilePlatform platform;

/**
* Main constructor.
*
* @param driver The WebDriver to use when locating elements
* @param searchContext The SearchContext to use when locating elements
* @param field The field representing this element
* @param timeOutInSeconds How long to wait for the element to appear. Measured in seconds.
*/
public SmartAjaxElementLocator(WebDriver driver, Field field, MobilePlatform platform, int timeOutInSeconds) {
this(new SystemClock(), driver, field, platform, timeOutInSeconds);
public SmartAjaxElementLocator(SearchContext searchContext, Field field, MobilePlatform platform, int timeOutInSeconds) {
this(new SystemClock(), searchContext, field, platform, timeOutInSeconds);

}

public SmartAjaxElementLocator(Clock clock, WebDriver driver, Field field, MobilePlatform platform, int timeOutInSeconds) {
super(driver, field, platform);
public SmartAjaxElementLocator(Clock clock, SearchContext searchContext, Field field, MobilePlatform platform, int timeOutInSeconds) {
super(searchContext, field, platform);
this.timeOutInSeconds = timeOutInSeconds;
this.clock = clock;
this.field = field;
this.driver = driver;
this.searchContext = searchContext;
this.platform = platform;
}

Expand Down Expand Up @@ -70,7 +72,7 @@ private boolean calledFromAQuickMethod() {
public WebElement findElementImmediately() {
SmartAnnotations annotations = new SmartAnnotations(field, platform);
By by = annotations.buildBy();
WebElement element = driver.findElement(by);
WebElement element = searchContext.findElement(by);
if (element == null) {
throw new NoSuchElementException("No such element found for criteria " + by.toString());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,91 +1,44 @@
package net.thucydides.core.annotations.locators;

import net.serenitybdd.core.annotations.ImplementedBy;
import net.thucydides.core.annotations.NotImplementedException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import net.serenitybdd.core.pages.PageObject;
import net.serenitybdd.core.pages.WebElementFacade;
import net.serenitybdd.core.pages.WebElementDescriber;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.ElementLocator;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class SmartElementHandler extends AbstractSingleItemHandler<WebElementFacade> {

public class SmartElementHandler implements InvocationHandler{
private final ElementLocator locator;
private final WebDriver driver;
private final Class<?> implementerClass;
private final long timeoutInMilliseconds;
private static final String NO_SUITABLE_CONSTRUCTOR_FOUND_FMT2 = "No suitable constructor found. "
+ "Expected: %s(WebDriver, ElementLocator, long) or %s(WebDriver, ElementLocator, WebElement, long)";

private Class<?> getImplementer(Class<?> interfaceType) {
if (!interfaceType.isInterface()){
throw new NotImplementedException(interfaceType.getSimpleName() +
" is not an interface");
}
Class<?> implementerClass = null;
ImplementedBy implBy = interfaceType.getAnnotation(ImplementedBy.class);
if (implBy == null){
// todo Remove when thucydides ImplementedBy is finally removed
net.thucydides.core.annotations.ImplementedBy implByDep = interfaceType.getAnnotation(net.thucydides.core.annotations.ImplementedBy.class);
if(implByDep == null) {
throw new NotImplementedException(interfaceType.getSimpleName() +
" is not implemented by any class (or not annotated by @ImplementedBy)");
} else {
implementerClass = implByDep.value();
}
} else {
implementerClass = implBy.value();
}
if (!interfaceType.isAssignableFrom(implementerClass)) {
throw new NotImplementedException(String.format("implementer Class '%s' does not implement the interface '%s'", implementerClass, interfaceType.getName()));
}
return implementerClass;
public SmartElementHandler(Class<?> interfaceType, ElementLocator locator, PageObject page,
long timeoutInMilliseconds) {
super(WebElementFacade.class, interfaceType, locator, page, timeoutInMilliseconds);
}

public SmartElementHandler(Class<?> interfaceType, ElementLocator locator,
WebDriver driver, long timeoutInMilliseconds) {
this.driver = driver;
this.locator = locator;
if (!WebElementFacade.class.isAssignableFrom(interfaceType)) {
throw new NotImplementedException("interface not assignable to WebElementFacade");
}

this.implementerClass = getImplementer(interfaceType);
this.timeoutInMilliseconds = timeoutInMilliseconds;
}

public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
try {
if ("getWrappedElement".equals(method.getName())) {
return locator.findElement();
} else if ("toString".equals(method.getName())) {
return toStringForElement();
@Override
protected Object newElementInstance(long timeoutInMilliseconds) throws InvocationTargetException,
NoSuchMethodException, InstantiationException, IllegalAccessException {
Constructor<?> constructor = null;
Object instance = null;
try {
constructor = implementerClass.getConstructor(WebDriver.class, ElementLocator.class, long.class);
instance = constructor.newInstance(page.getDriver(), locator, timeoutInMilliseconds);
}
catch (NoSuchMethodException e) {
try {
constructor = implementerClass.getConstructor(WebDriver.class, ElementLocator.class, WebElement.class, long.class);
instance = constructor.newInstance(page.getDriver(), locator, (WebElement) null, timeoutInMilliseconds);
}
catch (NoSuchMethodException e1) {
String className = implementerClass.getSimpleName();
throw new RuntimeException(String.format(NO_SUITABLE_CONSTRUCTOR_FOUND_FMT2, className));
}
Object webElementFacadeExt = newElementInstance(driver, locator, timeoutInMilliseconds);

return method.invoke(implementerClass.cast(webElementFacadeExt), objects);
} catch (InvocationTargetException e) {
// Unwrap the underlying exception
throw e.getCause();
}
}

private Object newElementInstance(WebDriver driver, ElementLocator locator, long timeoutInMilliseconds) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<?> constructor = implementerClass.getConstructor(WebDriver.class, ElementLocator.class, long.class);
return constructor.newInstance(driver, locator, timeoutInMilliseconds);
}

private String toStringForElement() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Object webElementFacadeExt = newElementInstance(driver, locator, 100);
if (webElementFacadeExt == null) {
return "<" + locator.toString() + ">";
} else {
return new WebElementDescriber().webElementDescription((WebElement) webElementFacadeExt,locator);
}
return instance;
}

}

Original file line number Diff line number Diff line change
@@ -1,41 +1,26 @@
package net.thucydides.core.annotations.locators;

import io.appium.java_client.AppiumDriver;
import net.thucydides.core.guice.Injectors;
import net.thucydides.core.util.EnvironmentVariables;
import java.lang.reflect.Field;

import net.thucydides.core.webdriver.MobilePlatform;
import net.thucydides.core.webdriver.appium.AppiumConfiguration;
import org.openqa.selenium.WebDriver;

import org.openqa.selenium.SearchContext;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;

import java.lang.reflect.Field;

public final class SmartElementLocatorFactory implements ElementLocatorFactory {
private final WebDriver webDriver;
private final SearchContext searchContext;
private int timeoutInSeconds;
private MobilePlatform platform;

public SmartElementLocatorFactory(WebDriver webDriver, int timeoutInSeconds) {
this.webDriver = webDriver;
this.timeoutInSeconds = timeoutInSeconds;
this.platform = platformFor(webDriver);

}

private MobilePlatform platformFor(WebDriver webDriver) {
if (webDriver instanceof AppiumDriver) {
AppiumConfiguration appiumConfiguration = AppiumConfiguration.from(
Injectors.getInjector().getProvider(EnvironmentVariables.class).get());
return appiumConfiguration.getTargetPlatform();
}
return MobilePlatform.NONE;
public SmartElementLocatorFactory(SearchContext searchContext, MobilePlatform platform, int timeoutInSeconds) {
this.searchContext = searchContext;
this.timeoutInSeconds = timeoutInSeconds;
this.platform = platform;
}

public ElementLocator createLocator(Field field) {
// FIXME: Need to pass through the appium platform either here, or in both ElementLocator instances
return new SmartAjaxElementLocator(webDriver, field, platform, timeoutInSeconds);
return new SmartAjaxElementLocator(searchContext, field, platform, timeoutInSeconds);
}


}
Loading

0 comments on commit 4d131ed

Please sign in to comment.