> 场景:网页使用 Playwright 勾选 Element UI 表格(el-table)中匹配病人的行。
一、问题描述
在 el-table 中勾选匹配病人的行时遇到三个问题:
-
表格里**部分行的复选框根本不可选**(被 CSS 隐藏),但代码识别不出来,导致对这些行执行勾选动作。
-
对不可选行调用 `locator.click()` / `check()` 时,操作**一直卡在 `waiting...`**,最终报 `Timeout exceeded`(默认等 30 秒,设短超时后等 2 秒)。
-
同时,有些行**一开始就已经被勾上了**(`is-checked`),再点会把它取消掉。
二、排查与尝试过程
尝试 1:用 `isDisabled()` 判断可选性 ------ 失败
原以为不可选行会有 `disabled` 属性。但打印两个 `<input>` 的 outerHTML 后发现两者完全一致:
```html
<input type="checkbox" aria-hidden="false" class="el-checkbox__original" value="">
```
都没有 `disabled`,结果 `isDisabled()` 对两行都返回 `false`,`if (!disabled)` 走进了不可选行并卡死。
尝试 2:检查 `pointer-events` / `is-disabled` 类 ------ 方向偏了
打印 `label` 完整 HTML 后发现两行都是 `pointerEvents: auto`、都没有 `is-disabled` 类,即不是 CSS 禁用也不是原生禁用。但第 1 行 label 带 `is-checked`------说明它是**已勾选**,不是不可选。
尝试 3:拿到完整 `<tbody>` 才看到真信号 ------ 成功定位
完整 HTML 显示选择列 `<td>` 的差异:
-
可选行:`td class="...el-table-column--selection undefined"`
-
不可选行:`td class="...el-table-column--selection hiddenTdCheckbox"`
**关键差异是选择列 `<td>` 上的 `hiddenTdCheckbox` 类**------它用 CSS 把勾选框藏起来,所以不可选。之前的 `disabled` / `is-disabled` 思路都没打到点。
卡死原因
`click()` 默认要等元素「可见、稳定、能接收事件」,`hiddenTdCheckbox` 行被隐藏,永远不满足,于一直接 `waiting` 直到超时。
三、最终解决方法
1. 用 `hiddenTdCheckbox` 类识别不可选行,提前跳过(真实信号)
```ts
const selClass = (await row.locator('td.el-table-column--selection').getAttribute('class')) ?? '';
if (selClass.includes('hiddenTdCheckbox')) continue; // 不可选,跳过
```
2. 用 `isChecked()` 跳过已勾选行(防止点一下反被取消)
```ts
const input = row.locator('input.el-checkbox__original');
if (await input.isChecked()) continue;
```
3. 用 `evaluate` 触发勾选,彻底避免等待/卡死
```ts
await input.evaluate((el: HTMLInputElement) => el.click());
```
`evaluate` 是浏览器内同步立即执行,不做任何 actionability 等待,不会被隐藏/失稳元素卡住。
4. 兜底:短超时 + `try/catch` 保证不卡流程
```ts
try {
await row.locator('.el-checkbox__inner').click({ timeout: 2000 });
} catch (e) {
console.log('点击失败,跳过:' + (e as Error).message);
}
```
四、完整逻辑
```ts
const n = await rows.count();
for (let i = 0; i < n; i++) {
const row = rows.nth(i);
const selClass = (await row.locator('td.el-table-column--selection').getAttribute('class')) ?? '';
if (selClass.includes('hiddenTdCheckbox')) continue; // 不可选行跳过
const input = row.locator('input.el-checkbox__original');
if (await input.isChecked()) continue; // 已勾选跳过
await input.evaluate((el: HTMLInputElement) => el.click()); // 勾选未选行
}
```
五、核心结论
-
Element UI 表格「不可选」行的判定信号是选择列 `<td>` 的 **`hiddenTdCheckbox` 类(CSS 隐藏)**,不是 `disabled` 属性。
-
勾选动作用 `evaluate` 触发比 `click()` / `check()` 更稳,不会被隐藏 / 重渲染元素卡住。
-
`force: true` 不是「不等待」,隐藏元素仍会超时,必须用 `hiddenTdCheckbox` 类提前跳过。