Introduce page.tracing (#181)

This patch introduces page.tracing, which allows to start and stop
trace recording for a page. The trace could be then saved to file.
This commit is contained in:
JoelEinbinder 2017-08-02 10:45:11 -07:00 committed by Andrey Lushnikov
parent 0218960713
commit bd72e40e73
5 changed files with 148 additions and 0 deletions

View File

@ -54,6 +54,7 @@
+ [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
+ [page.setViewport(viewport)](#pagesetviewportviewport)
+ [page.title()](#pagetitle)
+ [page.tracing](#pagetracing)
+ [page.type(text, options)](#pagetypetext-options)
+ [page.uploadFile(selector, ...filePaths)](#pageuploadfileselector-filepaths)
+ [page.url()](#pageurl)
@ -71,6 +72,9 @@
+ [mouse.down([options])](#mousedownoptions)
+ [mouse.move(x, y)](#mousemovex-y)
+ [mouse.up([options])](#mouseupoptions)
* [class: Tracing](#class-tracing)
+ [tracing.start([options])](#tracingstartoptions)
+ [tracing.stop(path)](#tracingstoppath)
* [class: Dialog](#class-dialog)
+ [dialog.accept([promptText])](#dialogacceptprompttext)
+ [dialog.dismiss()](#dialogdismiss)
@ -638,6 +642,9 @@ In case of multiple pages in one browser, each page can have its own viewport si
Shortcut for [page.mainFrame().title()](#frametitle).
#### page.tracing
- returns: <[Tracing]>
#### page.type(text, options)
- `text` <[string]> A text to type into a focused element.
- `options` <[Object]>
@ -827,6 +834,25 @@ Dispatches a `mousemove` event.
Dispatches a `mouseup` event.
### class: Tracing
You can use [`tracing.start`](#tracingstartoptions) and [`tracing.stop`](#tracingstoppath) to create a trace file which can be opened in Chrome DevTools or [timeline viewer](https://chromedevtools.github.io/timeline-viewer/).
```js
await page.tracing.start();
await page.navigate('https://www.google.com');
await page.tracing.stop('trace.json');
```
#### tracing.start([options])
- `options` <[Object]>
- `screenshots` <[boolean]> captures screenshots in the trace.
- returns: <[Promise]>
#### tracing.stop(path)
- `path` <[string]> A path to write the trace file to.
- returns: <[Promise]>
### class: Dialog
[Dialog] objects are dispatched by page via the ['dialog'](#event-dialog) event.
@ -1194,3 +1220,4 @@ If changed, the request url will be modified in a way that's not observable by p
[Mouse]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-mouse "Mouse"
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
[Tracing]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-tracing "Tracing"

View File

@ -23,6 +23,7 @@ let Dialog = require('./Dialog');
let EmulationManager = require('./EmulationManager');
let FrameManager = require('./FrameManager');
let {Keyboard, Mouse} = require('./Input');
let Tracing = require('./Tracing');
let helper = require('./helper');
class Page extends EventEmitter {
@ -61,6 +62,7 @@ class Page extends EventEmitter {
this._frameManager = new FrameManager(client, this._mouse);
this._networkManager = new NetworkManager(client);
this._emulationManager = new EmulationManager(client);
this._tracing = new Tracing(client);
/** @type {!Map<string, function>} */
this._inPageCallbacks = new Map();
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
@ -98,6 +100,13 @@ class Page extends EventEmitter {
return this._keyboard;
}
/**
* @return {!Tracing}
*/
get tracing() {
return this._tracing;
}
/**
* @return {!Array<!Frame>}
*/

85
lib/Tracing.js Normal file
View File

@ -0,0 +1,85 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const helper = require('./helper');
class Tracing {
/**
* @param {!Connection} client
*/
constructor(client) {
this._client = client;
this._recording = false;
}
/**
* @param {Object=} options
*/
async start(options = {}) {
console.assert(!this._recording, 'Cannot start recording trace while already recording trace.');
const categoriesArray = [
'-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline',
'disabled-by-default-devtools.timeline.frame', 'toplevel',
'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack',
'disabled-by-default-v8.cpu_profiler'
];
if (options.screenshots)
categoriesArray.push('disabled-by-default-devtools.screenshot');
this._recording = true;
await this._client.send('Tracing.start', {
transferMode: 'ReturnAsStream',
categories: categoriesArray.join(',')
});
}
/**
* @param {string} path
*/
async stop(path) {
let fulfill;
let contentPromise = new Promise(x => fulfill = x);
this._client.once('Tracing.tracingComplete', event => {
this._readStream(event.stream, path).then(fulfill);
});
await this._client.send('Tracing.end');
this._recording = false;
return contentPromise;
}
/**
* @param {string} handle
* @param {string=} path
* @return {!Promise}
*/
async _readStream(handle, path) {
let eof = false;
let file = fs.openSync(path, 'w');
while (!eof) {
let response = await this._client.send('IO.read', {handle});
eof = response.eof;
if (path)
fs.writeSync(file, response.data);
}
fs.closeSync(file);
await this._client.send('IO.close', {handle});
}
}
helper.tracePublicAPI(Tracing);
module.exports = Tracing;

View File

@ -1481,6 +1481,32 @@ describe('Page', function() {
await Promise.all(pages.map(page => page.close()));
}));
});
describe('Tracing', function() {
let outputFile = path.join(__dirname, 'assets', 'trace.json');
afterEach(function() {
fs.unlinkSync(outputFile);
});
it('should output a trace', SX(async function() {
await page.tracing.start({screenshots: true});
await page.navigate(PREFIX + '/grid.html');
await page.tracing.stop(outputFile);
expect(fs.existsSync(outputFile)).toBe(true);
}));
it('should throw if tracing on two pages', SX(async function() {
await page.tracing.start();
let newPage = await browser.newPage();
let error = null;
try {
await newPage.tracing.start();
} catch (e) {
error = e;
}
await newPage.close();
expect(error).toBeTruthy();
await page.tracing.stop(outputFile);
}));
});
});
if (process.env.COVERAGE) {

View File

@ -40,6 +40,7 @@ const EXCLUDE_METHODS = new Set([
'InterceptedRequest.constructor',
'Keyboard.constructor',
'Mouse.constructor',
'Tracing.constructor',
'Page.constructor',
'Page.create',
'Request.constructor',