我有一个类似数组的节点对象(它是一个旋转木马),它们的顺序是随每次页面刷新随机生成的,剧作家发现所有元素都是可见的,但其中一些是在视口之外(基于收到的错误)。我需要确保,当我试图点击它的时候,元素是在视口中,否则我得到一个错误,说元素是在外面。
如何确定如果一个随机选择的节点元素的数组类对象实际上是在视窗内?
不幸的是,在Puppeteer中还没有像isInterSectingViewport这样的方法。(像这样)
剧作家的作者在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;
});
我使用这个方法的情况:我想知道,当我点击某个元素后,我滚动到另一个元素。不幸的是,boundingBox方法在我的情况下没有帮助。
您可以将此功能添加到我的BasePage类/**
* @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方法的实现。使用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()
})
const 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);
除了cardinalX的答案。您可以使用page创建辅助函数。waitForFunction等待元素进入viewport
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);