Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
feat(explorer): allow element explorer to start as a server
Browse files Browse the repository at this point in the history
If element explorer is run with a port (i.e. --debuggerServerPort 1234),
it will start up a server that listens to command from the port instead of
a repl that listens to process.stdin.
  • Loading branch information
hankduan committed Mar 25, 2015
1 parent cfa234d commit 45341c9
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 29 deletions.
1 change: 1 addition & 0 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var optimist = require('optimist').
describe('resultJsonOutputFile', 'Path to save JSON test result').
describe('troubleshoot', 'Turn on troubleshooting output').
describe('elementExplorer', 'Interactively test Protractor commands').
describe('debuggerServerPort', 'Start a debugger server at specified port instead of repl').
alias('browser', 'capabilities.browserName').
alias('name', 'capabilities.name').
alias('platform', 'capabilities.platform').
Expand Down
111 changes: 93 additions & 18 deletions lib/debugger/clients/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ var CommandRepl = require('../modes/commandRepl');
*/
var WdRepl = function() {
this.client = new baseDebugger.Client();
this.replServer;
this.cmdRepl;
};

/**
Expand All @@ -36,45 +34,122 @@ WdRepl.prototype.initClient_ = function() {
};

/**
* Eval function for processing a single step in repl.
* Instantiate a server to handle IO.
* @param {number} port The port to start the server.
* @private
* @param {string} cmd
* @param {object} context
* @param {string} filename
* @param {function} callback
*/
WdRepl.prototype.stepEval_ = function(cmd, context, filename, callback) {
cmd = cmd.slice(1, cmd.length - 2);
this.cmdRepl.stepEval(cmd, callback);
WdRepl.prototype.initServer_ = function(port) {
var net = require('net');
var self = this;
var cmdRepl = new CommandRepl(this.client);

var received = '';
net.createServer(function(sock) {
sock.on('data', function(data) {
received += data.toString();
var eolIndex = received.indexOf('\r\n');
if (eolIndex === 0) {
return;
}
var input = received.substring(0, eolIndex);
received = received.substring(eolIndex + 2);
if (data[0] === 0x1D) {
// '^]': term command
self.client.req({command: 'disconnect'}, function() {
// Intentionally blank.
});
sock.end();
} else if (input[input.length - 1] === '\t') {
// If the last character is the TAB key, this is an autocomplete
// request. We use everything before the TAB as the init data to feed
// into autocomplete.
input = input.substring(0, input.length - 1);
cmdRepl.complete(input, function(err, res) {
if (err) {
sock.write('ERROR: ' + err + '\r\n');
} else {
sock.write(JSON.stringify(res) + '\r\n');
}
});
} else {
// Normal input
input = input.trim();
cmdRepl.stepEval(input, function(err, res) {
if (err) {
sock.write('ERROR: ' + err + '\r\n');
return;
}
if (res === undefined) {
res = '';
}
sock.write(res + '\r\n');
});
}
});
}).listen(port);

console.log('Server listening on 127.0.0.1:' + port);
};

/**
* Instantiate all repl objects, and debuggerRepl as current and start repl.
* Instantiate a repl to handle IO.
* @private
*/
WdRepl.prototype.initRepl_ = function() {
var self = this;
this.cmdRepl = new CommandRepl(this.client);
var cmdRepl = new CommandRepl(this.client);

// Eval function for processing a single step in repl.
var stepEval = function(cmd, context, filename, callback) {
// The command that eval feeds is of the form '(CMD\n)', so we trim the
// double quotes and new line.
cmd = cmd.slice(1, cmd.length - 2);
cmdRepl.stepEval(cmd, function(err, res) {
// Result is a string representation of the evaluation.
if (res !== undefined) {
console.log(res);
}
callback(err, undefined);
});
};

self.replServer = repl.start({
prompt: self.cmdRepl.prompt,
var replServer = repl.start({
prompt: cmdRepl.prompt,
input: process.stdin,
output: process.stdout,
eval: self.stepEval_.bind(self),
eval: stepEval,
useGlobal: false,
ignoreUndefined: true
});

self.replServer.complete = self.cmdRepl.complete.bind(self.cmdRepl);
replServer.complete = cmdRepl.complete.bind(cmdRepl);

self.replServer.on('exit', function() {
replServer.on('exit', function() {
console.log('Exiting...');
self.client.req({command: 'disconnect'}, function() {
// Intentionally blank.
});
});
};

/**
* Instantiate a repl or a server.
* @private
*/
WdRepl.prototype.initReplOrServer_ = function() {
// Note instead of starting either repl or server, another approach is to
// feed the server socket into the repl as the input/output streams. The
// advantage is that the process becomes much more realistic because now we're
// using the normal repl. However, it was not possible to test autocomplete
// this way since we cannot immitate the TAB key over the wire.
var debuggerServerPort = process.argv[3];
if (debuggerServerPort) {
this.initServer_(debuggerServerPort);
} else {
this.initRepl_();
}
};

/**
* Initiate the debugger.
* @public
Expand All @@ -85,7 +160,7 @@ WdRepl.prototype.init = function() {
console.log(' e.g., list(by.binding(\'\')) gets all bindings.');

this.initClient_();
this.initRepl_();
this.initReplOrServer_();
};

var wdRepl = new WdRepl();
Expand Down
13 changes: 12 additions & 1 deletion lib/debugger/clients/wddebugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,19 @@ WdDebugger.prototype.stepEval_ = function(cmd, context, filename, callback) {
this.replServer.prompt = this.currentRepl.prompt;
this.replServer.complete = this.currentRepl.complete.bind(this.currentRepl);
callback();
} else if (this.currentRepl === this.cmdRepl) {
// If we are currently in command repl mode.
this.cmdRepl.stepEval(cmd, function(err, res) {
// Result is a string representation of the evaluation, so we console.log
// the result to print it properly. Then we callback with undefined so
// that the result isn't printed twice.
if (res !== undefined) {
console.log(res);
}
callback(err, undefined);
});
} else {
this.currentRepl.stepEval(cmd, callback);
this.dbgRepl.stepEval(cmd, callback);
}
};

Expand Down
9 changes: 2 additions & 7 deletions lib/debugger/modes/commandRepl.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,9 @@ var CommandRepl = function(client) {
*/
CommandRepl.prototype.stepEval = function(expression, callback) {
expression = expression.replace(/"/g, '\\\"');

var expr = 'browser.dbgCodeExecutor_.execute("' + expression + '")';
this.evaluate_(expr, function(err, res) {
// Result is a string representation of the evaluation.
if (res !== undefined) {
console.log(res);
}
callback(err, undefined);
});
this.evaluate_(expr, callback);
};

/**
Expand Down
15 changes: 13 additions & 2 deletions lib/protractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ var Protractor = function(webdriverInstance, opt_baseUrl, opt_rootElement) {
this.mockModules_ = [];

this.addBaseMockModules_();

/**
* If specified, start a debugger server at specified port instead of repl
* when running element explorer.
* @private {number}
*/
this.debuggerServerPort_;
};

/**
Expand Down Expand Up @@ -662,11 +669,15 @@ Protractor.prototype.initDebugger_ = function(debuggerClientPath, opt_debugPort)
process._debugProcess(process.pid);

var flow = webdriver.promise.controlFlow();
var self = this;
var pausePromise = flow.execute(function() {
log.puts('Starting WebDriver debugger in a child process. Pause is ' +
'still beta, please report issues at github.com/angular/protractor\n');
var nodedebug = require('child_process').
fork(debuggerClientPath, [process.debugPort]);
var args = [process.debugPort];
if (self.debuggerServerPort_) {
args.push(self.debuggerServerPort_);
}
var nodedebug = require('child_process').fork(debuggerClientPath, args);
process.on('exit', function() {
nodedebug.kill('SIGTERM');
});
Expand Down
3 changes: 3 additions & 0 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ Runner.prototype.createBrowser = function() {
if (config.getPageTimeout) {
browser_.getPageTimeout = config.getPageTimeout;
}
if (config.debuggerServerPort) {
browser_.debuggerServerPort_ = config.debuggerServerPort;
}
var self = this;

/**
Expand Down
47 changes: 47 additions & 0 deletions scripts/interactive_tests/interactive_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
var InteractiveTest = require('./interactive_test_util').InteractiveTest;
var port = 6969;
var test = new InteractiveTest('node lib/cli.js --elementExplorer true', port);

// Check state persists.
test.addCommandExpectation('var x = 3');
test.addCommandExpectation('x', '3');

// Check can return functions.
test.addCommandExpectation('var y = function(param) {return param;}');
test.addCommandExpectation('y', 'function (param) {return param;}');

// Check promises complete.
test.addCommandExpectation('browser.driver.getCurrentUrl()', 'data:,');
test.addCommandExpectation('browser.get("http://localhost:8081")');
test.addCommandExpectation('browser.getCurrentUrl()',
'http://localhost:8081/#/form');

// Check promises are resolved before being returned.
test.addCommandExpectation('var greetings = element(by.binding("greeting"))');
test.addCommandExpectation('greetings.getText()', 'Hiya');

// Check require is injected.
test.addCommandExpectation('var q = require("q")');
test.addCommandExpectation(
'var deferred = q.defer(); ' +
'setTimeout(function() {deferred.resolve(1)}, 100); ' +
'deferred.promise',
'1');

// Check errors are handled gracefully
test.addCommandExpectation('element(by.binding("nonexistent"))');
test.addCommandExpectation('element(by.binding("nonexistent")).getText()',
'ERROR: NoSuchElementError: No element found using locator: ' +
'by.binding("nonexistent")');

// Check complete calls
test.addCommandExpectation('\t',
'[["element(by.id(\'\'))","element(by.css(\'\'))",' +
'"element(by.name(\'\'))","element(by.binding(\'\'))",' +
'"element(by.xpath(\'\'))","element(by.tagName(\'\'))",' +
'"element(by.className(\'\'))"],""]');
test.addCommandExpectation('ele\t', '[["element"],"ele"]');
test.addCommandExpectation('br\t', '[["break","","browser"],"br"]');

test.run();

Loading

0 comments on commit 45341c9

Please sign in to comment.