1/** 2 * Copyright 2017 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16import { assert } from './assert.js'; 17import { helper } from './helper.js'; 18import { CDPSession } from './Connection.js'; 19 20/** 21 * @public 22 */ 23export interface TracingOptions { 24 path?: string; 25 screenshots?: boolean; 26 categories?: string[]; 27} 28 29/** 30 * The Tracing class exposes the tracing audit interface. 31 * @remarks 32 * You can use `tracing.start` and `tracing.stop` to create a trace file 33 * which can be opened in Chrome DevTools or {@link https://chromedevtools.github.io/timeline-viewer/ | timeline viewer}. 34 * 35 * @example 36 * ```js 37 * await page.tracing.start({path: 'trace.json'}); 38 * await page.goto('https://www.google.com'); 39 * await page.tracing.stop(); 40 * ``` 41 * 42 * @public 43 */ 44export class Tracing { 45 _client: CDPSession; 46 _recording = false; 47 _path = ''; 48 49 /** 50 * @internal 51 */ 52 constructor(client: CDPSession) { 53 this._client = client; 54 } 55 56 /** 57 * Starts a trace for the current page. 58 * @remarks 59 * Only one trace can be active at a time per browser. 60 * @param options - Optional `TracingOptions`. 61 */ 62 async start(options: TracingOptions = {}): Promise<void> { 63 assert( 64 !this._recording, 65 'Cannot start recording trace while already recording trace.' 66 ); 67 68 const defaultCategories = [ 69 '-*', 70 'devtools.timeline', 71 'v8.execute', 72 'disabled-by-default-devtools.timeline', 73 'disabled-by-default-devtools.timeline.frame', 74 'toplevel', 75 'blink.console', 76 'blink.user_timing', 77 'latencyInfo', 78 'disabled-by-default-devtools.timeline.stack', 79 'disabled-by-default-v8.cpu_profiler', 80 ]; 81 const { 82 path = null, 83 screenshots = false, 84 categories = defaultCategories, 85 } = options; 86 87 if (screenshots) categories.push('disabled-by-default-devtools.screenshot'); 88 89 const excludedCategories = categories 90 .filter((cat) => cat.startsWith('-')) 91 .map((cat) => cat.slice(1)); 92 const includedCategories = categories.filter((cat) => !cat.startsWith('-')); 93 94 this._path = path; 95 this._recording = true; 96 await this._client.send('Tracing.start', { 97 transferMode: 'ReturnAsStream', 98 traceConfig: { 99 excludedCategories, 100 includedCategories, 101 }, 102 }); 103 } 104 105 /** 106 * Stops a trace started with the `start` method. 107 * @returns Promise which resolves to buffer with trace data. 108 */ 109 async stop(): Promise<Buffer> { 110 let fulfill: (value: Buffer) => void; 111 let reject: (err: Error) => void; 112 const contentPromise = new Promise<Buffer>((x, y) => { 113 fulfill = x; 114 reject = y; 115 }); 116 this._client.once('Tracing.tracingComplete', async (event) => { 117 try { 118 const readable = await helper.getReadableFromProtocolStream( 119 this._client, 120 event.stream 121 ); 122 const buffer = await helper.getReadableAsBuffer(readable, this._path); 123 fulfill(buffer); 124 } catch (error) { 125 reject(error); 126 } 127 }); 128 await this._client.send('Tracing.end'); 129 this._recording = false; 130 return contentPromise; 131 } 132} 133