diff --git a/src/main/java/io/github/uchagani/stagehand/FieldDecorator.java b/src/main/java/io/github/uchagani/stagehand/FieldDecorator.java
index b7ee78a..dd262f1 100644
--- a/src/main/java/io/github/uchagani/stagehand/FieldDecorator.java
+++ b/src/main/java/io/github/uchagani/stagehand/FieldDecorator.java
@@ -17,16 +17,9 @@ public FieldDecorator(Page page) {
this.locatorFactory = new LocatorFactory(page);
}
- public Object decorate(ClassLoader loader, Field field) {
- if (!Locator.class.isAssignableFrom(field.getType())) {
- return null;
- }
-
- if (!field.isAnnotationPresent(Find.class)) {
- return null;
- }
-
- Locator locator = locatorFactory.createLocator(field);
+ public Object decorate(Field field, Object pageObjectInstance) {
+ Locator locator = locatorFactory.createLocator(field, pageObjectInstance);
+ ClassLoader loader = pageObjectInstance.getClass().getClassLoader();
return proxyForLocator(loader, locator);
}
diff --git a/src/main/java/io/github/uchagani/stagehand/LocatorFactory.java b/src/main/java/io/github/uchagani/stagehand/LocatorFactory.java
index 00163db..f58f3f0 100644
--- a/src/main/java/io/github/uchagani/stagehand/LocatorFactory.java
+++ b/src/main/java/io/github/uchagani/stagehand/LocatorFactory.java
@@ -5,6 +5,7 @@
import com.microsoft.playwright.Page;
import io.github.uchagani.stagehand.annotations.Find;
import io.github.uchagani.stagehand.annotations.PageObject;
+import io.github.uchagani.stagehand.annotations.Under;
import java.lang.reflect.Field;
@@ -15,13 +16,26 @@ public LocatorFactory(Page page) {
this.page = page;
}
- public Locator createLocator(Field field) {
+ public Locator createLocator(Field field, Object pageObjectInstance) {
Class> clazz = field.getDeclaringClass();
PageObject pageAnnotation = clazz.getAnnotation(PageObject.class);
Find findAnnotation = field.getAnnotation(Find.class);
+ Under underAnnotation = field.getAnnotation(Under.class);
if (pageAnnotation.frame().length == 0) {
- return page.locator(findAnnotation.value());
+ if (underAnnotation == null) {
+ return page.locator(findAnnotation.value());
+ }
+
+ Locator locator;
+ try {
+ Field depField = clazz.getField(underAnnotation.value());
+ locator = (Locator) depField.get(pageObjectInstance);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e.getCause());
+ }
+
+ return locator.locator(findAnnotation.value());
}
FrameLocator frameLocator = page.frameLocator(pageAnnotation.frame()[0]);
diff --git a/src/main/java/io/github/uchagani/stagehand/PageFactory.java b/src/main/java/io/github/uchagani/stagehand/PageFactory.java
index dd7095b..3541299 100644
--- a/src/main/java/io/github/uchagani/stagehand/PageFactory.java
+++ b/src/main/java/io/github/uchagani/stagehand/PageFactory.java
@@ -1,11 +1,18 @@
package io.github.uchagani.stagehand;
+import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
+import io.github.uchagani.stagehand.annotations.Find;
import io.github.uchagani.stagehand.annotations.PageObject;
+import io.github.uchagani.stagehand.annotations.Under;
import io.github.uchagani.stagehand.exeptions.MissingPageObjectAnnotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
public class PageFactory {
public static T create(Class pageToCreate, Page page) {
@@ -13,7 +20,7 @@ public static T create(Class pageToCreate, Page page) {
}
public static void initElements(Object pageObject, Page page) {
- if(pageObject.getClass().isAnnotationPresent(PageObject.class)) {
+ if (pageObject.getClass().isAnnotationPresent(PageObject.class)) {
initElements(new FieldDecorator(page), pageObject);
} else {
throw new MissingPageObjectAnnotation("Only pages marked with @PageObject can can be initialized by the PageFactory.");
@@ -22,13 +29,13 @@ public static void initElements(Object pageObject, Page page) {
private static T instantiatePage(Class pageClassToProxy, Page page) {
try {
- if(pageClassToProxy.isAnnotationPresent(PageObject.class)) {
+ if (pageClassToProxy.isAnnotationPresent(PageObject.class)) {
T pageObjectInstance;
try {
Constructor constructor = pageClassToProxy.getConstructor(Page.class);
pageObjectInstance = constructor.newInstance(page);
} catch (NoSuchMethodException e) {
- pageObjectInstance = pageClassToProxy.getDeclaredConstructor().newInstance();
+ pageObjectInstance = pageClassToProxy.getDeclaredConstructor().newInstance();
}
initElements(new FieldDecorator(page), pageObjectInstance);
return pageObjectInstance;
@@ -41,24 +48,74 @@ private static T instantiatePage(Class pageClassToProxy, Page page) {
}
private static void initElements(FieldDecorator decorator, Object pageObjectInstance) {
- Class> proxyIn = pageObjectInstance.getClass();
- while (proxyIn != Object.class) {
- proxyFields(decorator, pageObjectInstance, proxyIn);
- proxyIn = proxyIn.getSuperclass();
+ Class> pageObjectClass = pageObjectInstance.getClass();
+ while (pageObjectClass != Object.class) {
+ proxyFields(decorator, pageObjectInstance, pageObjectClass);
+ pageObjectClass = pageObjectClass.getSuperclass();
}
}
- private static void proxyFields(FieldDecorator decorator, Object page, Class> proxyIn) {
- Field[] fields = proxyIn.getDeclaredFields();
+ private static void proxyFields(FieldDecorator decorator, Object pageObjectInstance, Class> pageObjectClass) {
+ Field[] fields = pageObjectClass.getDeclaredFields();
+ List fieldNamesAlreadyProxied = new ArrayList<>();
+ List fieldsWithDependencies = new ArrayList<>();
+
for (Field field : fields) {
- Object value = decorator.decorate(page.getClass().getClassLoader(), field);
- if (value != null) {
- try {
- field.setAccessible(true);
- field.set(page, value);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
+ if (isProxyable(field)) {
+ if (hasDependencies(field)) {
+ fieldsWithDependencies.add(field);
+ continue;
}
+ proxyField(decorator, field, pageObjectInstance);
+ fieldNamesAlreadyProxied.add(field.getName());
+ }
+ }
+
+ int sizeBefore;
+ while (!fieldsWithDependencies.isEmpty()) {
+ sizeBefore = fieldsWithDependencies.size();
+ List proxiedScopedFields = new ArrayList<>();
+
+ for (Field field : fieldsWithDependencies) {
+ List dependencyNames = Collections.singletonList(field.getAnnotation(Under.class).value());
+
+ if (fieldNamesAlreadyProxied.containsAll(dependencyNames)) {
+ proxyField(decorator, field, pageObjectInstance);
+ fieldNamesAlreadyProxied.add(field.getName());
+ proxiedScopedFields.add(field);
+ }
+ }
+
+ for (Field proxied : proxiedScopedFields) {
+ fieldsWithDependencies.remove(proxied);
+ }
+
+ if (sizeBefore == fieldsWithDependencies.size()) {
+ throw new RuntimeException("Unable to find dependencies for the following Fields:");
+ }
+ }
+ }
+
+ private static boolean hasDependencies(Field field) {
+ return field.isAnnotationPresent(Under.class);
+ }
+
+ private static boolean isProxyable(Field field) {
+ if (!Locator.class.isAssignableFrom(field.getType())) {
+ return false;
+ }
+
+ return field.isAnnotationPresent(Find.class);
+ }
+
+ private static void proxyField(FieldDecorator decorator, Field field, Object pageObjectInstance) {
+ Object value = decorator.decorate(field, pageObjectInstance);
+ if (value != null) {
+ try {
+ field.setAccessible(true);
+ field.set(pageObjectInstance, value);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
}
}
}
diff --git a/src/main/java/io/github/uchagani/stagehand/annotations/Under.java b/src/main/java/io/github/uchagani/stagehand/annotations/Under.java
new file mode 100644
index 0000000..82e05c5
--- /dev/null
+++ b/src/main/java/io/github/uchagani/stagehand/annotations/Under.java
@@ -0,0 +1,12 @@
+package io.github.uchagani.stagehand.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Under {
+ String value();
+}
diff --git a/src/test/java/io/github/uchagani/stagehand/pages/PageWithNestedIframe.java b/src/test/java/io/github/uchagani/stagehand/pages/PageWithNestedIframe.java
index 6d60f8c..2c70a0d 100644
--- a/src/test/java/io/github/uchagani/stagehand/pages/PageWithNestedIframe.java
+++ b/src/test/java/io/github/uchagani/stagehand/pages/PageWithNestedIframe.java
@@ -6,6 +6,8 @@
@PageObject(frame = {"#parentIframe", "#childIframe"})
public class PageWithNestedIframe {
+
@Find("#child-iframe-paragraph")
public Locator paragraph;
+
}
diff --git a/src/test/java/io/github/uchagani/stagehand/pages/PageWithoutConstructor.java b/src/test/java/io/github/uchagani/stagehand/pages/PageWithoutConstructor.java
index 2e08fd8..1fd6174 100644
--- a/src/test/java/io/github/uchagani/stagehand/pages/PageWithoutConstructor.java
+++ b/src/test/java/io/github/uchagani/stagehand/pages/PageWithoutConstructor.java
@@ -3,6 +3,7 @@
import com.microsoft.playwright.Locator;
import io.github.uchagani.stagehand.annotations.Find;
import io.github.uchagani.stagehand.annotations.PageObject;
+import io.github.uchagani.stagehand.annotations.Under;
@PageObject
public class PageWithoutConstructor {
@@ -12,4 +13,25 @@ public class PageWithoutConstructor {
@Find(".paragraph")
public Locator paragraph;
+ @Find("#firstNameFormDiv")
+ public Locator firstNameFormDiv;
+
+ @Find("form")
+ @Under("firstNameFormDiv")
+ public Locator firstNameForm;
+
+ @Find("input")
+ @Under("firstNameForm")
+ public Locator firstNameInput;
+
+ @Find("#lastNameFormDiv")
+ public Locator lastNameFormDiv;
+
+ @Find("form")
+ @Under("lastNameFormDiv")
+ public Locator lastNameForm;
+
+ @Find("input")
+ @Under("lastNameForm")
+ public Locator lastNameInput;
}
diff --git a/src/test/java/io/github/uchagani/stagehand/tests/HTMLConstants.java b/src/test/java/io/github/uchagani/stagehand/tests/HTMLConstants.java
index d3338b5..0f5acb9 100644
--- a/src/test/java/io/github/uchagani/stagehand/tests/HTMLConstants.java
+++ b/src/test/java/io/github/uchagani/stagehand/tests/HTMLConstants.java
@@ -1,6 +1,6 @@
package io.github.uchagani.stagehand.tests;
public class HTMLConstants {
- public static final String SIMPLE_HTML = "This is a paragraph.
";
+ public static final String SIMPLE_HTML = "This is a paragraph.
";
public static final String IFRAME_HTML = "
";
}
diff --git a/src/test/java/io/github/uchagani/stagehand/tests/PageFactoryTests.java b/src/test/java/io/github/uchagani/stagehand/tests/PageFactoryTests.java
index dec7271..7b63160 100644
--- a/src/test/java/io/github/uchagani/stagehand/tests/PageFactoryTests.java
+++ b/src/test/java/io/github/uchagani/stagehand/tests/PageFactoryTests.java
@@ -53,6 +53,23 @@ public void create_canInitializeFields_inClassWithAConstructor() {
assertThat(homePage.paragraph.textContent()).isEqualTo("This is a paragraph.");
}
+ @Test
+ public void create_canInitialize_dependentFields() {
+ PageWithoutConstructor homePage = PageFactory.create(PageWithoutConstructor.class, page);
+
+ assertThat(homePage.firstNameInput.getAttribute("placeholder")).isEqualTo("First Name");
+ assertThat(homePage.lastNameInput.getAttribute("placeholder")).isEqualTo("Last Name");
+ }
+
+ @Test
+ public void initElements_canInitialize_dependentFields() {
+ PageWithoutConstructor homePage = new PageWithoutConstructor();
+ PageFactory.initElements(homePage, page);
+
+ assertThat(homePage.firstNameInput.getAttribute("placeholder")).isEqualTo("First Name");
+ assertThat(homePage.lastNameInput.getAttribute("placeholder")).isEqualTo("Last Name");
+ }
+
@Test
public void initElements_canInitializeFieldsMarkedWithFindAnnotation() {
PageWithoutConstructor homePage = new PageWithoutConstructor();