Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a normal HTML login form, get the username and password in your Node.js process, open the authorized login page, locate the controls with Puppeteer locators, fill them, submit, and wait for the site’s real authenticated signal. Use page.authenticate() only for an HTTP authentication challenge; it does not type into ordinary username and password fields.

The example below uses environment variables so secrets are not placed in source code. Replace its URL, selectors, and success condition with those from the application you are authorized to automate.

Complete Puppeteer example

This ES-module script handles a conventional form that navigates to another document after submission:

import puppeteer from 'puppeteer';

const username = process.env.LOGIN_USERNAME;
const password = process.env.LOGIN_PASSWORD;

if (!username || !password) {
  throw new Error('Set LOGIN_USERNAME and LOGIN_PASSWORD before running');
}

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.test/login', { waitUntil: 'domcontentloaded' });

  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(),
  ]);

  await page.locator('[data-testid="account-menu"]').wait();
  console.log('Login succeeded');
} finally {
  await browser.close();
}

Puppeteer’s page-interactions guide recommends locators because they wait for an element to be present and ready for the requested action. See the page interactions guide. The URL, selectors, and data-testid in this listing are placeholders, not universal values.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Before you automate

Install and choose a module format

In a new project, install Puppeteer with npm install puppeteer. The package downloads a compatible browser unless your project is configured to use an existing executable. The script above uses modern ES modules; set "type": "module" in package.json, or convert the imports and top-level awaits to your project’s module style.

Supply credentials at runtime

Set LOGIN_USERNAME and LOGIN_PASSWORD through your deployment platform’s secret injection or your local shell. For example, on a Unix-like shell:

LOGIN_USERNAME='alice@example.com' LOGIN_PASSWORD='use-a-secret-store' node login.js

The Puppeteer API documentation does not prescribe a particular secret-management product. Keep the values out of source control, command history where practical, logs, screenshots, crash dumps, and error messages. Never print the password to diagnose a failed run.

Find and fill the right controls

Prefer stable selectors

Use a field’s name, associated label, accessible role, or a test ID supplied by the application. Puppeteer selectors support CSS and Puppeteer-specific syntax for text, accessibility attributes, XPath, and shadow DOM; the available selector should reflect the page’s actual markup. A selector such as input:nth-of-type(2) is fragile because an unrelated field can change the position.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.locator('input[name="email"]').fill(username);
await page.locator('input[autocomplete="current-password"]').fill(password);

If the site exposes labels, an accessibility-oriented locator can be clearer:

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
await page.getByLabel('Email address').fill(username);
await page.getByLabel('Password').fill(password);

Confirm the selector identifies the intended control. A locator can wait for readiness, but it cannot tell whether you selected the username field, whether the credentials are valid, or whether the application accepted them.

When the form is in an iframe

A document inside an iframe has its own browsing context. Locate the frame, then use that frame’s page-like API (or a frame locator in the Puppeteer version you use) to find and fill the controls. Inspect the frame URL and name rather than assuming the first iframe is the login form. Cross-origin policy still applies: automation does not grant permission to read an unrelated origin’s content.

fill() versus keyboard typing

Use fill() for ordinary fields

Locator.fill() accepts a string for inputs, textareas, selects, and contenteditable controls. It is the concise choice for normal form assignment and includes locator waiting behavior. Its reference is at pptr.dev’s Locator.fill() documentation.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use typing APIs when key events matter

page.type() and page.keyboard.type() generate keyboard and input events character by character. Use them only when the application genuinely depends on those events—for example, a widget that validates each keystroke or masks input in a way that does not respond correctly to a direct fill.

await page.locator('input[name="username"]').click();
await page.keyboard.type(username, { delay: 20 });
await page.locator('input[name="password"]').click();
await page.keyboard.type(password, { delay: 20 });

The method references are Page.type() and Keyboard.type(). Do not add an arbitrary delay as a substitute for waiting on a real condition.

Submit and determine whether login succeeded

Full document navigation

A click and navigation can race if started separately. Start both promises together:

await Promise.all([
  page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
  page.locator('button[type="submit"]').click(),
]);

Puppeteer’s waitForNavigation() can resolve with a response, or with null for History API and anchor navigation. A resolved wait therefore does not prove that authentication worked; check a page-specific authenticated signal afterward.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Single-page applications

Many React, Vue, and other SPA logins update the current document instead of navigating. Do not wait forever for a URL load. Click, then wait for a reliable authenticated element, URL change, response, or application state:

await page.locator('button[type="submit"]').click();
await page.locator('[data-testid="account-menu"]').wait();

If the application changes the URL without a document load, wait for the expected URL or use a success element. A fixed sleep is weaker because network and server timing vary.

Detect rejected credentials

Wait for either the success signal or a visible error, with a bounded timeout, so a bad password does not look like a hung test:

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
const success = page.locator('[data-testid="account-menu"]');
const error = page.locator('[role="alert"]');

await page.locator('button[type="submit"]').click();
try {
  await Promise.race([
    success.wait(),
    error.wait(),
  ]);
} catch {
  throw new Error('Neither a success indicator nor a login error appeared');
}

if (await error.isVisible().catch(() => false)) {
  throw new Error('The application rejected the supplied credentials');
}

Adapt this logic to the application’s actual error markup. Do not include the username or password in the thrown message.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

HTTP authentication is a different flow

When the server responds with an HTTP authentication challenge, there may be no HTML form to fill. Use page.authenticate() before requesting the protected page:

await page.authenticate({
  username: process.env.HTTP_AUTH_USERNAME,
  password: process.env.HTTP_AUTH_PASSWORD,
});
await page.goto('https://example.test/protected', { waitUntil: 'domcontentloaded' });

The Page.authenticate() reference describes credentials for HTTP authentication and notes that request interception is enabled behind the scenes, which can have a performance cost. It is not a replacement for locating and filling an HTML login form.

Common failures and fixes

“Waiting failed” or a selector timeout

  • Cause: The selector does not match the rendered markup, the form has not appeared, or the control is inside an iframe or shadow root.
  • Fix: Inspect the live DOM, use a stable name, label, role, or test ID, wait for the relevant container, and switch to the correct frame or shadow-DOM selector.

The click does nothing

  • Cause: A disabled button, client-side validation error, overlay, or an event-dependent widget prevented submission.
  • Fix: Wait for the button to be actionable, inspect visible validation messages, dismiss only expected overlays, and try keyboard typing if the site requires key events.

waitForNavigation() never resolves

  • Cause: The login is an SPA transition or uses History API rather than a document navigation.
  • Fix: Remove the navigation wait and wait for the authenticated UI, URL, response, or state that the application actually produces.

The script reports success but the session is not authenticated

  • Cause: The chosen success selector exists on both anonymous and authenticated pages, or it appeared before the request completed.
  • Fix: Choose a signal unique to the signed-in state, then verify a protected page or account-specific request when permitted.

Credentials are rejected despite looking correct

  • Cause: Wrong tenant or login URL, a required hidden field, an account lock, an unhandled consent step, or a site-specific second factor.
  • Fix: Confirm the authorized login endpoint and required fields, inspect the application’s visible error, and implement the site’s permitted authentication flow. Do not attempt to bypass multi-factor authentication, bot checks, or access controls.

Unexpected blank pages or browser crashes

  • Cause: Browser launch configuration, resource limits, or a page that takes longer to load.
  • Fix: Capture only non-sensitive diagnostics, set purposeful navigation and locator timeouts, close the browser in a finally block, and tune concurrency to the available CPU and memory.

Reliability, performance, and privacy practices

  • Set a navigation timeout appropriate to the application and keep locator waits bounded; unlimited waits turn an outage into a stuck worker.
  • Reuse a browser process for a controlled batch, but create an isolated context or profile per account so cookies and local storage cannot leak between users.
  • Close pages, contexts, and the browser even on failure. The try/finally pattern in the example prevents orphaned Chromium processes.
  • Do not enable request or page logging that records form bodies, authorization headers, cookies, or screenshots containing credentials.
  • Capture diagnostics only after redacting secrets. A screenshot of a password field, autofill suggestion, or account identifier can itself be sensitive.
  • Respect the site’s terms and rate limits and automate only accounts and pages for which you have authorization.

Version and API notes

Puppeteer reference pages found for this guidance carry different version labels: locator documentation was shown as 25.12.0, Page.type() as 25.10.0, and Keyboard.type() as 25.9.0. Treat the behavior described here as documentation current on September 29, 2026, and check the API reference and installed package before pinning code to a specific version. The getting started guide covers project setup, while the Locator class reference lists locator behavior.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean image or PDF of a page rather than an interactive login workflow, ScreenshotNeo provides a one-request website screenshot API. It accepts the consent banner as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for all options. A cURL request is:

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

ScreenshotNeo also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. It supports full-page captures with lazy images loaded, CSS-selector element captures, device presets and custom viewports, dark mode, retina scale, PDF paper and page settings, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work.

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Create a free ScreenshotNeo account to try it without a card.

Frequently Asked Questions

Can Puppeteer read credentials from a browser prompt?

For a normal form, obtain the values in your Node.js process and fill the page controls. Browser JavaScript dialogs and HTTP authentication challenges are separate mechanisms.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use a fixed delay after clicking Login?

No. Wait for the application-specific success or error signal. Use navigation waiting only when the site actually performs a document navigation.

Can this automate a one-time passcode or MFA challenge?

Only implement an authentication flow that the account owner and site permit. This pattern does not bypass MFA, bot protection, or access controls.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.