如何在Vue Vitest中访问自定义元素的Shadow DOM以进行测试
我之前在测试Vue项目里的Web组件时也遇到过一模一样的问题,尤其是用Vitest的时候,Shadow DOM的访问经常踩坑。让我一步步告诉你怎么解决,帮你顺利测试自定义元素内部的内容:
1. 先确保自定义元素在测试环境中正确注册
Shadow DOM访问失败最常见的原因之一就是自定义元素还没被定义,导致测试执行时元素还不是一个有效的Web组件,自然拿不到Shadow DOM。
- 如果你的
custom-header是用Vue 3的defineCustomElement创建的Web组件,记得在测试前先注册它:
import { defineCustomElement } from 'vue'; import CustomHeader from './components/CustomHeader.ce.vue'; // 注册自定义元素 const CustomHeaderElement = defineCustomElement(CustomHeader); customElements.define('custom-header', CustomHeaderElement);
- 如果是第三方Web组件库,要确保在测试文件开头就导入对应的组件脚本,让测试环境能识别这个自定义元素。
2. 等待自定义元素定义完成(关键!)
即使你注册了元素,浏览器(或者测试用的jsdom环境)需要一点时间来完成元素的定义流程。直接去拿shadowRoot很可能会拿到null,因为元素还没初始化好。
一定要用customElements.whenDefined()来等待元素就绪,这个API会返回一个Promise,等元素定义完成后才会resolve:
test('测试custom-header的Shadow DOM内容', async () => { const wrapper = mount(YourTestComponent); // 挂载包含custom-header的Vue组件 // 等待custom-header元素定义完成 await customElements.whenDefined('custom-header'); // 现在再去获取元素和Shadow DOM const customHeaderEl = wrapper.find('custom-header').element; const shadowRoot = customHeaderEl.shadowRoot; // 这时候shadowRoot应该就不是null了 expect(shadowRoot).not.toBeNull(); // 接下来就可以正常查询内部元素了 const navEl = shadowRoot.querySelector('nav'); expect(navEl).toBeTruthy(); expect(navEl.textContent).toContain('导航内容'); // 测试文本内容 });
3. 排查测试环境的Shadow DOM支持
Vitest默认用jsdom作为测试环境,旧版本的jsdom对Shadow DOM的支持不够完善。如果上面的步骤还是不行,检查一下你的jsdom版本——建议升级到v20以上,确保它支持open模式的Shadow DOM。
你可以在package.json里查看jsdom的版本,或者直接更新:
npm update jsdom --save-dev
4. 用waitFor处理延迟初始化的情况
如果你的Web组件内部有异步逻辑(比如请求数据后渲染内容),Shadow DOM里的元素可能会更晚出现。这时候可以用@testing-library/vue提供的waitFor来轮询元素,直到它出现:
import { waitFor } from '@testing-library/vue'; // ... 前面的注册和挂载代码 await waitFor(() => { const navEl = customHeaderEl.shadowRoot?.querySelector('nav'); expect(navEl).toBeInTheDocument(); }, { timeout: 2000 }); // 可以设置超时时间,默认是1000ms
额外注意事项
- 你已经确认Shadow DOM是
open模式,这点很重要——如果是closed模式,无论如何都拿不到shadowRoot,所以确保组件代码里的Shadow DOM是open的(比如attachShadow({ mode: 'open' }))。 - 如果你是在Vue SFC里直接使用自定义元素,要确保Vue不会把它当成未知组件处理——可以在Vue配置里把
custom-header加入compilerOptions.isCustomElement,避免Vue的编译警告:
// vite.config.js 或者 vue.config.js export default { vue: { compilerOptions: { isCustomElement: tag => tag === 'custom-header' } } }
这样应该就能顺利访问到Shadow DOM里的内容,进行各种断言测试了。如果还有问题,可以检查一下自定义元素的初始化逻辑,看看有没有在connectedCallback里做异步操作导致Shadow DOM延迟生成的情况,这时候只要对应延长等待时间或者等待异步操作完成就行。
备注:内容来源于stack exchange,提问作者SandyKrish




