Puppeteer Login Screenshot Tutorial for a Private Web Page
Log in through an authorized site flow, verify the private page is ready, and save a Puppeteer screenshot with a clean, isolated session.
To screenshot an authorized private page with Puppeteer, authenticate through the website’s normal login flow or restore session state you are allowed to use, verify that the target page is signed in and ready, then call page.screenshot(). page.authenticate() is for HTTP authentication challenges; it does not fill an application’s login form or bypass account protections. Puppeteer’s screenshot guide uses navigation followed by Page.screenshot(), and supports capturing a specific element as well. Puppeteer screenshot guide; Page.authenticate() reference.
This guide uses a site-specific login form as an example. Replace its URL and selectors with the ones documented by the service you are authorized to access. Multi-factor authentication, single sign-on, and account challenges vary by site; there is no universal login sequence.
1. Set up Puppeteer
Use a supported Node.js installation and install Puppeteer in a project directory:
npm init -y
npm install puppeteer
Save the example below as screenshot-private.mjs. It reads credentials from environment variables so they do not need to live in source code. Run it with your authorized account:
LOGIN_URL='https://app.example.com/login' \
TARGET_URL='https://app.example.com/reports/monthly' \
LOGIN_USER='your-account' \
LOGIN_PASSWORD='your-password' \
node screenshot-private.mjs
2. Log in, confirm access, and capture
The example creates a dedicated browser context, submits a normal username-and-password form, waits for a site-specific signed-in indicator, navigates to the private page, checks that the login page did not reappear, and writes a full-page PNG. Change the selectors and readiness condition to match the target service. Do not use this flow for an account or page you are not authorized to access.
import puppeteer from 'puppeteer';
const loginUrl = process.env.LOGIN_URL;
const targetUrl = process.env.TARGET_URL;
const username = process.env.LOGIN_USER;
const password = process.env.LOGIN_PASSWORD;
if (!loginUrl || !targetUrl || !username || !password) {
throw new Error('Set LOGIN_URL, TARGET_URL, LOGIN_USER, and LOGIN_PASSWORD.');
}
const browser = await puppeteer.launch({ headless: true });
const context = await browser.createBrowserContext();
try {
const page = await context.newPage();
await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
page.setDefaultNavigationTimeout(45_000);
page.setDefaultTimeout(15_000);
await page.goto(loginUrl, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('input[name="username"]');
await page.locator('input[name="username"]').fill(username);
await page.locator('input[name="password"]').fill(password);
// Replace with the actual submit control for this authorized site.
await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }).catch(() => null),
page.locator('button[type="submit"]').click(),
]);
// Replace with a selector visible only after a successful login.
await page.waitForSelector('[data-testid="account-menu"]', { visible: true });
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
// Replace with a target-specific marker that means the page is ready.
await page.waitForSelector('[data-testid="report-content"]', { visible: true });
// Guard against a redirect or an expired session before saving the image.
if (page.url().includes('/login')) {
throw new Error('The target redirected to login; the session is not authenticated.');
}
await page.screenshot({ path: 'private-page.png', fullPage: true });
console.log('Saved private-page.png');
} finally {
// Closing the isolated context also discards its cookies and local storage.
await context.close();
await browser.close();
}
The login selectors in this example are placeholders, not universal selectors. Use stable attributes exposed by your own application when possible. Puppeteer’s official screenshot guide documents saving with Page.screenshot(), and the method accepts screenshot options. Page.screenshot() reference.
3. Choose how to authenticate
Application login form
Use the site’s supported login flow, as in the runnable example. For forms that do not trigger a full navigation, wait for a logged-in marker or a response/state change instead of assuming a navigation occurred. If the site requires a one-time code, consent, or interactive identity-provider step, follow its authorized process; do not try to circumvent it.
HTTP Basic or Digest authentication
For a server-level HTTP authentication challenge, configure Puppeteer before navigating:
await page.authenticate({ username: process.env.HTTP_USER, password: process.env.HTTP_PASSWORD });
await page.goto('https://protected.example.com/report', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'http-auth-page.png', fullPage: true });
This is a different mechanism from entering credentials into a website form. Puppeteer notes that Page.authenticate() turns request interception on behind the scenes, which may affect performance. Pass null to disable HTTP authentication when needed. Page.authenticate() reference.
Restore authorized cookie state
If the site operator provides session cookies for an authorized automation workflow, set them on a dedicated context before visiting the site. Treat session cookies like passwords: keep them out of logs, screenshots, source control, and shared artifacts. Cookie attributes such as domain, path, expiry, and secure scope must match the target site.
const context = await browser.createBrowserContext();
await context.setCookie({
name: 'session',
value: process.env.SESSION_COOKIE,
domain: 'app.example.com',
path: '/',
secure: true,
httpOnly: true,
sameSite: 'Lax',
});
const page = await context.newPage();
await page.goto('https://app.example.com/reports/monthly', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-testid="report-content"]', { visible: true });
await page.screenshot({ path: 'private-page.png', fullPage: true });
await context.close();
The exact cookie name and attributes are site-specific. Puppeteer provides browser cookie methods to inspect, set, and remove cookie state. A dedicated browser context isolates cookies and local storage from other contexts. Puppeteer cookies guide; BrowserContext reference.
4. Pick the right screenshot scope and output
| Need | Option | Example |
|---|---|---|
| Save the visible viewport | Default screenshot | await page.screenshot({ path: 'page.png' }) |
| Capture the whole document | fullPage: true |
await page.screenshot({ path: 'page.png', fullPage: true }) |
| Capture a focused region | Element screenshot | await (await page.waitForSelector('.report')).screenshot({ path: 'report.png' }) |
| Keep image bytes in memory | Omit path |
const bytes = await page.screenshot() |
For a specific element, wait for the element to exist before capturing. Puppeteer’s guide says element screenshots try to scroll a hidden element into view. A full-page image can be very tall; use an element screenshot when only a report, card, or panel is needed. Screenshots guide.
5. Readiness, session isolation, and safe handling
- Use a dedicated context when isolation matters. Browser contexts have isolated cookies and local storage; closing the context closes its pages and discards that session boundary. BrowserContext reference.
- Wait for an application-ready signal.
domcontentloadedonly gives a useful navigation milestone; wait for a page-specific selector that confirms the content has rendered. If the page fills in data asynchronously, the selector should represent that data, not merely the outer shell. - Check the resulting state before capture. Verify the URL and a signed-in or target-content marker. Otherwise, the output may be a login form or an access error saved as a valid PNG.
- Protect output files. Private data may appear in the screenshot itself. Restrict where it is saved and who can access it, and avoid printing credentials, cookies, or sensitive page content in diagnostics.
- Close resources in a
finallyblock. This prevents failed waits or navigation from leaving browser contexts running.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot shows the login page | Login failed, session expired, or target navigation redirected. | Wait for a site-specific signed-in marker after login and a target-content marker after navigating. Check the final URL before capture. |
waitForNavigation times out after clicking submit |
The login flow updates the page without a full document navigation, or opens a new page. | Wait for the authenticated-state selector or the expected URL/state change. For popups, handle the new page in the same browser context. |
| Selector wait times out | Placeholder selector is wrong, the page is not ready, or content is inside a frame. | Inspect the authorized page’s DOM, choose a stable selector, increase the timeout only if the site needs it, and target the relevant frame if applicable. |
| Cookie restore still appears signed out | Cookie scope or expiry is wrong, the service also requires local storage, or the session has been revoked. | Use current authorized session state with correct cookie domain/path and visit the matching origin. Do not assume one cookie is sufficient; follow the service’s supported session setup. |
| HTTP auth keeps prompting | Page.authenticate() is being used for an app form, or credentials are incorrect. |
Use the normal application flow for an app login; use authenticate() only for an HTTP authentication challenge. |
| Image is blank or partially rendered | Capture ran before app data or images rendered. | Wait for the page’s content-ready selector and, where necessary, a targeted image or application state condition before taking the screenshot. |
| Browser seems slow with HTTP auth | HTTP authentication enables request interception behind the scenes. | Use it only when needed and account for interception overhead in the workflow; do not add interception-based logic unnecessarily. |
| Saved image contains sensitive details | The page itself includes private data. | Limit access to the output and remove or redact sensitive information before sharing. Never include secrets in the screenshot unless the workflow explicitly requires them and access is controlled. |
7. Performance, reliability, and cost
For a single capture, the largest reliability improvement is usually waiting for the right application state rather than relying only on a generic network-idle condition. Some applications keep network connections open or load content after initial navigation, so choose a selector tied to the content you need. A full-page image takes more output space than a viewport or element capture. Reuse a browser process for repeated authorized captures when appropriate, but keep separate users or jobs in separate contexts so their session state does not mix.
Puppeteer itself is a browser automation library, so operating it means provisioning and maintaining a browser runtime and handling the site’s login flow. The dossier documents no universal runtime cost or benchmark; cost depends on your hosting and workload.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-request API returns a screenshot or PDF; this call saves a capture of a public target URL (adapt the URL to a page you are authorized to access):
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
See the ScreenshotNeo API documentation for request options. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free for 1,000 screenshots a month, with no card.
FAQ
Can Puppeteer log in to any private website with one generic script?
No. The site’s login controls, identity provider, and session requirements determine the authorized flow, so selectors and readiness checks must be adapted to that site.
Does a screenshot prove that the account was authorized?
No. It only records the rendered page at capture time. Authorization must come from the account and access permissions used by the operator.
Should I keep the browser context between runs?
Use a fresh context when you want session isolation. If an approved workflow requires a persistent session, store and protect that state as a credential, and account for expiry or revocation.
Can I capture just one panel instead of a long page?
Yes. Wait for the panel’s selector and call its element screenshot method to save a focused image.


