ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Puppeteer 扩展隔离世界实操:用 page.extensionRealms() 驱动 Chrome 扩展内容脚本

Puppeteer 扩展隔离世界实操:用 page.extensionRealms() 驱动 Chrome 扩展内容脚本 Puppeteer 扩展隔离世界实操用 page.extensionRealms() 驱动 Chrome 扩展内容脚本【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer导读本文聚焦 PuppeteerJavaScript API for Chrome and Firefox中用于与Chrome 扩展内容脚本content scripts执行环境交互的Page.extensionRealms()方法它返回页面主 frame 上由扩展注入内容脚本所创建的扩展执行世界extension execution realm列表配合Realm上的evaluate()等方法可以让自动化测试代码真正运行在内容脚本的上下文中。读完本文你将掌握扩展隔离世界的底层模型、如何按扩展 ID 定位目标 realm以及如何在自己的 E2E 测试中注入并断言内容脚本的执行结果。什么是扩展执行世界Extension Execution RealmChrome 扩展的内容脚本默认运行在隔离世界isolated world中它可以访问 DOM但与页面主世界main world的 JavaScript 全局对象相互隔离二者不能直接共享变量。在 Puppeteer 的 Realm 抽象下这种由扩展内容脚本在页面 frame 中创建的隔离世界就被称为extension execution realm / extension realm。Page.extensionRealms() 的 API 文档这样描述其语义Retrieves the list of extension execution realms in the main frame of the page. These realms correspond to extension content scripts running on the page.也就是说该方法返回页面主 frame上所有正在运行的扩展内容脚本所对应的执行世界。注意两个关键限定返回的 realm 来自page.mainFrame()主 frame并不包含 iframe 中的内容脚本世界只要某扩展的内容脚本注入了该页面它的隔离世界就会出现在列表中无论脚本此刻是否正在执行任务。从方法签名看该方法在Page上被声明为抽象方法abstract语义上等价于对主 frame 调用extensionRealms()class Page { abstract extensionRealms(): Realm[]; }从 Page 到 FrameAPI 继承关系Page.extensionRealms()实际上是 Frame.extensionRealms() 的快捷方式shortcut。API 文档原文明确写到Shortcut formainFrame().extensionRealms().二者在 Puppeteer 公共 API 层均被抽象声明分别在 api/Page.ts 与 api/Frame.ts 中可以找到对应的public抽象方法// api/Frame.ts /** * Retrieves the list of extension execution realms associated with this frame. * Extension execution realms are created by extension content scripts injected * into the frame. */ abstract extensionRealms(): Realm[]; // api/Page.ts —— 快捷方式注释与抽象声明 /** * Retrieves the list of extension execution realms in the main frame of the page. * Shortcut for Frame.extensionRealms | mainFrame().extensionRealms(). */ abstract extensionRealms(): Realm[];而具体协议实现则由 CDP 与 WebDriver BiDi 两个后端的页面/frame 子类各自完成。例如 CDP 后端在 cdp/Frame.ts 中的实现非常直观——直接返回该 frame 内部维护的全部扩展世界对象override extensionRealms(): Realm[] { return Object.values(this.extensionWorlds); }从这段实现可以推断CDP 后端会为每个 frame 维护一张「扩展世界注册表」凡是进入该页面的扩展内容脚本世界都会登记其中extensionRealms()只是把它暴露出来。BiDi 后端同样在 bidi/Frame.ts 中提供了对应的重写实现BiDi 的 page 类路径见 bidi/Page.ts。返回值 Realm内容脚本世界的操作入口返回的每个元素都是 Realm 类的实例。它是对一个 JavaScript 执行环境world/context的抽象在扩展场景下通常对应扩展内容脚本所在的隔离世界。Realm 类提供以下与扩展调试相关的成员详见其 API 文档与 api/Realm.ts 源码成员类型/签名说明originstring \| undefined只读Experimental创建该 Realm 的来源。若是扩展内容脚本创建的世界通常返回形如chrome-extension://extension-id的扩展 originextension()PromiseExtension \| nullExperimental返回创建该 Realm 的 Extension 对象若 realm 由扩展注入产生通常会被填充evaluate(pageFunction, args)Promise...在 realm 的上下文中求值函数并返回结果支持 Promise 结果与 JSHandle 参数evaluateHandle(pageFunction, args)PromiseJSHandle同上但返回结果的 JSHandle 引用waitForFunction(pageFunction, options, args)PromiseJSHandle在 realm 上下文中轮询等待某函数返回真值[disposeSymbol]()—释放该 realm由于Page类本身也继承自Realm代表主世界执行环境Realm实际上是 Puppeteer 中Page、Frame与各类 worker 求值能力的公共基类。前置条件让扩展先运行起来extensionRealms()依赖扩展真正被加载并注入页面因此通常需要配合以下任一方式先启动扩展详见 Chrome Extensions 指南方式一启动时通过 launch 选项加载import puppeteer from puppeteer; import path from path; const pathToExtension path.join(process.cwd(), my-extension); const browser await puppeteer.launch({ enableExtensions: [pathToExtension], });方式二运行时动态安装const browser await puppeteer.launch({enableExtensions: true}); const extensionId await browser.installExtension(pathToExtension);无论哪种方式之后用browser.newPage()创建页面并page.goto()导航到会注入内容脚本的网址扩展内容脚本的隔离世界就会随之出现在page.extensionRealms()的结果中。实战定位扩展世界并在内容脚本上下文中求值为什么需要在内容脚本的上下文里求值因为页面主世界的window与扩展隔离世界并不共享变量——内容脚本写入window的全局数据、它修改的 DOM 属性乃至注入的元素都需要在那个隔离世界内才能真正读取到。page.extensionRealms()正是为这类断言而生的入口。Chrome Extensions 指南 给出了完整的推荐流程先用browser.installExtension()拿到扩展 ID再遍历page.extensionRealms()借助realm.extension()返回的 Extension 对象按id精确匹配目标扩展最后在选中的 realm 上调用evaluate()import puppeteer from puppeteer; import path from path; const pathToExtension path.join(process.cwd(), my-extension); const browser await puppeteer.launch({enableExtensions: [pathToExtension]}); // 1. 打开一个会触发内容脚本注入的页面 const page await browser.newPage(); await page.goto(https://example.com); // 2. 安装扩展并获取其 ID const extensionId await browser.installExtension(pathToExtension); // 3. 在主 frame 的扩展世界中找到属于该扩展的那一个 const realms page.extensionRealms(); let extensionRealm; for (const realm of realms) { const extension await realm.extension(); if (extension?.id extensionId) { extensionRealm realm; break; } } if (!extensionRealm) { throw new Error(Extension realm not found); } // 4. 在内容脚本上下文内执行代码 const result await extensionRealm.evaluate(() { return document.title; }); console.log(result); await browser.close();也可以利用Realm.origin进行更轻量的筛选——扩展世界通常带有形如chrome-extension://id的 originExperimentalconst target page .extensionRealms() .find(realm realm.origin?.includes(extensionId));为什么需要按 ID 匹配而不是直接取第一个同一页面可能同时注入多个扩展的内容脚本extensionRealms()返回的数组顺序并不代表任何优先级。若直接把数组第一项当作目标扩展的世界可能匹配到错误扩展甚至非扩展来源的世界。因此在选取 realm 前应先判断extension()返回对象或origin是否与目标扩展 ID 相符正如上文realm.extension()返回的 Extension 带有id、name、version、enabled、path等可核对属性。边界情况与常见报错找不到目标 realm抛Extension realm not found通常是内容脚本未匹配当前 URL、扩展未启用或注入时机早于installExtension()/导航完成。可先确认扩展的 manifest 中content_scripts.matches覆盖了所访问页面。主 frame 局限page.extensionRealms()只覆盖主 frame。若内容脚本运行在 iframe 中需改用frame.extensionRealms()配合page.frames()遍历子 frame 找到目标 frame来完成。注入时序extensionRealms()是同步快照式查询若内容脚本尚未注入完成列表可能为空必要时结合waitForFunction或重试逻辑等待。深度理解这套 API 在自动化中的定位page.extensionRealms()是 Puppeteer Chrome 扩展 E2E 测试体系中的一环与其配套的扩展能力还包括后台上下文访问通过browser.waitForTarget()等待service_workerMV3或background_pageMV2类型的 target再经target.worker()/target.page()操作扩展后台弹出页测试extension.triggerAction(page)或page.triggerExtensionAction(extension)模拟用户点击工具栏图标后等待 popup target扩展实例管理Extension 类下的pages()、workers()方法以及browser.extensions()/browser.uninstallExtension()。从源码结构看整套扩展能力均建立在统一抽象的Extension、Realm与 Page/Frame 之上并同时落地于 CDP 与 BiDi 两条协议链路这意味着无论在传统 CDP 还是 WebDriver BiDi 模式下编写扩展测试extensionRealms()的调用方式都保持一致。仓库中 test/src/cdp/extensions.test.ts 与 test/src/cdp/realms.test.ts 即为覆盖扩展加载、realm 枚举与求值链路的测试用例可作为编写自己测试的参考蓝本。小结page.extensionRealms()返回主 frame上扩展内容脚本的隔离世界列表是mainFrame().extensionRealms()的快捷方式二者在 api/Page.ts 与 api/Frame.ts 中均有抽象声明。返回的Realm提供evaluate()、evaluateHandle()、waitForFunction()、origin与extension()等成员是「在扩展上下文中执行断言」的关键入口。CDP 后端通过 frame 内部维护的extensionWorlds注册表返回全部扩展世界见 cdp/Frame.tsBiDi 后端亦提供对应实现。实战时结合browser.launch({enableExtensions})/browser.installExtension()与按扩展 ID 的 realm 匹配即可在内容脚本上下文内可靠地求值并断言结果。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表