
在 cypress 测试中,直接在异步回调函数外部访问变量常导致 ‘未定义’ 错误。本文将详细讲解如何利用 cypress 的别名(alias)机制,从服务器响应中捕获并封装复杂数据对象。通过 `cy.wrap().as()` 创建别名,再使用 `cy.get().then()` 安全地在后续测试步骤中重用这些数据,从而有效管理测试状态,提升测试代码的健壮性和可维护性。
在 Cypress 端到端测试中,经常需要从前一个步骤(例如 API 响应)中提取数据,并在后续步骤中重用。然而,由于 Cypress 命令的异步性质和其独特的命令队列机制,直接在 cy.then() 回调函数内部定义变量并尝试在外部访问,通常会导致 ReferenceError: [变量名] is not defined 的错误。这正是许多初学者在尝试将响应数据保存为普通 JavaScript 对象并在测试中后续步骤重用时遇到的常见挑战。
理解问题:为什么直接定义变量会失败?
考虑以下场景,我们希望从服务器响应中解析出一个商品对象 Item,并在后续的 cy.addItemToShoppingCart 命令中使用它:
cy.wait('@pageLoaded').then(({ response }) => { expect(response.statusCode).to.equal(200); let i = randomItem(); // 在回调函数内部创建对象 const Item = { Id: response.body.items[i].id, IpuCode: response.body.items[i].ipuCode, Description: response.body.items[i].description, PackSize: response.body.items[i].packSize, PackType: response.body.items[i].packType, }; cy.log(Item); // 在这里可以正常访问 Item});// 尝试在这里重用 Item// cy.addItemToShoppingCart(Item.IpuCode, pharmacyId, Item.Id, currentDateTime);// 结果:ReferenceError: Item is not defined
Item 变量是在 cy.then() 的回调函数作用域内定义的。当 Cypress 执行到 cy.addItemToShoppingCart 这行代码时,外部作用域并不知道 Item 变量的存在,因为 cy.then() 的回调函数可能尚未执行,或者即使执行了,其内部变量也不会自动暴露到外部命令队列中。Cypress 的命令是排队执行的,而不是像普通 JavaScript 那样立即执行。
解决方案:使用 Cypress 别名(Aliases)管理对象数据
Cypress 提供了一种强大且优雅的机制来解决这个问题,即使用别名(Aliases)。别名允许我们将数据、DOM 元素或路由请求保存起来,并在测试的任何后续步骤中通过其别名进行引用。对于复杂的数据对象,我们可以使用 cy.wrap() 命令将其包装成一个可别名的 Cypress Subject。
1. 捕获数据并创建别名
首先,在获取到服务器响应并构建出所需的对象后,使用 cy.wrap() 将该对象包装起来,并通过 .as() 命令为其指定一个别名。
cy.wait('@pageLoaded').then(({ response }) => { expect(response.statusCode).to.equal(200); let i = randomItem(); // 从响应中提取数据并构建对象 const itemData = { id: response.body.items[i].id, ipuCode: response.body.items[i].ipuCode, description: response.body.items[i].description, packSize: response.body.items[i].packSize, packType: response.body.items[i].packType, }; // 使用 cy.wrap() 包装对象并创建别名 cy.wrap(itemData).as('itemDetails'); // 建议使用驼峰命名法 cy.log('Aliased Item Details:', itemData);});
在上述代码中,我们将从响应中解析出的商品信息封装成 itemData 对象,然后使用 cy.wrap(itemData).as(‘itemDetails’) 将其保存为名为 ‘itemDetails’ 的别名。
2. 在后续测试中重用别名对象
要访问已保存的别名对象,需要使用 cy.get(‘@aliasName’) 命令。这个命令会从 Cypress 的内部存储中检索别名所指向的值,并将其作为下一个命令的主题(subject)传递。通常,我们会紧接着使用 .then() 回调函数来处理这个别名对象。
// 在测试的后续步骤中cy.get('@itemDetails').then(item => { // 此时,item 变量就是我们之前保存的 itemDetails 对象 cy.log('Reusing Aliased Item:', item); // 现在可以安全地访问 item 对象的属性 cy.addItemToShoppingCart(item.ipuCode, pharmacyId, item.id, currentDateTime); // ... 其他使用 item 属性的 Cypress 命令});
通过这种方式,cy.get(‘@itemDetails’) 将等待 itemDetails 别名可用(即 cy.wrap().as(‘itemDetails’) 命令执行完毕),然后将其值传递给 .then() 回调函数。这样就确保了数据的正确传递和可用性,避免了作用域问题。
完整示例代码
// Cypress 测试文件describe('商品购物车功能测试', () => { let pharmacyId = 'somePharmacyId'; // 示例变量 let currentDateTime = new Date().toISOString(); // 示例变量 function randomItem() { // 模拟随机选择一个商品索引 return Math.floor(Math.random() * 5); // 假设有5个商品 } it('应该能够从响应中获取商品并添加到购物车', () => { // 1. 访问页面并等待 API 响应 cy.visit('/some-page'); // 假设页面会触发一个名为 '@pageLoaded' 的 API 请求 cy.intercept('GET', '/api/items*', { fixture: 'items.json' }).as('pageLoaded'); // 模拟 API 响应 cy.wait('@pageLoaded').then(({ response }) => { expect(response.statusCode).to.equal(200); let i = randomItem(); // 2. 从响应中解析数据并创建对象 const itemData = { id: response.body.items[i].id, ipuCode: response.body.items[i].ipuCode, description: response.body.items[i].description, packSize: response.body.items[i].packSize, packType: response.body.items[i].packType, }; // 3. 将对象保存为 Cypress 别名 cy.wrap(itemData).as('itemDetails'); cy.log('已将商品详情保存为别名:', itemData); }); // 4. 在后续测试步骤中重用别名对象 cy.get('@itemDetails').then(item => { cy.log('从别名中获取商品:', item); // 假设 cy.addItemToShoppingCart 是一个自定义 Cypress 命令 cy.addItemToShoppingCart(item.ipuCode, pharmacyId, item.id, currentDateTime); // 进一步的断言或操作 cy.get('.shopping-cart-item').should('contain', item.description); cy.get('.shopping-cart-item').should('contain', item.packSize); }); });});// 假设在 cypress/support/commands.js 中定义了自定义命令// Cypress.Commands.add('addItemToShoppingCart', (ipuCode, pharmacyId, itemId, dateTime) => {// cy.request('POST', '/api/add-to-cart', {// ipuCode,// pharmacyId,// itemId,// dateTime// }).then(response => {// expect(response.status).to.equal(200);// cy.log('商品已添加到购物车');// });// });
注意事项与最佳实践
命名约定: 在 JavaScript 中,通常推荐使用驼峰命名法(camelCase)来命名变量和对象属性(例如 ipuCode 而不是 IpuCode)。这有助于保持代码风格的一致性。别名范围: Cypress 别名在当前 it 块中是可用的。如果需要在多个 it 块之间共享数据,可以使用 beforeEach 或 before 钩子来定义别名,或者将数据存储到 Cypress.env() 中(但对于测试内部数据,别名通常更合适)。单值别名: 即使是单个值,也可以使用 cy.wrap(value).as(‘aliasName’) 来创建别名,然后通过 cy.get(‘@aliasName’).then(value => { /* use value */ }) 来访问。清晰的测试流程: 使用别名有助于将数据获取和数据使用逻辑分离,使测试代码更具可读性和可维护性。
总结
通过 Cypress 的别名机制,我们可以有效地管理测试中的异步数据流,避免因 JavaScript 作用域和 Cypress 命令队列特性引起的问题。将从响应中获取的复杂数据对象通过 cy.wrap().as() 保存为别名,并在后续步骤中通过 cy.get(‘@aliasName’).then() 安全地重用,是编写健壮、可维护的 Cypress 测试的关键实践之一。这种方法不仅解决了数据访问问题,也提升了测试代码的结构性和可读性。
以上就是在 Cypress 测试中创建和重用对象数据的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1535192.html
微信扫一扫
支付宝扫一扫