From 99a8127938381d350f1d597f51fce2912d018fb2 Mon Sep 17 00:00:00 2001 From: Hank Duan Date: Tue, 3 Mar 2015 18:38:41 -0800 Subject: [PATCH] experimental(debugger): make element explorer work with node 0.12.0 Node has changed its debugger significantly in 0.12.0, and these changes are necessary to get it to work now. --- lib/debugger/clients/explorer.js | 22 +++-------- lib/debugger/clients/wddebugger.js | 46 ++++++++++------------ lib/debugger/debuggerCommons.js | 61 ++++++++++++++++++++++++++++++ lib/protractor.js | 28 +++++++------- 4 files changed, 100 insertions(+), 57 deletions(-) create mode 100644 lib/debugger/debuggerCommons.js diff --git a/lib/debugger/clients/explorer.js b/lib/debugger/clients/explorer.js index d7df41f0b..d1afc29fe 100644 --- a/lib/debugger/clients/explorer.js +++ b/lib/debugger/clients/explorer.js @@ -1,5 +1,5 @@ var repl = require('repl'); -var baseDebugger = require('_debugger'); +var debuggerCommons = require('../debuggerCommons'); var CommandRepl = require('../modes/commandRepl'); /** @@ -9,7 +9,7 @@ var CommandRepl = require('../modes/commandRepl'); * @constructor */ var WdRepl = function() { - this.client = new baseDebugger.Client(); + this.client; this.replServer; this.cmdRepl; }; @@ -19,20 +19,8 @@ var WdRepl = function() { * @private */ WdRepl.prototype.initClient_ = function() { - var client = this.client; - - client.once('ready', function() { - - client.setBreakpoint({ - type: 'scriptRegExp', - target: '.*executors\.js', //jshint ignore:line - line: 37 - }, function() {}); - }); - - var host = 'localhost'; - var port = process.argv[2] || 5858; - client.connect(port, host); // TODO - might want to add retries here. + this.client = + debuggerCommons.attachDebugger(process.argv[2], process.argv[3]); }; /** @@ -44,7 +32,7 @@ WdRepl.prototype.initClient_ = function() { * @param {function} callback */ WdRepl.prototype.stepEval_ = function(cmd, context, filename, callback) { - cmd = cmd.slice(1, cmd.length - 2); + cmd = debuggerCommons.trimReplCmd(cmd); this.cmdRepl.stepEval(cmd, callback); }; diff --git a/lib/debugger/clients/wddebugger.js b/lib/debugger/clients/wddebugger.js index 86d3a36ac..b9a6a1787 100644 --- a/lib/debugger/clients/wddebugger.js +++ b/lib/debugger/clients/wddebugger.js @@ -1,5 +1,5 @@ var repl = require('repl'); -var baseDebugger = require('_debugger'); +var debuggerCommons = require('../debuggerCommons'); var CommandRepl = require('../modes/commandRepl'); var DebuggerRepl = require('../modes/debuggerRepl'); @@ -11,7 +11,7 @@ var DebuggerRepl = require('../modes/debuggerRepl'); * @constructor */ var WdDebugger = function() { - this.client = new baseDebugger.Client(); + this.client; this.replServer; // repl is broken into 'command repl' and 'debugger repl'. @@ -26,28 +26,17 @@ var WdDebugger = function() { * @private */ WdDebugger.prototype.initClient_ = function() { - var client = this.client; - - client.once('ready', function() { + this.client = + debuggerCommons.attachDebugger(process.argv[2], process.argv[3]); + this.client.once('ready', function() { console.log(' ready\n'); - - client.setBreakpoint({ - type: 'scriptRegExp', - target: '.*executors\.js', //jshint ignore:line - line: 37 - }, function() { - console.log('press c to continue to the next webdriver command'); - console.log('press d to continue to the next debugger statement'); - console.log('type "repl" to enter interactive mode'); - console.log('type "exit" to break out of interactive mode'); - console.log('press ^C to exit'); - console.log(); - }); + console.log('press c to continue to the next webdriver command'); + console.log('press d to continue to the next debugger statement'); + console.log('type "repl" to enter interactive mode'); + console.log('type "exit" to break out of interactive mode'); + console.log('press ^C to exit'); + console.log(); }); - - var host = 'localhost'; - var port = process.argv[2] || 5858; - client.connect(port, host); // TODO - might want to add retries here. }; /** @@ -60,19 +49,26 @@ WdDebugger.prototype.initClient_ = function() { */ WdDebugger.prototype.stepEval_ = function(cmd, context, filename, callback) { // The loop won't come back until 'callback' is called. - // Strip out the () which the REPL adds and the new line. // Note - node's debugger gets around this by adding custom objects // named 'c', 's', etc to the REPL context. They have getters which // perform the desired function, and the callback is stored for later use. // Think about whether this is a better pattern. - cmd = cmd.slice(1, cmd.length - 2); + + cmd = debuggerCommons.trimReplCmd(cmd); if (this.currentRepl === this.dbgRepl && cmd === 'repl' || this.currentRepl === this.cmdRepl && cmd === 'exit') { // switch repl mode this.currentRepl = this.currentRepl === this.dbgRepl ? this.cmdRepl : this.dbgRepl; - this.replServer.prompt = this.currentRepl.prompt; + // For node backward compatibility. In older versions of node `setPrompt` + // does not exist, and we set the prompt by overwriting `replServer.prompt` + // directly. + if (this.replServer.setPrompt) { + this.replServer.setPrompt(this.currentRepl.prompt); + } else { + this.replServer.prompt = this.currentRepl.prompt; + } this.replServer.complete = this.currentRepl.complete.bind(this.currentRepl); callback(); } else { diff --git a/lib/debugger/debuggerCommons.js b/lib/debugger/debuggerCommons.js new file mode 100644 index 000000000..60bc7884d --- /dev/null +++ b/lib/debugger/debuggerCommons.js @@ -0,0 +1,61 @@ +var baseDebugger = require('_debugger'); + +/** + * Create a debugger client and attach to a running protractor process. + * Set a break point at webdriver executor. + * @param {number} pid Pid of the process to attach the debugger to. + * @param {number=} opt_port Port to set up the debugger connection over. + * @return {!baseDebugger.Client} The connected debugger client. + */ +exports.attachDebugger = function(pid, opt_port) { + var client = new baseDebugger.Client(); + + client.once('ready', function() { + client.setBreakpoint({ + type: 'scriptRegExp', + target: '.*executors\.js', //jshint ignore:line + line: 37 + }, function() {}); + }); + + process.debugPort = opt_port || 5858; + // Call this private function instead of sending SIGUSR1 because Windows. + process._debugProcess(pid); + + var host = 'localhost'; + var connectWithRetry = function(attempts) { + var socket = client.connect(process.debugPort, host); + socket.on('connect', function() { + client.reqContinue(function() { + // Intentionally blank. + }); + }); + socket.on('error', function(e) { + if (attempts === 1) { + throw e; + } else { + setTimeout(function() { + connectWithRetry(attempts - 1); + }, 200); + } + }); + }; + connectWithRetry(10); + + return client; +}; + +/** + * Trim excess symbols from the repl command so that it is consistent with + * the user input. + * @param {string} cmd Cmd provided by the repl server. + * @return {string} The trimmed cmd. + */ +exports.trimReplCmd = function(cmd) { + // Given user input 'foobar', some versions of node provide '(foobar\n)', + // while other versions of node provide 'foobar\n'. + if (cmd.length >= 2 && cmd[0] === '(' && cmd[cmd.length - 1] === ')') { + cmd = cmd.substring(1, cmd.length - 1); + } + return cmd.slice(0, cmd.length - 1); +}; diff --git a/lib/protractor.js b/lib/protractor.js index b7c03daff..c9c1f1367 100644 --- a/lib/protractor.js +++ b/lib/protractor.js @@ -653,26 +653,26 @@ Protractor.prototype.initDebugger_ = function(debuggerClientPath, opt_debugPort) return asString; }; - if (opt_debugPort) { - process.debugPort = opt_debugPort; - } - - // Call this private function instead of sending SIGUSR1 because Windows. - process._debugProcess(process.pid); - + var vm_ = require('vm'); + var browserUnderDebug = this; var flow = webdriver.promise.controlFlow(); - var pausePromise = flow.execute(function() { + + 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]); + fork(debuggerClientPath, + [process.pid, opt_debugPort || process.debugPort]); process.on('exit', function() { nodedebug.kill('SIGTERM'); - }); + }); }); - - var vm_ = require('vm'); - var browserUnderDebug = this; + + var pausePromise = flow.timeout(1000, 'waiting for debugger to attach') + .then(function() { + // Necessary for backward compatibility with node < 0.12.0 + browserUnderDebug.executeScript_('', 'empty debugger hook'); + }); // Helper used only by debuggers at './debugger/modes/*.js' to insert code // into the control flow. @@ -773,8 +773,6 @@ Protractor.prototype.initDebugger_ = function(debuggerClientPath, opt_debugPort) return found; }); }; - - flow.timeout(1000, 'waiting for debugger to attach'); }; /**