microsoft/playwright · error · Error

Video recording has already been started.

Error message

Video recording has already been started.

What it means

Thrown by Context.startVideoRecording when called while a recording is already in flight. The context holds a single _video slot that is set on start and only cleared by stopVideoRecording, so a second start is rejected as a state-machine violation.

Source

Thrown at packages/playwright-core/src/tools/backend/context.ts:219

    if (!tab)
      throw new Error(`Tab ${index} not found`);
    const url = tab.page.url();
    await tab.page.close();
    return url;
  }

  async workspaceFile(fileName: string, perCallWorkspaceDir: string | undefined): Promise<string> {
    return await workspaceFile(this.options, fileName, perCallWorkspaceDir);
  }

  async outputFile(template: FilenameTemplate, options: { origin: 'code' | 'llm' }): Promise<string> {
    const baseName = template.suggestedFilename || `${template.prefix}-${(template.date ?? new Date()).toISOString().replace(/[:.]/g, '-')}${template.ext ? '.' + template.ext : ''}`;
    return await outputFile(this.options, baseName, options);
  }

  async startVideoRecording(fileName: string, params: VideoParams) {
    if (this._video)
      throw new Error('Video recording has already been started.');
    this._video = { params, fileName, fileNames: [] };
    const browserContext = await this.ensureBrowserContext();
    for (const page of browserContext.pages())
      await this._startPageVideo(page);
  }

  async stopVideoRecording(): Promise<string[]> {
    if (!this._video)
      return [];
    const video = this._video;
    for (const page of this._rawBrowserContext.pages())
      await page.screencast.stop();
    this._video = undefined;
    return [...video.fileNames];
  }

  private async _startPageVideo(page: playwrightTypes.Page) {
    if (!this._video)

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Call context.stopVideoRecording() (or the browser_video_record_stop tool) before starting a new recording.
  2. Guard the call: check whether a recording is already active via the tool/context API before invoking start.
  3. If reusing one context across tasks, tear down video in a finally block so a failed run cannot leave _video set.

Example fix

// before
await context.startVideoRecording(file, params);
await context.startVideoRecording(file, params); // throws

// after
if (await context.isVideoRecording?.() !== true) {
  await context.startVideoRecording(file, params);
}
Defensive patterns

Strategy: validation

Validate before calling

// Before calling startVideoRecording, ensure no recording is active.
// If you control the Context, track recording state externally:
let videoActive = false;
async function safeStart(ctx, file, params) {
  if (videoActive) await ctx.stopVideoRecording();
  await ctx.startVideoRecording(file, params);
  videoActive = true;
}
async function safeStop(ctx) {
  if (!videoActive) return [];
  videoActive = false;
  return ctx.stopVideoRecording(); // safe to call when inactive (returns [])
}

Type guard

// No public guard; mirror the internal state with a wrapper.
const videoState = new WeakMap<Context, boolean>();
function isVideoRecording(ctx: Context): boolean {
  return videoState.get(ctx) ?? false;
}

Try / catch

// stopVideoRecording is safe to call when inactive (returns []). Prefer calling it
// defensively rather than try/catch on start.
await context.stopVideoRecording(); // no-op if inactive
await context.startVideoRecording(file, params);

Prevention

When it happens

Trigger: Calling startVideoRecording twice without an intervening stopVideoRecording; or invoking the browser_video_record_start MCP tool a second time while the first recording is still active.

Common situations: MCP/LLM agent retrying a video-capture step without first issuing stop; an orchestration script that re-enters a 'record' code path after an error path that never stopped the previous recording.

Related errors


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