Cypress 中怎么做表单测试?
表单测试要测什么
表单是用户和系统交互的主要入口,测试不到位直接影响业务。一个注册表单如果邮箱校验没测到,上线后用户可能注册失败;一个支付表单如果金额边界没覆盖,可能导致资金问题。
Cypress 做表单测试,核心就三件事:定位元素、模拟输入、验证结果。但实际写起来,动态渲染、异步校验、跨域接口这些坑一个接一个。下面按实际开发流程一步步来。
环境准备
安装和启动没什么特别的:
bashnpm install cypress --save-dev npx cypress open
注意一点:本地测试环境和生产环境的表单行为可能不同,尤其是验证逻辑和接口响应。测试数据尽量用 fixture 管理,不要硬编码在用例里。
定位表单元素
元素定位是表单测试的第一步,也是最容易出问题的一步。选择器写得不好,页面一改测试就挂。
优先用 data-testid
javascriptcy.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 结构 基本没问题。
输入和校验
基本输入
javascriptcy.get('[data-testid="email-input"]').type('test@example.com'); cy.get('[data-testid="email-input"]').should('have.value', 'test@example.com');
实时校验的验证
很多表单有实时校验,比如邮箱格式输入过程中就提示错误:
javascriptcy.get('[data-testid="email-input"]').type('invalid-email'); cy.contains('请输入有效的邮箱地址').should('be.visible');
这里用 cy.contains() 比用 cy.get() 找错误提示更可靠,因为错误提示的 DOM 结构可能变化,但文本内容相对稳定。
密码字段的处理
javascriptcy.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();
清除输入
javascriptcy.get('[data-testid="username-input"]').clear();
注意 clear() 在某些自定义输入框上可能不生效,这时可以试 type('{selectall}{backspace}') 代替。
表单提交和异步处理
直接提交
javascriptcy.get('button[type="submit"]').click(); cy.url().should('include', '/success');
用 intercept 拦截接口
这是面试高频考点。表单提交通常会调接口,测试不应该依赖真实后端:
javascriptcy.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: '邮箱已被注册' } });
这种能力让测试可以覆盖各种边界场景,不依赖后端状态。
边界场景测试
面试里最加分的就是边界场景,只测正常流程的测试用例没什么含金量。
空值提交
javascriptcy.get('button[type="submit"]').click(); cy.contains('必填字段不能为空').should('be.visible');
超长输入
javascriptconst longText = 'a'.repeat(300); cy.get('[data-testid="username-input"]').type(longText); // 验证是否有长度限制提示 cy.contains('不能超过').should('be.visible');
特殊字符
javascriptcy.get('[data-testid="username-input"]').type('<script>alert(1)</script>'); // 确认 XSS 被正确处理
文件上传
javascriptcy.get('[type="file"]').attachFile({ filePath: 'test.pdf' }); cy.get('.upload-success').should('be.visible');
文件上传需要安装 cypress-file-upload 插件。
用 fixture 管理测试数据
javascriptcy.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 对跨域请求有限制,但测试中又经常需要调不同域的接口:
javascriptcy.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 准备数据
javascriptdescribe('注册表单测试', () => { beforeEach(() => { cy.visit('/register'); }); it('正常注册', () => { /* ... */ }); it('邮箱为空提示错误', () => { /* ... */ }); it('密码强度不足提示错误', () => { /* ... */ }); });
每个 it 块保持独立,不依赖其他用例的执行结果。
并行执行
bashnpx cypress run --parallel
并行执行要注意:测试用例之间不能有状态依赖,否则并行时会出现随机失败。
面试高频问题速查
-
Cypress 做表单测试的核心步骤? 定位元素、模拟输入、提交表单、验证结果。
-
为什么优先用 data-testid? 稳定,不受样式和 DOM 结构变化影响。
-
cy.intercept 和 cy.route 的区别?
cy.route是旧 API,Cypress 6+ 已废弃;cy.intercept支持拦截和修改请求/响应,功能更强大。 -
怎么测试异步校验? 用
cy.intercept拦截校验接口,cy.wait等待响应,再断言 UI 反馈。 -
表单测试怎么处理跨域? 用
cy.intercept模拟返回绕过跨域,或者在cypress.config.js配置e2e.experimentalOriginDependencies。 -
Cypress 的自动重试机制和手动 wait 怎么选? 优先用断言驱动等待(
should),只在断言无法覆盖的场景(如动画)才用cy.wait()。 -
如何测试文件上传? 安装
cypress-file-upload插件,使用attachFile()方法。 -
自定义输入框 clear() 不生效怎么办? 用
type('{selectall}{backspace}')替代。 -
怎么管理多套测试数据? 用
cy.fixture()加载 JSON 文件,不同场景用不同 fixture。 -
表单测试常见的边界场景有哪些? 空值提交、超长输入、特殊字符/XSS、并发提交、网络超时。