microsoft/playwright · 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.

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@f1d33b5029 (2026-09-21). Data as JSON: /api/errors/e69ee66fc3048412. Report an issue: GitHub.

Appendix: source

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

      if (params.mode === 'entries')
        return { entries: result.entries };

      const artifact = new Artifact(this._context, result.zipFileName);
      artifact.reportFinished();
      return { artifact };
    } finally {
      // Always release the recording state, even when saving the chunk failed.
      this._isStopping = false;
      if (this._state)
        this._state.recording = false;
    }
  }

  private _stopChunk(params: TracingTracingStopChunkParams, coverageFile: string | undefined): { entries: NameValue[], zipFileName: string } | undefined {
    if (!this._state || !this._state.recording) {
      if (params.mode !== 'discard')
        throw new Error(`Must start tracing before stopping`);
      return undefined;
    }

    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 f1d33b5029)