5月27日 23:01

Cypress 中怎么做表单测试?

表单测试要测什么

表单是用户和系统交互的主要入口,测试不到位直接影响业务。一个注册表单如果邮箱校验没测到,上线后用户可能注册失败;一个支付表单如果金额边界没覆盖,可能导致资金问题。

Cypress 做表单测试,核心就三件事:定位元素、模拟输入、验证结果。但实际写起来,动态渲染、异步校验、跨域接口这些坑一个接一个。下面按实际开发流程一步步来。

环境准备

安装和启动没什么特别的:

bash
npm install cypress --save-dev npx cypress open

注意一点:本地测试环境和生产环境的表单行为可能不同,尤其是验证逻辑和接口响应。测试数据尽量用 fixture 管理,不要硬编码在用例里。

定位表单元素

元素定位是表单测试的第一步,也是最容易出问题的一步。选择器写得不好,页面一改测试就挂。

优先用 data-testid

javascript
cy.get('[data-testid="username-input"]') .type('testuser');

data-testid 是最稳定的定位方式,不受样式和 DOM 结构变化影响。

CSS 选择器能不用就不用

javascript
// 这种写法脆弱,class 一改就挂 cy.get('.form-control input[type="text"]') .should('be.empty');

绝对不要用的选择器

  • #id:ID 可能在重构时被移除
  • div > span > input:DOM 层级一变就全挂
  • :nth-child():顺序一调就完蛋

面试中经常问「选择器优先级」,回答 data-testid > CSS class > id > DOM 结构 基本没问题。

输入和校验

基本输入

javascript
cy.get('[data-testid="email-input"]').type('test@example.com'); cy.get('[data-testid="email-input"]').should('have.value', 'test@example.com');

实时校验的验证

很多表单有实时校验,比如邮箱格式输入过程中就提示错误:

javascript
cy.get('[data-testid="email-input"]').type('invalid-email'); cy.contains('请输入有效的邮箱地址').should('be.visible');

这里用 cy.contains() 比用 cy.get() 找错误提示更可靠,因为错误提示的 DOM 结构可能变化,但文本内容相对稳定。

密码字段的处理

javascript
cy.get('[type="password"]').type('MyPassword123!');

密码字段不要用 should('have.value') 去断言内容,因为有些浏览器安全策略会干扰。断言 should('have.prop', 'type', 'password') 确认类型就够了。

下拉框和单选框

javascript
// 下拉框选择 cy.get('select#city').select('北京'); // 单选框 cy.get('[type="radio"]').check('option1'); // 复选框 cy.get('[type="checkbox"]').check();

清除输入

javascript
cy.get('[data-testid="username-input"]').clear();

注意 clear() 在某些自定义输入框上可能不生效,这时可以试 type('{selectall}{backspace}') 代替。

表单提交和异步处理

直接提交

javascript
cy.get('button[type="submit"]').click(); cy.url().should('include', '/success');

用 intercept 拦截接口

这是面试高频考点。表单提交通常会调接口,测试不应该依赖真实后端:

javascript
cy.intercept('POST', '/api/register').as('register'); cy.get('button[type="submit"]').click(); cy.wait('@register').its('response.statusCode').should('eq', 200);

模拟接口返回

不止拦截,还可以模拟后端返回不同场景:

javascript
// 模拟注册成功 cy.intercept('POST', '/api/register', { statusCode: 200, body: { message: '注册成功' } }); // 模拟邮箱已存在 cy.intercept('POST', '/api/register', { statusCode: 409, body: { error: '邮箱已被注册' } });

这种能力让测试可以覆盖各种边界场景,不依赖后端状态。

边界场景测试

面试里最加分的就是边界场景,只测正常流程的测试用例没什么含金量。

空值提交

javascript
cy.get('button[type="submit"]').click(); cy.contains('必填字段不能为空').should('be.visible');

超长输入

javascript
const longText = 'a'.repeat(300); cy.get('[data-testid="username-input"]').type(longText); // 验证是否有长度限制提示 cy.contains('不能超过').should('be.visible');

特殊字符

javascript
cy.get('[data-testid="username-input"]').type('<script>alert(1)</script>'); // 确认 XSS 被正确处理

文件上传

javascript
cy.get('[type="file"]').attachFile({ filePath: 'test.pdf' }); cy.get('.upload-success').should('be.visible');

文件上传需要安装 cypress-file-upload 插件。

用 fixture 管理测试数据

javascript
cy.fixture('user').then((user) => { cy.get('[data-testid="username-input"]').type(user.name); cy.get('[data-testid="email-input"]').type(user.email); });

cypress/fixtures/user.json 里维护测试数据,多套数据方便覆盖不同场景。

常见坑和解决办法

元素加载延迟导致测试失败

Cypress 自带重试机制,但有时候还是不够:

javascript
// 不推荐:硬等 cy.wait(3000); // 推荐:断言驱动等待 cy.get('[data-testid="form"]').should('be.visible'); cy.get('[data-testid="password-input"]').type('password123');

原则:能用断言等待就不要用 cy.wait(时间)

跨域接口问题

Cypress 对跨域请求有限制,但测试中又经常需要调不同域的接口:

javascript
cy.intercept('POST', 'https://api.other-domain.com/login', { body: { token: 'valid' } }).as('login');

intercept 拦截跨域请求并模拟返回,绕过跨域问题。

日期选择器测试困难

很多日期选择器用了自定义渲染,原生 type() 打不进去:

javascript
// 方案1:直接赋值(绕过 UI) cy.get('input[type="date"]').invoke('val', '2025-06-01').trigger('change'); // 方案2:用 force 覆盖可见性检查 cy.get('.datepicker-input').type('2025-06-01', { force: true });

自定义输入框 clear() 不生效

有些组件库的输入框不是原生 input,clear() 不起作用:

javascript
// 替代方案 cy.get('[data-testid="search-input"]').type('{selectall}{backspace}');

自定义命令封装复用逻辑

如果多个用例都要填同一个表单,封装成自定义命令:

javascript
// cypress/support/commands.js Cypress.Commands.add('fillLoginForm', (username, password) => { cy.get('[data-testid="username-input"]').type(username); cy.get('[data-testid="password-input"]').type(password); }); // 用例中使用 it('登录成功', () => { cy.fillLoginForm('admin', 'password123'); cy.get('button[type="submit"]').click(); cy.url().should('include', '/dashboard'); });

命令封装让用例更简洁,改起来也只改一处。

测试的组织和运行

用 before/beforeEach 准备数据

javascript
describe('注册表单测试', () => { beforeEach(() => { cy.visit('/register'); }); it('正常注册', () => { /* ... */ }); it('邮箱为空提示错误', () => { /* ... */ }); it('密码强度不足提示错误', () => { /* ... */ }); });

每个 it 块保持独立,不依赖其他用例的执行结果。

并行执行

bash
npx cypress run --parallel

并行执行要注意:测试用例之间不能有状态依赖,否则并行时会出现随机失败。

面试高频问题速查

  1. Cypress 做表单测试的核心步骤? 定位元素、模拟输入、提交表单、验证结果。

  2. 为什么优先用 data-testid? 稳定,不受样式和 DOM 结构变化影响。

  3. cy.intercept 和 cy.route 的区别? cy.route 是旧 API,Cypress 6+ 已废弃;cy.intercept 支持拦截和修改请求/响应,功能更强大。

  4. 怎么测试异步校验?cy.intercept 拦截校验接口,cy.wait 等待响应,再断言 UI 反馈。

  5. 表单测试怎么处理跨域?cy.intercept 模拟返回绕过跨域,或者在 cypress.config.js 配置 e2e.experimentalOriginDependencies

  6. Cypress 的自动重试机制和手动 wait 怎么选? 优先用断言驱动等待(should),只在断言无法覆盖的场景(如动画)才用 cy.wait()

  7. 如何测试文件上传? 安装 cypress-file-upload 插件,使用 attachFile() 方法。

  8. 自定义输入框 clear() 不生效怎么办?type('{selectall}{backspace}') 替代。

  9. 怎么管理多套测试数据?cy.fixture() 加载 JSON 文件,不同场景用不同 fixture。

  10. 表单测试常见的边界场景有哪些? 空值提交、超长输入、特殊字符/XSS、并发提交、网络超时。

标签:Cypress