vitest-dev / vitest-dev/vitest
Certain shadow DOM manipulation can crash the Webkit orchestrator
Nobody has claimed this yet.
- 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
- 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.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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