Use one Playwright browser and BrowserContext, create a separate Page for each URL, and run a bounded number of capture tasks in parallel. Save every result to a unique, sanitized path, wait for the page state your site needs, and close each Page in a finally block. Add setFullPage(true) when you need the entire scrollable document rather than the current viewport.
The complete Java pattern below captures several URLs concurrently, writes full-page PNG files, and prevents one failed page from silently mixing with another job’s output.
Complete Java example for bulk screenshots
The official Playwright Pages model allows multiple pages in one BrowserContext. Reusing a browser process avoids launching a new Chromium instance for every URL, while a fixed executor limits how many pages are active at once.
import com.microsoft.playwright.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
public class BulkScreenshots {
public static void main(String[] args) throws Exception {
List<String> urls = List.of(
"https://example.com/one",
"https://example.com/two",
"https://example.com/three");
Path outputDir = Paths.get("screenshots");
Files.createDirectories(outputDir);
try (Playwright pw = Playwright.create()) {
Browser browser = pw.chromium().launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().setViewportSize(1440, 900));
ExecutorService pool = Executors.newFixedThreadPool(3);
List<Future<?>> jobs = new ArrayList<>();
for (int i = 0; i < urls.size(); i++) {
final int index = i;
jobs.add(pool.submit(() -> {
Page page = context.newPage();
try {
page.navigate(urls.get(index));
page.waitForLoadState();
Path path = outputDir.resolve(String.format("%03d.png", index));
page.screenshot(new Page.ScreenshotOptions()
.setPath(path)
.setFullPage(true)
.setScale(ScreenshotScale.CSS));
} finally {
page.close();
}
}));
}
for (Future<?> job : jobs) job.get();
pool.shutdown();
context.close();
browser.close();
}
}
}
Replace the example URLs with your input list. The viewport is 1,440 by 900 CSS pixels, and ScreenshotScale.CSS keeps one output pixel per CSS pixel. The loop assigns a stable numeric name, but production jobs should derive names from a URL slug plus a collision-resistant identifier.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteWhat each part does
- One Playwright instance and browser: the process is launched once for the batch.
- One BrowserContext: pages share context settings such as viewport and can be created and closed cheaply compared with a new browser process.
- One Page per URL: each navigation and screenshot has isolated page state.
- Fixed executor: the pool of three bounds simultaneous work. Increase it only after observing CPU, memory, network and target-site behavior.
finallycleanup: a navigation or screenshot exception does not leave the page open.Future.get(): the main thread waits for every submitted job and receives task failures.
Prepare the input and output safely
Use deterministic, collision-safe filenames
Never use a raw URL as a filesystem name. Query strings, slashes, Unicode characters and reserved names vary by operating system. A practical naming function keeps a readable slug and appends a stable index or generated ID:
static String fileStem(String url, int index) {
String slug = url.replaceFirst("^https?://", "")
.replaceAll("[^A-Za-z0-9._-]+", "_");
if (slug.length() > 100) slug = slug.substring(0, 100);
return String.format("%03d_%s", index, slug);
}
Resolve the result beneath a directory you control and create that directory before submitting work. If two URLs can normalize to the same slug, retain the index or append a UUID.
Record failures for retry
Do not abort the whole batch because one URL times out or returns an unexpected page. Wrap each task in a catch block that records the URL and exception, then let the other futures complete. Retry only failed items, preferably with a limit, rather than rerunning successful captures.
Choose the capture you actually need
Viewport or full page
Without setFullPage(true), Playwright captures the current viewport. With it, Playwright captures the complete scrollable document as if the page could fit on a very tall screen. Full-page images can be extremely tall, so use viewport mode for above-the-fold monitoring and full-page mode for archives, audits and visual comparisons of an entire document.
// Current viewport
page.screenshot(new Page.ScreenshotOptions().setPath(path));
// Entire scrollable document
page.screenshot(new Page.ScreenshotOptions()
.setPath(path)
.setFullPage(true));
PNG, JPEG or WebP
| Format | Use it when | Trade-off |
|---|---|---|
| PNG | You need lossless text, UI edges or pixel comparisons | Usually larger files |
| JPEG | Photographic pages where small files matter | Lossy compression; quality must be chosen |
| WebP | You want modern compression for supported consumers | Confirm that every downstream tool accepts WebP |
PNG is the default. For JPEG, set the quality in the screenshot options and use a .jpg path. WebP is supported by the Java API; use a .webp path and verify your image pipeline.
CSS scale or device scale
ScreenshotScale.CSS keeps one pixel per CSS pixel and produces predictable dimensions. ScreenshotScale.DEVICE follows device pixels, which is useful for high-DPI fidelity but can create larger images. Pick one scale for the entire batch if you intend to compare images.
Capture a component instead of the page
For a card, chart or navigation bar, prefer a locator screenshot:
Locator chart = page.locator("[data-testid='sales-chart']");
chart.screenshot(new Locator.ScreenshotOptions()
.setPath(outputDir.resolve("sales-chart.png")));
A locator expresses the element you want and is preferred over the discouraged ElementHandle screenshot API. If the selector matches nothing, wait for the component or treat that URL as a failed job.
Make captures repeatable
Pages that animate, personalize content or load data late can produce different pixels on every run. Apply the controls that match your purpose:
- Wait for readiness:
waitForLoadState()waits for the page load state, but a single-page app may need an additional selector wait such aspage.locator("main").waitFor()or a short, justified delay. - Disable animations: inject a stylesheet that sets transitions and animations to none when motion is not part of the test.
- Mask dynamic regions: locator screenshots support masking; mask timestamps, avatars, rotating ads or user-specific data.
- Inject CSS: hide carets, scrollbars or known volatile elements without changing application code.
- Fix the environment: set an explicit viewport and, when relevant, a consistent timezone, locale, user agent and authentication state in the BrowserContext.
Use a selector that proves the useful content is present rather than assuming network idle means the UI is complete. Some applications keep long-lived connections open, so waiting indefinitely for network idle can stall a batch.
Rank #3
Bound concurrency without losing reliability
A larger pool is not automatically faster. Each page consumes memory, CPU, sockets and bandwidth, and the target site may throttle parallel requests. Start with a small fixed pool, measure completion time and failure rate, then adjust. The official documentation publishes no throughput benchmark, so there is no responsible universal value for URLs per second.
One page per task versus a page pool
| Approach | Advantages | Costs and risks |
|---|---|---|
| One new Page per URL | Simple isolation; cleanup is explicit; easy to map failures to URLs | Page creation and navigation overhead for every item |
| Small reusable Page pool | Can reduce repeated setup for very large batches | Requires strict reset of cookies, storage, route handlers and page state between jobs |
For most Java batch jobs, create a page for each task and close it in finally. Reuse the BrowserContext for shared settings, but do not let one job’s mutable page state leak into another.
Navigation timeouts and retries
Set a deliberate timeout appropriate to your pages rather than allowing a hung URL to occupy a worker forever. On timeout, log the URL, exception type and elapsed time. A retry can help with transient network errors; it cannot fix a consistently blocked page, a login requirement or a bot challenge. Keep retry counts bounded and preserve the original failure in your report.
Advanced options for production batches
Authenticated and customized pages
Create the BrowserContext with the viewport and other settings your site requires. Add cookies, headers or an authenticated storage state before opening pages when the screenshots must represent a signed-in experience. Keep secrets out of filenames and logs.
In-memory processing
If you upload images directly or post-process them, omit setPath. The screenshot method returns a byte array that can be sent to storage or an image library. Writing to disk is simpler and easier to inspect; memory output avoids temporary files but increases heap pressure for large full-page captures.
Rank #4
PDF is a different output
A screenshot is a raster image. If the requirement is paginated, selectable document output, use Playwright’s PDF capability instead of stretching a full-page image. Keep the two workflows separate so image consumers do not receive a PDF unexpectedly.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Troubleshooting common failures
The output is only the top of the page
Cause: the default is viewport capture. Fix: add .setFullPage(true) and verify that lazy-loaded content has appeared before capture.
Files overwrite one another
Cause: multiple jobs resolve to the same path. Fix: include a stable index or collision-resistant ID, sanitize the URL, and never share a constant filename between workers.
The batch hangs on one URL
Cause: navigation or a readiness wait has no effective bound. Fix: set navigation and operation timeouts, record the failure, close the page in finally, and continue or retry according to your policy.
Pages show different content on every run
Cause: animations, rotating data, personalization or late requests. Fix: wait for a meaningful selector, disable animations, mask dynamic regions and use a consistent context configuration.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
Memory usage keeps climbing
Cause: too many concurrent full-page captures, unclosed pages or retaining screenshot byte arrays. Fix: lower the executor size, close every Page, stream or write results promptly, and avoid retaining completed images in a large list.
A locator screenshot fails
Cause: the selector did not resolve or the element is not visible. Fix: wait for the locator, confirm the selector against the loaded DOM, and decide whether a missing component is a retryable failure or an expected variant.
Or skip the browser setup
If you need an HTTP endpoint rather than managing Chromium workers, ScreenshotNeo returns a screenshot or PDF from one request. Its cleanup steps accept cookie and consent banners and remove more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
cURL (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
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}`);
ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. Every plan includes its features; the Free plan provides 1,000 screenshots per month without a card, and paid plans start at $5 for 3,000 shots. Sign up for the free plan.
Recommended Free Tools
Frequently Asked Questions
Can one BrowserContext use different viewport sizes in the same batch?
A BrowserContext has shared settings, so use separate contexts when jobs require different viewport configurations.
Should I use CSS or device scale for visual regression?
Use CSS scale for stable CSS-pixel dimensions; choose device scale only when high-DPI output is the requirement, and keep the choice consistent across runs.
Where should I put authentication data?
Load cookies or authenticated storage into the context before creating pages, and keep credentials out of source control, filenames and logs.
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.

