Class WebDriverDecorator

  • Direct Known Subclasses:
    EventFiringDecorator

    @Beta
    public class WebDriverDecorator
    extends java.lang.Object
    This class helps to create decorators for instances of WebDriver and derived objects, such as WebElements and Alert, that can extend or modify their "regular" behavior. It provides a flexible alternative to subclassing WebDriver.

    Here is a general usage pattern:

    1. implement a subclass of WebDriverDecorator that adds something to WebDriver behavior:
      
       public class MyWebDriverDecorator extends WebDriverDecorator { ... }
           
      (see below for details)
    2. use a decorator instance to decorate a WebDriver object:
      
       WebDriver original = new FirefoxDriver();
       WebDriver decorated = new MyWebDriverDecorator().decorate(original);
           
    3. use the decorated WebDriver instead of the original one:
      
       decorated.get("http://example.com/");
       ...
       decorated.quit();
          
    By subclassing WebDriverDecorator you can define what code should be executed
    • before executing a method of the underlying object,
    • after executing a method of the underlying object,
    • instead of executing a method of the underlying object,
    • when an exception is thrown by a method of the underlying object.
    The same decorator is used under the hood to decorate all the objects derived from the underlying WebDriver instance. For example, decorated.findElement(someLocator) automatically decorates the returned WebElement.

    Instances created by the decorator implement all the same interfaces as the original objects.

    When you implement a decorator there are two main options (that can be used both separately and together):

    Let's consider both approaches by examples.

    One of the most widely used decorator examples is a logging decorator. In this case we want to add the same piece of logging code before and after each invoked method:

    
       public class LoggingDecorator extends WebDriverDecorator {
         final Logger logger = LoggerFactory.getLogger(Thread.currentThread().getName());
    
         @Override
         public void beforeCall(Decorated<?> target, Method method, Object[] args) {
           logger.debug("before {}.{}({})", target, method, args);
         }
         @Override
         public void afterCall(Decorated<?> target, Method method, Object[] args, Object res) {
           logger.debug("after {}.{}({}) => {}", target, method, args, res);
         }
       }
     
    For the second example let's implement a decorator that implicitly waits for an element to be visible before any click or sendKeys method call.
    
       public class ImplicitlyWaitingDecorator extends WebDriverDecorator {
         private WebDriverWait wait;
    
         @Override
         public Decorated<WebDriver> createDecorated(WebDriver driver) {
           wait = new WebDriverWait(driver, Duration.ofSeconds(10));
           return super.createDecorated(driver);
         }
         @Override
         public Decorated<WebElement> createDecorated(WebElement original) {
           return new DefaultDecorated<>(original, this) {
             @Override
             public void beforeCall(Method method, Object[] args) {
               String methodName = method.getName();
               if ("click".equals(methodName) || "sendKeys".equals(methodName)) {
                 wait.until(d -> getOriginal().isDisplayed());
               }
             }
           };
         }
       }
     
    This class is not a pure decorator, it allows to not only add new behavior but also replace "normal" behavior of a WebDriver or derived objects.

    Let's suppose you want to use JavaScript-powered clicks instead of normal ones (yes, this allows to interact with invisible elements, it's a bad practice in general but sometimes it's inevitable). This behavior change can be achieved with the following "decorator":

    
       public class JavaScriptPoweredDecorator extends WebDriverDecorator {
         @Override
         public Decorated<WebElement> createDecorated(WebElement original) {
           return new DefaultDecorated<>(original, this) {
             @Override
             public Object call(Method method, Object[] args) throws Throwable {
               String methodName = method.getName();
               if ("click".equals(methodName)) {
                 JavascriptExecutor executor = (JavascriptExecutor) getDecoratedDriver().getOriginal();
                 executor.executeScript("arguments[0].click()", getOriginal());
                 return null;
               } else {
                 return super.call(method, args);
               }
             }
           };
         }
       }
     
    It is possible to apply multiple decorators to compose behaviors added by each of them. For example, if you want to log method calls and implicitly wait for elements visibility you can use two decorators:
    
       WebDriver original = new FirefoxDriver();
       WebDriver decorated =
         new ImplicitlyWaitingDecorator().decorate(
           new LoggingDecorator().decorate(original));