Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Make decorated drivers and elements implementing the wrapping interfaces
  • Loading branch information
shs96c committed Jun 28, 2021
1 parent 1e3cc6b commit 4798b00
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 15 deletions.
1 change: 1 addition & 0 deletions java/client/src/org/openqa/selenium/WrapsDriver.java
Expand Up @@ -21,6 +21,7 @@
* This interface indicates that the implementing class knows about the driver that contains it and
* can export it.
*/
@FunctionalInterface
public interface WrapsDriver {
/**
* @return The driver that contains this element.
Expand Down
1 change: 1 addition & 0 deletions java/client/src/org/openqa/selenium/WrapsElement.java
Expand Up @@ -20,6 +20,7 @@
/**
* Indicates that there is an underlying element that can be used
*/
@FunctionalInterface
public interface WrapsElement {
WebElement getWrappedElement();
}
Expand Up @@ -21,15 +21,19 @@
import org.openqa.selenium.Beta;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsDriver;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.internal.Require;
import org.openqa.selenium.virtualauthenticator.VirtualAuthenticator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -230,9 +234,11 @@ public Object call(Decorated<?> target, Method method, Object[] args) throws Thr

public void afterCall(Decorated<?> target, Method method, Object[] args, Object res) {}

public Object onError(Decorated<?> target, Method method, Object[] args,
InvocationTargetException e) throws Throwable
{
public Object onError(
Decorated<?> target,
Method method,
Object[] args,
InvocationTargetException e) throws Throwable {
throw e.getTargetException();
}

Expand Down Expand Up @@ -275,6 +281,7 @@ private Object decorateResult(Object toDecorate) {
protected final <Z> Z createProxy(final Decorated<Z> decorated) {
Set<Class<?>> decoratedInterfaces = extractInterfaces(decorated);
Set<Class<?>> originalInterfaces = extractInterfaces(decorated.getOriginal());
Map<Class<?>, InvocationHandler> derivedInterfaces = deriveAdditionalInterfaces(decorated.getOriginal());

final InvocationHandler handler = (proxy, method, args) -> {
try {
Expand All @@ -288,6 +295,10 @@ protected final <Z> Z createProxy(final Decorated<Z> decorated) {
decorated.afterCall(method, result, args);
return result;
}
if (derivedInterfaces.containsKey(method.getDeclaringClass())) {
return derivedInterfaces.get(method.getDeclaringClass()).invoke(proxy, method, args);
}

return method.invoke(decorated.getOriginal(), args);
} catch (InvocationTargetException e) {
return decorated.onError(method, e, args);
Expand All @@ -297,6 +308,7 @@ protected final <Z> Z createProxy(final Decorated<Z> decorated) {
Set<Class<?>> allInterfaces = new HashSet<>();
allInterfaces.addAll(decoratedInterfaces);
allInterfaces.addAll(originalInterfaces);
allInterfaces.addAll(derivedInterfaces.keySet());
Class<?>[] allInterfacesArray = allInterfaces.toArray(new Class<?>[0]);

return (Z) Proxy.newProxyInstance(
Expand Down Expand Up @@ -329,4 +341,47 @@ private static void extractInterfaces(final Set<Class<?>> collector, final Class
}
extractInterfaces(collector, clazz.getSuperclass());
}

private Map<Class<?>, InvocationHandler> deriveAdditionalInterfaces(Object object) {
Map<Class<?>, InvocationHandler> handlers = new HashMap<>();

if (object instanceof WebDriver && !(object instanceof WrapsDriver)) {
handlers.put(WrapsDriver.class, (proxy, method, args) -> {
if ("getWrappedDriver".equals(method.getName())) {
return object;
}
throw new UnsupportedOperationException(method.getName());
});
}

if (object instanceof WebElement && !(object instanceof WrapsElement)) {
handlers.put(WrapsElement.class, (proxy, method, args) -> {
if ("getWrappedElement".equals(method.getName())) {
return object;
}
throw new UnsupportedOperationException(method.getName());
});
}

try {
Method toJson = object.getClass().getDeclaredMethod("toJson");
toJson.setAccessible(true);

handlers.put(JsonSerializer.class, ((proxy, method, args) -> {
if ("toJson".equals(method.getName())) {
return toJson.invoke(object);
}
throw new UnsupportedOperationException(method.getName());
}));
} catch (NoSuchMethodException e) {
// Fine. Just fall through
}

return handlers;
}

@FunctionalInterface
interface JsonSerializer {
Object toJson();
}
}
Expand Up @@ -11,6 +11,7 @@ java_test_suite(
),
deps = [
"//java/client/src/org/openqa/selenium:core",
"//java/client/src/org/openqa/selenium/json",
"//java/client/src/org/openqa/selenium/remote",
"//java/client/src/org/openqa/selenium/support/decorators",
"//java/client/test/org/openqa/selenium/testing:annotations",
Expand Down
Expand Up @@ -17,29 +17,29 @@

package org.openqa.selenium.support.decorators;

import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsDriver;
import org.openqa.selenium.WrapsElement;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.remote.Dialect;
import org.openqa.selenium.remote.IsRemoteWebDriver;
import org.openqa.selenium.remote.IsRemoteWebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.remote.internal.WebElementToJsonConverter;
import org.openqa.selenium.testing.UnitTests;

import java.util.Map;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.openqa.selenium.remote.Dialect.OSS;
import static org.openqa.selenium.json.Json.MAP_TYPE;

@Category(UnitTests.class)
public class DecoratedRemoteWebDriverTest {
Expand All @@ -55,13 +55,38 @@ public void canConvertDecoratedToRemoteWebDriverInterface() {
assertThat(decoratedDriver.getSessionId()).isEqualTo(sessionId);
}

@Test(expected = ClassCastException.class)
@Test
public void cannotConvertDecoratedToRemoteWebDriver() {
SessionId sessionId = new SessionId(UUID.randomUUID());
RemoteWebDriver originalDriver = mock(RemoteWebDriver.class);
when(originalDriver.getSessionId()).thenReturn(sessionId);

RemoteWebDriver decoratedDriver = (RemoteWebDriver) new WebDriverDecorator().decorate(originalDriver);
WebDriver decorated = new WebDriverDecorator().decorate(originalDriver);

assertThat(decorated).isNotInstanceOf(RemoteWebDriver.class);
}

@Test
public void decoratedDriversShouldImplementWrapsDriver() {
RemoteWebDriver originalDriver = mock(RemoteWebDriver.class);

WebDriver decorated = new WebDriverDecorator().decorate(originalDriver);

assertThat(decorated).isInstanceOf(WrapsDriver.class);
}

@Test
public void decoratedElementsShouldImplementWrapsElement() {
RemoteWebDriver originalDriver = mock(RemoteWebDriver.class);
RemoteWebElement originalElement = new RemoteWebElement();
String elementId = UUID.randomUUID().toString();
originalElement.setParent(originalDriver);
originalElement.setId(elementId);

when(originalDriver.findElement(any())).thenReturn(originalElement);

WebDriver decoratedDriver = new WebDriverDecorator().decorate(originalDriver);
WebElement element = decoratedDriver.findElement(By.id("test"));

assertThat(element).isInstanceOf(WrapsElement.class);
}

@Test
Expand All @@ -77,10 +102,12 @@ public void canConvertDecoratedRemoteWebElementToJson() {
WebDriver decoratedDriver = new WebDriverDecorator().decorate(originalDriver);

WebElement element = decoratedDriver.findElement(By.id("test"));
WebElementToJsonConverter converter = new WebElementToJsonConverter();
ImmutableMap<String, String> result = (ImmutableMap<String, String>) converter.apply(element);

assertThat(result.get(Dialect.OSS.getEncodedElementKey())).isEqualTo(elementId);
Json json = new Json();
String raw = json.toJson(element);
Map<String, String> result = json.toType(raw, MAP_TYPE);

assertThat(result.get(Dialect.W3C.getEncodedElementKey())).isEqualTo(elementId);
}

}

0 comments on commit 4798b00

Please sign in to comment.