Cypress 严格实行测试隔离,默认禁止跨测试用例共享状态;推荐通过 API 调用(而非 UI 操作)复用测试数据,以保证测试的独立性、稳定性与可维护性。
首先分享我的一个深刻体会:在 Cypress 中,每个 it() 测试执行时如同被封闭在独立空间——浏览器中的全部状态,包括 localStorage、sessionStorage、cookies 以及页面 DOM,都会被彻底清空。前一测试耗费大量 UI 操作创建出的员工数据,到下一用例时便不复存在。因此,当你发现不得不反复登录、重新打开页面才能继续时,不必抱怨——这正是 Cypress 精心设计的“测试隔离”机制,也是它推崇的核心原则。
✅ 最佳实践:使用 API 辅助函数代替 UI 流程来准备数据
与其在每次测试中重复执行完整的 UI 操作(点击按钮、填写表单、提交、等待弹窗),不如封装一个轻量级的 API 调用函数,让测试在需要时快速生成所需数据。这种方法远比任何复杂的“状态共享”技巧更可靠:
// cypress/support/commands.jsCypress.Commands.add('createStaffMember', (staffData = {}) => {
const payload = {
name: staffData.name || 'Test Staff',
email: staffData.email || `test+${Date.now()}@example.com`,
role: staffData.role || 'editor'
};
cy.request('POST', '/api/staff', payload)
.then((response) => {
expect(response.status).to.eq(201);
cy.log(`✅ Staff created with ID: ${response.body.id}`);
return response.body;
});
});
借助这个辅助函数,测试中可以这样使用:
describe('Staff Management', () => {
beforeEach(() => {
cy.visit('/staff/list'); // 统一入口,不依赖前序测试状态
});
it('creates a staff member via UI', () => {
cy.get('[data-testid="add-staff-btn"]').click();
cy.get('[name="name"]').type('Jane Doe');
cy.get('[name="email"]').type('jane@example.com');
cy.get('form').submit();
cy.contains('.toast', 'Staff added successfully').should('be.visible');
});
it('displays newly created staff in list (via API setup)', () => {
// ✅ 独立创建:不依赖上一个测试的 UI 行为
cy.createStaffMember({ name: 'John Smith', email: 'john@example.com' });
// 刷新列表或触发重新加载(如需验证渲染)
cy.reload();
// 断言该员工出现在表格中
cy.contains('tbody tr', 'John Smith').should('exist');
});
});
⚠️ 注意事项与权衡
- 绝不推荐禁用测试隔离(例如设置 testIsolation: false)。虽然技术上可行,但一旦启用,失败定位能力将大幅下降——一个测试崩溃,后续一连串都会跟着失败,调试过程将异常困难。
- 不要将多个断言堆放在同一个 it() 中。有人为图省事,把“创建+搜索+编辑+删除”全部写在一个测试里,虽然暂时绕开了状态问题,但违背了原子性原则,严重影响可读性与可维护性。
- 清理测试数据(可选但推荐)。如果环境敏感或数据会长期保留,可以在 afterEach 中调用
cy.request('DELETE', '/api/staff/{id}')进行清理,保持环境干净整洁。
✅ 总结
真正健壮的 Cypress 测试,并非仅仅“模拟用户操作流程”的剧本重演,而是以开发者视角进行的前后端契约协同:UI 测试专注验证交互逻辑与视觉反馈,数据准备则交由更稳定、更高效的 API 层处理。这种分层设计不仅完美解决了状态传递难题,还显著提升了执行速度、稳定性和团队协作效率——这才是关键所在。
