Embedding GhostDriver into PhantomJS(!!!)

Finally. After so much work, this is finally a reality.
To launch PhantomJS in "Remote WebDriver mode":

```bash
$ phantomjs --webdriver=OPTIONAL_IP:OPTIONAL_PORT
```

Also, GhostDriver brings along support for Selenium Grid: now PhantomJS can register itself to a Selenium Grid HUB.
Just launch it in Webdriver Mode with the following extra options:
```bash
$ phantomjs --webdriver=OPTIONAL_IP:OPTIONAL_PORT --webdriver-selenium-grid-hub=http://url.to.selenium.grid.hub:port
```

http://code.google.com/p/phantomjs/issues/detail?id=49
1.8
Ivan De Marino 2012-11-18 23:58:21 +00:00 committed by Ariya Hidayat
parent 2dcccc8968
commit ffa9fab316
7 changed files with 135 additions and 12 deletions

View File

@ -193,7 +193,7 @@ phantom.onError = phantom.defaultErrorHandler;
var paths = [], dir;
if (request[0] === '.') {
paths.push(fs.absolute(joinPath(this.dirname, request)));
paths.push(fs.absolute(joinPath(phantom.webdriverMode ? ":/ghostdriver" : this.dirname, request)));
} else if (request[0] === '/') {
paths.push(fs.absolute(request));
} else {
@ -284,8 +284,11 @@ phantom.onError = phantom.defaultErrorHandler;
cwd = fs.absolute(phantom.libraryPath);
mainFilename = joinPath(cwd, basename(require('system').args[0]) || 'repl');
mainModule._setFilename(mainFilename);
// include CoffeeScript which takes care of adding .coffee extension
require('_coffee-script');
// include CoffeeScript which takes care of adding .coffee extension (only if not in Webdriver mode)
if (!phantom.webdriverMode) {
require('_coffee-script');
}
}());
}());

View File

@ -63,6 +63,8 @@ static const struct QCommandLineConfigEntry flags[] =
{ QCommandLine::Option, '\0', "script-encoding", "Sets the encoding used for the starting script, default is 'utf8'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "web-security", "Enables web security, 'yes' (default) or 'no'", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "ssl-protocol", "Sets the SSL protocol (supported protocols: 'SSLv3' (default), 'SSLv2', 'TLSv1', 'any')", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "webdriver", "Starts in 'Remote WebDriver mode' (embedded GhostDriver): '[[<IP>:]<PORT>]' (default '127.0.0.1:8910') ", QCommandLine::Optional },
{ QCommandLine::Option, '\0', "webdriver-selenium-grid-hub", "URL to the Selenium Grid HUB: 'URL_TO_HUB' (default 'none') (NOTE: works only together with '--webdriver') ", QCommandLine::Optional },
{ QCommandLine::Param, '\0', "script", "Script", QCommandLine::Flags(QCommandLine::Optional|QCommandLine::ParameterFence)},
{ QCommandLine::Param, '\0', "argument", "Script argument", QCommandLine::OptionalMultiple },
{ QCommandLine::Switch, 'h', "help", "Shows this message and quits", QCommandLine::Optional },
@ -99,6 +101,20 @@ void Config::processArgs(const QStringList &args)
m_cmdLine->setArguments(args);
m_cmdLine->setConfig(flags);
m_cmdLine->parse();
// Inject command line parameters to be picked up by GhostDriver
if (isWebdriverMode()) {
QStringList argsForGhostDriver;
m_scriptFile = "main.js"; //< launch script
argsForGhostDriver << m_webdriver; //< ip:port
if (!m_seleniumGridHub.isEmpty()) {
argsForGhostDriver << m_seleniumGridHub; //< selenium grid url
}
// Clear current args and override with those
setScriptArgs(argsForGhostDriver);
}
}
void Config::loadJsonFile(const QString &filePath)
@ -424,6 +440,48 @@ bool Config::javascriptCanCloseWindows() const
return m_javascriptCanCloseWindows;
}
void Config::setWebdriver(const QString &webdriverConfig)
{
// This option can be provided empty: in that case we should use the default IP:PORT configuration
QString ip = "127.0.0.1";
QString port = "8910";
// Parse and validate the configuration
bool isValidPort;
QStringList wdCfg = webdriverConfig.split(':');
if (wdCfg.length() == 1 && wdCfg[0].toInt(&isValidPort) && isValidPort) {
// Only a PORT was provided
port = wdCfg[0];
} else if(wdCfg.length() == 2 && !wdCfg[0].isEmpty() && wdCfg[1].toInt(&isValidPort) && isValidPort) {
// Both IP and PORT provided
ip = wdCfg[0];
port = wdCfg[1];
}
// Setting the "webdriver" configuration
m_webdriver = QString("%1:%2").arg(ip).arg(port);
}
QString Config::webdriver() const
{
return m_webdriver;
}
bool Config::isWebdriverMode() const
{
return !m_webdriver.isEmpty();
}
void Config::setSeleniumGridHub(const QString &hubUrl)
{
m_seleniumGridHub = hubUrl;
}
QString Config::seleniumGridHub() const
{
return m_seleniumGridHub;
}
// private:
void Config::resetToDefaults()
{
@ -455,6 +513,8 @@ void Config::resetToDefaults()
m_helpFlag = false;
m_printDebugMessages = false;
m_sslProtocol = "sslv3";
m_webdriver = QString();
m_seleniumGridHub = QString();
}
void Config::setProxyAuthPass(const QString &value)
@ -598,6 +658,12 @@ void Config::handleOption(const QString &option, const QVariant &value)
if (option == "ssl-protocol") {
setSslProtocol(value.toString());
}
if (option == "webdriver") {
setWebdriver(value.toString());
}
if (option == "selenium-grid-hub") {
setSeleniumGridHub(value.toString());
}
}
void Config::handleParam(const QString& param, const QVariant &value)
@ -623,4 +689,4 @@ QString Config::sslProtocol() const
void Config::setSslProtocol(const QString& sslProtocolName)
{
m_sslProtocol = sslProtocolName.toLower();
}
}

View File

@ -38,7 +38,7 @@
class QCommandLine;
class Config: QObject
class Config: public QObject
{
Q_OBJECT
Q_PROPERTY(QString cookiesFile READ cookiesFile WRITE setCookiesFile)
@ -58,6 +58,8 @@ class Config: QObject
Q_PROPERTY(bool javascriptCanOpenWindows READ javascriptCanOpenWindows WRITE setJavascriptCanOpenWindows)
Q_PROPERTY(bool javascriptCanCloseWindows READ javascriptCanCloseWindows WRITE setJavascriptCanCloseWindows)
Q_PROPERTY(QString sslProtocol READ sslProtocol WRITE setSslProtocol)
Q_PROPERTY(QString webdriver READ webdriver WRITE setWebdriver)
Q_PROPERTY(QString seleniumGridHub READ seleniumGridHub WRITE setSeleniumGridHub)
public:
Config(QObject *parent = 0);
@ -152,6 +154,13 @@ public:
void setSslProtocol(const QString& sslProtocolName);
QString sslProtocol() const;
void setWebdriver(const QString& webdriverConfig);
QString webdriver() const;
bool isWebdriverMode() const;
void setSeleniumGridHub(const QString& hubUrl);
QString seleniumGridHub() const;
public slots:
void handleSwitch(const QString &sw);
void handleOption(const QString &option, const QVariant &value);
@ -196,6 +205,8 @@ private:
bool m_javascriptCanOpenWindows;
bool m_javascriptCanCloseWindows;
QString m_sslProtocol;
QString m_webdriver;
QString m_seleniumGridHub;
};
#endif // CONFIG_H

View File

@ -354,7 +354,7 @@ bool FileSystem::changeWorkingDirectory(const QString &path) const
QString FileSystem::absolute(const QString &relativePath) const
{
return QFileInfo(relativePath).absoluteFilePath();
return QFileInfo(relativePath).absoluteFilePath();
}
// Files

View File

@ -35,6 +35,9 @@
#include <QFileInfo>
#include <QFile>
#include <QWebPage>
#include <QDebug>
#include <QMetaObject>
#include <QMetaProperty>
#include "consts.h"
#include "terminal.h"
@ -179,12 +182,37 @@ bool Phantom::execute()
if (m_terminated)
return false;
if (m_config.scriptFile().isEmpty()) {
// REPL mode requested
#ifndef QT_NO_DEBUG_OUTPUT
qDebug() << "Phantom - execute: Configuration";
const QMetaObject* configMetaObj = m_config.metaObject();
for (int i = 0, ilen = configMetaObj->propertyCount(); i < ilen; ++i) {
qDebug() << " " << i << configMetaObj->property(i).name() << ":" << m_config.property(configMetaObj->property(i).name()).toString();
}
qDebug() << "Phantom - execute: Script & Arguments";
qDebug() << " " << "script:" << m_config.scriptFile();
QStringList args = m_config.scriptArgs();
for (int i = 0, ilen = args.length(); i < ilen; ++i) {
qDebug() << " " << i << "arg:" << args.at(i);
}
#endif
if (m_config.isWebdriverMode()) { // Remote WebDriver mode requested
qDebug() << "Phantom - execute: Starting Remote WebDriver mode";
Terminal::instance()->cout("PhantomJS is launching GhostDriver...");
if (!Utils::injectJsInFrame(":/ghostdriver/main.js", m_scriptFileEnc, QDir::currentPath(), m_page->mainFrame(), true)) {
m_returnValue = -1;
return false;
}
} else if (m_config.scriptFile().isEmpty()) { // REPL mode requested
qDebug() << "Phantom - execute: Starting REPL mode";
// Create the REPL: it will launch itself, no need to store this variable.
REPL::getInstance(m_page->mainFrame(), this);
} else {
// Load the User Script
} else { // Load the User Script
qDebug() << "Phantom - execute: Starting normal mode";
if (m_config.debug()) {
// Debug enabled
if (!Utils::loadJSForDebug(m_config.scriptFile(), m_scriptFileEnc, QDir::currentPath(), m_page->mainFrame(), m_config.remoteDebugAutorun())) {
@ -261,6 +289,11 @@ void Phantom::setCookiesEnabled(const bool value)
}
}
bool Phantom::webdriverMode() const
{
return m_config.isWebdriverMode();
}
// public slots:
QObject *Phantom::createWebPage()
{
@ -331,12 +364,19 @@ void Phantom::loadModule(const QString &moduleSource, const QString &filename)
bool Phantom::injectJs(const QString &jsFilePath)
{
QString pre = "";
qDebug() << "Phantom - injectJs:" << jsFilePath;
// If in Remote Webdriver Mode, we need to manipulate the PATH, to point it to a resource in `ghostdriver.qrc`
if (webdriverMode()) {
pre = ":/ghostdriver/";
qDebug() << "Phantom - injectJs: prepending" << pre;
}
if (m_terminated)
return false;
return Utils::injectJsInFrame(jsFilePath, libraryPath(), m_page->mainFrame());
return Utils::injectJsInFrame(pre + jsFilePath, libraryPath(), m_page->mainFrame());
}
void Phantom::exit(int code)

View File

@ -56,6 +56,7 @@ class Phantom: public REPLCompletable
Q_PROPERTY(QObject *page READ page)
Q_PROPERTY(bool cookiesEnabled READ areCookiesEnabled WRITE setCookiesEnabled)
Q_PROPERTY(QVariantList cookies READ cookies WRITE setCookies)
Q_PROPERTY(bool webdriverMode READ webdriverMode)
private:
// Private constructor: the Phantom class is a singleton
@ -99,6 +100,8 @@ public:
bool areCookiesEnabled() const;
void setCookiesEnabled(const bool value);
bool webdriverMode() const;
public slots:
QObject *createWebPage();
QObject *createWebServer();

View File

@ -85,7 +85,7 @@ public:
Multiple = 0x04, /**< argument can be used multiple time and will produce multiple signals. */
ParameterFence = 0x08, //**< all arguments after this point are considered parameters, not options. */
MandatoryMultiple = Mandatory|Multiple,
OptionalMultiple = Optional|Multiple,
OptionalMultiple = Optional|Multiple
} Flags;
/**