Skip to content

Browser mode: failure screenshots of multiple instances share one path, so a test failing in two browsers can hang the run #11949

Open

Description

@wintery

Describe the bug

With more than one browser.instances entry, a test that fails in every instance saves its failure screenshot to the same file in each one:

.vitest/attachments/failure-screenshots/<test file>/<test name>.png

The path has no browser or project name in it (packages/browser/src/client/tester/runner.ts, onTaskFinished). Then resolveTestAttachment copies that file to <attachmentsDir>/<sha1 of the path>.png, which is the same target for every instance too.

This causes two problems:

  1. The instances overwrite each other's screenshot. With 12 tests failing in Chromium and WebKit, .vitest/attachments holds 12 failure screenshots and 12 copies, not 24 of each. Each instance's report links the same file, which shows whichever browser wrote it last.
  2. When both instances fail the same test at about the same moment, one instance rewrites the screenshot while the other's copyFile is reading it. Sometimes the copy never finishes: one node process sits at 100% CPU with the screenshot open for read and the attachment open for write, and vitest run never exits. On macOS, about 3 in 10 runs of the repro below hung. With screenshotFailures: false, or with one instance, 0 in 10 hung.

Putting the instance's name in the path would fix both, for example failure-screenshots/<browser>/<file>/<test>.png.

Reproduction

package.json

{
  "type": "module",
  "devDependencies": {
    "@vitest/browser-playwright": "5.1.0",
    "playwright": "1.65.0",
    "vitest": "5.1.0"
  }
}

vitest.config.js

import { playwright } from "@vitest/browser-playwright";
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    browser: {
      enabled: true,
      headless: true,
      provider: playwright(),
      instances: [{ browser: "chromium" }, { browser: "webkit" }],
    },
  },
});

race.test.js

import { expect, test } from "vitest";

// Large, noisy PNGs widen the window in which both browsers write the same file.
for (let i = 0; i < 12; i++) {
  test(`fails ${i}`, () => {
    const canvas = document.createElement("canvas");
    canvas.width = 1200;
    canvas.height = 900;
    const context = canvas.getContext("2d");
    const pixels = context.createImageData(canvas.width, canvas.height);
    for (let p = 0; p < pixels.data.length; p++) pixels.data[p] = Math.random() * 256;
    context.putImageData(pixels, 0, 0);
    document.body.replaceChildren(canvas);
    expect(i).toBe(-1);
  });
}

Steps:

  1. npm install && npx playwright install chromium webkit
  2. Run rm -rf .vitest && npx vitest run several times.
  3. Each run should fail in about 3 seconds. Some runs never exit.
  4. In a run that finishes, find .vitest -type f | wc -l prints 24 for 24 failures: 12 screenshots and 12 copies.

While a run hangs, ps and lsof show:

  PID  %CPU ELAPSED COMMAND
12386  99.3   00:20 node (vitest run)

node 12386  45r  1106816  .vitest/attachments/failure-screenshots/race.test.js/fails-10.png
node 12386  46w  1106816  .vitest/attachments/b534c997898759bb19dbabfc6f6bb8d3a08a14b6.png

A sample of the stuck process shows a libuv worker looping in uv__fs_work (pread, sendfile).

System Info

OS: macOS 27.0.1, arm64
Node: 26.13.0
vitest: 5.1.0
@vitest/browser-playwright: 5.1.0
playwright: 1.65.0

Used Package Manager

pnpm

Validations

  • Follow our Code of Conduct
  • Read the Contributing Guidelines.
  • Read the docs.
  • Check that there isn't already an issue that reports the same bug, to avoid creating a duplicate.
  • The provided reproduction is a minimal reproducible example of the bug.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions