question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow functions registered via page.exposeFunction to receive JSHandles

See original GitHub issue

When traversing DOM tree to find elements for testing UI in order to better share code i try to register a bunch of helper functions via page.exposeFunction. Currently this api does not allow to pass JSHandles between these functions (or any other non-serializable object), which is a tough limitation.

A small sample to reproduce what I’m trying to achieve:

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.exposeFunction('arrayLength', (array) => { return array.length; });
await page.goto('https://google.com');
await new Promise(res => setTimeout(res, 1000));
const numberOfDivs = await page.evaluate(
    async(selector) => await window.arrayLength(
        Array.from(document.querySelectorAll(selector))),
    'div');
console.log('number of divs:', numberOfDivs);
await browser.close();

It fails on some web pages (like google.com) and works on others (like example.com), what depends on whether there are circular references in DOM tree or not, which seems like not good behavior at all.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

4reactions
aslushnikovcommented, Jan 10, 2018

As there is no such opportunity right now…

@rjktcby I think this is achievable, please point me to where I’m wrong.

My intent is to develop code base of helper functions and not care a lot about what kind of objects they can or can’t receive as arguments. Some of them need to be executed inside browser page context - it would be handy to just expose them once and call whenever I need it.

You can define a set of helper functions in page context with a simple page.evaluate:

await page.evaluate(() => {
    window.niceUtils = {
        // finds distance to first traversed element that satisfies custom predicate
        distanceTo: function(element, predicate, traverser) {
            if (predicate(element)) {
            return 0;
            }
            traversedDistance = traverser(element, predicate);
            return traversedDistance != null ? traversedDistance + 1 : null;
        },

        // finds distance to first child that satisfies custom predicate
        distanceToChild: function (element, predicate) {
            return distanceTo(element, predicate, (element) => {
            const childDistances = Array.from(element.children)
                                        .map(el => distanceToChild(el, predicate))
                                        .filter(el => el != null);
            return childDistances.length > 0 ?
                Math.min.apply(null, childDistances) :
                null;
            });
        },

        // finds distance to first parent that satisfies custom predicate
        distanceToParent: function(element, predicate) {
            return distanceTo(element, predicate, (element) => {
            return element.parentElement != null ?
                distanceToParent(element.parentElement, predicate) :
                null;
            });
        }
    };
});

I want to be able to call them on different elements (usually queried by selector) and filter results as I need in each individual case.

You can call these functions like this:

page.$eval('.my-selector', (element, predicate) => niceUtils.distanceToChild(element, new Function(predicate)), predicate);

What’s wrong with this approach?

0reactions
trusktrcommented, Aug 6, 2018

I’m new here, but seems like it’d be neat to pass references between both sides. This would allow, for example, people to come up with alternatives to Electron, for example starting by doing page.exposeFunction('require', ...).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Puppeteer documentation
Puppeteer can respond to the dialog via Dialog's accept or dismiss methods. ... JSHandle instances can be passed as arguments to the page.evaluateHandle...
Read more >
Puppeteer find list of shadowed elements and get ...
As this answer shows, the only way to work with this JSHandle is to run it through another evaluate , at which point...
Read more >
API Reference — Pyppeteer 0.0.25 documentation
Pyppeteer allows creation of “incognito” browser context with browser. ... Registered function can be called from chrome process.
Read more >
playwright/api.md
Locale will affect `navigator.language` value, `Accept-Language` request header ... function used to register a routing with [browserContext.route(url, ...
Read more >
BrowserContext | Playwright 中文文档
open call, the popup will belong to the parent page's browser context. Playwright allows creation of "incognito" browser contexts with browser.newContext() ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found