Skip to content
Closed
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
58 changes: 35 additions & 23 deletions java/client/src/org/openqa/selenium/support/PageFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

package org.openqa.selenium.support;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
import org.openqa.selenium.support.pagefactory.DefaultFieldDecorator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
Expand All @@ -26,6 +26,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;


/**
Expand All @@ -52,31 +53,31 @@ public class PageFactory {
* which takes a WebDriver instance as its only argument or falling back on a no-arg constructor.
* An exception will be thrown if the class cannot be instantiated.
*
* @param driver The driver that will be used to look up the elements
* @param context The org.openqa.selenium.SearchContext instance that will be used to
* look up the elements
* @param pageClassToProxy A class which will be initialised.
* @param <T> Class of the PageObject
* @return An instantiated instance of the class with WebElement and List&lt;WebElement&gt;
* fields proxied
* @see FindBy
* @see CacheLookup
*/
public static <T> T initElements(WebDriver driver, Class<T> pageClassToProxy) {
T page = instantiatePage(driver, pageClassToProxy);
initElements(driver, page);
public static <T> T initElements(SearchContext context, Class<T> pageClassToProxy) {
T page = instantiatePage(context, pageClassToProxy);
initElements(context, page);
return page;
}

/**
* As {@link org.openqa.selenium.support.PageFactory#initElements(org.openqa.selenium.WebDriver,
* As {@link org.openqa.selenium.support.PageFactory#initElements(org.openqa.selenium.SearchContext,
* Class)} but will only replace the fields of an already instantiated Page Object.
*
* @param driver The driver that will be used to look up the elements
* @param context The org.openqa.selenium.SearchContext instance that will be used to look up the elements
* @param page The object with WebElement and List&lt;WebElement&gt; fields that
* should be proxied.
*/
public static void initElements(WebDriver driver, Object page) {
final WebDriver driverRef = driver;
initElements(new DefaultElementLocatorFactory(driverRef), page);
public static void initElements(SearchContext context, Object page) {
initElements(new DefaultElementLocatorFactory(context), page);
}

/**
Expand All @@ -88,8 +89,7 @@ public static void initElements(WebDriver driver, Object page) {
* @param page The object to decorate the fields of
*/
public static void initElements(ElementLocatorFactory factory, Object page) {
final ElementLocatorFactory factoryRef = factory;
initElements(new DefaultFieldDecorator(factoryRef), page);
initElements(new DefaultFieldDecorator(factory), page);
}

/**
Expand All @@ -110,6 +110,11 @@ public static void initElements(FieldDecorator decorator, Object page) {
private static void proxyFields(FieldDecorator decorator, Object page, Class<?> proxyIn) {
Field[] fields = proxyIn.getDeclaredFields();
for (Field field : fields) {
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
continue;
}

Object value = decorator.decorate(page.getClass().getClassLoader(), field);
if (value != null) {
try {
Expand All @@ -122,19 +127,26 @@ private static void proxyFields(FieldDecorator decorator, Object page, Class<?>
}
}

private static <T> T instantiatePage(WebDriver driver, Class<T> pageClassToProxy) {
@SuppressWarnings("unchecked")
private static <T> T instantiatePage(SearchContext context, Class<T> pageClassToProxy) {
try {
try {
Constructor<T> constructor = pageClassToProxy.getConstructor(WebDriver.class);
return constructor.newInstance(driver);
} catch (NoSuchMethodException e) {
return pageClassToProxy.newInstance();
Constructor<?>[] availableConstructors = pageClassToProxy.getDeclaredConstructors();
for (Constructor<?> c: availableConstructors) {
Class<?>[] parameterTypes = c.getParameterTypes();
if (parameterTypes.length != 1) {
continue;
}

Class<?> parameterClazz = parameterTypes[0];
if (!parameterClazz.isAssignableFrom(context.getClass())) {
continue;
}
c.setAccessible(true);
return pageClassToProxy.cast(c.newInstance(context));
}
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {

return pageClassToProxy.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.openqa.selenium.support.pagefactory.internal;

import static java.lang.String.format;

import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.ElementLocator;
Expand All @@ -33,13 +35,15 @@ public LocatingElementHandler(ElementLocator locator) {
}

public Object invoke(Object object, Method method, Object[] objects) throws Throwable {

if ("toString".equals(method.getName())) {
return format("Proxy element for: %s", locator.toString());
}

WebElement element;
try {
element = locator.findElement();
} catch (NoSuchElementException e) {
if ("toString".equals(method.getName())) {
return "Proxy element for: " + locator.toString();
}
throw e;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.openqa.selenium.support.pagefactory.internal;

import static java.lang.String.format;

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

Expand All @@ -33,6 +35,10 @@ public LocatingElementListHandler(ElementLocator locator) {
}

public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
if ("toString".equals(method.getName())) {
return format("Proxy list of elements for: %s", locator.toString());
}

List<WebElement> elements = locator.findElements();

try {
Expand Down
82 changes: 82 additions & 0 deletions java/client/test/org/openqa/selenium/support/PageFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.pagefactory.FieldDecorator;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.TickingClock;
Expand All @@ -45,6 +46,9 @@
public class PageFactoryTest {

private WebDriver driver;

@FindBy
private WebElement proxiedField;

@Test
public void shouldProxyElementsInAnInstantiatedPage() {
Expand All @@ -67,6 +71,62 @@ public void shouldInsertProxiesForPublicWebElements() {
assertThat(page.list, is(notNullValue()));
}

@Test
public void shouldInsertProxiesForPublicWebElements2() {
RemoteWebElement e = null;
PublicPage page = PageFactory.initElements(e , PublicPage.class);

assertThat(page.q, is(notNullValue()));
assertThat(page.list, is(notNullValue()));
}

@Test
public void shouldInsertProxiesForPublicWebElementsWhenClassHasConstructorWithWebElementParameter() {
RemoteWebElement e = mock(RemoteWebElement.class);
NestedWidget page = PageFactory.initElements(e , NestedWidget.class);

assertThat(page.q, is(notNullValue()));
assertThat(page.list, is(notNullValue()));
}

@Test
public void shouldInsertProxiesForPublicWebElementsWhenProxyOfElementIsPassedThroughConsctructor() {
WebDriver d = mock(WebDriver.class);
PageFactory.initElements(d, this);

try{
NestedWidget page = PageFactory.initElements(proxiedField , NestedWidget.class);
assertThat(page.q, is(notNullValue()));
assertThat(page.list, is(notNullValue()));
}
finally{
proxiedField = null;
}
}

@Test
public void shouldNotInstantiatePageObjectWhenThereIsNoRelevantConstructor() {
try{
WebDriver d = mock(WebDriver.class);
@SuppressWarnings("unused")
NestedWidget page = PageFactory.initElements(d , NestedWidget.class);
fail("Should not instantiate class because it has no relevant constructors");
}
catch (Exception e){
assertThat(e.getClass(), equalTo(RuntimeException.class));
assertThat(e.getCause().getClass(), equalTo(InstantiationException.class));
}
}

@Test
public void shouldNotInsertProxiesForPublicStaticAndFinalFields() {
PageObjectWithStaticAndFinalFields p = new PageObjectWithStaticAndFinalFields();
PageFactory.initElements(driver , p);

assertThat(PageObjectWithStaticAndFinalFields.staticField, is(nullValue()));
assertThat(p.finalField, is(equalTo(p.finalField)));
}

@Test
public void shouldProxyElementsFromParentClassesToo() {
ChildPage page = new ChildPage();
Expand Down Expand Up @@ -211,6 +271,28 @@ public static class PublicPage {
public WebElement rendered;
}

public static class PageObjectWithStaticAndFinalFields{
@FindBy(name = "q")
public static WebElement staticField;

@FindBy(name = "q")
public final WebElement finalField = mock(WebElement.class);
}

public static class NestedWidget {

@FindBy(name = "q")
public WebElement q;

@FindBy(name = "q")
public List<WebElement> list;

public WebElement rendered;

NestedWidget(WebElement e){
}
}

public static class ChildPage extends PublicPage {

public WebElement submit;
Expand Down