2

Problem

I have a list of the same nodes that has the same handler or function attached to it.

I need to click each and every interactive node present in the DOM (this is the functionality or requirement).

I would like to skip the Node to be clicked if they are already clicked or if the Node has the same handler attached to it.

Please find below the screenshot for the reference. In which the event listener has a click event with the handler name attached to it. Can we get the name of the click handler or function?

I am using DOMDebugger.getEventListeners({ objectId: remoteObjectId }) but it will not returning the handler and originalHandler don't why.

enter image description here

Pradeep Gill
  • 318
  • 2
  • 9
  • Apparently it's a "framework" listener which is attached to `document` or `window` and then uses *event delegation* (based on element selector or whatever) to route to your actual listener. To get all listeners you can specify `depth` parameter. – wOxxOm Jul 23 '20 at 16:43

2 Answers2

2

There is a quirk mentioned here: you need objectGroup set to get the handler. Here are some ways to get the name:

const html = `
  <!doctype html>
  <html>
    <head>
      <meta charset='UTF-8'>
      <title>Test</title>
      <script>
        function main() {
          document.body.addEventListener('click', logClick);
        }

        function logClick() {
          console.log('click');
        }
      </script>
    </head>
    <body onload='main();'>Text.</body>
  </html>`;

const puppeteer = require('puppeteer');

(async function main() {
  try {
    const browser = await puppeteer.launch();
    const [page] = await browser.pages();

    await page.goto(`data:text/html,${html}`);

    const cdp = await page.target().createCDPSession();

    const nodeObject = (await cdp.send('Runtime.evaluate', {
      expression: "document.querySelector('body')",
      objectGroup: 'foobar',
    })).result;

    const listenerObject = (await cdp.send('DOMDebugger.getEventListeners', {
      objectId: nodeObject.objectId,
    })).listeners[0].handler;

    const listenerName1 = (await cdp.send('Runtime.callFunctionOn', {
      functionDeclaration: 'function() { return this.name; }',
      objectId: listenerObject.objectId,
      returnByValue: true,
    })).result.value;

    const listenerName2 = (await cdp.send('Runtime.getProperties', {
      objectId: listenerObject.objectId,
      ownProperties: true,
    })).result.find(property => property.name === 'name').value.value;

    await cdp.send('Runtime.releaseObject', { objectId: listenerObject.objectId });
    await cdp.send('Runtime.releaseObject', { objectId: nodeObject.objectId });
    await cdp.send('Runtime.releaseObjectGroup', { objectGroup: 'foobar' });

    console.log(listenerName1);
    console.log(listenerName2);

    await browser.close();
  } catch (err) {
    console.error(err);
  }
})();

Output:

logClick
logClick

UPD Anonymous handlers:

const puppeteer = require('puppeteer');

(async function main() {
  try {
    const browser = await puppeteer.launch();
    const [page] = await browser.pages();

    await page.goto('https://jqueryui.com/resources/demos/datepicker/inline.html');

    const cdp = await page.target().createCDPSession();

    const nodeObject = (await cdp.send('Runtime.evaluate', {
      expression: "document.querySelector('a.ui-datepicker-next')",
      objectGroup: 'foobar',
    })).result;

    const listenerObject = (await cdp.send('DOMDebugger.getEventListeners', {
      objectId: nodeObject.objectId,
    })).listeners[0].handler;

    console.log(listenerObject);

    await cdp.send('Runtime.releaseObject', { objectId: listenerObject.objectId });
    await cdp.send('Runtime.releaseObject', { objectId: nodeObject.objectId });
    await cdp.send('Runtime.releaseObjectGroup', { objectGroup: 'foobar' });

    await browser.close();
  } catch (err) {
    console.error(err);
  }
})();

output:

{
  type: 'function',
  className: 'Function',
  description: 'function( e ) {\n' +
    '\n' +
    '\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n' +
    '\t\t\t\t// when an event is called after a page has unloaded\n' +
    '\t\t\t\treturn typeof jQuery !== "undefined" &&\n' +
    '\t\t\t\t\t( !e || jQuery.event.triggered !== e.type ) ?\n' +
    '\t\t\t\t\tjQuery.event.dispatch.apply( eventHandle.elem, arguments ) :\n' +
    '\t\t\t\t\tundefined;\n' +
    '\t\t\t}',
  objectId: '{"injectedScriptId":3,"id":2}'
}
vsemozhebuty
  • 12,992
  • 1
  • 26
  • 26
  • 1
    Thank you @vsemozhebuty **objectGroup** is the most important key. – Pradeep Gill Jul 27 '20 at 12:21
  • The above code does not work for the https://jqueryui.com/resources/demos/datepicker/inline.html. Here i am tring to get expression: "document.querySelector('a.ui-datepicker-next')", next and previous buttons of the calendar. Does above code only work for SPA? – Pradeep Gill Jul 30 '20 at 12:24
  • @PradeepGill Maybe there are some anonymous functions among the handlers. See an example with your code at the end of the answer. – vsemozhebuty Jul 30 '20 at 16:58
0

if the elemenets are statics and not dynamic in your website I advise you a solution. because this elements have a common selector you can you use this.page.$$ and get the array of all them. After that create a object with {button: elementHandle, isClicked: } and this way you could know if the node is clicked or not. I wish that this solution will help you.

page.$$ - https://pptr.dev/#?product=Puppeteer&version=v5.2.1&show=api-pageselector-1

remark: if to selector have more event or something changed you could know if it clicked. for example the color.