1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const net = require("net");
|
4 | const selenium_webdriver_1 = require("selenium-webdriver");
|
5 | const util = require("util");
|
6 | const logger_1 = require("./logger");
|
7 | let breakpointHook = require('./breakpointhook.js');
|
8 | let logger = new logger_1.Logger('protractor');
|
9 | class DebugHelper {
|
10 | constructor(browserUnderDebug_) {
|
11 | this.browserUnderDebug_ = browserUnderDebug_;
|
12 | }
|
13 | initBlocking(debuggerClientPath, onStartFn, opt_debugPort) {
|
14 | this.init_(debuggerClientPath, true, onStartFn, opt_debugPort);
|
15 | }
|
16 | init(debuggerClientPath, onStartFn, opt_debugPort) {
|
17 | this.init_(debuggerClientPath, false, onStartFn, opt_debugPort);
|
18 | }
|
19 | /**
|
20 | * 1) Set up helper functions for debugger clients to call on (e.g.
|
21 | * execute code, get autocompletion).
|
22 | * 2) Enter process into debugger mode. (i.e. process._debugProcess).
|
23 | * 3) Invoke the debugger client specified by debuggerClientPath.
|
24 | *
|
25 | * @param {string} debuggerClientPath Absolute path of debugger client to use.
|
26 | * @param {boolean} blockUntilExit Whether to block the flow until process exit or resume
|
27 | * immediately.
|
28 | * @param {Function} onStartFn Function to call when the debugger starts. The
|
29 | * function takes a single parameter, which represents whether this is the
|
30 | * first time that the debugger is called.
|
31 | * @param {number=} opt_debugPort Optional port to use for the debugging
|
32 | * process.
|
33 | *
|
34 | * @return {Promise} If blockUntilExit, a promise resolved when the debugger process
|
35 | * exits. Otherwise, resolved when the debugger process is ready to begin.
|
36 | */
|
37 | init_(debuggerClientPath, blockUntilExit, onStartFn, opt_debugPort) {
|
38 | const vm_ = require('vm');
|
39 | let flow = selenium_webdriver_1.promise.controlFlow();
|
40 | let context = { require: require };
|
41 | global.list = (locator) => {
|
42 | return global.protractor.browser.findElements(locator).then((arr) => {
|
43 | let found = [];
|
44 | for (let i = 0; i < arr.length; ++i) {
|
45 | arr[i].getText().then((text) => {
|
46 | found.push(text);
|
47 | });
|
48 | }
|
49 | return found;
|
50 | });
|
51 | };
|
52 | for (let key in global) {
|
53 | context[key] = global[key];
|
54 | }
|
55 | let sandbox = vm_.createContext(context);
|
56 | let debuggingDone = selenium_webdriver_1.promise.defer();
|
57 | // We run one flow.execute block for the debugging session. All
|
58 | // subcommands should be scheduled under this task.
|
59 | let executePromise = flow.execute(() => {
|
60 | process['debugPort'] = opt_debugPort || process['debugPort'];
|
61 | this.validatePortAvailability_(process['debugPort']).then((firstTime) => {
|
62 | onStartFn(firstTime);
|
63 | let args = [process.pid, process['debugPort']];
|
64 | if (this.browserUnderDebug_.debuggerServerPort) {
|
65 | args.push(this.browserUnderDebug_.debuggerServerPort);
|
66 | }
|
67 | let nodedebug = require('child_process').fork(debuggerClientPath, args);
|
68 | process.on('exit', function () {
|
69 | nodedebug.kill('SIGTERM');
|
70 | });
|
71 | nodedebug
|
72 | .on('message', (m) => {
|
73 | if (m === 'ready') {
|
74 | breakpointHook();
|
75 | if (!blockUntilExit) {
|
76 | debuggingDone.fulfill();
|
77 | }
|
78 | }
|
79 | })
|
80 | .on('exit', () => {
|
81 | // Clear this so that we know it's ok to attach a debugger
|
82 | // again.
|
83 | this.dbgCodeExecutor = null;
|
84 | debuggingDone.fulfill();
|
85 | });
|
86 | });
|
87 | return debuggingDone.promise;
|
88 | }, 'debugging tasks');
|
89 | // Helper used only by debuggers at './debugger/modes/*.js' to insert code
|
90 | // into the control flow, via debugger 'evaluate' protocol.
|
91 | // In order to achieve this, we maintain a task at the top of the control
|
92 | // flow, so that we can insert frames into it.
|
93 | // To be able to simulate callback/asynchronous code, we poll this object
|
94 | // whenever `breakpointHook` is called.
|
95 | this.dbgCodeExecutor = {
|
96 | execPromise_: undefined,
|
97 | execPromiseResult_: undefined,
|
98 | execPromiseError_: undefined,
|
99 | // A dummy repl server to make use of its completion function.
|
100 | replServer_: require('repl').start({
|
101 | input: { on: function () { }, resume: function () { } },
|
102 | // dummy readable stream
|
103 | output: { write: function () { } },
|
104 | useGlobal: true
|
105 | }),
|
106 | // Execute a function, which could yield a value or a promise,
|
107 | // and allow its result to be accessed synchronously
|
108 | execute_: function (execFn_) {
|
109 | this.execPromiseResult_ = this.execPromiseError_ = undefined;
|
110 | this.execPromise_ = execFn_();
|
111 | // Note: This needs to be added after setting execPromise to execFn,
|
112 | // or else we cause this.execPromise_ to get stuck in pending mode
|
113 | // at our next breakpoint.
|
114 | this.execPromise_.then((result) => {
|
115 | this.execPromiseResult_ = result;
|
116 | breakpointHook();
|
117 | }, (err) => {
|
118 | this.execPromiseError_ = err;
|
119 | breakpointHook();
|
120 | });
|
121 | },
|
122 | // Execute a piece of code.
|
123 | // Result is a string representation of the evaluation.
|
124 | execute: function (code) {
|
125 | let execFn_ = () => {
|
126 | // Run code through vm so that we can maintain a local scope which is
|
127 | // isolated from the rest of the execution.
|
128 | let res;
|
129 | try {
|
130 | res = vm_.runInContext(code, sandbox);
|
131 | }
|
132 | catch (e) {
|
133 | res = selenium_webdriver_1.promise.when('Error while evaluating command: ' + e);
|
134 | }
|
135 | if (!selenium_webdriver_1.promise.isPromise(res)) {
|
136 | res = selenium_webdriver_1.promise.when(res);
|
137 | }
|
138 | return res.then((res) => {
|
139 | if (res === undefined) {
|
140 | return undefined;
|
141 | }
|
142 | else {
|
143 | // The '' forces res to be expanded into a string instead of just
|
144 | // '[Object]'. Then we remove the extra space caused by the ''
|
145 | // using substring.
|
146 | return util.format.apply(this, ['', res]).substring(1);
|
147 | }
|
148 | });
|
149 | };
|
150 | this.execute_(execFn_);
|
151 | },
|
152 | // Autocomplete for a line.
|
153 | // Result is a JSON representation of the autocomplete response.
|
154 | complete: function (line) {
|
155 | let execFn_ = () => {
|
156 | let deferred = selenium_webdriver_1.promise.defer();
|
157 | this.replServer_.complete(line, (err, res) => {
|
158 | if (err) {
|
159 | deferred.reject(err);
|
160 | }
|
161 | else {
|
162 | deferred.fulfill(JSON.stringify(res));
|
163 | }
|
164 | });
|
165 | return deferred.promise;
|
166 | };
|
167 | this.execute_(execFn_);
|
168 | },
|
169 | // Code finished executing.
|
170 | resultReady: function () {
|
171 | return !(this.execPromise_.state_ === 'pending');
|
172 | },
|
173 | // Get asynchronous results synchronously.
|
174 | // This will throw if result is not ready.
|
175 | getResult: function () {
|
176 | if (!this.resultReady()) {
|
177 | throw new Error('Result not ready');
|
178 | }
|
179 | if (this.execPromiseError_) {
|
180 | throw this.execPromiseError_;
|
181 | }
|
182 | return this.execPromiseResult_;
|
183 | }
|
184 | };
|
185 | return executePromise;
|
186 | }
|
187 | /**
|
188 | * Validates that the port is free to use. This will only validate the first
|
189 | * time it is called. The reason is that on subsequent calls, the port will
|
190 | * already be bound to the debugger, so it will not be available, but that is
|
191 | * okay.
|
192 | *
|
193 | * @returns {Promise<boolean>} A promise that becomes ready when the
|
194 | * validation
|
195 | * is done. The promise will resolve to a boolean which represents whether
|
196 | * this is the first time that the debugger is called.
|
197 | */
|
198 | validatePortAvailability_(port) {
|
199 | if (this.debuggerValidated_) {
|
200 | return selenium_webdriver_1.promise.when(false);
|
201 | }
|
202 | let doneDeferred = selenium_webdriver_1.promise.defer();
|
203 | // Resolve doneDeferred if port is available.
|
204 | let tester = net.connect({ port: port }, () => {
|
205 | doneDeferred.reject('Port ' + port + ' is already in use. Please specify ' +
|
206 | 'another port to debug.');
|
207 | });
|
208 | tester.once('error', (err) => {
|
209 | if (err.code === 'ECONNREFUSED') {
|
210 | tester
|
211 | .once('close', () => {
|
212 | doneDeferred.fulfill(true);
|
213 | })
|
214 | .end();
|
215 | }
|
216 | else {
|
217 | doneDeferred.reject('Unexpected failure testing for port ' + port + ': ' + JSON.stringify(err));
|
218 | }
|
219 | });
|
220 | return doneDeferred.promise.then((firstTime) => {
|
221 | this.debuggerValidated_ = true;
|
222 | return firstTime;
|
223 | }, (err) => {
|
224 | console.error(err);
|
225 | return process.exit(1);
|
226 | });
|
227 | }
|
228 | isAttached() {
|
229 | return !!this.dbgCodeExecutor;
|
230 | }
|
231 | }
|
232 | exports.DebugHelper = DebugHelper;
|
233 | //# sourceMappingURL=debugger.js.map |
\ | No newline at end of file |