向 puppeteer.Page 对象添加自定义方法
Adding a custom method to puppeteer.Page object
我想向 puppeteer.Page
对象添加自定义方法,这样我就可以像这样调用它们:
let page = await browser.newPage();
page.myNewCustomMethod();
这是我创建的众多自定义方法之一。它通过 XPath 表达式找到第一个可用元素,使用表达式数组:
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;
}
我必须像这样调用它...
let element = await findAnyByXPath(page, arrayOfExpressions);
对我来说,这在编辑器中看起来很奇怪,尤其是在调用许多自定义方法的区域中。在我看来,有点“断章取义”。所以我宁愿这样调用它:
page.findAnyByXPath(arrayOfExpressions);
我知道有一个 page.exposeFunction
方法,但这不是我要找的。
有什么方法可以做到这一点?
你能做到吗?是的
您可以通过修改其原型来扩展 JavaScript 中的任何对象。为了向 Page
对象添加函数,您可以使用 __proto__
属性.
访问 Page
对象的原型
这是一个简单的示例,将函数 customMethod
添加到所有 Page 对象:
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
请注意,您首先需要一个 Page
对象才能访问原型,因为构造函数(或 class)本身并未公开。
你应该这样做吗?编号
您可能已经注意到上面链接的 MDN 文档中的红色警告。仔细阅读它们。一般情况下,不建议更改您正在使用且未自己创建的对象的原型。有人已经创建了原型,他并不指望有人会对其进行修补。有关更多信息,请查看此 Whosebug 问题:
- "Why is extending native objects a bad practice?"
怎么做呢?
相反,您应该只使用自己的函数。拥有自己的函数并使用 page
作为参数调用它们没有错:
// simple function
findAnyByXPath(page);
// your own "namespace" with more functionality
myLibrary.findAnyByXPath(page);
myLibrary.anotherCustomFunction(page);
通常,您也可以扩展 class Page
,但在这种情况下,库不会导出 class
本身。因此,您只能创建一个包装器 class,它在内部执行相同的功能,但在顶部提供更多功能。但这将是一种非常复杂的方法,在这种情况下确实值得付出努力。
扩展@Thomas 的 ,如果您想覆盖 Page
的原始方法:
const extendPage = (page: Page) => {
const { goto: originalGoto } = page;
page.goto = function goto(url, options) {
console.log("Goto:", url);
// do your things
return originalGoto.apply(page, arguments);
};
return page;
};
const page = extendPage(await browser.newPage());
await page.goto("https://google.com"); // Goto: https://www.google.com
要在每次创建新 Page
时附加其他方法,您可以从 Browser
监听 targetcreated
事件并在回调中扩展页面:
const browser = await puppeteer.launch();
browser.on("targetcreated", async (target: Target) => {
if (target.type() === "page") {
const page = await target.page();
extendPage(page);
}
});
const page = await browser.newPage(); // extended page
如果要添加新方法并更新 Typescript 定义:
import { Page, PageEmittedEvents } from "puppeteer";
async function htmlOnly(this: Page) {
await this.setRequestInterception(true); // enable request interception
this.on(PageEmittedEvents.Request, (req) => {
if (req.resourceType() === 'document') return req.continue();
return req.abort();
});
}
declare module "puppeteer" {
interface Page {
htmlOnly: () => Promise<void>;
}
}
export const extendPage = (page: Page) => {
page.htmlOnly = htmlOnly;
return page;
};
browser.on("targetcreated", async (target: Target) => {
if (target.type() === "page") {
const page = await target.page();
extendPage(page);
}
});
const page = await browser.newPage();
await page.htmlOnly();
我想向 puppeteer.Page
对象添加自定义方法,这样我就可以像这样调用它们:
let page = await browser.newPage();
page.myNewCustomMethod();
这是我创建的众多自定义方法之一。它通过 XPath 表达式找到第一个可用元素,使用表达式数组:
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;
}
我必须像这样调用它...
let element = await findAnyByXPath(page, arrayOfExpressions);
对我来说,这在编辑器中看起来很奇怪,尤其是在调用许多自定义方法的区域中。在我看来,有点“断章取义”。所以我宁愿这样调用它:
page.findAnyByXPath(arrayOfExpressions);
我知道有一个 page.exposeFunction
方法,但这不是我要找的。
有什么方法可以做到这一点?
你能做到吗?是的
您可以通过修改其原型来扩展 JavaScript 中的任何对象。为了向 Page
对象添加函数,您可以使用 __proto__
属性.
Page
对象的原型
这是一个简单的示例,将函数 customMethod
添加到所有 Page 对象:
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
请注意,您首先需要一个 Page
对象才能访问原型,因为构造函数(或 class)本身并未公开。
你应该这样做吗?编号
您可能已经注意到上面链接的 MDN 文档中的红色警告。仔细阅读它们。一般情况下,不建议更改您正在使用且未自己创建的对象的原型。有人已经创建了原型,他并不指望有人会对其进行修补。有关更多信息,请查看此 Whosebug 问题:
- "Why is extending native objects a bad practice?"
怎么做呢?
相反,您应该只使用自己的函数。拥有自己的函数并使用 page
作为参数调用它们没有错:
// simple function
findAnyByXPath(page);
// your own "namespace" with more functionality
myLibrary.findAnyByXPath(page);
myLibrary.anotherCustomFunction(page);
通常,您也可以扩展 class Page
,但在这种情况下,库不会导出 class
本身。因此,您只能创建一个包装器 class,它在内部执行相同的功能,但在顶部提供更多功能。但这将是一种非常复杂的方法,在这种情况下确实值得付出努力。
扩展@Thomas 的 Page
的原始方法:
const extendPage = (page: Page) => {
const { goto: originalGoto } = page;
page.goto = function goto(url, options) {
console.log("Goto:", url);
// do your things
return originalGoto.apply(page, arguments);
};
return page;
};
const page = extendPage(await browser.newPage());
await page.goto("https://google.com"); // Goto: https://www.google.com
要在每次创建新 Page
时附加其他方法,您可以从 Browser
监听 targetcreated
事件并在回调中扩展页面:
const browser = await puppeteer.launch();
browser.on("targetcreated", async (target: Target) => {
if (target.type() === "page") {
const page = await target.page();
extendPage(page);
}
});
const page = await browser.newPage(); // extended page
如果要添加新方法并更新 Typescript 定义:
import { Page, PageEmittedEvents } from "puppeteer";
async function htmlOnly(this: Page) {
await this.setRequestInterception(true); // enable request interception
this.on(PageEmittedEvents.Request, (req) => {
if (req.resourceType() === 'document') return req.continue();
return req.abort();
});
}
declare module "puppeteer" {
interface Page {
htmlOnly: () => Promise<void>;
}
}
export const extendPage = (page: Page) => {
page.htmlOnly = htmlOnly;
return page;
};
browser.on("targetcreated", async (target: Target) => {
if (target.type() === "page") {
const page = await target.page();
extendPage(page);
}
});
const page = await browser.newPage();
await page.htmlOnly();