UNPKG

10.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const net = require("net");
4const selenium_webdriver_1 = require("selenium-webdriver");
5const util = require("util");
6const logger_1 = require("./logger");
7let breakpointHook = require('./breakpointhook.js');
8let logger = new logger_1.Logger('protractor');
9class 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}
232exports.DebugHelper = DebugHelper;
233//# sourceMappingURL=debugger.js.map
\No newline at end of file