ScreenshotNeo

BlogHow-to

How to Screenshot Competitor Pages That Require JavaScript to Load

Use Playwright to wait for JavaScript-rendered content, then capture a viewport, full page, or specific element reliably.

By the ScreenshotNeo team4 October 20268 min read

To screenshot a competitor page that requires JavaScript, open it in a real browser with an automation tool such as Playwright, wait for the specific content you need to become visible, and then capture the viewport, full scrollable page, or a selected element. A navigation event such as DOMContentLoaded is only a starting point: asynchronously rendered content may appear later. A locator-based wait ties readiness to the actual heading, panel, or other content you want to capture.

This guide uses Playwright with JavaScript. It also includes Python and cURL options, capture settings, troubleshooting, and a hosted API alternative. The examples use a placeholder URL and selector; adapt them to the page you are allowed to access. Review the target site’s terms and access rules, especially before automating access or redistributing screenshots.

1. Install Playwright and a browser

In a new Node.js project, install Playwright and its Chromium browser:

npm init -y
npm install playwright
npx playwright install chromium

Save the following as screenshot.js. Replace the example URL and heading name with the target page and a distinctive heading that appears when the content you need has rendered.

2. Wait for the rendered content, then capture it

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({
      viewport: { width: 1440, height: 1000 },
      deviceScaleFactor: 1
    });

    await page.goto('https://competitor.example/', {
      waitUntil: 'domcontentloaded',
      timeout: 30000
    });

    // Replace this with a locator that identifies the content you need.
    await page.getByRole('heading', { name: 'Pricing' }).waitFor({
      state: 'visible',
      timeout: 20000
    });

    // Viewport screenshot. Use fullPage: true to capture the scrollable page.
    await page.screenshot({ path: 'competitor.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Run it with node screenshot.js. The sample uses domcontentloaded for navigation and then waits for a visible heading. The heading locator is illustrative: a site may not expose that exact accessible name, so inspect the rendered page and choose a locator that matches its actual content. Playwright documents navigation events, locator waits, and screenshot options in its Page API and locator guide.

3. Choose the right capture mode

What you need Playwright capture Notes
What is visible without scrolling page.screenshot({ path: 'viewport.png' }) The default captures the viewport. Set a consistent viewport for comparisons.
The full scrollable page page.screenshot({ path: 'full.png', fullPage: true }) Captures the full page as a tall image. Very long pages can create large files.
A pricing panel or other component await page.getByRole('main').screenshot({ path: 'main.png' }) Use a locator for the particular element; choose a locator that uniquely identifies it.

For an element capture, replace getByRole('main') with a locator suited to the target. For example:

const pricing = page.locator('[data-testid="pricing-table"]');
await pricing.waitFor({ state: 'visible' });
await pricing.screenshot({ path: 'pricing.png' });

Prefer accessible locators such as roles and labels when they identify the target reliably. A CSS selector can be appropriate when the page provides a stable attribute. Avoid relying on a long chain of layout-dependent selectors that may change when the page is redesigned.

4. Make captures useful for comparison

When comparing screenshots over time or between pages, keep the viewport dimensions, device scale, target readiness condition, and capture mode consistent. Decide how to handle animations and overlays: Playwright screenshot options include animation handling, clipping, masks, and scale. These settings can reduce some visual variation, but live data, rotating content, and third-party widgets can still change between captures. See the Playwright screenshot API for the available options and their behavior.

Use the narrowest readiness signal that represents the content you care about. A visible pricing heading may be enough if the comparison is just the heading; if you need the prices too, wait for a price element or assert its expected text. Locator-based waits and web assertions are designed to check meaningful page state. A fixed sleep can be useful for brief diagnosis, but it is a poor production readiness condition because page timing varies.

Do not treat network silence as proof that a page is ready. Playwright marks networkidle as discouraged for testing and recommends web assertions to assess readiness instead. A page may keep making requests, or may finish its requests before the component you need appears. See the Page API guidance.

5. Python alternative with Playwright

If your automation is in Python, the same approach works with Playwright’s Python package. Install it and the browser:

python -m pip install playwright
python -m playwright install chromium

Save as screenshot.py and run python screenshot.py:

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        try:
            page = await browser.new_page(
                viewport={"width": 1440, "height": 1000},
                device_scale_factor=1,
            )
            await page.goto(
                "https://competitor.example/",
                wait_until="domcontentloaded",
                timeout=30_000,
            )
            await page.get_by_role(
                "heading", name="Pricing"
            ).wait_for(state="visible", timeout=20_000)
            await page.screenshot(path="competitor.png", full_page=True)
        finally:
            await browser.close()

asyncio.run(main())

As with the JavaScript example, replace the example URL and locator. For a specific component, wait for its locator and call that locator’s screenshot(path=...) method instead of capturing the full page.

6. cURL is not a JavaScript browser

A plain HTTP request can download the server’s response, but it does not execute client-side JavaScript or render the page like a browser. That makes cURL alone unsuitable for capturing content that only appears after browser-side scripts run. Use cURL when the site already provides the needed content in its response or when calling a screenshot service that runs a browser for you.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its API runs the capture for you; a single GET request returns an image or PDF. The call below captures the example competitor URL as WebP:

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

See the ScreenshotNeo API documentation for authentication and options. Cookie banners are accepted and removed along with known consent platforms, newsletter popups, and chat widgets before the shot; each of those steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card required.

Troubleshooting

Symptom Likely cause What to do
Blank or incomplete screenshot The browser captured before the desired component rendered. Wait for the specific visible element or text you need, then capture. Do not assume a navigation milestone means all asynchronous content is ready.
Locator wait times out The locator does not match the rendered page, the content is hidden, or the page did not reach the expected state. Inspect the page and verify the locator, accessible name, and visibility. Check whether a consent screen or other overlay is blocking the content, and check that navigation succeeded.
networkidle hangs or finishes too early Ongoing requests can prevent network idle; alternatively, network quiet may not correspond to the content being ready. Use a locator wait or assertion for the actual target content. Playwright discourages network idle as a test readiness signal.
Screenshot omits content below the fold The default capture is only the viewport. Set fullPage: true, or capture the specific element that contains the needed content.
Element screenshot fails or captures the wrong thing The locator is missing, matches multiple elements, or identifies a neighboring component. Inspect the current DOM and choose a stable role, text, label, or attribute. Wait for the intended element to be visible before capturing.
Images or content differ between runs Viewport, animation, live data, overlays, or page state changed. Keep viewport and capture settings consistent, wait for the same target condition, and decide how screenshot animation controls, clipping, or masks should be used. Dynamic site content may still vary.
Navigation fails or takes too long The target may be unavailable, slow, or require a state your script does not have. Check the URL and navigation error, use an appropriate navigation timeout, and confirm the page is accessible under the site’s rules. Avoid masking a failed navigation by taking a screenshot anyway.

Performance, reliability, and cost

  • Readiness: Waiting for one meaningful locator usually gives a clearer condition than waiting for an arbitrary duration or network quiet. Set a finite timeout so failures surface instead of hanging indefinitely.
  • Capture size: Viewport captures are bounded. Full-page screenshots can be much taller and use more memory and storage, especially on long pages; capture only the required region when that meets the task.
  • Repeatability: A fixed viewport and the same locator make runs more comparable, but do not freeze changing prices, personalized content, or other live data.
  • Operational cost: A self-hosted Playwright workflow requires a compatible browser installation and compute to run it. A hosted screenshot API reduces browser setup work and may price by usage; check its current plan details and billing behavior before choosing it.
  • Access and retention: Whether automated access and keeping or sharing a screenshot is permitted depends on the target site and context. Consult its applicable terms and access rules.

FAQ

Can I screenshot a JavaScript page without a browser?

Not reliably when the content exists only after client-side JavaScript runs. A plain HTTP fetch does not execute that JavaScript. Use browser automation or a screenshot API that renders pages in a browser.

Should I wait for a fixed number of seconds?

Use a locator or assertion tied to the content you need. Fixed delays can be flaky because rendering time varies; they are best reserved for diagnosis or a known site-specific delay.

Can Playwright capture just one element?

Yes. Wait for a locator that identifies the element, then call its screenshot method. This is useful for a component such as a pricing table.

Does full-page capture scroll the page?

Playwright captures the full scrollable page as a tall image when full-page mode is enabled. Pages with lazy-loaded content may need additional handling to make that content appear before capture.

Will screenshots be pixel-identical on every run?

Not necessarily. Viewport and screenshot settings help standardize the capture, but changing page data, animations, and third-party content can still affect pixels.

References