No Tears Guide to Creating E2E Test Scripts for Playwright & Puppeteer
Getting browser automation scripts to do exactly what you want can feel like a never-ending battle between you and your code. I still remember vividly when I was up late one night years ago writing a Selenium script for a change I was making. I was banging my head against the table as I was running out of Selenium incantations to cast on my test to make it work.
Fast forward to today, I’m writing up this guide on a few tricks I wish I knew at the time, to quickly craft reliable E2E tests, instead of crying over error logs and wrangling clueless automation scripts.
A recorder is a tool that can record the browser actions you take, and turn those actions into a fully-working automation script. So you can start coding out your test, by simply stepping through your user flow as a user would.
I know there are people who might be skeptical, but hear me out. Just as automation frameworks have dramatically improved over time, test recorders have gotten some serious love over time as well.
I find test recorders pretty useful for a few reasons:
No more hunting for selectors - There’s no reason to be hunting for selectors yourself, when you can automate the work away. The best recorders use selector logic that will give you stable selectors, rather than trying to enumerate every div and class name on the way to your element. (We’ll also talk more about selectors later!)
No more manually writing commands - Just like selectors, there’s no need to manually type out every “click” “fill” and “waitFor” command, when you can get all of that for free by just simply walking through your web pages.
Best practices are built in - While recorders won’t generate perfect code, they will have best-practices built-in. Instead of needing to worry about using “fill” instead of “type”, or remembering how to await for text in Puppeteer, you can just let the recorder generate code that’s aligned with best practices, so you can focus on the rest of your work.
Ultimately, using a test recorder can give you a head start, save you from doing the boring parts of testing, and let you focus on the harder parts of perfecting a test automation script.
Here’s a few recorders that can help you get started:
DeploySentinel Recorder (opens in a new tab): A Chrome/Firefox Extension that generates Playwright/Puppeteer scripts (Disclaimer: I am one of the authors, but only because I wanted to build the most accurate recorder available)
Playwright Codegen (opens in a new tab): A CLI tool included in Playwright.
Puppeteer Chrome DevTool Recorder (opens in a new tab): A recorder built into Chrome for Puppeteer.
Headless Recorder (opens in a new tab): A Chrome Extension that can automate some simpler actions.
As dynamic CSS classes and elements are the norm today, it can be difficult to try to pick selectors that are robust to a rapidly changing code base, or even a rapidly changing web page due to async logic.
Especially if your project uses something like Tailwinds, styled-components, or even a JS framework like React or Vue that use async logic to render elements, you might be struggling to figure out how to target the right element reliably.
The best way to combat these issues is to explicitly introduce a stable selector to elements you’re looking to test, and nothing beats stability like introducing test IDs to your application.
The concept is very simple: for elements you need to interact with, append a
data-test-id=”my-element” attribute to the HTML element. In your automation
scripts, you can easily target the element with
Now you’ll never need to worry about your selectors breaking the next time your team decides to change button colors or re-builds the application with a new minified class name.
However, this requires adding new attributes to elements you need to target. Depending on how open the application owners are to adding these new attributes, it may be difficult to rely on them. If test IDs aren’t possible, I’d fall back to the next best solution.
Luckily with accessibility becoming a higher priority for web applications, more and more critical elements that need to be interacted with might already have a machine-friendly label attached to it.
Usually you’ll see attributes such as
elements you want to interact with. Those attributes tend to be more stable than
CSS classes and can serve as a good stop-gap measure until you’re able to
implement test IDs for the elements you need to test.
A script that utilizes these attributes might look like
await page.click('[alt="Main Logo"]');
If you’re testing an application that might not have all the accessibility selectors built out yet, and haven’t had time to implement test IDs, the last solution you can look towards is targeting elements by text content.
At first glance, it might sound like an incredibly fragile proposition. Indeed it can be for certain elements, but for others it may be the best stable solution available. Can you remember the last time your team updated the “Sign In” button text on your application?
For elements with non-dynamic text content, usually buttons or input placeholders, text content can be a fairly reliable way of target elements.
Luckily in Playwright, it’s incredibly easy to target elements by text like so:
In Puppeteer, you’ll need to dip into XPaths to target elements by text:
await page.waitForXPath('//*[contains(., "Sign In")]'); const [element] = await page.$x('//*[contains(., "Sign In")]'); await element.click();
These best practices of stable selectors mentioned here and more are already built-in to DeploySentinel Recorder’s (opens in a new tab) selector picking logic. So you don’t have to hunt for a specific test ID or accessibility selector.
If you’re banging your head against a test script trying to figure out why it isn’t working, whipping out the debugging modes is probably the fastest way to find out why your script isn’t doing what you want.
With Playwright, it’s incredibly easy to append
PWDEBUG=1 in front of your
script to pull up Playwright Inspector (opens in a new tab),
where it’ll be able to step through everything Playwright is doing in great
detail during the test. If there’s a step you have issues with, you can add
await page.pause() to pause the test run so you can inspect the page at that
point in time.
If you’re executing the script in a remote environment, you can take advantage of Playwright’s Trace Viewer (opens in a new tab) which records detailed logs and DOM snapshots before and after every action.
If you’re using DeploySentinel to run your test - Playwright traces are captured by default and viewable at any time to debug test runs.
In general you can also enable headed mode with slow motion enabled to visually see what your script is doing. Both Playwright and Puppeteer support this with just two extra lines of code. See the docs for Playwright (opens in a new tab) and Puppeteer (opens in a new tab) here.
The DeploySentinel Recorder (opens in a new tab) will always have these two options commented out but inserted as part of every script generated to make it easy to debug locally.
Lastly if there’s an issue that requires you to look at network requests or browser logs, you can have Playwright and Puppeteer open the Chrome dev tools panel on browser launch so all logs and network requests are captured from the start automatically for you. See the Playwright docs (opens in a new tab) here or the dev tools section of Puppeteer’s debugging docs (opens in a new tab).
I hope this list of tips helps you out in creating testing scripts for Puppeteer or Playwright.
If you’re looking to run tests easily and affordably - you can try DeploySentinel for free (opens in a new tab), with many of the above best practices included for you with no extra effort.