Tracing.ts (3634B)
1 /** 2 * @license 3 * Copyright 2017 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 import type {CDPSession} from '../api/CDPSession.js'; 7 import { 8 getReadableAsTypedArray, 9 getReadableFromProtocolStream, 10 } from '../common/util.js'; 11 import {assert} from '../util/assert.js'; 12 import {Deferred} from '../util/Deferred.js'; 13 import {isErrorLike} from '../util/ErrorLike.js'; 14 15 /** 16 * @public 17 */ 18 export interface TracingOptions { 19 path?: string; 20 screenshots?: boolean; 21 categories?: string[]; 22 } 23 24 /** 25 * The Tracing class exposes the tracing audit interface. 26 * @remarks 27 * You can use `tracing.start` and `tracing.stop` to create a trace file 28 * which can be opened in Chrome DevTools or {@link https://chromedevtools.github.io/timeline-viewer/ | timeline viewer}. 29 * 30 * @example 31 * 32 * ```ts 33 * await page.tracing.start({path: 'trace.json'}); 34 * await page.goto('https://www.google.com'); 35 * await page.tracing.stop(); 36 * ``` 37 * 38 * @public 39 */ 40 export class Tracing { 41 #client: CDPSession; 42 #recording = false; 43 #path?: string; 44 45 /** 46 * @internal 47 */ 48 constructor(client: CDPSession) { 49 this.#client = client; 50 } 51 52 /** 53 * @internal 54 */ 55 updateClient(client: CDPSession): void { 56 this.#client = client; 57 } 58 59 /** 60 * Starts a trace for the current page. 61 * @remarks 62 * Only one trace can be active at a time per browser. 63 * 64 * @param options - Optional `TracingOptions`. 65 */ 66 async start(options: TracingOptions = {}): Promise<void> { 67 assert( 68 !this.#recording, 69 'Cannot start recording trace while already recording trace.', 70 ); 71 72 const defaultCategories = [ 73 '-*', 74 'devtools.timeline', 75 'v8.execute', 76 'disabled-by-default-devtools.timeline', 77 'disabled-by-default-devtools.timeline.frame', 78 'toplevel', 79 'blink.console', 80 'blink.user_timing', 81 'latencyInfo', 82 'disabled-by-default-devtools.timeline.stack', 83 'disabled-by-default-v8.cpu_profiler', 84 ]; 85 const {path, screenshots = false, categories = defaultCategories} = options; 86 87 if (screenshots) { 88 categories.push('disabled-by-default-devtools.screenshot'); 89 } 90 91 const excludedCategories = categories 92 .filter(cat => { 93 return cat.startsWith('-'); 94 }) 95 .map(cat => { 96 return cat.slice(1); 97 }); 98 const includedCategories = categories.filter(cat => { 99 return !cat.startsWith('-'); 100 }); 101 102 this.#path = path; 103 this.#recording = true; 104 await this.#client.send('Tracing.start', { 105 transferMode: 'ReturnAsStream', 106 traceConfig: { 107 excludedCategories, 108 includedCategories, 109 }, 110 }); 111 } 112 113 /** 114 * Stops a trace started with the `start` method. 115 * @returns Promise which resolves to buffer with trace data. 116 */ 117 async stop(): Promise<Uint8Array | undefined> { 118 const contentDeferred = Deferred.create<Uint8Array | undefined>(); 119 this.#client.once('Tracing.tracingComplete', async event => { 120 try { 121 assert(event.stream, 'Missing "stream"'); 122 const readable = await getReadableFromProtocolStream( 123 this.#client, 124 event.stream, 125 ); 126 const typedArray = await getReadableAsTypedArray(readable, this.#path); 127 contentDeferred.resolve(typedArray ?? undefined); 128 } catch (error) { 129 if (isErrorLike(error)) { 130 contentDeferred.reject(error); 131 } else { 132 contentDeferred.reject(new Error(`Unknown error: ${error}`)); 133 } 134 } 135 }); 136 await this.#client.send('Tracing.end'); 137 this.#recording = false; 138 return await contentDeferred.valueOrThrow(); 139 } 140 }