ScreenshotNeo

BlogHow-to

How to screenshot Indian government tender pages in Node.js with Playwright

Capture Indian government tender pages in Node.js with Playwright, including full-page settings, CAPTCHA limits, troubleshooting, and a repeatable record.

By the ScreenshotNeo team4 October 20269 min read

Use Playwright’s Node.js API to open the official tender page and save a full-page image with page.screenshot({ path: 'tender.png', fullPage: true }). This captures the page’s full scrollable height; omit fullPage to capture only the visible viewport. The example below uses a placeholder URL. Replace it with the actual tender URL, and expect that access and rendering can differ between portals.

1. Install Playwright and capture a tender page

In a new Node.js project, install Playwright and its Chromium browser. The commands below install the package and the browser binary it needs.

npm init -y
npm install playwright
npx playwright install chromium

Save this as screenshot-tender.js. Set TENDER_URL to the official page you are permitted to access.

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

async function main() {
  const url = process.env.TENDER_URL;
  if (!url) {
    throw new Error('Set TENDER_URL to the official tender page URL');
  }

  const browser = await chromium.launch({ headless: true });
  try {
    const context = await browser.newContext({
      viewport: { width: 1440, height: 1000 },
      deviceScaleFactor: 1,
      locale: 'en-IN',
      timezoneId: 'Asia/Kolkata',
    });
    const page = await context.newPage();

    const response = await page.goto(url, {
      waitUntil: 'domcontentloaded',
      timeout: 60000,
    });

    if (response) {
      console.log(`HTTP ${response.status()} ${response.url()}`);
    }
    await page.screenshot({ path: 'tender.png', fullPage: true });
    console.log(`Saved tender.png for ${page.url()}`);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run it with the tender URL in the environment:

TENDER_URL='https://example.gov.in/tender-page' node screenshot-tender.js

The sample sets a fixed viewport, device scale, locale, and timezone to make repeated captures more consistent. These settings do not guarantee identical output across different operating systems, browser versions, hardware, or headless modes.

2. Choose viewport, format, and image scale

A normal screenshot records only what is visible in the viewport. Set fullPage: true when you need the complete scrollable document. For a specific region, use the screenshot API’s clipping option; for a particular element, locate it and call locator.screenshot().

Need Setting or method Trade-off
Visible portion only page.screenshot({ path: 'viewport.png' }) Compact capture, but content below the fold is absent.
Entire scrollable page page.screenshot({ path: 'full.png', fullPage: true }) May create a tall, large image; inspect that all content rendered before capture.
One element await page.locator('CSS_SELECTOR').screenshot({ path: 'element.png' }) Useful for a notice or results panel, if the element exists and is visible.
Region of the viewport clip: { x, y, width, height } Coordinates are relative to the page viewport; content outside the rectangle is excluded.
One image pixel per CSS pixel scale: 'css' Usually smaller and easier to archive or compare.
Device-pixel detail scale: 'device' Can produce larger images, depending on device scale factor.

Playwright screenshot options also include image format selection, JPEG quality, and masking locators. PNG is a lossless default suitable for text-heavy records. JPEG quality applies to JPEG output; it does not make PNG smaller. Masking can cover dynamic or sensitive regions in a visual test, but do not obscure information in a record intended to represent the notice faithfully. Consult the Playwright Page screenshot API for the current option definitions.

await page.screenshot({
  path: 'tender.png',
  type: 'png',
  fullPage: true,
  scale: 'css',
});

3. Handle loading, dynamic content, and CAPTCHA safely

Government tender portals do not all behave alike. The Government of India eProcurement advanced-search page examined for this guide asks users to provide a CAPTCHA to list active tenders, and its login page displays a JavaScript requirement. Those observations apply to those pages, not automatically to every central, state, or department portal. Check the specific portal and follow its access rules. See the official eProcurement portal and its login page.

If a CAPTCHA or other access challenge blocks the page, stop for legitimate user interaction. Do not automate solving or bypassing the challenge. You can arrange a user-assisted workflow that pauses while a permitted user completes the step, then continue capturing the page if the portal allows it.

For pages whose content loads after initial navigation, wait for a meaningful selector rather than relying on a short fixed delay. Replace .tender-details with a selector actually present on the page:

await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.locator('.tender-details').waitFor({ state: 'visible', timeout: 30000 });
await page.screenshot({ path: 'tender.png', fullPage: true });

If the portal renders content through client-side JavaScript, use an appropriate wait such as a visible content selector or waitUntil: 'networkidle' when that condition is suitable. Network idle can be unreliable on pages with continuing background requests. A long timeout does not overcome a CAPTCHA, login requirement, access denial, or broken page.

4. Keep captures repeatable and useful as records

  1. Use the same Playwright and browser versions, operating system, viewport, device scale factor, locale, timezone, and headless setting for captures you intend to compare.
  2. Wait for the tender content that matters, then capture the viewport or full page deliberately.
  3. Keep the image with a small metadata record: official page URL, capture time in UTC, Playwright/browser version, viewport, and whether full-page capture was used.
  4. For important dates, values, or deadlines, check the original tender notification and the publishing department’s source.

The Central Public Procurement Portal (CPPP) says the service is provided by the Department of Expenditure in association with NIC, while tender documents are owned, published, and maintained by the respective government departments. Its disclaimer assigns responsibility for accuracy, authenticity, and validity to those publishing authorities and advises users with doubts to consult the original tender notification and contact the department or organisation. A screenshot is a visual record; it does not independently establish that details are current or authoritative. Read the CPPP disclaimer.

5. Other runnable capture clients

The primary workflow here is Node.js with Playwright. For automation from another environment, Playwright provides language APIs too; these alternatives use the same principle: navigate to the actual permitted tender URL and take a full-page screenshot. They are included for teams that need a shell or Python entry point.

cURL

cURL does not render a website or create a screenshot. Use it to invoke a script or service you control; it cannot replace the browser capture above. There is no universal cURL command for taking a screenshot of a tender page.

Python with Playwright

import asyncio
import os
from playwright.async_api import async_playwright

async def main():
    url = os.environ.get("TENDER_URL")
    if not url:
        raise RuntimeError("Set TENDER_URL to the official tender page URL")
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        try:
            page = await browser.new_page(
                viewport={"width": 1440, "height": 1000},
                device_scale_factor=1,
                locale="en-IN",
                timezone_id="Asia/Kolkata",
            )
            response = await page.goto(url, wait_until="domcontentloaded", timeout=60000)
            if response:
                print(f"HTTP {response.status} {response.url}")
            await page.screenshot(path="tender.png", full_page=True)
        finally:
            await browser.close()

asyncio.run(main())

Install the Python package and browser, then run with TENDER_URL set. The Node.js sample is the direct answer for the title; this Python variant is only for Python-based workflows.

python -m pip install playwright
python -m playwright install chromium
TENDER_URL='https://example.gov.in/tender-page' python screenshot_tender.py

Node.js with Playwright

Use the complete script in section 1. It is the recommended implementation for this title and saves tender.png locally.

6. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. Its API returns an image or PDF from one GET request, so it can be useful when you do not want to install and manage a browser. Use it only where you are permitted to access the page; it cannot turn a CAPTCHA or access challenge into authorized access.

See the ScreenshotNeo API documentation. Replace the example URL with the actual public tender page URL:

cURL

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

Python

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

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.gov.in/tender-page' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned HTTP ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', bytes));

ScreenshotNeo accepts cookie or consent banners before capture and removes 60+ known consent platforms, newsletter popups, and chat widgets; each of those steps 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 provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

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

7. Troubleshooting

Symptom Likely cause What to do
Executable doesn't exist or browser launch fails The Playwright package is installed but its browser binary is missing. Run npx playwright install chromium in the project environment and check that the runtime can launch Chromium.
Navigation times out The portal is slow, continuing network activity, unreachable, or waiting on an access challenge. Check the URL and response, use a longer navigation timeout if appropriate, and wait for the relevant content selector. Handle any challenge through legitimate interaction.
Screenshot is blank or incomplete The page may not have rendered its content when capture began, or the site may have returned an error or challenge page. Inspect the current page URL, status, and visible page content; wait for the tender details selector before capturing.
Full-page capture misses images or lower content Content may load lazily as it enters the viewport, or may require interaction. Confirm the page’s required user interactions and portal policy. Scroll in a controlled way and wait for content to appear before taking the full-page capture.
Output differs between runs or machines Browser version, OS, viewport, device scale, font rendering, hardware, or headless mode changed. Keep those settings and the browser environment consistent; record them with the image.
Portal asks for CAPTCHA or JavaScript The portal is applying an access or browser requirement. Do not bypass it. Use the portal’s legitimate user flow and capture only when permitted.
Image file is unexpectedly large A full-page image or device-pixel scale can substantially increase pixel dimensions. Use scale: 'css' where appropriate, choose a narrower viewport if it remains readable, or capture a relevant element/region instead of the entire page.

8. Performance, reliability, and cost

For occasional captures, a single browser launch is straightforward. For repeated jobs, reuse a browser process and create a fresh context or page per capture as appropriate; close pages, contexts, and the browser when finished. Limit concurrency to what the host can support, since each browser page consumes memory and CPU. Set navigation and selector timeouts so a stalled job does not run indefinitely, and log failures alongside the URL and timestamp.

Full-page images use more memory and storage as page height and scale increase. Prefer CSS-pixel scale for compact records; choose device scale when finer detail is necessary. Stable browser versions and capture settings improve visual consistency, but Playwright notes rendering can vary with the operating system, browser version, settings, hardware, power conditions, and headless mode. No screenshot should be treated as a substitute for checking the authoritative notice.

Playwright is an open-source browser automation library, but running captures still has infrastructure costs: compute, memory, storage, and maintenance of browsers. A managed API can avoid browser setup, though usage and plan limits matter. ScreenshotNeo’s stated plans are Free: 1,000 shots/month with no card; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free. Its billing headers distinguish billed captures from bot checks, blank pages, timeouts, failed loads, and cache hits, which cost nothing. Check the current documentation for API usage details.

9. Frequently asked questions

Can a screenshot prove a tender is still open?

No. It records what the page displayed at capture time. Confirm current status and important details against the original notice and the publishing department.

Should I capture a search result or the tender detail page?

Capture the page that contains the information you need to preserve. Search interfaces can require filters or a CAPTCHA, while the individual notice may be available through a separate official URL.

Can I use screenshots for visual regression checks?

Yes, if you keep the rendering environment and capture settings consistent and account for content that changes over time. Compare only the regions and page state that matter to your check.