我有一个类似数组的节点对象(它是一个旋转木马),每次页面刷新时它们的顺序都是随机生成的,playwright发现所有元素都可见,但其中一些在视口之外(基于收到的错误)。我需要确保尝试单击的元素在视口内,否则我会收到指示该元素在外部的错误。如何确定随机选择的节点元素是否实际上在视口内?
不幸的是,Playwright目前还没有像Puppeteer中的isInterSectingViewport方法。(类似这样)
因此,Playwright的作者可以在Slack社区中帮助我(您可以在官方网站上找到它)。
const result = await page.$eval(selector, async element => {
const visibleRatio: number = await new Promise(resolve => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
// Firefox doesn't call IntersectionObserver callback unless
// there are rafs.
requestAnimationFrame(() => {});
});
return visibleRatio > 0;
});
/**
* @returns {!Promise<boolean>}
*/
isIntersectingViewport(selector: string): Promise<boolean> {
return this.page.$eval(selector, async element => {
const visibleRatio: number = await new Promise(resolve => {
const observer = new IntersectionObserver(entries => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
// Firefox doesn't call IntersectionObserver callback unless
// there are rafs.
requestAnimationFrame(() => {});
});
return visibleRatio > 0;
});
}
顺便说一下,除了一行代码之外的所有代码都来自于GitHub Puppeteer中的isInterSectingViewport方法的实现。
(method) LocatorAssertions.toBeInViewport(options?: {
ratio?: number;
timeout?: number;
}): Promise<void>
Ensures the Locator points to an element that intersects viewport, according to the intersection observer API.
Usage
const locator = page.getByRole('button');
// Make sure at least some part of element intersects viewport.
await expect(locator).toBeInViewport();
// Make sure element is fully outside of viewport.
await expect(locator).not.toBeInViewport();
// Make sure that at least half of the element intersects viewport.
await expect(locator).toBeInViewport({ ratio: 0.5 });
使用 CSS 选择器检查元素是否在视口中:
import { test, expect, devices } from '@playwright/test'
const url = 'https://example.com'
const selector = 'h1'
test.use({
headless: false,
browserName: 'webkit',
...devices['iPhone 13 Mini'],
})
test('visibility', async ({ page }) => {
await page.goto(url)
const box = await page.locator(selector).boundingBox() // it contains x, y, width, and height only
let isVisible = await page.evaluate((selector) => {
let isVisible = false
let element = document.querySelector(selector)
if (element) {
let rect = element.getBoundingClientRect()
if (rect.top >= 0 && rect.left >= 0) {
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
if (rect.right <= vw && rect.bottom <= vh) {
isVisible = true
}
}
}
return isVisible
}, selector)
await expect(isVisible).toBeTruthy()
})
除了cardinalX 答案之外,您可以使用page.waitForFunction创建辅助函数来等待元素进入视口。
import { Page } from '@playwright/test';
export const waitToBeInViewport = async (
page: Page,
selector: string,
) => page.waitForFunction(async (selectorParam: string) => {
const element = document.querySelector(selectorParam);
const visibleRatio: number = await new Promise((resolve) => {
const observer = new IntersectionObserver((entries) => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(element);
requestAnimationFrame(() => { });
});
return visibleRatio > 0; // where 0 - element has just appeared, and 1 - element fully visible;
}, selector);
{"_guid": "handle@73d2b54286357906ed761446c1a5c78a", "_type": "JSHandle"}
。可能我没有正确使用它。有什么建议吗? - Joe Lloydconst firstId = "#someId";
// it happens that I am evaluating in a frame, rather than page
const result = await frame.evaluate((firstId) => {
// define a function that handles the issue
// returns true if element is within viewport, false otherwise
function isInViewport(el) {
// find element on page
const element = document.querySelector(el);
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <=
(window.innerWidth || document.documentElement.clientWidth)
);
};
return isInViewport(firstId);
}, firstId);
// back to node context
console.log(result);
evaluate(callback)
内部的所有内容都很难调试!因为回调函数在浏览器范围内运行。
另一种方法是利用locator.boundingBox()
和page.viewportSize()
来实现我们自己的expect(locator).toBeInViewport()
,但遗憾的是它只返回void,意味着我们无法获得布尔值并根据该值执行连续动作。(也许可以滚动来模拟人类行为)
这种方式非常简单:
const box = await mylocator.boundingBox()
const view = page.viewportSize() // note: doesn't need await
const isMyLocatorInViewport = box.y + box.height - view.height < 0 && box.y > 0