Selenium vs. Playwright for Screenshots of Lazy-Loaded Web Pages
Learn how to trigger lazy-loaded content, verify it is ready, and capture reliable full-page screenshots with Selenium or Playwright.
For screenshots of lazy-loaded or infinite-scroll pages, both Selenium and Playwright need you to trigger the content and verify it has appeared before capture. A full-page screenshot controls the capture extent; it does not guarantee that every image or list item has loaded. Playwright documents a fullPage option and guidance for scrolling a target into view to load more of an infinite list. Selenium also supports screenshots, but document readiness does not guarantee that JavaScript-driven content has finished appearing. There is no source-supported universal speed or reliability winner: choose based on your workflow, browser setup, and repeatability requirements.
1. What actually makes a lazy-loaded screenshot complete?
Lazy loading postpones work until content approaches the viewport or a page-specific trigger fires. A screenshot can therefore capture a blank image placeholder, an incomplete list, or content that has not yet been fetched—even when navigation has completed.
Use this sequence for either framework:
- Navigate to the page and establish any required state, such as authentication.
- Scroll through the relevant region or bring its trigger into view.
- Wait for a meaningful condition: the expected item exists, an image has loaded, a loading indicator has disappeared, or the expected count has been reached.
- Capture the viewport, element, or full page as required.
A fixed delay can be useful for a known animation or delayed trigger, but it is a weak readiness check on its own: network and rendering time vary. Prefer an observable condition tied to the content being captured.
2. Playwright: trigger loading, wait, then capture
Playwright documents page.screenshot({ fullPage: true }) for capturing the full scrollable page. Its scrolling guidance notes that actions usually scroll elements into view automatically, while explicitly scrolling a bottom target can force an infinite list to load more. The example below assumes the page has a footer and exposes the final expected item as an accessible text locator; adapt both to the application.
const { chromium } = require('playwright');
(async () => {
const url = process.env.PAGE_URL || 'https://example.com/catalog';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.getByText('Footer text').scrollIntoViewIfNeeded();
await page.getByText('Expected final item').waitFor({ state: 'visible', timeout: 15000 });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
})();
Install the package with npm install playwright and install a browser with npx playwright install chromium. The URL and selectors are examples, not universal selectors. If the page only loads another batch when scrolling a container, scroll that container rather than the document. A single scroll to the footer may load only one batch; repeat the trigger until the expected content appears or a known terminal condition is reached.
Playwright options and checks
fullPage: truecaptures the full scrollable page; omit it for a viewport image.locator.scrollIntoViewIfNeeded()brings a target into view. It does not assert that all earlier or later records are loaded.locator.waitFor({ state: 'visible' })is suitable when a known target becomes visible. For a count, poll the locator count until it reaches the expected value.- Use a page-specific loading signal where possible, such as waiting for a spinner to become hidden after triggering the final batch.
- For visual comparisons, keep browser, operating system, headless mode, and related rendering conditions stable. Playwright documents that these conditions can affect screenshots.
3. Selenium: use explicit waits and a deliberate scroll
Selenium’s page-load strategies wait on document.readyState, but its documentation cautions that JavaScript applications can keep changing after that state is reached. Use an explicit wait for the content that matters. This Node.js example scrolls a target into view, waits for the expected final item, and then saves a screenshot.
const { Builder, By, until } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
(async () => {
const url = process.env.PAGE_URL || 'https://example.com/catalog';
const options = new chrome.Options().addArguments('--headless=new');
const driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build();
try {
await driver.get(url);
const footer = await driver.findElement(By.css('footer'));
await driver.executeScript('arguments[0].scrollIntoView({block: "end"})', footer);
await driver.wait(until.elementLocated(By.xpath("//*[normalize-space()='Expected final item']")), 15000);
const image = await driver.takeScreenshot();
require('node:fs').writeFileSync('page.png', image, 'base64');
} finally {
await driver.quit();
}
})();
Install with npm install selenium-webdriver and provide a compatible Chrome browser and driver setup for your environment. The XPath and footer selector are illustrative. Selenium’s JavaScript API describes takeScreenshot() as a best-effort capture with an ordered preference starting at the entire page, but do not treat that as identical behavior across every browser driver and language binding. Verify the output dimensions and captured extent in the browser setup you use.
Make Selenium waits content-specific
until.elementLocated above proves only that the target exists in the DOM. If the item must be visible, wait for visibility with a condition built around driver.findElement and isDisplayed(), or poll an application-specific state. If the page appends batches, repeat the scroll and wait cycle, checking that the item count increases or the terminal marker appears. Do not use readyState alone as the application-ready signal.
4. Selenium vs. Playwright: which workflow fits?
| Question | Playwright | Selenium |
|---|---|---|
| How do I request a full-page capture? | The Page API documents page.screenshot({ fullPage: true }). |
The cited JavaScript WebDriver API describes screenshot capture as best effort, preferring the entire page first. Verify exact behavior in your driver and binding. |
| How do I trigger more infinite content? | Playwright guidance recommends scrolling a bottom target into view to force additional list items to load. | Use WebDriver scrolling or JavaScript execution, then wait on a content-specific condition. The cited Selenium material does not prescribe a lazy-list recipe. |
| Does navigation completion mean the content is ready? | No. Verify the target content before capture. | No. Selenium explicitly cautions that JavaScript-driven changes may follow document readiness. |
| Which is universally faster or more reliable? | The consulted sources provide no controlled comparison establishing a universal winner. Measure the workflow in your own browser and environment. | |
Pick the framework your team can run consistently, then test the exact page behavior: document scrolling versus nested containers, how new batches are triggered, what proves completion, and whether your required screenshot extent is supported as expected. For visual regression, stabilize the rendering environment and avoid capturing before asynchronous images and fonts settle.
5. Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Lower rows or cards are missing. | The screenshot extent was set, but the page never loaded those items. | Scroll the relevant trigger into view, repeat for each batch if needed, then wait for the expected item or count. |
| Images appear blank or as placeholders. | The image’s lazy-load trigger did not fire, or the image request has not completed. | Scroll the image into view and wait for its loaded state, for example a nonzero naturalWidth, using an application-specific locator. |
| The capture starts too early despite navigation waiting. | Document readiness occurred before client-side rendering or later content updates. | Wait for the rendered content or a loading indicator to settle; do not treat readyState as application readiness. |
| Only one more batch loads after scrolling to the bottom. | The page requires another scroll cycle for each batch, or the scrollable area is an inner container. | Identify the actual scrolling element and loop until the target count or terminal marker is reached, with a timeout and no-progress exit. |
| The screenshot is cropped or has unexpected dimensions. | Capture extent semantics differ by API, browser, driver, or binding. | Confirm the requested mode and inspect dimensions on the exact runtime. Playwright’s documented full-page option is explicit; Selenium’s cited JavaScript API describes best-effort behavior. |
| Visual baselines differ between runs. | Rendering environment or page state changed, or content is inherently dynamic. | Keep browser and host settings consistent, wait for stable content, and mask or control volatile regions when your comparison workflow allows it. |
6. Reliability, performance, and cost
Capture time depends on how much content must be fetched and rendered, the page’s own behavior, and the browser environment. Scrolling through many batches adds work, while capturing a very tall page can consume more memory and produce a larger file. Avoid an unbounded scroll loop: set a deadline, detect lack of progress, and stop when the expected final item or terminal marker appears.
For repeatability, use the same browser version, viewport, headless configuration, and host environment for comparable captures. Playwright’s visual comparison guidance specifically calls out host OS, browser, settings, hardware, power source, and headless mode as factors that can change rendering. Keep authentication and data state stable too, and wait for the relevant network-driven content rather than an arbitrary short delay.
Self-hosted browser automation has infrastructure and maintenance costs: browser installation, execution time, concurrency limits, and handling browser or driver updates. A screenshot API shifts browser setup out of the calling script and charges according to its service plan. Evaluate both against capture volume, the need to control page interactions, and how much browser infrastructure you want to maintain.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It can return a screenshot or PDF from one GET request; see the API documentation for parameters. The request below captures a page image:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/catalog -o shot.webp
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, along with newsletter popups and chat widgets, before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per 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.
8. FAQ
Does full-page capture automatically load every lazy image?
No. It defines capture extent, not page readiness. Trigger the lazy-load behavior and verify the content first.
Should I use a long fixed sleep?
Use a content-specific condition when available. A delay can help with a known timed effect, but it cannot reliably prove that the intended content loaded.
Can I say Playwright is faster than Selenium for this task?
Not from the cited sources. They do not provide a controlled benchmark; compare both in the browser and environment your team will use.
Does Selenium’s screenshot API always capture the entire page?
The cited JavaScript API describes a best-effort preference order beginning with the entire page. Confirm behavior for the particular driver and language binding in use.
