Stencil 组件测试实战指南:使用 WebdriverIO 编写真实浏览器组件测试
发布时间:2026/9/23 2:53:05 锦皓数字建站

开发工具前端前端构建【免费下载链接】stencilA toolchain for building scalable, enterprise-ready component systems on top of TypeScript and Web Component standards. Stencil components can be distributed natively to React, Angular, Vue, ( more) and traditional web applications from a single, framework-agnostic codebase.项目地址https://gitcode.com/gh_mirrors/st/stencil点击查看免费下载导读Web Components 组件只有在真实浏览器环境中运行才能暴露诸如属性序列化、Shadow DOM 插槽分配、Scoped 样式隔离、生命周期与用户交互之间的竞态条件等编译期发现不了、单测测不出的问题。本指南以 Stencil 仓库自带的端到端组件测试目录 test/wdio 为核心系统讲解如何基于 WebdriverIO 的wdio/browser-runner为 Stencil 组件编写、运行与调试真实浏览器测试涵盖测试目录规划、构建流程、渲染 API、异步匹配器与多浏览器配置并深入源码佐证其底层实现帮助你为组件系统搭建一套可复用的真实浏览器测试基础设施。一、认识 WebdriverIO 组件测试体系test/wdio目录存放的是一组 Stencil 组件测试用于验证涉及用户交互或组件渲染的各种场景。与纯 Node 环境的 Jest 单元测试不同这里的测试运行在真实浏览器中能够覆盖用户点击、输入等交互触发组件重渲染后的 DOM 状态属性Prop序列化、反射到属性reflect等浏览器侧行为Shadow DOM 与 Scoped 封装下的插槽slot内容分发生命周期钩子、监听器listener在真实事件循环下的执行顺序CSS 变量、自定义样式、全局脚本等浏览器特性。从目录结构可以看到test/wdio 下按行为主题划分了几十个测试套件attribute-basic属性基础、slot-nested-order嵌套插槽顺序、slot-hide-content无目标插槽时隐藏内容、shadow-dom-basic、event-listener-capture、scoped-slot-*系列、lifecycle-*系列等。每个子目录内的组件和测试文件同处一地保持了良好的内聚性。可用脚本一览test/wdio/package.json 中定义了三个核心脚本脚本作用npm run build使用 Stencil 编译器将所有组件构建为lazy-loaded bundle同时构建主应用、global-script、prerender、invisible-prehydration、es2022、auto-loader、test-sibling 等多个变体npm run wdio运行 WebdriverIO 测试npm test依次执行build→wdio→end-to-end如果从仓库根目录运行package.json 中还有两条封装命令npm run test.wdio # cd test/wdio npm ci npm run test npm run test.wdio.testOnly # 跳过构建仅执行 wdio 测试test.wdio: cd test/wdio npm ci npm run test, test.wdio.testOnly: cd test/wdio npm ci npm run wdio筛选与调试可以通过--spec参数只运行特定场景npm run wdio -- --spec conditional-basic # 等价于运行 test/wdio/conditional-basic/cmp.test.tsx调试时推荐开启 watch 模式让测试文件修改后自动重跑npm run wdio -- --spec conditional-basic --watch需要注意的是watch 模式只监听测试文件的改动。如果修改了被测组件cmp.tsx需要另开一个终端手动重新执行npm run build把组件重新编译成 lazy bundle测试才能感知到变更。这正是组件必须先构建、后测试这一设计约束的体现。二、测试运行原理组件如何进入浏览器2.1 两步式流程WebdriverIO 组件测试遵循一个关键前提所有组件必须预先编译成 lazy-loaded Stencil 组件才能执行测试。整个流程分两步npm run build由node ../../bin/stencil build --es5等命令编译组件。构建产物包括dist、dist-custom-elements输出到test-components目录customElementsExportBehavior: bundle、dist-hydrate-script输出到hydrate目录等见 test/wdio/stencil.config.ts。npm run wdioWebdriverIO 的浏览器 runner 启动后通过 Mocha 的require钩子加载设置脚本 test/wdio/setup.ts。2.2 setup.ts 做了什么test/wdio/setup.ts 是连接构建产物与测试用例的桥梁其核心逻辑是const testRequiresManualSetup window.__wdioSpec__.includes(custom-elements-output-tag-class-different) || window.__wdioSpec__.includes(custom-elements-delegates-focus) || window.__wdioSpec__.includes(custom-elements-output) || window.__wdioSpec__.includes(no-external-runtime) || window.__wdioSpec__.includes(global-script) || window.__wdioSpec__.endsWith(custom-tag-name.test.tsx) || // ... if (!testRequiresManualSetup) { await import(./dist/testapp/testapp.esm.js); } if (window.__wdioSpec__.includes(global-script.test.tsx)) { await import(./www-global-script/build/testglobalscript.esm.js); }它依据当前运行 spec 的文件名通过window.__wdioSpec__注入判断绝大多数测试套件直接动态import(./dist/testapp/testapp.esm.js)把全部编译好的自定义组件注册到浏览器测试文件里无需任何额外 import即可使用my-component标签少数特殊套件如custom-elements-output、no-external-runtime、global-script等需要测试自己手动设置组件因此被排除在自动注册之外涉及全局脚本的测试还会额外加载独立的testglobalscript.esm.js构建产物。该脚本顶部注释还引用了 WebdriverIO 官方wdio-browser-runner的 setup 实现packages/wdio-browser-runner/src/browser/setup.ts作为该做法的依据说明这是浏览器 runner 的标准加载机制。2.3 wdio.conf.ts 的 runner 配置test/wdio/wdio.conf.ts 中runner 被配置为browser并使用preset: stencilrunner: [ browser, { preset: stencil, viteConfig: { resolve: { alias: { stencil/core/internal: path.resolve(__dirname, .., .., internal), stencil/core: path.resolve(__dirname, .., .., internal), }, }, }, }, ],关键点preset: stencil是 WebdriverIO 为 Stencil 提供的官方预设负责把测试中的 JSX/TSX 正确编译并在浏览器中渲染组件通过 Vitealias把stencil/core指向仓库自身的internal目录保证测试使用当前仓库正在开发的运行时而非 npm 上发布的版本这是仓库自测self-hosting的核心技巧测试文件通过specs: [[./**/*.test.tsx, ./**/*.test.ts]]匹配框架为 Mochaui: bdd并通过mochaOpts.require: [./setup.ts]注入 2.2 节所述设置脚本maxInstances: 10控制并行 worker 数waitforTimeout: 3000是异步匹配的默认超时specFileRetries: isCI ? 1 : 0在 CI 环境对失败 spec 重试一次。2.4 多浏览器支持配置通过BROWSER环境变量控制运行目标浏览器BROWSERCHROME npm run wdio # 默认值仅 Chrome BROWSERFIREFOX npm run wdio # 仅 Firefox BROWSEREDGE npm run wdio # 仅 Edge BROWSERALL npm run wdio # Chrome Edge 全量对应能力capability的注册逻辑位于 test/wdio/wdio.conf.ts 末尾if ([CHROME, ALL].includes(BROWSER_CONFIGURATION)) { (config.capabilities as WebdriverIO.Capabilities[]).push({ browserName: chrome, browserVersion: stable, wdio:enforceWebDriverClassic: true, // this is 3x faster? }); } // FIREFOX 与 EDGE 分支同理值得注意的是配置中明确注释Disable FF tests due to issues in the WebDriver protocol且ALL组合仅包含 Chrome 与 EdgeFirefox 需要显式通过BROWSERFIREFOX单独触发——这是仓库实测得出的兼容性取舍并非所有浏览器都默认纳入全量回归。三、创建新的测试套件3.1 目录与命名约定测试套件存放在test/wdio下的子目录中与被测组件同处一地colocated。创建步骤如下第一步创建一个描述性的目录名最好是能简洁概括被测行为的名词或动词mkdir test/wdio/my-excellent-new-test-suite仓库中现成的命名示例svg-class、slot-nested-order、slot-hide-content、conditional-basic、event-listener-capture等均一目了然地描述被测行为。第二步在目录内创建被测组件cmp.tsx只保留足以复现目标行为的最小实现。标签名tag与类名可自定义但尽量与测试主题相关例如更偏好slot-relocation而非my-test-component-2。[!IMPORTANT]组件标签名必须全局唯一不得与其他任何测试套件的组件标签冲突。因为setup.ts会把所有构建产物一次性注册进浏览器重名标签会互相覆盖、导致测试结果不可信。第三步创建测试文件必须以.test.tsx结尾。约定命名方式为主组件文件名换扩展名若组件写在cmp.tsx测试就写在cmp.test.tsx。第四步运行npm run build或从根目录执行npm run test.wdio它会自动先构建新组件才会进入 lazy bundle 供测试使用。3.2 从零看一个真实套件attribute-basic以 test/wdio/attribute-basic/cmp.tsx 为例被测组件覆盖了多种属性形态import { Component, h, Prop } from stencil/core; Component({ tag: attribute-basic, }) export class AttributeBasic { private _getter getter; Prop() single single; Prop() multiWord multiWord; Prop({ attribute: my-custom-attr }) customAttr my-custom-attr; Prop() get getter() { return this._getter; } set getter(newVal: string) { this._getter newVal; } render() { return ( div div classsingle{this.single}/div div classmultiWord{this.multiWord}/div div classcustomAttr{this.customAttr}/div div classgetter{this.getter}/div div label classhtmlForLabel htmlFor{a} htmlFor /label input typecheckbox id{a}/input /div /div ); } }对应测试文件 test/wdio/attribute-basic/cmp.test.tsximport { h } from stencil/core; import { render } from wdio/browser-runner/stencil; import { $, expect } from wdio/globals; describe(attribute-basic, () { before(async () { render({ template: () attribute-basic-root/attribute-basic-root, }); }); it(button click rerenders, async () { await $(attribute-basic.hydrated).waitForExist(); await expect($(.single)).toHaveText(single); await expect($(.multiWord)).toHaveText(multiWord); await expect($(.customAttr)).toHaveText(my-custom-attr); await expect($(.htmlForLabel)).toHaveAttribute(for, a); await expect($(.getter)).toHaveText(getter); const button await $(button); await button.click(); await expect($(.single)).toHaveText(single-update); await expect($(.multiWord)).toHaveText(multiWord-update); await expect($(.customAttr)).toHaveText(my-custom-attr-update); await expect($(.getter)).toHaveText(getter-update); }); });这个套件同时验证了属性默认值渲染、Prop({ attribute })自定义属性名映射、getter/setter 形式的Prop以及交互点击后组件重渲染输出新文本的完整链路。四、编写测试用例4.1 用render渲染组件渲染组件使用wdio/browser-runner/stencil提供的render辅助方法import { render } from wdio/browser-runner/stencil; render({ template: () my-component/my-component, });由于 setup.ts 已经注册了所有编译好的组件测试里不需要 import 组件本身模板中直接写 JSX 标签即可。4.2 组织测试结构推荐使用常见的describe/it语法组织测试用before/beforeEach钩子把组件渲染进页面。完整的简单示例import { h } from stencil/core; import { render } from wdio/browser-runner/stencil; import { $, expect } from wdio/globals; describe(attribute-basic, function () { before(async () { render({ template: () attribute-basic-root/attribute-basic-root, }); }); it(button click rerenders, async () { await expect($(.single)).toHaveText(single); // ... }); });$选择器与expect断言均来自wdio/globals与 WebdriverIO 的 e2e 测试 API 保持一致。4.3 真实套件解析slot-hide-contenttest/wdio/slot-hide-content/cmp.test.tsx 展示了更复杂的用法——在beforeEach中渲染带插槽内容的组件、为按钮绑定 DOM 事件、并直接操作document查询节点import { Fragment, h } from stencil/core; import { render } from wdio/browser-runner/stencil; describe(slot-hide-content, function () { beforeEach(async () { render({ template: () ( slot-hide-content-scoped classNametest-cmp p idslotted-1Hello/p /slot-hide-content-scoped slot-hide-content-open classNametest-cmp p idslotted-2Hello/p /slot-hide-content-open button typebuttonEnable slot/button / ), }); await $(button).waitForExist(); document.querySelector(button).addEventListener(click, () { document.querySelectorAll(.test-cmp).forEach((ref) ref.setAttribute(enabled, true)); }); }); describe(scoped encapsulation, () { it(should hide content when no slot is provided, async () { const host document.body.querySelector(slot-hide-content-scoped); const slottedContent host.querySelector(#slotted-1); expect(slottedContent).toBeDefined(); expect(slottedContent.hasAttribute(hidden)).toBe(true); expect(slottedContent.parentElement.tagName).toContain(SLOT-HIDE-CONTENT-SCOPED); document.querySelector(button).click(); await browser.pause(); expect(slottedContent.hasAttribute(hidden)).toBe(false); expect(slottedContent.parentElement.classList).toContain(slot-wrapper); }); }); });注意这里既使用了await expect($(button)).toExist()这类异步匹配等待按钮渲染就绪也允许在钩子里直接操作真实 DOM 事件再通过browser.pause()等待渲染更新后同步断言——两种方式可以按场景混用。4.4 条件渲染测试conditional-basictest/wdio/conditional-basic/cmp.test.tsx 是交互驱动重渲染的极简范式初始状态下结果区为空文本点击按钮后变为Contentdescribe(conditional-basic, () { beforeEach(async () { render({ template: () conditional-basic/conditional-basic, }); }); it(contains a button as a child, async () { await expect($(button)).toBeExisting(); }); it(button click rerenders, async () { const button $(button); const results $(div.results); await expect(results).toHaveText(); await button.click(); await expect(results).toHaveText(Content); }); });两个测试共享同一个beforeEach渲染describe/it结构把按钮存在性与点击后重渲染拆成两个独立断言场景。五、异步匹配器避免竞态条件的正确姿势5.1 为什么必须用异步匹配组件渲染是异步的元素可能尚未挂载、文本可能尚未更新。若用同步断言直接读取 DOM// 组件此刻可能尚未渲染元素不存在或文本不对 expect(document.querySelector(.single).textContent).toBe(single);这一行在元素还没渲染出来时就会直接抛错属于典型的竞态条件race condition。而 WebdriverIO 的异步匹配器会自动重跑断言直到条件满足或超时失败// 让 WebdriverIO 反复抓取并断言组件内容直到条件满足 await expect($(.single)).toHaveText(single);其语义是在超时窗口内默认waitforTimeout: 3000ms可在 wdio.conf.ts 调整反复求值$(.single)的文本是否为single期间组件完成渲染/重渲染即通过从而彻底规避时序问题。仓库中几乎每个.test.tsx文件的断言都遵循这一写法。5.2 常见异步匹配器速查匹配器用途仓库使用示例toHaveText(text)断言元素文本内容await expect($(.single)).toHaveText(single)toHaveAttribute(name, value)断言属性值await expect($(.htmlForLabel)).toHaveAttribute(for, a)toBeExisting()/toExist()断言元素已存在await expect($(button)).toBeExisting()waitForExist()显式等待元素出现await $(attribute-basic.hydrated).waitForExist()配合 WebdriverIO 官方文档中的组件测试、API 与 Expect Matchers 手册见原文档 Resources 一节所列主题可继续深入。六、从源码印证这套测试在验证什么test/wdio中约 100 个测试套件覆盖的正是 Stencil 运行时src/runtime与编译器src/compiler中最容易出问题的行为面从套件命名即可映射到对应实现模块插槽与 scoped 封装slot-*、scoped-slot-*、shadow-dom-*系列验证 src/runtime/slot-polyfill-utils.ts 与 src/utils/shadow-css.ts 中的内容分发与样式作用域逻辑属性与反射attribute-*、reflect-*、property-serializer对应 src/runtime/parse-property-value.ts 的属性序列化与 src/runtime/set-value.ts 的赋值路径生命周期与监听器lifecycle-*、listen-*、event-*对应 src/runtime/initialize-component.ts、src/runtime/host-listener.ts条件/重渲染conditional-*、async-rerender、key-reorder对应 src/runtime/update-component.ts 与 src/runtime/vdom 的虚拟 DOM 协调逻辑样式css-variables、dynamic-css-variables、slotted-css、style-plugin验证 src/runtime/styles.ts 的样式注入与 CSS 变量继承。这些测试由 wdio.conf.ts 统一驱动Mocha 框架timeout: 60000、retries: 1保证每个用例在真实浏览器中得到充分执行是 Stencil 发布流程test.prod包含test.wdio不可或缺的一环。七、本地运行与调试清单按以下顺序即可在本地完整跑通# 1. 进入测试目录并安装依赖 cd test/wdio npm ci # 2. 构建全部测试组件lazy bundle 及各变体 npm run build # 3. 只跑一个场景调试期推荐 npm run wdio -- --spec conditional-basic # 4. 调试模式修改测试文件自动重跑 npm run wdio -- --spec conditional-basic --watch # 5. 从仓库根目录一键运行自动 npm ci build wdio npm run test.wdio常见注意事项修改被测组件cmp.tsx后必须手动重跑npm run buildwatch 模式不会监听组件文件新增测试套件时确保组件标签名全局唯一见 setup.ts 的全量注册机制断言一律使用await expect($(...)).toXxx()异步匹配形式避免同步读 DOM 引入竞态需要多浏览器验证时通过BROWSER环境变量选择Chrome 为默认Firefox 需显式指定全量ALL组合为 Chrome Edge。结语以 WebdriverIObrowserrunner 为核心、以先编译 lazy bundle、再在真实浏览器中渲染断言为基本盘的这套测试体系是 Stencil 在 test/wdio 目录下沉淀出的成熟实践renderAPI 屏蔽了组件注册细节异步匹配器消除了时序竞态多浏览器 capability 支持跨引擎回归。无论你是 Stencil 组件库的维护者还是希望为自有组件系统引入真实浏览器测试都可以直接复用 test/wdio/wdio.conf.ts 与 test/wdio/setup.ts 的组合快速搭建起同样可构建、可筛选、可调试、可并行扩展的组件测试流水线。赞分享开发工具前端前端构建【免费下载链接】stencilA toolchain for building scalable, enterprise-ready component systems on top of TypeScript and Web Component standards. Stencil components can be distributed natively to React, Angular, Vue, ( more) and traditional web applications from a single, framework-agnostic codebase.项目地址https://gitcode.com/gh_mirrors/st/stencil点击查看免费下载相关推荐使用 WebdriverIO Browser Runner 在真实浏览器中测试 Stencil 组件使用 WebdriverIO Browser Runner 在真实浏览器中测试 Stencil 组件 本篇技术指南讲解如何在 WebdriverIO 的浏览器运测试质量保障WebdriverIO React 组件测试完整指南基于真实浏览器的组件测试实战WebdriverIO React 组件测试完整指南基于真实浏览器的组件测试实战 导读 本文基于 WebdriverIO 的 Browser Runner浏测试质量保障WebdriverIO 组件测试指南用 Browser Runner 在真实浏览器中测试 Vue.js 组件WebdriverIO 组件测试指南用 Browser Runner 在真实浏览器中测试 Vue.js 组件 Vue.js 是一个上手简单、性能出色且用途广泛测试质量保障创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。