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

Commit 06bd573

Browse files
committedMar 11, 2014
feat(pause): add the browser.pause method to enter a webdriver-specific 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.
1 parent 2f7c25a commit 06bd573

File tree

2 files changed

+235
-11
lines changed

2 files changed

+235
-11
lines changed
 

‎lib/protractor.js

+85-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ var WEB_ELEMENT_FUNCTIONS = [
1313
'getSize', 'getLocation', 'isEnabled', 'isSelected', 'submit', 'clear',
1414
'isDisplayed', 'getOuterHtml', 'getInnerHtml'];
1515

16+
var STACK_SUBSTRINGS_TO_FILTER = [
17+
'node_modules/minijasminenode/lib/',
18+
'node_modules/selenium-webdriver',
19+
'at Module.',
20+
'at Object.Module.',
21+
'at Function.Module',
22+
'(timers.js:',
23+
'jasminewd/index.js',
24+
'protractor/lib/'
25+
];
26+
1627
/*
1728
* Mix in other webdriver functionality to be accessible via protractor.
1829
*/
@@ -933,6 +944,78 @@ Protractor.prototype.debugger = function() {
933944
}, 'add breakpoint to control flow');
934945
};
935946

947+
/**
948+
* Beta (unstable) pause function for debugging webdriver tests. Use
949+
* browser.pause() in your test to enter the protractor debugger from that
950+
* point in the control flow.
951+
* Does not require changes to the command line (no need to add 'debug').
952+
*/
953+
Protractor.prototype.pause = function() {
954+
// Patch in a function to help us visualize what's going on in the control
955+
// flow.
956+
webdriver.promise.ControlFlow.prototype.getControlFlowText = function() {
957+
var descriptions = [];
958+
959+
var getDescriptions = function(frameOrTask, descriptions) {
960+
if (frameOrTask.getDescription) {
961+
var getRelevantStack = function(stack) {
962+
return stack.filter(function(line) {
963+
var include = true;
964+
for (var i = 0; i < STACK_SUBSTRINGS_TO_FILTER.length; ++i) {
965+
if (line.toString().indexOf(STACK_SUBSTRINGS_TO_FILTER[i]) !==
966+
-1) {
967+
include = false;
968+
}
969+
}
970+
return include;
971+
});
972+
};
973+
descriptions.push({
974+
description: frameOrTask.getDescription(),
975+
stack: getRelevantStack(frameOrTask.snapshot_.getStacktrace())
976+
});
977+
} else {
978+
for (var i = 0; i < frameOrTask.children_.length; ++i) {
979+
getDescriptions(frameOrTask.children_[i], descriptions);
980+
}
981+
}
982+
};
983+
if (this.history_.length) {
984+
getDescriptions(this.history_[this.history_.length - 1], descriptions);
985+
}
986+
if (this.activeFrame_.getPendingTask()) {
987+
getDescriptions(this.activeFrame_.getPendingTask(), descriptions);
988+
}
989+
getDescriptions(this.activeFrame_.getRoot(), descriptions);
990+
var asString = '-- WebDriver control flow schedule \n';
991+
for (var i = 0; i < descriptions.length; ++i) {
992+
asString += ' |- ' + descriptions[i].description;
993+
if (descriptions[i].stack.length) {
994+
asString += '\n |---' + descriptions[i].stack.join('\n |---');
995+
}
996+
if (!(i == descriptions.length - 1)) {
997+
asString += '\n';
998+
}
999+
}
1000+
return asString;
1001+
};
1002+
1003+
// Call this private function instead of sending SIGUSR1 because Windows.
1004+
process._debugProcess(process.pid);
1005+
var flow = webdriver.promise.controlFlow();
1006+
1007+
flow.execute(function() {
1008+
console.log('Starting WebDriver debugger in a child process. Pause is ' +
1009+
'still beta, please report issues at github.com/angular/protractor');
1010+
var nodedebug = require('child_process').
1011+
fork(__dirname + '/wddebugger.js', ['localhost:5858']);
1012+
process.on('exit', function() {
1013+
nodedebug.kill('SIGTERM');
1014+
})
1015+
});
1016+
flow.timeout(1000, 'waiting for debugger to attach');
1017+
};
1018+
9361019
/**
9371020
* Builds a single web element from a locator with a findElementsOverride.
9381021
* Throws an error if an element is not found, and issues a warning
@@ -1004,20 +1087,11 @@ exports.filterStackTrace = function(text) {
10041087
if (!text) {
10051088
return text;
10061089
}
1007-
var substringsToFilter = [
1008-
'node_modules/minijasminenode/lib/',
1009-
'node_modules/selenium-webdriver',
1010-
'at Module.',
1011-
'at Object.Module.',
1012-
'at Function.Module',
1013-
'(timers.js:',
1014-
'jasminewd/index.js'
1015-
];
10161090
var lines = [];
10171091
text.split(/\n/).forEach(function(line) {
10181092
var include = true;
1019-
for (var i = 0; i < substringsToFilter.length; ++i) {
1020-
if (line.indexOf(substringsToFilter[i]) !== -1) {
1093+
for (var i = 0; i < STACK_SUBSTRINGS_TO_FILTER.length; ++i) {
1094+
if (line.indexOf(STACK_SUBSTRINGS_TO_FILTER[i]) !== -1) {
10211095
include = false;
10221096
}
10231097
}

‎lib/wddebugger.js

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
console.log('------- WebDriver Debugger -------');
2+
3+
var util = require('util');
4+
var repl = require('repl');
5+
/**
6+
* BETA BETA BETA
7+
* Custom protractor debugger which steps through one control flow task
8+
* at a time.
9+
*/
10+
11+
var baseDebugger = require('_debugger');
12+
13+
var client = new baseDebugger.Client();
14+
15+
var host = 'localhost';
16+
var port = 5858;
17+
18+
var debuggerRepl;
19+
20+
var resumeReplCallback = null;
21+
var resume = function() {
22+
if (resumeReplCallback) {
23+
resumeReplCallback();
24+
}
25+
resumeReplCallback = null;
26+
}
27+
28+
29+
var debugStepperEval = function(cmd, context, filename, callback) {
30+
// The loop won't come back until 'callback' is called.
31+
// Strip out the () which the REPL adds and the new line.
32+
// Note - node's debugger gets around this by adding custom objects
33+
// named 'c', 's', etc to the REPL context. They have getters which
34+
// perform the desired function, and the callback is stored for later use.
35+
// Think about whether this is a better pattern.
36+
var cmd = cmd.slice(1, cmd.length - 2);
37+
switch (cmd) {
38+
case 'c':
39+
resumeReplCallback = callback;
40+
client.reqContinue(function(err, res) {
41+
// Intentionally blank.
42+
});
43+
break;
44+
case 'frame':
45+
client.req({command: 'frame'}, function(err, res) {
46+
console.log('frame response: ' + util.inspect(res));
47+
callback(null, 1);
48+
});
49+
break;
50+
case 'scopes':
51+
client.req({command: 'scopes'}, function(err, res) {
52+
console.log('scopes response: ' + util.inspect(res, {depth: 4}));
53+
callback(null, 1);
54+
});
55+
break;
56+
case 'scripts':
57+
client.req({command: 'scripts'}, function(err, res) {
58+
console.log('scripts response: ' + util.inspect(res, {depth: 4}));
59+
callback(null, 1);
60+
});
61+
break;
62+
case 'source':
63+
client.req({command: 'source'}, function(err, res) {
64+
console.log('source response: ' + util.inspect(res, {depth: 4}));
65+
callback(null, 1);
66+
});
67+
break;
68+
case 'backtrace':
69+
client.req({command: 'backtrace'}, function(err, res) {
70+
console.log('backtrace response: ' + util.inspect(res, {depth: 4}));
71+
callback(null, 1);
72+
});
73+
break;
74+
case 'd':
75+
client.req({command: 'disconnect'}, function(err, res) {});
76+
callback(null, 1);
77+
break;
78+
default:
79+
console.log('Unrecognized command.');
80+
callback(null, undefined);
81+
break;
82+
}
83+
}
84+
85+
var replOpts = {
86+
prompt: 'wd-debug> ',
87+
input: process.stdin,
88+
output: process.stdout,
89+
eval: debugStepperEval,
90+
useGlobal: false,
91+
ignoreUndefined: true
92+
};
93+
94+
var initializeRepl = function() {
95+
debuggerRepl = repl.start(replOpts);
96+
97+
debuggerRepl.on('exit', function() {
98+
process.exit(0);
99+
});
100+
};
101+
102+
client.once('ready', function() {
103+
console.log(' ready\n');
104+
105+
client.setBreakpoint({
106+
type: 'scriptRegExp',
107+
target: 'selenium-webdriver/executors.js',
108+
line: 37
109+
},
110+
function(err, res) {
111+
console.log('press c to continue to the next webdriver command');
112+
console.log('press d to continue to the next debugger statement');
113+
console.log('press ^C to exit');
114+
});
115+
});
116+
117+
// TODO - might want to add retries here.
118+
client.connect(port, host);
119+
120+
client.on('break', function(res) {
121+
client.req({
122+
command: 'evaluate',
123+
arguments: {
124+
frame: 0,
125+
maxStringLength: 2000,
126+
expression: 'protractor.promise.controlFlow().getControlFlowText()'
127+
}
128+
}, function(err, controlFlowResponse) {
129+
if (!err) {
130+
client.req({
131+
command: 'evaluate',
132+
arguments: {
133+
frame: 0,
134+
maxStringLength: 1000,
135+
expression: 'command.getName()'
136+
}
137+
}, function (err, response) {
138+
if (response.value) {
139+
console.log('-- Next command: ' + response.value);
140+
}
141+
console.log(controlFlowResponse.value);
142+
if (!debuggerRepl) {
143+
initializeRepl();
144+
}
145+
resume();
146+
});
147+
}
148+
});
149+
});
150+

0 commit comments

Comments
 (0)
This repository has been archived.