Cypress 踩坑记 - DOM 遮挡
Cypress是一个非常流行的测试工具,然而实际使用过程中发现一些问题,这里做些记录。
问题发现
在 Cypress 下 click 是非常常用的指令,然而在一些特殊场景下 click 并不能如想象中那般正常工作。
比如现在有一个弹窗,我们需要测试在点击遮罩层时是否可以正常关闭弹窗。

测试代码比较简单:
/// <reference types="cypress" />
context('Actions', () => {
beforeEach(() => {
cy.visit('http://localhost:3300/Modal');
});
it('Override', () => {
cy.get('.mantine-Button-root').click();
cy.get('.mantine-Modal-root').should('exist');
cy.get('.mantine-Modal-overlay').click();
});
});
然后执行 Cypress,发现一切如想象中那般简单,很顺利就通过了。

然而当往 Model 中填充了一些内容后,却发现突然这里就报错了。

当然,报错是没问题,遮罩层确实被内容遮挡了。问题是刚刚明明也是一样被遮挡,为何就不报错,只是因为内容多了一点就报错,这就很不合适了。
查看文档会发现 click 还支持坐标或位置参数。

然而,并没有什么用,也就是说这个点击位置无关,应该是和 Cypress 判断元素遮挡有关系,看起来 Cypress 遮挡计算还需要优化。
原因排查
排查源码可以发现 Cypress 的 click 会经过一些判定:
if (force !== true) {
// now that we know our element isn't animating its time
// to figure out if it's being covered by another element.
// this calculation is relative from the viewport so we
// only care about fromElViewport coords
$elAtCoords =
options.ensure.notCovered && ensureElIsNotCovered(cy, win, $el, coords.fromElViewport, options, _log, onScroll);
Cypress.ensure.isNotHiddenByAncestors($el, name, _log);
}
其中比较重要的参数是 coords.fromElViewport,其数值长这样:
{
"top": 0,
"left": 0,
"right": 1000,
"bottom": 660,
"topCenter": 330,
"leftCenter": 500,
"x": 500,
"y": 330
}
注意其中的 x 和 y,可以认为就是中心点的坐标。
然后 Cypress 会使用该坐标获取该位置最顶层的元素:
const getElementAtPointFromViewport = function (fromElViewport) {
// get the element at point from the viewport based
// on the desired x/y normalized coordinations
let elAtCoords;
elAtCoords = $dom.getElementAtPointFromViewport(win.document, fromElViewport.x, fromElViewport.y);
if (elAtCoords) {
$elAtCoords = $dom.wrap(elAtCoords);
return $elAtCoords;
}
return null;
};
const ensureDescendents = function (fromElViewport) {
// figure out the deepest element we are about to interact
// with at these coordinates
$elAtCoords = getElementAtPointFromViewport(fromElViewport);
debug('elAtCoords', $elAtCoords);
debug('el has pointer-events none?');
ensureElDoesNotHaveCSS($el, 'pointer-events', 'none', name, log);
debug('is descendent of elAtCoords?');
ensureIsDescendent($el, $elAtCoords, name, log);
return $elAtCoords;
};
可以发现这里直接使用 x 和 y 去获取元素,然后和当前目标元素去做了对比。
这也就是为什么 click 有时候可以点,有时候不可以的原因了,简单说就是中心点被遮了就可以点,没被遮就不可以点,还真是简单粗暴 。这也导致了 click 的不稳定现象。
结果验证
那我们来验证下是不是如此,首先我们先创建一个非常小的遮挡元素,然后放在中央位置,测试下是不是会出问题。代码如下:
import style from './Covered-1.module.css';
const Covered = () => {
return (
<div className={style.parent} data-test-id='parent'>
<div className={style.mask} data-test-id='mask'></div>
<div className={style.child} data-test-id='child'></div>
</div>
);
};
export default Covered;
.parent {
width: 100%;
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.mask {
background: rgb(0, 0, 0, 0.3);
position: absolute;
inset: 0;
}
.child {
background: purple;
width: 5px;
height: 5px;
z-index: 10;
}
测试用例就点击 mask 即可。
/// <reference types="cypress" />
context('Actions', () => {
beforeEach(() => {
cy.visit('http://localhost:3300/Covered-1');
});
it('Override', () => {
cy.get('[data-test-id="mask"]').click();
});
});
结果果然不出所料:

为了严谨,我们再测试下另一个 case,我们将四周全部用元素遮挡住,只留下中心一点,然后点击,验证下是不是可以正常。代码如下:
import style from './Covered-2.module.css';
const Covered = () => {
return (
<div className={style.parent} data-test-id='parent'>
<div className={style.mask} data-test-id='mask'></div>
<div className={style.child + ' ' + style.left} data-test-id='child'></div>
<div className={style.child + ' ' + style.right} data-test-id='child'></div>
<div className={style.child + ' ' + style.top} data-test-id='child'></div>
<div className={style.child + ' ' + style.bottom} data-test-id='child'></div>
</div>
);
};
export default Covered;
.parent {
width: 100%;
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.mask {
background: rgb(0, 0, 0, 0.3);
position: absolute;
inset: 0;
}
.child {
background: purple;
z-index: 10;
position: absolute;
top: 0;
left: 0;
}
.left,
.right {
width: 49%;
height: 100%;
}
.right {
right: 0;
left: unset;
}
.top,
.bottom {
height: 49%;
width: 100%;
}
.bottom {
top: unset;
bottom: 0;
}
测试代码无需更改:
/// <reference types="cypress" />
context('Actions', () => {
beforeEach(() => {
cy.visit('http://localhost:3300/Covered-2');
});
it('Override', () => {
cy.get('[data-test-id="mask"]').click();
});
});
不出所料,果然可以点击。

最后
说实在的 Cypress 这样的遮挡检查方式不太妥当,过于简单粗暴而且很容易让人困惑。理论上而言可以使用 layer 层层比对交叉区域来判定更为妥当。不知道是不是有什么文档导致放弃了。
还有点击的方式感觉也可以再优化一下,比如提供了坐标或者方位,那就应该以提供的坐标或方位来做遮挡判定,现在遇到这种情况只能使用 force,然而使用了 force 这个测试的意义就少了一大半。
Cypress 踩坑记 - DOM 遮挡的更多相关文章
- Vue + TypeScript + Element 搭建简洁时尚的博客网站及踩坑记
前言 本文讲解如何在 Vue 项目中使用 TypeScript 来搭建并开发项目,并在此过程中踩过的坑 . TypeScript 具有类型系统,且是 JavaScript 的超集,TypeScript ...
- Hook踩坑记:React Hook react-unity-webgl
自公司前后分离上手React以来,一个坑一个坑的踩,Class的全生命周期云里雾里,还么屡明白,就抱上了Hook的大腿不松手,确实爽到飞起.修改到Hook的过程基本比较顺畅,直接少了三分之一的代码,组 ...
- Spark踩坑记——Spark Streaming+Kafka
[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...
- Spark踩坑记——数据库(Hbase+Mysql)
[TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...
- 【踩坑记】从HybridApp到ReactNative
前言 随着移动互联网的兴起,Webapp开始大行其道.大概在15年下半年的时候我接触到了HybridApp.因为当时还没毕业嘛,所以并不清楚自己未来的方向,所以就投入了HybridApp的怀抱. Hy ...
- Spark踩坑记——共享变量
[TOC] 前言 Spark踩坑记--初试 Spark踩坑记--数据库(Hbase+Mysql) Spark踩坑记--Spark Streaming+kafka应用及调优 在前面总结的几篇spark踩 ...
- Spark踩坑记——从RDD看集群调度
[TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...
- djangorestframework+vue-cli+axios,为axios添加token作为headers踩坑记
情况是这样的,项目用的restful规范,后端用的django+djangorestframework,前端用的vue-cli框架+webpack,前端与后端交互用的axios,然后再用户登录之后,a ...
- HttpWebRequest 改为 HttpClient 踩坑记-请求头设置
HttpWebRequest 改为 HttpClient 踩坑记-请求头设置 Intro 这两天改了一个项目,原来的项目是.net framework 项目,里面处理 HTTP 请求使用的是 WebR ...
- vue踩坑记
vue踩坑记 易错点 语法好难啊qwq 不要把'data'写成'date' 在v-html/v-bind中使用vue变量时不需要加变量名 在非vue事件中使用vue中变量时需要加变量名 正确 < ...
随机推荐
- MybatisX无法自动生成entity实体类
在做项目的时候,安装MybatisX插件可以让我们不用写实体类,加快我们的开发速度,让我们更专注于业务逻辑的开发,可是最近在做项目的时候,发现MybatisX插件的MybatisX-Generator ...
- 2020 ccpc秦皇岛 赛后总结!!!!
amazing!!!! 金牌!!!!! 总结一下这次的发挥,以及如何冲到了金牌. 1 有队友单开了银牌题,50分钟过了K题,当时只有5个人过K.他敲的过程中另个队友想出来另外一题的思路,等过了K,我直 ...
- 【Unity3D】常用快捷键
1 单键 Q:扒手拖动(Scene) W:移动(GameObject) E:旋转 R:三维缩放(GameObject 不会变形) T:单维缩放(GameObject 会变形) Y:平移.旋转.缩放 F ...
- Flutter中如何取消任务
前言 在开发过程中,取消需求是很常见的,但很容易被忽略.然而,取消需求的好处也很大.例如,在页面中会发送很多请求.如果页面被切走并处于不可见状态,就需要取消未完成的请求任务.如果未及时取消,则可能会导 ...
- webrtc QOS笔记三 Nack机制浅析
nack源码浅析 nack源码浅析 Video Nack nack模块 nack list keyFrame list & recovered list nack 发送的策略 nack 模块的 ...
- ARM 详解
一.ARM 的发展史 ARM 的发展历史可以追溯到 1978 年,当年克里斯·库里(Chris Currry)所任职的公司遭遇财务危机,发展每况愈下,库里在和创始人深入沟通后,决定离职.当时的库里对微 ...
- 投资组合计算分析——R语言
"投资组合"是指金融资产(如股票.债券和现金)的任何组合.投资组合有很多类型,包括市场投资组合和零投资投资组合.可以使用以下任何一种投资方法和原则来管理投资组合的资产分配:股息加权 ...
- [Linux/CENTOS]YUM提示: Another app is currently holding the yum lock; waiting for it to exit...
1 问题描述 使用yum安装Nginx的安装依赖组件: yum -y install gcc gcc-c++ automake autoconf libtool make 但是,在执行过程中出现如下信 ...
- 数组练习 fill sort
package day05; import java.util.Arrays; //fill sort equals public class testArrays { public static v ...
- 解决svn本身上传没有权限和配置自动更新的钩子
第一步 :建立你的web程序目录和版本库目录 mkdir /data/webwww/project1 svnadmin create /data/svnwww/project1 进入/data/web ...