跳转到内容
搜索文档

Playwright

最后更新 查看 MarkdownAgent 设置

Playwright 是 Microsoft 开发的开源包,可执行浏览器自动化任务;常用于编写前端测试、创建屏幕截图或爬取页面。

Workers 团队 fork 了 Playwright 的一个版本,并进行了修改以兼容 Cloudflare WorkersBrowser Run

我们的版本已开源,可在 Cloudflare 的 Playwright fork 中找到。npm 包可从 npmjs 安装为 @cloudflare/playwright

npm i -D @cloudflare/playwright

在 Worker 中使用 Playwright

在此示例中,你将在 Cloudflare Worker 中使用 todomvc 应用程序运行 Playwright 测试。

如果你想跳过步骤快速开始,请选择下方的 部署到 Cloudflare

部署到 Cloudflare

确保在 Wrangler 配置文件中配置了 browser 绑定

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "cloudflare-playwright-example",
	"main": "src/index.ts",
	"workers_dev": true,
	"compatibility_flags": ["nodejs_compat"],
	// Set this to today's date
	"compatibility_date": "2026-08-17",
	"upload_source_maps": true,
	"browser": {
		"binding": "MYBROWSER",
	},
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "cloudflare-playwright-example"
main = "src/index.ts"
workers_dev = true
compatibility_flags = [ "nodejs_compat" ]
# Set this to today's date
compatibility_date = "2026-08-17"
upload_source_maps = true

[browser]
binding = "MYBROWSER"

安装 npm 包:

npm i -D @cloudflare/playwright

下面介绍一些 Playwright 用法示例:

截取屏幕截图

使用浏览器自动化截取网页屏幕截图是常见用例。此脚本指示浏览器导航到 https://demo.playwright.dev/todomvc,创建一些条目,截取页面屏幕截图,并在响应中返回图像。

import { launch } from "@cloudflare/playwright";

export default {
	async fetch(request: Request, env: Env) {
		const browser = await launch(env.MYBROWSER);
		const page = await browser.newPage();

		await page.goto("https://demo.playwright.dev/todomvc");

		const TODO_ITEMS = [
			"buy some cheese",
			"feed the cat",
			"book a doctors appointment",
		];

		const newTodo = page.getByPlaceholder("What needs to be done?");
		for (const item of TODO_ITEMS) {
			await newTodo.fill(item);
			await newTodo.press("Enter");
		}

		const img = await page.screenshot();
		await browser.close();

		return new Response(img, {
			headers: {
				"Content-Type": "image/png",
			},
		});
	},
};

追踪(Trace)

Playwright trace 是工作流执行的详细日志,捕获用户点击和导航操作、页面屏幕截图以及生成的控制台消息等信息,用于调试。开发者可以获取 trace.zip 文件并在本地打开,或上传到 Playwright Trace Viewer——一个帮助你探索数据的 GUI 工具。

以下是生成 trace 文件的 Worker 示例:

import fs from "fs";
import { launch } from "@cloudflare/playwright";

export default {
	async fetch(request: Request, env: Env) {
		const browser = await launch(env.MYBROWSER);
		const page = await browser.newPage();

		// Start tracing before navigating to the page
		await page.context().tracing.start({ screenshots: true, snapshots: true });

		await page.goto("https://demo.playwright.dev/todomvc");

		const TODO_ITEMS = [
			"buy some cheese",
			"feed the cat",
			"book a doctors appointment",
		];

		const newTodo = page.getByPlaceholder("What needs to be done?");
		for (const item of TODO_ITEMS) {
			await newTodo.fill(item);
			await newTodo.press("Enter");
		}

		// Stop tracing and save the trace to a zip file
		await page.context().tracing.stop({ path: "trace.zip" });
		await browser.close();
		const file = await fs.promises.readFile("trace.zip");

		return new Response(file, {
			status: 200,
			headers: {
				"Content-Type": "application/zip",
			},
		});
	},
};

断言

Playwright 最常见的用例之一是软件测试。Playwright 在 API 中包含测试断言功能;详情请参阅 Playwright 文档中的 Assertions。以下示例展示 Worker 对 todomvc 演示页面执行 expect() 测试断言:

import { launch } from "@cloudflare/playwright";
import { expect } from "@cloudflare/playwright/test";

export default {
	async fetch(request: Request, env: Env) {
		const browser = await launch(env.MYBROWSER);
		const page = await browser.newPage();

		await page.goto("https://demo.playwright.dev/todomvc");

		const TODO_ITEMS = [
			"buy some cheese",
			"feed the cat",
			"book a doctors appointment",
		];

		const newTodo = page.getByPlaceholder("What needs to be done?");
		for (const item of TODO_ITEMS) {
			await newTodo.fill(item);
			await newTodo.press("Enter");
		}

		await expect(page.getByTestId("todo-title")).toHaveCount(TODO_ITEMS.length);

		await Promise.all(
			TODO_ITEMS.map((value, index) =>
				expect(page.getByTestId("todo-title").nth(index)).toHaveText(value),
			),
		);
	},
};

存储状态(Storage state)

Playwright 支持 storage state 以获取并持久化 cookies 和其他存储数据。在此示例中,你将使用 storage state 在 Workers KV 中持久化 cookies 和其他存储数据。

首先,确保你有一个 KV 命名空间。可以使用以下命令创建:

npx wrangler kv namespace create KV

然后,将 KV 命名空间添加到 Wrangler 配置文件:

{
	"name": "storage-state-examples",
	"main": "src/index.ts",
	"compatibility_flags": ["nodejs_compat"],
	// Set this to today's date
	"compatibility_date": "2026-08-17",
	"browser": {
		"binding": "MYBROWSER",
	},
	"kv_namespaces": [
		{
			"binding": "KV",
			"id": "<YOUR-KV-NAMESPACE-ID>",
		},
	],
}
name = "storage-state-examples"
main = "src/index.ts"
compatibility_flags = [ "nodejs_compat" ]
# Set this to today's date
compatibility_date = "2026-08-17"

[browser]
binding = "MYBROWSER"

[[kv_namespaces]]
binding = "KV"
id = "<YOUR-KV-NAMESPACE-ID>"

现在,你可以使用 storage state 在 KV 中持久化 cookies 和其他存储数据:

src/index.tsts
// gets persisted storage state from KV or undefined if it does not exist
const storageStateJson = await env.KV.get("storageState");
const storageState = storageStateJson
	? ((await JSON.parse(
			storageStateJson,
		)) as BrowserContextOptions["storageState"])
	: undefined;

await using browser = await launch(env.MYBROWSER);
// creates a new context with storage state persisted in KV
await using context = await browser.newContext({ storageState });

await using page = await context.newPage();

// do some actions on the page that may update client-side storage

// gets updated storage state: cookies, localStorage, and IndexedDB
const updatedStorageState = await context.storageState({ indexedDB: true });

// persists updated storage state in KV
await env.KV.put("storageState", JSON.stringify(updatedStorageState));

Keep Alive(保持连接)

如果用户省略 browser.close() 语句,浏览器实例将保持打开,可随时再次连接并复用,但默认情况下会在 1 分钟不活动后自动关闭。用户可以选择使用 keep_alive 选项(以毫秒为单位)将空闲时间延长至最多 10 分钟:

const browser = await playwright.launch(env.MYBROWSER, { keep_alive: 600000 });

使用上述配置,即使不活动,浏览器也会保持打开最多 10 分钟。

会话复用

提升 Browser Run Worker 性能的最佳方式是复用会话——在完成使用后保持浏览器打开,并在每次有新请求时连接到该会话。Playwright 处理 browser.close 的方式与 Puppeteer 不同。在 Playwright 中,如果浏览器是通过 connect 会话获取的,会话将断开连接。如果浏览器是通过 launch 会话获取的,会话将关闭。

import { env } from "cloudflare:workers";
import { acquire, connect } from "@cloudflare/playwright";

async function reuseSameSession() {
	// acquires a new session
	const { sessionId } = await acquire(env.BROWSER);

	for (let i = 0; i < 5; i++) {
		// connects to the session that was previously acquired
		const browser = await connect(env.BROWSER, sessionId);

		// ...

		// this will disconnect the browser from the session, but the session will be kept alive
		await browser.close();
	}
}

设置自定义 user agent

要在 Playwright 中指定自定义 user agent,在使用 browser.newContext() 创建新浏览器上下文时在选项中设置。此后从此上下文创建的所有页面都将使用新的 user agent。当目标网站根据 user agent 提供不同内容时很有用。

const context = await browser.newContext({
	userAgent:
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
});

使用 headful 模式进行本地调试(实验性)

使用 wrangler devvite dev 进行本地开发时,Chrome 默认以 headless 模式运行。要以可见(headful)模式启动 Chrome,请设置 X_BROWSER_HEADFUL 环境变量:

X_BROWSER_HEADFUL=true npx wrangler dev

或使用 Cloudflare Vite 插件

X_BROWSER_HEADFUL=true npx vite dev

这将打开浏览器窗口,以便实时观看 Playwright 自动化,更易于调试导航、元素选择和页面交互。

会话管理

为了便于浏览器会话管理,我们扩展了 Playwright API,添加了新方法:

列出打开的会话

playwright.sessions() 列出当前运行的会话。它将返回类似以下的输出:

[
	{
		"connectionId": "2a2246fa-e234-4dc1-8433-87e6cee80145",
		"connectionStartTime": 1711621704607,
		"sessionId": "478f4d7d-e943-40f6-a414-837d3736a1dc",
		"startTime": 1711621703708
	},
	{
		"sessionId": "565e05fb-4d2a-402b-869b-5b65b1381db7",
		"startTime": 1711621703808
	}
]

注意会话 478f4d7d-e943-40f6-a414-837d3736a1dc 有活动的 worker 连接(connectionId=2a2246fa-e234-4dc1-8433-87e6cee80145),而会话 565e05fb-4d2a-402b-869b-5b65b1381db7 是空闲的。连接处于活动状态时,其他 worker 无法连接到该会话。

列出最近的会话

playwright.history() 列出最近的会话,包括打开和已关闭的。它有助于了解当前用量。

[
	{
		"closeReason": 2,
		"closeReasonText": "BrowserIdle",
		"endTime": 1711621769485,
		"sessionId": "478f4d7d-e943-40f6-a414-837d3736a1dc",
		"startTime": 1711621703708
	},
	{
		"closeReason": 1,
		"closeReasonText": "NormalClosure",
		"endTime": 1711123501771,
		"sessionId": "2be00a21-9fb6-4bb2-9861-8cd48e40e771",
		"startTime": 1711123430918
	}
]

会话 2be00a21-9fb6-4bb2-9861-8cd48e40e771 由客户端显式调用 browser.close() 关闭,而会话 478f4d7d-e943-40f6-a414-837d3736a1dc 因达到最大空闲时间而关闭(查看限制)。

你也应该能够在仪表板中访问此信息,尽管可能略有延迟。

活动限制

playwright.limits() 列出你的活动限制:

{
	"activeSessions": [
		{ "id": "478f4d7d-e943-40f6-a414-837d3736a1dc" },
		{ "id": "565e05fb-4d2a-402b-869b-5b65b1381db7" }
	],
	"allowedBrowserAcquisitions": 1,
	"maxConcurrentSessions": 2,
	"timeUntilNextAllowedBrowserAcquisition": 0
}
  • activeSessions 列出当前打开会话的 ID
  • maxConcurrentSessions 定义可同时打开多少个浏览器
  • allowedBrowserAcquisitions 指定根据当前速率限制是否可以打开新的浏览器会话
  • timeUntilNextAllowedBrowserAcquisition 定义启动新浏览器前的等待时间

Playwright API

完整的 Playwright API 可在 Playwright API 文档 中找到。

以下功能尚未完全支持,但我们正在积极开发中:

不是详尽列表——随着我们努力与原始功能集实现更全面的对等支持,预计会有快速变化。你也可以查看最新测试结果,获取已完全支持功能的细粒度最新列表。

这篇文档对您有帮助吗?