Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: nested page objects i.e. widget objects #25

Merged
merged 1 commit into from
Feb 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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