Puppeteer in NodeJS reports '错误:节点不可见或不是HTMLElement'

h4cxqtbf  于 2023-05-22  发布在  Node.js
关注(0)|答案(5)|浏览(181)

我正在使用NodeJS的“puppeteer”来测试一个特定的网站。在大多数情况下,它似乎工作正常,但它报告了一些地方:
错误:节点不可见或不是HTMLElement
下面的代码选取了一个在两种情况下都不在屏幕上的链接。
第一个链接工作正常,而第二个链接出现故障。
有什么区别吗?两个链接都不在屏幕上。
感谢任何帮助,干杯,:)

示例代码

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事件。

7y4bm7vi

7y4bm7vi1#

而不是

await button.click();

这样做:

await button.evaluate(b => b.click());

这告诉浏览器在给定元素上运行JavaScript HTMLElement.click()方法,即使该元素被隐藏,屏幕外或被其他元素覆盖,也会在该元素上触发click event,而button.click()使用Puppeteer的ElementHandle.click(),它可以在该元素上运行HTMLElement.click()
1.滚动页面直到元素在视图中
1.获取元素的边界框(这一步是错误发生的地方),并计算该框中间的屏幕 xy 像素坐标
1.将虚拟鼠标移动到这些坐标,并将鼠标设置为“向下”,然后返回到“向上”,这将触发鼠标下的元素上的单击事件

velaa5lx

velaa5lx2#

首先,传递给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];

重要的是要注意,您正在为您单击的第二个链接使用错误的选择器。
您的原始选择器试图选择仅对xsextra small 屏幕(移动的电话)可见的元素。
您需要收集指定视口可见的链接数组。
因此,您需要将第二个选择器更改为:

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();
})();
nimxete2

nimxete23#

对于那些仍然有麻烦的人来说,这对我来说很有效:
await page.evaluate(()=>document.querySelector('#sign-in-btn').click())
基本上只是以不同的方式获取元素,然后单击它。
我不得不这样做的原因是因为我试图点击通知窗口中的一个按钮,该按钮位于应用程序的其他部分之外(Chrome似乎认为它是不可见的,即使它不是)。

lymgl2op

lymgl2op4#

我知道我迟到了,但我发现了一个边缘案例,给了我很多悲伤,还有这个帖子,所以我想我会发布我的发现。
罪魁祸首:CSS

scroll-behavior: smooth

如果你有这个,你会有一个糟糕的时间。
解决方案:

await page.addStyleTag({ content: "{scroll-behavior: auto !important;}" });

希望这对你们中的一些人有帮助。

a6b3iqyw

a6b3iqyw5#

我的方式

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()
   ]);
}

相关问题