All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.automationrockstars.gir.ui.part.UiPartProxy Maven / Gradle / Ivy

There is a newer version: 2.0.3
Show newest version
/*
 * 
 */

package com.automationrockstars.gir.ui.part;

import com.automationrockstars.base.ConfigLoader;
import com.automationrockstars.design.gir.webdriver.ByOrder;
import com.automationrockstars.design.gir.webdriver.FilterableSearchContext;
import com.automationrockstars.design.gir.webdriver.HasLocator;
import com.automationrockstars.design.gir.webdriver.UiObject;
import com.automationrockstars.gir.ui.*;
import com.automationrockstars.gir.ui.Optional;
import com.automationrockstars.gir.ui.context.Image;
import com.automationrockstars.gir.ui.context.SearchContextService;
import com.google.common.base.*;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.openqa.selenium.By;
import org.openqa.selenium.By.ById;
import org.openqa.selenium.*;
import org.openqa.selenium.support.pagefactory.ByAll;
import org.openqa.selenium.support.pagefactory.ByChained;
import org.openqa.selenium.support.ui.FluentWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.yandex.qatools.htmlelements.annotations.Timeout;

import java.lang.reflect.*;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

public class UiPartProxy implements InvocationHandler {

    private static final Logger LOG = LoggerFactory.getLogger(UiPart.class);

    private final UiPart ui;

    public UiPartProxy(Class generic) {
        if (generic.getAnnotation(Image.class) != null) {
            ui = new ImageUiPartDelegate(generic);
        } else {
            ui = new WebUiPartDelegate(generic);
        }
    }

    public UiPartProxy(Class generic, UiObject toWrap) {
        if (generic.getAnnotation(Image.class) != null) {
            ui = new ImageUiPartDelegate(generic, toWrap);
        } else {
            ui = new WebUiPartDelegate(generic, toWrap);
        }
    }

    private static boolean isCustom(Method method) {
        Class declaringClass = method.getDeclaringClass();
        List> nativeMethodOwners = Lists.newArrayList(UiPart.class.getInterfaces());
        nativeMethodOwners.add(UiPart.class);
        nativeMethodOwners.add(Object.class);
        nativeMethodOwners.add(WebUiPartDelegate.class);
        nativeMethodOwners.add(WebElement.class);
        nativeMethodOwners.add(TakesScreenshot.class);
        nativeMethodOwners.add(HasLocator.class);
        return !nativeMethodOwners.contains(declaringClass);
    }

    private static int minimumSize(Object target) {
        int result = 0;
        if (target instanceof Method) {
            Method m = (Method) target;
            if (m.getAnnotation(MinimumElements.class) != null) {
                result = m.getAnnotation(MinimumElements.class).value();
            }
        } else if (target instanceof Field) {
            Field f = (Field) target;
            if (f.getAnnotation(MinimumElements.class) != null) {
                result = f.getAnnotation(MinimumElements.class).value();
            }
        }
        return result;
    }

    private static int calculateTimeout(Object target) {
        int defaultTimeout = ConfigLoader.config().getInt(FilterableSearchContext.STUBBORN_WAIT_PARAM, 5);
        if (target instanceof Method) {
            Method tm = (Method) target;
            if (tm.getAnnotation(Optional.class) != null) {
                defaultTimeout = 1;
            }
            if (tm.getAnnotation(Timeout.class) != null) {
                return tm.getAnnotation(Timeout.class).value();
            } else if (tm.getAnnotation(com.automationrockstars.gir.ui.Timeout.class) != null) {
                return tm.getAnnotation(com.automationrockstars.gir.ui.Timeout.class).value();
            } else {
                return defaultTimeout;
            }
        } else if (target instanceof Class) {
            Class tm = (Class) target;
            if (tm.getAnnotation(Optional.class) != null) {
                defaultTimeout = 1;
            }
            if (tm.getAnnotation(Timeout.class) != null) {
                return ((Timeout) tm.getAnnotation(Timeout.class)).value();
            } else if (tm.getAnnotation(com.automationrockstars.gir.ui.Timeout.class) != null) {
                return ((com.automationrockstars.gir.ui.Timeout) tm.getAnnotation(com.automationrockstars.gir.ui.Timeout.class)).value();
            } else {
                return defaultTimeout;
            }
        } else if (target instanceof Field) {
            Field tm = (Field) target;
            if (tm.getAnnotation(Optional.class) != null) {
                defaultTimeout = 1;
            }
            if (tm.getAnnotation(Timeout.class) != null) {
                return ((Timeout) tm.getAnnotation(Timeout.class)).value();
            } else if (tm.getAnnotation(com.automationrockstars.gir.ui.Timeout.class) != null) {
                return ((com.automationrockstars.gir.ui.Timeout) tm.getAnnotation(com.automationrockstars.gir.ui.Timeout.class)).value();
            } else {
                return defaultTimeout;
            }
        } else {
            return ConfigLoader.config().getInt(FilterableSearchContext.STUBBORN_WAIT_PARAM, 5);
        }
    }

    @SuppressWarnings("unchecked")
    private static WebElement decorate(WebElement initial, Class... decorators) {
        WebElement result = initial;
        if (decorators != null) {
            for (Class decorator : Lists.newArrayList(decorators)) {

                try {
                    result = decorator.getConstructor(WebElement.class).newInstance(result);
                } catch (NoSuchMethodException e) {
                    LOG.error("Decoratoe {} does not have constructor acceptin WebElement. Ignoring", decorator);
                } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                        | InvocationTargetException | SecurityException e) {
                    LOG.error("Cannot decorate {} with {} due to {}", initial, decorator, e);

                }
            }
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private static  T convert(WebElement initial, final Class wanted, Class... decorators) {
        initial = decorate(initial, decorators);
        if (UiPart.class.isAssignableFrom(wanted)) {
            Class resulting = (Class) wanted;
            UiObject toWrap = null;
            By locator = UiParts.buildBy(resulting);
            if (HasLocator.class.isAssignableFrom(initial.getClass())) {
                locator = ((HasLocator) initial).getLocator();
            }
            if (initial instanceof UiObject) {
                toWrap = (UiObject) initial;
            } else {
                toWrap = new UiObject(initial, locator);
            }
            return (T) Proxy.newProxyInstance(wanted.getClassLoader(),
                    new Class[]{resulting}, new UiPartProxy(resulting, toWrap));
        }
        if (wanted.isAssignableFrom(initial.getClass())) {
            return (T) initial;
        }
        try {
            if (initial.getClass().equals(EmptyUiObject.class) &&
                    (wanted.equals(ru.yandex.qatools.htmlelements.element.Select.class) || wanted.equals(org.openqa.selenium.support.ui.Select.class))) {
                ((EmptyUiObject) initial).withTagName("select");
            }
            return wanted.getConstructor(WebElement.class).newInstance(initial);
        } catch (Exception ignore) {
            LOG.trace("Cannot do ", ignore);
        }
        return (T) initial;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static Object adjustResults(final List result, final Type type, final Class... decorators) {
        Preconditions.checkState(result.size() > 0, "WebElement not found");
        Class wanted = null;
        if (type instanceof Class) {
            wanted = (Class) type;
        } else {
            if (type instanceof ParameterizedType) {
                wanted = (Class) ((ParameterizedType) type).getRawType();
            }
        }
        if (Collection.class.isAssignableFrom(wanted) ||
                Iterable.class.isAssignableFrom(wanted) ||
                wanted.isArray()) {
            if (type instanceof ParameterizedType) {
                final Class collectionOf = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
                FluentIterable filteredResult = FluentIterable.from(result).transform(new com.google.common.base.Function() {
                    public Object apply(WebElement input) {
                        return convert(input, collectionOf, decorators);
                    }
                });
                if (wanted.equals(FluentIterable.class)) {
                    return filteredResult;
                } else if (wanted.isArray()) {
                    return filteredResult.toArray(collectionOf);
                } else {
                    return filteredResult.toList();
                }
            } else return result;
        } else {
            return convert(result.get(0), wanted, decorators);
        }
    }

    private static FindBy getFindBy(Method method) {
        if (method.getAnnotation(FindBy.class) != null) {
            return method.getAnnotation(FindBy.class);
        } else if (method.getAnnotation(org.openqa.selenium.support.FindBy.class) != null) {
            return FindByAugmenters.translate(method.getAnnotation(org.openqa.selenium.support.FindBy.class));
        } else {
            throw new IllegalArgumentException(String.format("Method %s doesn't have FindBy annotation", method));
        }
    }

    private static FindBy getFindBy(Class clazz) {
        if (clazz.getAnnotation(FindBy.class) != null) {
            return clazz.getAnnotation(FindBy.class);
        } else if (clazz.getAnnotation(org.openqa.selenium.support.FindBy.class) != null) {
            return FindByAugmenters.translate(clazz.getAnnotation(org.openqa.selenium.support.FindBy.class));
        } else {
            throw new IllegalArgumentException(String.format("Class %s doesn't have FindBy annotation", clazz));
        }
    }

    private static org.openqa.selenium.By[] byBuilder(Object host, FindByAugmenter augmenter, org.openqa.selenium.support.FindBy[] locators) {
        List result = Lists.newArrayList();
        for (org.openqa.selenium.support.FindBy locator : locators) {
            result.add(augmenter.augment(uiPartOf(host), FindByAugmenters.translate(locator)));
        }
        return result.toArray(new By[]{By.id("empty")});
    }

    private static org.openqa.selenium.By[] byBuilder(Object host, FindByAugmenter augmenter, FindBy[] locators) {
        List result = Lists.newArrayList();
        for (FindBy locator : locators) {
            result.add(augmenter.augment(uiPartOf(host), locator));
        }

        return result.toArray(new By[]{By.id("empty")});
    }

    private static org.openqa.selenium.By chainedBuilder(Object host, FindByAugmenter augmenter, FindBys locator) {
        return new ByChained(byBuilder(host, augmenter, locator.value()));
    }

    private static org.openqa.selenium.By chainedBuilder(Object host, FindByAugmenter augmenter, org.openqa.selenium.support.FindBys locator) {
        return new ByChained(byBuilder(host, augmenter, locator.value()));
    }

    private static org.openqa.selenium.By allBuilder(Object host, FindByAugmenter augmenter, FindAll locator) {
        return new ByAll(byBuilder(host, augmenter, locator.value()));
    }

    private static org.openqa.selenium.By allBuilder(Object host, FindByAugmenter augmenter, org.openqa.selenium.support.FindAll locator) {
        return new ByAll(byBuilder(host, augmenter, locator.value()));
    }

    private static org.openqa.selenium.By byBuilder(Object host, Class uiPartClass) {
        if (uiPartClass.getAnnotation(WithFindByAugmenter.class) != null) {
            FindByAugmenter augmenter = FindByAugmenters.instance(uiPartClass.getAnnotation(WithFindByAugmenter.class).value());
            if (uiPartClass.getAnnotation(FindAll.class) != null) {
                return allBuilder(host, augmenter, uiPartClass.getAnnotation(FindAll.class));
            } else if ((uiPartClass.getAnnotation(org.openqa.selenium.support.FindAll.class) != null)) {
                return allBuilder(host, augmenter, uiPartClass.getAnnotation(org.openqa.selenium.support.FindAll.class));
            } else if (uiPartClass.getAnnotation(FindBys.class) != null) {
                return chainedBuilder(host, augmenter, uiPartClass.getAnnotation(FindBys.class));
            } else if (uiPartClass.getAnnotation(org.openqa.selenium.support.FindBys.class) != null) {
                return chainedBuilder(host, augmenter, uiPartClass.getAnnotation(org.openqa.selenium.support.FindBys.class));
            }
            return augmenter.augment(uiPartClass, getFindBy(uiPartClass));
        } else return UiParts.buildBy(uiPartClass);
    }

    private static org.openqa.selenium.By byBuilder(Object host, Method uiPartChild) {
        if (uiPartChild.getAnnotation(WithFindByAugmenter.class) != null) {
            FindByAugmenter augmenter = FindByAugmenters.instance(uiPartChild.getAnnotation(WithFindByAugmenter.class).value());
            if (uiPartChild.getAnnotation(FindAll.class) != null) {
                return allBuilder(host, augmenter, uiPartChild.getAnnotation(FindAll.class));
            } else if ((uiPartChild.getAnnotation(org.openqa.selenium.support.FindAll.class) != null)) {
                return allBuilder(host, augmenter, uiPartChild.getAnnotation(org.openqa.selenium.support.FindAll.class));
            } else if (uiPartChild.getAnnotation(FindBys.class) != null) {
                return chainedBuilder(host, augmenter, uiPartChild.getAnnotation(FindBys.class));
            } else if (uiPartChild.getAnnotation(org.openqa.selenium.support.FindBys.class) != null) {
                return chainedBuilder(host, augmenter, uiPartChild.getAnnotation(org.openqa.selenium.support.FindBys.class));
            }
            return augmenter.augment(uiPartOf(host), getFindBy(uiPartChild));
        } else return UiParts.buildBy(uiPartChild);
    }

    private static FluentIterable visible(FluentIterable toFilter) {
        return toFilter.filter(new Predicate() {

            @Override
            public boolean apply(WebElement input) {
                if (input.getTagName().equalsIgnoreCase("body")) {
                    throw new NoSuchElementException(String.format("Usable element covering %s cannot be found", input));
                }
                return FilterableSearchContext.isVisible(input);
            }
        });

    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static Class uiPartOf(Object host) {
        if (host == null) {
            return null;
        }
        return (Class) Iterables.find(Lists.newArrayList(host.getClass().getInterfaces()), new Predicate() {

            @Override
            public boolean apply(Class input) {
                return UiPart.class.isAssignableFrom(input);
            }
        });
    }

    static By buildBy(Object host, Class uiPart) {
        return byBuilder(host, uiPart);
    }

    private List search(UiPart host, org.openqa.selenium.By by, int timeout, int minimumSize) {
        List result;
        if (timeout >= 0 || minimumSize > 0) {
            result = (host).delay()
                    .withTimeout(timeout, TimeUnit.SECONDS)
                    .until(UiParts.allVisible(by, minimumSize));
        } else {
            result = ((UiPart) host).findElements(by);
        }
        LOG.trace("Required size {} actual {}", minimumSize, result.size());
        return result;
    }

    @SuppressWarnings("unchecked")
    private Object invokeCustomMethod(final Object host, Method method, Object[] args) throws Throwable {
        LOG.info("Working on {} inside {}", MoreObjects.firstNonNull((method.getAnnotation(Name.class) == null) ? null : method.getAnnotation(Name.class).value(), method.getName()), host);
        Preconditions.checkArgument(args == null || args.length == 0, "UiPart method cannot accept arguments");
        final Class wantedResult = method.getReturnType();
        org.openqa.selenium.By by = null;

        if (UiPart.class.isAssignableFrom(method.getReturnType())) {
            if (ui instanceof WebUiPartDelegate) {
                ((WebUiPartDelegate) ui).initialPageSetUp();
            }
            final Class resultClass = (Class) method.getReturnType();
            final AtomicInteger order = new AtomicInteger(0);
            By tparentBy = By.tagName("html");
            if (HasLocator.class.isAssignableFrom(host.getClass())) {
                tparentBy = ((HasLocator) host).getLocator();
            }
            final By parentBy = tparentBy;
            List result = FluentIterable.from(Lists.newArrayList(UiParts.get(resultClass)))
                    .transform(new com.google.common.base.Function() {

                        @Override
                        public WebElement apply(UiPart input) {
                            input.setLocator(new ByChained(parentBy, new ByOrder(input.getLocator(), order.getAndIncrement())));
                            return (WebElement) input;
                        }
                    })
                    .toList();
            return adjustResults(result, method.getReturnType(), decorators(uiPartOf(host)));
        } else if (Iterable.class.isAssignableFrom(wantedResult) || wantedResult.isArray()) {
            if (method.getGenericReturnType() instanceof ParameterizedType) {
                final Class collectionOf = (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
                if (UiPart.class.isAssignableFrom(collectionOf)) {
                    if (ui instanceof WebUiPartDelegate) {
                        ((WebUiPartDelegate) ui).initialPageSetUp();
                    }
                    by = byBuilder(host, (Class) collectionOf);
                }
            }
        }
        List result = Lists.newArrayList();
        if (by == null) {
            by = byBuilder(host, method);
        }
        final int timeout = calculateTimeout(method);
        final int minimumSize = minimumSize(method);
        LOG.debug("Using timeout {} to wait for at least {} elements", timeout, minimumSize);
        final boolean visibleOnly = ConfigLoader.config().getBoolean("webdriver.visibleOnly", true);
        if (method.getAnnotation(Covered.class) != null) {
            ConfigLoader.config().setProperty("webdriver.visibleOnly", false);
        }
        try {
            if (method.getAnnotation(Optional.class) != null) {
                if (!Strings.isNullOrEmpty(method.getAnnotation(Optional.class).condition())) {
                    throw new UnsupportedOperationException("condition is for future use");
                }
                FilterableSearchContext.setWait(method.getAnnotation(Optional.class).timeout());
            }
            if (isImage(method)) {
                result = searchByImage((UiPart) host, by, timeout, minimumSize);
            } else {
                result = search((UiPart) host, by, timeout, minimumSize);
            }
            if (result.isEmpty() && method.getAnnotation(Optional.class) != null) {
                result.add(new EmptyUiObject());
            }
        } catch (NoSuchElementException | TimeoutException e) {
            if (method.getAnnotation(Optional.class) != null) {
                result.add(new EmptyUiObject());
            } else {
                LOG.error("Error on {} inside {}: {}", MoreObjects.firstNonNull((method.getAnnotation(Name.class) == null) ? null : method.getAnnotation(Name.class).value(), method.getName()), host, e.toString());
                Throwables.propagate(e);
            }
        } finally {
            ConfigLoader.config().setProperty("webdriver.visibleOnly", visibleOnly);
            FilterableSearchContext.unsetWait();
        }
        if (method.getAnnotation(Covered.class) != null && method.getAnnotation(Covered.class).lookForVisibleParent() && !result.isEmpty() && !UiParts.isEmpty(result.get(0))) {
            result = findVisibleParent(result, visibleOnly);
        } else if (method.getAnnotation(Covered.class) == null) {
            result = Lists.newArrayList(Iterables.filter(result, UiParts.visible()));
        }
        if (result.size() < 1) {
            LOG.error("Error on {} inside {}: NoSuchElement", MoreObjects.firstNonNull((method.getAnnotation(Name.class) == null) ? null : method.getAnnotation(Name.class).value(), method.getName()), host);
            throw new NoSuchElementException("WebElement identified " + by + " not found");
        }

        return adjustResults(setNames(result, method, host), method.getGenericReturnType(), decorators(uiPartOf(host)));

    }

    private List searchByImage(UiPart host, final By by, int timeout, final int minimumSize) {
        SearchContext context = SearchContextService.provideForImage();
        FluentWait delay = host.delay();

        if (!(host.getLocator() instanceof ById && host.getLocator().toString().startsWith("By.id: image:"))) {
            delay = new FluentWait(context);
        }

        return delay.withTimeout(timeout, TimeUnit.SECONDS)

                .withMessage(String.format("Element identified %s not found", by))
                .until(new Function>() {
                    @Override
                    public List apply(SearchContext input) {
                        try {
                            WebElement result = input.findElement(by);
                            return Lists.newArrayList(result);
                        } catch (NoSuchElementException yach) {
                            Throwables.propagate(yach);
                            return Lists.newArrayList();
                        }
                    }
                });


    }

    private boolean isImage(Method method) {
        return method.getAnnotation(Image.class) != null;
    }

    @SuppressWarnings("unused")
    private List setNames(List elements, final Method method, final Object host) {
        String preName = method.getName();
        if (method.getAnnotation(Name.class) != null) {
            preName = method.getAnnotation(Name.class).value();
        } else if (method.getAnnotation(ru.yandex.qatools.htmlelements.annotations.Name.class) != null) {
            preName = method.getAnnotation(ru.yandex.qatools.htmlelements.annotations.Name.class).value();
        }
        final String name = preName;
        SearchContext parent = null;
        if (SearchContext.class.isAssignableFrom(host.getClass())) {
            parent = (SearchContext) host;
        }
        final AtomicInteger order = new AtomicInteger();
        return Lists.newArrayList(Iterables.transform(elements, new com.google.common.base.Function() {
            @Override
            public WebElement apply(WebElement input) {
                WebElement result = input;
                boolean nameSet = false;
                com.google.common.base.Optional setName = Iterables.tryFind(Lists.newArrayList(input.getClass().getMethods()), new Predicate() {
                    @Override
                    public boolean apply(Method input) {
                        return input.getName().equals("setName") && input.getParameterTypes().length == 1 && input.getParameterTypes()[0].equals(String.class);
                    }
                });
                if (setName.isPresent()) {
                    try {
                        setName.get().invoke(result, name);
                        nameSet = true;
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                    }
                }
                By parentBy = By.tagName("html");
                if (HasLocator.class.isAssignableFrom(host.getClass())) {
                    parentBy = ((HasLocator) host).getLocator();
                }
                if (HasLocator.class.isAssignableFrom(input.getClass())) {
                    ((HasLocator) input).setLocator(new ByChained(parentBy, new ByOrder(UiParts.buildBy(method), order.getAndIncrement())));
                }
                if (!nameSet) {
                    result = UiObject.wrap(input, new ByChained(new ByOrder(UiParts.buildBy(method), order.getAndIncrement())), name);
                }
                return result;
            }
        }));
    }

    private List findVisibleParent(final List initial, final boolean visibleOnly) {
        ConfigLoader.config().setProperty("webdriver.visibleOnly", false);

        List previous = initial;
        FluentIterable hidden = FluentIterable.from(previous);
        List result = visible(hidden).toList();

        while (result.isEmpty()) {
            previous = Lists.newArrayList(hidden.toList());
            hidden = FluentIterable.from(previous).transform(new com.google.common.base.Function() {

                public WebElement apply(WebElement input) {
                    return input.findElement(org.openqa.selenium.By.xpath(".."));
                }
            });
            result = visible(hidden).toList();

        }
        ConfigLoader.config().setProperty("webdriver.visibleOnly", visibleOnly);
        return result;
    }

    public Object invoke(Object host, Method method, Object[] args) throws Throwable {
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            if (isCustom(method)) {
                return invokeCustomMethod(host, method, args);
            } else {
                Class[] classes = null;
                if (args != null && args.length > 0) {
                    classes = new Class[args.length];
                    for (int i = 0; i < args.length; i++) {
                        classes[i] = args[i].getClass();
                    }
                }
                try {
                    return method.invoke(ui, args);
                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                }
            }
        } catch (Throwable t) {
            ErrorHandlingService.handle(t, host, method, args);
            Throwables.propagate(t);
            return null;
        } finally {
            if (method.getDeclaringClass() != Object.class) {
                MetricsService.finished(host, method, args, stopwatch.elapsed(TimeUnit.NANOSECONDS));
            }
        }


    }

    private Class[] decorators(Class hostClass) {
        Class[] result = null;
        if (hostClass.getAnnotation(WithDecorators.class) != null)
            result = hostClass.getAnnotation(WithDecorators.class).value();
        return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy