0

What could be the reason of me not being able to access the Proxy of the CustomPage class in my test file? I get this error... enter image description here

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);
    return new Proxy(customPage, {
      get: function (target, property) {
        return customPage[property] || browser[property] || page[property];
      },
    });
  }
  constructor(page) {
    this.page = page;
  }
  getContentsOf(selector) {
    return this.page.$eval(selector,el=>el.innerHTML);
  }
}

module.exports = CustomPage;

const Page = require("./helpers/page");

let page;

beforeEach( async () => {
    page = await Page.build();
    await page.goto('http://localhost:5002');
});
afterEach(async () => {
  await page.close();
});
describe("we started HomePage tests and...",() => {
  test("can see blog create form", async () => {
    const subscribeButton = await page.$eval(".subscribe-btn",el=>el.innerHTML);
    expect(subscribeButton).toEqual("გამოწერა");
  });
});

I don't know what exactly should I try to fix it.

2

1 Answer 1

0

The issue has to do with binding, as described in ES6 proxied class, access private property (Cannot read private member #hidden from an object whose class did not declare it). Proxy get should return a function that's bound to the instance you want to call it on:

const puppeteer = require("puppeteer"); // ^22.2.0

class CustomPage {
  static async build() {
    const browser = await puppeteer.launch({headless: false});
    const page = await browser.newPage();
    const customPage = new CustomPage(page);
    const targets = [customPage, browser, page];
    return new Proxy(customPage, {
      get: (target, property) => {
        for (const t of targets) {
          if (t[property] === undefined) {
            continue;
          }
          else if (typeof t[property] === "function") {
            return t[property].bind(t);
          }

          return t[property];
        }
      }
    });
  }

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

  getContentsOf(selector) {
    return this.page.$eval(selector, el => el.innerHTML);
  }
}

let page;
(async () => {
  page = await CustomPage.build();
  await page.setContent("<p>hello world</p>");
  console.log(await page.getContentsOf("p")); // => hello world
})()
  .catch(err => console.error(err))
  .finally(() => page?.close());

That said, red flags should be raised here--this is much too complicated and prone to confusing errors like the one you're already battling. Best case scenario, as a client of CustomPage, if I call a function, I have no idea which instance is receiving the call. page.close() becomes impossible to call because browser.close() is called instead. Even if the intended behavior occurs, the code will still be difficult to read.

If you want to write an abstraction on the browser and/or page, don't do it prematurely. Wait until you have a need for it. If you do, try for something like a page object model from Playwright. In a POM, you don't expose page or browser to the caller at all, enforcing all test actions occur on a higher level based on the specific set of actions and elements for a page. This ensures your tests are written at a consistent level of abstraction, as the caller is unable to access lower-level page and browser functions unless the POM has explicitly exposed them.

Not the answer you're looking for? Browse other questions tagged or ask your own question.