我正在使用NodeJS的"puppeteer"来测试特定的网站。 在大多数情况下,它似乎工作正常,但它报告的某些地方:
错误:节点不可见或不是 HTML 环境
以下代码选取一个链接,在这两种情况下,该链接都位于屏幕之外。
第一个链路工作正常,而第二个链路失败。
有什么区别? 两个链接都关闭了屏幕。
任何帮助表示赞赏, 干杯,:)
示例代码
const puppeteer = require('puppeteer');
const initialPage = 'https://website.com/path';
const selectors = [
'div[id$="-bVMpYP"] article a',
'div[id$="-KcazEUq"] article a'
];
(async () => {
let selector, handles, handle;
const width=1024, height=1600;
const browser = await puppeteer.launch({
headless: false,
defaultViewport: { width, height }
});
const page = await browser.newPage();
await page.setViewport({ width, height});
page.setUserAgent('UA-TEST');
// Load first page
let stat = await page.goto(initialPage, { waitUntil: 'domcontentloaded'});
// Click on selector 1 - works ok
selector = selectors[0];
await page.waitForSelector(selector);
handles = await page.$$(selector);
handle = handles[12]
console.log('Clicking on: ', await page.evaluate(el => el.href, handle));
await handle.click(); // OK
// Click that selector 2 - fails
selector = selectors[1];
await page.waitForSelector(selector);
handles = await page.$$(selector);
handle = handles[12]
console.log('Clicking on: ', await page.evaluate(el => el.href, handle));
await handle.click(); // Error: Node is either not visible or not an HTMLElement
})();
我正在尝试模拟真实用户点击网站的行为,这就是为什么我使用.click()
,而不是.goto()
,因为a
标签具有onclick
事件。
而不是
await button.click();
这样做:
await button.evaluate(b => b.click());
这告诉浏览器在给定元素上运行JavaScriptHTMLElement.click()
方法,该方法将在该元素上触发click
事件,即使它是隐藏的,屏幕外的或被不同的元素覆盖的,而button.click()
使用Puppeteer的ElementHandle.click()
- 滚动页面,直到元素在视图中
- 获取元素的边界框(此步骤是发生错误的位置(,并计算该框中间的屏幕X和Y像素坐标
- 将虚拟鼠标移动到这些坐标,并将鼠标设置为"向下",然后设置回"向上",这将触发鼠标下元素的单击事件
首先,你传递给puppeteer.launch()
的defaultViewport
对象没有键,只有值。
您需要将其更改为:
'defaultViewport' : { 'width' : width, 'height' : height }
您传递给page.setViewport()
的对象也是如此。
您需要将此行代码更改为:
await page.setViewport( { 'width' : width, 'height' : height } );
第三,函数page.setUserAgent()
返回一个promise
,所以你需要await
这个函数:
await page.setUserAgent( 'UA-TEST' );
此外,您忘记在handle = handles[12]
之后添加分号。
您应该将其更改为:
handle = handles[12];
此外,您不会在单击第一个链接后等待导航完成 (page.waitForNavigation()
(。
单击第一个链接后,您应该添加:
await page.waitForNavigation();
我注意到第二页有时会在导航时挂起,因此您可能会发现增加默认导航超时(page.setDefaultNavigationTimeout()
(很有用:
page.setDefaultNavigationTimeout( 90000 );
再一次,您忘记在handle = handles[12]
后添加分号,因此需要将其更改为:
handle = handles[12];
请务必注意,您正在单击的第二个链接使用了错误的选择器。
您的原始选择器正在尝试选择仅对超小屏幕(移动电话(xs
可见的元素。
您需要收集一组对指定的视口可见的链接。
因此,您需要将第二个选择器更改为:
div[id$="-KcazEUq"] article .dfo-widget-sm a
单击第二个链接后,您也应该等待导航完成:
await page.waitForNavigation();
最后,您可能还想在完成程序后关闭浏览器(browser.close()
(:
await browser.close();
注意:您可能还需要研究如何处理
unhandledRejection
错误。
这是最终的解决方案:
'use strict';
const puppeteer = require( 'puppeteer' );
const initialPage = 'https://statsregnskapet.dfo.no/departementer';
const selectors = [
'div[id$="-bVMpYP"] article a',
'div[id$="-KcazEUq"] article .dfo-widget-sm a'
];
( async () =>
{
let selector;
let handles;
let handle;
const width = 1024;
const height = 1600;
const browser = await puppeteer.launch(
{
'defaultViewport' : { 'width' : width, 'height' : height }
});
const page = await browser.newPage();
page.setDefaultNavigationTimeout( 90000 );
await page.setViewport( { 'width' : width, 'height' : height } );
await page.setUserAgent( 'UA-TEST' );
// Load first page
let stat = await page.goto( initialPage, { 'waitUntil' : 'domcontentloaded' } );
// Click on selector 1 - works ok
selector = selectors[0];
await page.waitForSelector( selector );
handles = await page.$$( selector );
handle = handles[12];
console.log( 'Clicking on: ', await page.evaluate( el => el.href, handle ) );
await handle.click(); // OK
await page.waitForNavigation();
// Click that selector 2 - fails
selector = selectors[1];
await page.waitForSelector( selector );
handles = await page.$$( selector );
handle = handles[12];
console.log( 'Clicking on: ', await page.evaluate( el => el.href, handle ) );
await handle.click();
await page.waitForNavigation();
await browser.close();
})();
对于仍然遇到麻烦的人,这对我有用:
await page.evaluate(()=>document.querySelector('#sign-in-btn').click())
基本上只是以不同的方式获取元素,然后单击它。
我必须这样做的原因是因为我试图单击位于应用程序其余部分之外的通知窗口中的按钮(Chrome 似乎认为它是不可见的,即使它不是(。
我知道我参加聚会迟到了,但我发现了一个让我很悲伤的边缘情况,这个线程,所以我想我会发布我的发现。
罪魁祸首: .CSS
scroll-behavior: smooth
如果你有这个,你会过得很糟糕。
解决方案:
await page.addStyleTag({ content: "{scroll-behavior: auto !important;}" });
希望这对你们中的一些人有所帮助。
我的方式
async function getVisibleHandle(selector, page) {
const elements = await page.$$(selector);
let hasVisibleElement = false,
visibleElement = '';
if (!elements.length) {
return [hasVisibleElement, visibleElement];
}
let i = 0;
for (let element of elements) {
const isVisibleHandle = await page.evaluateHandle((e) => {
const style = window.getComputedStyle(e);
return (style && style.display !== 'none' &&
style.visibility !== 'hidden' && style.opacity !== '0');
}, element);
var visible = await isVisibleHandle.jsonValue();
const box = await element.boxModel();
if (visible && box) {
hasVisibleElement = true;
visibleElement = elements[i];
break;
}
i++;
}
return [hasVisibleElement, visibleElement];
}
用法
let selector = "a[href='https://example.com/']";
let visibleHandle = await getVisibleHandle(selector, page);
if (visibleHandle[1]) {
await Promise.all([
visibleHandle[1].click(),
page.waitForNavigation()
]);
}