Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow access to PhantomJS API from WebDriver. #262

Merged
merged 1 commit into from
Sep 10, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class PhantomJSCommandExecutor extends HttpCommandExecutor {
* @param service The PhantomJSDriverService to send commands to.
*/
public PhantomJSCommandExecutor(PhantomJSDriverService service) {
super(service.getUrl());
super(PhantomJSDriver.getCustomCommands(), service.getUrl());
this.service = service;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@

package org.openqa.selenium.phantomjs;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.service.DriverCommandExecutor;
import org.openqa.selenium.remote.service.DriverService;
import org.openqa.selenium.remote.*;
import org.openqa.selenium.remote.internal.WebElementToJsonConverter;

import java.util.HashMap;
import java.util.Map;

/**
* A {@link org.openqa.selenium.WebDriver} implementation that controls a PhantomJS running in Remote WebDriver mode.
Expand Down Expand Up @@ -103,8 +106,8 @@ public PhantomJSDriver(Capabilities desiredCapabilities) {
* @param service The service to use.
* @param desiredCapabilities The capabilities required from PhantomJS/GhostDriver.
*/
public PhantomJSDriver(DriverService service, Capabilities desiredCapabilities) {
super(new DriverCommandExecutor(service), desiredCapabilities);
public PhantomJSDriver(PhantomJSDriverService service, Capabilities desiredCapabilities) {
super(new PhantomJSCommandExecutor(service), desiredCapabilities);
}

/**
Expand All @@ -120,4 +123,49 @@ public <X> X getScreenshotAs(OutputType<X> target) throws WebDriverException {
String base64 = (String) execute(DriverCommand.SCREENSHOT).getValue();
return target.convertFromBase64Png(base64);
}

/**
* Execute a PhantomJS fragment. Provides extra functionality not found in WebDriver
* but available in PhantomJS.
* <p/>
* See the <a href="https://github.com/ariya/phantomjs/wiki/API-Reference">PhantomJS API<</a>
* for details on what is available.
* <p/>
* A 'page' variable pointing to currently selected page is available for use.
* If there is no page yet, one is created.
* <p/>
* When overriding any callbacks be sure to wrap in a try/catch block, as failures
* may cause future WebDriver calls to fail.
* <p/>
* Certain callbacks are used by GhostDriver (the PhantomJS WebDriver implementation)
* already. Overriding these may cause the script to fail. It's a good idea to check
* for existing callbacks before overriding.
*
* @param script The fragment of PhantomJS JavaScript to execute.
* @param args List of arguments to pass to the function that the script is wrapped in.
* These can accessed in the script as 'arguments[0]', 'arguments[1]',
* 'arguments[2]', etc
* @return The result of the evaluation.
*/
public Object executePhantomJS(String script, Object... args) {
script = script.replaceAll("\"", "\\\"");

Iterable<Object> convertedArgs = Iterables.transform(
Lists.newArrayList(args), new WebElementToJsonConverter());
Map<String, ?> params = ImmutableMap.of(
"script", script, "args", Lists.newArrayList(convertedArgs));

return execute(COMMAND_EXECUTE_PHANTOM_SCRIPT, params).getValue();
}

private static final String COMMAND_EXECUTE_PHANTOM_SCRIPT = "executePhantomScript";

protected static Map<String, CommandInfo> getCustomCommands() {
Map<String, CommandInfo> customCommands = new HashMap<String, CommandInfo>();

customCommands.put(COMMAND_EXECUTE_PHANTOM_SCRIPT,
new CommandInfo("/session/:sessionId/phantom/execute", HttpVerb.POST));

return customCommands;
}
}
18 changes: 16 additions & 2 deletions src/request_handlers/session_request_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ ghostdriver.SessionReqHand = function(session) {
CLICK : "click",
BUTTON_DOWN : "buttondown",
BUTTON_UP : "buttonup",
DOUBLE_CLICK : "doubleclick"
DOUBLE_CLICK : "doubleclick",
PHANTOM_DIR : "/phantom/",
PHANTOM_EXEC : "execute",
};

var
Expand Down Expand Up @@ -133,7 +135,7 @@ ghostdriver.SessionReqHand = function(session) {
} else if (req.urlParsed.file === _const.REFRESH && req.method === "POST") {
_refreshCommand(req, res);
return;
} else if (req.urlParsed.file === _const.EXECUTE && req.method === "POST") {
} else if (req.urlParsed.file === _const.EXECUTE && req.urlParsed.directory === "/" && req.method == "POST") {
_executeCommand(req, res);
return;
} else if (req.urlParsed.file === _const.EXECUTE_ASYNC && req.method === "POST") {
Expand All @@ -157,6 +159,9 @@ ghostdriver.SessionReqHand = function(session) {
} else if (req.urlParsed.file === _const.MOVE_TO && req.method === "POST") {
_postMouseMoveToCommand(req, res);
return;
} else if (req.urlParsed.file === _const.PHANTOM_EXEC && req.urlParsed.directory === _const.PHANTOM_DIR && req.method === "POST") {
_executePhantomJS(req, res);
return;
} else if (req.urlParsed.file === _const.CLICK && req.method === "POST") {
_postMouseClickCommand(req, res, "click");
return;
Expand Down Expand Up @@ -837,6 +842,15 @@ ghostdriver.SessionReqHand = function(session) {
res.success(_session.getId(), _protoParent.getSessionCurrWindow.call(this, _session, req).title);
};

_executePhantomJS = function(req, res) {
var params = JSON.parse(req.post);
if (typeof(params) === "object" && params.script && params.args) {
res.success(_session.getId(), _session.executePhantomJS(_protoParent.getSessionCurrWindow.call(this, _session, req), params.script, params.args));
} else {
throw _errors.createInvalidReqMissingCommandParameterEH(req);
}
};

// public:
return {
handle : _handle,
Expand Down
10 changes: 10 additions & 0 deletions src/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,15 @@ ghostdriver.Session = function(desiredCapabilities) {
_setTimeout(_const.TIMEOUT_NAMES.PAGE_LOAD, ms);
},

_executePhantomJS = function(page, script, args) {
try {
var code = new Function(script);
return code.apply(page, args);
} catch (e) {
return e;
}
},

_aboutToDelete = function() {
var k;

Expand Down Expand Up @@ -519,6 +528,7 @@ ghostdriver.Session = function(desiredCapabilities) {
getAsyncScriptTimeout : _getAsyncScriptTimeout,
getImplicitTimeout : _getImplicitTimeout,
getPageLoadTimeout : _getPageLoadTimeout,
executePhantomJS : _executePhantomJS,
timeoutNames : _const.TIMEOUT_NAMES,
isLoading : _isLoading
};
Expand Down
49 changes: 49 additions & 0 deletions test/java/src/test/java/ghostdriver/PhantomJSCommandTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ghostdriver;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.phantomjs.PhantomJSDriver;

import static junit.framework.TestCase.assertEquals;

public class PhantomJSCommandTest extends BaseTest {
@Test
public void executePhantomJS() {
WebDriver d = getDriver();
if (!(d instanceof PhantomJSDriver)) {
// Skip this test if not using PhantomJS.
// The command under test is only available when using PhantomJS
return;
}

PhantomJSDriver phantom = (PhantomJSDriver)d;

// Do we get results back?
Object result = phantom.executePhantomJS("return 1 + 1");
assertEquals(new Long(2), (Long)result);

// Can we read arguments?
result = phantom.executePhantomJS("return arguments[0] + arguments[0]", new Long(1));
assertEquals(new Long(2), (Long)result);

// Can we override some browser JavaScript functions in the page context?
result = phantom.executePhantomJS("var page = this;" +
"page.onInitialized = function () { " +
"page.evaluate(function () { " +
"Math.random = function() { return 42 / 100 } " +
"})" +
"}");

phantom.get("http://ariya.github.com/js/random/");

WebElement numbers = phantom.findElement(By.id("numbers"));
boolean foundAtLeastOne = false;
for(String number : numbers.getText().split(" ")) {
foundAtLeastOne = true;
assertEquals("42", number);
}
assert(foundAtLeastOne);
}
}