9

I'm having some issues when I use a Proxy with the puppeteer library.

This is the class definition

const puppeteer = require("puppeteer");

class CustomPage {
  static async build() {
    const browser = await puppeteer.launch({ headless: false });
    const page = await browser.newPage();
    const customPage = new CustomPage(page);
    const superPage = new Proxy(customPage, {
      get: function (target, property) {
        //pay attention to the order between browser and page because of an issue
        return customPage[property] || browser[property] || page[property];
      }
    });
    console.log("superPage in CustomPage class definition", superPage);
    return superPage;
  }
  constructor(page) {
    this.page = page;
  }
}

module.exports = CustomPage;

And this is the error that I get when I run it

 TypeError: Cannot read private member from an object whose class did not declare it

       7 | beforeEach(async () => {
       8 |   page = await Page.build();
    >  9 |   await page.goto("localhost:3000");
         |              ^
      10 | });

Could you please help me with that?

Pablo Alaniz
  • 163
  • 1
  • 8

3 Answers3

4

Looks like goto function calls some properties that declared as private like this #someProperty. Proxies and private properties cannot work together.

Here is the issue about that: https://github.com/tc39/proposal-class-fields/issues/106

In short, you have to abandon the idea of using one of them: proxies or private properties. In your case, since private properties are part of Puppeteer, you'll have to abandon proxy. I think it can be replaced with a wrapper.

  • Thanks for your answer. I will appreciate your help. The reason is that I am working on a node course, and the project works fine for the teacher. If you take a look at that, you can realize that Idea. The following link is the page definition: https://github.com/StephenGrider/AdvancedNodeComplete/blob/master/AdvancedNodeStarter/tests/helpers/page.js, and this is the Jest's test: https://github.com/StephenGrider/AdvancedNodeComplete/blob/master/AdvancedNodeStarter/tests/header.test.js – Pablo Alaniz Jun 30 '22 at 17:21
  • The teacher is using only one class to manage the operations inside the same. Opening, closing, going to a link, whatever you need, you can use the CustomPage to do all the stuffs. So the test doesn't know if you are using a browser, a page, etc... – Pablo Alaniz Jun 30 '22 at 17:25
  • 1
    I figure out what is wrong, I can see that your teacher uses very old version of puppeteer, specifically 1.0.0, you can see that in his package.json. You probably installed latest version of it. I checked how proxy works with your teachers puppeteer version and it works fine, no errors there, so just downgrade version of puppeteer to the one that your teacher uses. Probably .goto implementation is using private properties only in latest versions of puppeteer and did not used them in early versions :) – Ilham Khabibullin Jun 30 '22 at 22:58
  • Good point and makes sense. I will try and let you know as soon as possible before closing this issue. Keep in touch! – Pablo Alaniz Jul 01 '22 at 14:35
  • Effectively, you're right. Now everything is fine and I can make the tests perfectly. Thank you so much for your time and your help. I was very disgusted by this error. Regards. – Pablo Alaniz Jul 01 '22 at 18:01
3

If anyone would prefer to not have to abandon the proxy I came up with a way that works(presumably for most cases can't speak for all).

{
  get(target, prop, receiver) {
    return (params) => {
      return target[prop](params);
    }
  }
}

Maybe this is what he meant by "some wrapper"? Not sure. It's been working for me so far.

1

I'm using puppeteer v18.0.5 and this works for me:

const puppeteer = require("puppeteer");
const { sessionFactory } = require("../factories/sessionFactory");
const { userFactory } = require("../factories/userFactory");

class CustomPage {
  constructor(page) {
    this.page = page;
  }

  async login() {
    const user = await userFactory();
    const { session, sig } = sessionFactory(user);

    await this.page.setCookie({ name: "session", value: session });
    await this.page.setCookie({ name: "session.sig", value: sig });

    // Refresh page
    await this.page.goto("http://localhost:3000");

    // Wait until the element appears
    await this.page.waitForSelector('a[href="/auth/logout"]');
  }

  static async build() {
    const browser = await puppeteer.launch({
      headless: false,
    });
    const page = await browser.newPage();
    const customPage = new CustomPage(page);

    return new Proxy(customPage, {
      get: (target, property, receiver) => {
        if (target[property]) {
          return target[property];
        }

        let value = browser[property];
        if (value instanceof Function) {
          return function (...args) {
            return value.apply(this === receiver ? browser : this, args);
          };
        }

        value = page[property];
        if (value instanceof Function) {
          return function (...args) {
            return value.apply(this === receiver ? page : this, args);
          };
        }

        return value;
      },
    });
  }
}

module.exports = CustomPage;
  • 1
    This worked for me, but I had to add `args: ['--no-sandbox']` with puppeteer.launch and the following above let value = browser... : `let value = customPage[property]; if (value instanceof Function) {return function (...args) {return value.apply(this === receiver ? customPage : this, args);};}` – sudocity Mar 07 '23 at 00:29