# @drupal/playwright 中文文档

规范地址：https://npmcn.com/package/%40drupal/playwright/

版本：2\.0\.0
npm 元数据更新时间：2026-11-06T10:34:33.637Z
中文译文更新时间：2026-11-12T20:51:37.090Z
软件包许可：GPL-2\.0\+

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40drupal/playwright
原文对照：https://npmcn.com/package/%40drupal/playwright/?lang=original
源码仓库：https://git.drupalcode.org/project/playwright

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：149 kB，38 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.16\.0
- 运行依赖：1 个
- 首次发布：2025-08-27T14:52:07.781Z
- 最新版本发布：2026-11-06T10:34:33.467Z
- 近 30 天下载量：14387（2026-10-13 至 2026-11-11，npm 官方下载统计）
- 已发布版本数：8（近 12 个月 5 个）

## 中文简介

用 Playwright 测试 Drupal 站点的固定和命令

## 安装

```sh
npm install @drupal/playwright
```

也可以使用：`pnpm add @drupal/playwright`、`yarn add @drupal/playwright`、`bun add @drupal/playwright`

## README

# 剧作家

此工程提供定着和帮助对象用于写入 [剧作家](https://playwright.dev/) 测试与 Drupal 网站交互.

## 所需资源

-   节点.js 22.16.0左右.

## 固定设备

为安装Drupal并随后工作提供了两个固定装置 带着它 用于处理 drupal 对象的文档是 [这里](https://npmcn.com/package/%40drupal/playwright/docs/classes/Drupal.md#methods)。 。 。 .

必须指出的是,默认测试可以运行在任何可用的 工人,这意味着它可能运行在 Drupal 安装之前的一套 测试已经开始 这可以结合测试来改变 配置和您使用的固定.

### 完全平行

都摆好了 `fullyParallel: true` 内 `playwright.config.ts`,或 `test.describe.configure({ mode: 'parallel' });` 在您的光谱文件中.

在描述区块内平行运行的测试。 这样可以更快地执行 因为测试不等待对方, 但要求测试是独立的 没有共享状态,因为任何测试 都可能最终在任何工人 任何顺序。 这意味着 使用这个固定装置进行的测试 决不能对它造成任何破坏 网站。 但是,只要清理完之后,它们就可以进行改建 如果测试失败, Playwright 将丢弃整个工人 程序并开始新的程序)。 这也适用于任何在钩子上运行的东西 例如 `beforeAll` - 它可能最终运行 在多个工人,这是 变化保持不变.

```mermaid
flowchart TD
 subgraph W1["Worker 1"]
  W1si["Site install"]
  W1g1["group1 beforeAll"]
  W1g1t1["group1 test1"]
  W1g2["group2 beforeAll"]
  W1g2t2["group2 test3 - FAIL, end worker
  flaky test example
  (retries: 1)"]
 end
 subgraph W2["Worker 2"]
  W2si["Site install"]
  W2g1["group1 beforeAll"]
  W2g1t2["group1 test2"]
  W2g2["group2 beforeAll"]
  W2g2t2["group2 test2"]
  W2g3["group3 beforeAll"]
  W2g3t2["group3 test2"]
  W2g4["group4 beforeAll"]
  W2g4t1["group4 test1"]
  W2g42["group4 beforeAll"]
  W2g4t2["group4 test2"]
  W2g4t3["group4 test3"]
 end
 subgraph W3["Worker 3"]
  W3si["Site install"]
  W3g1["group1 beforeAll"]
  W3g1t3["group1 test3 - FAIL, end worker
  (retries: 0)"]
 end
 subgraph W4["Worker 4"]
  W4si["Site install"]
  W4g2["group2 beforeAll"]
  W4g2t1["group2 test1"]
  W4g3["group3 beforeAll"]
  W4g3t1["group3 test1"]
  W4g32["group3 beforeAll"]
  W4g3t3["group3 test3"]
 end
 subgraph W5["Worker 5"]
    W5si["Site install"]
    W5g4["group4 beforeAll"]
    W5g4t3["group4 test3"]
 end
 subgraph W6["Worker 6"]
  W6si["Site install"]
  W6g2["group2 beforeAll"]
  W6g2t3["group2 test3"]
 end

W1si --> W1g1
W1g1 --> W1g1t1
W1g1t1 --> W1g2
W1g2 --> W1g2t2

 W2si --> W2g1
 W2g1 --> W2g1t2
 W2g1t2 --> W2g2
 W2g2 --> W2g2t2
 W2g2t2 --> W2g3
 W2g3 --> W2g3t2
 W2g3t2 --> W2g4
 W2g4 --> W2g4t1
 W2g4t1 --> W2g42
 W2g42 --> W2g4t2
 W2g4t2 --> W2g4t3

W3si --> W3g1
W3g1 --> W3g1t3

W4si --> W4g2
W4g2 --> W4g2t1
W4g2t1 --> W4g3
W4g3 --> W4g3t1
W4g3t1 --> W4g32
W4g32 --> W4g3t3

W5si --> W5g4
W5g4 --> W5g4t3

W6si --> W6g2
W6g2 --> W6g2t3

W3 --> W5
W3g1t3 -.-> W5si
W1 --> W6
W1g2t2 -.-> W6si

 A["fullyParallel: true"] --> W1si
 A --> W2si
 A --> W3si
 A --> W4si
```

```typescript
import { expect } from '@playwright/test';
import { parallelWorker as test } from '@drupal/playwright';

test.describe('Drupal', () => {
  test('Can create a role, add permissions, and create a user', async ({
    drupal,
    page,
  }) => {
    await drupal.loginAsAdmin();
    await drupal.createRole({ name: 'content editor' });
    await drupal.addPermissions({
      role: 'content editor',
      permissions: ['access content overview', 'administer blocks'],
    });
    await drupal.createUser({
      username: 'catbro',
      password: 'meow',
      email: 'catbro@example.com',
      roles: ['content editor'],
    });
    await drupal.logout();
    await drupal.login({ username: 'catbro', password: 'meow' });
    await page.goto('./admin/content');
    await expect(
      page.getByText('There are no content items yet.'),
    ).toBeVisible();
    await page.goto('./admin/structure/block');
    await expect(page.locator('h1')).toHaveText('Block layout');
    await expect(
      page.locator('table[data-drupal-selector="edit-blocks"]'),
    ).toBeVisible();
  });
});
```

### 默认

都摆好了 `fullyParallel: false` 内 `playwright.config.ts`,或 `test.describe.configure({ mode: 'default' });` 在你的光谱文件中, 但你会 仍然导入 `parallelWorker` 修补.

在描述区块内进行测试,顺序为同一工人工序 测试平行运行的文件 。 然而,测试仍可能分布在 工人和工人仍然可能被重新使用.

```mermaid
flowchart TD
 subgraph W1["Worker 1"]
  W1si["Site install"]
  W1g1["group1 beforeAll"]
  W1g1t1["group1 test1"]
  W1g1t2["group1 test2"]
  W1g1t3["group1 test3"]
  W1g3["group3 beforeAll"]
  W1g3t1["group3 test1"]
  W1g3t2["group3 test2 - FAIL, end worker
  (retries: 0)"]
 end
 subgraph W2["Worker 2"]
  W2si["Site install"]
  W2g2["group2 beforeAll"]
  W2g2t1["group2 test1"]
  W2g2t2["group2 test2"]
  W2g2t3["group2 test3"]
  W2g4["group4 beforeAll"]
  W2g4t1["group4 test1"]
  W2g4t2["group4 test2"]
  W2g4t3["group4 test3"]
 end
 subgraph W3["Worker 3"]
  W3si["Site install"]
  W3g3["group3 beforeAll"]
  W3g3t3["group3 test3"]
 end

W1 --> W3

W1si --> W1g1
W1g1 --> W1g1t1
W1g1t1 --> W1g1t2
W1g1t2 --> W1g1t3
W1g1t3 --> W1g3
W1g3 --> W1g3t1
W1g3t1 --> W1g3t2

W2si --> W2g2
W2g2 --> W2g2t1
W2g2t1 --> W2g2t2
W2g2t2 --> W2g2t3
W2g2t3 --> W2g4
W2g4 --> W2g4t1
W2g4t1 --> W2g4t2
W2g4t2 --> W2g4t3

W3si --> W3g3
W3g3 --> W3g3t3

W1g3t2 -.-> W3si

 A["fullyParallel: false"] --> W1si
 A --> W2si
```

### 自定义站点安装

这个 `parallelWorker` 站点安装配置有工人范围选项. 因为它是工人范围, 设置在文件的顶端,在 `playwright.config.ts`,或者在再出口的预配置上 `test` (不在内) 个人 `test()`) (中文(简体) ). 宣布不同值的测试在不同的工人中进行, 每一个都安装了自己的网站.

Playwright保证这一点:工人只被重新用于测试,而其试验是: 工作范围选项匹配它开始的选项, 所以文件使用 `standard` 绝对不会对工人的 `minimal` 网站。 如果不然的话 安排在这样的工人, Playwright 停止该工人并开始一个新的, 用于安装所请求配置文件的网站。 声明相同的文件 配置文件仍然可以共享一个工人。 设置 `installProfile` 内置 a `test.describe()` 块是一个加载时间错误,因为它需要一个新的 工人通过文件.

因此,运行中使用的每个不同的剖面图至少需要一个额外的站点 安装,所以在可能情况下,倾向于每个套件的单个配置.

| 选项 | 默认 | 说明 |
| --- | --- | --- |
| `installProfile` | `'minimal'` | 安装传递给 `Drupal.install()`。 。 。 . |

```typescript
import { parallelWorker as test } from '@drupal/playwright';

test.use({ installProfile: 'standard' });
```

### 每项试验隔离

每个测试可以使用一个孤立的 Drupal 安装 `isolatedPerTest` 修补.

```mermaid
flowchart TD
 subgraph W1["Worker 1"]
  W1g1["group1 beforeAll"]
  W1si1["Site install"]
  W1g1t1["group1 test1"]
  W1g3["group3 beforeAll"]
  W1si2["Site install"]
  W1g3t1["group3 test1"]
  W1si3["Site install"]
  W1g3t2["group3 test2"]
  W1si4["Site install"]
  W1g3t3["group3 test3"]
 end
 subgraph W2["Worker 2"]
  W2g1["group2 beforeAll"]
  W2si1["Site install"]
  W2g2t1["group2 test1"]
  W2si2["Site install"]
  W2g2t2["group2 test2"]
  W2si3["Site install"]
  W2g2t3["group2 test3"]
  W2g4["group4 beforeAll"]
  W2si4["Site install"]
  W2g4t1["group4 test1"]
  W2si5["Site install"]
  W2g4t2["group4 test2"]
  W2si6["Site install"]
  W2g4t3["group4 test3"]
 end

W1g1 --> W1si1
W1si1 --> W1g1t1
W1g1t1 --> W1g3
W1g3 --> W1si2
W1si2 --> W1g3t1
W1g3t1 --> W1si3
W1si3 --> W1g3t2
W1g3t2 --> W1si4
W1si4 --> W1g3t3

W2g1 --> W2si1
W2si1 --> W2g2t1
W2g2t1 --> W2si2
W2si2 --> W2g2t2
W2g2t2 --> W2si3
W2si3 --> W2g2t3
W2g2t3 --> W2g4
W2g4 --> W2si4
W2si4 --> W2g4t1
W2g4t1 --> W2si5
W2si5 --> W2g4t2
W2g4t2 --> W2si6
W2si6 --> W2g4t3

 A["isolatedPerTest"] --> W1g1
 A --> W2g1
```

```typescript
import { expect } from '@playwright/test';
import { isolatedPerTest as test } from '@drupal/playwright';

test.describe('Drupal', () => {
  test('Install a module', async ({ drupal, page }) => {
    await drupal.loginAsAdmin();
    const response = await page.goto('./admin/structure/views');
    expect(response).not.toBeNull();
    expect(response!.status()).toBe(404);
    await drupal.installModules(['views', 'views_ui']);
    await page.goto('./admin/structure/views');
    await expect(page.locator('h1')).toHaveText('Views');
  });

  test('Verify views is not installed', async ({ drupal, page }) => {
    await drupal.loginAsAdmin();
    const response = await page.goto('./admin/structure/views');
    expect(response).not.toBeNull();
    expect(response!.status()).toBe(404);
  });
});
```

整个文件的选项可以通过 `test.use`数字 :

```typescript
import { expect } from '@playwright/test';
import { isolatedPerTest as test } from '@drupal/playwright';

test.use({ installProfile: 'standard', modules: ['views', 'views_ui'], enableTestExtensions: true });

test.describe('Drupal', () => {
    test('Do something', async ({ drupal, page }) => {
```

### 每个测试被隔离,有快照

这个 `isolatedPerTestSnapshot` fixture 使每次测试都相同 孤立为 `isolatedPerTest`,但安装网站 **每个工人一次** 财务报告和审定财务报表 在每次测试前恢复快照,而不是重排。 这对 安装了大量剖面图或食谱的站点,使测试运行得更快 仍然保证测试之间不泄露数据.

> **需要sqlite(英语:Sqlite).** 复制独立 sqlite 的快照/ 备份作品 数据库(和所管理的文件目录)。 设定 `DRUPAL_TEST_DB_URL` 改为 `sqlite://` URL (中文(简体) ). 与其它数据库一起,固定器立即投出——使用 `isolatedPerTest` 或者说 `parallelWorker` 换句话说.

```typescript
import { expect } from '@playwright/test';
import { isolatedPerTestSnapshot as test } from '@drupal/playwright';

test.describe('Drupal', () => {
  test('Install a module', async ({ drupal, page }) => {
    await drupal.loginAsAdmin();
    await drupal.installModules(['views', 'views_ui']);
    await page.goto('./admin/structure/views');
    await expect(page.locator('h1')).toHaveText('Views');
  });

  test('Verify views is not installed', async ({ drupal, page }) => {
    // The previous test's module install was rolled back by the restore.
    await drupal.loginAsAdmin();
    const response = await page.goto('./admin/structure/views');
    expect(response!.status()).toBe(404);
  });
});
```

#### 什么恢复覆盖

在每次测试前,固定恢复数据库和网站 `files` 财务报告和审定财务报表 `private` 目录. 其他站点文件, 例如 `settings.php`,没有 恢复。 数据库与 sqlite 复制 [在线备份 API](https://www.sqlite.org/backup.html),所以这是安全的,即使 前一个测试的请求仍在运行中 .

恢复后不需要缓存清除 。 恢复数据库缓存表格 和数据库一起拍摄的快照没有 `last_write_timestamp_*` 缓存项,所以 Drupal 忽略了上一个项 测试在APCu.

#### 自定义快照

被烤入快照的布局配置有工人范围选项. 因为他们是工人范围, 让他们在文件的顶端,在 `playwright.config.ts`,或者在再出口的预配置上 `test` (不在内) 个人 `test()`) (中文(简体) ). 宣布不同值的测试在不同的工人中进行, 每一个都有自己的快照.

| 选项 | 默认 | 说明 |
| --- | --- | --- |
| `installProfile` | `'minimal'` | 安装传递给 `Drupal.install()`。 。 。 . |
| `modules` | `[]` | 在快照前公开安装模块 . |
| `enableTestExtensions` | `false` | 使德鲁帕尔的只测试模块可以发现. |
| `snapshotCache` | `false` | 重新使用一个工人的快照 横穿,而代码不变. |

```typescript
test.use({ installProfile: 'standard', modules: ['views', 'views_ui'] });
```

建议的消费项目模式是再出口预先配置的 `test` 因此单个规格不需要锅炉板:

```typescript
// tests/fixtures/test.ts
import { isolatedPerTestSnapshot } from '@drupal/playwright';

export const test = isolatedPerTestSnapshot;
test.use({
  installProfile: 'my_distribution',
  enableTestExtensions: true,
  modules: ['views', 'views_ui'],
});
```

```typescript
// tests/my-feature.spec.ts
import { expect } from '@playwright/test';
import { test } from './fixtures/test.js';

test('starts from the pre-configured snapshot', async ({ drupal, page }) => {
  await drupal.loginAsAdmin();
  // ...
});
```

#### 重用快照横行

默认每次运行时都会从零开始重建快照. 启用 `snapshotCache` 将每个工人的工地固定起来,然后恢复运行 它不重置重置, 跳过安装和设置 完全在一个缓存命中. 仅在编辑测试文件时,这样做非常有用.

```typescript
test.use({ snapshotCache: true });
```

缓存由可以改变一切的指纹键入 在快照中显示: `installProfile`, (中文(简体) ), `modules` 财务报告和审定财务报表 `enableTestExtensions` 选项,解析后的数据库/基址,并通过 git Drupal 状态 根之活树. 测试文件被排除在指纹之外 所以 编辑a `*.spec.ts` 重用缓存快照,同时更改模块, 配置或其他源文件重建它. 仅 `*.spec.ts` 被排除为 默认, 设置 `testFileGlobs` 以匹配不同的布局。 只有当前 指纹保存在磁盘上; 较旧的快照自动被刷出 .

> **需要 git( 或 a) `key`) (中文(简体) ).** 工作树的指纹取决于 德鲁帕尔根是一个git检查出。 如果不是,你也不会提供 `key`, (中文(简体) ), 缓存以警告方式禁用自己, 而不是冒着重用 stale 的风险 示意图.

通过一个对象来调谐它 :

```typescript
test.use({
  snapshotCache: {
    // Provide a custom cache key
    key: process.env.MY_CACHE_KEY,
    // Where snapshots are stored
    // defaults to a directory under the OS temp
    // Also configurable with DRUPAL_TEST_SNAPSHOT_CACHE_DIR
    dir: '.cache/playwright-snapshots',
    // Paths excluded from the git fingerprint (defaults to ['**/*.spec.ts'])
    testFileGlobs: ['**/*.spec.ts', 'tests/**'],
  },
});
```

夹克也可以强迫上或下 `DRUPAL_TEST_SNAPSHOT_CACHE` 环境变量,它取代了选项 – 方便保持它 本地但关闭 CI :

```bash
DRUPAL_TEST_SNAPSHOT_CACHE=0 npx playwright test   # force off (e.g. in CI)
DRUPAL_TEST_SNAPSHOT_CACHE=1 npx playwright test   # force on
```

每个工人只重用自己写的插槽 所以平行运行停留 孤立,恢复的数据库保留它安装的前缀。 一个跑步 使用更多的工人 比缓存运行 只是重建额外的工人 快照和缓存它们.

### 每个文件被隔离

您也可以每个测试文件安装一个孤立的 Drupal, 但这需要 一些手工作业。 不过,它能确保测试数据不泄露 在测试文件之外,并使用 `default` 模式.

如果你想把一组测试放在一个文件中, 你可以结合 这些战略旨在同时对共享工人进行一些测试 安装 和一些隔离.

```mermaid
flowchart TD
 subgraph W1["Worker 1"]
  W1g1["group1 beforeAll (Site install)"]
  W1g1t1["group1 test1"]
  W1g1t2["group1 test2"]
  W1g1t3["group1 test3"]
  W1g3["group3 beforeAll (Site install)"]
  W1g3t1["group3 test1"]
  W1g3t2["group3 test2"]
  W1g3t3["group3 test3"]
 end
 subgraph W2["Worker 2"]
  W2g2["group2 beforeAll (Site install)"]
  W2g2t1["group2 test1"]
  W2g2t2["group2 test2"]
  W2g2t3["group2 test3"]
  W2g4["group4 beforeAll (Site install)"]
  W2g4t1["group4 test1"]
  W2g4t2["group4 test2"]
  W2g4t3["group4 test3"]
 end

 W1g1 --> W1g1t1
 W1g1t1 --> W1g1t2
 W1g1t2 --> W1g1t3
 W1g1t3 --> W1g3
 W1g3 --> W1g3t1
 W1g3t1 --> W1g3t2
 W1g3t2 --> W1g3t3

 W2g2 --> W2g2t1
 W2g2t1 --> W2g2t2
 W2g2t2 --> W2g2t3
 W2g2t3 --> W2g4
 W2g4 --> W2g4t1
 W2g4t1 --> W2g4t2
 W2g4t2 --> W2g4t3


 A["isolatedPerTest"] --> W1g1
 A --> W2g2
```

```typescript
import { Drupal, type DrupalSite } from '@drupal/playwright';
import { test, expect } from '@playwright/test';

test.describe('Isolated', () => {
  let drupalSite: DrupalSite;
  let drupal: Drupal;

  test.beforeAll(async ({ browser }) => {
    drupalSite = await Drupal.install();
    const page = await browser.newPage();
    drupal = new Drupal({ page, drupalSite });
    await drupal.setTestCookie();
    await drupal.loginAsAdmin();
    await drupal.setPreprocessing({ css: true, javascript: true });
    await drupal.logout();
    await page.close();
  });
  test.beforeEach(async ({ page }) => {
    drupal = new Drupal({ page, drupalSite });
    await drupal.setTestCookie();
  });
  test.afterAll(async ({}) => {
    await drupalSite.teardown();
  });

  test('Verify CSS and JS aggregation are on', async ({ page }) => {
    await drupal.loginAsAdmin();
    await page.goto('./admin/config/development/performance');
    await expect(page.getByLabel('Aggregate CSS files')).toBeChecked();
    await expect(page.getByLabel('Aggregate JavaScript files')).toBeChecked();
  });
});
```

```typescript
import { Drupal, type DrupalSite } from '@drupal/playwright';
import { test, expect } from '@playwright/test';

test.describe.configure({ mode: 'default' });

test.describe('Isolated (Serial)', () => {
  let drupalSite: DrupalSite;
  let drupal: Drupal;

  test.beforeAll(async ({ browser }) => {
    drupalSite = await Drupal.install();
  });
  test.beforeEach(async ({ page }) => {
    drupal = new Drupal({ page, drupalSite });
    await drupal.setTestCookie();
  });
  test.afterAll(async ({}) => {
    await drupalSite.teardown();
  });

  test('Turn CSS and JS aggregation on', async ({ page }) => {
    await drupal.loginAsAdmin();
    await drupal.setPreprocessing({ css: true, javascript: true });
  });

  test('Verify CSS and JS aggregation are on', async ({ page }) => {
    await drupal.loginAsAdmin();
    await page.goto('./admin/config/development/performance');
    await expect(page.getByLabel('Aggregate CSS files')).toBeChecked();
    await expect(page.getByLabel('Aggregate JavaScript files')).toBeChecked();
  });
});
```

## 子目录中的站点

如果您的测试可能针对安装在子目录中的 Drupal 站点, 例如 . `http://localhost/web/` 在 Drupal.org CI 上,导航路径 `baseURL`,从 `./`数字 :

```ts
await page.goto('./user/login'); // ✓
await page.goto('/user/login'); // ✗ ignores any path in baseURL
```

领先 `/` 将子目录去掉,所以测试404s. 这也适用于: `waitForURL()`, (中文(简体) ), `toHaveURL()` 财务报告和审定财务报表 `page.request`。固定装置添加后导 斜线 `baseURL` 需要做到这一点。 如果你的网站总是从 root,任意一种形式都可行.

抓住根相通的路径 用 `eslint --fix`,添加 ESLint 语句 插件为 `eslint.config.mjs`数字 :

```js
import drupalPlaywright from '@drupal/playwright/eslint';

export default [
  // ...
  drupalPlaywright.configs.recommended,
];
```

## 常见问题

### @drupal/playwright 是什么？

用 Playwright 测试 Drupal 站点的固定和命令

### 如何安装 @drupal/playwright？

在项目目录运行 npm install @drupal/playwright，或使用 pnpm add / yarn add / bun add。

### @drupal/playwright 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @drupal/playwright 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @drupal/playwright 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22\.16\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @drupal/playwright 可以商用吗？

@drupal/playwright 使用 GPL-2\.0\+ 许可。强 Copyleft：分发包含该代码的软件时，整个作品需以兼容的 GPL 许可开源。仅在内部使用、不对外分发时不触发此义务。以上为概括，不构成法律意见，请以许可原文为准。

### @drupal/playwright 的下载量有多少？

根据 npm 官方下载统计，@drupal/playwright 在 2026/10/13 – 2026/11/11 这 30 天内被下载 14,387 次，日均约 480 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @drupal/playwright 还在维护吗？

最新版本 2\.0\.0 发布于 2026/11/06（7 天前）；至今共发布 8 个版本，其中过去 12 个月发布了 5 个；首次发布于 2025/08/27。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

