microsoft/playwright · error · Error

Must start tracing before stopping

Error message

Must start tracing before stopping

What it means

Tracing.stopChunk() throws when there is no active/recording state AND the requested mode is not 'discard'. A non-discard stopChunk (entries/archive export) requires that tracing was actually started and a chunk recorded; calling it on an idle tracer means there is nothing to export.

Source

Thrown at packages/playwright-core/src/server/trace/recorder/tracing.ts:396

    const result = await progress.race(recorder.export(mode));
    this.harRecorders.delete(harId || '');
    return result;
  }

  private _closeAllGroups() {
    while (this._currentGroupId())
      this._groupEnd();
  }

  async stopChunk(progress: Progress, params: TracingTracingStopChunkParams): Promise<{ artifact?: Artifact, entries?: NameValue[] }> {
    if (this._isStopping)
      throw new Error(`Tracing is already stopping`);
    this._isStopping = true;

    if (!this._state || !this._state.recording) {
      this._isStopping = false;
      if (params.mode !== 'discard')
        throw new Error(`Must start tracing before stopping`);
      return {};
    }

    this._closeAllGroups();

    this._context.instrumentation.removeListener(this);
    eventsHelper.removeEventListeners(this._eventListeners);
    if (this._state.options.screencast)
      this._stopScreencast();
    // We don't need websocket frames outside of the recording window. This also
    // stops updating websocket content blobs, which we want to stay unchanged for zipping.
    this._harTracer.setOmitWebSocketFrames(true);
    if (this._state.options.snapshotDom)
      this._snapshotter?.stop();

    this.flushHarEntries();

    // Network file survives across chunks, make a snapshot before returning the resulting entries.

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Only call stopChunk with an export path when you know tracing was started and a chunk is open.
  2. Use mode: 'discard' for the teardown path that must be safe when nothing was recorded.
  3. Guard the export call behind a flag you set in startChunk.

Example fix

// before
afterEach(async () => {
  await context.tracing.stopChunk({ path: 'trace.zip' }); // throws if never started
});

// after
afterEach(async () => {
  if (tracingStarted) {
    await context.tracing.stopChunk({ path: 'trace.zip' });
  } else {
    await context.tracing.stopChunk({ mode: 'discard' }).catch(() => {});
  }
});
Defensive patterns

Strategy: validation

Validate before calling

let tracingActive = false;
async function guardedStop(ctx, out) {
  if (!tracingActive) {
    return ctx.tracing.stopChunk({ mode: 'discard' }).catch(() => ({}));
  }
  tracingActive = false;
  return ctx.tracing.stopChunk({ path: out });
}

Try / catch

try { await context.tracing.stopChunk({ path: out }); }
catch (e) {
  if (/Must start tracing before stopping/i.test(String(e.message))) return;
  throw e;
}

Prevention

When it happens

Trigger: Calling context.tracing.stopChunk({ path: 'x.zip' }) (default mode 'archive') or mode 'entries' before tracing.start() / startChunk(); calling export after tracing has already stopped and cleared _state.

Common situations: A conditional afterEach that always tries to export a trace even when tracing was disabled for that test; orchestrating chunks without first starting; calling stopChunk twice (second has no state).

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/e69ee66fc3048412. Report an issue: GitHub.