首页
/ Playwright Worker 类详解:监控、求值与操作 Web Worker 的完整 API 指南

Playwright Worker 类详解:监控、求值与操作 Web Worker 的完整 API 指南

2026-09-06 15:15:48作者:冯爽妲Honey

本文以 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)。

语义要点:

  1. 若传入的函数返回 Promise,evaluate 会等待 Promise resolve 后返回其值;
  2. 若返回值不可序列化(非 Serializable),evaluate 返回 undefined
  3. Playwright 额外支持转移 JSON 无法序列化的少数值:-0NaNInfinity-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 维护一个 ExecutionContextcreateExecutionContext 创建,kind 为 'worker')。evaluateExpressionevaluateExpressionHandle 都等待 _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 事件,并关闭内部 LongStandingScopepackages/playwright-core/src/client/worker.ts)。

event: Worker.console(since v1.57)

当 Worker 内的 JS 调用 console API(如 console.logconsole.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 事件下发,并携带 typetextargs(JSHandle 形式)与 locationtimestamp 字段。客户端侧则在构造 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;
  });
}
  1. 超时来源的继承链:Worker 自身不持有超时设置,而是依次取所属 Page_timeoutSettings → 所属 BrowserContext_timeoutSettings → 全局默认。这解释了文档中"默认值可通过 setDefaultTimeout 修改"的机制;
  2. Worker 关闭即失败:只要等待的不是 close 事件本身,一旦 Worker 关闭,等待会以带关闭原因的 TargetClosedError 立即失败(_closeErrorWithReason),不会傻等到超时;
  3. 等待过程由 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.concurrentAbortable 取消信号;
    • 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 中有系统性验证:

  1. Worker 脚本本身是网络请求new Worker('/worker/worker.js') 拉取脚本的 request/requestfinished 事件会被上报,且能拿到完整的请求/响应头(should report worker script as network requestshould resolve worker script allHeaders in main frame/iframe 等用例);
  2. Worker 内的 fetch 归属到创建它的页面/iframe:iframe 内 Worker 发起的请求,request.frame() 指向该 iframe(should attribute network activity for worker inside iframe to the iframe);
  3. Worker 流量可以被 page.route 拦截should report and intercept network from nested worker 用例里,顶层 Worker 和"Worker 里再 new 出来的嵌套 Worker"发出的请求都被同一 route.fulfill 改写,两条 console 日志都输出被改写后的 {"foo":"not bar"}——这说明 Playwright 会递归地识别嵌套 Worker;
  4. 额外 HTTP 头与离线模式生效于 Workerpage.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)的谓词、timeoutsignalcallback 参数与默认值差异(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

登录后查看全文
热门项目推荐
相关项目推荐