问题总结:
我遇到一个问题,在 Playwright E2E 测试期间,附加到 Next.js 应用程序中按钮的
onClick
事件并不总是被触发。
假设:
出现此问题的原因似乎是,Playwright 在 React 完全水合页面之前与按钮进行交互,这意味着负责附加
onClick
处理程序的 JavaScript 尚未执行。
详情:
这是我的一项测试的示例:
test('Navigate from list to details', async ({ page }) => {
await page.goto('/list');
await expect(page.getByTestId('list-container')).toBeVisible();
await page.getByTestId('list-item').first().locator('button').click();
await expect(page.getByTestId('details-container')).toBeVisible();
});
当测试导航到
/list
时,list-container
是可见的,因为它是在服务器端渲染的,但按钮单击并不总是触发 onClick
事件。
我尝试过的:
page.waitForSelector()
等待特定元素,但由于该元素是服务器渲染的,因此并不能解决问题。page.waitForTimeout()
添加显式延迟,但这也是不鼓励的。问题:
让 Playwright 在与元素交互之前等待 React 完成水合作用的最佳方法是什么?
谢谢您的帮助!
如果可以单击按钮但不执行任何操作,则说明您的应用程序损坏了,而不是您的测试损坏了。您可能应该更改代码,以便按钮不显示或处于禁用状态,直到它有侦听器。现代 SPA 通常会使用加载框架或类似的框架来向用户表明它尚未准备好进行交互。这与SSR完全兼容。
现在,您可以重复单击该按钮,直到某些操作成功,但您实际上是在通过 hack 来阻止错误。而且许多按钮不会受到垃圾点击的影响,例如,如果第二次点击是第一次点击的撤消,或者点击不是幂等的。
这是一个在 4 秒内不起作用的按钮的玩具示例,以及通过轮询直到单击起作用来尽快成功单击该按钮的测试:
import {expect, test} from "@playwright/test"; // ^1.46.1
const html = `<!DOCTYPE html><html><body>
<button>click</button>
<script>
setTimeout(
() =>
document
.querySelector("button")
.addEventListener("click", event => {
event.target.textContent = "clicked";
}),
4_000
);
</script>
</body></html>`;
test("button with delayed listener changes content", async ({page}) => {
await page.setContent(html);
await expect(async () => {
await page.getByRole("button").click();
await expect(page.getByRole("button")).toHaveText("clicked");
}).toPass()
});
这是应用程序:
setTimeout(
() =>
document
.querySelector("button")
.addEventListener("click", event => {
event.target.textContent = "clicked";
}),
4_000
);
<button>click</button>
但这只是对您实际用例的猜测,需要进行调整。我需要查看您的应用程序的可重现示例,以提供 100% 有保证的工作解决方案。
顺便说一句,请注意,不鼓励使用测试 ID,就像
.first()
一样。 .getByRole()
是大多数选择的首选工具。简而言之,测试 ID 会用嘈杂的属性污染您的 UI 代码,需要命名,并且不一定可访问或与用户实际使用网站的方式无关。