Playwright Worker 类详解:监控、求值与操作 Web Worker 的完整 API 指南
本文以 Playwright 官方 API 文档中的 Worker 类(docs/src/api/class-worker.md)为主线,完整讲解如何监听页面创建的 Web Worker、读取其 URL、在 Worker 上下文中执行 JS 表达式、等待其 close/console 事件,并结合仓库中的客户端/服务端源码与测试用例,说明 Worker 事件的分发链路、超时机制的取值来源,以及 Worker 网络活动归属等关键行为,帮助读者把 Worker 相关的自动化能力真正落地到测试脚本中。
Worker 类是什么
Worker 类(since v1.8)代表一个 Web Worker。在 Playwright 中的事件模型是:
- 当页面派生出一个 dedicated worker 时,
worker事件会在 Page 对象上发出(对应 Page.worker 事件); - 当 Worker 终止时,
close事件会在 Worker 对象自身上发出。
也就是说,创建信号在 Page 上,销毁信号在 Worker 上,二者配合即可完整追踪一个 Worker 的生命周期。
四种语言的完整用法(继承自官方文档示例):
page.on('worker', worker => {
console.log('Worker created: ' + worker.url());
worker.on('close', worker => console.log('Worker destroyed: ' + worker.url()));
});
console.log('Current workers:');
for (const worker of page.workers())
console.log(' ' + worker.url());
page.onWorker(worker -> {
System.out.println("Worker created: " + worker.url());
worker.onClose(worker1 -> System.out.println("Worker destroyed: " + worker1.url()));
});
System.out.println("Current workers:");
for (Worker worker : page.workers())
System.out.println(" " + worker.url());
def handle_worker(worker):
print("worker created: " + worker.url)
worker.on("close", lambda: print("worker destroyed: " + worker.url))
page.on('worker', handle_worker)
print("current workers:")
for worker in page.workers:
print(" " + worker.url)
page.Worker += (_, worker) =>
{
Console.WriteLine($"Worker created: {worker.Url}");
worker.Close += (_, _) => Console.WriteLine($"Worker closed {worker.Url}");
};
Console.WriteLine("Current Workers:");
foreach(var pageWorker in page.Workers)
{
Console.WriteLine($"\tWorker: {pageWorker.Url}");
}
API 总览
| 成员 | 类型 | 语言支持 | 起始版本 | 说明 |
|---|---|---|---|---|
worker.close |
event | 全部 | v1.8 | 该 dedicated Web Worker 被终止时发出 |
worker.console |
event | 全部 | v1.57 | Worker 内 JS 调用 console.log/console.dir 等 API 时发出 |
worker.url() |
method | 全部 | v1.8 | 返回 Worker 的 URL |
worker.evaluate |
async method | 全部 | v1.8 | 在 Worker 上下文求值表达式/函数,返回可序列化值 |
worker.evaluateHandle |
async method | 全部 | v1.8 | 同上,但返回 JSHandle |
worker.waitForEvent |
async method | JS、Python(Python 别名 expect_event) |
v1.57 | 等待指定事件并返回事件数据 |
worker.waitForClose |
async method | Java | v1.10 | 执行动作并等待 Worker 关闭 |
worker.waitForConsoleMessage |
async method | Java | v1.57 | 执行动作并等待一条 console 消息 |
url() 与 page.workers()
worker.url()(since v1.8)直接返回创建该 Worker 时的 URL。注意用 new Worker(URL.createObjectURL(new Blob(...))) 内联创建的 Worker,其 URL 是一个 blob: 地址,这一点在测试断言时要留意(仓库测试 tests/page/workers.spec.ts 中就用 expect(page.url()).not.toContain('blob') 防止把 Worker 的 blob URL 误当成页面 URL)。
page.workers() 返回当前存活的全部 Worker。从客户端源码 packages/playwright-core/src/client/page.ts 可以看到,Page 内部维护一个 _workers 集合,_onWorker 在收到新 Worker 时将其加入集合并触发 worker 事件;而 Worker 对象在 close 时会将自身从该集合移除(packages/playwright-core/src/client/worker.ts),因此 page.workers() 始终是"当前存活"视图。这一点被测试 should clear upon navigation 验证:页面导航后 Worker 全部销毁,page.workers().length 变为 0。
evaluate 与 evaluateHandle:在 Worker 上下文中执行 JS
worker.evaluate(since v1.8)返回表达式的求值结果,参数说明(对应 docs/src/api/params.md 中的参数片段):
expression:要在 Worker 上下文求值的 JavaScript 表达式;若表达式求值结果是一个函数,该函数会被自动调用。JS 版本也可以直接传函数(pageFunction为 function 或 string);arg(可选):传给expression的参数,可为EvaluationArgument类型(可序列化的值或 JSHandle)。
语义要点:
- 若传入的函数返回 Promise,
evaluate会等待 Promise resolve 后返回其值; - 若返回值不可序列化(非
Serializable),evaluate返回undefined; - Playwright 额外支持转移
JSON无法序列化的少数值:-0、NaN、Infinity、-Infinity。
worker.evaluateHandle(since v1.8)与 evaluate 的唯一区别是返回 JSHandle 而非序列化值,参数与 Promise 等待语义相同。
测试用例给出了典型用法(tests/page/workers.spec.ts):
it('Page.workers @smoke', async function({ page, server, browserName, browserMajorVersion }) {
await Promise.all([
page.waitForEvent('worker'),
page.goto(server.PREFIX + '/worker/worker.html')]);
const worker = page.workers()[0];
expect(worker.url()).toContain('worker.js');
expect(await worker.evaluate(() => self['workerFunction']())).toBe('worker function result');
await page.goto(server.EMPTY_PAGE);
expect(page.workers().length).toBe(0);
});
源码视角:求值如何落到 Worker 执行上下文
客户端 Worker.evaluate 把表达式打包成 channel 请求发给服务端(packages/playwright-core/src/client/worker.ts):
async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }, kNoTimeout);
return parseResult(result.value);
}
服务端的 Worker 类(packages/playwright-core/src/server/page.ts)为每个 Worker 维护一个 ExecutionContext(createExecutionContext 创建,kind 为 'worker')。evaluateExpression 与 evaluateExpressionHandle 都等待 _executionContextPromise 解析后,调用 js.evaluateExpression 完成求值,前者 returnByValue: true,后者 returnByValue: false:
async evaluateExpression(progress: Progress, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
return progress.race(js.evaluateExpression(await this._executionContextPromise, expression, { returnByValue: true, isFunction }, arg));
}
值得注意的是 workerScriptLoaded() 与 destroyExecutionContext 的配合:当 Worker 的脚本尚未加载完成,执行上下文处于"未就绪"状态,_executionContextPromise 保持 pending;脚本加载完成或上下文重建后才会 resolve。从测试中多处 it.skip(browserName === 'chromium' && browserMajorVersion < 143, 'needs workerScriptLoaded event') 可以推断,较新版本的 Chromium 引入了 workerScriptLoaded 事件来保证"Worker 已创建但脚本还没执行完"时对 worker.evaluate 的调用能够安全排队,而不是立即失败。
事件:close 与 console
event: Worker.close(since v1.8)
当该 dedicated Web Worker 被终止时发出,参数为 Worker 本身。客户端构造函数中可以看到,服务端 channel 的 close 消息会先清理归属关系(web worker 从 page._workers 移除、service worker 从 browserContext._serviceWorkers 移除),再对外发出 close 事件,并关闭内部 LongStandingScope(packages/playwright-core/src/client/worker.ts)。
event: Worker.console(since v1.57)
当 Worker 内的 JS 调用 console API(如 console.log、console.dir)时发出,参数为 ConsoleMessage。从测试可以确认一个重要行为:同一条 Worker console 消息会同时出现在 Worker、Page、BrowserContext 三个对象上,且是同一个消息对象(tests/page/workers.spec.ts):
const [message1, message2, message3] = await Promise.all([
worker.waitForEvent('console'),
page.waitForEvent('console'),
page.context().waitForEvent('console'),
worker.evaluate(() => { console.log('hello from worker'); }),
]);
expect(message1.text()).toBe('hello from worker');
expect(message1).toBe(message2);
expect(message1).toBe(message3);
同时测试 should not report console logs from workers twice 验证了消息不会被重复上报。另外,Worker 内 throw 抛出的未捕获错误会以 pageerror 事件的形式上报到 Page(见 should report errors 测试)。
从服务端分发链看,WorkerDispatcher 监听 Worker.Events.Console,只有当客户端订阅了 console 事件(this._subscriptions.has('console'))时才把消息序列化为 console 事件下发,并携带 type、text、args(JSHandle 形式)与 location、timestamp 字段。客户端侧则在构造 Worker 时注册了 console 事件的订阅映射(packages/playwright-core/src/client/worker.ts):
this._setEventToSubscriptionMapping(new Map<string, channels.WorkerUpdateSubscriptionParams['event']>([
[Events.Worker.Console, 'console'],
]));
源码注释还说明:通过 chromium._connectToWorker(service worker 场景)接入的 Worker,其 console 事件只在 Worker 对象上收到。
waitForEvent:等待 Worker 事件(JS / Python)
worker.waitForEvent(since v1.57,langs: js, python,Python 别名为 expect_event)等待事件触发并把事件数据传给谓词函数,当谓词返回真值时返回事件数据;若在事件触发前页面关闭,会抛出错误。参数与选项(参数语义见 docs/src/api/params.md):
event:事件名,与传给worker.on(event)的名称相同(对 Worker 即'console'/'close');optionsOrPredicate(JS 可选):谓词函数或选项对象predicate:接收事件数据,返回真值时结束等待;timeout:最长等待毫秒数。JS 版本默认0(不超时),默认值可通过配置中的actionTimeout选项,或BrowserContext.setDefaultTimeout/Page.setDefaultTimeout方法修改;signal(JS,since v1.62):AbortSignal,可用于取消等待。提供signal不会禁用默认超时,要彻底关闭超时需传timeout: 0。
C#/Java/Python 侧对应的 timeout 默认值为 30000(30 秒),传 0 可禁用。
官方文档示例:
// Start waiting for download before clicking. Note no await.
const consolePromise = worker.waitForEvent('console');
await worker.evaluate('console.log(42)');
const consoleMessage = await consolePromise;
async with worker.expect_event("console") as event_info:
await worker.evaluate("console.log(42)")
message = await event_info.value
with worker.expect_event("console") as event_info:
worker.evaluate("console.log(42)")
message = event_info.value
注意这个模式的关键点:先启动等待、再触发行为,否则会漏掉在 await 之前就已发出的事件。
源码视角:waitForEvent 的超时与取消
客户端实现(packages/playwright-core/src/client/worker.ts)展示了三个值得了解的细节:
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
return await this._wrapApiCall(async () => {
const timeoutSettings = this._page?._timeoutSettings ?? this._context?._timeoutSettings ?? new TimeoutSettings();
const timeoutOptions = timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeoutOptions, `Timeout ${timeoutOptions.timeout}ms exceeded while waiting for event "${event}"`);
if (event !== Events.Worker.Close)
waiter.rejectOnEvent(this, Events.Worker.Close, () => this._closeErrorWithReason());
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
});
}
- 超时来源的继承链:Worker 自身不持有超时设置,而是依次取所属
Page的_timeoutSettings→ 所属BrowserContext的_timeoutSettings→ 全局默认。这解释了文档中"默认值可通过setDefaultTimeout修改"的机制; - Worker 关闭即失败:只要等待的不是
close事件本身,一旦 Worker 关闭,等待会以带关闭原因的TargetClosedError立即失败(_closeErrorWithReason),不会傻等到超时; - 等待过程由
Waiter封装:先挂超时拒绝、再挂 close 拒绝、最后按谓词匹配事件。
waitForClose 与 waitForConsoleMessage(Java)
Java 侧采用"执行动作 + 等待事件"的组合式 API,文档中的两个方法都是这个模式:
worker.waitForClose(since v1.10,langs: java):执行传入的callback(触发关闭的动作)并等待 Worker 关闭,返回Worker。选项:timeout(since v1.9):最长等待毫秒数,默认 30000,传 0 禁用,可用setDefaultTimeout修改;signal(since v1.9):java.util.concurrent的Abortable取消信号;callback(since v1.9):Runnable,执行触发事件的动作。
worker.waitForConsoleMessage(since v1.57,langs: java):执行callback并等待一条 console 消息,返回ConsoleMessage。选项:predicate(since v1.57):function(ConsoleMessage): boolean,接收消息对象,返回true时结束等待;timeout(since v1.57):默认 30000,传 0 禁用;signal(since v1.57):取消信号;callback(since v1.57):Runnable。
JS/Python 用户请用 waitForEvent('close') / waitForEvent('console')(Python 的 expect_event 上下文管理器)实现等价逻辑。
Worker 的网络行为:从测试用例看归属与拦截
Worker 类虽然只暴露 URL、求值与事件几个方法,但 Worker 发起的网络活动会完整接入 Page 的网络事件体系,这在 tests/page/workers.spec.ts 中有系统性验证:
- Worker 脚本本身是网络请求:
new Worker('/worker/worker.js')拉取脚本的request/requestfinished事件会被上报,且能拿到完整的请求/响应头(should report worker script as network request、should resolve worker script allHeaders in main frame/iframe等用例); - Worker 内的
fetch归属到创建它的页面/iframe:iframe 内 Worker 发起的请求,request.frame()指向该 iframe(should attribute network activity for worker inside iframe to the iframe); - Worker 流量可以被
page.route拦截:should report and intercept network from nested worker用例里,顶层 Worker 和"Worker 里再 new 出来的嵌套 Worker"发出的请求都被同一route.fulfill改写,两条 console 日志都输出被改写后的{"foo":"not bar"}——这说明 Playwright 会递归地识别嵌套 Worker; - 额外 HTTP 头与离线模式生效于 Worker:
page.setExtraHTTPHeaders设置的头会出现在 Worker 脚本请求与 Worker 内fetch请求上;browserContext.setOffline(true)后worker.evaluate(() => navigator.onLine)变为false。
这些测试意味着:为页面配置路由/断言时,默认就覆盖了其派生的 Worker 流量,无需在 Worker 对象上单独挂网络钩子。
生命周期细节:导航清理与销毁事件
测试 should emit created and destroyed events 展示了一个完整的内联 Worker 生命周期:页面 evaluate 创建 Worker → page 发出 worker 事件 → 调用 workerObj.terminate() → Worker 对象发出 close 事件,且 close 回调参数正是同一个 Worker 对象。Worker 销毁后,原来持有的 JSHandle 会失效,getProperty 抛出包含目标已关闭信息(kTargetClosedErrorMessage)的错误——对 Worker 内对象持有引用做断言时需要注意这一点。
should clear upon navigation / should clear upon cross-process navigation 两个用例则验证了:同进程与跨进程导航都会销毁旧文档的 Worker,close 事件触发、page.workers() 清空。
小结与相关源码索引
围绕 Worker API 文档,本文覆盖的要点:
- 事件模型:
worker(Page 上)与close/console(Worker 上)的分工,四种语言的完整示例; - 求值能力:
evaluate/evaluateHandle的参数语义、Promise 等待、可序列化值限制与特殊值支持; - 等待能力:
waitForEvent(JS/Python)、waitForClose/waitForConsoleMessage(Java)的谓词、timeout、signal、callback参数与默认值差异(JS 默认0不超时,C#/Java/Python 默认 30000ms); - 源码链路:客户端 packages/playwright-core/src/client/worker.ts(事件订阅、close 清理、waitForEvent 超时继承)→ 服务端 packages/playwright-core/src/server/page.ts(Worker 执行上下文与求值)→ 分发器 packages/playwright-core/src/server/dispatchers/pageDispatcher.ts(基于订阅的 console 事件下发);
- 行为验证:tests/page/workers.spec.ts 中对事件、console 去重、嵌套 Worker 拦截、导航清理等的测试用例。
延伸阅读:Page API(worker 事件)、ConsoleMessage API、JSHandle API、BrowserContext API。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00