Puppeteer tutorial: your first reliable Node.js browser script
Install Puppeteer, open a browser page, wait for delayed content, extract a value and save a screenshot with one self-contained example.
Puppeteer is a JavaScript library for controlling Chrome and Firefox. Use it for screenshots, PDFs, page interactions and browser-rendered data. This tutorial uses Node.js, not PuppeteerSharp or Python wrappers.
Install Puppeteer
Use a supported Node.js release. In your own Node.js project:
npm install puppeteer
The full package manages a matching browser. puppeteer-core does not download one and requires deliberate executable/channel/connection management. If installation scripts were blocked, see installation and cache diagnosis.
Your first script, step by step
The steps below explain one small browser script. Follow the flow first, then copy the complete version into first.mjs. Its HTML is inline, so there is no website account, local server or separate demo file to prepare.
1. Launch the browser and create a page
Import Puppeteer and launch a headless browser. Create a page and give ordinary waits a five-second default. The complete script wraps the page work in try and closes the browser in finally.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();
page.setDefaultTimeout(5000);
2. Add a page with delayed content
This page starts with Loading, then changes the result to Ready after a short delay. It gives us something meaningful to wait for.
await page.setContent(`
<h1>My first browser script</h1>
<p id="result">Loading</p>
<script>
setTimeout(() => {
const result =
document.querySelector("#result");
result.textContent = "Ready";
}, 100);
</script>
`);
3. Wait for the result and read its text
Wait for the actual Ready value instead of sleeping for an estimated amount of time. Then extract text from the page into Node.js.
await page.waitForFunction(() => {
const result = document.querySelector("#result");
return result?.textContent === "Ready";
});
const result = await page.$eval(
"#result",
(element) => element.textContent,
);
4. Save a screenshot and print the result
Save the prepared page as first.png and print Ready. Browser cleanup belongs in finally, as shown in the combined script.
await page.screenshot({
path: "first.png",
});
console.log(result); // Ready
Complete script
Here are all the pieces together, including setup and cleanup. Save this as first.mjs and run node first.mjs.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({
headless: true,
});
try {
const page = await browser.newPage();
page.setDefaultTimeout(5000);
await page.setContent(`
<h1>My first browser script</h1>
<p id="result">Loading</p>
<script>
setTimeout(() => {
const result =
document.querySelector("#result");
result.textContent = "Ready";
}, 100);
</script>
`);
await page.waitForFunction(() => {
const result = document.querySelector("#result");
return result?.textContent === "Ready";
});
const result = await page.$eval(
"#result",
(element) => element.textContent,
);
await page.screenshot({
path: "first.png",
});
console.log(result); // Ready
} finally {
await browser.close();
}
The example prints Ready and saves first.png in the directory where you run it. finally closes the owned browser on success or failure. The page callback runs inside Chrome; return plain serializable data to Node rather than DOM nodes.
Why the wait matters
The page starts with Loading and changes later. Preparing the document is not the same as receiving the application result. The example waits for its exact expected text instead of guessing a fixed sleep.
On your application, choose a selector, response or state that proves the next operation is safe. See waiting patterns. Locators help with actionability, but cannot invent an absent result.
Navigate to a URL instead
Inside the same browser/page lifecycle, replace setContent() with navigation to a page you are authorized to inspect:
const response = await page.goto("https://example.com", {
waitUntil: "domcontentloaded",
timeout: 5000,
});
if (!response) {
throw new Error("Required document response missing");
}
if (!response.ok()) {
throw new Error(`HTTP ${response.status()}`);
}
await page.waitForSelector("h1", {
timeout: 5000,
});
const heading = await page.$eval(
"h1",
(element) => element.textContent,
);
console.log(heading);
For a real application, replace h1 with meaningful required content. A 200 response can still be a login or error page. goto() may throw, return a non-OK response or legitimately return null for about:blank and same-document navigation; define your task’s policy. See navigation errors.
Browser compatibility
Use the browser installed by Puppeteer unless your application needs a different, compatible browser.
An isolated browser context separates session data, but is not a security sandbox. Keep the browser sandbox enabled.
Next tasks
Try screenshots, PDF generation, dynamic scraping, forms or files. For an overview of the website, read About.