zoukankan      html  css  js  c++  java
  • 在 Cypress 中使用 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 iframe</h1>
      <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.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"
    /// <reference types="cypress-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/

  • 相关阅读:
    数据结构-栈与队列
    数据结构-选择排序
    数据结构-冒泡排序
    数据结构-插入排序
    mysql安装最后一步不响应解决
    ScvQ常用的网站(持续更新...)
    排序算法(二)
    排序算法(一)
    原码、反码、补码
    进制转换
  • 原文地址:https://www.cnblogs.com/aliceyang/p/15322215.html
Copyright © 2011-2022 走看看