Diagram of Playwright's architecture

Intro

When you load up the repository for the Playwright library, you may be overwhelmed by the numerous packages living both inside the packages folder, and within the root folder. In addition, if you try and trace the code used by a Playwright test, you will likely be befuddled by the many folders in the package which may or may not contain the code you are looking for, such as finding the definitions of the code in the import

example-script.ts
import { test, chromium } from "@playwright/test";

making it much more difficult to understand the core architecture of the playwright project. In this post we aim to remedy this problem by giving a high-level overview of the project and its architecture. This includes a description of what features and facets are contained within the various directories of the project, helping guide you to further your understanding of the project.

Root directory

In the root directory we find the following folders

playwright/
browser_patches/
docs/
examples/
packages/
tests/
utils/

along with the package.json which includes the following keys

package.json
{
  "name": "playwright-internal",
  "private": true,
  "workspaces": ["packages/*"]
}

The package.json gives us the hint the core functionality is within the packages/ directory since the root package.json file is labelled private, has "internal" in its name, and uses the "workspaces" key, which tells node this project has multiple packages. Let's go over the other top-level directories real quick before diving into the packages/ folder

  • browser_patches - This folder contains browser patches improving adding additional controls and features to some of the browser engines used within this project. Interestingly, this give a patch to webkit so it can run within Windows.
  • docs - The docs folder contains markdown files which compile to the documentation found on the Playwright docs.
  • examples - Here are examples of test scripts to refer to, while getting started with using the Playwright project.
  • tests - Even testing libraries should have automated tests, which are written in Playwright.
  • utils - The utils directory contains miscellaneous tools used during build time, docker images, functionality for Azure, a types file generator, and more.

Packages directory

The packages directory contains over 20 separate packages, making it more difficult to understand. We will split up this list into easy-to-understand partitions.

Test reporting/generation packages

Playwright contains many different test reporting packages each with their own set of functionalities. Briefly, the list of packages in this category is

playwright/packages
html-reporter/
recorder/
trace-viewer/
trace/
web/

which contains the code associated with either handling test reports or generating them with the recorder package. In addition, there is the trace and trace viewer which can be recorded during playwright test runs. In addition, the web/ package provides utilities shared across the various web-based tools listed here.

Browser specialized packages

Each of the packages here are simply exports of the playwright-core package along with an install script for the associated browser only. You can find them on npmjs, for example, playwright-chromium is its own package which has playwright specialized for only automating chromium.

playwright/packages
playwright-chromium/
playwright-firefox/
playwright-webkit/

Browser installation packages

The packages listed here only include install scripts for each of the browsers. For example, playwright-browser-chromium corresponds to the @playwright/browser-chromium package on npmjs.

playwright/packages
playwright-browser-chromium/
playwright-browser-firefox/
playwright-browser-webkit/

Component testing packages

These packages are all associated to the experimental component testing framework in playwright. These give functionality to playwright similar to how Jest can render individual components for different libraries.

playwright/packages
playwright-ct-core/
playwright-ct-react/
playwright-ct-react17/
playwright-ct-solid/
playwright-ct-svelte/
playwright-ct-vue/
playwright-ct-vue2/

Core functionality packages

Finally, we reach the core packages provided in playwright, which contain all of the main functionality of the playwright project.

playwright/packages
playwright-core/
playwright-test/
playwright/
protocol/

Note the playwright-test folder is simply a wrapper for binding playwright-core and playwright together in the @playwright/test package installable through npm. If you look in the index.js file, all you'll see is an export of playwright/test, which lives in the playwright package.

Another package to look at briefly is the protocol package which has automatically generated code through some of the build scripts in the utils directory of the root directory of the playwright project. This simply contains a YAML file, protocol.yml, which lists out all the interfaces used by playwright's protocol. If you ever wanted to build a driver for playwright in another language, this is one of the core files to consult while building this library.

Now, the two main packages of the entire playwright library are the playwright and playwrite-core packages. The first of which is a wrapper around playwright-core and is responsible for managing all the testing logic. This includes starting up test workers, running test executions, reporting test results, managing retries, and test assertions. A good heuristic for the code contained in this package is any kind of configuration logic you would find in the playwright.config.ts file, or any of the associated test running code, such as

my-test.spec.ts
import { test } from "@playwright/test";

then you are likely importing code from the playwright package.

The other main package is playwright-core which contains all of the browser automation functionality. This functionality is split up into two main components, the client library and the server library. The server library, found in packages/playwright-core/src/server, contains all the logic responsible for the browser automation actions. This means it will dispatch actions over the Chrome Devtools Protocol (CDP), or something similar to it, which tells the browser to perform actions like "click the login button", "input 'Hello' into the text form", or "navigate to 'https://news.ycombinator.com'". The server library contains implementations for each of the supported browsers and provides a unified API provided by a websocket server for other processes to access this automation functionality.

On the other hand, the client library contains all the user facing API's you would access while writing any browser automation scripts. So if you have a script like

my-automation-script.ts
import { chromium } from "@playwright/test";

(async function () {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto("https://playwright.dev");
})();

really you are importing chromium from an instance of the class in playwright-core/client/playwright. Then, calling browser.newPage and page.goto is making requests to interfaces in the server library, which communicates with the browsers over CDP or something similar. This means if you ever wanted to write your own browser automation tool which is browser-independent, a useful starting point would be to use the server library in the playwright-core package since it provides a comprehensive and battle tested API for this kind of functionality.

This splitting of responsibility design pattern works well because it allows for supporting multiple languages which can use Playwright's browser automation API. In fact, if you look at other implementations of the Playwright library, you will find the same design patterns (such as ChannelOwner) as you would in the the client library found in playwright-core.

Looking beyond

Hopefully you have a clearer picture about how the Playwright project is structured and have some intuition about where you can look if you ever want to extend its functionality or use it as a base library for some other browser-automation project.

In later posts we will deep dive into more of the specific design patterns used in Playwright's codebase which allow it to work so well, enriching your understanding of how the library works.