How to Wait for Login Before Generating PDFs With Playwright for Java
Wait for a reliable post-login signal and the report content before calling Playwright’s page.pdf() in Java. Includes runnable patterns, PDF options, and fixes for common failures.

To avoid exporting a login screen or incomplete report, wait for two things before calling page.pdf(): a signal that authentication succeeded, and a signal that the specific content for the PDF is ready. A successful navigation or the browser’s load event alone does not prove that a modern application has finished fetching and rendering its data.
Choose an application-specific signal: a known post-login URL, an authenticated-only locator, or a successful authentication response. Then wait for a report-specific heading, table, chart, or other readiness indicator before printing. The example below uses Java with Playwright; replace the example URL, selectors, and readiness condition with ones from your application.
1. Use a post-login signal, then a report-ready signal
This complete example reads credentials from environment variables, waits for a redirect to /account as part of the sign-in click, waits for the report heading, and saves a PDF. Set APP_USER and APP_PASSWORD in the process environment before running it. The page labels, route, and heading are illustrative; they must match the target site.

import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class LoginReportPdf {
public static void main(String[] args) {
String username = System.getenv("APP_USER");
String password = System.getenv("APP_PASSWORD");
if (username == null || password == null) {
throw new IllegalStateException("Set APP_USER and APP_PASSWORD");
}
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
try {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.setDefaultTimeout(15_000);
page.setDefaultNavigationTimeout(30_000);
page.navigate("https://example.com/login");
page.getByLabel("Email").fill(username);
page.getByLabel("Password").fill(password);
// Pair the navigation wait with the action that triggers it.
page.waitForURL("**/account", () ->
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in")).click()
);
// Authentication can succeed before report data has rendered.
page.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Monthly report")).waitFor();
page.locator("[data-report-state='ready']").waitFor(
new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE)
);
page.pdf(new Page.PdfOptions()
.setPath(Paths.get("report.pdf"))
.setFormat("A4")
.setPrintBackground(true)
.setPreferCSSPageSize(true)
.setMargin("12mm", "12mm", "14mm", "12mm"));
context.close();
} finally {
browser.close();
}
}
}
}
Use the Playwright Java dependency and browser installation instructions for the version pinned by your project. The Java API overloads and available options are versioned, so check the matching Page API reference when upgrading. The key sequence is stable: trigger login, observe a meaningful success condition, wait for the PDF content, then export.
2. Pick a signal that proves what you need
Different wait conditions prove different things. Use the narrowest stable condition that represents success in the actual application.
| Signal | What it establishes | Use it when | What to add |
|---|---|---|---|
| Post-login URL | The browser reached a route matching the expected destination. | Sign-in reliably redirects to an account route. | A separate report-ready condition if data loads afterward. |
| Authenticated-only locator | A known account element is present or visible. | An SPA changes content without a URL transition. | A locator tied to the report’s actual content. |
| Successful auth response | A specific request returned the expected success response. | The app exposes a dependable login API request. | A UI readiness wait; a response does not prove rendering is complete. |
load or network quiet |
A broad browser lifecycle or traffic condition occurred. | Useful for navigation context, not sufficient as the only readiness test. | An application-level success and content condition. |
Redirecting login: wait around the click
A redirect can happen quickly. Start the URL wait as part of the triggering action so the browser does not navigate before the automation begins waiting:
page.waitForURL("**/account", () ->
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in")).click()
);
For apps that redirect to a route with variable path segments, use an appropriate URL pattern or predicate that distinguishes a successful account route from the login route. Do not treat any URL change as proof of success: failed logins can redirect to an error page, and applications sometimes return to the login route with a message.
Single-page app: wait for an authenticated locator
If the URL stays the same, wait for an element that appears only after authentication, such as an account menu or dashboard heading. Prefer accessible roles and labels when they are stable. If the page has an explicit loading indicator, waiting for it to disappear can help, but pair that with a positive condition that confirms the intended content appeared. A missing spinner alone may mean an error rendered or the spinner never mounted.
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Account menu")).waitFor();
page.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Monthly report")).waitFor();
API-driven login: wait for the right response
When the application’s successful login is represented by a known request, match the endpoint and success status rather than waiting for arbitrary traffic to stop. Confirm the endpoint and response shape from the application’s documented or observed behavior; do not copy a guessed URL into production automation.
Response authResponse = page.waitForResponse(
response -> response.url().contains("/api/session")
&& response.status() == 200,
() -> page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in")).click()
);
// A successful response is not necessarily a rendered, ready report.
page.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Monthly report")).waitFor();
Be specific about status and endpoint. A broad predicate can match an unrelated background request and let the workflow continue too early. If the app returns a successful response for invalid credentials or requires a second verification step, verify the application’s actual success state as well.
3. Wait for the content that will be printed
Authentication and document readiness are separate milestones. Once logged in, the page may fetch report data, load charts, apply filters, or render a table asynchronously. Wait for the condition that means the PDF’s intended content is complete. Examples include a report title plus a ready-state marker, the expected table rows, or a chart’s rendered state.
Be careful with conditions that are technically true but too weak: a heading may render before its data; a container may exist while empty; and a request may succeed while client-side rendering is still underway. When completeness matters, assert meaningful content such as a known account name, date range, row count, or completion marker. The right check depends on the application.
Playwright auto-waits for many actions, but that does not define when application data is ready for export. The navigation guide explains that pages can continue fetching and rendering after the load event. The Page API discourages using networkidle as a general test readiness condition; long-lived connections or background traffic can make it misleading. Avoid fixed sleeps as the main synchronization mechanism: a short delay can be too short under load and unnecessarily long when the page is fast.
4. Configure PDF output deliberately
page.pdf() renders with print CSS media by default. That means @media print rules affect the PDF. If you need the screen layout instead, emulate screen media before calling PDF:

page.emulateMedia(new Page.EmulateMediaOptions().setMedia(Media.SCREEN));
page.pdf(new Page.PdfOptions().setPath(Paths.get("report.pdf")));
Set the output choices explicitly where they affect the result. Playwright documents Letter as the default paper format, zero margins by default, and background printing off by default. CSS page size is not preferred by default. Confirm exact methods against the version in your build.
| Choice | Typical decision | Relevant option |
|---|---|---|
| Page dimensions | Select standard paper or explicit width and height. | setFormat("A4"), or width and height options. |
| Orientation | Use landscape for wide tables or charts. | setLandscape(true). |
| Margins | Reserve space for readable content, headers, or footers. | setMargin(...). |
| Backgrounds | Enable when color fills or background graphics carry meaning. | setPrintBackground(true). |
| CSS page size | Honor site-defined @page dimensions when appropriate. |
setPreferCSSPageSize(true). |
| Page ranges | Export only selected pages if the document is large. | setPageRanges("1-3"). |
| Headers and footers | Add print metadata when the document needs it. | Header/footer template options in the Page API. |
Print styles can hide navigation or restructure the page. Inspect the application’s @media print and @page rules if output differs from the screen. If fonts, images, lazy-loaded sections, or charts are required, include their readiness in the application-specific wait logic before export. The PDF method creates output from the current page; it does not decide whether the page’s application data is complete.
5. Reuse authentication state safely
For repeated report jobs, signing in every time can add time and introduce another failure point. Playwright supports saving and reusing browser authentication state. Browser contexts isolate session state, and saved state can include cookies and other sensitive browser data. Treat a storage-state file as a credential: keep it out of source control, restrict access, and store it using the secret-handling practices appropriate to the environment. Use a dedicated protected test account where possible.
A saved session can expire, be revoked, require additional verification, or be bound to a particular flow. Therefore, do not assume that successfully loading the report URL means the state is still authenticated. Check an authenticated-only signal; if it fails, handle the site’s real reauthentication or verification path. The official Playwright authentication guide describes saving and reusing state. Keep the state file outside the repository and avoid printing its contents into logs.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The PDF contains the login page. | The wait observed navigation or page load, not successful authentication; login may have failed or state expired. | Wait for an authenticated-only URL or locator. Check for validation errors and reauthentication steps before proceeding. |
| The report title appears, but the PDF is empty or incomplete. | The title rendered before report data or charts finished. | Add a report-specific ready signal, such as a completion marker or expected content. Do not rely on the title alone. |
waitForURL times out. |
The app uses an SPA, redirects elsewhere, or login was unsuccessful. | Inspect the actual route and login outcome. For no-navigation apps, wait for an authenticated locator or the specific auth response. |
| The response wait passes, but the PDF is still incomplete. | The API response arrived before the UI rendered it. | Keep the response wait for authentication if useful, then wait separately for rendered report content. |
| The wait times out on an element. | The selector or accessible name does not match, the element is hidden, or the app did not reach that state. | Verify the locator against the real page and choose whether existence or visibility is required. Diagnose login errors before increasing timeouts. |
| Colors or backgrounds disappear. | Background printing is off by default, or print CSS removes the styling. | Enable print backgrounds and inspect the page’s print styles. Use screen media only when that is the intended design. |
| PDF pages have unexpected size or clipping. | Paper format, margins, orientation, or CSS @page sizing differs from expectations. |
Set dimensions and margins explicitly, choose orientation, and decide whether CSS page size should take priority. |
| Automation hangs waiting for network idle. | The app maintains background requests or open connections. | Replace network quiet with an app-level readiness condition. |
| A reused session intermittently redirects to sign-in. | The session expired, was revoked, or requires a new verification step. | Validate authenticated state before capture and renew it through the application’s supported login flow. |
7. Performance, reliability, and cost considerations
For reliable automation, use one browser context per isolated session or job when session separation matters, and close contexts and browsers after the work. Reusing a browser process can avoid repeated startup overhead in a long-lived worker, while keeping contexts separate prevents accidental cookie sharing. The right lifecycle depends on whether your runner is a short command or a persistent service.
Wait on the smallest meaningful condition. A broad wait can slow every run, while a weak condition creates bad PDFs and retries. Use finite timeouts that reflect the environment, and report which stage failed: navigation, authentication, report readiness, or PDF generation. This makes failures diagnosable without logging credentials. For large reports, consider the PDF’s page count, image weight, and selected page range; exporting fewer pages can reduce unnecessary output when only a subset is needed.
Cost depends on where the browser runs and how often the workflow retries; the Playwright API itself does not prescribe a hosting price. Account for browser compute, job duration, storage, and failed attempts in your own environment. Authentication state reuse can reduce repeated login work, but it adds secret-storage and session-expiry handling. Build retry logic around transient failures only, and re-check login and document readiness on each attempt instead of blindly exporting after a timeout.
Or skip the browser setup
If the task is capturing a public page as an image or PDF rather than automating an authenticated account workflow, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns an image or PDF; its PDF options include paper size, margins, landscape, and page ranges. It does not replace Playwright login automation for private account pages. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Should I wait for DOMContentLoaded before signing in?
It can be appropriate to wait for the login form to be usable, but it does not prove sign-in succeeded. Wait for a post-login signal after submitting credentials.
Can I generate a PDF from an already open PDF URL?
This workflow generates a PDF from a rendered page using page.pdf(). Playwright’s Page API notes a headless-mode limitation around navigating to PDF documents; that is distinct from printing a web page to PDF.
Does page.pdf() return a file path?
It can save to a path when configured with the path option, or return PDF bytes for handling in memory. Choose based on whether the next step needs a local file or a buffer.
Is one authenticated locator enough?
It can establish that the account UI appeared, but it may not establish that the report is complete. Add a content-specific condition whenever the page renders the document data asynchronously.


