组件样式测试
你将学习到
- 为什么需要测试组件样式
- 如何正确渲染带样式的组件
- 如何处理第三方 CSS 库
为什么需要测试组件样式?
样式表是组件业务逻辑的重要组成部分。最典型的例子就是模态框组件,常见问题包括:z-index 层级问题、无法关闭遮罩层、关闭模态框后仍无法与父页面交互等。
基于 Node 的测试运行器(如 Jest 或 Vitest)无法捕获这类问题,因为它们是在 JSDom 等模拟 DOM 环境中渲染样式的。JSDom 没有盒模型,某些断言(如判断父元素是否覆盖子元素阻止点击)在没有更真实环境的情况下无法测试。
而基于浏览器的测试运行器(如 Cypress)可以渲染应用样式和组件,并利用真实的盒模型和样式渲染引擎。Cypress 的 cy.click
等命令和 should('be.visible')
等断言能确保你测试的 UI 对终端用户可见且可交互,这是浏览器测试运行器的独特优势。
正确渲染组件
首次挂载任何新组件时,你可能会发现组件显示不正常。除非你的应用完全使用组件作用域 CSS(如 Styled Components 或 Vue 的 Scoped Styles),否则需要遵循本指南才能使组件在生产环境中正常显示和行为。
确保你在生产环境中的所有操作都在组件 HTML 文件或组件支持文件中完成。
组件支持文件
加载组件或端到端测试文件时,会首先加载一个支持文件。默认情况下,首次设置 Cypress 组件测试时会自动创建该文件,位于 cypress/support/component.js
。这个文件让你有机会设置测试环境。
对于组件测试,你可以用这个文件设置通常在挂载组件前就已存在的页面级配置,例如:
- 运行时 JavaScript 代码(状态管理、路由、UI 库)
- 全局样式(样式重置、Tailwind)
一般来说,你的组件支持文件应该与应用的入口 JavaScript 文件(如 main.js、index.js)和主 CSS 文件(如 main.css、index.css)非常相似。
第三方 CSS 库(Tailwind、Bootstrap、PopperJS)
组件包含三个部分:标记、样式和脚本逻辑。三者共同作用才能交付可工作的组件。
样式也是业务逻辑的一部分。
- Tailwind
- CSS 模块
- 作用域样式
- Styled Components
- 常规样式表
- UI 库
本指南将帮助你设置测试基础设施以正确渲染组件 样式。
根据应用构建方式的不同,首次挂载新组件时可能会完全或部分丢失样式。
这很正常。许多应用都有一些在组件文件外运行的一次性设置。
我们在预期运行环境中构建应用,并假设组件总是在根级组件(如 <App>
)或带样式规则的顶级选择器(如 #app { /* 样式 */ }
)中渲染。
当尝试隔离组件进行测试时,我们需要重建这个环境。稍后会详细说明。首先,让我们讨论样式表测试以及 Cypress 与其他组件测试工具的最大区别。
导入样式表
每个应用或组件库导入样式的方式略有不同。我们将介绍几种方法,并说明如何快速重构组件使其更易于测试。
如果不遵循本指南,你的组件可以挂载,但显示不正确,且无法充分利用 Cypress 最有价值的功能,特别是对宽度、高度和溢出的隐式检查,以确保组件不仅存在于页面 HTML 中,而且对用户可见。
样式设置规则
所有应用样式最终都需要进入 Cypress,这样挂载组件时才能正确显示。
我们提供两个钩子供你配置样式:
- 名为
cypress/support/component-index.html
的 HTML 文件 - 名为
cypress/support/component.js
的 JavaScript 支持文件
创建类生产测试环境时,应始终模仿你自己的应用设置。如果应用在 head
中有多个 <link>
标签加载字体或其他样式表,确保 cypress/support/component-index.html
文件包含相同的 <link>
标签。同理,如果在应用的 main.js
文件中加载样式,确保在 cypress/support/component.js
文件中 import
这些样式。
因此,强烈建议创建一个 src/setup.js
文件,在 main.js
入口点和测试设置中复用。示例项目结构如下:
> /cypress
> /support
> /component.js
> /src
> /main.js
> /main.css
> /setup.js
setup.js 内容可能如下:
import '~normalize/normalize.css'
import 'font-awesome'
import './main.css'
export const createStore = () => {
return /* store */
}
export const createRouter = () => {
return /* router */
}
export const createApp = () => {
return <App router={createRouter()} store={createStore()}></App>
}
在 main.js
中的使用方式如下:
import { createApp } from './setup.js'
ReactDOM.render(createApp())
而 Cypress 会在支持文件中复用:
/* 就这样简单! */
import '../../src/setup.js'
本节其余部分将讨论你可能遇到的特定样式问题,包括:字体、图标字体、样式重置、全局应用样式和第三方组件库样式。
全局应用样式
全局应用样式通常位于以下位置之一:
- 在应用
head
中导入的styles.css
文件。
这应该在 Cypress 索引 HTML 文件中加载。
- 在根级组件如
App.jsx
、App.vue
、App.svelte
等中。
通过将根 CSS 从 App 或入口组 件中解耦,将这些全局样式提取到顶级样式表中。Vue 和 Svelte 都将全局应用样式嵌入到主入口组件中。应用的其余部分期望在这些组件中渲染,因此在编写这些组件时所做的任何假设都必须在测试环境中复制,否则组件显示会不正常。
<style>
/* 在某些脚手架中,App.vue 文件没有单独的样式文件 */
#app {
font-family: Sans-serif;
}
</style>
应改为
/* App.vue */ <style src="./app.css" />
和
/* cypress/support/component.js */ import '../../src/app.css'
- 在应用的
main.js
文件中(随后挂载根级组件)。
上一节已经介绍了如何复用应用开头导入的样式表。
import './main.css'
- 在配置文件中,如
next.config.js
。
你通常需要为这些样式表提供公共路径。可以在 cypress/support/component-index.html
文件中导入相同的路径。
CSS 重置或 Normalize 未生效
你是否在 cypress/support/component-index.html
或 cypress/support/component.js
中导入了 normalize 文件?
字体:所有内容都以 Times New Roman 渲染
大多数应用通过两种方式处理字体。
index.html
在head
标签中加载外部字体。
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Readex+Pro:wght@200;300;400;500;600;700&family=Roboto&display=swap"
rel="stylesheet"
/>
</head>
或通过 @import
语句
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Readex+Pro:wght@200;300;400;500;600;700&family=Roboto&display=swap');
</style>
</head>
- 主样式表加载字体
/* main.css */
@font-face {
font-family: 'Fira Sans';
src: url('fonts/fira/eot/FiraSans-Regular.eot');
src:
url('fonts/fira/eot/FiraSans-Regular.eot') format('embedded-opentype'),
url('fonts/fira/woff2/FiraSans-Regular.woff2') format('woff2'),
url('fonts/fira/woff/FiraSans-Regular.woff') format('woff'),
url('fonts/fira/woff2/FiraSans-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
图标字体:所有图标都无法渲染
主题提供者:组件显示/编译不正常,因为无法访问提供者
主题提供者或其他应用级包装器(如 I18n 或 Material UI)通过围绕应用注入自身来工作。进行组件测试时,你还没有渲染组件周围的层级结构。
要解决这类问题,人们会查看自定义命令和包装器。
首先解释为什么不对,必须先说明什么是"类生产环境"。
因此我们有这个前后对比,现在任务是逐步检查被测组件,尝试找出生产环境和测试环境之间的差异。
有时这些差异简单如颜色或字体不匹配。其他时候,整个组件或部分可能无法编译。
显示不正常的原因是:
- 我的浏览器支持深色模式
<App>
组件提供了自己的样式