19

问题如标题所示,即访问其父级隐藏的元素。问题在于,根据cypress.io 文档

在以下情况下,元素被视为隐藏

  • 它的宽度或高度为 0。
  • 它的 CSS 属性(或祖先)是可见性:隐藏。
  • 它的 CSS 属性(或祖先)是 display: none。
  • 它的 CSS 属性是 position:fixed 并且它在屏幕外或被掩盖。

但是我正在使用的代码要求我单击其父级隐藏的元素,而元素本身是可见的

因此,每次我尝试单击该元素时,都会引发错误读数:

CypressError:重试超时:预期“< mdc-select-item#mdc-select-item-4.mdc-list-item>”为“可见”

此元素“< mdc-select-item#mdc-select-item-4.mdc-list-item>”不可见,因为它的父元素“< mdc-select-menu.mdc-simple-menu.mdc-select__menu>”有 CSS 属性:'display: none'

在此处输入图像描述

我正在使用的元素是 a dropdown item,它是用pug. 该元素是在angular-mdc-web中定义的组件,它使用mdc-select下拉菜单mdc-select-item及其元素(项目),这是我必须访问的。

类似结构的示例代码:

//pug
mdc-select(placeholder="installation type"
            '[closeOnScroll]'="true")
    mdc-select-item(value="false") ITEM1
    mdc-select-item(value="true") ITEM2

在上面,ITEM1是我必须访问的元素。我这样做cypress.io如下:

//cypress.io
// click on the dropdown menu to show the dropdown (items)
cy.get("mdc-select").contains("installation type").click();
// try to access ITEM1
cy.get('mdc-select-item').contains("ITEM1").should('be.visible').click();

已尝试{force:true}强制单击该项目,但没有运气。已尝试使用{enter}父项上的按键选择项目mdc-select,但再次失败,因为它抛出:

CypressError:cy.type() 只能在 textarea 或 :text 上调用。您的主题是:< mdc-select-label class="mdc-select__selected-text">选择 ...</mdc-select-label>

也尝试使用selectcommand,但它不可能,因为 Cypress 引擎无法将元素识别为select元素(因为它不是,内部工作方式不同)。它抛出:

CypressError:cy.select() 只能在 . 您的主题是:< mdc-select-label class="mdc-select__selected-text">选择 ...</mdc-select-label>

问题在于,在打开下拉项目时,作为父级的 具有一些内部计算的mdc-select-menu属性mdc-select-itemdisplay:none

在此处输入图像描述

此属性被覆盖为display:flex,但这无济于事。

在此处输入图像描述

都没有想法。这适用于Selenium,但不适用于cypress.io。除了转移到其他框架或更改 UI 代码之外,任何线索可能是针对这种情况的黑客攻击?

4

6 回答 6

13

在多次咬牙切齿之后,我想我有一个答案。

我认为根本原因是mdc-select-itemhas display:flex,这使得它超出了它的父母的界限(严格来说,这感觉就像 display flex 的错误应用,但是如果我没记错教程的话......)。

赛普拉斯在确定可见性时会进行大量父级检查,请参阅visibility.coffee

## WARNING:
## developer beware. visibility is a sink hole
## that leads to sheer madness. you should
## avoid this file before its too late.
...
when $parent = parentHasDisplayNone($el.parent())
  parentNode = $elements.stringify($parent, "short")

  "This element '#{node}' is not visible because its parent '#{parentNode}' has CSS property: 'display: none'"
...
when $parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent())
  parentNode  = $elements.stringify($parent, "short")
  width       = elOffsetWidth($parent)
  height      = elOffsetHeight($parent)

  "This element '#{node}' is not visible because its parent '#{parentNode}' has CSS property: 'overflow: hidden' and an effective width and height of: '#{width} x #{height}' pixels."

但是,当使用 时.should('be.visible'),即使我们实际上可以看到孩子,我们也会遇到无法通过孩子可见性检查的父属性。
我们需要一个替代测试。

解决方法

参考jquery.js,这是元素本身可见性的一种定义(忽略父属性)。

jQuery.expr.pseudos.visible = function( elem ) {
  return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
}

所以我们可以用它作为替代方案的基础。

describe('Testing select options', function() {

  // Change this function if other criteria are required.
  const isVisible = (elem) => !!( 
    elem.offsetWidth || 
    elem.offsetHeight || 
    elem.getClientRects().length 
  )

  it('checks select option is visible', function() {

    const doc = cy.visit('http://localhost:4200')
    cy.get("mdc-select").contains("installation type").click()

    //cy.get('mdc-select-item').contains("ITEM1").should('be.visible') //this will fail
    cy.get('mdc-select-item').contains("ITEM1").then (item1 => {
      expect(isVisible(item1[0])).to.be.true
    });
  });

  it('checks select option is not visible', function() {

    const doc = cy.visit('http://localhost:4200')
    cy.get("mdc-select").contains("installation type").click()

    cy.document().then(function(document) {

      const item1 = document.querySelectorAll('mdc-select-item')[0]
      item1.style.display = 'none'

      cy.get('mdc-select-item').contains("ITEM1").then (item => {
        expect(isVisible(item[0])).to.be.false
      })
    })
  });

  it('checks select option is clickable', function() {

    const doc = cy.visit('http://localhost:4200')
    cy.get("mdc-select").contains("installation type").click()
    
    //cy.get('mdc-select-item').contains("ITEM1").click()    // this will fail
    cy.get('mdc-select-item').contains("ITEM1").then (item1 => {
    
      cy.get('mdc-select-item').contains("ITEM2").then (item2 => {
        expect(isVisible(item2[0])).to.be.true  //visible when list is first dropped
      });
          
      item1.click();
      cy.wait(500)
          
      cy.get('mdc-select-item').contains("ITEM2").then (item2 => {
        expect(isVisible(item2[0])).to.be.false  // not visible after item1 selected
      });
    });
    
  })

脚注 - 使用“then”(或“each”)

您通常在 cypress 中使用断言的方式是通过命令链,它基本上包装了正在测试的元素并处理诸如重试和等待 DOM 更改之类的事情。

.should('be.visible')但是,在这种情况下,我们在标准可见性断言和用于构建页面的框架之间存在矛盾,因此我们使用then(fn)( ref ) 来访问未包装的 DOM。然后,我们可以使用stand jasmine expect 语法应用我们自己版本的可见性测试。

事实证明,您也可以使用带有 的函数.should(fn),这也可以

it('checks select option is visible - 2', function() {
  const doc = cy.visit('http://localhost:4200')
  cy.get("mdc-select").contains("installation type").click()

  cy.get('mdc-select-item').contains("ITEM1").should(item1 => {
    expect(isVisible(item1[0])).to.be.true
  });
});

使用should而不是then在可见性测试中没有区别,但请注意该should版本可以多次重试该功能,因此它不能与click测试一起使用(例如)。

从文档中,

.then() 和 .should()/.and() 有什么区别?

使用 .then() 只允许您在回调函数中使用产生的主题,并且应该在您需要操作某些值或执行某些操作时使用。

另一方面,当使用带有 .should() 或 .and() 的回调函数时,有特殊的逻辑可以重新运行回调函数,直到其中没有断言抛出。您应该注意不希望多次执行的 .should() 或 .and() 回调函数中的副作用。

你也可以通过扩展 chai 断言来解决这个问题,但是这方面的文档并不广泛,因此可能需要更多的工作。

于 2017-12-04T01:16:59.530 回答
5

为了方便和可重用性,我不得不混合 Richard Matsen 和 Josef Biehler 的答案。

定义命令

// Access element whose parent is hidden
Cypress.Commands.add('isVisible', {
  prevSubject: true
}, (subject) => {
  const isVisible = (elem) => !!(
    elem.offsetWidth ||
    elem.offsetHeight ||
    elem.getClientRects().length
  )
  expect(isVisible(subject[0])).to.be.true
})

您现在可以将它与 contains 链接起来

describe('Testing select options', function() {
  it('checks select option is visible', function() {

    const doc = cy.visit('http://localhost:4200')
    cy.get("mdc-select").contains("installation type").click()

    //cy.get('mdc-select-item').contains("ITEM1").should('be.visible') // this will fail
    cy.get('mdc-select-item').contains("ITEM1").isVisible()
  });
});
于 2019-08-13T08:45:56.670 回答
3

我遇到了这个主题,但无法运行您的示例。所以我尝试了一下,我的最终解决方案是这样的。也许其他人也需要这个。请注意,我使用打字稿。

第一:定义自定义命令

Cypress.Commands.add("isVisible", { prevSubject: true}, (p1: string) => {
      cy.get(p1).should((jq: JQuery<HTMLElement>) => {
        if (!jq || jq.length === 0) {
            //assert.fail(); seems that we must not assetr.fail() otherwise cypress will exit immediately
            return;
        }

        const elem: HTMLElement = jq[0];
        const doc: HTMLElement = document.documentElement;
        const pageLeft: number = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
        const pageTop: number = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
        let elementLeft: number;
        let elementTop: number;
        let elementHeight: number;
        let elementWidth: number;

        const length: number = elem.getClientRects().length;

        if (length > 0) {
            // TODO: select correct border box!!
            elementLeft = elem.getClientRects()[length - 1].left;
            elementTop = elem.getClientRects()[length - 1].top;
            elementWidth = elem.getClientRects()[length - 1].width;
            elementHeight = elem.getClientRects()[length - 1].height;
        }

        const val: boolean = !!( 
            elementHeight > 0 && 
            elementWidth > 0 && 
            elem.getClientRects().length > 0 &&
            elementLeft >= pageLeft &&
            elementLeft <= window.outerWidth &&
            elementTop >= pageTop &&
            elementTop <= window.outerHeight
        );

        assert.isTrue(val);
      });
});

请注意 TODO。就我而言,我的目标是一个有两个边框的按钮。第一个高度和宽度为 0。所以我必须选择第二个。请根据您的需要进行调整。

第二:使用它

cy.wrap("#some_id_or_other_locator").isVisible();
于 2019-01-23T11:50:50.043 回答
1

一个相关问题:赛普拉斯无法找到选项卡元素,因为它的显示样式为:无(即使它在页面上可见)

我的解决方法:赛普拉斯可以通过匹配文本并单击来定位选项卡

    cy.get("[data-cy=parent-element]").contains("target text").click();
于 2020-04-17T19:07:08.847 回答
1

scrollIntoView我可以通过在获取元素后调用来解决它。看到这个答案

于 2021-01-14T18:00:38.647 回答
0

为了扩展 BTL 的答案,如果有人遇到错误 - Property 'isVisible' does not exist on type 'Chainable<JQuery<HTMLElement>>in Typescript,以下是我commands.ts在 cypress 顶部添加的内容以摆脱它 -

declare global {
    namespace Cypress {
        interface Chainable {
            isVisible;
        }
    }
} 

expect(isVisible(subject[0])).to.be.true如果您看到任何带有assert.True(isVisible(subject[0]));期望的 chai 断言错误并且不想导入它,则可能会替换为 - 如 Josef Biehler 回答中所示。

于 2019-11-14T09:44:06.520 回答