npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@drupal/playwright

v1.1.1

Published

Fixtures and commands for testing Drupal sites with Playwright

Readme

Playwright

This project provides fixtures and helper objects for working with Drupal sites.

Fixtures

Two fixtures are provided for installing Drupal and then subsequently working with it. Documentation for working with the drupal object is here.

It's very important to note that by default tests can run on any available worker, which means it could be running on a Drupal install that a previous set of tests has run on. This can be altered with a combination of test configuration and which fixture you use.

Fully Parallel

This is set with either fullyParallel: true in playwright.config.ts, or test.describe.configure({ mode: 'parallel' }); in your spec file.

Tests within a describe block run in parallel. This provides faster execution since tests don't wait for each other, but requires tests to be independent with no shared state as any test could end up in any worker in any order. This means that tests running with this fixture must not do anything destructive to the site. However, they can make alterations as long as they clean up after themselves (if the test fails then Playwright will discard the entire worker process and start a new one). This also applies to anything that runs in a hook such as beforeAll - it could end up running on multiple workers and it's changes perpetuated.

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();
  });
});

Default

This is set with either fullyParallel: false in playwright.config.ts, or test.describe.configure({ mode: 'default' }); in your spec file, but you would still import the parallelWorker fixture.

Tests within the describe block run in order in the same worker process, with test files running in parallel. However, tests may still be distributed across workers and workers may still be reused.

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

Isolated per test

You can have an isolated Drupal install per test by using the isolatedPerTest fixture.

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);
  });
});

Isolated per file

You can also have an isolated Drupal install per test file, but this requires some manual work. However, it will allow you to ensure test data does not leak outside a test file, and utilise the default mode if needed.

If you'd like to keep a set of tests together in one file, you could combine these strategies to have some tests run in parallel on the shared worker installation, and some in isolation.

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();
  });
});