剧作家
此工程提供定着和帮助对象用于写入 剧作家 测试与 Drupal 网站交互.
所需资源
- 节点.js 22.16.0左右.
固定设备
为安装Drupal并随后工作提供了两个固定装置 带着它 用于处理 drupal 对象的文档是 这里。 。 。 .
必须指出的是,默认测试可以运行在任何可用的 工人,这意味着它可能运行在 Drupal 安装之前的一套 测试已经开始 这可以结合测试来改变 配置和您使用的固定.
完全平行
都摆好了 fullyParallel: true 内 playwright.config.ts,或
test.describe.configure({ mode: 'parallel' }); 在您的光谱文件中.
在描述区块内平行运行的测试。 这样可以更快地执行
因为测试不等待对方, 但要求测试是独立的
没有共享状态,因为任何测试 都可能最终在任何工人 任何顺序。 这意味着
使用这个固定装置进行的测试 决不能对它造成任何破坏
网站。 但是,只要清理完之后,它们就可以进行改建
如果测试失败, Playwright 将丢弃整个工人
程序并开始新的程序)。 这也适用于任何在钩子上运行的东西
例如 beforeAll - 它可能最终运行 在多个工人,这是
变化保持不变.
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
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: '[email protected]',
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 修补.
在描述区块内进行测试,顺序为同一工人工序 测试平行运行的文件 。 然而,测试仍可能分布在 工人和工人仍然可能被重新使用.
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()。 。 。 . |
import { parallelWorker as test } from '@drupal/playwright';
test.use({ installProfile: 'standard' });
每项试验隔离
每个测试可以使用一个孤立的 Drupal 安装 isolatedPerTest
修补.
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
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数字 :
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换句话说.
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,所以这是安全的,即使
前一个测试的请求仍在运行中 .
恢复后不需要缓存清除 。 恢复数据库缓存表格
和数据库一起拍摄的快照没有
last_write_timestamp_* 缓存项,所以 Drupal 忽略了上一个项
测试在APCu.
自定义快照
被烤入快照的布局配置有工人范围选项.
因为他们是工人范围, 让他们在文件的顶端,在
playwright.config.ts,或者在再出口的预配置上 test (不在内)
个人 test()) (中文(简体) ). 宣布不同值的测试在不同的工人中进行,
每一个都有自己的快照.
| 选项 | 默认 | 说明 |
|---|---|---|
installProfile | 'minimal' | 安装传递给 Drupal.install()。 。 。 . |
modules | [] | 在快照前公开安装模块 . |
enableTestExtensions | false | 使德鲁帕尔的只测试模块可以发现. |
snapshotCache | false | 重新使用一个工人的快照 横穿,而代码不变. |
test.use({ installProfile: 'standard', modules: ['views', 'views_ui'] });
建议的消费项目模式是再出口预先配置的
test 因此单个规格不需要锅炉板:
// tests/fixtures/test.ts
import { isolatedPerTestSnapshot } from '@drupal/playwright';
export const test = isolatedPerTestSnapshot;
test.use({
installProfile: 'my_distribution',
enableTestExtensions: true,
modules: ['views', 'views_ui'],
});
// 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 将每个工人的工地固定起来,然后恢复运行
它不重置重置, 跳过安装和设置 完全在一个缓存命中.
仅在编辑测试文件时,这样做非常有用.
test.use({ snapshotCache: true });
缓存由可以改变一切的指纹键入
在快照中显示: installProfile, (中文(简体) ), modules 财务报告和审定财务报表 enableTestExtensions
选项,解析后的数据库/基址,并通过 git Drupal 状态
根之活树. 测试文件被排除在指纹之外 所以
编辑a *.spec.ts 重用缓存快照,同时更改模块,
配置或其他源文件重建它. 仅 *.spec.ts 被排除为
默认, 设置 testFileGlobs 以匹配不同的布局。 只有当前
指纹保存在磁盘上; 较旧的快照自动被刷出 .
需要 git( 或 a)
key) (中文(简体) ). 工作树的指纹取决于 德鲁帕尔根是一个git检查出。 如果不是,你也不会提供key, (中文(简体) ), 缓存以警告方式禁用自己, 而不是冒着重用 stale 的风险 示意图.
通过一个对象来调谐它 :
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 :
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 模式.
如果你想把一组测试放在一个文件中, 你可以结合 这些战略旨在同时对共享工人进行一些测试 安装 和一些隔离.
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
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();
});
});
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,从 ./数字 :
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数字 :
import drupalPlaywright from '@drupal/playwright/eslint';
export default [
// ...
drupalPlaywright.configs.recommended,
];