Migrating from Puppeteer to Playwright
Tags: puppeteer, testing, monitoring, javascript
Published: 2021-11-16 17:13:26

---
title: Migrating from Puppeteer to Playwright
published: true
description: The switch from Puppeteer to Playwright is easy. But is it worth it? And how exactly does one migrate existing scripts from one tool to another? What are the required code-level changes, and what new features and approaches does the switch enable?
tags: 
- puppeteer 
- testing
- monitoring
- javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9mwt2cya2snumtbu75k.png
---

_This article originally appeared in [Checkly's Guides](https://www.checklyhq.com/guides/puppeteer-to-playwright/)._

## Puppeteer and Playwright today

While they share a number of similarities, [Puppeteer](https://pptr.dev) and [Playwright](https://playwright.dev) have evolved at different speeds over the last two years, with Playwright gaining a lot of momentum and arguably even leaving Puppeteer behind.

These developments have led many to switch from Puppeteer to Playwright. This guide aims to show what practical steps are necessary and what new possibilities this transition enables. Do not let the length of this article discourage you - in most cases the migration is quick and painless.

### Why switch

While a comprehensive comparison of each tool's strengths and weaknesses could fill up a guide of its own (see our [previous benchmarks](https://blog.checklyhq.com/cypress-vs-selenium-vs-playwright-vs-puppeteer-speed-comparison/) for an example), the case for migrating to Playwright today is rather straightforward:

1. As of the writing of this guide, Playwright has been frequently and consistently adding game changing features (see [below](#new-possibilities-to-be-aware-of) for a partial list) for many months, with Puppeteer releasing in turn mostly smaller changes and bug fixes. This led to a reversal of the feature gap that had once separated the two tools.
2. Playwright maintains an edge in performance in real-world E2E scenarios (see benchmark linked above), resulting in lower execution times for test suites and faster monitoring checks.
3. Playwright scripts seem to run even more stable than their already reliable Puppeteer counterparts.
4. The Playwright community on [GitHub](https://github.com/microsoft/playwright/discussions), [Twitter](https://twitter.com/playwrightweb), [Slack](https://aka.ms/playwright-slack) and beyond has gotten very vibrant, while Puppeteer's has gone more and more quiet. 

## What to change in your scripts - short version

Below you can find a cheat sheet with Puppeteer commands and the corresponding evolution in Playwright. Keep reading for a longer, more in-depth explanation of each change.

Remember to add `await` as necessary.

| Puppeteer                                   | Playwright                                       |
| ------------------------------------------- | ------------------------------------------------ |
| `require('puppeteer')`                      | `require('playwright')`                          |
| `puppeteer.launch(...)`                     | `playwright.chromium.launch(...)`                |
| `browser.createIncognitoBrowserContext(...)`|  `browser.newContext(...)`                       |
| `page.setViewport(...)`                     | `page.setViewportSize(...)`                      |
| `page.waitForSelector(selector)` `page.click(selector);` | `page.click(selector)`              |
| `page.waitForXPath(XPathSelector)`          | `page.waitForSelector(XPathSelector)`            |
| `page.$x(xpath_selector)`                   | `page.$(xpath_selector)`                         |
| `page.waitForNetworkIdle(...)`              | `page.waitForLoadState({ state: 'networkidle' }})` |
| `page.waitForFileChooser(...)`              | Removed, [handled differently](https://playwright.dev/docs/input/#upload-files). |
| `page.waitFor(timeout)`                     | `page.waitForTimeout(timeout)` |
| `page.type(selector, text)`                 | `page.fill(selector, text)` |
| `page.cookies([...urls])`                   | `browserContext.cookies([urls])` |
| `page.deleteCookie(...cookies)`             | `browserContext.clearCookies()` |
| `page.setCookie(...cookies)`                | `browserContext.addCookies(cookies)` |
| `page.on('request', ...)`                   | Handled through [page.route](https://playwright.dev/docs/api/class-page#page-route). |
| `elementHandle.uploadFile(...)`             | `elementHandle.setInputFiles(...)` |
| Tricky file download.                       | Better [support for downloads](https://playwright.dev/docs/downloads). |

> Did we forget anything? Please let us know by getting in touch, or [submit your own PR](https://github.com/checkly/checklyhq.com).

## What to change in your scripts - in depth

### Require Playwright package

In Puppeteer, the first few lines of your script would have most likely looked close to the following:

```js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // ...
```

With Playwright you do not need to change much:

```js
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // ...
```

Playwright offers cross-browser support out of the box, and you can choose which browser to run with just by changing the first line, e.g. to `const { webkit } = require('playwright');`
In Puppeteer, this would have been done throught the browser's launch options:

```js
  const browser = await puppeteer.launch({ product: 'firefox' })
```

### The browser context

[Browser contexts](https://playwright.dev/docs/api/class-browsercontext) already existed in Puppeteer:

```js
const browser = await puppeteer.launch();
const context = await browser.createIncognitoBrowserContext();
const page = await context.newPage();
```

Playwright's API puts even more importance on them, and handles them a little differently:

```js
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
```

Like in Puppeteer, for basic cases and single-page flows, you can use the default context:

```js
const browser = await chromium.launch();
const page = await browser.newPage();
```

> When in doubt, explicitly create a new context at the beginning of your script.

### Waiting

The auto-waiting mechanism in Playwright means you will likely not need to care about explicitly waiting as often. Still, waiting being one of the trickiest bits of UI automation, you will still want to know different ways of having your script explicitly wait for one or more conditions to be met.

In this area, Playwright brings about several changes you want to be mindful of: 

1. [`page.waitForNavigation`](https://playwright.dev/docs/api/class-page#page-wait-for-navigation) and [`page.waitForSelector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector) remain, but in many cases will not be necessary due to auto-waiting.

2. [`page.waitForEvent`](https://playwright.dev/docs/api/class-page#page-wait-for-event) has been added.

3. Puppeteer's [`page.waitForXPath`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagewaitforxpathxpath-options) has been incorporated into [`page.waitForSelector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector), which recognises XPath expressions automatically.

4. [`page.waitForFileChooser`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagewaitforfilechooseroptions) been removed removed (see the [official dedicated page](https://playwright.dev/docs/input#upload-files) and our [file upload example](https://www.checklyhq.com/learn/headless/e2e-account-settings/) for new usage)

5. [`page.waitForNetworkIdle`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagewaitfornetworkidleoptions) has been generalised into [`page.waitForLoadState`](https://playwright.dev/docs/api/class-page#page-wait-for-load-state) (see the `networkidle` state to recreate previous behaviour)

6. [`page.waitForUrl`](https://playwright.dev/docs/api/class-page#page-wait-for-url) has been added allowing you to wait until a URL has been loaded by the page's main frame.

7. [`page.waitFor(timeout)`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagewaitforselectororfunctionortimeout-options-args) becomes [`page.waitForTimeout(timeout)`](https://playwright.dev/docs/api/class-frame#frame-wait-for-timeout).

> This is as good a place as any to remind that `page.waitForTimeout` should never be used in production scripts! Hard waits/sleeps should be used only for debugging purposes.

### Setting viewport

Puppeteer's `page.setViewport` becomes `page.setViewportSize` in Playwright.

### Typing

While puppeteer's [`page.type`](https://playwright.dev/docs/api/class-page#page-type) is available in Playwright and still handles fine-grained keyboard events, Playwright adds [`page.fill`](https://playwright.dev/docs/api/class-page#page-fill) specifically for filling and clearing forms.

### Cookies

With Puppeteer cookies are handled at the page level; with Playwright you manipulate them at the BrowserContext level. 

The old...

1. [`page.cookies([...urls])`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagecookiesurls)
2. [`page.deleteCookie(...cookies)`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagedeletecookiecookies)
3. [`page.setCookie(...cookies)`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-pagesetcookiecookies)

...become:

1. [`browserContext.cookies([urls])`](https://playwright.dev/docs/api/class-browsercontext#browser-context-cookies)
2. [`browserContext.clearCookies()`](https://playwright.dev/docs/api/class-browsercontext#browser-context-clear-cookies)
3. [`browserContext.addCookies(cookies)`](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-cookies)

Note the slight differences in the methods and how the cookies are passed to them.

### XPath selectors

XPath selectors starting with `//` or `..` are automatically recognised by Playwright, whereas Puppeteer had dedicated methods for them. That means you can use e.g. `page.$(xpath_selector)` instead of `page.$x(xpath_selector)`, and `page.waitForSelector(xpath_selector)` instead of `page.waitForXPath(xpath_selector)`. The same holds true for `page.click` and `page.fill`.

### Device emulation

Playwright [device emulation settings](https://playwright.dev/docs/emulation) are set at Browser Context level, e.g.:

```js
const pixel2 = devices['Pixel 2'];
const context = await browser.newContext({
  ...pixel2,
});
```

On top of that, permission, geolocation and other device parameters are also available for you to control.

### File download

Trying to download files in Puppeteer in headless mode can be tricky. Playwright makes this more streamlined:

```js
const [download] = await Promise.all([
  page.waitForEvent('download'),
  page.click('#orders > ul > li:nth-child(1) > a')
])

const path = await download.path();
```

See our [example on file download](https://checklyhq.com/learn/headless/e2e-file-download/).

### File upload

Puppeteer's [`elementHandle.uploadFile`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-elementhandleuploadfilefilepaths) becomes [`elementHandle.setInputFiles`](https://playwright.dev/docs/api/class-elementhandle#element-handle-set-input-files).

See our [example on file upload](https://checklyhq.com/learn/headless/e2e-account-settings/).

### Request interception

Request interception in Puppeteer is handled via [`page.on('request', ...)`](https://pptr.dev/#?product=Puppeteer&version=v11.0.0&show=api-event-request):

```js
await page.setRequestInterception(true)

page.on('request', (request) => {
  if (request.resourceType() === 'image') request.abort()
  else request.continue()
})
```

In Playwright, [`page.route`](https://playwright.dev/docs/api/class-page#page-route) can be used to intercept requests with a URL matching a specific pattern:

```js
await page.route('**/*', (route) => {
  return route.request().resourceType() === 'image'
    ? route.abort()
    : route.continue()
})
```

See our [full guide](https://checklyhq.com/learn/headless/request-interception/) on request interception for more examples.

> For many of the points in the list above, variations of the same function exist at [`Page`](https://playwright.dev/docs/api/class-page/), [`Frame`](https://playwright.dev/docs/api/class-frame/) and [`ElementHandle`](https://playwright.dev/docs/api/class-elementhandle/) level. For simplicity, we reported only one. 

## New possibilities to be aware of

When moving from Puppeteer to Playwright, make sure you inform yourself about the many completely new features Playwright introduces, as they might open up new solutions and possibilities for your testing or monitoring setup.

### New selector engines

Playwright brings with it added flexibility when referencing UI elements via selectors by exposing [different selector engines](https://playwright.dev/docs/selectors). Aside from CSS and XPath, it adds:

1. Playwright-specific selectors, e.g.: `:nth-match(:text("Buy"), 3)`
2. Text selectors, e.g.: `text=Add to Cart`
3. Chained selectors, e.g.: `css=preview >> text=In stock`

You can even create your own [custom selector engine](https://playwright.dev/docs/extensibility#custom-selector-engines).

For more information on selectors and how to use them, see [our dedicated guide](https://checklyhq.com/learn/headless/basics-selectors/).

### Saving and reusing state

Playwright makes it easy for you to save the authenticated state (cookies and localStorage) of a given session and reuse it for subsequent script runs. 

Reusing state can [save significant amounts of time](https://checklyhq.com/learn/headless/valuable-tests/#keep-tests-independent) on larger suites by skipping the pre-authentication phase in scripts where it is not supposed to be directly tested / monitored.

### Locator API

You might be interested in checking out Playwright's [Locator API](https://playwright.dev/docs/api/class-locator), which encapsulates the logic necessary to retrieve a given element, allowing you to easily retrieve an up-to-date DOM element at different points in time in your script.

This is particularly helpful if you are structuring your setup according to the [Page Object Model](https://martinfowler.com/bliki/PageObject.html), or if you are interested to do start doing that.

### Playwright Inspector

The [Playwright Inspector](https://playwright.dev/docs/inspector) is a GUI tool that comes in very handy when debugging scripts, allowing you to step instruction-by-instruction through your script to more esily identify the cause of a failure.

![playwright inspector](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ssqtmsbkr63pevqh3f9.png)

The Inspector also comes in handy due its ability to suggest selectors for page elements and even record new scripts from scratch.

### Playwright Test

Playwright comes with its own runner, [Playwright Test](https://playwright.dev/docs/intro), which adds useful features around end-to-end testing, like out-of-the-box parallelisation, test fixtures, hooks and more. 

### Trace Viewer

The [Playwright Trace Viewer](https://playwright.dev/docs/trace-viewer) allows you to explore traces recorded using Playwright Test or the BrowserContext Tracing API. Traces are where you can get the most fine-grained insights into your script's execution.

![playwright trace inspection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bj82rte2k8klvona821v.png)

### Test Generator

You can use the [Playwright Test Generator](https://playwright.dev/docs/codegen) to record interactions in your browser. The output will be a full-fledged script ready to review and execute.

![page being inspected with playwright codegen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3fhrteu35ledx1anv1t7.png)

## Switching to Playwright for richer browser check results

Checkly users switching to Playwright can take advantage of its new Rich Browser Check Results, which come with [tracing and Web Vitals](https://www.checklyhq.com/docs/browser-checks/tracing-web-vitals/) and make it easier to isolate the root cause of a failed check and remediate faster.

![performance and error tracing check results on checkly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l53vwbka3rw3d7olgq3q.png)

This reveals additional information about the check execution, including:
1. Overview of all errors raised (console, network and script errors)
2. A timeline summarising the execution across page navigations
3. For each page visited, a network & timing timeline, Web Vitals, console and network tabs.
4. In case of a failing check, a screenshot on failure.

> Aside from running a Playwright script, performance and error tracing also require the use of [Runtime](https://www.checklyhq.com/docs/runtimes/) `2021.06` or newer.

> Note that cross-browser support is not available on Checkly - [our Browser checks run on Chromium](https://www.checklyhq.com/docs/browser-checks/) only.

banner image: ["rocket engine"](https://www.flickr.com/photos/92081855@N07/8369463912) by [industrial arts](https://www.flickr.com/photos/92081855@N07), licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/?ref=ccsearch&atype=rich)