0

I'm attempting to write a proxy for RemoteWebDriver to add custom logging for methods such as findElement etc. As an experiment I found a TimingHandler that just provides a start/stop time stamp for methods - it works fine outside of selenium. However, I can't get it to work with RemoteWebDriver.

Questions: Is there something about RemoteWebDriver which prevents the use of a proxy? Am I implementing this incorrectly? Is there a different/better way to do this?

public class TimingDynamicInvocationHandler implements InvocationHandler {

  private static final Logger logger = LogManager.getLogger(TimingDynamicInvocationHandler.class.getName());

  private final Map<String, Method> methods = new HashMap<>();

  private Object target;

  public TimingDynamicInvocationHandler(Object target) {
      this.target = target;

      for(Method method: target.getClass().getDeclaredMethods()) {
        System.out.println("TDIHandler method: " + method.getName());
          this.methods.put(method.getName(), method);
      }
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) 
    throws Throwable {
      System.out.println("invoke method: " + method.getName());
      long start = System.nanoTime();
      Object result = methods.get(method.getName()).invoke(target, args);
      long elapsed = System.nanoTime() - start;

      logger.info("Executing {} finished in {} ns", method.getName(), 
        elapsed);

      return result;
  }
}

I am using TestNG and I have a BaseTest class, all tests extend from this. I use a beforeMethod which creates the driver. Without the proxy I do the following and it works as expected:

 driver = new RemoteWebDriver(new URL(HUB_URL), caps);
 driver.get(APP_URL);
 driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

However when I change to use the proxy it hangs:

    RemoteWebDriver rrDriver = new RemoteWebDriver(new URL(HUB_URL), caps);
    TimingDynamicInvocationHandler handler = new TimingDynamicInvocationHandler(rrDriver);
    Class<?> proxyClass = Proxy.getProxyClass(WebDriver.class.getClassLoader(), WebDriver.class);
    driver = (WebDriver) proxyClass.getConstructor(TimingDynamicInvocationHandler.class).newInstance(handler);

I get the following output:

12:06:06.941 [main] ERROR BaseTest 175 beforeMethod - java.lang.NoSuchMethodException: com.sun.proxy.$Proxy22.<init>(com.timr.utils.aop.TimingDynamicInvocationHandler)
mancocapac
  • 812
  • 6
  • 23

1 Answers1

0

It believe RemoteWebDriver is not a good candidate for dynamic proxy as it is already part of the CompoundHandler found in the selenium client org.openqa.selenium.remote.Augmenter.java, see : CompoundHandler implements InvocationHandler.

In its place I have opted for a simpler solution extending each of the Driver classes, RemoteWebDriver, ChromeDriver, FirefoxDriver.

public class ChromeLoggingDriver extends ChromeDriver {
  private static final Logger log = LogManager.getLogger(ChromeLoggingDriver.class.getName());

  public ChromeLoggingDriver(ChromeOptions options) {
    super(options);
  } 

  @Override
  public void get(String url) {
    log.info("url: " + url);
    super.get(url);
  }

  @Override
  public String getCurrentUrl() {
    String url = super.getCurrentUrl();
    log.info(url);
    return url;
  }

  @Override
  public String getTitle() {
    String title = super.getTitle();
    log.info(title);
    return title;
  }

  @Override
  public List<WebElement> findElements(By by) {
    log.info(by.toString());
    return super.findElements(by);
  }

  @Override
  public WebElement findElement(By by) {
    log.info(by.toString());
    return super.findElement(by);
  }

  // ... 

  @Override
  public void close() {
    log.info("");
    super.close();
  }

  @Override
  public void quit() {
    log.info("");
    super.quit();
  }
}
mancocapac
  • 812
  • 6
  • 23
  • This is not the correct answer. As it turns out drivers such as FirefoxDriver, ChromeDriver extend from RemoteWebDriver which in turn implements WebDriver along with lots of other Interfaces: WebDriver, JavascriptExecutor, etc. Typical use is WebDriver driver = new FirefoxDriver(...), this works because at some point the driver is upcast back to a RemoteWebDriver. I found this out when trying to cast my WebDriverLogger to a JavaScriptExecutor - which fails. so I am still looking for a way to provide custom logging. – mancocapac Aug 09 '18 at 05:23
  • I modified the above code to create 3 separate logging drivers, they extend FirefoxDriver, ChromeDriver, RemoteDriver respectively. There is a lot of repeat code in this solution, if someone has a better idea please let me know. – mancocapac Aug 09 '18 at 20:30