mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-27 21:58:52 +03:00
chore: save chrome trace on the client side (#24414)
This commit is contained in:
parent
e036603aa3
commit
4949cef09c
@ -60,6 +60,20 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel> {
|
||||
return stream.stream();
|
||||
}
|
||||
|
||||
async readIntoBuffer(): Promise<Buffer> {
|
||||
const stream = (await this.createReadStream())!;
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
stream.on('data', (chunk: Buffer) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
stream.on('end', () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async cancel(): Promise<void> {
|
||||
return this._channel.cancel();
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||
import type { Page } from './page';
|
||||
@ -24,6 +25,8 @@ import { isSafeCloseError, kBrowserClosedError } from '../common/errors';
|
||||
import type * as api from '../../types/types';
|
||||
import { CDPSession } from './cdpSession';
|
||||
import type { BrowserType } from './browserType';
|
||||
import { Artifact } from './artifact';
|
||||
import { mkdirIfNeeded } from '../utils';
|
||||
|
||||
export class Browser extends ChannelOwner<channels.BrowserChannel> implements api.Browser {
|
||||
readonly _contexts = new Set<BrowserContext>();
|
||||
@ -33,6 +36,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||
_browserType!: BrowserType;
|
||||
_options: LaunchOptions = {};
|
||||
readonly _name: string;
|
||||
private _path: string | undefined;
|
||||
|
||||
// Used from @playwright/test fixtures.
|
||||
_connectHeaders?: HeadersArray;
|
||||
@ -104,11 +108,20 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||
}
|
||||
|
||||
async startTracing(page?: Page, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
|
||||
this._path = options.path;
|
||||
await this._channel.startTracing({ ...options, page: page ? page._channel : undefined });
|
||||
}
|
||||
|
||||
async stopTracing(): Promise<Buffer> {
|
||||
return (await this._channel.stopTracing()).binary;
|
||||
const artifact = Artifact.from((await this._channel.stopTracing()).artifact);
|
||||
const buffer = await artifact.readIntoBuffer();
|
||||
await artifact.delete();
|
||||
if (this._path) {
|
||||
await mkdirIfNeeded(this._path);
|
||||
await fs.promises.writeFile(this._path, buffer);
|
||||
this._path = undefined;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
@ -723,14 +723,13 @@ scheme.BrowserNewBrowserCDPSessionResult = tObject({
|
||||
});
|
||||
scheme.BrowserStartTracingParams = tObject({
|
||||
page: tOptional(tChannel(['Page'])),
|
||||
path: tOptional(tString),
|
||||
screenshots: tOptional(tBoolean),
|
||||
categories: tOptional(tArray(tString)),
|
||||
});
|
||||
scheme.BrowserStartTracingResult = tOptional(tObject({}));
|
||||
scheme.BrowserStopTracingParams = tOptional(tObject({}));
|
||||
scheme.BrowserStopTracingResult = tObject({
|
||||
binary: tBinary,
|
||||
artifact: tChannel(['Artifact']),
|
||||
});
|
||||
scheme.EventTargetInitializer = tOptional(tObject({}));
|
||||
scheme.EventTargetWaitForEventInfoParams = tObject({
|
||||
|
@ -16,9 +16,10 @@
|
||||
*/
|
||||
|
||||
import type { BrowserOptions } from '../browser';
|
||||
import path from 'path';
|
||||
import { Browser } from '../browser';
|
||||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||
import { assert } from '../../utils';
|
||||
import { assert, createGuid } from '../../utils';
|
||||
import * as network from '../network';
|
||||
import type { PageBinding, PageDelegate, Worker } from '../page';
|
||||
import { Page } from '../page';
|
||||
@ -30,11 +31,12 @@ import type * as channels from '@protocol/channels';
|
||||
import type { CRSession } from './crConnection';
|
||||
import { ConnectionEvents, CRConnection } from './crConnection';
|
||||
import { CRPage } from './crPage';
|
||||
import { readProtocolStream } from './crProtocolHelper';
|
||||
import { saveProtocolStream } from './crProtocolHelper';
|
||||
import type { Protocol } from './protocol';
|
||||
import type { CRDevTools } from './crDevTools';
|
||||
import { CRServiceWorker } from './crServiceWorker';
|
||||
import type { SdkObject } from '../instrumentation';
|
||||
import { Artifact } from '../artifact';
|
||||
|
||||
export class CRBrowser extends Browser {
|
||||
readonly _connection: CRConnection;
|
||||
@ -48,7 +50,6 @@ export class CRBrowser extends Browser {
|
||||
private _version = '';
|
||||
|
||||
private _tracingRecording = false;
|
||||
private _tracingPath: string | null = '';
|
||||
private _tracingClient: CRSession | undefined;
|
||||
private _userAgent: string = '';
|
||||
|
||||
@ -276,7 +277,7 @@ export class CRBrowser extends Browser {
|
||||
return await this._connection.createBrowserSession();
|
||||
}
|
||||
|
||||
async startTracing(page?: Page, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
|
||||
async startTracing(page?: Page, options: { screenshots?: boolean; categories?: string[]; } = {}) {
|
||||
assert(!this._tracingRecording, 'Cannot start recording trace while already recording trace.');
|
||||
this._tracingClient = page ? (page._delegate as CRPage)._mainFrameSession._client : this._session;
|
||||
|
||||
@ -287,7 +288,6 @@ export class CRBrowser extends Browser {
|
||||
'disabled-by-default-v8.cpu_profiler', 'disabled-by-default-v8.cpu_profiler.hires'
|
||||
];
|
||||
const {
|
||||
path = null,
|
||||
screenshots = false,
|
||||
categories = defaultCategories,
|
||||
} = options;
|
||||
@ -295,7 +295,6 @@ export class CRBrowser extends Browser {
|
||||
if (screenshots)
|
||||
categories.push('disabled-by-default-devtools.screenshot');
|
||||
|
||||
this._tracingPath = path;
|
||||
this._tracingRecording = true;
|
||||
await this._tracingClient.send('Tracing.start', {
|
||||
transferMode: 'ReturnAsStream',
|
||||
@ -303,15 +302,18 @@ export class CRBrowser extends Browser {
|
||||
});
|
||||
}
|
||||
|
||||
async stopTracing(): Promise<Buffer> {
|
||||
async stopTracing(): Promise<Artifact> {
|
||||
assert(this._tracingClient, 'Tracing was not started.');
|
||||
const [event] = await Promise.all([
|
||||
new Promise(f => this._tracingClient!.once('Tracing.tracingComplete', f)),
|
||||
this._tracingClient.send('Tracing.end')
|
||||
]);
|
||||
const result = await readProtocolStream(this._tracingClient, (event as any).stream!, this._tracingPath);
|
||||
const tracingPath = path.join(this.options.artifactsDir, createGuid() + '.crtrace');
|
||||
await saveProtocolStream(this._tracingClient, (event as any).stream!, tracingPath);
|
||||
this._tracingRecording = false;
|
||||
return result;
|
||||
const artifact = new Artifact(this, tracingPath);
|
||||
artifact.reportFinished();
|
||||
return artifact;
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
|
@ -114,6 +114,6 @@ export class CRPDF {
|
||||
pageRanges,
|
||||
preferCSSPageSize
|
||||
});
|
||||
return await readProtocolStream(this._client, result.stream!, null);
|
||||
return await readProtocolStream(this._client, result.stream!);
|
||||
}
|
||||
}
|
||||
|
@ -40,26 +40,31 @@ export async function releaseObject(client: CRSession, objectId: string) {
|
||||
await client.send('Runtime.releaseObject', { objectId }).catch(error => {});
|
||||
}
|
||||
|
||||
export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<Buffer> {
|
||||
export async function saveProtocolStream(client: CRSession, handle: string, path: string) {
|
||||
let eof = false;
|
||||
let fd: fs.promises.FileHandle | undefined;
|
||||
if (path) {
|
||||
await mkdirIfNeeded(path);
|
||||
fd = await fs.promises.open(path, 'w');
|
||||
}
|
||||
const bufs = [];
|
||||
await mkdirIfNeeded(path);
|
||||
const fd = await fs.promises.open(path, 'w');
|
||||
while (!eof) {
|
||||
const response = await client.send('IO.read', { handle });
|
||||
eof = response.eof;
|
||||
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
||||
bufs.push(buf);
|
||||
if (fd)
|
||||
await fd.write(buf);
|
||||
await fd.write(buf);
|
||||
}
|
||||
if (fd)
|
||||
await fd.close();
|
||||
await fd.close();
|
||||
await client.send('IO.close', { handle });
|
||||
return Buffer.concat(bufs);
|
||||
}
|
||||
|
||||
export async function readProtocolStream(client: CRSession, handle: string): Promise<Buffer> {
|
||||
let eof = false;
|
||||
const chunks = [];
|
||||
while (!eof) {
|
||||
const response = await client.send('IO.read', { handle });
|
||||
eof = response.eof;
|
||||
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
|
||||
chunks.push(buf);
|
||||
}
|
||||
await client.send('IO.close', { handle });
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
|
||||
export function toConsoleMessageLocation(stackTrace: Protocol.Runtime.StackTrace | undefined): types.ConsoleMessageLocation {
|
||||
|
@ -28,6 +28,7 @@ import { serverSideCallMetadata } from '../instrumentation';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { Selectors } from '../selectors';
|
||||
import type { BrowserTypeDispatcher } from './browserTypeDispatcher';
|
||||
import { ArtifactDispatcher } from './artifactDispatcher';
|
||||
|
||||
export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChannel, BrowserTypeDispatcher> implements channels.BrowserChannel {
|
||||
_type_Browser = true;
|
||||
@ -81,7 +82,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
|
||||
if (!this._object.options.isChromium)
|
||||
throw new Error(`Tracing is only available in Chromium`);
|
||||
const crBrowser = this._object as CRBrowser;
|
||||
return { binary: await crBrowser.stopTracing() };
|
||||
return { artifact: ArtifactDispatcher.from(this, await crBrowser.stopTracing()) };
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +143,7 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
|
||||
if (!this._object.options.isChromium)
|
||||
throw new Error(`Tracing is only available in Chromium`);
|
||||
const crBrowser = this._object as CRBrowser;
|
||||
return { binary: await crBrowser.stopTracing() };
|
||||
return { artifact: ArtifactDispatcher.from(this, await crBrowser.stopTracing()) };
|
||||
}
|
||||
|
||||
async cleanupContexts() {
|
||||
|
@ -1348,13 +1348,11 @@ export type BrowserNewBrowserCDPSessionResult = {
|
||||
};
|
||||
export type BrowserStartTracingParams = {
|
||||
page?: PageChannel,
|
||||
path?: string,
|
||||
screenshots?: boolean,
|
||||
categories?: string[],
|
||||
};
|
||||
export type BrowserStartTracingOptions = {
|
||||
page?: PageChannel,
|
||||
path?: string,
|
||||
screenshots?: boolean,
|
||||
categories?: string[],
|
||||
};
|
||||
@ -1362,7 +1360,7 @@ export type BrowserStartTracingResult = void;
|
||||
export type BrowserStopTracingParams = {};
|
||||
export type BrowserStopTracingOptions = {};
|
||||
export type BrowserStopTracingResult = {
|
||||
binary: Binary,
|
||||
artifact: ArtifactChannel,
|
||||
};
|
||||
|
||||
export interface BrowserEvents {
|
||||
|
@ -955,7 +955,6 @@ Browser:
|
||||
startTracing:
|
||||
parameters:
|
||||
page: Page?
|
||||
path: string?
|
||||
screenshots: boolean?
|
||||
categories:
|
||||
type: array?
|
||||
@ -963,7 +962,7 @@ Browser:
|
||||
|
||||
stopTracing:
|
||||
returns:
|
||||
binary: binary
|
||||
artifact: Artifact
|
||||
|
||||
|
||||
events:
|
||||
|
Loading…
Reference in New Issue
Block a user