ScreenshotNeo

BlogHow-to

How to Screenshot a Private Dashboard After OTP Login with Puppeteer

Log in to an authorized dashboard, complete its OTP challenge, wait for a reliable ready state, and capture a full page or a specific panel with Puppeteer.

By the ScreenshotNeo team4 October 20269 min read

To screenshot a private dashboard after OTP login with Puppeteer, automate the dashboard’s normal, authorized login flow in a browser, complete its one-time password challenge through the service’s supported process, wait for a dashboard-specific ready signal, then capture the page or a selected element. Use page.screenshot({ path: 'dashboard.png', fullPage: true }) for the full page or elementHandle.screenshot() for a chart, table, or panel.

OTP delivery, login selectors, and the best ready signal depend on the application. The example below uses placeholders you must adapt to an account and environment you are authorized to access. Puppeteer’s Page.authenticate() is for HTTP authentication; it is not a general-purpose way to submit an application login or OTP. See the Puppeteer screenshots guide and Page.authenticate() API reference.

1. Install Puppeteer

This runnable example uses JavaScript modules and Puppeteer’s bundled browser. Use a current Node.js release supported by the Puppeteer version you install. The login URLs and selectors are placeholders; replace them with the dashboard’s documented login flow and stable selectors.

mkdir dashboard-shot
cd dashboard-shot
npm init -y
npm install puppeteer

Save the following as screenshot-dashboard.mjs. It reads credentials and the OTP from environment variables so secrets do not need to be embedded in source code. Supply the OTP through your authorized process, such as a secure operator-provided value or an application-supported test mechanism. Do not try to bypass an OTP challenge or the service’s access controls.

2. Complete login and capture the dashboard

import puppeteer from 'puppeteer';

const dashboardUrl = process.env.DASHBOARD_URL ?? 'https://dashboard.example.com/';
const username = process.env.DASHBOARD_USERNAME;
const password = process.env.DASHBOARD_PASSWORD;
const otp = process.env.DASHBOARD_OTP;

if (!username || !password || !otp) {
  throw new Error('Set DASHBOARD_USERNAME, DASHBOARD_PASSWORD, and DASHBOARD_OTP');
}

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
  page.setDefaultTimeout(20_000);

  await page.goto(`${dashboardUrl.replace(/\\/$/, '')}/login`, {
    waitUntil: 'domcontentloaded',
  });

  // Replace these selectors with selectors from your own application.
  await page.locator('input[name="email"]').fill(username);
  await page.locator('input[name="password"]').fill(password);
  await page.locator('button[type="submit"]').click();

  // Wait for the OTP step, then enter the authorized, current OTP.
  await page.locator('input[name="otp"]').wait();
  await page.locator('input[name="otp"]').fill(otp);
  await page.locator('button[type="submit"]').click();

  // Use a stable element unique to the authenticated dashboard as its ready signal.
  await page.locator('[data-testid="dashboard-root"]').wait();

  // Optional: also wait for a key chart or table if the root appears before data.
  await page.locator('[data-testid="revenue-chart"]').wait();

  await page.screenshot({ path: 'dashboard.png', fullPage: true });
  console.log('Saved dashboard.png');
} finally {
  await browser.close();
}

Run it with environment variables supplied by your secret manager or shell. Avoid typing secrets directly into commands that may be recorded in shell history or process listings.

node screenshot-dashboard.mjs

Set DASHBOARD_URL, DASHBOARD_USERNAME, DASHBOARD_PASSWORD, and DASHBOARD_OTP in the process environment before running. For production automation, retrieve credentials and OTPs through the organization’s approved secret and authentication workflow.

3. Choose a reliable ready signal

Successful navigation does not prove the dashboard is ready. A redirect can finish before client-side rendering, data loading, or chart drawing. Puppeteer does not define one universal signal that means every private dashboard is fully rendered.

  • Wait for an application element: use a stable dashboard root, heading, or data table selector that only appears after authentication.
  • Wait for a critical panel: if the root renders before data, wait for a chart, table row, or application loading indicator to disappear as well.
  • Use navigation events for navigation: waiting for a navigation can help when the login submission causes a full page navigation. It does not replace a dashboard-specific rendered-state check.
  • Avoid treating network quiet as proof: dashboards that poll, use streaming connections, or load data in stages may never become network-idle, or may become quiet before the visible content is complete.

Choose selectors that are stable across routine UI changes. A test ID or semantic role is usually more robust than a long CSS path tied to layout details. If the application exposes an approved test-ready signal, prefer it.

4. Capture the full dashboard or one element

Full page

Set fullPage: true to capture the full page content, including content below the viewport. This is useful for a dashboard report or archive. Check the resulting image for fixed headers, sticky controls, and content that appears only after scrolling.

await page.screenshot({ path: 'dashboard.png', fullPage: true });

One chart, table, or panel

Locate the element after it has rendered, then use its screenshot method. Puppeteer scrolls an element into view as needed. If the page replaces the element during rendering, its handle can become detached and the screenshot will fail; locate it after the update or wait for the replacement first.

const chart = await page.waitForSelector('[data-testid="revenue-chart"]');
if (!chart) throw new Error('Revenue chart was not found');
await chart.screenshot({ path: 'revenue-chart.png' });

Clip a region

For a rectangular crop known in page coordinates, use the screenshot clip option. Make sure the crop coordinates and dimensions cover the intended content at the page’s current layout and scale.

await page.screenshot({
  path: 'dashboard-summary.png',
  clip: { x: 40, y: 80, width: 900, height: 600 },
});

Puppeteer’s ScreenshotOptions reference documents full-page capture, clipping, image type, and other output settings. Check the reference for the Puppeteer version installed in your project before relying on version-specific options.

5. Handle OTP and session state safely

There is no generic Puppeteer API that completes every application’s OTP challenge. The challenge may arrive through an authenticator, email, SMS, hardware key, or identity provider. Use the application’s supported flow and obtain the code through an authorized process. OTPs expire, may be single-use, and can trigger lockouts if repeatedly submitted incorrectly.

For repeat runs, ask the service owner whether it provides an approved test account, test OTP mode, or documented session mechanism. Puppeteer supports browser-level cookie operations, but whether a site accepts a restored session is site-specific. Do not copy or replay session cookies to evade a service’s OTP policy. The Puppeteer cookies guide is on the Next documentation path; check the stable API docs and your installed version before depending on it. Cookie attributes such as httpOnly, secure, and sameSite affect how cookies are used.

Keep credentials, OTPs, browser profiles, and captured screenshots out of source control and ordinary logs. A screenshot of a private dashboard may contain customer, financial, or operational data. Restrict access, retention, and storage to the requirements of the dashboard owner and your organization.

6. HTTP authentication is a different feature

page.authenticate({ username, password }) supplies credentials for HTTP authentication, such as a browser’s HTTP Basic Authentication challenge. It does not fill an application login form and does not generate or submit an OTP. Puppeteer documents that this method enables request interception internally, which can affect performance. Use it only when the server actually requests HTTP authentication, then handle the application’s own login separately if needed.

await page.authenticate({
  username: process.env.HTTP_AUTH_USERNAME,
  password: process.env.HTTP_AUTH_PASSWORD,
});

See the official API reference for the method’s scope and behavior.

7. Troubleshoot common failures

Symptom Likely cause Fix
OTP field never appears The login failed, the site uses a different challenge, or the selector is incorrect. Inspect the authorized login flow and current page state. Update the selector and handle the application’s actual challenge step.
OTP submission returns to login The code expired, was mistyped, or the account/session was rejected. Obtain a fresh code through the supported process. Avoid rapid retries that could trigger account protections.
Screenshot shows a login page The login did not complete, or the script captured before the authenticated redirect/render finished. Check for an authenticated dashboard-only element before capture and fail if it never appears.
Dashboard shell appears but data is missing The app root rendered before asynchronous data or charts finished loading. Wait for a critical panel, data row, or app-specific loading completion state.
Element screenshot says the handle is detached The application replaced the node after the handle was obtained. Wait for the final render and reacquire the element immediately before calling screenshot().
Full-page image is unexpectedly large or incomplete The dashboard contains long or dynamically loaded content, or its layout changes during capture. Wait for required content, inspect scroll-dependent loading behavior, and consider capturing a specific panel or a clip.
Navigation wait times out The app uses client-side routing, persistent network activity, or a navigation event different from the one expected. Wait for the dashboard’s actual ready element instead of relying only on a navigation or network-idle event.
HTTP authentication does not log in to the dashboard The site uses an application form and OTP rather than HTTP authentication. Automate the supported form and OTP flow; use page.authenticate() only for an HTTP authentication challenge.
Works locally but fails in automation Environment differences, missing browser dependencies, blocked access, or unavailable OTP delivery. Compare browser version, network access, environment variables, and approved OTP delivery. Do not disable the site’s access controls to make the run pass.

8. Performance, reliability, and cost

  • Keep the browser lifecycle bounded: close pages and browsers in a finally block so failed login or capture does not leave browser processes running.
  • Reuse a browser carefully: a long-running worker can avoid repeated browser startup, but isolate each job’s page and authentication state. Do not share a logged-in context across unrelated users or tenants.
  • Wait for the needed state: arbitrary long sleeps waste time and still do not guarantee readiness. Prefer the application’s stable state and use a bounded timeout with a clear failure message.
  • Limit output size: full-page captures of long dashboards can consume substantial memory and storage. Capture only the relevant element when that meets the need.
  • Budget for authorized OTP interaction: manual or external code delivery can dominate run time and make unattended jobs unsuitable unless the service owner provides an approved automation method.
  • Plan for sensitive output: encrypt or access-control screenshots and temporary browser data as appropriate; set retention and cleanup rules.
  • Cost: Puppeteer is an open-source browser automation library, but running it still uses your compute, storage, and operational time. Browser hosting, CI minutes, and secure OTP delivery may add costs depending on your environment.

Or skip the browser setup

If your authorized dashboard URL is accessible to ScreenshotNeo, its one-call API can return a screenshot without you managing Puppeteer or a browser. ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo; see the API documentation for request options and authentication. This captures a URL; it does not complete an OTP challenge or grant access to a private dashboard. Use it only where the page is accessible through an approved supported setup.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

For an authenticated private dashboard, verify that your approved access method is supported before choosing an API workflow. 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, and paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Can Puppeteer read an OTP from my phone automatically?

Not generically. OTP delivery and authorized automation depend on the identity provider and your organization’s policy. Use its supported mechanism rather than attempting to bypass the challenge.

Only if the service owner explicitly supports that approach for your use case. Puppeteer can manage cookies, but that does not guarantee the service will accept a restored session or waive its OTP requirements.

Should I capture the full page or just a dashboard panel?

Capture the full page for a complete dashboard record; capture an element when a particular chart or table is the deliverable and a smaller output is preferable.

Does ScreenshotNeo automate OTP login?

No such capability is established here. Its API captures a URL; it does not complete an OTP challenge. Confirm access compatibility for any private page before using it.