ScreenshotNeo

BlogHow-to

How to Take Automated Screenshots of Locally Hosted Websites

Capture local websites with Playwright or Puppeteer: set a viewport, wait for the page to be ready, and save a viewport, full-page, or element screenshot.

By the ScreenshotNeo team4 October 20268 min read

To take automated screenshots of a locally hosted website, run Playwright or Puppeteer in an environment that can reach the local server, set the viewport before navigation, wait for the application state you need, and save a viewport, full-page, or element screenshot. Start your development server first and use its actual URL and port. If the browser runs in a container or remote runner, its localhost refers to that environment, not automatically to your development machine.

1. Start the local site and choose the browser environment

  1. Start the app using your project’s usual development command.
  2. Confirm its URL, such as http://127.0.0.1:3000.
  3. Run the screenshot script on the same machine, or configure network access from the container or remote runner to the machine serving the app.
  4. Choose a viewport and decide whether you need the visible viewport, the full page, or a particular element.

Using 127.0.0.1 instead of localhost can make the destination explicit, but it does not solve a network boundary: inside a container, it still points to that container. Configure a reachable host address or appropriate container networking when the server and browser run in different environments.

2. Capture a local site with Playwright

Install Playwright in your project and install its browser binaries as described in the Playwright installation guide. Save this as screenshot.js and run it with Node.js:

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

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

    await page.goto('http://127.0.0.1:3000');
    // Replace this with a readiness condition specific to your app if needed.
    await page.screenshot({ path: 'homepage.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

This captures the full page. Remove fullPage: true for a viewport screenshot. If navigation resolving does not mean the content you need is ready, wait for a meaningful app-specific condition before capturing:

await page.goto('http://127.0.0.1:3000');
await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
await page.screenshot({ path: 'dashboard.png' });

For example, wait for a heading, a loaded-results indicator, or another stable element that signals the target content is ready. The exact locator depends on your application.

Capture one element or return image bytes

Use a locator screenshot when you only need one component. Use a buffer when the next step uploads or processes the image without saving it first:

const card = page.locator('[data-testid="summary-card"]');
await card.screenshot({ path: 'summary-card.png' });

const imageBytes = await page.screenshot({ fullPage: true });
// Pass imageBytes to your upload, comparison, or processing step.

Playwright documents screenshot controls such as output path, full-page capture, format, scale, background handling, masks, and animation behavior. Check the Page API for the exact options supported by the version you use.

3. Capture a local site with Puppeteer

Install Puppeteer in your Node.js project following the Puppeteer installation guide. Save this as puppeteer-shot.js and run it with Node.js:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1440, height: 900 });
    await page.goto('http://127.0.0.1:3000', {
      waitUntil: 'networkidle2',
    });
    await page.screenshot({ path: 'homepage.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

networkidle2 is one possible navigation condition, not a guarantee that every app is visually ready. Pages with long-lived requests, delayed fonts, animations, or client-rendered content may need a more specific signal. Puppeteer supports page screenshots and element screenshots; an element screenshot attempts to scroll a hidden element into view. See the Puppeteer screenshots guide for the current API.

4. Choose the screenshot shape

Capture Use it for Things to account for
Viewport A screenshot of what is visible at the selected window dimensions. Set the viewport before navigation so responsive layout is selected consistently.
Full page A tall image of the scrollable page. Long pages can produce large files; lazy-loaded images may need scrolling or other app-specific preparation to appear.
Element A component, card, chart, or other selected region. Use a stable selector. Make sure the target exists and has loaded before capture.
Image bytes Uploading, processing, or comparing an image in memory. Choose an output format and pass the returned bytes to the next pipeline step.

5. Make repeated captures consistent

  • Set the same viewport dimensions before every navigation in a desktop/mobile matrix.
  • Wait for the UI state relevant to the capture instead of relying on a fixed sleep.
  • Name files with the route and viewport, for example settings-desktop.png and settings-mobile.png.
  • Reduce visual noise from animations, rotating content, timestamps, or other volatile regions by making test data and rendering deterministic where practical. Playwright screenshot options include animation handling and masks; consult its API documentation for the options available in your version.
  • Keep browser, operating system, rendering settings, and execution environment consistent when comparing screenshots. Playwright notes that these can affect rendered output.

For regression checks, Playwright Test supports await expect(page).toHaveScreenshot(). It creates a reference image on first use and compares later runs against it; the documentation says it captures until two consecutive screenshots match before saving the actual image. Review baseline changes and update them intentionally when a visual change is expected. See Playwright visual comparisons.

6. Run captures across routes and viewport sizes

A small loop can produce a deliberate matrix. Keep the viewport assignment before navigation so each route is laid out for the intended dimensions:

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

(async () => {
  const browser = await chromium.launch();
  try {
    const cases = [
      { name: 'home-desktop', url: 'http://127.0.0.1:3000/', width: 1440, height: 900 },
      { name: 'home-mobile', url: 'http://127.0.0.1:3000/', width: 390, height: 844 },
      { name: 'pricing-desktop', url: 'http://127.0.0.1:3000/pricing', width: 1440, height: 900 },
    ];

    const page = await browser.newPage();
    for (const item of cases) {
      await page.setViewportSize({ width: item.width, height: item.height });
      await page.goto(item.url);
      // Add the app's readiness locator here if navigation is not enough.
      await page.screenshot({ path: `${item.name}.png`, fullPage: true });
    }
  } finally {
    await browser.close();
  }
})();

For larger batches, consider whether one browser process with sequential pages or a limited number of workers fits your machine and app. Excessive parallelism can compete for CPU and memory and make capture timing less predictable.

7. Troubleshooting

Symptom Likely cause Fix
Connection refused or navigation timeout The server is stopped, the URL or port is wrong, or the browser environment cannot reach the host. Confirm the server is listening and test the URL from the same environment that launches the browser. For a container, configure a reachable host address rather than assuming its loopback is your machine.
Screenshot contains a loading state or missing data Navigation completed before client rendering, data loading, or a required interaction finished. Wait for an app-specific locator or state that indicates the content is ready.
Page never reaches network idle The app uses polling, streaming, analytics, or other ongoing network activity. Use a more targeted readiness condition rather than making network quiet a universal requirement.
Mobile screenshot has desktop layout The viewport was set after navigation or not set for that page. Set the intended dimensions before navigating to each page.
Element screenshot fails or is empty The selector matches nothing, the element is hidden, or it has not rendered. Use a stable selector and wait for the target to appear and become visible before capture.
Lazy images are missing in a full-page image The site loads images only as the user scrolls. Scroll through the page or trigger the app’s image-loading behavior before capturing, then verify the result.
Visual snapshots differ between machines Rendering can vary with operating system, browser version, settings, hardware, power source, or headless mode. Use the same environment as the baseline and review changes before updating reference images.
Browser launch fails in CI Browser binaries or required runtime dependencies may not be installed in that environment. Follow the relevant Playwright or Puppeteer installation instructions for the runner and install the browser required by the script.

8. Performance, reliability, and cost

Local browser screenshots have no per-request screenshot API charge, but they consume the CPU, memory, storage, and maintenance time of the machine or CI runner. Reuse a browser process for a batch where appropriate, limit concurrency to what the runner can handle, and avoid capturing full pages when a viewport or element image meets the requirement.

For reliable output, fail the job when navigation or the app-specific readiness condition fails, close the browser in a finally block, and keep screenshot environments stable for visual comparisons. Fixed delays can waste time while still failing to capture the right state. Keep outputs and baselines under deliberate review so expected design changes do not hide regressions.

Or skip the browser setup

For a publicly reachable URL, ScreenshotNeo can return a screenshot from one GET request. A development server bound only to your computer’s loopback address is not reachable by an external service; expose a suitably reachable URL or use local browser automation for that case. See the ScreenshotNeo API documentation for parameters and response details.

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}`);
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers indicate the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free 1,000 screenshots per month, with no card required.

FAQ

Can a hosted screenshot service capture my computer’s localhost?

No, not when the service cannot reach your machine. A remote service needs a reachable URL. Use a local browser process or provide a network-accessible address for the app.

Should I choose Playwright or Puppeteer?

Start with the library that fits your existing project and testing workflow. Both document page screenshot workflows; the research here does not establish a comparative browser-support ranking.

How do I avoid screenshots changing because of animation?

Make volatile content stable where practical, and use the screenshot controls offered by your browser library to handle animations or mask changing regions.

Can I capture a PDF instead of an image?

Browser automation can support print-oriented workflows, but configure and verify those separately from image screenshots. ScreenshotNeo also offers PDF capture for reachable URLs.