package com.redheap.selenium.component;
import com.redheap.selenium.errors.SubIdNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import oracle.adf.view.rich.automation.selenium.RichWebDrivers;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.RemoteWebElement;
public class AdfComponent /*extends BaseObject*/ {
private final WebDriver driver;
private final String clientid;
private final WebElement element;
private long timeoutMillisecs = DFLT_WAIT_TIMEOUT_MSECS;
public static final long DFLT_WAIT_TIMEOUT_MSECS = 30000;
private static final Logger logger = Logger.getLogger(AdfComponent.class.getName());
private static final ServiceLoader<ComponentFactory> factories = ServiceLoader.load(ComponentFactory.class);
private static final Map<String, Class<? extends AdfComponent>> defaultComponents = new HashMap<>();
protected static final String JS_FIND_COMPONENT = "var comp=AdfPage.PAGE.findComponentByAbsoluteId(arguments[0]);";
protected static final String JS_FIND_PEER = JS_FIND_COMPONENT + "var peer=comp.getPeer(); peer.bind(comp);";
protected static final String JS_FIND_ELEMENT = JS_FIND_PEER + "var elem=peer.getDomElement();";
private static final String JS_GET_ABSOLUTE_LOCATOR = JS_FIND_COMPONENT + "return comp.getAbsoluteLocator()";
private static final String JS_GET_COMPONENT_TYPE = JS_FIND_COMPONENT + "return comp.getComponentType()";
private static final String JS_FIND_ANCESTOR_COMPONENT =
"return AdfRichUIPeer.getFirstAncestorComponent(arguments[0]).getClientId();";
private static final String JS_FIND_RELATIVE_COMPONENT_CLIENTID =
JS_FIND_COMPONENT + "child=comp.findComponent(arguments[1],true); return child?child.getClientId():null;";
private static final String JS_FIND_COMPONENT_BY_LOCATOR =
"var comp=AdfPage.PAGE.findComponentByAbsoluteLocator(arguments[0]); return [comp.getComponentType(),comp.getClientId()]";
private static final String JS_GET_DESCENDANT_COMPONENTS =
JS_FIND_COMPONENT + "var desc=comp.getDescendantComponents();" + "var retval=[];" +
"desc.forEach(function(comp){retval.push([comp.getClientId(),comp.getComponentType()]);});" + "return retval;";
private static final String JS_FIND_CHILD_COMPONENTS =
JS_FIND_COMPONENT + "var retval=[];" +
"comp.visitChildren(function(comp){retval.push([comp.getClientId(),comp.getComponentType()]);return 1});" +
"return retval;";
private static final String JS_GET_PROPERTY_KEYS =
"return Object.getOwnPropertyNames(" + JS_FIND_COMPONENT + "return comp.getPropertyKeys());";
private static final String JS_GET_PROPERTY = JS_FIND_COMPONENT + "return comp.getProperty(arguments[1])";
private static final String JS_SCROLLINTOVIEW_FOCUS_SUBTARGET =
JS_FIND_COMPONENT + "comp.scrollIntoView(arguments[1], arguments[2])";
private static final String JS_SCROLLINTOVIEW_FOCUS = JS_FIND_COMPONENT + "comp.scrollIntoView(arguments[1]);";
private static final String JS_SCROLLINTOVIEW_SUBTARGET = JS_FIND_COMPONENT + "comp.scrollIntoView(arguments[1]);";
private static final String JS_SCROLLINTOVIEW = JS_FIND_COMPONENT + "comp.scrollIntoView();";
private static final String JS_FIND_CONTENT_NODE =
JS_FIND_ELEMENT + "return AdfDhtmlEditableValuePeer.GetContentNode(comp, elem);";
private static final String JS_FIND_SUBID_ELEMENT =
JS_FIND_PEER + "return peer.getSubIdDomElement(comp,arguments[1]);";
private static final String JS_FIND_SUBID_CLIENTID =
JS_FIND_PEER +
"var subelem=peer.getSubIdDomElement(comp,arguments[1]); if(!subelem){return null;}return AdfRichUIPeer.getFirstAncestorComponent(subelem).getClientId();";
private static final String JS_GET_DISABLED = JS_FIND_COMPONENT + "return comp.getDisabled();";
private static final String JS_IS_NAMINGCONTAINER =
JS_FIND_COMPONENT + "return AdfUIComponent.__isNamingContainer(comp.constructor);";
private static final Pattern RELATIVE_ID = Pattern.compile("(:*)(.+)");
protected AdfComponent(WebDriver driver, String clientid) {
this.driver = driver;
this.clientid = clientid;
final List<WebElement> elems = driver.findElements(By.id(clientid));
// some components do not have element (like dynamic declarative component)
// TODO: why not lazy resolving in getElement?
this.element = (elems != null && elems.size() == 1) ? elems.get(0) : null;
}
public static <C extends AdfComponent> C forClientId(WebDriver driver, String clientid) {
// find component type
String type = (String) ((JavascriptExecutor) driver).executeScript(JS_GET_COMPONENT_TYPE, clientid);
return forClientId(driver, type, clientid);
}
private static <C extends AdfComponent> C forClientId(WebDriver driver, String componentType, String clientid) {
// instantiate default component or one from ServiceLoader
Iterator<ComponentFactory> iter = factories.iterator();
if (iter.hasNext()) {
final List<WebElement> elems = driver.findElements(By.id(clientid));
// some components do not have element (like dynamic declarative component)
WebElement element = (elems != null && elems.size() == 1) ? elems.get(0) : null;
while (iter.hasNext()) {
ComponentFactory factory = iter.next();
final C component = factory.createComponent(driver, componentType, clientid, element);
if (component != null) {
return component;
}
}
}
// none found from serviceloaders so try to use our defaults
if (!defaultComponents.containsKey(componentType)) {
// no class loaded yet for this type
// determing java class from javascript type:
// oracle.adf.RichInputText to com.redheap.selenium.component.AdfInputText
String simpleType = componentType.substring(componentType.lastIndexOf('.') + ".Rich".length()); // InputText
String className = AdfComponent.class.getPackage().getName() + ".Adf" + simpleType;
final Class<? extends C> c;
try {
c = (Class<? extends C>) ClassUtils.getClass(className);
defaultComponents.put(componentType, c); // remember class to prevent class loading each time
} catch (ClassNotFoundException e) {
defaultComponents.put(componentType, null); // no class found and remember this
}
}
Class<? extends C> cls = (Class<? extends C>) defaultComponents.get(componentType);
if (cls == null) {
throw new UnsupportedOperationException("unknown component type: " + componentType);
}
try {
return ConstructorUtils.invokeConstructor(cls, driver, clientid);
} catch (Exception e) {
throw new WebDriverException("unable to instantiate adf component: " + clientid,
e.getCause() != null ? e.getCause() : e);
}
}
public static <T extends AdfComponent> T forElement(WebElement element) {
RemoteWebElement rwe = (RemoteWebElement) element;
RemoteWebDriver rwd = (RemoteWebDriver) rwe.getWrappedDriver();
String clientid = (String) rwd.executeScript(JS_FIND_ANCESTOR_COMPONENT, element);
return forClientId(rwd, clientid);
}
public void setTimout(long milliseconds) {
this.timeoutMillisecs = milliseconds;
}
protected Object executeScript(CharSequence script, Object... args) {
JavascriptExecutor jsrunner = (JavascriptExecutor) driver;
logger.finer("executing script '" + script + "' with params " + Arrays.asList(args));
Object result = jsrunner.executeScript(script.toString(), args);
logger.finer("executed script returned: " + result +
(result == null ? "" : String.format(" (%s)", result.getClass())));
return result;
}
/**
* Makes wrapped RemoteWebElement availabl for subclasses.
* @return RemoteWebElement supplied to the constructor.
*/
public WebElement getElement() {
return element;
}
public WebDriver getDriver() {
return driver;
}
public <T extends AdfComponent> T findAdfComponent(String relativeClientId) {
String clientid = (String) executeScript(JS_FIND_RELATIVE_COMPONENT_CLIENTID, getClientId(), relativeClientId);
if (clientid == null) {
return null;
}
return AdfComponent.forClientId(driver, clientid);
}
public <T extends AdfComponent> T findAdfComponentByLocator(String relativeLocator) {
// use logic similar to relativeClientId:
// - when starting with :, the locator is absolute and starts from root of page
// - when starting with multiple :, first traverse up the naming containers
// - when not starting with :, traverse up to nearest naming container (which chould be this component itself)
// - can use indices with relative lookups:
// * lookup "[3]it2" from an af:table
// * lookup "::[3]it2" from tab1[1]:it2
// TODO: refactor or duplicate this to com.redheap.selenium.AdfFinder to implement org.openqa.selenium.By
Matcher matcher = RELATIVE_ID.matcher(relativeLocator);
if (!matcher.matches()) {
throw new IllegalArgumentException("illegal locator: " + relativeLocator);
}
int qualifiers = matcher.group(1).length();
String searchScopedId = matcher.group(2);
boolean relative = (qualifiers == 0 || qualifiers > 1);
String absoluteLocator;
if (!relative) {
absoluteLocator = searchScopedId;
} else {
String base = getAbsoluteLocator();
// traverse up based on number of : prefixes in relative locator
int upcount;
if (qualifiers > 1) {
upcount = qualifiers - 1;
} else {
// when base is not a NamingContainer we need to go up one first to get to the NamingContainer
upcount = isNamingContainer() ? 0 : 1;
}
while (upcount > 0) {
base = base.substring(0, base.lastIndexOf(":"));
upcount--;
}
if (searchScopedId.startsWith("[")) {
// indexed locator like [3]it4
if (base.endsWith("]")) {
// strip index from base
base = base.substring(0, base.lastIndexOf("["));
}
absoluteLocator = base + searchScopedId; // no need for : separator
} else {
absoluteLocator = base + ":" + searchScopedId;
}
}
logger.fine("locating component by absolute locator " + absoluteLocator);
List<?> jsresult = (List<?>) executeScript(JS_FIND_COMPONENT_BY_LOCATOR, absoluteLocator);
String componentType = (String) jsresult.get(0);
String clientid = (String) jsresult.get(1);
return AdfComponent.forClientId(driver, componentType, clientid);
}
public String getId() {
// simple (local) id
return getElement().getAttribute("id");
}
public String getClientId() {
return clientid;
}
public String getAbsoluteLocator() {
return (String) executeScript(JS_GET_ABSOLUTE_LOCATOR, getClientId());
}
public boolean isDisplayed() {
return getElement().isDisplayed();
}
private boolean isNamingContainer() {
return Boolean.TRUE.equals(executeScript(JS_IS_NAMINGCONTAINER, getClientId()));
}
public void click() {
WebElement element = getElement();
logger.fine("clicking " + element);
element.click();
waitForPpr();
}
public void clickWithDialogDetect() {
WebElement element = getElement();
logger.fine("clicking " + element);
element.click();
waitForPprWithDialogDetect();
}
public void contextClick() {
new Actions(driver).contextClick(getElement()).perform();
waitForPpr();
}
public void doubleClick() {
new Actions(driver).doubleClick(getElement()).perform();
waitForPpr();
}
public void moveMouseTo() {
new Actions(driver).moveToElement(getElement()).perform();
waitForPpr();
}
public void dragAndDropTo(AdfComponent target) {
dragAndDropTo(target.getElement());
waitForPpr();
}
public void dragAndDropTo(WebElement target) {
new Actions(driver).clickAndHold(this.getElement()).moveToElement(target).release(target).perform();
waitForPpr();
}
public List<ComponentReference> getDescendantComponents() {
return buildReferences((List<List<String>>) executeScript(JS_GET_DESCENDANT_COMPONENTS, getClientId()));
}
public List<ComponentReference> getChildComponents() {
return buildReferences((List<List<String>>) executeScript(JS_FIND_CHILD_COMPONENTS, getClientId()));
}
public boolean isDisabled() {
return (Boolean) executeScript(JS_GET_DISABLED, getClientId());
}
public List<String> getPropertyKeys() {
return (List<String>) executeScript(JS_GET_PROPERTY_KEYS, getClientId());
}
public Object getProperty(String propName) {
return executeScript(JS_GET_PROPERTY, getClientId(), propName);
}
public void scrollIntoView(boolean focus, String subTargetId) {
executeScript(JS_SCROLLINTOVIEW_FOCUS_SUBTARGET, getClientId(), focus, subTargetId);
}
public void scrollIntoView(boolean focus) {
executeScript(JS_SCROLLINTOVIEW_FOCUS, getClientId(), focus);
}
public void scrollIntoView(String subTargetId) {
executeScript(JS_SCROLLINTOVIEW_SUBTARGET, getClientId(), subTargetId);
}
public void scrollIntoView() {
executeScript(JS_SCROLLINTOVIEW, getClientId());
}
protected void scrollIntoView(WebElement element) {
executeScript("arguments[0].scrollIntoView()", element);
}
protected List<ComponentReference> buildReferences(List<List<String>> jsResult) {
List<ComponentReference> retval = new ArrayList<ComponentReference>(jsResult.size());
for (List<String> comp : jsResult) {
retval.add(new ComponentReference(comp.get(0), comp.get(1)));
}
return retval;
}
protected WebElement findContentNode() {
final Object result = executeScript(JS_FIND_CONTENT_NODE, getClientId());
if (result instanceof WebElement) {
return (WebElement) result;
} else {
throw new SubIdNotFoundException("could not find content node for " + getElement());
}
}
protected WebElement findSubIdElement(String subid) {
return (WebElement) executeScript(JS_FIND_SUBID_ELEMENT, getClientId(), subid);
}
protected List<WebElement> findSubIdElements(String subid) {
return (List<WebElement>) executeScript(JS_FIND_SUBID_ELEMENT, getClientId(), subid);
}
protected <T extends AdfComponent> T findSubIdComponent(String subid) {
final String subClientId = (String) executeScript(JS_FIND_SUBID_CLIENTID, getClientId(), subid);
if (subClientId == null) {
return null;
}
if (getClientId().equals(subClientId)) {
WebElement elem = findSubIdElement(subid);
throw new SubIdNotFoundException("subid " + subid + " for " + getElement() +
" does not point to a component but to sub-element " +
(elem == null ? "unknown" : elem.getTagName()));
}
return AdfComponent.forClientId(driver, subClientId);
}
protected void waitForPpr() {
RichWebDrivers.waitForServer(driver, timeoutMillisecs);
}
protected void waitForPprWithDialogDetect() {
RichWebDrivers.waitForServer(driver, timeoutMillisecs, true);
}
protected boolean isPlatform(Platform p) {
return ((RemoteWebDriver) driver).getCapabilities().getPlatform().is(p);
}
public void hover() {
WebElement element = getElement();
// we use pause to give javascript timer to detect hover and show popup
new Actions(getDriver()).moveToElement(element).pause(1000).perform();
waitForPpr();
}
@Override
public String toString() {
return new ToStringBuilder(this).append("clientid", clientid).build();
}
}