microsoft/playwright · error · Error

No recording in progress

Error message

No recording in progress

What it means

Thrown by the dashboard page-tab controller's `stopRecording` when `_recordingPath` is null. `_recordingPath` is set by `startRecording` and cleared by `stopRecording`, so calling stop without a prior start, or calling stop twice, triggers it.

Source

Thrown at packages/playwright-core/src/tools/dashboard/dashboardController.ts:681

  async keydown(params: { key: string }) {
    await wrapInternal(this._page, () => this._page.keyboard.down(params.key));
  }

  async keyup(params: { key: string }) {
    await wrapInternal(this._page, () => this._page.keyboard.up(params.key));
  }

  async startRecording() {
    const artifactsDir = this._owner.artifactsDirFor(this._page.context());
    this._recordingPath = path.join(artifactsDir, `recording-${Date.now()}.webm`);
    if (this._screencastRunning)
      await this._restartScreencast(this._page);
  }

  async stopRecording(): Promise<{ streamId: string }> {
    const p = this._recordingPath;
    if (!p)
      throw new Error('No recording in progress');
    this._recordingPath = null;
    if (this._screencastRunning)
      await this._restartScreencast(this._page);
    const handle = await fs.promises.open(p, 'r');
    const streamId = crypto.randomUUID();
    this._owner._streams.set(streamId, { handle, path: p });
    return { streamId };
  }

  async screenshot(): Promise<{ data: string; viewportWidth: number; viewportHeight: number; ariaSnapshot: string }> {
    const buffer = await wrapInternal(this._page, () => this._page.screenshot({ type: 'png' }));
    const ariaSnapshot = await wrapInternal(this._page, () => this._page.ariaSnapshot({ boxes: true, mode: 'ai' }));
    const vp = await this._viewportSize();
    return {
      data: buffer.toString('base64'),
      viewportWidth: vp.width,
      viewportHeight: vp.height,
      ariaSnapshot,

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Guard the stop call: only invoke `stopRecording` when a recording is actually in progress (track state in the caller).
  2. Avoid double-stop — disable the stop control after the first click until a new recording starts.
  3. If `startRecording` can fail, ensure the UI's recording flag is reset on failure so stop is not attempted.

Example fix

// before
controller.stopRecording(); // may throw
// after
if (isRecording) {
  const { streamId } = await controller.stopRecording();
  isRecording = false;
}
Defensive patterns

Strategy: validation

Validate before calling

let recording = false;
async function safeStart() { if (!recording) { await controller.startRecording(); recording = true; } }
async function safeStop() {
  if (!recording) return null;
  const r = await controller.stopRecording(); recording = false;
  return r;
}

Try / catch

try {
  await controller.stopRecording();
} catch (e) {
  if (!/No recording in progress/.test((e as Error).message)) throw e;
  // otherwise no-op
}

Prevention

When it happens

Trigger: Calling `stopRecording` before `startRecording`; calling `stopRecording` a second time after the first call already nulled `_recordingPath`; a UI button (stop) wired to stop without checking recording state.

Common situations: Dashboard user clicks stop on a tab where recording was never started; a UI state desync where the stop button is enabled but no recording is active; an error during `startRecording` left `_recordingPath` unset but the UI believes a recording is running.

Related errors


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