vitest-dev / vitest-dev/vitest

Certain shadow DOM manipulation can crash the Webkit orchestrator

Open
#10,422 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

pending triage
Dominant language
TypeScript
Stars
17.1k
Forks
2k
Avg merge
1d 22h
Merged PRs (30d)
94

Description

Describe the bug

When a light DOM element is removed from the document while content is still being copied within a 'slotchange' handler in its shadow DOM, the orchestrator to crashes. Or something... this is all very opaque to me.

The attached video shows me exercising this error case (if you want full quality, hit me with an FTP server where I can upload it).

I get several different failure modes:

(timestamp 00:19) Browser connection was closed while running tests; caused by RPC is closed, cannot call "createTesters":

  1 Error: Failed to run the test /Users/dolan/IdeaProjects/papertrace/code/app/tests/repro.spec.ts.
  2  ❯ ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2626:17
  3 
  4 Caused by: Error: [vitest] Browser connection was closed while running tests. Was the page closed unexpectedly?
  5  ❯ WebSocket.<anonymous> ../node_modules/@vitest/browser/dist/index.js:3034:16
  6  ❯ WebSocket.emit node:events:520:35
  7  ❯ WebSocket.emitClose ../node_modules/ws/lib/websocket.js:273:10
  8  ❯ Socket.socketOnClose ../node_modules/ws/lib/websocket.js:1346:15
  9  ❯ Socket.emit node:events:520:35
 10  ❯ TCP.<anonymous> node:net:346:12
 11 
 12 Caused by: Error: [birpc] rpc is closed, cannot call "createTesters"
 13  ❯ ../node_modules/@vitest/browser/dist/index.js:2896:34
 14  ❯ Proxy.$close ../node_modules/@vitest/browser/dist/index.js:2895:18
 15  ❯ WebSocket.<anonymous> ../node_modules/@vitest/browser/dist/index.js:3034:9
 16  ❯ WebSocket.emit node:events:520:35
 17  ❯ WebSocket.emitClose ../node_modules/ws/lib/websocket.js:273:10
 18  ❯ Socket.socketOnClose ../node_modules/ws/lib/websocket.js:1346:15
 19  ❯ Socket.emit node:events:520:35
 20  ❯ TCP.<anonymous> node:net:346:12

Sometimes this causes Vitest to quit, sometimes I can quit with Ctrl+C. Sometimes when I press Ctrl+C, it hangs for a very long time but eventually quits. (03:50)

(01:33) All suites pass, but the test continues running forever.

(09:18) Orchestrator not found:

Vitest caught 1 unhandled error during the test run.
 37 This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.
 38 
 39 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Error ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯    ⎯⎯⎯⎯
 40 Error: Orchestrator not found for session 9a970be4-be06-48ce-a99f-5929af39fa3f. This is a bug in Vitest. Please, open a new issue with reproduction.
 41  ❯ BrowserPool.getOrchestrator ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2573:28
 42  ❯ BrowserPool.runNextTest ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2600:29
 43  ❯ ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2535:10
 44  ❯ BrowserPool.runTests ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2532:22
 45  ❯ runTests ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2448:26
 46  ❯ ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2461:53
 47  ❯ runWorkspaceTests ../node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:2461:35

The reproduction below triggers the bug extremely intermittently. However, it comes up frequently in my actual test suite. Starting at 10:20, you can see me hit the "Orchestrator not found" error on five consecutive runs when testing the real custom element.

https://github.com/user-attachments/assets/635a4422-1815-4b82-82d7-7dc5da0f4a91

Reproduction

The reproduction below is a simplified version of my custom element, which renders a button (hidden except for screen readers) and a picture of a hotkey for visual users to click the button without mousing or tabbing. The developer places the action name into the component's default slot, and the component displays the label both next to the hotkey and inside the button. To mirror the content, an event handler watches the 'slotchange' event and copies then new content from one slot to the other, inside the element's shadow root.

Stripped-down component and test:

import { describe, it, afterEach } from 'vitest';
import { findAllByShadowRole } from 'shadow-dom-testing-library';

const template = document.createElement('template');
template.innerHTML = `
<div class="keybind_prompt">
  <button id="the_button"><slot id="button_label"></slot></button>
  <div id="instructions" role="img">
    <img id="icon" aria-labelledby="prompt_label" class="keybind_icon" />
    <slot id="prompt_label"></slot>
  </div>
</div>
`;

export default class CustomElement extends HTMLElement {
  private readonly slots: NodeListOf<HTMLSlotElement>;
  private slotListener: EventListener | null = null;

  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.appendChild(template.content.cloneNode(true));
    this.slots = shadow.querySelectorAll('slot');
  }

  private connectedCallback() {
    this.slotListener = () => {
      const nodes = this.slots[0].assignedNodes();
      for (let i = 1; i < this.slots.length; i++) {
        this.slots[i].replaceChildren(...nodes.map((n) => n.cloneNode(true)));
      }
    };

    this.slots[0].addEventListener('slotchange', this.slotListener);
  }

  private disconnectedCallback() {
    if (this.slotListener !== null) {
      this.slots[0].removeEventListener('slotchange', this.slotListener);
    }
    this.slotListener = null;
  }
}

customElements.define('custom-element', CustomElement);

describe('custom-element', () => {

  afterEach(() => {
    document.body.innerHTML = '';
  });

  it('should be able to find the instructions', async () => {
    document.body.innerHTML =
      '<custom-element>Action</custom-element>';
    await findAllByShadowRole(document.body, 'img')[0];
  });
});
System Info
System:
    OS: macOS 15.7.3
    CPU: (8) arm64 Apple M1 Pro
    Memory: 105.55 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.11.0 - ~/.nvm/versions/node/v24.11.0/bin/node
    Yarn: 1.22.21 - ~/.nvm/versions/node/v24.11.0/bin/yarn
    npm: 11.11.1 - ~/.nvm/versions/node/v24.11.0/bin/npm
    pnpm: 8.15.4 - ~/.nvm/versions/node/v24.11.0/bin/pnpm
Used Package Manager

npm

Validations

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Run the provided TypeScript reproduction in Vitest's browser mode, then inspect the BrowserPool.getOrchestrator and runNextTest paths named in the error. Add a regression test for removing the light-DOM element during slotchange content copying; done means the test completes without a crash, hang, closed browser connection, or missing orchestrator.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.