QA How-To
Puppeteer Tutorial for Beginners (2026)
Follow this Puppeteer tutorial to install the library, control browsers, choose selectors, wait correctly, test pages, intercept traffic, and run in CI.
18 min read | 2,858 words
TL;DR
Puppeteer tutorial success depends on correct lifecycle management, reliable synchronization, isolated data, and actionable failure evidence. Follow the runnable examples, then apply the review checklist before scaling the suite.
Key Takeaways
- Start with a deterministic smoke test before adding framework layers.
- Use stable, user-meaningful selectors and observable waits.
- Isolate browser state and shared test data for parallel safety.
- Keep lifecycle, cleanup, and artifacts explicit.
- Diagnose failures from evidence before increasing timeouts.
- Use abstractions only when they improve ownership and readability.
Puppeteer is a Node.js library for controlling Chrome and Firefox with a high-level browser automation API. This Puppeteer tutorial teaches beginners to launch a browser, navigate, interact with pages, wait for real states, test behavior, capture evidence, intercept traffic, and run automation in CI.
Puppeteer is intentionally a library rather than a complete testing framework. That flexibility is powerful, but you must design assertions, lifecycle, isolation, reporting, and cleanup deliberately.
TL;DR
Puppeteer tutorial readers should build from a deterministic smoke test toward isolated, diagnosable automation. Keep selectors stable, wait for observable outcomes, own test data, close resources, and retain evidence in CI.
- Start with the smallest runnable example.
- Treat synchronization and isolation as design concerns.
- Use artifacts to classify failures before changing timeouts.
- Add abstraction only when it improves ownership or readability.
1. Puppeteer tutorial: Install Puppeteer and Run a Smoke Script
Use a supported Node.js LTS release. Installing puppeteer downloads a compatible Chrome for Testing build by default, while puppeteer-core omits the browser and is intended for externally managed browsers.
mkdir puppeteer-demo && cd puppeteer-demo
npm init -y
npm install puppeteer
Add "type": "module" to package.json, then create a script that launches and closes cleanly. Keep the lockfile. Avoid copying an executable path from another developer's machine because browser discovery differs across environments.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
2. Launch a Browser and Open a Page
The browser process owns contexts, and contexts own pages. Use try/finally so cleanup happens after errors.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
const response = await page.goto('https://example.com', {
waitUntil: 'domcontentloaded'
});
if (!response?.ok()) throw new Error(`Navigation failed: ${response?.status()}`);
console.log(await page.title());
} finally {
await browser.close();
}
Headless mode is appropriate for routine execution. Use visible mode for focused diagnosis, not as a different assertion strategy.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
3. Select and Interact with Elements
Stable selectors describe user semantics or an explicit test contract. Puppeteer supports CSS plus query-handler syntax for text, ARIA, XPath, and shadow DOM scenarios. Its locator API waits for elements and actionability.
const link = page.locator('::-p-role(link[name="More information..."])');
await link.wait();
await link.click();
| Selector approach | Strength | Risk |
|---|---|---|
| ARIA query | user-facing semantics | weak markup can limit it |
| Test attribute | explicit stable contract | requires product convention |
| CSS | universal and precise | easy to couple to layout |
| Text | readable for stable copy | localization and duplicate text |
| XPath | complex relationships | often hard to maintain |
Do not use index position unless order itself is being tested.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
4. Wait for Application State Correctly
Waiting is the central browser automation skill. Document navigation readiness and application readiness are different. DOMContentLoaded says the document parsed, not that asynchronous account data appeared.
Use locators, page.waitForSelector(), page.waitForFunction(), response predicates, or application-specific signals. Avoid fixed sleeps. When an action triggers navigation, register the waiter before the action:
await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click('a')
]);
For a single-page update, wait for the confirmation element or changed URL rather than waitForNavigation. The browser test synchronization guide covers race diagnosis.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
5. Read Data and Cross the Browser Boundary
page.evaluate() executes in the browser context. Node variables do not exist there unless passed as arguments. Results must be serializable or represented by handles.
const summary = await page.evaluate(() => ({
title: document.title,
heading: document.querySelector('h1')?.textContent?.trim() ?? null
}));
console.log(summary);
Use $eval when you want to resolve one selector and transform that element, and $eval for a collection. Keep browser-evaluated functions small. Business assertions and file or database operations belong in Node, while DOM-specific inspection belongs in the page.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
6. Add Tests with Node Test Runner
Puppeteer can work with any runner. Node's built-in runner provides a small runnable example without another dependency.
import test from 'node:test';
import assert from 'node:assert/strict';
import puppeteer from 'puppeteer';
test('example page contract', async t => {
const browser = await puppeteer.launch();
t.after(async () => browser.close());
const page = await browser.newPage();
await page.goto('https://example.com');
assert.equal(await page.$eval('h1', el => el.textContent), 'Example Domain');
assert.match(await page.title(), /Example Domain/);
});
Run with node --test. For a suite, centralize browser lifecycle, artifacts, and timeouts without hiding test intent.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
7. Use Contexts for Isolation and Data for Independence
Create browser contexts to isolate cookies, local storage, and cache between scenarios. Close each context after use. Context isolation does not reset shared services, so create unique records and accounts for parallel tests.
Prefer API or fixture setup when a lengthy UI path is not the subject of the test. Track created data and clean it even after failure. Avoid one global page used by concurrent cases. Independence means any test can run alone, in a new order, or beside another worker with the same result.
For framework architecture, see the JavaScript end-to-end testing guide.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
8. Intercept Requests and Observe Responses
Enable interception before requests begin. Every intercepted request needs one terminal decision.
await page.setRequestInterception(true);
page.on('request', request => {
if (request.resourceType() === 'image') return request.abort();
return request.continue();
});
page.on('response', response => {
if (response.status() >= 500) console.error(response.status(), response.url());
});
await page.goto('https://example.com');
Use interception for intentional dependency control, fault injection, or focused resource experiments. Do not mock every service in a test labeled end to end. Redact authorization headers and sensitive bodies from logs.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
9. Handle Frames, Dialogs, Popups, and Screenshots
A frame is a separate document context. Locate the correct frame, then query inside it. Attach dialog handlers before triggering alerts. Start waiting for a popup before the click that opens it. These cases share one rule: subscribe before the event can happen.
Screenshots are evidence, not assertions. Capture them on failure or at intentional visual checkpoints, and use deterministic viewport and data. PDF generation is supported for page rendering use cases, but it should not be confused with visual regression testing. A visual test needs controlled fonts, environment, baselines, and a comparison policy.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
10. Debug Puppeteer Automation
Run with { headless: false, devtools: true, slowMo: 50 } temporarily to observe behavior. Listen for console, pageerror, requestfailed, and response status events. Capture the URL, screenshot, and minimal DOM evidence at the point of failure.
Classify the issue before changing code: product defect, selector problem, timing race, test data collision, browser difference, or environment failure. Increasing a timeout can hide the symptom while making every failure slower. Follow the flaky browser test debugging guide for a repeatable investigation.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
11. Run Puppeteer in CI and Containers
CI needs deterministic Node dependencies and compatible browser system libraries. The bundled browser simplifies version matching. In restricted containers, follow Puppeteer's official container guidance rather than disabling sandbox protections without understanding the risk.
name: puppeteer
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: node --test
Retain failure artifacts, bound concurrency to available CPU and memory, and use unique test data. Compare runner choices in the Puppeteer vs Playwright guide.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
12. Puppeteer Tutorial Practice Project
Automate a small workflow in stages. First verify a public page. Next submit a local sample form, validate an error, and capture a screenshot only on failure. Add context isolation, API-created data, request observation, and CI.
Deliberately cause a navigation race and then fix it with pre-registered waiting. Break a selector and inspect the evidence. Finally, explain the browser boundary in page.evaluate() to another engineer. Teaching the lifecycle reveals gaps faster than copying more scripts.
Practice lens: explain what evidence proves this part works, how cleanup behaves after failure, and whether another test can execute at the same time. A production suite needs an observable contract, not only a successful API call. Review the failure message as if another engineer will diagnose it in CI without your local browser.
13. Puppeteer tutorial: Review Checklist
Before merging, run the test alone and as part of the suite. Confirm the name describes behavior, setup is visible, data is unique, selectors express a stable contract, and every assertion reports a useful difference. Force one failure and inspect what CI would retain. Check that browser processes, contexts, pages, records, routes, and listeners are cleaned up.
Reviewers should ask what risk the test covers and whether a lower-level test could provide faster feedback. Browser tests are most valuable at integration boundaries and critical user workflows. Duplicate coverage increases runtime without necessarily increasing confidence. Track flaky outcomes separately, assign ownership, and remove or quarantine only with an explicit repair plan.
Reliability review: define the user-visible success condition before selecting an automation API. This keeps the check from accepting an intermediate screen. On failure, retain the locator, current URL, and nearby application evidence so triage begins with facts.
Isolation review: browser storage is only one state layer. Accounts, records, queues, caches, flags, and external services can connect concurrent cases. Give each case unique ownership or reset the dependency through a supported interface.
Synchronization review: a browser event is not always the business outcome. Navigation, DOM readiness, and network quiet can occur before a feature becomes usable. Wait for the result a user or service consumer can observe.
Selector review: choose a locator because it represents a stable interface, not because developer tools can copy it. Accessible names and deliberate test attributes usually survive harmless layout changes better than positional selectors.
Failure review: capture evidence at the first unexpected state. Later screenshots may show only a timeout or cleanup action. Preserve concise console, network, URL, and DOM context while redacting credentials and personal data.
Lifecycle review: every browser, context, page, route, listener, temporary file, and test record needs an owner. Teardown must run after assertion errors as well as success, otherwise later cases inherit contamination.
Parallelism review: more workers expose hidden dependencies. Validate a case alone, in a different order, and concurrently against unique data before using sharding to shorten feedback time.
Abstraction review: a helper should name a stable capability or manage lifecycle. A wrapper that merely renames a native call adds another debugging point and can conceal important options.
Assertion review: check outcomes at the requirement level. An enabled button proves less than a completed operation, while a database query alone can miss a broken experience. Match evidence to risk.
Data review: create the smallest record set the scenario needs and use collision-resistant identifiers. Cleanup should be safe to repeat so partial setup or retry does not leave permanent debris.
CI review: align browser, system dependencies, locale, timezone, viewport, and configuration closely enough for reproducibility. Upload artifacts even when the test command exits unsuccessfully.
Timeout review: a longer allowance can fit a known slow boundary, but keep it local and justified. Global increases delay every failure and often conceal a missing readiness condition.
Interview Questions and Answers
These concise answers are starting points. In an interview, add a specific example, a tradeoff, and the evidence you would collect.
Q: What is Puppeteer best used for?
It is useful for browser automation, web testing, controlled scraping, screenshots, PDFs, diagnostics, developer tools, and performance experiments.
Q: What does puppeteer.launch() return?
It returns a Browser instance representing the launched browser process. Create contexts and pages from it, then close it during teardown.
Q: What is the difference between puppeteer and puppeteer-core?
The full package manages a compatible browser download by default. The core package omits that download and expects you to manage a browser and executable connection.
Q: How should you wait after a click?
Wait for the outcome the click triggers. Use a navigation waiter registered before the click for document navigation, or wait for a specific UI or network state for SPA changes.
Q: Why use a browser context?
It isolates cookies and storage at lower cost than a new browser process. External server data still needs independent setup.
Q: Where does DOM code run?
Functions passed to page.evaluate, $eval, or $eval run in the browser page. Node code runs in the automation process.
Q: Can Puppeteer run tests by itself?
It can execute automation scripts, but a test suite typically adds a runner and assertion library to manage discovery, lifecycle, reporting, and failures.
Q: How do you capture a screenshot?
Call page.screenshot() with a path or receive the buffer. Use screenshots as diagnostic or visual-comparison input, not as the sole assertion.
Common Mistakes
- Installing
puppeteer-corewhile expecting an automatic browser download. Replace the shortcut with an explicit state, isolated resource, or diagnostic step. Record the reason in review so the same pattern does not return. - Forgetting to close browser processes after errors. Replace the shortcut with an explicit state, isolated resource, or diagnostic step. Record the reason in review so the same pattern does not return.
- Registering event or navigation waits after the trigger. Replace the shortcut with an explicit state, isolated resource, or diagnostic step. Record the reason in review so the same pattern does not return.
- Using fixed sleeps instead of observable conditions. Replace the shortcut with an explicit state, isolated resource, or diagnostic step. Record the reason in review so the same pattern does not return.
- Sharing pages and mutable data across concurrent tests. Replace the shortcut with an explicit state, isolated resource, or diagnostic step. Record the reason in review so the same pattern does not return.
- Assuming
page.evaluatecan access Node variables. Replace the shortcut with an explicit state, isolated resource, or diagnostic step. Record the reason in review so the same pattern does not return. - Disabling security controls to make CI pass without investigation. Replace the shortcut with an explicit state, isolated resource, or diagnostic step. Record the reason in review so the same pattern does not return.
A common pattern connects these mistakes: the script assumes time, order, or environment will behave favorably. Reliable automation replaces assumptions with explicit contracts and useful evidence.
As the Puppeteer project grows, separate reusable browser capabilities from scenario data and runner policy. Browser helpers can own actions such as opening an isolated context or attaching diagnostic listeners. Data builders can create valid records without knowing about the page. The runner should control timeouts, concurrency, teardown, and artifact retention. Tests then remain short narratives of behavior. Review these boundaries whenever a helper accepts many unrelated flags or a test must reach through several objects to perform a simple action. Healthy structure reduces the number of files touched by ordinary product changes and makes failures easier to localize.
Conclusion
Puppeteer tutorial mastery comes from understanding lifecycle, synchronization, isolation, and diagnosis together. The code examples provide a runnable base, while the design guidance keeps that base maintainable as coverage grows.
Create the smallest example now, run it in the same environment used by CI, and deliberately inspect one failure. That loop builds practical judgment faster than memorizing a long command list.
Interview Questions and Answers
What is Puppeteer best used for?
It is useful for browser automation, web testing, controlled scraping, screenshots, PDFs, diagnostics, developer tools, and performance experiments.
What does `puppeteer.launch()` return?
It returns a Browser instance representing the launched browser process. Create contexts and pages from it, then close it during teardown.
What is the difference between `puppeteer` and `puppeteer-core`?
The full package manages a compatible browser download by default. The core package omits that download and expects you to manage a browser and executable connection.
How should you wait after a click?
Wait for the outcome the click triggers. Use a navigation waiter registered before the click for document navigation, or wait for a specific UI or network state for SPA changes.
Why use a browser context?
It isolates cookies and storage at lower cost than a new browser process. External server data still needs independent setup.
Where does DOM code run?
Functions passed to `page.evaluate`, `$eval`, or `$eval` run in the browser page. Node code runs in the automation process.
Can Puppeteer run tests by itself?
It can execute automation scripts, but a test suite typically adds a runner and assertion library to manage discovery, lifecycle, reporting, and failures.
How do you capture a screenshot?
Call `page.screenshot()` with a path or receive the buffer. Use screenshots as diagnostic or visual-comparison input, not as the sole assertion.
How do you handle iframes?
Find the relevant Frame object and perform selectors or evaluation in that frame. The top-level page query does not automatically search frame documents.
What causes Puppeteer flakiness?
Common causes include timing races, unstable selectors, shared state, uncontrolled animations, environment differences, and missing diagnostics.
How do you observe failed requests?
Listen to `requestfailed` and response events before navigation or the triggering action. Record useful metadata while redacting secrets.
How should beginners practice?
Build a small deterministic workflow, add assertions and cleanup, then introduce one advanced concern at a time such as frames, interception, or CI.
Frequently Asked Questions
What is Puppeteer best used for?
It is useful for browser automation, web testing, controlled scraping, screenshots, PDFs, diagnostics, developer tools, and performance experiments.
What does `puppeteer.launch()` return?
It returns a Browser instance representing the launched browser process. Create contexts and pages from it, then close it during teardown.
What is the difference between `puppeteer` and `puppeteer-core`?
The full package manages a compatible browser download by default. The core package omits that download and expects you to manage a browser and executable connection.
How should you wait after a click?
Wait for the outcome the click triggers. Use a navigation waiter registered before the click for document navigation, or wait for a specific UI or network state for SPA changes.
Why use a browser context?
It isolates cookies and storage at lower cost than a new browser process. External server data still needs independent setup.
Where does DOM code run?
Functions passed to `page.evaluate`, `$eval`, or `$eval` run in the browser page. Node code runs in the automation process.