|
| 1 | +--- |
| 2 | +source-updated-at: 2025-05-16T04:52:11.000Z |
| 3 | +translation-updated-at: 2025-05-20T22:55:18.019Z |
| 4 | +title: 如何在 Next.js 中配置 Cypress |
| 5 | +nav_title: Cypress |
| 6 | +description: 学习如何在 Next.js 中配置 Cypress 以进行端到端 (E2E) 测试和组件测试。 |
| 7 | +--- |
| 8 | + |
| 9 | +[Cypress](https://www.cypress.io/) 是一个用于 **端到端测试 (E2E)** 和 **组件测试** 的测试运行器。本文将展示如何在 Next.js 中配置 Cypress 并编写第一个测试。 |
| 10 | + |
| 11 | +> **警告:** |
| 12 | +> |
| 13 | +> - Cypress 13.6.3 以下版本不支持 [TypeScript 5](https://github.com/cypress-io/cypress/issues/27731) 的 `moduleResolution:"bundler"` 配置。该问题已在 Cypress 13.6.3 及更高版本中修复。[cypress v13.6.3](https://docs.cypress.io/guides/references/changelog#13-6-3) |
| 14 | +
|
| 15 | +<AppOnly> |
| 16 | + |
| 17 | +## 快速开始 |
| 18 | + |
| 19 | +您可以使用 `create-next-app` 配合 [with-cypress 示例](https://github.com/vercel/next.js/tree/canary/examples/with-cypress) 快速开始: |
| 20 | + |
| 21 | +```bash filename="终端" |
| 22 | +npx create-next-app@latest --example with-cypress with-cypress-app |
| 23 | +``` |
| 24 | + |
| 25 | +</AppOnly> |
| 26 | + |
| 27 | +## 手动配置 |
| 28 | + |
| 29 | +要手动配置 Cypress,请安装 `cypress` 作为开发依赖: |
| 30 | + |
| 31 | +```bash filename="终端" |
| 32 | +npm install -D cypress |
| 33 | +# 或 |
| 34 | +yarn add -D cypress |
| 35 | +# 或 |
| 36 | +pnpm install -D cypress |
| 37 | +``` |
| 38 | + |
| 39 | +将 Cypress 的 `open` 命令添加到 `package.json` 的 scripts 字段中: |
| 40 | + |
| 41 | +```json filename="package.json" |
| 42 | +{ |
| 43 | + "scripts": { |
| 44 | + "dev": "next dev", |
| 45 | + "build": "next build", |
| 46 | + "start": "next start", |
| 47 | + "lint": "next lint", |
| 48 | + "cypress:open": "cypress open" |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +首次运行 Cypress 以打开测试套件: |
| 54 | + |
| 55 | +```bash filename="终端" |
| 56 | +npm run cypress:open |
| 57 | +``` |
| 58 | + |
| 59 | +您可以选择配置 **E2E 测试** 和/或 **组件测试**。选择任一选项将自动在项目中创建 `cypress.config.js` 文件和 `cypress` 文件夹。 |
| 60 | + |
| 61 | +## 创建第一个 Cypress E2E 测试 |
| 62 | + |
| 63 | +确保 `cypress.config` 文件包含以下配置: |
| 64 | + |
| 65 | +```ts filename="cypress.config.ts" switcher |
| 66 | +import { defineConfig } from 'cypress' |
| 67 | + |
| 68 | +export default defineConfig({ |
| 69 | + e2e: { |
| 70 | + setupNodeEvents(on, config) {}, |
| 71 | + }, |
| 72 | +}) |
| 73 | +``` |
| 74 | + |
| 75 | +```js filename="cypress.config.js" switcher |
| 76 | +const { defineConfig } = require('cypress') |
| 77 | + |
| 78 | +module.exports = defineConfig({ |
| 79 | + e2e: { |
| 80 | + setupNodeEvents(on, config) {}, |
| 81 | + }, |
| 82 | +}) |
| 83 | +``` |
| 84 | + |
| 85 | +然后创建两个新的 Next.js 文件: |
| 86 | + |
| 87 | +<AppOnly> |
| 88 | + |
| 89 | +```jsx filename="app/page.js" |
| 90 | +import Link from 'next/link' |
| 91 | + |
| 92 | +export default function Page() { |
| 93 | + return ( |
| 94 | + <div> |
| 95 | + <h1>首页</h1> |
| 96 | + <Link href="/about">关于</Link> |
| 97 | + </div> |
| 98 | + ) |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +```jsx filename="app/about/page.js" |
| 103 | +import Link from 'next/link' |
| 104 | + |
| 105 | +export default function Page() { |
| 106 | + return ( |
| 107 | + <div> |
| 108 | + <h1>关于</h1> |
| 109 | + <Link href="/">首页</Link> |
| 110 | + </div> |
| 111 | + ) |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +</AppOnly> |
| 116 | + |
| 117 | +<PagesOnly> |
| 118 | + |
| 119 | +```jsx filename="pages/index.js" |
| 120 | +import Link from 'next/link' |
| 121 | + |
| 122 | +export default function Home() { |
| 123 | + return ( |
| 124 | + <div> |
| 125 | + <h1>首页</h1> |
| 126 | + <Link href="/about">关于</Link> |
| 127 | + </div> |
| 128 | + ) |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +```jsx filename="pages/about.js" |
| 133 | +import Link from 'next/link' |
| 134 | + |
| 135 | +export default function About() { |
| 136 | + return ( |
| 137 | + <div> |
| 138 | + <h1>关于</h1> |
| 139 | + <Link href="/">首页</Link> |
| 140 | + </div> |
| 141 | + ) |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +</PagesOnly> |
| 146 | + |
| 147 | +添加测试以验证导航功能: |
| 148 | + |
| 149 | +```js filename="cypress/e2e/app.cy.js" |
| 150 | +describe('导航测试', () => { |
| 151 | + it('应能跳转到关于页面', () => { |
| 152 | + // 从首页开始 |
| 153 | + cy.visit('http://localhost:3000/') |
| 154 | + |
| 155 | + // 找到包含 "about" 的链接并点击 |
| 156 | + cy.get('a[href*="about"]').click() |
| 157 | + |
| 158 | + // 新 URL 应包含 "/about" |
| 159 | + cy.url().should('include', '/about') |
| 160 | + |
| 161 | + // 新页面应包含 "About" 标题 |
| 162 | + cy.get('h1').contains('About') |
| 163 | + }) |
| 164 | +}) |
| 165 | +``` |
| 166 | + |
| 167 | +### 运行 E2E 测试 |
| 168 | + |
| 169 | +Cypress 会模拟用户操作,因此需要 Next.js 服务器处于运行状态。建议针对生产环境代码运行测试以更接近真实场景。 |
| 170 | + |
| 171 | +先运行 `npm run build && npm run start` 构建 Next.js 应用,然后在另一个终端窗口运行 `npm run cypress:open` 启动 Cypress 并执行 E2E 测试套件。 |
| 172 | + |
| 173 | +> **须知:** |
| 174 | +> |
| 175 | +> - 可以通过在 `cypress.config.js` 中添加 `baseUrl: 'http://localhost:3000'` 配置,使用 `cy.visit("/")` 替代完整 URL |
| 176 | +> - 或者安装 [`start-server-and-test`](https://www.npmjs.com/package/start-server-and-test) 包来同时运行 Next.js 生产服务器和 Cypress。安装后,在 `package.json` 的 scripts 字段添加 `"test": "start-server-and-test start http://localhost:3000 cypress"`。注意修改代码后需要重新构建应用 |
| 177 | +
|
| 178 | +## 创建第一个 Cypress 组件测试 |
| 179 | + |
| 180 | +组件测试可以单独构建和挂载特定组件,无需启动完整应用或服务器。 |
| 181 | + |
| 182 | +在 Cypress 应用中选择 **组件测试**,然后选择 **Next.js** 作为前端框架。这将在项目中创建 `cypress/component` 文件夹并更新 `cypress.config.js` 文件以启用组件测试。 |
| 183 | + |
| 184 | +确保 `cypress.config` 文件包含以下配置: |
| 185 | + |
| 186 | +```ts filename="cypress.config.ts" switcher |
| 187 | +import { defineConfig } from 'cypress' |
| 188 | + |
| 189 | +export default defineConfig({ |
| 190 | + component: { |
| 191 | + devServer: { |
| 192 | + framework: 'next', |
| 193 | + bundler: 'webpack', |
| 194 | + }, |
| 195 | + }, |
| 196 | +}) |
| 197 | +``` |
| 198 | + |
| 199 | +```js filename="cypress.config.js" switcher |
| 200 | +const { defineConfig } = require('cypress') |
| 201 | + |
| 202 | +module.exports = defineConfig({ |
| 203 | + component: { |
| 204 | + devServer: { |
| 205 | + framework: 'next', |
| 206 | + bundler: 'webpack', |
| 207 | + }, |
| 208 | + }, |
| 209 | +}) |
| 210 | +``` |
| 211 | + |
| 212 | +假设使用前一节的组件,添加测试验证组件渲染: |
| 213 | + |
| 214 | +<AppOnly> |
| 215 | + |
| 216 | +```tsx filename="cypress/component/about.cy.tsx" |
| 217 | +import Page from '../../app/page' |
| 218 | + |
| 219 | +describe('<Page />', () => { |
| 220 | + it('应正确渲染并显示预期内容', () => { |
| 221 | + // 挂载首页组件 |
| 222 | + cy.mount(<Page />) |
| 223 | + |
| 224 | + // 页面应包含 "Home" 标题 |
| 225 | + cy.get('h1').contains('Home') |
| 226 | + |
| 227 | + // 验证包含预期 URL 的链接可见 |
| 228 | + // 实际跳转测试更适合 E2E 测试 |
| 229 | + cy.get('a[href="/about"]').should('be.visible') |
| 230 | + }) |
| 231 | +}) |
| 232 | +``` |
| 233 | + |
| 234 | +</AppOnly> |
| 235 | + |
| 236 | +<PagesOnly> |
| 237 | + |
| 238 | +```jsx filename="cypress/component/about.cy.js" |
| 239 | +import AboutPage from '../../pages/about' |
| 240 | + |
| 241 | +describe('<AboutPage />', () => { |
| 242 | + it('应正确渲染并显示预期内容', () => { |
| 243 | + // 挂载关于页面组件 |
| 244 | + cy.mount(<AboutPage />) |
| 245 | + |
| 246 | + // 页面应包含 "About" 标题 |
| 247 | + cy.get('h1').contains('About') |
| 248 | + |
| 249 | + // 验证包含预期 URL 的链接可见 |
| 250 | + // 实际跳转测试更适合 E2E 测试 |
| 251 | + cy.get('a[href="/"]').should('be.visible') |
| 252 | + }) |
| 253 | +}) |
| 254 | +``` |
| 255 | + |
| 256 | +</PagesOnly> |
| 257 | + |
| 258 | +> **须知:** |
| 259 | +> |
| 260 | +> - Cypress 目前不支持对 `async` 服务端组件的组件测试,建议使用 E2E 测试 |
| 261 | +> - 由于组件测试不需要 Next.js 服务器,依赖服务器的功能(如 `<Image />` 组件)可能无法直接使用 |
| 262 | +
|
| 263 | +### 运行组件测试 |
| 264 | + |
| 265 | +在终端运行 `npm run cypress:open` 启动 Cypress 并执行组件测试套件。 |
| 266 | + |
| 267 | +## 持续集成 (CI) |
| 268 | + |
| 269 | +除了交互式测试,还可以使用 `cypress run` 命令在 CI 环境中运行无头测试: |
| 270 | + |
| 271 | +```json filename="package.json" |
| 272 | +{ |
| 273 | + "scripts": { |
| 274 | + //... |
| 275 | + "e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"", |
| 276 | + "e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"", |
| 277 | + "component": "cypress open --component", |
| 278 | + "component:headless": "cypress run --component" |
| 279 | + } |
| 280 | +} |
| 281 | +``` |
| 282 | + |
| 283 | +更多 Cypress 持续集成资源: |
| 284 | + |
| 285 | +- [Next.js 与 Cypress 示例](https://github.com/vercel/next.js/tree/canary/examples/with-cypress) |
| 286 | +- [Cypress 持续集成文档](https://docs.cypress.io/guides/continuous-integration/introduction) |
| 287 | +- [Cypress GitHub Actions 指南](https://on.cypress.io/github-actions) |
| 288 | +- [官方 Cypress GitHub Action](https://github.com/cypress-io/github-action) |
| 289 | +- [Cypress Discord 社区](https://discord.com/invite/cypress) |
0 commit comments