microsoft/playwright · error · Error

Recording is already in progress.

Error message

Recording is already in progress.

What it means

BrowserContextDispatcher allows only one active recording session per context. startRecording stores the RecorderApp in this._recorderApp; if it is already set, a programmatic recording is running and a second startRecording call is rejected. Stop the current recording first.

Solutions

  1. Call context.stopRecording() (or the stopRecording channel method) before starting again.
  2. Guard the start call in your code so startRecording is invoked at most once per context.
  3. Create a fresh browser context for each recording session.

Example fix

// before
await context.startRecording({ language: 'javascript' });
await context.startRecording({ language: 'javascript' });
// after
await context.startRecording({ language: 'javascript' });
await context.stopRecording();
await context.startRecording({ language: 'javascript' });
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await context.startRecording({ language: 'javascript' });
} catch (e) {
  if (e.message.includes('Recording is already in progress')) {
    await context.stopRecording();
    await context.startRecording({ language: 'javascript' });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling context.startRecording() (via the BrowserContext channel) twice without calling stopRecording() in between.

Common situations: Test harnesses or tools that kick off codegen/recording per test but only stop it conditionally; retry logic re-invoking startRecording after a partial failure that left the recorder attached.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microsoft/playwright@500c9c822c (2026-09-15). Data as JSON: /api/errors/0ba101fde44af298. Report an issue: GitHub.

Appendix: source

Thrown at packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts:361

  async storageState(params: channels.BrowserContextStorageStateParams, progress: Progress): Promise<channels.BrowserContextStorageStateResult> {
    return await this._context.storageState(progress, params);
  }

  async setStorageState(params: channels.BrowserContextSetStorageStateParams, progress: Progress): Promise<void> {
    await this._context.setStorageState(progress, params.storageState, 'api');
  }

  async close(params: channels.BrowserContextCloseParams, progress: Progress): Promise<void> {
    await this._context.close(progress, params);
  }

  async showRecorder(params: channels.BrowserContextShowRecorderParams, progress: Progress): Promise<void> {
    await progress.race(RecorderApp.show(this._context, params));
  }

  async startRecording(params: channels.BrowserContextStartRecordingParams, progress: Progress): Promise<void> {
    if (this._recorderApp)
      throw new Error('Recording is already in progress.');
    // Recorder events only go to the connection that started the recording.
    this._recorderApp = await progress.race(ProgrammaticRecorderApp.start(this._context, params, ({ event, data, page, code }) => {
      this._dispatchEvent('recorderEvent', { event, data, code, page: PageDispatcher.from(this, page) });
    }));
  }

  async stopRecording(params: channels.BrowserContextStopRecordingParams, progress: Progress): Promise<void> {
    const recorderApp = this._recorderApp;
    this._recorderApp = undefined;
    if (recorderApp)
      await progress.race(recorderApp.stop());
  }

  async exposeConsoleApi(params: channels.BrowserContextExposeConsoleApiParams, progress: Progress): Promise<void> {
    await this._context.exposeConsoleApi(progress);
  }

  async pause(params: channels.BrowserContextPauseParams, progress: Progress) {

View on GitHub (pinned to 500c9c822c)