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

Commit 45341c9

Browse files
committed
feat(explorer): allow element explorer to start as a server
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.
1 parent cfa234d commit 45341c9

File tree

10 files changed

+332
-29
lines changed

10 files changed

+332
-29
lines changed

lib/cli.js

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ var optimist = require('optimist').
5252
describe('resultJsonOutputFile', 'Path to save JSON test result').
5353
describe('troubleshoot', 'Turn on troubleshooting output').
5454
describe('elementExplorer', 'Interactively test Protractor commands').
55+
describe('debuggerServerPort', 'Start a debugger server at specified port instead of repl').
5556
alias('browser', 'capabilities.browserName').
5657
alias('name', 'capabilities.name').
5758
alias('platform', 'capabilities.platform').

lib/debugger/clients/explorer.js

+93-18
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ var CommandRepl = require('../modes/commandRepl');
1010
*/
1111
var WdRepl = function() {
1212
this.client = new baseDebugger.Client();
13-
this.replServer;
14-
this.cmdRepl;
1513
};
1614

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

3836
/**
39-
* Eval function for processing a single step in repl.
37+
* Instantiate a server to handle IO.
38+
* @param {number} port The port to start the server.
4039
* @private
41-
* @param {string} cmd
42-
* @param {object} context
43-
* @param {string} filename
44-
* @param {function} callback
4540
*/
46-
WdRepl.prototype.stepEval_ = function(cmd, context, filename, callback) {
47-
cmd = cmd.slice(1, cmd.length - 2);
48-
this.cmdRepl.stepEval(cmd, callback);
41+
WdRepl.prototype.initServer_ = function(port) {
42+
var net = require('net');
43+
var self = this;
44+
var cmdRepl = new CommandRepl(this.client);
45+
46+
var received = '';
47+
net.createServer(function(sock) {
48+
sock.on('data', function(data) {
49+
received += data.toString();
50+
var eolIndex = received.indexOf('\r\n');
51+
if (eolIndex === 0) {
52+
return;
53+
}
54+
var input = received.substring(0, eolIndex);
55+
received = received.substring(eolIndex + 2);
56+
if (data[0] === 0x1D) {
57+
// '^]': term command
58+
self.client.req({command: 'disconnect'}, function() {
59+
// Intentionally blank.
60+
});
61+
sock.end();
62+
} else if (input[input.length - 1] === '\t') {
63+
// If the last character is the TAB key, this is an autocomplete
64+
// request. We use everything before the TAB as the init data to feed
65+
// into autocomplete.
66+
input = input.substring(0, input.length - 1);
67+
cmdRepl.complete(input, function(err, res) {
68+
if (err) {
69+
sock.write('ERROR: ' + err + '\r\n');
70+
} else {
71+
sock.write(JSON.stringify(res) + '\r\n');
72+
}
73+
});
74+
} else {
75+
// Normal input
76+
input = input.trim();
77+
cmdRepl.stepEval(input, function(err, res) {
78+
if (err) {
79+
sock.write('ERROR: ' + err + '\r\n');
80+
return;
81+
}
82+
if (res === undefined) {
83+
res = '';
84+
}
85+
sock.write(res + '\r\n');
86+
});
87+
}
88+
});
89+
}).listen(port);
90+
91+
console.log('Server listening on 127.0.0.1:' + port);
4992
};
5093

5194
/**
52-
* Instantiate all repl objects, and debuggerRepl as current and start repl.
95+
* Instantiate a repl to handle IO.
5396
* @private
5497
*/
5598
WdRepl.prototype.initRepl_ = function() {
5699
var self = this;
57-
this.cmdRepl = new CommandRepl(this.client);
100+
var cmdRepl = new CommandRepl(this.client);
101+
102+
// Eval function for processing a single step in repl.
103+
var stepEval = function(cmd, context, filename, callback) {
104+
// The command that eval feeds is of the form '(CMD\n)', so we trim the
105+
// double quotes and new line.
106+
cmd = cmd.slice(1, cmd.length - 2);
107+
cmdRepl.stepEval(cmd, function(err, res) {
108+
// Result is a string representation of the evaluation.
109+
if (res !== undefined) {
110+
console.log(res);
111+
}
112+
callback(err, undefined);
113+
});
114+
};
58115

59-
self.replServer = repl.start({
60-
prompt: self.cmdRepl.prompt,
116+
var replServer = repl.start({
117+
prompt: cmdRepl.prompt,
61118
input: process.stdin,
62119
output: process.stdout,
63-
eval: self.stepEval_.bind(self),
120+
eval: stepEval,
64121
useGlobal: false,
65122
ignoreUndefined: true
66123
});
67124

68-
self.replServer.complete = self.cmdRepl.complete.bind(self.cmdRepl);
125+
replServer.complete = cmdRepl.complete.bind(cmdRepl);
69126

70-
self.replServer.on('exit', function() {
127+
replServer.on('exit', function() {
71128
console.log('Exiting...');
72129
self.client.req({command: 'disconnect'}, function() {
73130
// Intentionally blank.
74131
});
75132
});
76133
};
77134

135+
/**
136+
* Instantiate a repl or a server.
137+
* @private
138+
*/
139+
WdRepl.prototype.initReplOrServer_ = function() {
140+
// Note instead of starting either repl or server, another approach is to
141+
// feed the server socket into the repl as the input/output streams. The
142+
// advantage is that the process becomes much more realistic because now we're
143+
// using the normal repl. However, it was not possible to test autocomplete
144+
// this way since we cannot immitate the TAB key over the wire.
145+
var debuggerServerPort = process.argv[3];
146+
if (debuggerServerPort) {
147+
this.initServer_(debuggerServerPort);
148+
} else {
149+
this.initRepl_();
150+
}
151+
};
152+
78153
/**
79154
* Initiate the debugger.
80155
* @public
@@ -85,7 +160,7 @@ WdRepl.prototype.init = function() {
85160
console.log(' e.g., list(by.binding(\'\')) gets all bindings.');
86161

87162
this.initClient_();
88-
this.initRepl_();
163+
this.initReplOrServer_();
89164
};
90165

91166
var wdRepl = new WdRepl();

lib/debugger/clients/wddebugger.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,19 @@ WdDebugger.prototype.stepEval_ = function(cmd, context, filename, callback) {
7575
this.replServer.prompt = this.currentRepl.prompt;
7676
this.replServer.complete = this.currentRepl.complete.bind(this.currentRepl);
7777
callback();
78+
} else if (this.currentRepl === this.cmdRepl) {
79+
// If we are currently in command repl mode.
80+
this.cmdRepl.stepEval(cmd, function(err, res) {
81+
// Result is a string representation of the evaluation, so we console.log
82+
// the result to print it properly. Then we callback with undefined so
83+
// that the result isn't printed twice.
84+
if (res !== undefined) {
85+
console.log(res);
86+
}
87+
callback(err, undefined);
88+
});
7889
} else {
79-
this.currentRepl.stepEval(cmd, callback);
90+
this.dbgRepl.stepEval(cmd, callback);
8091
}
8192
};
8293

lib/debugger/modes/commandRepl.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,9 @@ var CommandRepl = function(client) {
2929
*/
3030
CommandRepl.prototype.stepEval = function(expression, callback) {
3131
expression = expression.replace(/"/g, '\\\"');
32+
3233
var expr = 'browser.dbgCodeExecutor_.execute("' + expression + '")';
33-
this.evaluate_(expr, function(err, res) {
34-
// Result is a string representation of the evaluation.
35-
if (res !== undefined) {
36-
console.log(res);
37-
}
38-
callback(err, undefined);
39-
});
34+
this.evaluate_(expr, callback);
4035
};
4136

4237
/**

lib/protractor.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ var Protractor = function(webdriverInstance, opt_baseUrl, opt_rootElement) {
206206
this.mockModules_ = [];
207207

208208
this.addBaseMockModules_();
209+
210+
/**
211+
* If specified, start a debugger server at specified port instead of repl
212+
* when running element explorer.
213+
* @private {number}
214+
*/
215+
this.debuggerServerPort_;
209216
};
210217

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

664671
var flow = webdriver.promise.controlFlow();
672+
var self = this;
665673
var pausePromise = flow.execute(function() {
666674
log.puts('Starting WebDriver debugger in a child process. Pause is ' +
667675
'still beta, please report issues at github.com/angular/protractor\n');
668-
var nodedebug = require('child_process').
669-
fork(debuggerClientPath, [process.debugPort]);
676+
var args = [process.debugPort];
677+
if (self.debuggerServerPort_) {
678+
args.push(self.debuggerServerPort_);
679+
}
680+
var nodedebug = require('child_process').fork(debuggerClientPath, args);
670681
process.on('exit', function() {
671682
nodedebug.kill('SIGTERM');
672683
});

lib/runner.js

+3
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ Runner.prototype.createBrowser = function() {
185185
if (config.getPageTimeout) {
186186
browser_.getPageTimeout = config.getPageTimeout;
187187
}
188+
if (config.debuggerServerPort) {
189+
browser_.debuggerServerPort_ = config.debuggerServerPort;
190+
}
188191
var self = this;
189192

190193
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
var InteractiveTest = require('./interactive_test_util').InteractiveTest;
2+
var port = 6969;
3+
var test = new InteractiveTest('node lib/cli.js --elementExplorer true', port);
4+
5+
// Check state persists.
6+
test.addCommandExpectation('var x = 3');
7+
test.addCommandExpectation('x', '3');
8+
9+
// Check can return functions.
10+
test.addCommandExpectation('var y = function(param) {return param;}');
11+
test.addCommandExpectation('y', 'function (param) {return param;}');
12+
13+
// Check promises complete.
14+
test.addCommandExpectation('browser.driver.getCurrentUrl()', 'data:,');
15+
test.addCommandExpectation('browser.get("http://localhost:8081")');
16+
test.addCommandExpectation('browser.getCurrentUrl()',
17+
'http://localhost:8081/#/form');
18+
19+
// Check promises are resolved before being returned.
20+
test.addCommandExpectation('var greetings = element(by.binding("greeting"))');
21+
test.addCommandExpectation('greetings.getText()', 'Hiya');
22+
23+
// Check require is injected.
24+
test.addCommandExpectation('var q = require("q")');
25+
test.addCommandExpectation(
26+
'var deferred = q.defer(); ' +
27+
'setTimeout(function() {deferred.resolve(1)}, 100); ' +
28+
'deferred.promise',
29+
'1');
30+
31+
// Check errors are handled gracefully
32+
test.addCommandExpectation('element(by.binding("nonexistent"))');
33+
test.addCommandExpectation('element(by.binding("nonexistent")).getText()',
34+
'ERROR: NoSuchElementError: No element found using locator: ' +
35+
'by.binding("nonexistent")');
36+
37+
// Check complete calls
38+
test.addCommandExpectation('\t',
39+
'[["element(by.id(\'\'))","element(by.css(\'\'))",' +
40+
'"element(by.name(\'\'))","element(by.binding(\'\'))",' +
41+
'"element(by.xpath(\'\'))","element(by.tagName(\'\'))",' +
42+
'"element(by.className(\'\'))"],""]');
43+
test.addCommandExpectation('ele\t', '[["element"],"ele"]');
44+
test.addCommandExpectation('br\t', '[["break","","browser"],"br"]');
45+
46+
test.run();
47+

0 commit comments

Comments
 (0)