Free tools Windows power users keep installed
One-click scans. No signup required.
Wait for an application-specific post-login condition—not merely the browser’s load event—before calling page.pdf(). In practice, that means combining a URL transition, authenticated-only locator, or successful authentication response with a second signal proving that the report or page content for the PDF has rendered.
This distinction matters because modern applications often fetch account data after navigation finishes. The workflow below shows a complete Java pattern, explains which wait to choose, secures reusable authentication state, and covers PDF rendering options and failure recovery.
The reliable sequence: authenticate, verify access, verify content, print
A robust flow has separate checkpoints:
- Open a new, isolated browser context.
- Submit credentials while waiting for the application’s authentication signal.
- Confirm that an authenticated-only page or control is present.
- Wait for the specific report, table, chart, or heading that must appear in the PDF.
- Choose print or screen media and explicit PDF options.
- Call
page.pdf()and close the browser.
Playwright’s navigation guidance explains that a page can continue fetching and rendering data after the load event. The Page API also cautions that broad network-quiet checks are not a substitute for an assertion about the result you need. Your readiness condition must therefore describe the target application’s successful state.
A Java example that waits for login and a report
The following is an illustrative Playwright for Java pattern. Replace the URL, accessible labels, route, and report heading with values from your application. Confirm method overloads against the Playwright Java dependency version in your project.
import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class AuthenticatedPdf {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://example.com/login");
page.getByLabel("Email").fill(System.getenv("APP_USER"));
page.getByLabel("Password").fill(System.getenv("APP_PASSWORD"));
// The callback starts the action while waitForURL observes its result.
page.waitForURL("**/account", () -> {
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in")).click();
});
// Authentication succeeded, but the report may still be loading.
page.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Monthly report")).waitFor();
page.pdf(new Page.PdfOptions()
.setPath(Paths.get("report.pdf"))
.setFormat("A4")
.setPrintBackground(true));
browser.close();
}
}
}
The URL wait and its triggering action are deliberately combined. This avoids a race in which the click starts navigation before a separately configured wait begins. The heading wait is a second, report-specific checkpoint; it prevents a PDF containing only the account shell or a loading state.
Choosing the right post-login signal
URL transition
Use page.waitForURL() when a successful login reliably redirects to an account route such as /account. A URL proves that the navigation reached the expected route, but it does not prove that asynchronous report data, charts, or lazy images are ready. Add a content locator whenever the PDF depends on those elements.
Authenticated-only locator
Single-page applications often authenticate without changing the URL. Wait for a stable element that unauthenticated users cannot see: a “Sign out” button, account heading, navigation item, or user menu. Prefer accessible roles and labels that reflect the real interface rather than brittle CSS classes.
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in")).click();
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign out")).waitFor();
page.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Monthly report")).waitFor();
Successful authentication response
If login is represented by an API call, wait for the specific endpoint and a successful status. A response predicate should identify both the expected URL and status; do not treat any response as proof of authentication.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteResponse auth = page.waitForResponse(
response -> response.url().endsWith("/api/session")
&& response.status() == 200,
() -> page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in")).click());
// The response proves the session request succeeded. The locator below
// proves that the content intended for the PDF has rendered.
page.getByTestId("report-ready").waitFor();
The exact endpoint and readiness element are application-specific. A successful session response can arrive before the report request or UI render completes.
Rank #2
Load states and network quiet
waitForLoadState() can be useful for a navigation lifecycle, but it is not a universal “ready” test. Background connections, polling, analytics, and delayed API requests can keep a page active or make it appear quiet too early. Playwright marks networkidle as discouraged for testing and recommends assertions tied to the intended outcome. Avoid fixed sleeps as your primary strategy: a short sleep fails on slow runs, while a long one wastes time on fast runs.
Waiting for the actual PDF content
Define what “ready” means for the document, then wait for that condition. Examples include:
- A report heading and date range are visible.
- A table contains at least one expected row.
- A chart container has a rendered canvas or SVG.
- A loading indicator is hidden and an error banner is absent.
- A specific API response has returned the data used by the report.
page.getByTestId("report-spinner").waitFor(
new Locator.WaitForOptions().setState(WaitForSelectorState.HIDDEN));
page.getByRole(AriaRole.TABLE).waitFor();
page.getByText("June 2026").waitFor();
For lazy-loaded sections, scroll or otherwise trigger the application’s normal loading behavior before waiting for the section’s stable locator. For fonts and images, wait on application-visible completion signals where available; page.pdf() does not know whether your data pipeline is complete.
Reusable authentication state without leaking credentials
Browser contexts isolate cookies, local storage, IndexedDB, and related session data. For repeated runs, Playwright’s authentication guidance describes saving a storage state after signing in and creating later contexts from that state instead of logging in every time.
// After a verified login:
context.storageState(new BrowserContext.StorageStateOptions()
.setPath(Paths.get("playwright/.auth/user.json")));
// In a later run:
BrowserContext authenticated = browser.newContext(
new Browser.NewContextOptions()
.setStorageStatePath(Paths.get("playwright/.auth/user.json")));
Page page = authenticated.newPage();
Treat the state file like a password. It can contain cookies and headers that permit impersonation. Keep it outside source control, protect it in CI, use a dedicated test account, and remove or rotate it when the session is revoked. A saved state can expire, require multifactor verification, or be bound to a device. If the authenticated-only check fails, follow the site’s real reauthentication path rather than generating a PDF anyway.
PDF media and output options
Microsoft’s Page API states: “page.pdf() generates a pdf of the page with print css media.” That means @media print rules apply by default. If the PDF should look like the on-screen page, switch media before printing:
page.emulateMedia(new Page.EmulateMediaOptions()
.setMedia(Media.SCREEN));
page.pdf(new Page.PdfOptions()
.setPath(Paths.get("screen-style.pdf"))
.setFormat("A4")
.setLandscape(false)
.setPrintBackground(true)
.setPreferCSSPageSize(true));
Set the output deliberately. The documented format default is Letter; margins default to none; background graphics are off by default. Options include paper format, explicit width and height, margins, landscape orientation, page ranges, print backgrounds, and whether CSS @page size takes priority.
Recommended Free Tools
Page.PdfOptions options = new Page.PdfOptions()
.setPath(Paths.get("report.pdf"))
.setFormat("Letter")
.setMargin(new Margin()
.setTop("12mm")
.setRight("12mm")
.setBottom("14mm")
.setLeft("12mm"))
.setPageRanges("1-3")
.setPrintBackground(true)
.setPreferCSSPageSize(true);
page.pdf(options);
Use the margin and page-range values appropriate to your document. Headless navigation to an existing PDF URL is a separate limitation from generating a PDF with page.pdf(); this workflow renders an HTML page into a new PDF.
Common failures and targeted fixes
The PDF contains the login page
Cause: credentials were rejected, the session cookie was not retained, or the script printed before authentication completed. Fix: wait for a URL, authenticated-only locator, or successful auth response; assert that the login form is gone; then check a protected element before printing. Log status and page URL without logging passwords or cookie values.
The account shell appears but report data is missing
Cause: navigation completed while API data was still loading. Fix: add a report-specific heading, table, chart, or ready marker wait. If the app exposes a data endpoint, wait for its successful response and still verify the rendered element.
Rank #4
The URL wait times out
Cause: the app uses an SPA route, a different redirect, a trailing slash, or an intermediate verification page. Fix: inspect the actual route and use a matching URL pattern, or replace the URL wait with an authenticated-only locator and the application’s verification flow.
The response wait never resolves
Cause: the endpoint, method, or status predicate is wrong, or the request is made before the listener is installed. Fix: register the wait around the click as shown, match the actual URL, and allow the documented success status. Do not broaden the predicate so far that unrelated responses satisfy it.
Charts or backgrounds are absent
Cause: print CSS hides screen-only elements or background printing is disabled. Fix: choose Media.SCREEN when appropriate and set setPrintBackground(true). Verify the page’s print stylesheet and wait for chart rendering.
A reused state suddenly stops working
Cause: session expiry, revocation, device binding, or a new verification requirement. Fix: regenerate state through the supported login flow and keep the new file protected. Never commit it or paste it into logs.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance, reliability, and maintenance
- Reuse a verified storage state for suites that use the same test account, while isolating each test in its own context.
- Prefer one meaningful locator or response predicate over multiple arbitrary delays.
- Keep selectors semantic and stable; update them when the product’s accessible names change.
- Use explicit PDF dimensions and CSS page rules so output does not change unexpectedly with defaults.
- Capture diagnostic evidence on failure—URL, visible error text, and a screenshot—without recording secrets.
- Pin and periodically review the Playwright Java version; API signatures and defaults are versioned documentation details.
Or skip the browser setup
If you only need a clean screenshot or PDF of a public URL, ScreenshotNeo provides a website screenshot API and MCP server. A single request can return PNG, JPEG, WebP, or PDF; it is not a replacement for a private login flow that requires your own credentials, but it removes much of the browser orchestration for public pages.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Best Value
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}`);
See the ScreenshotNeo documentation for the full API. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan.
Sign up for the free ScreenshotNeo plan to try 1,000 screenshots a month with no card.
Frequently Asked Questions
Can I wait only for the login button click to finish?
No. The click starts authentication, but you should wait for a post-login signal and then a signal that the content destined for the PDF is ready.
Should I use a fixed five-second delay?
No. Fixed delays are unreliable across machines and network conditions. Use a URL, locator, or response predicate tied to the application’s actual state.
Does page.pdf() use screen styles automatically?
No. It uses print CSS media by default. Call emulateMedia with Media.SCREEN when the PDF should follow screen styling.
Quick Recap
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.

