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

Commit

Permalink
feat(pause): add the browser.pause method to enter a webdriver-specif…
Browse files Browse the repository at this point in the history
…ic debugger

Warning: this is still beta, there may be issues.
Usage: In test code, insert a `browser.pause()` statement. This will stop
the test at that point in the webdriver control flow. No need to change the
command line you use to start the test. Once paused, you can step forward,
pausing before each webdriver command, and interact with the browser.
Exit the debugger to continue the tests.
  • Loading branch information
juliemr committed Mar 11, 2014
1 parent 2f7c25a commit 06bd573
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 11 deletions.
96 changes: 85 additions & 11 deletions lib/protractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ var WEB_ELEMENT_FUNCTIONS = [
'getSize', 'getLocation', 'isEnabled', 'isSelected', 'submit', 'clear',
'isDisplayed', 'getOuterHtml', 'getInnerHtml'];

var STACK_SUBSTRINGS_TO_FILTER = [
'node_modules/minijasminenode/lib/',
'node_modules/selenium-webdriver',
'at Module.',
'at Object.Module.',
'at Function.Module',
'(timers.js:',
'jasminewd/index.js',
'protractor/lib/'
];

/*
* Mix in other webdriver functionality to be accessible via protractor.
*/
Expand Down Expand Up @@ -933,6 +944,78 @@ Protractor.prototype.debugger = function() {
}, 'add breakpoint to control flow');
};

/**
* Beta (unstable) pause function for debugging webdriver tests. Use
* browser.pause() in your test to enter the protractor debugger from that
* point in the control flow.
* Does not require changes to the command line (no need to add 'debug').
*/
Protractor.prototype.pause = function() {
// Patch in a function to help us visualize what's going on in the control
// flow.
webdriver.promise.ControlFlow.prototype.getControlFlowText = function() {
var descriptions = [];

var getDescriptions = function(frameOrTask, descriptions) {
if (frameOrTask.getDescription) {
var getRelevantStack = function(stack) {
return stack.filter(function(line) {
var include = true;
for (var i = 0; i < STACK_SUBSTRINGS_TO_FILTER.length; ++i) {
if (line.toString().indexOf(STACK_SUBSTRINGS_TO_FILTER[i]) !==
-1) {
include = false;
}
}
return include;
});
};
descriptions.push({
description: frameOrTask.getDescription(),
stack: getRelevantStack(frameOrTask.snapshot_.getStacktrace())
});
} else {
for (var i = 0; i < frameOrTask.children_.length; ++i) {
getDescriptions(frameOrTask.children_[i], descriptions);
}
}
};
if (this.history_.length) {
getDescriptions(this.history_[this.history_.length - 1], descriptions);
}
if (this.activeFrame_.getPendingTask()) {
getDescriptions(this.activeFrame_.getPendingTask(), descriptions);
}
getDescriptions(this.activeFrame_.getRoot(), descriptions);
var asString = '-- WebDriver control flow schedule \n';
for (var i = 0; i < descriptions.length; ++i) {
asString += ' |- ' + descriptions[i].description;
if (descriptions[i].stack.length) {
asString += '\n |---' + descriptions[i].stack.join('\n |---');
}
if (!(i == descriptions.length - 1)) {
asString += '\n';
}
}
return asString;
};

// Call this private function instead of sending SIGUSR1 because Windows.
process._debugProcess(process.pid);
var flow = webdriver.promise.controlFlow();

flow.execute(function() {
console.log('Starting WebDriver debugger in a child process. Pause is ' +
'still beta, please report issues at github.com/angular/protractor');
var nodedebug = require('child_process').
fork(__dirname + '/wddebugger.js', ['localhost:5858']);
process.on('exit', function() {
nodedebug.kill('SIGTERM');
})
});
flow.timeout(1000, 'waiting for debugger to attach');
};

/**
* Builds a single web element from a locator with a findElementsOverride.
* Throws an error if an element is not found, and issues a warning
Expand Down Expand Up @@ -1004,20 +1087,11 @@ exports.filterStackTrace = function(text) {
if (!text) {
return text;
}
var substringsToFilter = [
'node_modules/minijasminenode/lib/',
'node_modules/selenium-webdriver',
'at Module.',
'at Object.Module.',
'at Function.Module',
'(timers.js:',
'jasminewd/index.js'
];
var lines = [];
text.split(/\n/).forEach(function(line) {
var include = true;
for (var i = 0; i < substringsToFilter.length; ++i) {
if (line.indexOf(substringsToFilter[i]) !== -1) {
for (var i = 0; i < STACK_SUBSTRINGS_TO_FILTER.length; ++i) {
if (line.indexOf(STACK_SUBSTRINGS_TO_FILTER[i]) !== -1) {
include = false;
}
}
Expand Down
150 changes: 150 additions & 0 deletions lib/wddebugger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
console.log('------- WebDriver Debugger -------');

var util = require('util');
var repl = require('repl');
/**
* BETA BETA BETA
* Custom protractor debugger which steps through one control flow task
* at a time.
*/

var baseDebugger = require('_debugger');

var client = new baseDebugger.Client();

var host = 'localhost';
var port = 5858;

var debuggerRepl;

var resumeReplCallback = null;
var resume = function() {
if (resumeReplCallback) {
resumeReplCallback();
}
resumeReplCallback = null;
}


var debugStepperEval = 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.
var cmd = cmd.slice(1, cmd.length - 2);
switch (cmd) {
case 'c':
resumeReplCallback = callback;
client.reqContinue(function(err, res) {
// Intentionally blank.
});
break;
case 'frame':
client.req({command: 'frame'}, function(err, res) {
console.log('frame response: ' + util.inspect(res));
callback(null, 1);
});
break;
case 'scopes':
client.req({command: 'scopes'}, function(err, res) {
console.log('scopes response: ' + util.inspect(res, {depth: 4}));
callback(null, 1);
});
break;
case 'scripts':
client.req({command: 'scripts'}, function(err, res) {
console.log('scripts response: ' + util.inspect(res, {depth: 4}));
callback(null, 1);
});
break;
case 'source':
client.req({command: 'source'}, function(err, res) {
console.log('source response: ' + util.inspect(res, {depth: 4}));
callback(null, 1);
});
break;
case 'backtrace':
client.req({command: 'backtrace'}, function(err, res) {
console.log('backtrace response: ' + util.inspect(res, {depth: 4}));
callback(null, 1);
});
break;
case 'd':
client.req({command: 'disconnect'}, function(err, res) {});
callback(null, 1);
break;
default:
console.log('Unrecognized command.');
callback(null, undefined);
break;
}
}

var replOpts = {
prompt: 'wd-debug> ',
input: process.stdin,
output: process.stdout,
eval: debugStepperEval,
useGlobal: false,
ignoreUndefined: true
};

var initializeRepl = function() {
debuggerRepl = repl.start(replOpts);

debuggerRepl.on('exit', function() {
process.exit(0);
});
};

client.once('ready', function() {
console.log(' ready\n');

client.setBreakpoint({
type: 'scriptRegExp',
target: 'selenium-webdriver/executors.js',
line: 37
},
function(err, res) {
console.log('press c to continue to the next webdriver command');
console.log('press d to continue to the next debugger statement');
console.log('press ^C to exit');
});
});

// TODO - might want to add retries here.
client.connect(port, host);

client.on('break', function(res) {
client.req({
command: 'evaluate',
arguments: {
frame: 0,
maxStringLength: 2000,
expression: 'protractor.promise.controlFlow().getControlFlowText()'
}
}, function(err, controlFlowResponse) {
if (!err) {
client.req({
command: 'evaluate',
arguments: {
frame: 0,
maxStringLength: 1000,
expression: 'command.getName()'
}
}, function (err, response) {
if (response.value) {
console.log('-- Next command: ' + response.value);
}
console.log(controlFlowResponse.value);
if (!debuggerRepl) {
initializeRepl();
}
resume();
});
}
});
});

0 comments on commit 06bd573

Please sign in to comment.