-
-
Notifications
You must be signed in to change notification settings - Fork 518
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from CoeJoder/widgetobjects
feat: nested page objects i.e. widget objects
- Loading branch information
Showing
21 changed files
with
778 additions
and
168 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
core/src/main/java/net/serenitybdd/core/pages/WidgetObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
|
||
} |
30 changes: 30 additions & 0 deletions
30
core/src/main/java/net/serenitybdd/core/pages/WidgetObjectImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
core/src/main/java/net/thucydides/core/annotations/locators/AbstractSingleItemHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 29 additions & 76 deletions
105
core/src/main/java/net/thucydides/core/annotations/locators/SmartElementHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} | ||
|
35 changes: 10 additions & 25 deletions
35
core/src/main/java/net/thucydides/core/annotations/locators/SmartElementLocatorFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
|
||
} |
Oops, something went wrong.