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

com.nordstrom.automation.selenium.model.ContainerMethodInterceptor Maven / Gradle / Ivy

Go to download

Selenium Foundation is an automation framework designed to extend and enhance the capabilities provided by Selenium (WebDriver).

There is a newer version: 28.3.1-s4
Show newest version
package com.nordstrom.automation.selenium.model;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.Callable;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nordstrom.automation.selenium.AbstractSeleniumConfig.WaitType;
import com.nordstrom.automation.selenium.exceptions.ContainerVacatedException;
import com.nordstrom.automation.selenium.exceptions.PageLoadRendererTimeoutException;
import com.nordstrom.automation.selenium.exceptions.PageNotLoadedException;
import com.nordstrom.automation.selenium.exceptions.TransitionErrorException;
import com.nordstrom.automation.selenium.exceptions.VacationStackTrace;
import com.nordstrom.automation.selenium.interfaces.DetectsLoadCompletion;
import com.nordstrom.automation.selenium.interfaces.TransitionErrorDetector;
import com.nordstrom.automation.selenium.model.Page.WindowState;
import com.nordstrom.automation.selenium.support.Coordinator;
import com.nordstrom.automation.selenium.support.Coordinators;
import com.nordstrom.common.base.ExceptionUnwrapper;

import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;

/**
 * This enumeration implements the method interceptor for Selenium Foundation component container objects.
 * This interceptor is implemented as a standard Java enumeration singleton and performs the following tasks:
 * 
    *
  • Block calls to objects that have been superseded (vacated) by prior actions.
  • *
  • Switch driver focus to the window/frame associated with the target object.
  • *
  • If informed that actions of the invoked method will cause the associated window to close:
      *
    • Wait for the window to close.
    • *
    • If the target object was spawned by another object, switch focus to this object...
    • *
    • ... otherwise, switch focus to the first window in the driver's collection.
    • *
    • Mark the target object as vacated to block further method calls.
    • *
  • *
  • If the invoked method returns a new container object:
      *
    • If the new object is a page:
        *
      • If the page object is associated with a new window, wait for the window to appear...
      • *
      • ... otherwise, mark the target object as vacated to block further method calls.
      • *
    • *
    • Wait for browser to rebuild its DOM.
    • *
    • Create an "enhanced" version of the new container object, which installs the interceptor.
    • *
    • If the new object is a page, verify that the browser has landed on the expected URL.
    • *
  • *
  • Return the result of the invoked method.
  • *
*/ public enum ContainerMethodInterceptor { INSTANCE; private static final ThreadLocal DEPTH = new InheritableThreadLocal() { /** * {@inheritDoc} */ @Override protected Integer initialValue() { return 0; } }; private static final ThreadLocal TARGET = new InheritableThreadLocal<>(); private static final ServiceLoader errorDetectorLoader = ServiceLoader.load(TransitionErrorDetector.class); private static final String RENDERER_TIMEOUT_MESSAGE = "receiving message from renderer"; /** * This is the method that intercepts component container methods in "enhanced" model objects. * * @param obj "enhanced" object upon which the method was invoked * @param method {@link Method} object for the invoked method * @param args method invocation arguments * @param proxy call-able proxy for the intercepted method * @return {@code anything} (the result of invoking the intercepted method) * @throws Exception {@code anything} (exception thrown by the intercepted method) */ @RuntimeType public Object intercept(@This final Object obj, @Origin final Method method, @AllArguments final Object[] args, @SuperCall final Callable proxy) throws Throwable { if (!(obj instanceof ComponentContainer)) { return proxy.call(); } increaseDepth(); long initialTime = System.currentTimeMillis(); ComponentContainer container = (ComponentContainer) obj; try { if (container.isVacated()) { throw new ContainerVacatedException(container.getVacated()); } WebDriver driver = container.getWrappedDriver(); if (TARGET.get() != container) { container.switchTo(); TARGET.set(container); } WebElement reference = null; Class returnType = method.getReturnType(); Page parentPage = container.getParentPage(); Set initialHandles = null; try { // get initial set of window handles initialHandles = driver.getWindowHandles(); } catch (WebDriverException eaten) { // framework errors are forbidden } boolean returnsContainer = ComponentContainer.class.isAssignableFrom(returnType); boolean returnsPage = Page.class.isAssignableFrom(returnType) && !Frame.class.isAssignableFrom(returnType); boolean detectsCompletion = returnsContainer && DetectsLoadCompletion.class.isAssignableFrom(returnType); // if expecting page without load logic if (returnsPage && !detectsCompletion) { try { // get stale wait reference element by CSS selector reference = driver.findElement(By.cssSelector("*")); } catch (WebDriverException e) { // get stale wait reference element by XPath reference = driver.findElement(By.xpath("/*")); } } // invoke intercepted method Object result = proxy.call(); // if result is container, we're done if (result == container) return result; // if expecting parent page window to close if (parentPage.getWindowState() == WindowState.WILL_CLOSE) { // wait until parent page window closes WaitType.WAIT.getWait(driver).until(Coordinators.windowIsClosed(parentPage.getWindowHandle())); // get grandparent page parentPage = parentPage.getSpawningPage(); // if grandparent found if (parentPage != null) { // switch page context parentPage.switchTo(); // record page context TARGET.set(parentPage); // otherwise (parent is a root page) } else { // get first available window handle String windowHandle = driver.getWindowHandles().iterator().next(); // switch driver context to this window driver.switchTo().window(windowHandle); // clear context TARGET.set(null); } // vacate container, recording associated method, stack trace, and reason container.setVacated(new VacationStackTrace(method, "Target window for this container has closed.")); // no stale wait reference = null; } // if container spec'd if (returnsContainer) { // ensure that a non-null result was returned Objects.requireNonNull(result, "A method that returns container objects cannot produce a null result"); String newHandle = null; ComponentContainer newChild = (ComponentContainer) result; // if page spec'd if (returnsPage) { Page newPage = (Page) result; // if expecting window to open for new page if (newPage.getWindowState() == WindowState.WILL_OPEN) { // wait until new window opens newHandle = WaitType.WAIT.getWait(driver).until(Coordinators.newWindowIsOpened(initialHandles)); // record parent of new page newPage.setSpawningPage(parentPage); // no stale wait reference = null; // otherwise (new page for existing window) } else { // vacate container, recording associated method, stack trace, and reason container.setVacated( new VacationStackTrace(method, "This page object no longer owns its target window.")); try { // get current driver context newHandle = driver.getWindowHandle(); } catch (WebDriverException eaten) { // framework errors are forbidden } } } // enhance new container result = newChild.enhanceContainer(newChild); // if context acquired if (newHandle != null) { // associate page with target window ((Page) result).setWindowHandle(newHandle); // wait for expected landing page to appear ComponentContainer.waitForLandingPage((Page) result); } // if load logic spec'd if (detectsCompletion) { // wait until load logic indicates completion ((ComponentContainer) result).getWait(WaitType.PAGE_LOAD) .ignoring(PageNotLoadedException.class) .until(loadIsComplete()); // otherwise, if stale wait } else if (reference != null) { // wait until reference element goes stale WaitType.PAGE_LOAD.getWait((ComponentContainer) result).until(loadIsComplete(reference)); } } return result; } catch (Throwable t) { Throwable thrown = ExceptionUnwrapper.unwrap(t); if (thrown instanceof TimeoutException) { thrown = differentiateTimeout((TimeoutException) thrown); } throw thrown; } finally { int level = decreaseDepth(); long interval = System.currentTimeMillis() - initialTime; if (level == 0) { container.getLogger().info("[{}] {} ({}ms)", level, method.getName(), interval); } else { container.getLogger().debug("[{}] {} ({}ms)", level, method.getName(), interval); } } } /** * Increment intercept depth counter * * @return updated depth count */ private static int increaseDepth() { return adjustDepth(1); } /** * Decrement intercept depth counter * * @return updated depth count */ private static int decreaseDepth() { return adjustDepth(-1); } /** * Apply the specified delta to intercept depth counter * * @param delta depth counter delta * @return updated depth count */ private static int adjustDepth(final int delta) { int i = DEPTH.get() + delta; DEPTH.set(i); return i; } /** * Differentiate browser renderer timeouts * * @param e undifferentiated timeout exception * @return differentiated timeout exception */ private static TimeoutException differentiateTimeout(TimeoutException e) { if (e.getClass().equals(TimeoutException.class)) { String m = e.getMessage(); if ((m != null) && m.contains(RENDERER_TIMEOUT_MESSAGE)) { TimeoutException d = new PageLoadRendererTimeoutException(m, e.getCause()); d.setStackTrace(e.getStackTrace()); return d; } } return e; } /** * Returns a 'wait' proxy that determines if the container has finished loading. * * @return 'true' if the container has finished loading; otherwise 'false' */ public static Coordinator loadIsComplete() { return new Coordinator() { @Override public Boolean apply(final SearchContext context) { scanForErrors(context); if (context instanceof DetectsLoadCompletion) { return ((DetectsLoadCompletion) context).isLoadComplete(); } else { Logger logger = LoggerFactory.getLogger(Enhanceable.getContainerClass(context)); logger.warn("This context doesn't provide load completion status"); return true; } } @Override public String toString() { return "container to finish loading"; } }; } /** * Returns a 'wait' proxy that determines if the container has finished loading. * * @param element the element to wait for * @return 'true' if the container has finished loading; otherwise 'false' */ public static Coordinator loadIsComplete(final WebElement element) { return new Coordinator() { private final Coordinator stalenessOfElement = Coordinators.stalenessOf(element); @Override public Boolean apply(final SearchContext context) { scanForErrors(context); return stalenessOfElement.apply(null); } @Override public String toString() { return "container to finish loading"; } }; } /** * Notify registered {@link TransitionErrorDetector} service providers to perform a scan for errors. *

* NOTE: The error scan is only performed if the specified search context is a {@link ComponentContainer}. * * @param context search context to scan for errors */ static void scanForErrors(SearchContext context) { if (context instanceof ComponentContainer) { synchronized(errorDetectorLoader) { for (TransitionErrorDetector detector : errorDetectorLoader) { String message = detector.scanForErrors((ComponentContainer) context); if (message != null) { throw new TransitionErrorException((ComponentContainer) context, message); } } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy