123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- const net = require("net");
- const selenium_webdriver_1 = require("selenium-webdriver");
- const util = require("util");
- const logger_1 = require("./logger");
- let breakpointHook = require('./breakpointhook.js');
- let logger = new logger_1.Logger('protractor');
- class DebugHelper {
- constructor(browserUnderDebug_) {
- this.browserUnderDebug_ = browserUnderDebug_;
- }
- initBlocking(debuggerClientPath, onStartFn, opt_debugPort) {
- this.init_(debuggerClientPath, true, onStartFn, opt_debugPort);
- }
- init(debuggerClientPath, onStartFn, opt_debugPort) {
- this.init_(debuggerClientPath, false, onStartFn, opt_debugPort);
- }
- /**
- * 1) Set up helper functions for debugger clients to call on (e.g.
- * execute code, get autocompletion).
- * 2) Enter process into debugger mode. (i.e. process._debugProcess).
- * 3) Invoke the debugger client specified by debuggerClientPath.
- *
- * @param {string} debuggerClientPath Absolute path of debugger client to use.
- * @param {boolean} blockUntilExit Whether to block the flow until process exit or resume
- * immediately.
- * @param {Function} onStartFn Function to call when the debugger starts. The
- * function takes a single parameter, which represents whether this is the
- * first time that the debugger is called.
- * @param {number=} opt_debugPort Optional port to use for the debugging
- * process.
- *
- * @return {Promise} If blockUntilExit, a promise resolved when the debugger process
- * exits. Otherwise, resolved when the debugger process is ready to begin.
- */
- init_(debuggerClientPath, blockUntilExit, onStartFn, opt_debugPort) {
- const vm_ = require('vm');
- let flow = selenium_webdriver_1.promise.controlFlow();
- let context = { require: require };
- global.list = (locator) => {
- return global.protractor.browser.findElements(locator).then((arr) => {
- let found = [];
- for (let i = 0; i < arr.length; ++i) {
- arr[i].getText().then((text) => {
- found.push(text);
- });
- }
- return found;
- });
- };
- for (let key in global) {
- context[key] = global[key];
- }
- let sandbox = vm_.createContext(context);
- let debuggingDone = selenium_webdriver_1.promise.defer();
- // We run one flow.execute block for the debugging session. All
- // subcommands should be scheduled under this task.
- let executePromise = flow.execute(() => {
- process['debugPort'] = opt_debugPort || process['debugPort'];
- this.validatePortAvailability_(process['debugPort']).then((firstTime) => {
- onStartFn(firstTime);
- let args = [process.pid, process['debugPort']];
- if (this.browserUnderDebug_.debuggerServerPort) {
- args.push(this.browserUnderDebug_.debuggerServerPort);
- }
- let nodedebug = require('child_process').fork(debuggerClientPath, args);
- process.on('exit', function () {
- nodedebug.kill('SIGTERM');
- });
- nodedebug
- .on('message', (m) => {
- if (m === 'ready') {
- breakpointHook();
- if (!blockUntilExit) {
- debuggingDone.fulfill();
- }
- }
- })
- .on('exit', () => {
- // Clear this so that we know it's ok to attach a debugger
- // again.
- this.dbgCodeExecutor = null;
- debuggingDone.fulfill();
- });
- });
- return debuggingDone.promise;
- }, 'debugging tasks');
- // Helper used only by debuggers at './debugger/modes/*.js' to insert code
- // into the control flow, via debugger 'evaluate' protocol.
- // In order to achieve this, we maintain a task at the top of the control
- // flow, so that we can insert frames into it.
- // To be able to simulate callback/asynchronous code, we poll this object
- // whenever `breakpointHook` is called.
- this.dbgCodeExecutor = {
- execPromise_: undefined,
- execPromiseResult_: undefined,
- execPromiseError_: undefined,
- // A dummy repl server to make use of its completion function.
- replServer_: require('repl').start({
- input: { on: function () { }, resume: function () { } },
- // dummy readable stream
- output: { write: function () { } },
- useGlobal: true
- }),
- // Execute a function, which could yield a value or a promise,
- // and allow its result to be accessed synchronously
- execute_: function (execFn_) {
- this.execPromiseResult_ = this.execPromiseError_ = undefined;
- this.execPromise_ = execFn_();
- // Note: This needs to be added after setting execPromise to execFn,
- // or else we cause this.execPromise_ to get stuck in pending mode
- // at our next breakpoint.
- this.execPromise_.then((result) => {
- this.execPromiseResult_ = result;
- breakpointHook();
- }, (err) => {
- this.execPromiseError_ = err;
- breakpointHook();
- });
- },
- // Execute a piece of code.
- // Result is a string representation of the evaluation.
- execute: function (code) {
- let execFn_ = () => {
- // Run code through vm so that we can maintain a local scope which is
- // isolated from the rest of the execution.
- let res;
- try {
- res = vm_.runInContext(code, sandbox);
- }
- catch (e) {
- res = selenium_webdriver_1.promise.when('Error while evaluating command: ' + e);
- }
- if (!selenium_webdriver_1.promise.isPromise(res)) {
- res = selenium_webdriver_1.promise.when(res);
- }
- return res.then((res) => {
- if (res === undefined) {
- return undefined;
- }
- else {
- // The '' forces res to be expanded into a string instead of just
- // '[Object]'. Then we remove the extra space caused by the ''
- // using substring.
- return util.format.apply(this, ['', res]).substring(1);
- }
- });
- };
- this.execute_(execFn_);
- },
- // Autocomplete for a line.
- // Result is a JSON representation of the autocomplete response.
- complete: function (line) {
- let execFn_ = () => {
- let deferred = selenium_webdriver_1.promise.defer();
- this.replServer_.complete(line, (err, res) => {
- if (err) {
- deferred.reject(err);
- }
- else {
- deferred.fulfill(JSON.stringify(res));
- }
- });
- return deferred.promise;
- };
- this.execute_(execFn_);
- },
- // Code finished executing.
- resultReady: function () {
- return !(this.execPromise_.state_ === 'pending');
- },
- // Get asynchronous results synchronously.
- // This will throw if result is not ready.
- getResult: function () {
- if (!this.resultReady()) {
- throw new Error('Result not ready');
- }
- if (this.execPromiseError_) {
- throw this.execPromiseError_;
- }
- return this.execPromiseResult_;
- }
- };
- return executePromise;
- }
- /**
- * Validates that the port is free to use. This will only validate the first
- * time it is called. The reason is that on subsequent calls, the port will
- * already be bound to the debugger, so it will not be available, but that is
- * okay.
- *
- * @returns {Promise<boolean>} A promise that becomes ready when the
- * validation
- * is done. The promise will resolve to a boolean which represents whether
- * this is the first time that the debugger is called.
- */
- validatePortAvailability_(port) {
- if (this.debuggerValidated_) {
- return selenium_webdriver_1.promise.when(false);
- }
- let doneDeferred = selenium_webdriver_1.promise.defer();
- // Resolve doneDeferred if port is available.
- let tester = net.connect({ port: port }, () => {
- doneDeferred.reject('Port ' + port + ' is already in use. Please specify ' +
- 'another port to debug.');
- });
- tester.once('error', (err) => {
- if (err.code === 'ECONNREFUSED') {
- tester
- .once('close', () => {
- doneDeferred.fulfill(true);
- })
- .end();
- }
- else {
- doneDeferred.reject('Unexpected failure testing for port ' + port + ': ' + JSON.stringify(err));
- }
- });
- return doneDeferred.promise.then((firstTime) => {
- this.debuggerValidated_ = true;
- return firstTime;
- }, (err) => {
- console.error(err);
- return process.exit(1);
- });
- }
- isAttached() {
- return !!this.dbgCodeExecutor;
- }
- }
- exports.DebugHelper = DebugHelper;
- //# sourceMappingURL=debugger.js.map
|