访问父级隐藏的元素 - cypress.io
Access element whose parent is hidden - cypress.io
题目如题,访问父元素被隐藏的元素。问题是,根据 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.
但是我正在使用的代码要求我单击一个元素,其 父元素是隐藏的,而元素本身是可见的.
所以每次我尝试点击该元素时,它都会抛出一个错误:
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'
我正在使用的元素是 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}
强制点击项目,但没有成功。已尝试在父 mdc-select
上使用 {enter}
按键 select 项目,但再次失败,因为它抛出:
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>
也尝试使用 select
command,但这是不可能的,因为赛普拉斯引擎无法将元素识别为 select
元素(因为它不是,内部工作方式不同)。它抛出:
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>
问题 是 mdc-select-menu
是 mdc-select-item
的父级 mdc-select-menu
具有 display:none
的 属性打开下拉项时的一些内部计算。
属性 被覆盖为 display:flex
,但这没有帮助。
完全没有想法。这适用于 Selenium
,但不适用于 cypress.io
。除了转移到其他框架或更改 UI 代码之外,对于这种情况可能有什么可能的黑客攻击的线索?
经过很多nashing-of-teeth,我想我有了答案。
我认为根本原因是mdc-select-item
有display:flex
,这使得它超出了parent的范围(严格来说,这感觉像是错误的应用显示 flex,如果我没有记错教程的话......)。
赛普拉斯在确定可见性时做了很多 parent 检查,请参阅 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')
时,我们遇到 parent 属性失败 child 可见性检查,即使我们实际上可以看到 child.
我们需要一个替代测试。
work-around
Ref jquery.js,这是元素本身可见性的一种定义(忽略 parent 属性)。
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
测试(举例)。
来自文档,
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.
您也可以通过扩展 chai 断言来解决问题,但是这方面的文档并不广泛,因此可能需要更多工作。
我遇到了这个话题,但无法 运行 你的例子。所以我尝试了一下,我的最终解决方案是这样的。也许其他人也需要这个。请注意,我使用的是打字稿。
首先:定义自定义命令
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。所以我必须 select 第二个。请根据您的需要进行调整。
第二:使用它
cy.wrap("#some_id_or_other_locator").isVisible();
为了方便和可重用性,我不得不混合 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
})
您现在可以从包含链接它
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()
});
});
为了扩展 BTL 的答案,如果有人遇到错误 - Property 'isVisible' does not exist on type 'Chainable<JQuery<HTMLElement>>
in Typescript
,以下是我在 cypress 的 commands.ts
顶部添加的内容以获取远离它 -
declare global {
namespace Cypress {
interface Chainable {
isVisible;
}
}
}
如果您看到 expect 的任何 chai 断言错误并且不想导入它,可能会将 expect(isVisible(subject[0])).to.be.true
替换为 assert.True(isVisible(subject[0]));
- 正如 Josef Biehler 的回答..
相关问题:
Cypress 无法找到选项卡元素,因为它具有显示样式:none(即使它在页面上可见)
我的解决方法:
Cypress 可以通过匹配文本并单击
来定位选项卡
cy.get("[data-cy=parent-element]").contains("target text").click();
我可以通过在获取一个元素后调用scrollIntoView
来解决它。参见 this answer。
题目如题,访问父元素被隐藏的元素。问题是,根据 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.
但是我正在使用的代码要求我单击一个元素,其 父元素是隐藏的,而元素本身是可见的.
所以每次我尝试点击该元素时,它都会抛出一个错误:
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'
我正在使用的元素是 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}
强制点击项目,但没有成功。已尝试在父 mdc-select
上使用 {enter}
按键 select 项目,但再次失败,因为它抛出:
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>
也尝试使用 select
command,但这是不可能的,因为赛普拉斯引擎无法将元素识别为 select
元素(因为它不是,内部工作方式不同)。它抛出:
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>
问题 是 mdc-select-menu
是 mdc-select-item
的父级 mdc-select-menu
具有 display:none
的 属性打开下拉项时的一些内部计算。
属性 被覆盖为 display:flex
,但这没有帮助。
完全没有想法。这适用于 Selenium
,但不适用于 cypress.io
。除了转移到其他框架或更改 UI 代码之外,对于这种情况可能有什么可能的黑客攻击的线索?
经过很多nashing-of-teeth,我想我有了答案。
我认为根本原因是mdc-select-item
有display:flex
,这使得它超出了parent的范围(严格来说,这感觉像是错误的应用显示 flex,如果我没有记错教程的话......)。
赛普拉斯在确定可见性时做了很多 parent 检查,请参阅 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')
时,我们遇到 parent 属性失败 child 可见性检查,即使我们实际上可以看到 child.
我们需要一个替代测试。
work-around
Ref jquery.js,这是元素本身可见性的一种定义(忽略 parent 属性)。
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
测试(举例)。
来自文档,
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.
您也可以通过扩展 chai 断言来解决问题,但是这方面的文档并不广泛,因此可能需要更多工作。
我遇到了这个话题,但无法 运行 你的例子。所以我尝试了一下,我的最终解决方案是这样的。也许其他人也需要这个。请注意,我使用的是打字稿。
首先:定义自定义命令
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。所以我必须 select 第二个。请根据您的需要进行调整。
第二:使用它
cy.wrap("#some_id_or_other_locator").isVisible();
为了方便和可重用性,我不得不混合 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
})
您现在可以从包含链接它
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()
});
});
为了扩展 BTL 的答案,如果有人遇到错误 - Property 'isVisible' does not exist on type 'Chainable<JQuery<HTMLElement>>
in Typescript
,以下是我在 cypress 的 commands.ts
顶部添加的内容以获取远离它 -
declare global {
namespace Cypress {
interface Chainable {
isVisible;
}
}
}
如果您看到 expect 的任何 chai 断言错误并且不想导入它,可能会将 expect(isVisible(subject[0])).to.be.true
替换为 assert.True(isVisible(subject[0]));
- 正如 Josef Biehler 的回答..
相关问题: Cypress 无法找到选项卡元素,因为它具有显示样式:none(即使它在页面上可见)
我的解决方法: Cypress 可以通过匹配文本并单击
来定位选项卡 cy.get("[data-cy=parent-element]").contains("target text").click();
我可以通过在获取一个元素后调用scrollIntoView
来解决它。参见 this answer。