ScreenshotNeo

BlogHow-to

How to Run Puppeteer Screenshot Tests on a Low-Cost VPS in India

Set up Puppeteer screenshot tests on an India-based Linux VPS, capture repeatable artifacts, and measure resource use before choosing concurrency.

By the ScreenshotNeo team4 October 20269 min read

Direct answer: provision a Linux VPS in an India region, install a Puppeteer-supported Node.js version and its matching Chrome for Testing browser, verify Linux runtime libraries, then run a small screenshot test that saves artifacts and closes the browser even when a test fails. Start with one worker and measure your own suite’s peak memory, CPU, duration, and failures before increasing concurrency. There is no documented universal VPS size or throughput figure for screenshot tests.

This guide uses Puppeteer with Node.js and a Debian or Ubuntu style Linux host. Puppeteer runs headless by default. Its system-requirements page surfaced for Puppeteer 25.12.0 specifies Node 22.12 or newer; check the current requirements before installing because version requirements change. Puppeteer system requirements

1. Choose a VPS you can measure

A VPS provides virtualized compute. DigitalOcean calls its virtual machines Droplets and lists Bangalore as a datacenter region. Its pricing page currently lists these examples:

Example plan Listed resources Listed price
Basic 512 MiB RAM, 1 vCPU, 10 GiB SSD, 500 GiB transfer $4/month
Basic 1 GiB RAM, 1 vCPU, 25 GiB SSD, 1,000 GiB transfer $6/month

These are provider-listed specifications, not recommendations for a particular test suite. The smaller instance may not have enough memory for your browser, test runner, and page workload. Confirm current prices, OS images, Bangalore availability, billing terms, taxes, and any account-specific limits before ordering. DigitalOcean says per-second billing with a 60-second or $0.01 minimum became effective January 1, 2026; recheck its current terms. DigitalOcean Droplet pricing

Choose a region based on where your team and test targets are and any operational requirements. Bangalore availability alone does not prove lower latency to a particular website. To choose between plans, run the same test set and browser version on each candidate and compare completion time, failed navigations, peak memory, and CPU use.

2. Install Node.js, Puppeteer, and Chrome

  1. Create a project directory and enter it.
  2. Install a Node.js version that satisfies the current Puppeteer system requirements. For Puppeteer 25.12.0, the surfaced minimum is Node 22.12+.
  3. Install the project dependency. The puppeteer package normally downloads a compatible Chrome for Testing browser.
mkdir -p ~/screenshot-checks
cd ~/screenshot-checks
npm init -y
npm install puppeteer

The Puppeteer installation guide gives an approximate Linux Chrome download size of 282 MB. Allow additional disk space for Node dependencies, temporary browser data, and retained screenshots. If your package manager blocks install scripts and the browser was not downloaded, install it explicitly:

npx puppeteer browsers install

puppeteer-core is a separate option for users who want to manage the browser themselves. It does not download Chrome; configure a compatible browser executable path or channel and keep browser upgrades aligned with the library. For a straightforward VPS setup, the full puppeteer package avoids that extra browser-management step. Puppeteer installation guide

3. Check Linux browser dependencies

Chrome may download correctly but fail to start if shared libraries are absent. Puppeteer’s troubleshooting guide suggests checking the browser binary with ldd. Locate the installed Chrome binary first if it is not on your PATH, then run:

ldd /path/to/chrome | grep not

Install the missing runtime packages using the package manager and package names for your distribution, then retry. Dependency names can vary by distribution and browser revision, so use Puppeteer’s current Linux troubleshooting instructions rather than copying a stale package list. Debian and Ubuntu x64 and arm64 are included in the surfaced system requirements. Puppeteer Linux troubleshooting

Do not add --no-sandbox as a routine launch fix. If a particular hosted environment requires a sandbox change, treat it as a security-sensitive, environment-specific decision and consult current security guidance for that environment.

4. Write a minimal screenshot test

This runnable ES module navigates to a URL, saves a PNG, and closes Chrome in a finally block. The output directory is created before capture.

import puppeteer from 'puppeteer';
import { mkdir } from 'node:fs/promises';

const target = process.env.TARGET_URL ?? 'https://example.com';
const output = process.env.SCREENSHOT_PATH ?? 'artifacts/example.png';

await mkdir('artifacts', { recursive: true });
const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
  const response = await page.goto(target, {
    waitUntil: 'networkidle2',
    timeout: 45_000,
  });
  if (!response) throw new Error(`Navigation returned no response: ${target}`);
  if (!response.ok()) {
    throw new Error(`Navigation returned HTTP ${response.status()} for ${target}`);
  }
  await page.screenshot({ path: output, fullPage: true });
  console.log(`Saved ${output} (${response.status()}): ${target}`);
} finally {
  await browser.close();
}

Save it as capture.mjs and run node capture.mjs. To change the target or output path, run TARGET_URL=https://your-site.example SCREENSHOT_PATH=artifacts/home.png node capture.mjs. The official screenshot workflow likewise navigates, calls Page.screenshot() with a path, and closes the browser. Puppeteer screenshot guide

Choose navigation and capture settings deliberately

  • waitUntil: Puppeteer’s example uses networkidle2. It can be unsuitable for apps with long-running requests or delayed rendering. Choose a lifecycle condition that matches the page, then wait for an application-specific selector or known readiness signal when needed.
  • timeout: set a finite navigation timeout so a broken target does not occupy a worker indefinitely. Choose the value from your target’s expected behavior and report timeouts as failures.
  • fullPage: use true for the full document, or omit it for the current viewport. Long pages can create larger images and use more memory.
  • setViewport: fix width, height, and device scale factor across runs so visual comparisons use consistent dimensions.
  • HTTP status: decide whether non-2xx responses should fail your test. The example treats them as failures instead of saving an error page as if it were a successful capture.

For repeatable visual checks, also pin the Puppeteer dependency in your lockfile, use the same browser revision, viewport, target data, and wait strategy, and avoid changing fonts or other host dependencies between comparison runs.

5. Run tests and retain useful artifacts

Put the capture script into your existing test runner or call it from a shell script. Keep each artifact path unique when running multiple cases; otherwise parallel jobs can overwrite one another. A simple sequential run can be driven by a list of URLs:

while IFS= read -r url; do
  [ -z "$url" ] && continue
  name=$(printf '%s' "$url" | sed 's#https\?://##; s#[^A-Za-z0-9._-]#_#g')
  TARGET_URL="$url" SCREENSHOT_PATH="artifacts/${name}.png" node capture.mjs || exit 1
done < urls.txt

Record enough information to reproduce a failure:

  • Target URL and test case name.
  • Puppeteer and browser versions.
  • Viewport and capture settings.
  • Navigation status, error or timeout, and elapsed time.
  • Screenshot artifact path and retention policy.
  • Server memory and CPU during the run, especially peak use at the intended concurrency.

Start with one browser process. Increase parallelism gradually while running the same suite and watch for memory pressure, slowdowns, navigation failures, and out-of-memory process exits. No source-backed figure establishes a reliable number of concurrent screenshot jobs for a given VPS size.

6. Tune reliability, performance, and cost

Reliability

  • Close pages and browsers in cleanup paths even when navigation or assertions fail.
  • Use finite timeouts and make retries limited. Repeatedly retrying a page that is consistently unavailable wastes compute and can hide an application issue.
  • Keep browser, Node, and package versions reproducible with a lockfile and a recorded deployment environment.
  • Ensure artifact writes have unique names, sufficient disk space, and an explicit retention policy.
  • Separate infrastructure failures, navigation failures, HTTP errors, and visual mismatches in logs so they can be diagnosed independently.

Performance

Measure your own pages. Network-heavy sites, long documents, image loading, fonts, and the chosen readiness condition all affect run time and resource demand. Compare concurrency only with equivalent pages, viewport, browser, test count, and wait strategy. Raising concurrency can increase throughput, but it also increases simultaneous browser and page resource use; determine the useful limit empirically rather than assuming a vCPU or RAM specification guarantees it.

Cost

Include the instance price, storage, transfer, artifact retention, and time spent maintaining browser dependencies in the cost decision. The approximately 282 MB browser download is an installation download, not the total disk requirement. A lower monthly price does not establish capacity, and an India region does not establish latency for your test targets. Recheck live provider terms before purchase.

Or skip the browser setup

If you need screenshots rather than a self-managed browser runtime, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its capture flow accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off.

For a quick API call, create an account key and run this cURL example. 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://example.com \
  -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

Use Node 18+ with the built-in fetch and Bun for the final file-writing line, or replace that line in Node with:

import { writeFile } from 'node:fs/promises';
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo bills only clean shots: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The API also supports full-page and selector capture, viewport and device options, custom waits, headers and cookies, custom CSS and JavaScript, request blocking, caching, async jobs, bulk capture, and more. There is no VPS browser installation to maintain for each capture.

The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

Troubleshooting

Symptom Likely cause What to do
Browser executable missing after installation Package-manager install scripts were blocked, or the browser was not installed. Run npx puppeteer browsers install and confirm the browser download completes.
Chrome fails immediately with missing shared library errors Linux runtime dependency is absent. Run ldd /path/to/chrome | grep not; install the missing distro-specific packages and retry.
Navigation times out The target is slow, unavailable, or keeps network activity open beyond the chosen wait condition. Check the target from the VPS, choose a wait condition appropriate to the app, wait for an app-ready selector if needed, and keep a finite timeout.
Screenshot is blank or incomplete The page may not have rendered its content before capture, or the target may serve a challenge or empty response. Inspect status and page content, wait for the relevant selector or render signal, and save diagnostic logs alongside the image.
Process exits or the VM becomes unresponsive during parallel runs Concurrency may exceed the resources available to this workload. Return to one worker, measure memory and CPU, then raise concurrency in small increments or choose a larger instance.
Screenshot cannot be written Output directory is missing, permissions are wrong, or disk is full. Create the directory, check ownership and free space, and use a unique artifact path.
Unexpected HTTP error page is saved Navigation completed but returned a non-success HTTP status. Check the response status and fail or classify the test explicitly, as the example does.

Frequently asked questions

Can I run screenshot tests without a graphical desktop?

Yes. Puppeteer runs headless by default, so the VPS does not need a desktop session for the standard workflow.

Should I use puppeteer-core?

Use it when you deliberately manage the browser binary yourself. It does not download Chrome, so you must provide a compatible executable path or channel.

Is a $4 VPS enough?

The listed price and specifications do not prove that it can run your suite. Measure your target pages and intended concurrency on the candidate host.

Does choosing Bangalore make screenshots faster?

Not necessarily. The region is an available location; speed depends on the test target and network path. Measure against the sites you capture.

How much storage should I reserve?

Plan for the browser download, project dependencies, temporary files, and retained artifacts. The browser download alone is approximately 282 MB according to Puppeteer’s installation guide.