I would like to add custom methods to the puppeteer.Page
object, so I could invoke them like so:
let page = await browser.newPage();
page.myNewCustomMethod();
Here is one out of many custom methods I have created. It finds first available element by the XPath expression, using the array of expressions:
const findAnyByXPath = async function (page: puppeteer.Page, expressions: string[]) {
for (const exp of expressions) {
const elements = await page.$x(exp);
if (elements.length) {
return elements[0];
}
}
return null;
}
I have to invoke it like so...
let element = await findAnyByXPath(page, arrayOfExpressions);
To me, that looks weird in the editor, especially in a region where many custom methods are being invoked. It looks to me, a bit of "out of context". So I would rather invoke it like that:
page.findAnyByXPath(arrayOfExpressions);
I'm aware that there is a page.exposeFunction
method, but it is not what I'm looking for.
What is a way to achieve this?
Can you do this? Yes.
You can extend any object in JavaScript by modifying its prototype. In order to add a function to a Page
object, you can access the prototype of a Page
object by using the __proto__
property.
Here is a simple example adding the function customMethod
to all Page objects:
const page = await browser.newPage();
page.__proto__.customMethod = async function () {
// ...
return 123;
}
console.log(await page.customMethod()); // 123
const anotherPage = await browser.newPage();
console.log(await anotherPage.customMethod()); // 123
Note, that you need a Page
object first, to access the prototype as the constructor function (or class) is not itself exposed.
Should you do this? No.
You probably already noticed the red warnings on the linked MDN docs above. Read them carefully. In general, it is not recommended to change the prototype of objects you are using and haven't created yourself. Someone has created the prototype and he did not expect anyone to tinker around with it. For further information check out this stackoverflow question:
How to do it instead?
Instead, you should just use your own functions. There is nothing wrong with having your own functions and call them with page
as argument like this:
// simple function
findAnyByXPath(page);
// your own "namespace" with more functionality
myLibrary.findAnyByXPath(page);
myLibrary.anotherCustomFunction(page);
Normally, you could also extend the class Page
, but in this case the library is not exporting the class
itself. Therefore, you can only create a wrapper class which executes the same functions inside but offers more functionality on top. But this would be a very sophisticated approach and is really worth the effort in this case.