Cypress:如何滚动到临时从DOM中移除的元素?
问题描述
我有一个带可折叠行的表格,行默认展开,且是动态加载的——随页面滚动才加载,滚动出视口的行会被从DOM中移除。总行数不到50条,视口内约显示5条。
已知两个随机行的HTML ID,想要对每行执行scrollIntoView()后折叠,但滚动到第一行时,第二行被移出DOM,导致测试找不到该行而失败。
HTML结构概述
<!-- 行容器Div(可点击折叠/展开) --> <div id="行ID"> <!-- 标题Div(始终可见) --> <div>标题文本</div> <!-- 内容Div(点击后折叠/展开) --> <div>行内容</div> </div>
点击最外层Div(ID所在元素)时,内容Div会折叠/展开,标题Div始终保持可见。
已尝试的测试代码
// 页面对象类中的方法 static collapseRows(rowIds: number[]): void { rowIds.forEach((rowId) => { cy.get(`[id = "${rowId}"]`) .scrollIntoView({ duration: 1000, easing: "linear" }) .then(() => { cy.get(`[id = "${rowId}"]`).click();// 折叠或展开行 }); }); }
解决方法
1. 优先处理视口内已存在的行
先筛选出当前DOM中已经加载的行(视口内可见的),处理完这些后再滚动加载并处理剩下的行,避免滚动导致已找到的行被移除。
static collapseRows(rowIds: number[]): void { // 筛选当前DOM中存在的行 const existingRows = rowIds.filter(rowId => { return Cypress.$(`[id="${rowId}"]`).length > 0; }); // 筛选需要滚动加载的行 const needScrollRows = rowIds.filter(rowId => !existingRows.includes(rowId)); // 先处理已加载的行 existingRows.forEach(rowId => { cy.get(`[id="${rowId}"]`).click(); }); // 再处理需要滚动加载的行 needScrollRows.forEach(rowId => { cy.get(`[id="${rowId}"]`, { timeout: 5000 }) .scrollIntoView({ duration: 1000, easing: "linear" }) .click(); }); }
2. 利用始终可见的标题定位行
因为标题Div始终在DOM中,哪怕行内容被移除,也可以通过标题文本找到对应的行容器:
// 需要传入行ID和对应标题的数组 static collapseRows(rowData: {id: number, title: string}[]): void { rowData.forEach(({id, title}) => { cy.contains('div', title) .parents(`[id="${id}"]`) .scrollIntoView({ duration: 1000, easing: "linear" }) .click(); }); }
调用示例:collapseRows([{id: 123, title: "用户列表"}, {id: 456, title: "订单详情"}])
3. 强制等待行加载完成
滚动后行可能需要短暂时间加载到DOM,给查找操作增加超时时间,或者在滚动后加短等待:
static collapseRows(rowIds: number[]): void { rowIds.forEach((rowId) => { cy.scrollTo(`#${rowId}`, { duration: 1000, easing: "linear" }); cy.wait(500); // 等待行加载到DOM cy.get(`[id="${rowId}"]`, { timeout: 5000 }) .click(); }); }
4. 确保串行执行操作
forEach是同步遍历,但Cypress命令是异步队列机制,可能导致多个滚动操作并行触发。改用Cypress._.each或递归处理,保证前一个操作完成后再执行下一个:
static collapseRows(rowIds: number[]): void { Cypress._.each(rowIds, (rowId) => { cy.get(`[id="${rowId}"]`, { timeout: 5000 }) .scrollIntoView({ duration: 1000, easing: "linear" }) .click(); }); }
递归实现方式:
static collapseRows(rowIds: number[]): void { if (rowIds.length === 0) return; const [currentId, ...restIds] = rowIds; cy.get(`[id="${currentId}"]`, { timeout: 5000 }) .scrollIntoView({ duration: 1000, easing: "linear" }) .click() .then(() => { this.collapseRows(restIds); }); }
内容的提问来源于stack exchange,提问作者MasterJoe
相关产品推荐
相关产品推荐

