diff --git a/docs/api.md b/docs/api.md index b2c9a79e..99538f22 100644 --- a/docs/api.md +++ b/docs/api.md @@ -13,6 +13,7 @@ * [puppeteer.executablePath()](#puppeteerexecutablepath) * [puppeteer.launch([options])](#puppeteerlaunchoptions) - [class: Browser](#class-browser) + * [event: 'disconnected'](#event-disconnected) * [event: 'targetchanged'](#event-targetchanged) * [event: 'targetcreated'](#event-targetcreated) * [event: 'targetdestroyed'](#event-targetdestroyed) @@ -291,6 +292,10 @@ puppeteer.launch().then(async browser => { await browser2.close(); }); ``` +#### event: 'disconnected' +Emitted when puppeteer gets disconnected from the browser instance. This might happen because one of the following: +- browser closed or crashed +- `browser.disconnect` method was called #### event: 'targetchanged' - <[Target]> diff --git a/lib/Browser.js b/lib/Browser.js index 5fa19825..d84b8bbe 100644 --- a/lib/Browser.js +++ b/lib/Browser.js @@ -33,6 +33,9 @@ class Browser extends EventEmitter { this._closeCallback = closeCallback || new Function(); /** @type {Map} */ this._targets = new Map(); + this._connection.setClosedCallback(() => { + this.emit(Browser.Events.Disconnected); + }); this._connection.on('Target.targetCreated', this._targetCreated.bind(this)); this._connection.on('Target.targetDestroyed', this._targetDestroyed.bind(this)); this._connection.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); @@ -136,7 +139,8 @@ class Browser extends EventEmitter { Browser.Events = { TargetCreated: 'targetcreated', TargetDestroyed: 'targetdestroyed', - TargetChanged: 'targetchanged' + TargetChanged: 'targetchanged', + Disconnected: 'disconnected' }; helper.tracePublicAPI(Browser); diff --git a/lib/Connection.js b/lib/Connection.js index dbf1ea8f..a54988ef 100644 --- a/lib/Connection.js +++ b/lib/Connection.js @@ -75,6 +75,13 @@ class Connection extends EventEmitter { }); } + /** + * @param {function()} callback + */ + setClosedCallback(callback) { + this._closeCallback = callback; + } + /** * @param {string} message */ @@ -108,6 +115,10 @@ class Connection extends EventEmitter { } _onClose() { + if (this._closeCallback) { + this._closeCallback(); + this._closeCallback = null; + } this._ws.removeAllListeners(); for (const callback of this._callbacks.values()) callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`)); diff --git a/test/test.js b/test/test.js index c22655d3..4e31f642 100644 --- a/test/test.js +++ b/test/test.js @@ -255,6 +255,32 @@ describe('Page', function() { })); }); + describe('Browser.Events.disconnected', function() { + it('should emitted when: browser gets closed, disconnected or underlying websocket gets closed', SX(async function() { + const originalBrowser = await puppeteer.launch(defaultBrowserOptions); + const browserWSEndpoint = originalBrowser.wsEndpoint(); + const remoteBrowser1 = await puppeteer.connect({browserWSEndpoint}); + const remoteBrowser2 = await puppeteer.connect({browserWSEndpoint}); + + let disconnectedOriginal = 0; + let disconnectedRemote1 = 0; + let disconnectedRemote2 = 0; + originalBrowser.on('disconnected', () => ++disconnectedOriginal); + remoteBrowser1.on('disconnected', () => ++disconnectedRemote1); + remoteBrowser2.on('disconnected', () => ++disconnectedRemote2); + + await remoteBrowser2.disconnect(); + expect(disconnectedOriginal).toBe(0); + expect(disconnectedRemote1).toBe(0); + expect(disconnectedRemote2).toBe(1); + + await originalBrowser.close(); + expect(disconnectedOriginal).toBe(1); + expect(disconnectedRemote1).toBe(1); + expect(disconnectedRemote2).toBe(1); + })); + }); + describe('Page.close', function() { it('should reject all promises when page is closed', SX(async function() { const newPage = await browser.newPage();