chore: flush har when recording tracing (#26746)
This commit is contained in:
parent
4bbd16d316
commit
99b8ca2be2
|
|
@ -20,6 +20,7 @@ import { Artifact } from '../artifact';
|
||||||
import type { BrowserContext } from '../browserContext';
|
import type { BrowserContext } from '../browserContext';
|
||||||
import type * as har from '@trace/har';
|
import type * as har from '@trace/har';
|
||||||
import { HarTracer } from './harTracer';
|
import { HarTracer } from './harTracer';
|
||||||
|
import type { HarTracerDelegate } from './harTracer';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import { yazl } from '../../zipBundle';
|
import { yazl } from '../../zipBundle';
|
||||||
import type { ZipFile } from '../../zipBundle';
|
import type { ZipFile } from '../../zipBundle';
|
||||||
|
|
@ -28,7 +29,7 @@ import type EventEmitter from 'events';
|
||||||
import { createGuid } from '../../utils';
|
import { createGuid } from '../../utils';
|
||||||
import type { Page } from '../page';
|
import type { Page } from '../page';
|
||||||
|
|
||||||
export class HarRecorder {
|
export class HarRecorder implements HarTracerDelegate {
|
||||||
private _artifact: Artifact;
|
private _artifact: Artifact;
|
||||||
private _isFlushed: boolean = false;
|
private _isFlushed: boolean = false;
|
||||||
private _tracer: HarTracer;
|
private _tracer: HarTracer;
|
||||||
|
|
|
||||||
|
|
@ -182,13 +182,7 @@ export class HarTracer {
|
||||||
return null;
|
return null;
|
||||||
if (!this._options.waitForContentOnStop)
|
if (!this._options.waitForContentOnStop)
|
||||||
return;
|
return;
|
||||||
const race = Promise.race([
|
const race = target.openScope.safeRace(promise);
|
||||||
new Promise<void>(f => target.on('close', () => {
|
|
||||||
this._barrierPromises.delete(race);
|
|
||||||
f();
|
|
||||||
})),
|
|
||||||
promise
|
|
||||||
]) as Promise<void>;
|
|
||||||
this._barrierPromises.add(race);
|
this._barrierPromises.add(race);
|
||||||
race.then(() => this._barrierPromises.delete(race));
|
race.then(() => this._barrierPromises.delete(race));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@ export class Page extends SdkObject {
|
||||||
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
|
private _eventsToEmitAfterInitialized: { event: string | symbol, args: any[] }[] = [];
|
||||||
readonly _disconnectedScope = new LongStandingScope();
|
readonly _disconnectedScope = new LongStandingScope();
|
||||||
readonly _crashedScope = new LongStandingScope();
|
readonly _crashedScope = new LongStandingScope();
|
||||||
|
readonly openScope = new LongStandingScope();
|
||||||
readonly _browserContext: BrowserContext;
|
readonly _browserContext: BrowserContext;
|
||||||
readonly keyboard: input.Keyboard;
|
readonly keyboard: input.Keyboard;
|
||||||
readonly mouse: input.Mouse;
|
readonly mouse: input.Mouse;
|
||||||
|
|
@ -276,6 +277,7 @@ export class Page extends SdkObject {
|
||||||
this.emit(Page.Events.Close);
|
this.emit(Page.Events.Close);
|
||||||
this._closedPromise.resolve();
|
this._closedPromise.resolve();
|
||||||
this.instrumentation.onPageClose(this);
|
this.instrumentation.onPageClose(this);
|
||||||
|
this.openScope.close('Page closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
_didCrash() {
|
_didCrash() {
|
||||||
|
|
@ -284,6 +286,7 @@ export class Page extends SdkObject {
|
||||||
this.emit(Page.Events.Crash);
|
this.emit(Page.Events.Crash);
|
||||||
this._crashedScope.close('Page crashed');
|
this._crashedScope.close('Page crashed');
|
||||||
this.instrumentation.onPageClose(this);
|
this.instrumentation.onPageClose(this);
|
||||||
|
this.openScope.close('Page closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
_didDisconnect() {
|
_didDisconnect() {
|
||||||
|
|
@ -292,6 +295,7 @@ export class Page extends SdkObject {
|
||||||
assert(!this._disconnected, 'Page disconnected twice');
|
assert(!this._disconnected, 'Page disconnected twice');
|
||||||
this._disconnected = true;
|
this._disconnected = true;
|
||||||
this._disconnectedScope.close('Page closed');
|
this._disconnectedScope.close('Page closed');
|
||||||
|
this.openScope.close('Page closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
async _onFileChooserOpened(handle: dom.ElementHandle) {
|
||||||
|
|
@ -711,6 +715,7 @@ export class Worker extends SdkObject {
|
||||||
private _executionContextPromise: Promise<js.ExecutionContext>;
|
private _executionContextPromise: Promise<js.ExecutionContext>;
|
||||||
private _executionContextCallback: (value: js.ExecutionContext) => void;
|
private _executionContextCallback: (value: js.ExecutionContext) => void;
|
||||||
_existingExecutionContext: js.ExecutionContext | null = null;
|
_existingExecutionContext: js.ExecutionContext | null = null;
|
||||||
|
readonly openScope = new LongStandingScope();
|
||||||
|
|
||||||
constructor(parent: SdkObject, url: string) {
|
constructor(parent: SdkObject, url: string) {
|
||||||
super(parent, 'worker');
|
super(parent, 'worker');
|
||||||
|
|
@ -732,6 +737,7 @@ export class Worker extends SdkObject {
|
||||||
if (this._existingExecutionContext)
|
if (this._existingExecutionContext)
|
||||||
this._existingExecutionContext.contextDestroyed('Worker was closed');
|
this._existingExecutionContext.contextDestroyed('Worker was closed');
|
||||||
this.emit(Worker.Events.Close, this);
|
this.emit(Worker.Events.Close, this);
|
||||||
|
this.openScope.close('Worker closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
private _tracesTmpDir: string | undefined;
|
private _tracesTmpDir: string | undefined;
|
||||||
private _allResources = new Set<string>();
|
private _allResources = new Set<string>();
|
||||||
private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
|
private _contextCreatedEvent: trace.ContextCreatedTraceEvent;
|
||||||
|
private _pendingHarEntries = new Set<har.Entry>();
|
||||||
|
|
||||||
constructor(context: BrowserContext | APIRequestContext, tracesDir: string | undefined) {
|
constructor(context: BrowserContext | APIRequestContext, tracesDir: string | undefined) {
|
||||||
super(context, 'tracing');
|
super(context, 'tracing');
|
||||||
|
|
@ -230,6 +231,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
if (this._state.recording)
|
if (this._state.recording)
|
||||||
throw new Error(`Must stop trace file before stopping tracing`);
|
throw new Error(`Must stop trace file before stopping tracing`);
|
||||||
this._harTracer.stop();
|
this._harTracer.stop();
|
||||||
|
this.flushHarEntries();
|
||||||
await this._fs.syncAndGetError();
|
await this._fs.syncAndGetError();
|
||||||
this._state = undefined;
|
this._state = undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -272,6 +274,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
if (this._state.options.snapshots)
|
if (this._state.options.snapshots)
|
||||||
await this._snapshotter?.stop();
|
await this._snapshotter?.stop();
|
||||||
|
|
||||||
|
this.flushHarEntries();
|
||||||
|
|
||||||
// Network file survives across chunks, make a snapshot before returning the resulting entries.
|
// Network file survives across chunks, make a snapshot before returning the resulting entries.
|
||||||
// We should pick a name starting with "traceName" and ending with .network.
|
// We should pick a name starting with "traceName" and ending with .network.
|
||||||
// Something like <traceName>someSuffixHere.network.
|
// Something like <traceName>someSuffixHere.network.
|
||||||
|
|
@ -387,14 +391,28 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
}
|
}
|
||||||
|
|
||||||
onEntryStarted(entry: har.Entry) {
|
onEntryStarted(entry: har.Entry) {
|
||||||
|
this._pendingHarEntries.add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEntryFinished(entry: har.Entry) {
|
onEntryFinished(entry: har.Entry) {
|
||||||
|
this._pendingHarEntries.delete(entry);
|
||||||
const event: trace.ResourceSnapshotTraceEvent = { type: 'resource-snapshot', snapshot: entry };
|
const event: trace.ResourceSnapshotTraceEvent = { type: 'resource-snapshot', snapshot: entry };
|
||||||
const visited = visitTraceEvent(event, this._state!.networkSha1s);
|
const visited = visitTraceEvent(event, this._state!.networkSha1s);
|
||||||
this._fs.appendFile(this._state!.networkFile, JSON.stringify(visited) + '\n', true /* flush */);
|
this._fs.appendFile(this._state!.networkFile, JSON.stringify(visited) + '\n', true /* flush */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flushHarEntries() {
|
||||||
|
const harLines: string[] = [];
|
||||||
|
for (const entry of this._pendingHarEntries) {
|
||||||
|
const event: trace.ResourceSnapshotTraceEvent = { type: 'resource-snapshot', snapshot: entry };
|
||||||
|
const visited = visitTraceEvent(event, this._state!.networkSha1s);
|
||||||
|
harLines.push(JSON.stringify(visited));
|
||||||
|
}
|
||||||
|
this._pendingHarEntries.clear();
|
||||||
|
if (harLines.length)
|
||||||
|
this._fs.appendFile(this._state!.networkFile, harLines.join('\n') + '\n', true /* flush */);
|
||||||
|
}
|
||||||
|
|
||||||
onContentBlob(sha1: string, buffer: Buffer) {
|
onContentBlob(sha1: string, buffer: Buffer) {
|
||||||
this._appendResource(sha1, buffer);
|
this._appendResource(sha1, buffer);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue