feat: expose raw devtools protocol connection (#1770)

feat: expose raw devtools protocol connection

This patch introduces `target.createCDPSession` method that
allows directly communicating with the target over the
Chrome DevTools Protocol.

Fixes #31.
This commit is contained in:
Andrey Lushnikov 2018-01-10 19:33:22 -08:00 committed by GitHub
parent ec8e40f1cb
commit 5368051610
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 162 additions and 51 deletions

View File

@ -87,6 +87,7 @@
* [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) * [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
* [page.setViewport(viewport)](#pagesetviewportviewport) * [page.setViewport(viewport)](#pagesetviewportviewport)
* [page.tap(selector)](#pagetapselector) * [page.tap(selector)](#pagetapselector)
* [page.target()](#pagetarget)
* [page.title()](#pagetitle) * [page.title()](#pagetitle)
* [page.touchscreen](#pagetouchscreen) * [page.touchscreen](#pagetouchscreen)
* [page.tracing](#pagetracing) * [page.tracing](#pagetracing)
@ -198,9 +199,13 @@
* [response.text()](#responsetext) * [response.text()](#responsetext)
* [response.url()](#responseurl) * [response.url()](#responseurl)
- [class: Target](#class-target) - [class: Target](#class-target)
* [target.createCDPSession()](#targetcreatecdpsession)
* [target.page()](#targetpage) * [target.page()](#targetpage)
* [target.type()](#targettype) * [target.type()](#targettype)
* [target.url()](#targeturl) * [target.url()](#targeturl)
- [class: CDPSession](#class-cdpsession)
* [cdpSession.detach()](#cdpsessiondetach)
* [cdpSession.send(method[, params])](#cdpsessionsendmethod-params)
- [class: Coverage](#class-coverage) - [class: Coverage](#class-coverage)
* [coverage.startCSSCoverage(options)](#coveragestartcsscoverageoptions) * [coverage.startCSSCoverage(options)](#coveragestartcsscoverageoptions)
* [coverage.startJSCoverage(options)](#coveragestartjscoverageoptions) * [coverage.startJSCoverage(options)](#coveragestartjscoverageoptions)
@ -1139,6 +1144,9 @@ In the case of multiple pages in a single browser, each page can have its own vi
This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.touchscreen](#pagetouchscreen) to tap in the center of the element. This method fetches an element with `selector`, scrolls it into view if needed, and then uses [page.touchscreen](#pagetouchscreen) to tap in the center of the element.
If there's no element matching `selector`, the method throws an error. If there's no element matching `selector`, the method throws an error.
#### page.target()
- returns: <[Target]> a target this page was created from.
#### page.title() #### page.title()
- returns: <[Promise]<[string]>> Returns page's title. - returns: <[Promise]<[string]>> Returns page's title.
@ -2166,6 +2174,11 @@ Contains the URL of the response.
### class: Target ### class: Target
#### target.createCDPSession()
- returns: <[Promise]<[CDPSession]>>
Creates a Chrome Devtools Protocol session attached to the target.
#### target.page() #### target.page()
- returns: <[Promise]<?[Page]>> - returns: <[Promise]<?[Page]>>
@ -2179,6 +2192,40 @@ Identifies what kind of target this is. Can be `"page"`, `"service_worker"`, or
#### target.url() #### target.url()
- returns: <[string]> - returns: <[string]>
### class: CDPSession
* extends: [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter)
The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
- protocol methods can be called with `session.send` method.
- protocol events can be subscribed to with `session.on` method.
Documentation on DevTools Protocol can be found here: [DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/).
```js
const client = await page.target().createCDPSession();
await client.send('Animation.enable');
await client.on('Animation.animationCreated', () => console.log('Animation created!'));
const response = await client.send('Animation.getPlaybackRate');
console.log('playback rate is ' + response.playbackRate);
await client.send('Animation.setPlaybackRate', {
playbackRate: response.playbackRate / 2
});
```
#### cdpSession.detach()
- returns: <[Promise]>
Detaches session from target. Once detached, session won't emit any events and can't be used
to send messages.
#### cdpSession.send(method[, params])
- `method` <[string]> protocol method name
- `params` <[Object]> Optional method parameters
- returns: <[Promise]<[Object]>>
### class: Coverage ### class: Coverage
Coverage gathers information about parts of JavaScript and CSS that were used by the page. Coverage gathers information about parts of JavaScript and CSS that were used by the page.
@ -2253,6 +2300,7 @@ reported.
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise" [Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String" [string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable" [stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable"
[CDPSession]: #class-cdpsession "CDPSession"
[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error" [Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"
[Frame]: #class-frame "Frame" [Frame]: #class-frame "Frame"
[ConsoleMessage]: #class-consolemessage "ConsoleMessage" [ConsoleMessage]: #class-consolemessage "ConsoleMessage"

View File

@ -193,6 +193,7 @@ class Target {
*/ */
constructor(browser, targetInfo) { constructor(browser, targetInfo) {
this._browser = browser; this._browser = browser;
this._targetId = targetInfo.targetId;
this._targetInfo = targetInfo; this._targetInfo = targetInfo;
/** @type {?Promise<!Page>} */ /** @type {?Promise<!Page>} */
this._pagePromise = null; this._pagePromise = null;
@ -202,13 +203,20 @@ class Target {
this._initializedCallback(true); this._initializedCallback(true);
} }
/**
* @return {!Promise<!Puppeteer.CDPSession>}
*/
createCDPSession() {
return this._browser._connection.createSession(this._targetId);
}
/** /**
* @return {!Promise<?Page>} * @return {!Promise<?Page>}
*/ */
async page() { async page() {
if (this._targetInfo.type === 'page' && !this._pagePromise) { if (this._targetInfo.type === 'page' && !this._pagePromise) {
this._pagePromise = this._browser._connection.createSession(this._targetInfo.targetId) this._pagePromise = this._browser._connection.createSession(this._targetId)
.then(client => Page.create(client, this._browser._ignoreHTTPSErrors, this._browser._appMode, this._browser._screenshotTaskQueue)); .then(client => Page.create(client, this, this._browser._ignoreHTTPSErrors, this._browser._appMode, this._browser._screenshotTaskQueue));
} }
return this._pagePromise; return this._pagePromise;
} }
@ -258,4 +266,4 @@ helper.tracePublicAPI(Target);
* @property {boolean} attached * @property {boolean} attached
*/ */
module.exports = { Browser, TaskQueue }; module.exports = { Browser, TaskQueue, Target };

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const {helper} = require('./helper');
const debugProtocol = require('debug')('puppeteer:protocol'); const debugProtocol = require('debug')('puppeteer:protocol');
const debugSession = require('debug')('puppeteer:session'); const debugSession = require('debug')('puppeteer:session');
@ -49,7 +50,7 @@ class Connection extends EventEmitter {
this._ws = ws; this._ws = ws;
this._ws.on('message', this._onMessage.bind(this)); this._ws.on('message', this._onMessage.bind(this));
this._ws.on('close', this._onClose.bind(this)); this._ws.on('close', this._onClose.bind(this));
/** @type {!Map<string, !Session>}*/ /** @type {!Map<string, !CDPSession>}*/
this._sessions = new Map(); this._sessions = new Map();
} }
@ -135,17 +136,17 @@ class Connection extends EventEmitter {
/** /**
* @param {string} targetId * @param {string} targetId
* @return {!Promise<!Session>} * @return {!Promise<!CDPSession>}
*/ */
async createSession(targetId) { async createSession(targetId) {
const {sessionId} = await this.send('Target.attachToTarget', {targetId}); const {sessionId} = await this.send('Target.attachToTarget', {targetId});
const session = new Session(this, targetId, sessionId); const session = new CDPSession(this, targetId, sessionId);
this._sessions.set(sessionId, session); this._sessions.set(sessionId, session);
return session; return session;
} }
} }
class Session extends EventEmitter { class CDPSession extends EventEmitter {
/** /**
* @param {!Connection} connection * @param {!Connection} connection
* @param {string} targetId * @param {string} targetId
@ -161,13 +162,6 @@ class Session extends EventEmitter {
this._sessionId = sessionId; this._sessionId = sessionId;
} }
/**
* @return {string}
*/
targetId() {
return this._targetId;
}
/** /**
* @param {string} method * @param {string} method
* @param {!Object=} params * @param {!Object=} params
@ -211,9 +205,8 @@ class Session extends EventEmitter {
} }
} }
async dispose() { async detach() {
console.assert(!!this._connection, 'Protocol error: Connection closed. Most likely the page has been closed.'); await this._connection.send('Target.detachFromTarget', {sessionId: this._sessionId});
await this._connection.send('Target.closeTarget', {targetId: this._targetId});
} }
_onClosed() { _onClosed() {
@ -223,6 +216,7 @@ class Session extends EventEmitter {
this._connection = null; this._connection = null;
} }
} }
helper.tracePublicAPI(CDPSession);
/** /**
* @param {!Error} error * @param {!Error} error
@ -234,4 +228,4 @@ function rewriteError(error, message) {
return error; return error;
} }
module.exports = {Connection, Session}; module.exports = {Connection, CDPSession};

View File

@ -25,7 +25,7 @@ const {helper, debugError} = require('./helper');
class Coverage { class Coverage {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
*/ */
constructor(client) { constructor(client) {
this._jsCoverage = new JSCoverage(client); this._jsCoverage = new JSCoverage(client);
@ -66,7 +66,7 @@ helper.tracePublicAPI(Coverage);
class JSCoverage { class JSCoverage {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
*/ */
constructor(client) { constructor(client) {
this._client = client; this._client = client;
@ -154,7 +154,7 @@ class JSCoverage {
class CSSCoverage { class CSSCoverage {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
*/ */
constructor(client) { constructor(client) {
this._client = client; this._client = client;

View File

@ -18,7 +18,7 @@ const {helper} = require('./helper');
class Dialog { class Dialog {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {string} type * @param {string} type
* @param {string} message * @param {string} message
* @param {(string|undefined)} defaultValue * @param {(string|undefined)} defaultValue

View File

@ -20,7 +20,7 @@ const {helper, debugError} = require('./helper');
class ElementHandle extends JSHandle { class ElementHandle extends JSHandle {
/** /**
* @param {!Puppeteer.ExecutionContext} context * @param {!Puppeteer.ExecutionContext} context
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Object} remoteObject * @param {!Object} remoteObject
* @param {!Puppeteer.Page} page * @param {!Puppeteer.Page} page
*/ */

View File

@ -16,7 +16,7 @@
class EmulationManager { class EmulationManager {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
*/ */
constructor(client) { constructor(client) {
this._client = client; this._client = client;

View File

@ -18,7 +18,7 @@ const {helper} = require('./helper');
class ExecutionContext { class ExecutionContext {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Object} contextPayload * @param {!Object} contextPayload
* @param {function(*):!JSHandle} objectHandleFactory * @param {function(*):!JSHandle} objectHandleFactory
*/ */
@ -117,7 +117,7 @@ class ExecutionContext {
class JSHandle { class JSHandle {
/** /**
* @param {!ExecutionContext} context * @param {!ExecutionContext} context
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Object} remoteObject * @param {!Object} remoteObject
*/ */
constructor(context, client, remoteObject) { constructor(context, client, remoteObject) {

View File

@ -24,7 +24,7 @@ const readFileAsync = helper.promisify(fs.readFile);
class FrameManager extends EventEmitter { class FrameManager extends EventEmitter {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {{frame: Object, childFrames: ?Array}} frameTree * @param {{frame: Object, childFrames: ?Array}} frameTree
* @param {!Puppeteer.Page} page * @param {!Puppeteer.Page} page
*/ */
@ -226,7 +226,7 @@ FrameManager.Events = {
*/ */
class Frame { class Frame {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {?Frame} parentFrame * @param {?Frame} parentFrame
* @param {string} frameId * @param {string} frameId
*/ */

View File

@ -28,7 +28,7 @@ const keyDefinitions = require('./USKeyboardLayout');
class Keyboard { class Keyboard {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
*/ */
constructor(client) { constructor(client) {
this._client = client; this._client = client;
@ -189,7 +189,7 @@ class Keyboard {
class Mouse { class Mouse {
/** /**
* @param {Puppeteer.Session} client * @param {Puppeteer.CDPSession} client
* @param {!Keyboard} keyboard * @param {!Keyboard} keyboard
*/ */
constructor(client, keyboard) { constructor(client, keyboard) {
@ -268,7 +268,7 @@ class Mouse {
class Touchscreen { class Touchscreen {
/** /**
* @param {Puppeteer.Session} client * @param {Puppeteer.CDPSession} client
* @param {Keyboard} keyboard * @param {Keyboard} keyboard
*/ */
constructor(client, keyboard) { constructor(client, keyboard) {

View File

@ -19,7 +19,7 @@ const Multimap = require('./Multimap');
class NetworkManager extends EventEmitter { class NetworkManager extends EventEmitter {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.FrameManager} frameManager * @param {!Puppeteer.FrameManager} frameManager
*/ */
constructor(client, frameManager) { constructor(client, frameManager) {
@ -281,7 +281,7 @@ class NetworkManager extends EventEmitter {
class Request { class Request {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {?string} requestId * @param {?string} requestId
* @param {string} interceptionId * @param {string} interceptionId
* @param {boolean} allowInterception * @param {boolean} allowInterception
@ -479,7 +479,7 @@ helper.tracePublicAPI(Request);
class Response { class Response {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Request} request * @param {!Request} request
* @param {number} status * @param {number} status
* @param {!Object} headers * @param {!Object} headers

View File

@ -31,17 +31,18 @@ const writeFileAsync = helper.promisify(fs.writeFile);
class Page extends EventEmitter { class Page extends EventEmitter {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.Target} target
* @param {boolean} ignoreHTTPSErrors * @param {boolean} ignoreHTTPSErrors
* @param {boolean} appMode * @param {boolean} appMode
* @param {!Puppeteer.TaskQueue} screenshotTaskQueue * @param {!Puppeteer.TaskQueue} screenshotTaskQueue
* @return {!Promise<!Page>} * @return {!Promise<!Page>}
*/ */
static async create(client, ignoreHTTPSErrors, appMode, screenshotTaskQueue) { static async create(client, target, ignoreHTTPSErrors, appMode, screenshotTaskQueue) {
await client.send('Page.enable'); await client.send('Page.enable');
const {frameTree} = await client.send('Page.getFrameTree'); const {frameTree} = await client.send('Page.getFrameTree');
const page = new Page(client, frameTree, ignoreHTTPSErrors, screenshotTaskQueue); const page = new Page(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue);
await Promise.all([ await Promise.all([
client.send('Page.setLifecycleEventsEnabled', { enabled: true }), client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
@ -60,14 +61,16 @@ class Page extends EventEmitter {
} }
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Puppeteer.Target} target
* @param {{frame: Object, childFrames: ?Array}} frameTree * @param {{frame: Object, childFrames: ?Array}} frameTree
* @param {boolean} ignoreHTTPSErrors * @param {boolean} ignoreHTTPSErrors
* @param {!Puppeteer.TaskQueue} screenshotTaskQueue * @param {!Puppeteer.TaskQueue} screenshotTaskQueue
*/ */
constructor(client, frameTree, ignoreHTTPSErrors, screenshotTaskQueue) { constructor(client, target, frameTree, ignoreHTTPSErrors, screenshotTaskQueue) {
super(); super();
this._client = client; this._client = client;
this._target = target;
this._keyboard = new Keyboard(client); this._keyboard = new Keyboard(client);
this._mouse = new Mouse(client, this._keyboard); this._mouse = new Mouse(client, this._keyboard);
this._touchscreen = new Touchscreen(client, this._keyboard); this._touchscreen = new Touchscreen(client, this._keyboard);
@ -101,6 +104,13 @@ class Page extends EventEmitter {
client.on('Performance.metrics', event => this._emitMetrics(event)); client.on('Performance.metrics', event => this._emitMetrics(event));
} }
/**
* @return {!Puppeteer.Target}
*/
target() {
return this._target;
}
_onTargetCrashed() { _onTargetCrashed() {
this.emit('error', new Error('Page crashed!')); this.emit('error', new Error('Page crashed!'));
} }
@ -505,7 +515,7 @@ class Page extends EventEmitter {
return request ? request.response() : null; return request ? request.response() : null;
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {string} url * @param {string} url
* @param {string} referrer * @param {string} referrer
* @return {!Promise<?Error>} * @return {!Promise<?Error>}
@ -691,7 +701,7 @@ class Page extends EventEmitter {
* @return {!Promise<!Buffer>} * @return {!Promise<!Buffer>}
*/ */
async _screenshotTask(format, options) { async _screenshotTask(format, options) {
await this._client.send('Target.activateTarget', {targetId: this._client.targetId()}); await this._client.send('Target.activateTarget', {targetId: this._target._targetId});
let clip = options.clip ? Object.assign({}, options['clip']) : undefined; let clip = options.clip ? Object.assign({}, options['clip']) : undefined;
if (clip) if (clip)
clip.scale = 1; clip.scale = 1;
@ -785,7 +795,8 @@ class Page extends EventEmitter {
} }
async close() { async close() {
await this._client.dispose(); console.assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
await this._client._connection.send('Target.closeTarget', {targetId: this._target._targetId});
} }
/** /**

View File

@ -22,7 +22,7 @@ const closeAsync = helper.promisify(fs.close);
class Tracing { class Tracing {
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
*/ */
constructor(client) { constructor(client) {
this._client = client; this._client = client;

7
lib/externs.d.ts vendored
View File

@ -1,5 +1,5 @@
import { Connection as RealConnection, Session as RealSession } from './Connection.js'; import { Connection as RealConnection, CDPSession as RealCDPSession } from './Connection.js';
import {Browser as RealBrowser, TaskQueue as RealTaskQueue} from './Browser.js'; import {Browser as RealBrowser, TaskQueue as RealTaskQueue, Target as RealTarget} from './Browser.js';
import * as RealPage from './Page.js'; import * as RealPage from './Page.js';
import {Mouse as RealMouse, Keyboard as RealKeyboard, Touchscreen as RealTouchscreen} from './Input.js'; import {Mouse as RealMouse, Keyboard as RealKeyboard, Touchscreen as RealTouchscreen} from './Input.js';
import {Frame as RealFrame, FrameManager as RealFrameManager} from './FrameManager.js'; import {Frame as RealFrame, FrameManager as RealFrameManager} from './FrameManager.js';
@ -10,12 +10,13 @@ import * as child_process from 'child_process';
export as namespace Puppeteer; export as namespace Puppeteer;
export class Connection extends RealConnection {} export class Connection extends RealConnection {}
export class Session extends RealSession {} export class CDPSession extends RealCDPSession {}
export class Mouse extends RealMouse {} export class Mouse extends RealMouse {}
export class Keyboard extends RealKeyboard {} export class Keyboard extends RealKeyboard {}
export class Touchscreen extends RealTouchscreen {} export class Touchscreen extends RealTouchscreen {}
export class TaskQueue extends RealTaskQueue {} export class TaskQueue extends RealTaskQueue {}
export class Browser extends RealBrowser {} export class Browser extends RealBrowser {}
export class Target extends RealTarget {}
export class Frame extends RealFrame {} export class Frame extends RealFrame {}
export class FrameManager extends RealFrameManager {} export class FrameManager extends RealFrameManager {}
export class NetworkManager extends RealNetworkManager {} export class NetworkManager extends RealNetworkManager {}

View File

@ -83,7 +83,7 @@ class Helper {
} }
/** /**
* @param {!Puppeteer.Session} client * @param {!Puppeteer.CDPSession} client
* @param {!Object} remoteObject * @param {!Object} remoteObject
*/ */
static async releaseObject(client, remoteObject) { static async releaseObject(client, remoteObject) {

View File

@ -3474,6 +3474,56 @@ describe('Page', function() {
}); });
}); });
describe('Target.createCDPSession', function() {
it('should work', async function({page, server}) {
const client = await page.target().createCDPSession();
await Promise.all([
client.send('Runtime.enable'),
client.send('Runtime.evaluate', { expression: 'window.foo = "bar"' })
]);
const foo = await page.evaluate(() => window.foo);
expect(foo).toBe('bar');
});
it('should send events', async function({page, server}) {
const client = await page.target().createCDPSession();
await client.send('Network.enable');
const events = [];
client.on('Network.requestWillBeSent', event => events.push(event));
await page.goto(server.EMPTY_PAGE);
expect(events.length).toBe(1);
});
it('should enable and disable domains independently', async function({page, server}) {
const client = await page.target().createCDPSession();
await client.send('Runtime.enable');
await client.send('Debugger.enable');
// JS coverage enables and then disables Debugger domain.
await page.coverage.startJSCoverage();
await page.coverage.stopJSCoverage();
// generate a script in page and wait for the event.
const [event] = await Promise.all([
waitForEvents(client, 'Debugger.scriptParsed'),
page.evaluate('//# sourceURL=foo.js')
]);
// expect events to be dispatched.
expect(event.url).toBe('foo.js');
});
it('should be able to detach session', async function({page, server}) {
const client = await page.target().createCDPSession();
await client.send('Runtime.enable');
const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true});
expect(evalResponse.result.value).toBe(3);
await client.detach();
let error = null;
try {
await client.send('Runtime.evaluate', {expression: '3 + 1', returnByValue: true});
} catch (e) {
error = e;
}
expect(error.message).toContain('Session closed.');
});
});
describe('JSCoverage', function() { describe('JSCoverage', function() {
it('should work', async function({page, server}) { it('should work', async function({page, server}) {
await page.coverage.startJSCoverage(); await page.coverage.startJSCoverage();
@ -3656,7 +3706,7 @@ runner.run();
* @param {!EventEmitter} emitter * @param {!EventEmitter} emitter
* @param {string} eventName * @param {string} eventName
* @param {number=} eventCount * @param {number=} eventCount
* @return {!Promise} * @return {!Promise<!Object>}
*/ */
function waitForEvents(emitter, eventName, eventCount = 1) { function waitForEvents(emitter, eventName, eventCount = 1) {
let fulfill; let fulfill;
@ -3664,12 +3714,12 @@ function waitForEvents(emitter, eventName, eventCount = 1) {
emitter.on(eventName, onEvent); emitter.on(eventName, onEvent);
return promise; return promise;
function onEvent() { function onEvent(event) {
--eventCount; --eventCount;
if (eventCount) if (eventCount)
return; return;
emitter.removeListener(eventName, onEvent); emitter.removeListener(eventName, onEvent);
fulfill(); fulfill(event);
} }
} }

View File

@ -31,7 +31,6 @@ const EXCLUDE_CLASSES = new Set([
'Multimap', 'Multimap',
'NavigatorWatcher', 'NavigatorWatcher',
'NetworkManager', 'NetworkManager',
'Session',
'TaskQueue', 'TaskQueue',
'WaitTask', 'WaitTask',
]); ]);