Skip to content

Commit

Permalink
SimonStewart: Adding the ability to augment a remote webdriver instan…
Browse files Browse the repository at this point in the history
…ce at run time. This is considered experimental.

git-svn-id: http://selenium.googlecode.com/svn/trunk@8657 07704840-8298-11de-bf8c-fd130f914ac9
  • Loading branch information
simon.m.stewart committed Apr 8, 2010
1 parent 9022a6b commit 093c045
Show file tree
Hide file tree
Showing 15 changed files with 653 additions and 24 deletions.
11 changes: 4 additions & 7 deletions common/test/java/org/openqa/selenium/SingleTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class SingleTestSuite extends TestCase {
private final static String SELENIUM = "org.openqa.selenium.SeleneseBackedWebDriver";

public static Test suite() throws Exception {
String driver = REMOTE;
String driver = REMOTE_IE;

System.setProperty("webdriver.development", "true");
System.setProperty("jna.library.path", "..\\build;build");
Expand All @@ -51,15 +51,12 @@ public static Test suite() throws Exception {

TestSuiteBuilder builder = new TestSuiteBuilder()
.addSourceDir("common")
.addSourceDir("jobbie")
.addSourceDir("remote/server")
.usingDriver(driver)
.keepDriverInstance()
.includeJavascriptTests()
.onlyRun("ExecutingJavascriptTest")
.method("testShouldBeAbleToExecuteSimpleJavascriptAndReturnAnArray")
// .method("testShouldBeAbleToExecuteSimpleJavascriptAndAStringsArray")
// .method("testShouldBeAbleToExecuteScriptAndReturnElementsList")
// .method("testShouldBeAbleToExecuteSimpleJavascriptAndReturnAWebElement")
.onlyRun("RemoteWebDriverTest")
.method("testCanAugmentWebDriverInstanceIfNecessary")
.exclude(ALL)
.exclude(Ignore.Driver.REMOTE)
.outputTestNames()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
limitations under the License.
*/

package org.openqa.selenium.support.events;
package org.openqa.selenium;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright 2007-2010 WebDriver committers
Copyright 2007-2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.openqa.selenium.remote;

import java.lang.reflect.Method;

import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;

// Deliberately package level visibility
class AddTakesScreenshot implements AugmenterProvider {

public Class<?> getDescribedInterface() {
return TakesScreenshot.class;
}

public InterfaceImplementation getImplementation(Object ignored) {
// The only method on TakesScreenshot is the one to take a screenshot
return new InterfaceImplementation() {
public Object invoke(ExecuteMethod executeMethod, Method method, Object... args) {
String base64 = (String) executeMethod.execute(DriverCommand.SCREENSHOT, null);
return ((OutputType<?>) args[0]).convertFromBase64Png(base64);
}
};
}
}
179 changes: 179 additions & 0 deletions remote/client/src/java/org/openqa/selenium/remote/Augmenter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
Copyright 2007-2010 WebDriver committers
Copyright 2007-2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.openqa.selenium.remote;

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.Map;
import java.util.Set;

import org.openqa.selenium.WebDriver;

/**
* Enhance the interfaces implemented by an instance of the
* {@link org.openqa.selenium.remote.RemoteWebDriver} based on the returned
* {@link org.openqa.selenium.remote.Capabilities} of the driver.
*
* Note: this class is still experimental. Use at your own risk.
*/
public class Augmenter {
private final Map<String, AugmenterProvider> augmentors =
new HashMap<String, AugmenterProvider>();

public Augmenter() {
addAugmentation(CapabilityType.TAKES_SCREENSHOT, new AddTakesScreenshot());
}

/**
* Add a mapping between a capability name and the implementation of the
* interface that name represents. For example
* (@link CapabilityType#TAKES_SCREENSHOT} is represents the interface
* {@link org.openqa.selenium.TakesScreenshot}, which is implemented via the
* {@link org.openqa.selenium.remote.AddTakesScreenshot} provider.
*
* Note: This method is still experimental. Use at your own risk.
*
* @param capabilityName The name of the capability to model
* @param handlerClass The provider of the interface and implementation
*/
public void addAugmentation(String capabilityName, AugmenterProvider handlerClass) {
augmentors.put(capabilityName, handlerClass);
}

/**
* Enhance the interfaces implemented by this instance of WebDriver iff that
* instance is a {@link org.openqa.selenium.remote.RemoteWebDriver}.
*
* The WebDriver that is returned may well be a dynamic proxy. You cannot
* rely on the concrete implementing class to remain constant.
*
* @param driver The driver to enhance
* @return A class implementing the described interfaces.
*/
public WebDriver augment(WebDriver driver) {
// TODO(simon): We should really add a "SelfDescribing" interface for this
if (!(driver instanceof RemoteWebDriver)) {
return driver;
}

Map<String,Object> capabilities = ((RemoteWebDriver) driver).getCapabilities().asMap();

CompoundHandler handler = new CompoundHandler((RemoteWebDriver) driver);

for (Map.Entry<String, Object> capablityName : capabilities.entrySet()) {
AugmenterProvider augmenter = augmentors.get(capablityName.getKey());
if (augmenter == null) {
continue;
}

Object value = capablityName.getValue();
if (value instanceof Boolean && !((Boolean) value).booleanValue()) {
continue;
}

handler.addCapabilityHander(augmenter.getDescribedInterface(),
augmenter.getImplementation(value));
}

if (handler.isNeedingApplication()) {
// Gather the existing interfaces
Set<Class<?>> interfaces = new HashSet<Class<?>>();
interfaces.addAll(handler.getInterfaces());
interfaces.addAll(getInterfacesFrom(driver.getClass()));

return (WebDriver) Proxy.newProxyInstance(getClass().getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]), handler);
}

return driver;
}

private Set<Class<?>> getInterfacesFrom(Class<?> clazz) {
Set<Class<?>> toReturn = new HashSet<Class<?>>();

if (clazz == null || Object.class.equals(clazz)) {
return toReturn;
}

Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> face : interfaces) {
toReturn.add(face);
toReturn.addAll(getInterfacesFrom(face));
}
toReturn.addAll(getInterfacesFrom(clazz.getSuperclass()));

return toReturn;
}

private class CompoundHandler implements InvocationHandler {
private Map<Method, InterfaceImplementation> handlers =
new HashMap<Method, InterfaceImplementation>();
private Set<Class<?>> interfaces = new HashSet<Class<?>>();
private final RemoteWebDriver driver;

private CompoundHandler(RemoteWebDriver driver) {
this.driver = driver;
}

public void addCapabilityHander(Class<?> fromInterface, InterfaceImplementation handledBy) {
interfaces.add(fromInterface);
for (Method method : fromInterface.getDeclaredMethods()) {
handlers.put(method, handledBy);
}
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
InterfaceImplementation handler = handlers.get(method);

try {
if (handler == null) {
return method.invoke(driver, args);
} else {
return handler.invoke(new ExecuteMethod(driver), method, args);
}
} catch (InvocationTargetException e) {
throw unwrapException(e);
}
}

private Throwable unwrapException(Throwable e) {
Throwable cause = e.getCause();
if (cause == null) {
return e;
}

if (cause.getClass().getName().startsWith("java.lang.reflect")) {
return unwrapException(cause);
}

return cause;
}

public Set<Class<?>> getInterfaces() {
return interfaces;
}

public boolean isNeedingApplication() {
return interfaces.size() > 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
Copyright 2007-2010 WebDriver committers
Copyright 2007-2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.openqa.selenium.remote;

/**
* Describes and provides an implementation for a particular interface for use
* with the {@link org.openqa.selenium.remote.Augmenter}. Think of this as a
* simulacrum of mixins.
*/
public interface AugmenterProvider {
/**
* @return The interface that this augmentor describes.
*/
Class<?> getDescribedInterface();

/**
* For the interface that this provider describes, return an implementation.
*
* @param value The value from the capability map
* @return An interface implementation
*/
InterfaceImplementation getImplementation(Object value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2007-2010 WebDriver committers
Copyright 2007-2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.openqa.selenium.remote;

import java.util.Map;

/**
* An encapsulation of
* {@link org.openqa.selenium.remote.RemoteWebDriver#executeScript(String, Object...)}.
*/
public class ExecuteMethod {
private final RemoteWebDriver driver;

public ExecuteMethod(RemoteWebDriver driver) {
this.driver = driver;
}

/**
* Execute the given command on the remote webdriver server. Any exceptions
* will be thrown by the underlying execute method.
*
* @param commandName The remote command to execute
* @param parameters The parameters to execute that command with
* @return The result of {@link Response#getValue()}.
*/
public Object execute(DriverCommand commandName, Map<String, Object> parameters) {
Response response;

if (parameters == null || parameters.size() == 0) {
response = driver.execute(commandName);
} else {
response = driver.execute(commandName, parameters);
}

return response.getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2007-2010 WebDriver committers
Copyright 2007-2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.openqa.selenium.remote;

import java.lang.reflect.Method;

/**
* An implementation of a particular interface, used by the
* {@link org.openqa.selenium.remote.Augmenter}.
*/
public interface InterfaceImplementation {
/**
* Called when it has become apparent that this is the right interface to
* implement a particular method.
*
* @param executeMethod Call this to actually call the remote instance
* @param method The method invoked by the user
* @param args The arguments to the method
* @return The return value, which will be passed to the user directly.
*/
Object invoke(ExecuteMethod executeMethod, Method method, Object... args);
}
Loading

0 comments on commit 093c045

Please sign in to comment.