热门标签 | 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



推荐阅读
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 本文介绍了停用Vaadin焦点颜色的全局方法。焦点环是一种辅助功能,用于指示字段已从键盘交互获得焦点。每个组件和主题的焦点环样式不同。文章提供了一种方便的方法来找到和修改焦点环样式,通过检查shadow DOM中的标签并覆盖相应的样式。同时,还介绍了使用with或导入样式表的方法来应用修改后的样式。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • React基础篇一 - JSX语法扩展与使用
    本文介绍了React基础篇一中的JSX语法扩展与使用。JSX是一种JavaScript的语法扩展,用于描述React中的用户界面。文章详细介绍了在JSX中使用表达式的方法,并给出了一个示例代码。最后,提到了JSX在编译后会被转化为普通的JavaScript对象。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • Hibernate延迟加载深入分析-集合属性的延迟加载策略
    本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。 ... [详细]
  • 本文介绍了2015年九月八日的js学习总结及相关知识点,包括参考书《javaScript Dom编程的艺术》、js简史、Dom、DHTML、解释型程序设计和编译型程序设计等内容。同时还提到了最佳实践是将标签放到HTML文档的最后,并且对语句和注释的使用进行了说明。 ... [详细]
  • jQuery如何判断一个元素是否被点击?
    本文介绍了使用jQuery判断一个元素是否被点击的方法,并通过示例进行了具体说明。注意要指定父级,否则会执行多次。 ... [详细]
  • Jquery 跨域问题
    为什么80%的码农都做不了架构师?JQuery1.2后getJSON方法支持跨域读取json数据,原理是利用一个叫做jsonp的概念。当然 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • 本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]
  • 本文介绍了Python字典视图对象的示例和用法。通过对示例代码的解释,展示了字典视图对象的基本操作和特点。字典视图对象可以通过迭代或转换为列表来获取字典的键或值。同时,字典视图对象也是动态的,可以反映字典的变化。通过学习字典视图对象的用法,可以更好地理解和处理字典数据。 ... [详细]
  • 本文整理了Java中org.gwtbootstrap3.client.ui.Icon.addDomHandler()方法的一些代码示例,展示了Icon.ad ... [详细]
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社区 版权所有