其父级被隐藏的访问元素 - cypress.io [英] Access element whose parent is hidden - cypress.io

查看:1185
本文介绍了其父级被隐藏的访问元素 - cypress.io的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题在标题中给出,即访问其父项隐藏的元素。问题是,根据



我使用的元素是下拉项,用 pug 编写。该元素是



此属性被覆盖为 display:flex ,但这没有帮助。





所有想法。这适用于 Selenium ,但不适用于 cypress.io 。任何线索除了转移到其他框架或更改UI代码之外可能存在什么样的情况?

解决方案

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



我认为根本原因是 mdc-select-item display:flex ,它允许它超过它的父母的界限(严格地说,这感觉就像显示flex的错误应用,如果我正确记得教程,但是......)。



Cypress在确定可见性时会进行大量的父检查,请参阅 visibility.coffee

  ##警告:
##开发人员要小心。能见度是一个沉重的漏洞
##导致纯粹的疯狂。你应该
##在它太晚之前避免使用这个文件。
...
当$ parent = parentHasDisplayNone($ el.parent())
parentNode = $ elements.stringify($ parent,short)

此元素'#{node}'不可见,因为其父'#{parentNode}'具有CSS属性:'display:none'
...
当$ parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($ el .parent())
parentNode = $ elements.stringify($ parent,short)
width = elOffsetWidth($ parent)
height = elOffsetHeight($ parent)

此元素'#{node}'不可见,因为其父元素'#{parentNode}'具有CSS属性:'overflow:hidden'和有效宽度和高度:'#{width} x#{height }' 像素。

但是,当使用 .should('be.visible'),即使我们实际上可以看到孩子,我们也会遇到父母属性未通过孩子可见性检查。

我们需要一个替代测试。



解决方法



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

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

因此我们可以将其作为替代方案的基础。

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

//如果需要其他标准,请更改此功能。
const isVisible =(elem)=> !!(
elem.offsetWidth ||
elem.offsetHeight ||
elem.getClientRects()。length


it('检查选择选项是可见的',function(){

const doc = cy.visit('http:// localhost:4200')
cy.get(mdc-select)。contains(安装类型)。click()

//cy.get('mdc-select-item')。contains( ITEM1)。应该('be.visible')//这将失败
cy.get('mdc-select-item')。contains(ITEM1)。then(item1 => {
expect(isVisible(item1 [0]))。to.be.true
});
});

it('检查选择选项不可见' ,function(){

const doc = cy.visit('http:// localhost:4200')
cy.get(mdc-select)。contains(安装类型)。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('check select option is clickable',function(){

const doc = cy.visit('http:// localhost:4200')
cy.get(mdc-select)。contains(安装类型)。click()

//cy.get('mdc-select-item').contains(\"ITEM1 ).click()//这将失败
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当列表首次被删除时
});

item1.click();
cy.wait(500)

cy.get('mdc -select-item')。contains(ITEM2)。then(item2 => {
expect(isVisible(item2 [0]))。to.be.false //在item1选中$ b后不可见$ b});
});

})






脚注 - 使用'then'(或'each')



你的方式通常使用asse cypress中的rtion是通过命令链,它基本上包装被测试的元素并处理重试和等待DOM更改等事情。



然而,在这种情况下,我们在标准可见性断言 .should('be.visible')以及用于构建页面的框架,因此我们使用然后(fn) ref )获取对未包装DOM的访问权限。然后,我们可以使用stand jasmine expect语法应用我们自己的可见性测试版本。



事实证明你也可以使用 .should(fn)的函数,这也适用

  it('检查选择选项是可见的 -  2',函数(){
const doc = cy.visit('http:// localhost:4200')
cy.get(mdc-select)。contains(安装类型)。click()

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

使用应该而不是然后在可见性测试中没有区别,但请注意应该版本可以多次重试该函数,因此无法使用使用点击测试(例如)。



来自文档,


.then()和.should()/。和()之间有什么区别?



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



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




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


The question is as given in the title, ie, to access element whose parent is hidden. The problem is that, as per the cypress.io docs :

An element is considered hidden if:

  • Its width or height is 0.
  • Its CSS property (or ancestors) is visibility: hidden.
  • Its CSS property (or ancestors) is display: none.
  • Its CSS property is position: fixed and it’s offscreen or covered up.

But the code that I am working with requires me to click on an element whose parent is hidden, while the element itself is visible.

So each time I try to click on the element, it throws up an error reading :

CypressError: Timed out retrying: expected '< mdc-select-item#mdc-select-item-4.mdc-list-item>' to be 'visible'

This element '< mdc-select-item#mdc-select-item-4.mdc-list-item>' is not visible because its parent '< mdc-select-menu.mdc-simple-menu.mdc-select__menu>' has CSS property: 'display: none'

The element I am working with is a dropdown item, which is written in pug. The element is a component defined in angular-mdc-web, which uses the mdc-select for the dropdown menu and mdc-select-item for its elements (items) which is what I have to access.

A sample code of similar structure :

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

In the above, ITEM1 is the element I have to access. This I do in cypress.io as follows :

//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();

Have tried with {force:true} to force the item click, but no luck. Have tried to select the items using {enter} keypress on the parent mdc-select, but again no luck as it throws :

CypressError: cy.type() can only be called on textarea or :text. Your subject is a: < mdc-select-label class="mdc-select__selected-text">Select ...< /mdc-select-label>

Also tried using the select command, but its not possible because the Cypress engine is not able to identify the element as a select element (because its not, inner workings are different). It throws :

CypressError: cy.select() can only be called on a . Your subject is a: < mdc-select-label class="mdc-select__selected-text">Select ...< /mdc-select-label>

The problem is that the mdc-select-menu that is the parent for the mdc-select-item has a property of display:none by some internal computations upon opening of the drop-down items.

This property is overwritten to display:flex, but this does not help.

All out of ideas. This works in Selenium, but does not with cypress.io. Any clue what might be a possible hack for the situation other than shifting to other frameworks, or changing the UI code?

解决方案

After much nashing-of-teeth, I think I have an answer.

I think the root cause is that mdc-select-item has display:flex, which allows it to exceed the bounds of it's parents (strictly speaking, this feels like the wrong application of display flex, if I remember the tutorial correctly, however...).

Cypress does a lot of parent checking when determining visibilty, see 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."

But, when using .should('be.visible'), we are stuck with parent properties failing child visibility check, even though we can actually see the child.
We need an alternate test.

The work-around

Ref jquery.js, this is one definition for visibility of the element itself (ignoring parent properties).

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

so we might use that as the basis for an alternative.

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
      });
    });

  })


Footnote - Use of 'then' (or 'each')

The way you normally use assertion in cypress is via command chains, which basically wraps the elements being tested and handles things like retry and waiting for DOM changes.

However, in this case we have a contradiction between the standard visibility assertion .should('be.visible') and the framework used to build the page, so we use then(fn) (ref) to get access to the unwrapped DOM. We can then apply our own version of the visibility test using stand jasmine expect syntax.

It turns out you can also use a function with .should(fn), this works as well

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
  });
});

Using should instead of then makes no difference in the visibility test, but note the should version can retry the function multiple times, so it can't be used with click test (for example).

From the docs,

What’s the difference between .then() and .should()/.and()?

Using .then() simply allows you to use the yielded subject in a callback function and should be used when you need to manipulate some values or do some actions.

When using a callback function with .should() or .and(), on the other hand, there is special logic to rerun the callback function until no assertions throw within it. You should be careful of side affects in a .should() or .and() callback function that you would not want performed multiple times.

You can also solve the problem by extending chai assertions, but the documentation for this isn't extensive, so potentially it's more work.

这篇关于其父级被隐藏的访问元素 - cypress.io的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆