SpectrumPage.java
package io.github.giulong.spectrum;
import io.github.giulong.spectrum.interfaces.Endpoint;
import io.github.giulong.spectrum.interfaces.JsWebElement;
import io.github.giulong.spectrum.internals.page_factory.SpectrumFieldDecorator;
import io.github.giulong.spectrum.utils.Reflections;
import io.github.giulong.spectrum.utils.js.JsWebElementListInvocationHandler;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.pagefactory.AjaxElementLocatorFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.util.List;
@Slf4j
@Getter
public abstract class SpectrumPage<T extends SpectrumPage<T, Data>, Data> extends SpectrumEntity<T, Data> {
@SuppressWarnings("unused")
private String endpoint;
/**
* Opens the web page at the URL made by the concatenation of the {@code baseUrl} provided in the {@code configuration.yaml}
* and the value of the {@code @Endpoint} annotation on the calling SpectrumPage.
* It also calls the {@link SpectrumPage#waitForPageLoading()} waitForPageLoading} before returning
*
* @return the calling SpectrumPage instance
*/
@SuppressWarnings("unchecked")
public T open() {
final String url = configuration.getApplication().getBaseUrl() + endpoint;
log.info("Opening {}", url);
driver.get(url);
waitForPageLoading();
return (T) this;
}
/**
* This is a method that by default just logs a warning. If you need to check for custom conditions before considering
* a page fully loaded, you should override this method, so that calling {@link SpectrumPage#open() open}
* on pages will call your implementation automatically
*
* @return the calling SpectrumPage instance
*/
@SuppressWarnings("unchecked")
public T waitForPageLoading() {
log.debug("Default no-op waitForPageLoading: override this method in your SpectrumPage!");
return (T) this;
}
/**
* Checks whether the SpectrumPage instance on which this is called is fully loaded
*
* @return true if the SpectrumPage is loaded
*/
public boolean isLoaded() {
final String currentUrl = driver.getCurrentUrl();
final String pageUrl = String.format("%s/%s", configuration.getApplication().getBaseUrl(), endpoint.replaceFirst("/", ""));
log.debug("Current url: {}", currentUrl);
log.debug("Page url: {}", pageUrl);
return pageUrl.equals(currentUrl);
}
SpectrumPage<?, Data> init() {
final String className = getClass().getSimpleName();
log.debug("Injecting already resolved fields into an instance of {}", className);
final Endpoint endpointAnnotation = getClass().getAnnotation(Endpoint.class);
final String endpointValue = endpointAnnotation != null ? endpointAnnotation.value() : "";
log.debug("The endpoint of page '{}' is '{}'", className, endpointValue);
Reflections.setField("endpoint", this, endpointValue);
final Duration autoWaitDuration = configuration.getDrivers().getWaits().getAuto().getTimeout();
PageFactory.initElements(new SpectrumFieldDecorator(new AjaxElementLocatorFactory(driver, (int) autoWaitDuration.toSeconds())), this);
Reflections
.getAnnotatedFields(this, JsWebElement.class)
.forEach(this::injectJsWebElementProxyInto);
return this;
}
@SneakyThrows
void injectJsWebElementProxyInto(final Field field) {
final Object value = field.get(this);
if (value instanceof List<?>) {
log.debug("Field {} is a list. Cannot build proxy eagerly", field.getName());
@SuppressWarnings("unchecked") final Object webElementProxy = Proxy.newProxyInstance(
List.class.getClassLoader(),
new Class<?>[]{List.class},
JsWebElementListInvocationHandler
.builder()
.jsWebElementProxyBuilder(jsWebElementProxyBuilder)
.webElements((List<WebElement>) value)
.build());
field.set(this, webElementProxy);
return;
}
field.set(this, jsWebElementProxyBuilder.buildFor(value));
}
}