Cypress 的
visible
匹配器根据多种因素将元素视为可见,但它没有考虑视口,因此滚动到屏幕外的元素仍被视为可见。
我需要测试页面锚点的链接是否正常工作。单击链接后,页面将滚动到具有链接 href 中定义的 id 的元素 (
example/#some-id
)。
如何验证元素是否在视口内?
我已经拼凑了以下命令,到目前为止这些命令似乎有效,但令人惊讶的是没有开箱即用的解决方案:
Cypress.Commands.add('topIsWithinViewport', { prevSubject: true }, subject => {
const windowInnerWidth = Cypress.config(`viewportWidth`);
const bounding = subject[0].getBoundingClientRect();
const rightBoundOfWindow = windowInnerWidth;
expect(bounding.top).to.be.at.least(0);
expect(bounding.left).to.be.at.least(0);
expect(bounding.right).to.be.lessThan(rightBoundOfWindow);
return subject;
})
Cypress.Commands.add('isWithinViewport', { prevSubject: true }, subject => {
const windowInnerWidth = Cypress.config(`viewportWidth`);
const windowInnerHeight = Cypress.config(`viewportHeight`);
const bounding = subject[0].getBoundingClientRect();
const rightBoundOfWindow = windowInnerWidth;
const bottomBoundOfWindow = windowInnerHeight;
expect(bounding.top).to.be.at.least(0);
expect(bounding.left).to.be.at.least(0);
expect(bounding.right).to.be.lessThan(rightBoundOfWindow);
expect(bounding.bottom).to.be.lessThan(bottomBoundOfWindow);
return subject;
})
如果有人感兴趣的话,我对 Undistracted 的方法做了一些重构:
Cypress.Commands.add('isWithinViewport', { prevSubject: true }, (subject) => {
const rect = subject[0].getBoundingClientRect();
expect(rect.top).to.be.within(0, window.innerHeight);
expect(rect.right).to.be.within(0, window.innerWidth);
expect(rect.bottom).to.be.within(0, window.innerHeight);
expect(rect.left).to.be.within(0, window.innerWidth);
return subject;
});
Cypress.Commands.add('isOutsideViewport', { prevSubject: true }, (subject) => {
const rect = subject[0].getBoundingClientRect();
expect(rect.top).not.to.be.within(0, window.innerHeight);
expect(rect.right).not.to.be.within(0, window.innerWidth);
expect(rect.bottom).not.to.be.within(0, window.innerHeight);
expect(rect.left).not.to.be.within(0, window.innerWidth);
return subject;
});
如果您在致电之前使用过
window.innerWidth
,请使用 window.innerHeight
和 cy.viewport
。还使用 .within
来方便外部添加。
这段代码对我有用
cy.get(element).eq(index).then(($el) => {
const bottom = Cypress.$(cy.state('window')).height()
console.log("element", $el[0])
const rect = $el[0].getBoundingClientRect()
const isInViewport = (
rect.top >= -2 &&
rect.left >= 0 &&
rect.bottom <= bottom &&
rect.right <= Cypress.$(cy.state('window')).width()
)
expect(isInViewport).to.be.true
})
迄今为止最优雅的方式
function isElementFullyVisible($el) {
const rect = $el[0].getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// Usage
const $element = $('#yourElementId'); // Replace with your selector
console.log(isElementFullyVisible($element));
如果元素不在视口约束内,所有其他解决方案都会立即失败。
我们进行了测试,在滚动到元素时进行这些视口断言,这需要使用片状
cy.wait()
命令。
因此有下面的解决方案:它们等待元素满足视口条件(就像通常的 Cypress 断言一样),并且只会在常规 cypress 超时后失败。这样就避免了任何
cy.wait()
。
我们通常在 cypress 自定义命令前加上“custom”前缀(以明确它是自定义 cypress 命令),但当然您可以根据需要更改函数名称。
// add to your cypress/support/commands.js file
// usage is as expected:
//
// use this to assert only the top of the element is within the viewport:
// cy.get('target-element').customTopIsWithinViewport();
//
// use this to assert that the entire element is within the viewport:
// cy.get('target-element').customIsEntirelyWithinViewport();
Cypress.Commands.add('customTopIsWithinViewport', { prevSubject: true }, (subject) => {
return cy.wrap(subject).should(($el) => {
const rect = $el[0].getBoundingClientRect();
const windowInnerWidth = Cypress.config('viewportWidth');
const windowInnerHeight = Cypress.config('viewportHeight');
expect(rect.top, 'Element top').to.be.within(0, windowInnerHeight);
expect(rect.right, 'Element right').to.be.within(0, windowInnerWidth);
expect(rect.left, 'Element left').to.be.within(0, windowInnerWidth);
});
});
Cypress.Commands.add('customIsEntirelyWithinViewport', { prevSubject: true }, subject => {
return cy.wrap(subject).should(($el) => {
const rect = $el[0].getBoundingClientRect();
const windowInnerWidth = Cypress.config('viewportWidth');
const windowInnerHeight = Cypress.config('viewportHeight');
expect(rect.top, 'Element top').to.be.at.least(0);
expect(rect.bottom, 'Element bottom').to.be.at.most(windowInnerHeight);
expect(rect.left, 'Element left').to.be.at.least(0);
expect(rect.right, 'Element right').to.be.at.most(windowInnerWidth);
});
})