如何在 cucumberjs / puppeteer 中验证 selector 不存在?
结论
selector 不存在时,不必使用 assert.throws()。具体写法取决于测试目的:
- 检查元素当前是否不存在:使用
page.$(),并断言返回值为null。 - 等待元素从页面中消失:使用
page.waitForSelector(selector, { hidden: true })。 - 验证等待元素出现会超时:使用异步 rejection assertion,例如 Node.js 的
assert.rejects(),不要使用assert.throws()。
立即检查元素不存在
page.$() 会立即查询 DOM,找不到元素时返回 null:
const assert = require('node:assert/strict');
const selector =
'[data-test-text-title-import-payment-file-widget-console="true"]';
const element = await page.$(selector);
assert.strictEqual(
element,
null,
`Expected selector ${selector} not to exist`
);
在 Cucumber step 中,断言失败会直接抛出错误,当前步骤也会随之失败,无需手动返回 rejected Promise:
Then('the import payment widget is not present', async function () {
const selector =
'[data-test-text-title-import-payment-file-widget-console="true"]';
const element = await this.page.$(selector);
assert.strictEqual(element, null, `The widget is present: ${selector}`);
});
也可以直接判断查询结果:
const element = await page.$(selector);
if (element !== null) {
throw new Error(`The widget is present: ${selector}`);
}
等待元素消失
如果页面会异步更新,立即调用 page.$() 可能查得太早。这种情况下,可以等待元素被移除或隐藏:
await page.waitForSelector(selector, {
hidden: true,
timeout: 30000
});
元素在超时时间内消失或隐藏时,Promise 会正常完成。否则,它会抛出超时错误,Cucumber 会将该步骤判定为失败。
hidden: true 接受两种状态:
- 元素已经不在 DOM 中;
- 元素仍在 DOM 中,但不可见。
如果要求元素必须从 DOM 中删除,可以等待查询结果变为 null:
await page.waitForFunction(
selector => document.querySelector(selector) === null,
{ timeout: 30000 },
selector
);
为什么原来的 assert.throws() 调用不正确
下面这段代码会先执行 page.waitForSelector():
assert.throws(await page.waitForSelector(selector));
因为使用了 await,传给 assert.throws() 的不是函数,而是等待结束后的结果。与此同时,page.waitForSelector() 返回的是 Promise。它超时时产生的是 Promise rejection,不是同步函数直接抛出的异常。
assert.throws() 通常用于同步代码:
assert.throws(() => {
throw new Error('expected error');
});
异步 Promise 应使用 assert.rejects():
const assert = require('node:assert/strict');
await assert.rejects(
() => page.waitForSelector(selector, { timeout: 1000 }),
error => error?.name === 'TimeoutError'
);
这里无需添加 .then(callback)。断言成功时会正常完成,失败时则会抛出 AssertionError。
不过,这种写法验证的是“元素没有在指定时间内出现”,所以测试通常要等到超时,运行速度较慢。如果只是检查元素当前不存在,应优先使用 page.$();如果要验证元素最终消失,则使用 { hidden: true }。
关于更新代码中的 timeout
下面代码里的 timeout 不会让 page.$() 等待:
await page.$(selector, { timeout: settings._30000 });
page.$() 是即时查询方法,不使用这个超时选项。可以改成:
const element = await page.$(selector);
if (element === null) {
return;
}
throw new Error('The widget is present');
如果需要等待,则使用:
await page.waitForSelector(selector, {
hidden: true,
timeout: settings._30000
});
原始 selector 的 title- import 之间还多了一个空格:
'[data-test-text-title- import-payment-file-widget-console="true"]'
这可能导致 selector 无效,或者查到错误的目标。需要确认正确形式是否为:
'[data-test-text-title-import-payment-file-widget-console="true"]'
备注:内容仅供参考。
