Skip to content

Commit a836466

Browse files
authored
fix(tracing): error handling (#6888)
- Reject when ZipFile signals an error. - Make sure snapshotter does not save trace events after stop(). - Await pending blob writes on stop().
1 parent b5ac393 commit a836466

File tree

4 files changed

+26
-19
lines changed

4 files changed

+26
-19
lines changed

src/server/snapshot/snapshotter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class Snapshotter {
116116
const snapshots = page.frames().map(async frame => {
117117
const data = await frame.nonStallingRawEvaluateInExistingMainContext(expression).catch(e => debugLogger.log('error', e)) as SnapshotData;
118118
// Something went wrong -> bail out, our snapshots are best-efforty.
119-
if (!data)
119+
if (!data || !this._started)
120120
return;
121121

122122
const snapshot: FrameSnapshot = {

src/server/trace/recorder/traceSnapshotter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class TraceSnapshotter extends EventEmitter implements SnapshotterDelegat
4848

4949
async stop(): Promise<void> {
5050
await this._snapshotter.stop();
51+
await this._writeArtifactChain;
5152
}
5253

5354
async dispose() {

src/server/trace/recorder/tracing.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import fs from 'fs';
1818
import path from 'path';
1919
import yazl from 'yazl';
20+
import { EventEmitter } from 'events';
2021
import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
2122
import { Artifact } from '../../artifact';
2223
import { BrowserContext } from '../../browserContext';
@@ -43,7 +44,7 @@ export class Tracing implements InstrumentationListener {
4344
private _resourcesDir: string;
4445
private _sha1s: string[] = [];
4546
private _started = false;
46-
private _tracesDir: string | undefined;
47+
private _tracesDir: string;
4748

4849
constructor(context: BrowserContext) {
4950
this._context = context;
@@ -54,8 +55,6 @@ export class Tracing implements InstrumentationListener {
5455

5556
async start(options: TracerOptions): Promise<void> {
5657
// context + page must be the first events added, this method can't have awaits before them.
57-
if (!this._tracesDir)
58-
throw new Error('Tracing directory is not specified when launching the browser');
5958
if (this._started)
6059
throw new Error('Tracing has already been started');
6160
this._started = true;
@@ -86,13 +85,13 @@ export class Tracing implements InstrumentationListener {
8685
if (!this._started)
8786
return;
8887
this._started = false;
89-
await this._snapshotter.stop();
9088
this._context.instrumentation.removeListener(this);
9189
helper.removeEventListeners(this._eventListeners);
9290
for (const { sdkObject, metadata } of this._pendingCalls.values())
9391
await this.onAfterCall(sdkObject, metadata);
9492
for (const page of this._context.pages())
9593
page.setScreencastOptions(null);
94+
await this._snapshotter.stop();
9695

9796
// Ensure all writes are finished.
9897
await this._appendEventChain;
@@ -103,21 +102,25 @@ export class Tracing implements InstrumentationListener {
103102
}
104103

105104
async export(): Promise<Artifact> {
106-
if (!this._traceFile)
107-
throw new Error('Tracing directory is not specified when launching the browser');
105+
if (!this._traceFile || this._started)
106+
throw new Error('Must start and stop tracing before exporting');
108107
const zipFile = new yazl.ZipFile();
109-
zipFile.addFile(this._traceFile, 'trace.trace');
110-
const zipFileName = this._traceFile + '.zip';
111-
this._traceFile = undefined;
112-
for (const sha1 of this._sha1s)
113-
zipFile.addFile(path.join(this._resourcesDir!, sha1), path.join('resources', sha1));
114-
zipFile.end();
115-
await new Promise(f => {
116-
zipFile.outputStream.pipe(fs.createWriteStream(zipFileName)).on('close', f);
108+
const failedPromise = new Promise<Artifact>((_, reject) => (zipFile as any as EventEmitter).on('error', reject));
109+
110+
const succeededPromise = new Promise<Artifact>(async fulfill => {
111+
zipFile.addFile(this._traceFile!, 'trace.trace');
112+
const zipFileName = this._traceFile! + '.zip';
113+
for (const sha1 of this._sha1s)
114+
zipFile.addFile(path.join(this._resourcesDir!, sha1), path.join('resources', sha1));
115+
zipFile.end();
116+
await new Promise(f => {
117+
zipFile.outputStream.pipe(fs.createWriteStream(zipFileName)).on('close', f);
118+
});
119+
const artifact = new Artifact(this._context, zipFileName);
120+
artifact.reportFinished();
121+
fulfill(artifact);
117122
});
118-
const artifact = new Artifact(this._context, zipFileName);
119-
artifact.reportFinished();
120-
return artifact;
123+
return Promise.race([failedPromise, succeededPromise]);
121124
}
122125

123126
async _captureSnapshot(name: 'before' | 'after' | 'action' | 'event', sdkObject: SdkObject, metadata: CallMetadata, element?: ElementHandle) {
@@ -181,6 +184,9 @@ export class Tracing implements InstrumentationListener {
181184
}
182185

183186
private _appendTraceEvent(event: any) {
187+
if (!this._started)
188+
return;
189+
184190
const visit = (object: any) => {
185191
if (Array.isArray(object)) {
186192
object.forEach(visit);

tests/tracing.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ test('should collect trace', async ({ context, page, server, browserName }, test
3838
expect(events.some(e => e.type === 'screencast-frame')).toBeTruthy();
3939
});
4040

41-
test('should collect trace', async ({ context, page, server }, testInfo) => {
41+
test('should not collect snapshots by default', async ({ context, page, server }, testInfo) => {
4242
await context.tracing.start();
4343
await page.goto(server.EMPTY_PAGE);
4444
await page.setContent('<button>Click</button>');

0 commit comments

Comments
 (0)