正文
很多时候我们需要绕过一些东西或者做预处理,需要判断一个元素是否存在,需要一个即使找不到元素也没用关系的处理方法。
官方提供了 locator.isVisible() 用来获取元素是否存在的。
但是!它会立即返回结果,不会智能等待。而且官方已经移除了 timeout 入参,所以就算传 timeout 也不会等待。
所以要么使用固定等待来使用这个方法
export async function sleep(delay: number) {
await new Promise((resolve) => setTimeout(resolve, delay))
}
export async function locatorExists(locator: Locator, delay = 3000): Promise<boolean> {
await sleep(delay)
const exists = await locator.isVisible()
return exists
}
但是这显示是很丑陋的,我看到了别人的解决方案可以智能等待。
ref: https://github.com/microsoft/playwright/issues/12224
export async function locatorExists(locator: Locator, timeout = 3000): Promise<boolean> {
const __locatorExists = async (__locator: Locator) =>
!((await __locator.waitFor({ timeout: timeout }).catch((e: Error) => e)) instanceof Error)
const exists = await __locatorExists(locator)
return exists
}
不过这个方法也有弊端,试想一下这个场景
- 你完成了最后一个任务
- 此时获取
去完成
按钮列表,接口可能花了200ms,弹出浮层可能花了 100ms,此时元素才从 1 -> 0 - 而你的代码是密集执行的,这时 Playwright 会脏读到元素,而返回 exists = true
因此当元素从有到无的时候,这个方法就会有问题,解决方案就是再提供一个 locatorNotExists 方法,内部 waitFor方法传入 {state: “detached”}
举一反三,还可以写一个获取元素数量的方法
这个方法已经失效了,waitFor() 被证实 locator 匹配多条内容时会抛出异常。
可以直接在断言时使用 expect.poll() ,例:await expect.poll(() => page.locator(‘.winner’).count()).toBe(3)
export async function locatorLength(locator: Locator, timeout = 1000): Promise<number> {
if (await locatorExists(locator, timeout)) {
return await locator.count()
}
return 0
}
使用例
test('Start the task', async ({ tasksDetailPage }) => {
if (await utils.locatorExists(tasksDetailPage.startBtnLocator)) {
await tasksDetailPage.cancel()
}
await tasksDetailPage.start()
await expect(page.getByText(/^Success$/)).toBeVisible()
})
test('Complete the task', async ({ tasksDetailPage }) => {
const beforeTasksCount = await utils.locatorLength(tasksDetailPage.taskIcons)
await tasksDetailPage.complete()
const afterTasksCount = await utils.locatorLength(tasksDetailPage.taskIcons)
expect(beforeTasksCount - 1).toEqual(studyPlanDetailPage)
})