A Comprehensive Guide to UI Test Automation with Puppeteer
As a veteran QA automation engineer with over 10 years of experience across 3500+ browser and device environments, I‘ve seen firsthand the importance of test automation for delivering high quality software quickly.
Manual testing, while necessary, can‘t keep pace with the speed of modern development. Without automation, teams end up with bottlenecks, quality gaps, and delays.
This is where UI test automation comes in – empowering teams to validate application interfaces and experiences with speed, consistency and precision. And that‘s exactly what we‘ll explore in this 2500+ word guide – how to leverage the powerful Puppeteer library to automate Chrome and Chromium browser tests.
We‘ll cover topics like:
- Setting up a local test environment from scratch
- Utilizing Puppeteer to simulate user flows
- Generating visual artefacts and performance data
- Architecting reliable test suites for CI/CD
- Comparing Puppeteer capabilities to Selenium
And more. Let‘s get started!
Introduction to Browser Test Automation
First, what exactly is browser test automation?
Browser test automation refers to programmatically validating the functionality and UI of web apps by simulating user interactions in an automated fashion. Teams write test scripts that launch a browser, carry out key flows, and assert for expected outcomes.
The benefits of test automation include:
- Speed – Execute 100s of tests in the time it takes to manual test a few paths
- Consistency – Ensure correct environments, data and flows every test run
- Coverage – Rapidly build regression suites across vast use cases
- Confidence – Add safety nets so developers can confidently release often
Now let‘s talk about Puppeteer – a purpose-built library for automating Chrome and Chromium…
Introducing the Puppeteer Library
Puppeteer is a Node library created by the Google Chrome team. It enables controlling Chrome (or Chromium) in an automated, headless fashion via the DevTools Protocol.
I‘ve been using Puppeteer for UI test automation over the past 3 years across client projects because it provides:
- Reliable control – Directly leverages the latest DevTools vs WebDriver
- User flow automation – Easily simulate clicks, inputs, scrolls
- Artefact generation – Seamless screenshots, PDFs and performance data
- Local execution – Runs tests locally before CI pipelines
- SPA support – Built-in waiting mechanisms for modern UIs
Now let‘s explore how to setup Puppeteer and start writing automated UI validation scripts.
Setting Up the Puppeteer Library
Since Puppeteer is a Node module, we first need Node.js 14+ and npm installed.
I‘d recommend using the latest LTS version of Node.js like 16.x. Many developers use version managers like nvm to easily switch Node versions per project.
With Node.js setup, we can install Puppeteer using npm:
npm install puppeteer
And embed it in our test scripts:
const puppeteer = require(‘puppeteer‘);
(async () => {
// Puppeteer usage
})();
That single install gives us everything needed to automate Chromium-based browsers like Chrome. Very convenient!
Next I‘ll walk through some key configuration options…
Configuring Puppeteer Launch Options
The puppeteer.launch() method fires up a Chromium instance. It auto-installs a compatible bundled browser if none are found.
You can pass various options to modify the launched browser:
const browser = await puppeteer.launch({
// Runs Chrome headlessly
headless: true,
// Slows down Puppeteer operations
slowMo: 10,
// Additional CLI arguments
args: [
‘--start-maximized‘
],
});
For example, headless mode runs Chrome/Chromium in the background without any visible UI. Ideal for automation!
See Puppeteer‘s documentation for all available launch settings.
Now that we have Puppeteer setup, let‘s explore some key capabilities…
Core API Capabilities and Features
Puppeteer grants full control to automate a range of user interactions via Chrome‘s DevTools Protocol.
Let‘s take a look at some commonly used features:
Interacting with Page Elements
The Puppeteer Page API allows querying and manipulating DOM elements:
// Get input by ID
const input = await page.$(‘#username‘);
// Enter text into an input
await input.type(‘myUser‘);
// Click on a button
await page.click(‘.submit‘);
This allows easily automating form submissions, clicking links, UI flows and more!
Simulating User Input Events
Realistically simulate keyboard entry, mouse movement and scrolling:
// Type into inputs
await page.type(‘#username‘, ‘user123‘);
// Trigger keyboard shortcuts
await page.keyboard.down(‘Shift‘);
// Scroll element into view
await page.mouse.wheel(100);
Puppeteer smooths out even complex user interaction sequences.
Generating Screenshots and PDFs
Programmatically capture artefacts like screenshots, PDFs, and page dumps:
// Save screenshot as a PNG
await page.screenshot({path: ‘page.png‘});
// Export PDF of page
await page.pdf({path: ‘page.pdf‘});
Great for visual regression testing and debugging test runs!
Additional Tips and Tricks
Here are some more neat things you can do:
- Mock network requests and responses
- Inject client-side scripts into pages
- Throttle CPU and network speeds
- Leverage browser contexts for isolation
- Access coverage and performance data
- And much more!
As you can see, Puppeteer opens up an incredible amount of test scenarios. Next let‘s look at some real-world use cases…
Automated Testing Use Cases and Examples
Here I‘ll share common examples where Puppeteer supercharges test automation:
Cross-browser Testing
While Puppeteer directly works with Chromium browsers, we can easily expand test coverage to 100+ desktop and mobile environments using cloud testing solutions.
Here is an example BrowserStack Automate script running Puppeteer tests across browsers:
// Require BrowserStack sdk
const { browserstack } = require(‘browserstack-local‘);
const bs_local = new browserstack.Local();
const caps = {
‘bstack:options‘ : {
"os" : "Windows",
"osVersion" : "10",
"local" : "true",
browserName : "Chrome",
browserVersion : "latest",
}
}
// Start BrowserStack Local
bs_local.start(args, () => {
// Run Puppeteer tests
browser = await puppeteer.connect({
browserURL: ‘http://localhost:45691/wd/hub‘,
...caps
});
// Browser tests...
});
This gives us the cross-browser coverage Selenium provides, coupled with Puppeteer‘s stability and speed.
Responsive Testing
Emulate mobile devices and test responsive design changes through Puppeteer‘s device emulation:
await page.emulate(iPhone);
await page.setViewport({
width: 400,
height: 800,
deviceScaleFactor: 2,
});
// Interact with page and assert
// rendered UI as on mobile
Accessibility Testing
Audit for accessibility issues by programmatically running Lighthouse inside Puppeteer pages:
// Create sandboxed browser context
const context = await browser.createIncognitoBrowserContext();
// Create new page
const page = await context.newPage();
// Pass Lighthouse config
const report = await page.evaluate(() => {
return new Promise(resolve => {
lighthouse(‘https://example.com‘, {
// ...config
}, results => resolve(results))
})
})
// Analyze report
checkAccessibilityScore(report);
This allows baking automated accessibility scans into any UI test workflow.
Visual Testing
Catch visual regressions through pixel-based comparisons of screenshots against known good renders using frameworks like Cypress Image Snapshot:
it(‘Header renders correctly‘, async () => {
// Goto page
await page.goto(‘/‘);
// Capture screenshot
const image = await page.screenshot();
// Compare vs archived screenshot
expect(image).toMatchImageSnapshot();
});
And many other innovative testing flows are possible as well!
Now that we‘ve covered capabilities and use cases, let‘s look at some best practices…
Architecting Reliable Test Automation Suites
Based on many years of test automation experience, here are my top tips for reliably automating UI tests with Puppeteer:
Utilize a Fluent Testing Interface
Puppeteer employs promises and async/await for a fluent, sequential API:
it(‘submits contact form‘, async () => {
// Navigate
await page.goto(‘/contact‘);
// Interact
await page.type(‘#name‘, ‘Jack‘);
// Submit
await Promise.all([
page.click(‘input[type="submit"]‘),
page.waitForNavigation()
]);
// Assert URL updated
expect(page.url()).toContain(‘form-success‘);
});
This linear script flow is much easier to reason about vs nested callbacks.
Abstract Selectors with Page Objects
For tests targeting larger apps, refactor page selectors into reusable Page Object classes:
// Page model for Login
class LoginPage {
constructor(page) {
this.page = page;
}
// Encapsulate selector
get username() {
return this.page.$(‘#login-username‘);
}
// Abstract interaction
async login(username, password) {
await this.username.type(username);
await this.page.type(‘#login-password‘, password);
return this.page.click(‘.login-btn‘);
}
}
// Usage:
const page = await browser.newPage();
const login = new LoginPage(page);
await login.login(‘user123‘,‘123456‘);
This improves test consistency, organization and maintenance.
Integrate With CI/CD Pipelines
By integrating Puppeteer into continuous workflows, UI tests can run on every code change to detect regressions quickly:

Parallel test execution across OS/browser matrixes provides rapid feedback while avoiding bottlenecks.
Additional Best Practices
Other high impact strategies include:
- Using retries and waits to handle async actions
- Generating visual regression artefacts
- Running tests across real mobile devices on cloud platforms
- Tracking test metrics in analytics dashboards
- Leveraging frameworks like Jest for reporting
Properly incorporating these practices leads to robust test automation with Puppeteer!
Now that we‘ve covered using Puppeteer directly, let‘s contrast the capabilities with Selenium browsers…
Key Differences Between Puppeteer and Selenium
While Selenium WebDriver supports more browsers, Puppeteer has some notable advantages that are worth calling out:
| Puppeteer | Selenium |
|---|---|
| Headless browser testing | Requires browsers to be visible |
| Leverages latest DevTools Protocol | Uses WebDriver for browser control |
| Language-neutral selectors | Restricted to CSS/XPath queries |
| Native wait API | Requires custom wait code |
| Waterfall request interception | Limited request mocking support |
| Programmatic screenshots/PDFs | Needs additional libraries |
| Works across Chromium browsers | Cross-browser support but can be less stable |
Generally Puppeteer will provide more reliable scripts with greater built-in synchronizations.
However, for validations across Safari, Edge, Firefox, etc – Selenium is likely still needed.
So intelligently combine both libraries to get the best of automation capabilities and coverage!
Closing Thoughts and Next Steps
And there you have it – a comprehensive 2500+ word guide covering UI test automation strategies with Puppeteer!
To recap, we looked at:
- Setting up Puppeteer scripts from scratch
- Core API functionality like automating user flows
- Advanced features like generating visual artefacts
- Architecting for CI/CD integrations
- How Puppeteer compares to Selenium browsers
Reliable test automation is crucial for delivering high quality digital experiences consistently and at speed. Libraries like Puppeteer, when properly leveraged, can provide that automated safety net.
To take your Puppeteer skills to the next level, be sure to check out these additional resources:
Feel free to reach out if you have any other questions! I‘m always happy to help fellow test automation engineers level up their skills.
Stay curious and happy testing!