热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

在Cypress中使用iframe

 返回赛普拉斯博客Cypress有一个使用iframe的困难。主要是因为所有内置的cyDOM遍历命令在它们#document到达iframe内的节点时都会硬停止。iframe

 返回赛普拉斯博客

Cypress 有一个 ... 使用 iframe 的困难。主要是因为所有内置的cyDOM 遍历命令在它们#document到达 iframe 内的节点时都会硬停止


iframe 看到 Cypress 命令时(重新制定)

如果您的 Web 应用程序使用 iframe,则处理这些 iframe 中的元素需要您自己的自定义代码。在这篇博文中,我将展示如何与 iframe 内的 DOM 元素交互(即使 iframe 是从另一个域提供的),如何监视window.fetchiframe 发出的请求,甚至如何存根来自 iframe 的 XHR 请求。

注意:您可以在存储库中的“使用 iframes”配方中找到此博客文章的源代码cypress-example-recipes


使用 iframe 的应用程序

让我们使用一个静态 HTML 页面并嵌入一个 iframe。这是完整的源代码。

<body>
<style>
iframe {
width: 90%;
height: 100%;
}
style>
<h1>XHR in iframeh1>
<iframe src="https://jsonplaceholder.cypress.io/"
data-cy="the-frame">iframe>
body>

提示:我们将按照选择元素指南的最佳实践使用data-cy属性来查找 iframe 

让我们在cypress/integration/first-spec.js访问页面的规范文件编写第一个测试

it('gets the post', () => {
cy.visit('index.html').contains('XHR in iframe')
cy.get('iframe')
})

测试通过,我们可以看到加载的 iframe。

显示 iframe

如果我们手动单击“尝试”按钮,iframe 确实会获取第一篇文章。

当用户点击“Try It”按钮时,结果显示在下方


单击 iframe 内的按钮

让我们尝试编写测试命令以找到“尝试”按钮,然后单击它。该按钮位于body元素documentiframe元素内。让我们编写一个辅助函数来获取body元素。

const getIframeDocument = () => {
return cy
.get('iframe[data-cy="the-frame"]')
// Cypress yields jQuery element, which has the real
// DOM element under property "0".
// From the real DOM iframe element we can get
// the "document" element, it is stored in "contentDocument" property
// Cypress "its" command can access deep properties using dot notation
// https://on.cypress.io/its
.its('0.contentDocument').should('exist')
}
const getIframeBody = () => {
// get the document
return getIframeDocument()
// automatically retries until body is loaded
.its('body').should('not.be.undefined')
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
.then(cy.wrap)
}
it('gets the post', () => {
cy.visit('index.html')
getIframeBody().find('#run-button').should('have.text', 'Try it').click()
getIframeBody().find('#result').should('include.text', '"delectus aut autem"')
})

不幸的是,测试失败了 -contentDocument元素永远不会从null.

Cypress 测试无法访问 iframe 的文档

我们的问题是我们的测试在域下运行localhost(您可以在浏览器的 url 中看到它),而按钮和 iframe 本身来自域jsonplaceholder.cypress.io浏览器不允许来自一个域的 Javascript 访问另一个域中的元素——这将是一个巨大的安全漏洞。因此,我们需要告诉运行测试的浏览器允许此类访问——毕竟,这是我们的测试,我们控制应用程序并且知道它嵌入的第 3 方 iframe 可以安全使用。

要启用跨域 iframe 访问,我将chromeWebSecurity在文件中将该属性设置为 falsecypress.json并重新运行测试。

{
"chromeWebSecurity": false
}

测试通过!

单击 iframe 内的按钮并断言 UI 更新


慢加载帧

在我们继续之前,我想确认即使 3rd 方 iframe 加载缓慢,我们的代码也能正常工作。我将切换默认使用 Electron 浏览器的 Cypress 在 Chrome 浏览器中运行测试。

Chrome 运行测试后(在 Cypress 创建的测试用户配置文件下),我打开Chrome 扩展程序商店并安装URL Throttler扩展程序。我启用此扩展并添加https://jsonplaceholder.cypress.io/URL 以减慢 2 秒。

URL Throttler 减慢 iframe 的加载速度

请注意测试现在如何花费超过 2 秒的时间 - 因为 iframe 被扩展程序延迟了。

使用 URL Throttler 扩展(黄色蜗牛图标)加载 iframe 会延迟 2 秒

提示:您可以在存储库中包含 Chrome 扩展并自动安装它 - 有关更多详细信息,请阅读我们的“如何在 Cypress 中加载 React DevTools 扩展”博客文章。

我们的测试使用内置命令 retries自动等待帧加载

// in getIframeDocument()
cy
.get('iframe[data-cy="the-frame"]')
.its('0.contentDocument')
// above "its" command will be retried until
// content document property exists
// in getIframeBody()
getIframeDocument()
// automatically retries until body is loaded
.its('body').should('not.be.undefined')

虽然这有效,但我必须注意,只有最后一个命令会its('body')被重试,这可能会导致测试失败。例如,Web 应用程序可能包含一个 iframe 占位符,该占位符body稍后会更改- 但我们的代码不会看到更改,因为它已经具有该contentDocument属性并且只会重试获取body(我在使用具有自己的 iframe 元素的 Stripe 信用卡小部件时看到了这种情况)。

因此,为了使测试代码更健壮并重试所有内容,我们应该将所有its命令合并为一个命令:

const getIframeBody = () => {
// get the iframe > document > body
// and retry until the body element is not empty
return cy
.get('iframe[data-cy="the-frame"]')
.its('0.contentDocument.body').should('not.be.empty')
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
// https://on.cypress.io/wrap
.then(cy.wrap)
}
it('gets the post using single its', () => {
cy.visit('index.html')
getIframeBody().find('#run-button').should('have.text', 'Try it').click()
getIframeBody().find('#result').should('include.text', '"delectus aut autem"')
})

好的。


自定义命令

我们可能会访问iframe的元素在多个测试,因此,让上面的效用函数为赛普拉斯自定义命令里面cypress/support/index.js的文件。自定义命令将自动在所有规范文件中可用,因为支持文件与每个规范文件连接在一起。

// cypress/support/index.js
Cypress.Commands.add('getIframeBody', () => {
// get the iframe > document > body
// and retry until the body element is not empty
return cy
.get('iframe[data-cy="the-frame"]')
.its('0.contentDocument.body').should('not.be.empty')
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
// https://on.cypress.io/wrap
.then(cy.wrap)
})
// cypress/integration/custom-command-spec.js
it('gets the post using custom command', () => {
cy.visit('index.html')
cy.getIframeBody()
.find('#run-button').should('have.text', 'Try it').click()
cy.getIframeBody()
.find('#result').should('include.text', '"delectus aut autem"')
})

我们可以cy.getIframeBody通过禁用内部命令的日志记录来隐藏代码中每一步的细节

Cypress.Commands.add('getIframeBody', () => {
// get the iframe > document > body
// and retry until the body element is not empty
cy.log('getIframeBody')
return cy
.get('iframe[data-cy="the-frame"]', { log: false })
.its('0.contentDocument.body', { log: false }).should('not.be.empty')
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
// https://on.cypress.io/wrap
.then((body) => cy.wrap(body, { log: false }))
})

左栏中的命令日志现在看起来好多了。

带有单个日志和断言的自定义命令


监视 window.fetch

当用户或 Cypress 单击“试用”按钮时,Web 应用程序正在向 REST API 端点发出提取请求。

来自 iframe 的 Ajax 调用

我们可以通过单击请求来检查服务器返回的响应。

在这种情况下,它是一个 JSON 对象,表示具有某些键和值的“待办事项”资源。让我们确认window.fetch应用程序使用预期参数调用了方法。我们可以使用命令cy.spy来监视对象的方法。

const getIframeWindow = () => {
return cy
.get('iframe[data-cy="the-frame"]')
.its('0.contentWindow').should('exist')
}
it('spies on window.fetch method call', () => {
cy.visit('index.html')
getIframeWindow().then((win) => {
cy.spy(win, 'fetch').as('fetch')
})
cy.getIframeBody().find('#run-button').should('have.text', 'Try it').click()
cy.getIframeBody().find('#result').should('include.text', '"delectus aut autem"')
// because the UI has already updated, we know the fetch has happened
// so we can use "cy.get" to retrieve it without waiting
// otherwise we would have used "cy.wait('@fetch')"
cy.get('@fetch').should('have.been.calledOnce')
// let's confirm the url argument
.and('have.been.calledWith', 'https://jsonplaceholder.cypress.io/todos/1')
})

我们window从 iframe获取一个对象,然后设置一个方法 spy usingcy.spy(win, 'fetch')并给它一个别名,as('fetch')以便稍后检索通过该方法的调用。我们可以看到间谍,当他们在命令日志中被调用时,我在下面的屏幕截图中用绿色箭头标记了它们。

Cypress 显示间谍和存根

提示:我们可以将实用程序函数移动getIframeWindow到自定义命令中,类似于我们创建cy.getIframeBody()命令的方式。


来自 iframe 的 Ajax 调用

监视像这样的方法调用window.fetch很有趣,但让我们更进一步。Cypress 可以直接监视和存根应用程序的网络请求,但前提是 Web 应用程序使用该XMLHttpRequest对象而不是window.fetch(我们将在#95 中修复此问题)。因此,如果我们想直接观察或存根 iframe 发出的应用程序网络调用,我们需要:



  1. window.fetchiframe 内部替换XMLHttpRequest来自应用程序窗口的内容 - 因为该对象具有 Cypress Test Runner 添加的监视和存根扩展。

  2. 调用cy.server然后使用cy.route观察网络调用。


复制 XMLHttpRequest 对象

我正在按照cypress-example-recipes 中的配方“Stubbing window.fetch”替换window.fetchunfetch polyfill - 并将XMLHttpRequest对象复制到 iframe 中。这是我们需要的实用程序代码。

let polyfill
// grab fetch polyfill from remote URL, could be also from a local package
before(() => {
const polyfillUrl = 'https://unpkg.com/unfetch/dist/unfetch.umd.js'
cy.request(polyfillUrl)
.then((response) => {
polyfill = response.body
})
})
const getIframeWindow = () => {
return cy
.get('iframe[data-cy="the-frame"]')
.its('0.contentWindow').should('exist')
}
const replaceIFrameFetchWithXhr = () => {
// see recipe "Stubbing window.fetch" in
// https://github.com/cypress-io/cypress-example-recipes
getIframeWindow().then((iframeWindow) => {
delete iframeWindow.fetch
// since the application code does not ship with a polyfill
// load a polyfilled "fetch" from the test
iframeWindow.eval(polyfill)
iframeWindow.fetch = iframeWindow.unfetch
// BUT to be able to spy on XHR or stub XHR requests
// from the iframe we need to copy OUR window.XMLHttpRequest into the iframe
cy.window().then((appWindow) => {
iframeWindow.XMLHttpRequest = appWindow.XMLHttpRequest
})
})
}

监视网络电话

这是第一个测试 - 它window.fetch监视网络调用,类似于上面监视测试。

it('spies on XHR request', () => {
cy.visit('index.html')
replaceIFrameFetchWithXhr()
// prepare to spy on XHR before clicking the button
cy.server()
cy.route('/todos/1').as('getTodo')
cy.getIframeBody().find('#run-button')
.should('have.text', 'Try it').click()
// let's wait for XHR request to happen
// for more examples, see recipe "XHR Assertions"
// in repository https://github.com/cypress-io/cypress-example-recipes
cy.wait('@getTodo').its('response.body').should('deep.equal', {
completed: false,
id: 1,
title: 'delectus aut autem',
userId: 1,
})
// and we can confirm the UI has updated correctly
getIframeBody().find('#result')
.should('include.text', '"delectus aut autem"')
})

请注意我们如何等待网络请求发生,并获得对我们可以在断言中使用的请求和响应对象的完全访问权限。

cy.wait('@getTodo').its('response.body').should('deep.equal', {
completed: false,
id: 1,
title: 'delectus aut autem',
userId: 1,
})

提示:阅读博客文章“Asserting Network Calls from Cypress Tests”以获取更多针对网络调用的断言示例。


存根网络调用

依赖 3rd 方 API 不太理想。让我们/todos/1用我们自己的存根响应替换那个调用XMLHttpRequest页面加载后的对象已经被复制和iframe是准备好了,让我们用它来返回一个对象。

it('stubs XHR response', () => {
cy.visit('index.html')
replaceIFrameFetchWithXhr()
// prepare to stub before clicking the button
cy.server()
cy.route('/todos/1', {
completed: true,
id: 1,
title: 'write tests',
userId: 101,
}).as('getTodo')
cy.getIframeBody().find('#run-button')
.should('have.text', 'Try it').click()
// and we can confirm the UI shows our stubbed response
cy.getIframeBody().find('#result')
.should('include.text', '"write tests"')
})

很好,cy.route用一个对象参数存根匹配的网络请求,我们的断言确认 iframe 显示文本“写测试”。

XHR 存根响应显示在结果区域


奖励:cypress-iframe 插件

我们的一位用户Keving Groat编写了带有自定义命令的cypress-iframe插件,简化了对 iframe 中元素的处理。安装插件,npm install -D cypress-iframe然后使用自定义命令。

// the next comment line loads the custom commands from the plugin
// so that our editor understands "cy.frameLoaded" and "cy.iframe"
///
import 'cypress-iframe'
describe('Recipe: blogs__iframes', () => {
it('fetches post using iframes plugin', () => {
cy.visit('index.html')
cy.frameLoaded('[data-cy="the-frame"]')
// after the frame has loaded, we can use "cy.iframe()"
// to retrieve it
cy.iframe().find('#run-button').should('have.text', 'Try it').click()
cy.iframe().find('#result').should('include.text', '"delectus aut autem"')
})
})

使用 cypress-iframe 命令的通过测试


结论

iframe 很烦人——我希望我们的 Cypress 团队有足够的时间来一劳永逸地解决它们。然而,它们不是表演者——您只需要按照这篇博文作为指南,并查看存储库中“使用 iframes”配方中的代码cypress-example-recipes绕过障碍。

 

参考:https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/

 


原文链接:https://www.cnblogs.com/aliceyang/p/15322215.html



推荐阅读
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
  • Python技巧:利用Cookie实现自动登录绕过验证码
    本文详细介绍了如何通过Python和Selenium库利用浏览器Cookie实现自动登录,从而绕过验证码验证。文章提供了具体的操作步骤,并附有代码示例,帮助读者理解和实践。 ... [详细]
  • ssm框架整合及工程分层1.先创建一个新的project1.1配置pom.xml ... [详细]
  • 本文探讨了如何在Classic ASP中实现与PHP的hash_hmac('SHA256', $message, pack('H*', $secret))函数等效的哈希生成方法。通过分析不同实现方式及其产生的差异,提供了一种使用Microsoft .NET Framework的解决方案。 ... [详细]
  • 探讨ChatGPT在法律和版权方面的潜在风险及影响,分析其作为内容创造工具的合法性和合规性。 ... [详细]
  • 本文详细介绍了如何解压并安装MySQL集群压缩包,创建用户和组,初始化数据库,配置环境变量,并启动相关服务。此外,还提供了详细的命令行操作步骤和常见问题的解决方案。 ... [详细]
  • 深入探讨Web页面中的锚点交互设计
    本文旨在分享Web前端开发中关于网页锚点效果的实现与优化技巧。随着Web技术的发展,越来越多的企业开始重视前端开发的质量和用户体验,而锚点功能作为提升用户浏览体验的重要手段之一,值得深入研究。 ... [详细]
  • 本文将详细介绍通过CAS(Central Authentication Service)实现单点登录的原理和步骤。CAS由耶鲁大学开发,旨在为多应用系统提供统一的身份认证服务。文中不仅涵盖了CAS的基本架构,还提供了具体的配置实例,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 掌握Mosek矩阵运算,轻松应对优化挑战
    本篇文章继续深入探讨Mosek学习笔记系列,特别是矩阵运算部分,这对于优化问题的解决至关重要。通过本文,您将了解到如何高效地使用Mosek进行矩阵初始化、线性代数运算及约束域的设定。 ... [详细]
  • 本文探讨了2019年前端技术的发展趋势,包括工具化、配置化和泛前端化等方面,并提供了详细的学习路线和职业规划建议。 ... [详细]
  • 理解文档对象模型(DOM)
    本文介绍了文档对象模型(DOM)的基本概念,包括其作为HTML文档的节点树结构,以及如何通过JavaScript操作DOM来实现网页的动态交互。 ... [详细]
  • 本文探讨了在React项目中实现子组件向父组件传递数据的方法,包括通过回调函数和使用React状态管理工具。 ... [详细]
  • 在使用C#中的Random.Next()方法生成随机数时,单次调用通常没有问题,但在循环中连续调用可能会导致生成相同的随机数。本文将探讨如何通过改进随机数生成器的初始化方式来避免这一问题。 ... [详细]
  • EasyMock实战指南
    本文介绍了如何使用EasyMock进行单元测试,特别是当测试对象的合作者依赖于外部资源或尚未实现时。通过具体的示例,展示了EasyMock在模拟对象行为方面的强大功能。 ... [详细]
  • 本文深入探讨了JavaScript中实现继承的四种常见方法,包括原型链继承、构造函数继承、组合继承和寄生组合继承。对于正在学习或从事Web前端开发的技术人员来说,理解这些继承模式对于提高代码质量和维护性至关重要。 ... [详细]
author-avatar
越野之族_205
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有