How to take full-page screenshots of a members-only site after login in Puppeteer
Log in through the site’s permitted flow, verify member content has rendered, then capture the entire page with Puppeteer’s fullPage option.
To take a full-page screenshot of a members-only site in Puppeteer, authenticate through the site’s permitted login flow, confirm that protected content has rendered, then call page.screenshot({ path: 'member-page.png', fullPage: true }). The fullPage option captures the whole page; it defaults to false, so set it explicitly. [Puppeteer screenshot guide; ScreenshotOptions reference]
There is no universal login selector or cookie recipe. Adapt authentication and readiness checks to the target site, and only access pages and accounts you are authorized to use.
1. Install Puppeteer and prepare credentials
In a new Node.js project, install Puppeteer:
npm install puppeteer
Keep usernames, passwords, and session cookies out of source control. Supply credentials through your deployment environment or a secret store. The example below uses environment variables and placeholder selectors; replace them with the login form and member-content selectors for your site.
2. Log in, verify access, and capture the full page
This runnable example submits an ordinary HTML form, checks for a site-specific authenticated state, visits the protected page, confirms member content is visible, and saves a PNG. It closes the browser even if navigation, login, or capture fails.
import puppeteer from 'puppeteer';
const loginUrl = process.env.LOGIN_URL;
const memberUrl = process.env.MEMBER_URL;
const username = process.env.MEMBER_USERNAME;
const password = process.env.MEMBER_PASSWORD;
if (!loginUrl || !memberUrl || !username || !password) {
throw new Error('Set LOGIN_URL, MEMBER_URL, MEMBER_USERNAME, and MEMBER_PASSWORD');
}
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(loginUrl, { waitUntil: 'domcontentloaded' });
// Replace these selectors with the actual site's form fields and submit control.
await page.locator('input[name="username"]').fill(username);
await page.locator('input[name="password"]').fill(password);
await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.locator('button[type="submit"]').click(),
]);
// This should be a reliable, site-specific sign that login succeeded.
await page.waitForSelector('[data-member-nav]', { timeout: 15000 });
await page.goto(memberUrl, { waitUntil: 'networkidle2' });
// Verify the protected page itself, not merely that navigation completed.
await page.waitForSelector('[data-member-content]', { timeout: 15000 });
await page.screenshot({ path: 'member-page.png', fullPage: true });
} finally {
await browser.close();
}
The selectors input[name="username"], input[name="password"], button[type="submit"], [data-member-nav], and [data-member-content] are examples only. Inspect the actual site’s permitted interface and choose selectors that match it. Some sites use a different submit event, redirect, or authentication flow.
Why verify twice?
A successful goto() only means Puppeteer completed navigation according to its wait condition. It does not prove the server granted access: the response might be a login page, an access-denied page, or an empty shell. Check for a member-only element after login and again on the page being captured. You can also inspect the final URL or assert that a known login form is absent.
3. Choose the authentication method that fits the site
| Authentication type | How to handle it | Important detail |
|---|---|---|
| HTML login form | Navigate to the form, fill its fields, submit it, then wait for a site-specific post-login signal. | Selectors and any MFA or other permitted steps vary by site; do not guess them. |
| HTTP authentication | Use await page.authenticate({ username, password }) before navigating. |
This is for HTTP authentication, not a generic way to submit an HTML form. Puppeteer notes that it enables request interception, which can affect performance. [Page.authenticate reference] |
| Existing cookie-backed session | Set valid cookies in the browser context before navigating to the protected page. | Cookie domain, path, security attributes, and expiration must match. Some sites also depend on local storage or server-side session state. |
HTTP authentication
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.authenticate({
username: process.env.HTTP_USERNAME,
password: process.env.HTTP_PASSWORD,
});
await page.goto(process.env.MEMBER_URL, { waitUntil: 'networkidle2' });
await page.waitForSelector('[data-member-content]');
await page.screenshot({ path: 'member-page.png', fullPage: true });
} finally {
await browser.close();
}
Restore an existing cookie session
Use this when you already have a valid session obtained through an authorized process. Cookie APIs belong to the browser or browser context; Puppeteer’s page-level cookie APIs are deprecated. Browser contexts isolate cookies and other storage, which is useful when handling separate accounts or jobs. [Puppeteer cookies guide; Page API reference]
import puppeteer from 'puppeteer';
// Supply a JSON array of valid cookie objects via a protected secret source.
const cookies = JSON.parse(process.env.SESSION_COOKIES ?? '[]');
const browser = await puppeteer.launch();
try {
const context = await browser.createBrowserContext();
await context.setCookie(...cookies);
const page = await context.newPage();
await page.goto(process.env.MEMBER_URL, { waitUntil: 'networkidle2' });
await page.waitForSelector('[data-member-content]', { timeout: 15000 });
await page.screenshot({ path: 'member-page.png', fullPage: true });
} finally {
await browser.close();
}
Cookie objects must contain the fields required by the target site and Puppeteer version. Validate that cookies apply to the requested domain and have not expired. Never commit session cookies: they can grant account access.
4. Wait for the right content before capture
Puppeteer’s screenshot guide uses networkidle2 as a navigation wait condition. Network quiet is not a universal guarantee that a member page is ready: analytics, polling, or long-lived requests can keep traffic active, while client-rendered content can appear after navigation. A site-specific content selector is often a more meaningful readiness check. [Puppeteer screenshot guide]
For a site that renders content after a known delay or action, wait for the actual condition instead of adding an arbitrary long sleep:
await page.goto(memberUrl, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-member-content]', { timeout: 15000 });
// If the page has a known, legitimate load trigger, perform it here.
await page.screenshot({ path: 'member-page.png', fullPage: true });
Long pages may load images or sections as they are scrolled. Check the resulting image for missing content. If the site uses scroll-triggered lazy loading, trigger its normal loading behavior and wait for the required content before capture. fullPage: true requests a full-page image; it does not guarantee that every application-specific lazy loader has run.
5. Full page, element, and screenshot options
| Need | Approach |
|---|---|
| Entire document | page.screenshot({ path: 'page.png', fullPage: true }) |
| Visible viewport | page.screenshot({ path: 'viewport.png' }); fullPage defaults to false. |
| One component | Find its element and call elementHandle.screenshot({ path: 'component.png' }). Puppeteer scrolls the element into view if needed. [ElementHandle screenshot reference] |
| Specific region | Use the screenshot clip option when you want a crop. In Puppeteer 25.12.0, captureBeyondViewport defaults to false without a clip and true with a clip. Check the reference for the version you install. |
For example, an element screenshot is useful when the member page contains a long article but you only need its main content panel:
const article = await page.waitForSelector('[data-member-article]');
if (!article) throw new Error('Member article was not found');
await article.screenshot({ path: 'member-article.png' });
Use Puppeteer’s ScreenshotOptions reference for the complete option set supported by your installed version, including output configuration. Set the output path and format deliberately for your workflow.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot shows the login page | Credentials, form submission, or session restoration failed; navigation itself completed. | Check the final URL and wait for a member-only selector before capture. Verify credentials and the site’s permitted login flow. |
| Login wait times out | The submit selector is wrong, submission did not navigate, or the site uses a different flow. | Inspect the actual page structure and wait for the site’s post-login state. Do not assume every form causes a navigation. |
| Protected content selector times out | Access was denied, the selector does not match, or client rendering is incomplete. | Check the page title, final URL, and visible state. Confirm the selector against the actual page and use an appropriate readiness condition. |
| Cookie session is not recognized | Cookies expired, have the wrong domain/path, or the site requires additional state. | Restore current authorized cookies with matching attributes. Check whether the site also uses local storage or server-side state. |
| Capture hangs waiting for network idle | Background requests or polling prevent network idleness. | Use a navigation condition suitable for the site, then wait for a specific content selector. |
| Images or lower sections are missing | Content loads lazily or after a site-specific trigger. | Trigger the site’s normal loading behavior, wait for the content, and inspect the saved image. |
| Browser remains after an error | Cleanup did not run on a failed path. | Put capture work inside try and close the browser in finally, as in the examples. |
| Very tall screenshot uses too much memory or fails | Full-document output dimensions and page complexity exceed what the browser or environment handles reliably. | Capture a relevant element or smaller sections, reduce unnecessary page content if appropriate, and validate limits in your runtime. There is no universal maximum independent of browser build and page content. |
7. Performance, reliability, and cost
Puppeteer runs a browser process, so resource use depends on the page, browser build, output dimensions, and environment. Reuse a browser where appropriate, isolate account state with separate browser contexts, and always close contexts and browsers when a job finishes. Very long pages can increase memory and output size; element capture can avoid rendering an unnecessarily large document.
For reliable captures, treat authentication and content verification as explicit steps, use bounded timeouts, record useful failure context without logging secrets, and make cleanup unconditional. The research does not establish a benchmark or universal resource limit.
Puppeteer is software you run, so there is no per-screenshot ScreenshotNeo charge for the DIY code. Your operating cost depends on the compute and browser infrastructure you provide; size it for your workload. If you want a hosted API instead, see the option below.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A single request captures a URL as PNG, JPEG, WebP, or PDF. This endpoint accepts a URL and API key; the example uses a public page. The API call does not authenticate to a members-only account, so use it only for pages accessible to the request or another supported authorized setup.
See the ScreenshotNeo API documentation for request options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
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 for 1,000 free screenshots a month, with no card.
FAQ
How do I take a full-page screenshot after logging in with Puppeteer?
Complete the site’s authorized login flow, verify a member-only element is present, then call page.screenshot({ path: 'page.png', fullPage: true }).
Can I use page.authenticate() for a website login form?
No. It supplies credentials for HTTP authentication. For an HTML form, use that site’s form flow and verify the resulting authenticated state.
Will fullPage: true load every lazy image?
Not necessarily. Lazy loading behavior depends on the site; verify the output and trigger the site’s normal loading behavior when needed.
Can I take a screenshot of just the member article?
Yes. Use ElementHandle.screenshot() on the article element instead of capturing the entire document.


