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
這個測試的意義就少了一大半。