Skip to content

Commit d079a12

Browse files
Java. Page Factory enhancement.
- Ability to use not only Webdriver to populate page object fields via method public static <T> T initElements(SearchContext context, Class<T> pageClassToProxy). This change was done in order to simplify page object instantiation and population when there is the necessity to implement something that describes widget or repeatable element set nested in a root element. - the additional checking of field modifiers before it will be populated. Field that is going to be filled should not be static and final. I found these issues some time ago. I tried to implement page object with final fields that weren't annotated by @findby's. Also I found that it tries to set proxy-WebElement as a value of a static WebElement-field. Actually I think that both issues are minor but it would be good if whey were fixed.
1 parent a6928d7 commit d079a12

File tree

4 files changed

+125
-20
lines changed

4 files changed

+125
-20
lines changed

java/client/src/org/openqa/selenium/support/PageFactory.java

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.openqa.selenium.support;
1919

20-
import org.openqa.selenium.WebDriver;
20+
import org.openqa.selenium.SearchContext;
2121
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
2222
import org.openqa.selenium.support.pagefactory.DefaultFieldDecorator;
2323
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
@@ -26,6 +26,7 @@
2626
import java.lang.reflect.Constructor;
2727
import java.lang.reflect.Field;
2828
import java.lang.reflect.InvocationTargetException;
29+
import java.lang.reflect.Modifier;
2930

3031

3132
/**
@@ -52,30 +53,31 @@ public class PageFactory {
5253
* which takes a WebDriver instance as its only argument or falling back on a no-arg constructor.
5354
* An exception will be thrown if the class cannot be instantiated.
5455
*
55-
* @param driver The driver that will be used to look up the elements
56+
* @param context The org.openqa.selenium.SearchContext instance that will be used to
57+
* look up the elements
5658
* @param pageClassToProxy A class which will be initialised.
5759
* @return An instantiated instance of the class with WebElement and List&lt;WebElement&gt;
5860
* fields proxied
5961
* @see FindBy
6062
* @see CacheLookup
6163
*/
62-
public static <T> T initElements(WebDriver driver, Class<T> pageClassToProxy) {
63-
T page = instantiatePage(driver, pageClassToProxy);
64-
initElements(driver, page);
64+
public static <T> T initElements(SearchContext context, Class<T> pageClassToProxy) {
65+
T page = instantiatePage(context, pageClassToProxy);
66+
initElements(context, page);
6567
return page;
6668
}
6769

6870
/**
69-
* As {@link org.openqa.selenium.support.PageFactory#initElements(org.openqa.selenium.WebDriver,
71+
* As {@link org.openqa.selenium.support.PageFactory#initElements(org.openqa.selenium.SearchContext,
7072
* Class)} but will only replace the fields of an already instantiated Page Object.
7173
*
72-
* @param driver The driver that will be used to look up the elements
74+
* @param context The org.openqa.selenium.SearchContext instance that will be used to look up the elements
7375
* @param page The object with WebElement and List&lt;WebElement&gt; fields that
7476
* should be proxied.
7577
*/
76-
public static void initElements(WebDriver driver, Object page) {
77-
final WebDriver driverRef = driver;
78-
initElements(new DefaultElementLocatorFactory(driverRef), page);
78+
public static void initElements(SearchContext context, Object page) {
79+
final SearchContext searchContextRef = context;
80+
initElements(new DefaultElementLocatorFactory(searchContextRef), page);
7981
}
8082

8183
/**
@@ -109,6 +111,10 @@ public static void initElements(FieldDecorator decorator, Object page) {
109111
private static void proxyFields(FieldDecorator decorator, Object page, Class<?> proxyIn) {
110112
Field[] fields = proxyIn.getDeclaredFields();
111113
for (Field field : fields) {
114+
int modifiers = field.getModifiers();
115+
if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers))
116+
continue;
117+
112118
Object value = decorator.decorate(page.getClass().getClassLoader(), field);
113119
if (value != null) {
114120
try {
@@ -121,14 +127,24 @@ private static void proxyFields(FieldDecorator decorator, Object page, Class<?>
121127
}
122128
}
123129

124-
private static <T> T instantiatePage(WebDriver driver, Class<T> pageClassToProxy) {
130+
@SuppressWarnings("unchecked")
131+
private static <T> T instantiatePage(SearchContext context, Class<T> pageClassToProxy) {
125132
try {
126-
try {
127-
Constructor<T> constructor = pageClassToProxy.getConstructor(WebDriver.class);
128-
return constructor.newInstance(driver);
129-
} catch (NoSuchMethodException e) {
130-
return pageClassToProxy.newInstance();
133+
Constructor<?>[] availableConstructors = pageClassToProxy.getDeclaredConstructors();
134+
for (Constructor<?> c: availableConstructors){
135+
136+
Class<?>[] parameterTypes = c.getParameterTypes();
137+
if (parameterTypes.length != 1)
138+
continue;
139+
140+
Class<?> parameterClazz = parameterTypes[0];
141+
if (!parameterClazz.isAssignableFrom(context.getClass()))
142+
continue;
143+
c.setAccessible(true);
144+
return (T) c.newInstance(context);
131145
}
146+
147+
return pageClassToProxy.newInstance();
132148
} catch (InstantiationException e) {
133149
throw new RuntimeException(e);
134150
} catch (IllegalAccessException e) {

java/client/src/org/openqa/selenium/support/pagefactory/internal/LocatingElementHandler.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ public LocatingElementHandler(ElementLocator locator) {
3232
}
3333

3434
public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
35+
36+
if ("toString".equals(method.getName())) {
37+
return "Proxy element for: " + locator.toString();
38+
}
39+
3540
WebElement element;
3641
try {
3742
element = locator.findElement();
3843
} catch (NoSuchElementException e) {
39-
if ("toString".equals(method.getName())) {
40-
return "Proxy element for: " + locator.toString();
41-
}
42-
else throw e;
44+
throw e;
4345
}
4446

4547
if ("getWrappedElement".equals(method.getName())) {

java/client/src/org/openqa/selenium/support/pagefactory/internal/LocatingElementListHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public LocatingElementListHandler(ElementLocator locator) {
3232
}
3333

3434
public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
35+
if ("toString".equals(method.getName())) {
36+
return "Proxy list of elements for: " + locator.toString();
37+
}
38+
3539
List<WebElement> elements = locator.findElements();
3640

3741
try {

java/client/test/org/openqa/selenium/support/PageFactoryTest.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,23 @@
3232
import org.openqa.selenium.TimeoutException;
3333
import org.openqa.selenium.WebDriver;
3434
import org.openqa.selenium.WebElement;
35+
import org.openqa.selenium.remote.RemoteWebElement;
3536
import org.openqa.selenium.support.pagefactory.FieldDecorator;
3637
import org.openqa.selenium.support.ui.ExpectedConditions;
3738
import org.openqa.selenium.support.ui.TickingClock;
3839
import org.openqa.selenium.support.ui.Wait;
3940
import org.openqa.selenium.support.ui.WebDriverWait;
41+
import org.testng.Assert;
4042

4143
import java.lang.reflect.Field;
4244
import java.util.List;
4345

4446
public class PageFactoryTest {
4547

4648
private WebDriver driver;
49+
50+
@FindBy
51+
private WebElement proxiedField;
4752

4853
@Test
4954
public void shouldProxyElementsInAnInstantiatedPage() {
@@ -66,6 +71,62 @@ public void shouldInsertProxiesForPublicWebElements() {
6671
assertThat(page.list, is(notNullValue()));
6772
}
6873

74+
@Test
75+
public void shouldInsertProxiesForPublicWebElements2() {
76+
RemoteWebElement e = null;
77+
PublicPage page = PageFactory.initElements(e , PublicPage.class);
78+
79+
assertThat(page.q, is(notNullValue()));
80+
assertThat(page.list, is(notNullValue()));
81+
}
82+
83+
@Test
84+
public void shouldInsertProxiesForPublicWebElementsWhenClassHasConstructorWithWebElementParameter() {
85+
RemoteWebElement e = mock(RemoteWebElement.class);
86+
NestedWidget page = PageFactory.initElements(e , NestedWidget.class);
87+
88+
assertThat(page.q, is(notNullValue()));
89+
assertThat(page.list, is(notNullValue()));
90+
}
91+
92+
@Test
93+
public void shouldInsertProxiesForPublicWebElementsWhenProxyOfElementIsPassedThroughConsctructor() {
94+
WebDriver d = mock(WebDriver.class);
95+
PageFactory.initElements(d, this);
96+
97+
try{
98+
NestedWidget page = PageFactory.initElements(proxiedField , NestedWidget.class);
99+
assertThat(page.q, is(notNullValue()));
100+
assertThat(page.list, is(notNullValue()));
101+
}
102+
finally{
103+
proxiedField = null;
104+
}
105+
}
106+
107+
@Test
108+
public void shouldNotInstantiatePageObjectWhenThereIsNoRelevantConstructor() {
109+
try{
110+
WebDriver d = mock(WebDriver.class);
111+
@SuppressWarnings("unused")
112+
NestedWidget page = PageFactory.initElements(d , NestedWidget.class);
113+
fail("Should not instantiate class because it has no relevant constructors");
114+
}
115+
catch (Exception e){
116+
Assert.assertEquals(e.getClass(), RuntimeException.class);
117+
Assert.assertEquals(e.getCause().getClass(), InstantiationException.class);
118+
}
119+
}
120+
121+
@Test
122+
public void shouldNotInsertProxiesForPublicStaticAndFinalFields() {
123+
PageObjectWithStaticAndFinalFields p = new PageObjectWithStaticAndFinalFields();
124+
PageFactory.initElements(driver , p);
125+
126+
assertThat(PageObjectWithStaticAndFinalFields.staticField, is(nullValue()));
127+
assertThat(p.finalField, is(equalTo(p.finalField)));
128+
}
129+
69130
@Test
70131
public void shouldProxyElementsFromParentClassesToo() {
71132
ChildPage page = new ChildPage();
@@ -210,6 +271,28 @@ public static class PublicPage {
210271
public WebElement rendered;
211272
}
212273

274+
public static class PageObjectWithStaticAndFinalFields{
275+
@FindBy(name = "q")
276+
public static WebElement staticField;
277+
278+
@FindBy(name = "q")
279+
public final WebElement finalField = mock(WebElement.class);
280+
}
281+
282+
public static class NestedWidget {
283+
284+
@FindBy(name = "q")
285+
public WebElement q;
286+
287+
@FindBy(name = "q")
288+
public List<WebElement> list;
289+
290+
public WebElement rendered;
291+
292+
NestedWidget(WebElement e){
293+
}
294+
}
295+
213296
public static class ChildPage extends PublicPage {
214297

215298
public WebElement submit;

0 commit comments

Comments
 (0)